[v2,1/1] media: i2c: max96717: add test pattern ctrl

Message ID 20240627151806.3999400-2-tomm.merciai@gmail.com (mailing list archive)
State Accepted
Delegated to: Sakari Ailus
Headers
Series [v2,1/1] media: i2c: max96717: add test pattern ctrl |

Commit Message

Tommaso Merciai June 27, 2024, 3:18 p.m. UTC
  Add v4l2 test pattern control.

Signed-off-by: Tommaso Merciai <tomm.merciai@gmail.com>
---
Changes since v1:
 - Rename and move pattern regs under VTX section as suggested by JMassot
 - Fix VTX regs order
 - Add comment saying that the deserializer should manage the link in
   pixel mode as suggested by JMassot

 drivers/media/i2c/max96717.c | 213 ++++++++++++++++++++++++++++++++---
 1 file changed, 197 insertions(+), 16 deletions(-)
  

Comments

Julien Massot June 27, 2024, 4:10 p.m. UTC | #1
Hi Tommaso,

On 6/27/24 5:18 PM, Tommaso Merciai wrote:
> Add v4l2 test pattern control.
> 
> Signed-off-by: Tommaso Merciai <tomm.merciai@gmail.com>

Thanks for your patch,
Just tested again on MAX96717F.

Reviewed-by: Julien Massot <julien.massot@collabora.com>
Tested-by: Julien Massot <julien.massot@collabora.com>

> ---
> Changes since v1:
>   - Rename and move pattern regs under VTX section as suggested by JMassot
>   - Fix VTX regs order
>   - Add comment saying that the deserializer should manage the link in
>     pixel mode as suggested by JMassot
> 
>   drivers/media/i2c/max96717.c | 213 ++++++++++++++++++++++++++++++++---
>   1 file changed, 197 insertions(+), 16 deletions(-)
> 
> diff --git a/drivers/media/i2c/max96717.c b/drivers/media/i2c/max96717.c
> index 949306485873..859a439b64d9 100644
> --- a/drivers/media/i2c/max96717.c
> +++ b/drivers/media/i2c/max96717.c
> @@ -16,6 +16,7 @@
>   #include <linux/regmap.h>
>   
>   #include <media/v4l2-cci.h>
> +#include <media/v4l2-ctrls.h>
>   #include <media/v4l2-fwnode.h>
>   #include <media/v4l2-subdev.h>
>   
> @@ -38,9 +39,35 @@
>   #define MAX96717_DEV_REV_MASK GENMASK(3, 0)
>   
>   /* VID_TX Z */
> +#define MAX96717_VIDEO_TX0 CCI_REG8(0x110)
> +#define MAX96717_VIDEO_AUTO_BPP BIT(3)
>   #define MAX96717_VIDEO_TX2 CCI_REG8(0x112)
>   #define MAX96717_VIDEO_PCLKDET BIT(7)
>   
> +/* VTX_Z */
> +#define MAX96717_VTX0                  CCI_REG8(0x24e)
> +#define MAX96717_VTX1                  CCI_REG8(0x24f)
> +#define MAX96717_PATTERN_CLK_FREQ      GENMASK(3, 1)
> +#define MAX96717_VTX_VS_DLY            CCI_REG24(0x250)
> +#define MAX96717_VTX_VS_HIGH           CCI_REG24(0x253)
> +#define MAX96717_VTX_VS_LOW            CCI_REG24(0x256)
> +#define MAX96717_VTX_V2H               CCI_REG24(0x259)
> +#define MAX96717_VTX_HS_HIGH           CCI_REG16(0x25c)
> +#define MAX96717_VTX_HS_LOW            CCI_REG16(0x25e)
> +#define MAX96717_VTX_HS_CNT            CCI_REG16(0x260)
> +#define MAX96717_VTX_V2D               CCI_REG24(0x262)
> +#define MAX96717_VTX_DE_HIGH           CCI_REG16(0x265)
> +#define MAX96717_VTX_DE_LOW            CCI_REG16(0x267)
> +#define MAX96717_VTX_DE_CNT            CCI_REG16(0x269)
> +#define MAX96717_VTX29                 CCI_REG8(0x26b)
> +#define MAX96717_VTX_MODE              GENMASK(1, 0)
> +#define MAX96717_VTX_GRAD_INC          CCI_REG8(0x26c)
> +#define MAX96717_VTX_CHKB_COLOR_A      CCI_REG24(0x26d)
> +#define MAX96717_VTX_CHKB_COLOR_B      CCI_REG24(0x270)
> +#define MAX96717_VTX_CHKB_RPT_CNT_A    CCI_REG8(0x273)
> +#define MAX96717_VTX_CHKB_RPT_CNT_B    CCI_REG8(0x274)
> +#define MAX96717_VTX_CHKB_ALT          CCI_REG8(0x275)
> +
>   /* GPIO */
>   #define MAX96717_NUM_GPIO         11
>   #define MAX96717_GPIO_REG_A(gpio) CCI_REG8(0x2be + (gpio) * 3)
> @@ -82,6 +109,12 @@
>   /* MISC */
>   #define PIO_SLEW_1 CCI_REG8(0x570)
>   
> +enum max96717_vpg_mode {
> +	MAX96717_VPG_DISABLED = 0,
> +	MAX96717_VPG_CHECKERBOARD = 1,
> +	MAX96717_VPG_GRADIENT = 2,
> +};
> +
>   struct max96717_priv {
>   	struct i2c_client		  *client;
>   	struct regmap			  *regmap;
> @@ -89,6 +122,7 @@ struct max96717_priv {
>   	struct v4l2_mbus_config_mipi_csi2 mipi_csi2;
>   	struct v4l2_subdev                sd;
>   	struct media_pad                  pads[MAX96717_PORTS];
> +	struct v4l2_ctrl_handler          ctrl_handler;
>   	struct v4l2_async_notifier        notifier;
>   	struct v4l2_subdev                *source_sd;
>   	u16                               source_sd_pad;
> @@ -96,6 +130,7 @@ struct max96717_priv {
>   	u8                                pll_predef_index;
>   	struct clk_hw                     clk_hw;
>   	struct gpio_chip                  gpio_chip;
> +	enum max96717_vpg_mode            pattern;
>   };
>   
>   static inline struct max96717_priv *sd_to_max96717(struct v4l2_subdev *sd)
> @@ -131,6 +166,118 @@ static inline int max96717_start_csi(struct max96717_priv *priv, bool start)
>   			       start ? MAX96717_START_PORT_B : 0, NULL);
>   }
>   
> +static int max96717_apply_patgen_timing(struct max96717_priv *priv,
> +					struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_mbus_framefmt *fmt =
> +		v4l2_subdev_state_get_format(state, MAX96717_PAD_SOURCE);
> +	const u32 h_active = fmt->width;
> +	const u32 h_fp = 88;
> +	const u32 h_sw = 44;
> +	const u32 h_bp = 148;
> +	u32 h_tot;
> +	const u32 v_active = fmt->height;
> +	const u32 v_fp = 4;
> +	const u32 v_sw = 5;
> +	const u32 v_bp = 36;
> +	u32 v_tot;
> +	int ret = 0;
> +
> +	h_tot = h_active + h_fp + h_sw + h_bp;
> +	v_tot = v_active + v_fp + v_sw + v_bp;
> +
> +	/* 75 Mhz pixel clock */
> +	cci_update_bits(priv->regmap, MAX96717_VTX1,
> +			MAX96717_PATTERN_CLK_FREQ, 0xa, &ret);
> +
> +	dev_info(&priv->client->dev, "height: %d width: %d\n", fmt->height,
> +		 fmt->width);
> +
> +	cci_write(priv->regmap, MAX96717_VTX_VS_DLY, 0, &ret);
> +	cci_write(priv->regmap, MAX96717_VTX_VS_HIGH, v_sw * h_tot, &ret);
> +	cci_write(priv->regmap, MAX96717_VTX_VS_LOW,
> +		  (v_active + v_fp + v_bp) * h_tot, &ret);
> +	cci_write(priv->regmap, MAX96717_VTX_HS_HIGH, h_sw, &ret);
> +	cci_write(priv->regmap, MAX96717_VTX_HS_LOW, h_active + h_fp + h_bp,
> +		  &ret);
> +	cci_write(priv->regmap, MAX96717_VTX_V2D,
> +		  h_tot * (v_sw + v_bp) + (h_sw + h_bp), &ret);
> +	cci_write(priv->regmap, MAX96717_VTX_HS_CNT, v_tot, &ret);
> +	cci_write(priv->regmap, MAX96717_VTX_DE_HIGH, h_active, &ret);
> +	cci_write(priv->regmap, MAX96717_VTX_DE_LOW, h_fp + h_sw + h_bp,
> +		  &ret);
> +	cci_write(priv->regmap, MAX96717_VTX_DE_CNT, v_active, &ret);
> +	/* B G R */
> +	cci_write(priv->regmap, MAX96717_VTX_CHKB_COLOR_A, 0xfecc00, &ret);
> +	/* B G R */
> +	cci_write(priv->regmap, MAX96717_VTX_CHKB_COLOR_B, 0x006aa7, &ret);
> +	cci_write(priv->regmap, MAX96717_VTX_CHKB_RPT_CNT_A, 0x3c, &ret);
> +	cci_write(priv->regmap, MAX96717_VTX_CHKB_RPT_CNT_B, 0x3c, &ret);
> +	cci_write(priv->regmap, MAX96717_VTX_CHKB_ALT, 0x3c, &ret);
> +	cci_write(priv->regmap, MAX96717_VTX_GRAD_INC, 0x10, &ret);
> +
> +	return ret;
> +}
> +
> +static int max96717_apply_patgen(struct max96717_priv *priv,
> +				 struct v4l2_subdev_state *state)
> +{
> +	unsigned int val;
> +	int ret = 0;
> +
> +	if (priv->pattern)
> +		ret = max96717_apply_patgen_timing(priv, state);
> +
> +	cci_write(priv->regmap, MAX96717_VTX0, priv->pattern ? 0xfb : 0,
> +		  &ret);
> +
> +	val = FIELD_PREP(MAX96717_VTX_MODE, priv->pattern);
> +	cci_update_bits(priv->regmap, MAX96717_VTX29, MAX96717_VTX_MODE,
> +			val, &ret);
> +	return ret;
> +}
> +
> +static int max96717_s_ctrl(struct v4l2_ctrl *ctrl)
> +{
> +	struct max96717_priv *priv =
> +		container_of(ctrl->handler, struct max96717_priv, ctrl_handler);
> +	int ret;
> +
> +	switch (ctrl->id) {
> +	case V4L2_CID_TEST_PATTERN:
> +		if (priv->enabled_source_streams)
> +			return -EBUSY;
> +		priv->pattern = ctrl->val;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	/* Use bpp from bpp register */
> +	ret = cci_update_bits(priv->regmap, MAX96717_VIDEO_TX0,
> +			      MAX96717_VIDEO_AUTO_BPP,
> +			      priv->pattern ? 0 : MAX96717_VIDEO_AUTO_BPP,
> +			      NULL);
> +
> +	/*
> +	 * Pattern generator doesn't work with tunnel mode.
> +	 * Needs RGB color format and deserializer tunnel mode must be disabled.
> +	 */
> +	return cci_update_bits(priv->regmap, MAX96717_MIPI_RX_EXT11,
> +			       MAX96717_TUN_MODE,
> +			       priv->pattern ? 0 : MAX96717_TUN_MODE, &ret);
> +}
> +
> +static const char * const max96717_test_pattern[] = {
> +	"Disabled",
> +	"Checkerboard",
> +	"Gradient"
> +};
> +
> +static const struct v4l2_ctrl_ops max96717_ctrl_ops = {
> +	.s_ctrl = max96717_s_ctrl,
> +};
> +
>   static int max96717_gpiochip_get(struct gpio_chip *gpiochip,
>   				 unsigned int offset)
>   {
> @@ -352,20 +499,28 @@ static int max96717_enable_streams(struct v4l2_subdev *sd,
>   	u64 sink_streams;
>   	int ret;
>   
> -	sink_streams = v4l2_subdev_state_xlate_streams(state,
> -						       MAX96717_PAD_SOURCE,
> -						       MAX96717_PAD_SINK,
> -						       &streams_mask);
> -
>   	if (!priv->enabled_source_streams)
>   		max96717_start_csi(priv, true);
>   
> -	ret = v4l2_subdev_enable_streams(priv->source_sd, priv->source_sd_pad,
> -					 sink_streams);
> -	if (ret) {
> -		dev_err(dev, "Fail to start streams:%llu on remote subdev\n",
> -			sink_streams);
> +	ret = max96717_apply_patgen(priv, state);
> +	if (ret)
>   		goto stop_csi;
> +
> +	if (!priv->pattern) {
> +		sink_streams =
> +			v4l2_subdev_state_xlate_streams(state,
> +							MAX96717_PAD_SOURCE,
> +							MAX96717_PAD_SINK,
> +							&streams_mask);
> +
> +		ret = v4l2_subdev_enable_streams(priv->source_sd,
> +						 priv->source_sd_pad,
> +						 sink_streams);
> +		if (ret) {
> +			dev_err(dev, "Fail to start streams:%llu on remote subdev\n",
> +				sink_streams);
> +			goto stop_csi;
> +		}
>   	}
>   
>   	priv->enabled_source_streams |= streams_mask;
> @@ -394,13 +549,23 @@ static int max96717_disable_streams(struct v4l2_subdev *sd,
>   	if (!priv->enabled_source_streams)
>   		max96717_start_csi(priv, false);
>   
> -	sink_streams = v4l2_subdev_state_xlate_streams(state,
> -						       MAX96717_PAD_SOURCE,
> -						       MAX96717_PAD_SINK,
> -						       &streams_mask);
> +	if (!priv->pattern) {
> +		int ret;
> +
> +		sink_streams =
> +			v4l2_subdev_state_xlate_streams(state,
> +							MAX96717_PAD_SOURCE,
> +							MAX96717_PAD_SINK,
> +							&streams_mask);
>   
> -	return v4l2_subdev_disable_streams(priv->source_sd, priv->source_sd_pad,
> -					   sink_streams);
> +		ret = v4l2_subdev_disable_streams(priv->source_sd,
> +						  priv->source_sd_pad,
> +						  sink_streams);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
>   }
>   
>   static const struct v4l2_subdev_pad_ops max96717_pad_ops = {
> @@ -513,6 +678,19 @@ static int max96717_subdev_init(struct max96717_priv *priv)
>   	v4l2_i2c_subdev_init(&priv->sd, priv->client, &max96717_subdev_ops);
>   	priv->sd.internal_ops = &max96717_internal_ops;
>   
> +	v4l2_ctrl_handler_init(&priv->ctrl_handler, 1);
> +	priv->sd.ctrl_handler = &priv->ctrl_handler;
> +
> +	v4l2_ctrl_new_std_menu_items(&priv->ctrl_handler,
> +				     &max96717_ctrl_ops,
> +				     V4L2_CID_TEST_PATTERN,
> +				     ARRAY_SIZE(max96717_test_pattern) - 1,
> +				     0, 0, max96717_test_pattern);
> +	if (priv->ctrl_handler.error) {
> +		ret = priv->ctrl_handler.error;
> +		goto err_free_ctrl;
> +	}
> +
>   	priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
>   	priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
>   	priv->sd.entity.ops = &max96717_entity_ops;
> @@ -552,6 +730,8 @@ static int max96717_subdev_init(struct max96717_priv *priv)
>   	v4l2_subdev_cleanup(&priv->sd);
>   err_entity_cleanup:
>   	media_entity_cleanup(&priv->sd.entity);
> +err_free_ctrl:
> +	v4l2_ctrl_handler_free(&priv->ctrl_handler);
>   
>   	return ret;
>   }
> @@ -563,6 +743,7 @@ static void max96717_subdev_uninit(struct max96717_priv *priv)
>   	v4l2_async_nf_cleanup(&priv->notifier);
>   	v4l2_subdev_cleanup(&priv->sd);
>   	media_entity_cleanup(&priv->sd.entity);
> +	v4l2_ctrl_handler_free(&priv->ctrl_handler);
>   }
>   
>   struct max96717_pll_predef_freq {
  
Sakari Ailus Aug. 28, 2024, 6:44 a.m. UTC | #2
Hi Tommaso,

Thanks for the patch.

On Thu, Jun 27, 2024 at 05:18:06PM +0200, Tommaso Merciai wrote:
> Add v4l2 test pattern control.
> 
> Signed-off-by: Tommaso Merciai <tomm.merciai@gmail.com>
> ---
> Changes since v1:
>  - Rename and move pattern regs under VTX section as suggested by JMassot
>  - Fix VTX regs order
>  - Add comment saying that the deserializer should manage the link in
>    pixel mode as suggested by JMassot
> 
>  drivers/media/i2c/max96717.c | 213 ++++++++++++++++++++++++++++++++---
>  1 file changed, 197 insertions(+), 16 deletions(-)
> 
> diff --git a/drivers/media/i2c/max96717.c b/drivers/media/i2c/max96717.c
> index 949306485873..859a439b64d9 100644
> --- a/drivers/media/i2c/max96717.c
> +++ b/drivers/media/i2c/max96717.c
> @@ -16,6 +16,7 @@
>  #include <linux/regmap.h>
>  
>  #include <media/v4l2-cci.h>
> +#include <media/v4l2-ctrls.h>
>  #include <media/v4l2-fwnode.h>
>  #include <media/v4l2-subdev.h>
>  
> @@ -38,9 +39,35 @@
>  #define MAX96717_DEV_REV_MASK GENMASK(3, 0)
>  
>  /* VID_TX Z */
> +#define MAX96717_VIDEO_TX0 CCI_REG8(0x110)
> +#define MAX96717_VIDEO_AUTO_BPP BIT(3)
>  #define MAX96717_VIDEO_TX2 CCI_REG8(0x112)
>  #define MAX96717_VIDEO_PCLKDET BIT(7)
>  
> +/* VTX_Z */
> +#define MAX96717_VTX0                  CCI_REG8(0x24e)
> +#define MAX96717_VTX1                  CCI_REG8(0x24f)
> +#define MAX96717_PATTERN_CLK_FREQ      GENMASK(3, 1)
> +#define MAX96717_VTX_VS_DLY            CCI_REG24(0x250)
> +#define MAX96717_VTX_VS_HIGH           CCI_REG24(0x253)
> +#define MAX96717_VTX_VS_LOW            CCI_REG24(0x256)
> +#define MAX96717_VTX_V2H               CCI_REG24(0x259)
> +#define MAX96717_VTX_HS_HIGH           CCI_REG16(0x25c)
> +#define MAX96717_VTX_HS_LOW            CCI_REG16(0x25e)
> +#define MAX96717_VTX_HS_CNT            CCI_REG16(0x260)
> +#define MAX96717_VTX_V2D               CCI_REG24(0x262)
> +#define MAX96717_VTX_DE_HIGH           CCI_REG16(0x265)
> +#define MAX96717_VTX_DE_LOW            CCI_REG16(0x267)
> +#define MAX96717_VTX_DE_CNT            CCI_REG16(0x269)
> +#define MAX96717_VTX29                 CCI_REG8(0x26b)
> +#define MAX96717_VTX_MODE              GENMASK(1, 0)
> +#define MAX96717_VTX_GRAD_INC          CCI_REG8(0x26c)
> +#define MAX96717_VTX_CHKB_COLOR_A      CCI_REG24(0x26d)
> +#define MAX96717_VTX_CHKB_COLOR_B      CCI_REG24(0x270)
> +#define MAX96717_VTX_CHKB_RPT_CNT_A    CCI_REG8(0x273)
> +#define MAX96717_VTX_CHKB_RPT_CNT_B    CCI_REG8(0x274)
> +#define MAX96717_VTX_CHKB_ALT          CCI_REG8(0x275)
> +
>  /* GPIO */
>  #define MAX96717_NUM_GPIO         11
>  #define MAX96717_GPIO_REG_A(gpio) CCI_REG8(0x2be + (gpio) * 3)
> @@ -82,6 +109,12 @@
>  /* MISC */
>  #define PIO_SLEW_1 CCI_REG8(0x570)
>  
> +enum max96717_vpg_mode {
> +	MAX96717_VPG_DISABLED = 0,
> +	MAX96717_VPG_CHECKERBOARD = 1,
> +	MAX96717_VPG_GRADIENT = 2,
> +};
> +
>  struct max96717_priv {
>  	struct i2c_client		  *client;
>  	struct regmap			  *regmap;
> @@ -89,6 +122,7 @@ struct max96717_priv {
>  	struct v4l2_mbus_config_mipi_csi2 mipi_csi2;
>  	struct v4l2_subdev                sd;
>  	struct media_pad                  pads[MAX96717_PORTS];
> +	struct v4l2_ctrl_handler          ctrl_handler;
>  	struct v4l2_async_notifier        notifier;
>  	struct v4l2_subdev                *source_sd;
>  	u16                               source_sd_pad;
> @@ -96,6 +130,7 @@ struct max96717_priv {
>  	u8                                pll_predef_index;
>  	struct clk_hw                     clk_hw;
>  	struct gpio_chip                  gpio_chip;
> +	enum max96717_vpg_mode            pattern;
>  };
>  
>  static inline struct max96717_priv *sd_to_max96717(struct v4l2_subdev *sd)
> @@ -131,6 +166,118 @@ static inline int max96717_start_csi(struct max96717_priv *priv, bool start)
>  			       start ? MAX96717_START_PORT_B : 0, NULL);
>  }
>  
> +static int max96717_apply_patgen_timing(struct max96717_priv *priv,
> +					struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_mbus_framefmt *fmt =
> +		v4l2_subdev_state_get_format(state, MAX96717_PAD_SOURCE);
> +	const u32 h_active = fmt->width;
> +	const u32 h_fp = 88;
> +	const u32 h_sw = 44;
> +	const u32 h_bp = 148;
> +	u32 h_tot;
> +	const u32 v_active = fmt->height;
> +	const u32 v_fp = 4;
> +	const u32 v_sw = 5;
> +	const u32 v_bp = 36;

Some comments here would be nice, what do these values signify for
instance?

> +	u32 v_tot;
> +	int ret = 0;
> +
> +	h_tot = h_active + h_fp + h_sw + h_bp;
> +	v_tot = v_active + v_fp + v_sw + v_bp;
> +
> +	/* 75 Mhz pixel clock */
> +	cci_update_bits(priv->regmap, MAX96717_VTX1,
> +			MAX96717_PATTERN_CLK_FREQ, 0xa, &ret);
> +
> +	dev_info(&priv->client->dev, "height: %d width: %d\n", fmt->height,
> +		 fmt->width);
> +
> +	cci_write(priv->regmap, MAX96717_VTX_VS_DLY, 0, &ret);
> +	cci_write(priv->regmap, MAX96717_VTX_VS_HIGH, v_sw * h_tot, &ret);
> +	cci_write(priv->regmap, MAX96717_VTX_VS_LOW,
> +		  (v_active + v_fp + v_bp) * h_tot, &ret);
> +	cci_write(priv->regmap, MAX96717_VTX_HS_HIGH, h_sw, &ret);
> +	cci_write(priv->regmap, MAX96717_VTX_HS_LOW, h_active + h_fp + h_bp,
> +		  &ret);
> +	cci_write(priv->regmap, MAX96717_VTX_V2D,
> +		  h_tot * (v_sw + v_bp) + (h_sw + h_bp), &ret);
> +	cci_write(priv->regmap, MAX96717_VTX_HS_CNT, v_tot, &ret);
> +	cci_write(priv->regmap, MAX96717_VTX_DE_HIGH, h_active, &ret);
> +	cci_write(priv->regmap, MAX96717_VTX_DE_LOW, h_fp + h_sw + h_bp,
> +		  &ret);
> +	cci_write(priv->regmap, MAX96717_VTX_DE_CNT, v_active, &ret);
> +	/* B G R */
> +	cci_write(priv->regmap, MAX96717_VTX_CHKB_COLOR_A, 0xfecc00, &ret);
> +	/* B G R */
> +	cci_write(priv->regmap, MAX96717_VTX_CHKB_COLOR_B, 0x006aa7, &ret);
> +	cci_write(priv->regmap, MAX96717_VTX_CHKB_RPT_CNT_A, 0x3c, &ret);
> +	cci_write(priv->regmap, MAX96717_VTX_CHKB_RPT_CNT_B, 0x3c, &ret);
> +	cci_write(priv->regmap, MAX96717_VTX_CHKB_ALT, 0x3c, &ret);
> +	cci_write(priv->regmap, MAX96717_VTX_GRAD_INC, 0x10, &ret);
> +
> +	return ret;
> +}
> +
> +static int max96717_apply_patgen(struct max96717_priv *priv,
> +				 struct v4l2_subdev_state *state)
> +{
> +	unsigned int val;
> +	int ret = 0;
> +
> +	if (priv->pattern)
> +		ret = max96717_apply_patgen_timing(priv, state);
> +
> +	cci_write(priv->regmap, MAX96717_VTX0, priv->pattern ? 0xfb : 0,
> +		  &ret);
> +
> +	val = FIELD_PREP(MAX96717_VTX_MODE, priv->pattern);
> +	cci_update_bits(priv->regmap, MAX96717_VTX29, MAX96717_VTX_MODE,
> +			val, &ret);
> +	return ret;
> +}
> +
> +static int max96717_s_ctrl(struct v4l2_ctrl *ctrl)
> +{
> +	struct max96717_priv *priv =
> +		container_of(ctrl->handler, struct max96717_priv, ctrl_handler);
> +	int ret;
> +
> +	switch (ctrl->id) {
> +	case V4L2_CID_TEST_PATTERN:
> +		if (priv->enabled_source_streams)
> +			return -EBUSY;
> +		priv->pattern = ctrl->val;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	/* Use bpp from bpp register */
> +	ret = cci_update_bits(priv->regmap, MAX96717_VIDEO_TX0,
> +			      MAX96717_VIDEO_AUTO_BPP,
> +			      priv->pattern ? 0 : MAX96717_VIDEO_AUTO_BPP,
> +			      NULL);
> +
> +	/*
> +	 * Pattern generator doesn't work with tunnel mode.
> +	 * Needs RGB color format and deserializer tunnel mode must be disabled.
> +	 */
> +	return cci_update_bits(priv->regmap, MAX96717_MIPI_RX_EXT11,
> +			       MAX96717_TUN_MODE,
> +			       priv->pattern ? 0 : MAX96717_TUN_MODE, &ret);
> +}
> +
> +static const char * const max96717_test_pattern[] = {
> +	"Disabled",
> +	"Checkerboard",
> +	"Gradient"
> +};
> +
> +static const struct v4l2_ctrl_ops max96717_ctrl_ops = {
> +	.s_ctrl = max96717_s_ctrl,
> +};
> +
>  static int max96717_gpiochip_get(struct gpio_chip *gpiochip,
>  				 unsigned int offset)
>  {
> @@ -352,20 +499,28 @@ static int max96717_enable_streams(struct v4l2_subdev *sd,
>  	u64 sink_streams;
>  	int ret;
>  
> -	sink_streams = v4l2_subdev_state_xlate_streams(state,
> -						       MAX96717_PAD_SOURCE,
> -						       MAX96717_PAD_SINK,
> -						       &streams_mask);
> -
>  	if (!priv->enabled_source_streams)
>  		max96717_start_csi(priv, true);
>  
> -	ret = v4l2_subdev_enable_streams(priv->source_sd, priv->source_sd_pad,
> -					 sink_streams);
> -	if (ret) {
> -		dev_err(dev, "Fail to start streams:%llu on remote subdev\n",
> -			sink_streams);
> +	ret = max96717_apply_patgen(priv, state);
> +	if (ret)
>  		goto stop_csi;
> +
> +	if (!priv->pattern) {
> +		sink_streams =
> +			v4l2_subdev_state_xlate_streams(state,
> +							MAX96717_PAD_SOURCE,
> +							MAX96717_PAD_SINK,
> +							&streams_mask);
> +
> +		ret = v4l2_subdev_enable_streams(priv->source_sd,
> +						 priv->source_sd_pad,
> +						 sink_streams);
> +		if (ret) {
> +			dev_err(dev, "Fail to start streams:%llu on remote subdev\n",
> +				sink_streams);
> +			goto stop_csi;
> +		}
>  	}
>  
>  	priv->enabled_source_streams |= streams_mask;
> @@ -394,13 +549,23 @@ static int max96717_disable_streams(struct v4l2_subdev *sd,
>  	if (!priv->enabled_source_streams)
>  		max96717_start_csi(priv, false);
>  
> -	sink_streams = v4l2_subdev_state_xlate_streams(state,
> -						       MAX96717_PAD_SOURCE,
> -						       MAX96717_PAD_SINK,
> -						       &streams_mask);
> +	if (!priv->pattern) {
> +		int ret;
> +
> +		sink_streams =
> +			v4l2_subdev_state_xlate_streams(state,
> +							MAX96717_PAD_SOURCE,
> +							MAX96717_PAD_SINK,
> +							&streams_mask);
>  
> -	return v4l2_subdev_disable_streams(priv->source_sd, priv->source_sd_pad,
> -					   sink_streams);
> +		ret = v4l2_subdev_disable_streams(priv->source_sd,
> +						  priv->source_sd_pad,
> +						  sink_streams);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
>  }
>  
>  static const struct v4l2_subdev_pad_ops max96717_pad_ops = {
> @@ -513,6 +678,19 @@ static int max96717_subdev_init(struct max96717_priv *priv)
>  	v4l2_i2c_subdev_init(&priv->sd, priv->client, &max96717_subdev_ops);
>  	priv->sd.internal_ops = &max96717_internal_ops;
>  
> +	v4l2_ctrl_handler_init(&priv->ctrl_handler, 1);
> +	priv->sd.ctrl_handler = &priv->ctrl_handler;
> +
> +	v4l2_ctrl_new_std_menu_items(&priv->ctrl_handler,
> +				     &max96717_ctrl_ops,
> +				     V4L2_CID_TEST_PATTERN,
> +				     ARRAY_SIZE(max96717_test_pattern) - 1,
> +				     0, 0, max96717_test_pattern);
> +	if (priv->ctrl_handler.error) {
> +		ret = priv->ctrl_handler.error;
> +		goto err_free_ctrl;
> +	}
> +
>  	priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;

With controls, you should add the HAS_EVENTS flag.

I'll take this one as we're in rc5 already, please address these in a
separate patch.

>  	priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
>  	priv->sd.entity.ops = &max96717_entity_ops;
> @@ -552,6 +730,8 @@ static int max96717_subdev_init(struct max96717_priv *priv)
>  	v4l2_subdev_cleanup(&priv->sd);
>  err_entity_cleanup:
>  	media_entity_cleanup(&priv->sd.entity);
> +err_free_ctrl:
> +	v4l2_ctrl_handler_free(&priv->ctrl_handler);
>  
>  	return ret;
>  }
> @@ -563,6 +743,7 @@ static void max96717_subdev_uninit(struct max96717_priv *priv)
>  	v4l2_async_nf_cleanup(&priv->notifier);
>  	v4l2_subdev_cleanup(&priv->sd);
>  	media_entity_cleanup(&priv->sd.entity);
> +	v4l2_ctrl_handler_free(&priv->ctrl_handler);
>  }
>  
>  struct max96717_pll_predef_freq {
  
Tommaso Merciai Sept. 4, 2024, 12:53 p.m. UTC | #3
Hi Sakari,
Sorry for delay and thanks for reviewing this.

On Wed, Aug 28, 2024 at 06:44:19AM +0000, Sakari Ailus wrote:
> Hi Tommaso,
> 
> Thanks for the patch.
> 
> On Thu, Jun 27, 2024 at 05:18:06PM +0200, Tommaso Merciai wrote:
> > Add v4l2 test pattern control.
> > 
> > Signed-off-by: Tommaso Merciai <tomm.merciai@gmail.com>
> > ---
> > Changes since v1:
> >  - Rename and move pattern regs under VTX section as suggested by JMassot
> >  - Fix VTX regs order
> >  - Add comment saying that the deserializer should manage the link in
> >    pixel mode as suggested by JMassot
> > 
> >  drivers/media/i2c/max96717.c | 213 ++++++++++++++++++++++++++++++++---
> >  1 file changed, 197 insertions(+), 16 deletions(-)
> > 
> > diff --git a/drivers/media/i2c/max96717.c b/drivers/media/i2c/max96717.c
> > index 949306485873..859a439b64d9 100644
> > --- a/drivers/media/i2c/max96717.c
> > +++ b/drivers/media/i2c/max96717.c
> > @@ -16,6 +16,7 @@
> >  #include <linux/regmap.h>
> >  
> >  #include <media/v4l2-cci.h>
> > +#include <media/v4l2-ctrls.h>
> >  #include <media/v4l2-fwnode.h>
> >  #include <media/v4l2-subdev.h>
> >  
> > @@ -38,9 +39,35 @@
> >  #define MAX96717_DEV_REV_MASK GENMASK(3, 0)
> >  
> >  /* VID_TX Z */
> > +#define MAX96717_VIDEO_TX0 CCI_REG8(0x110)
> > +#define MAX96717_VIDEO_AUTO_BPP BIT(3)
> >  #define MAX96717_VIDEO_TX2 CCI_REG8(0x112)
> >  #define MAX96717_VIDEO_PCLKDET BIT(7)
> >  
> > +/* VTX_Z */
> > +#define MAX96717_VTX0                  CCI_REG8(0x24e)
> > +#define MAX96717_VTX1                  CCI_REG8(0x24f)
> > +#define MAX96717_PATTERN_CLK_FREQ      GENMASK(3, 1)
> > +#define MAX96717_VTX_VS_DLY            CCI_REG24(0x250)
> > +#define MAX96717_VTX_VS_HIGH           CCI_REG24(0x253)
> > +#define MAX96717_VTX_VS_LOW            CCI_REG24(0x256)
> > +#define MAX96717_VTX_V2H               CCI_REG24(0x259)
> > +#define MAX96717_VTX_HS_HIGH           CCI_REG16(0x25c)
> > +#define MAX96717_VTX_HS_LOW            CCI_REG16(0x25e)
> > +#define MAX96717_VTX_HS_CNT            CCI_REG16(0x260)
> > +#define MAX96717_VTX_V2D               CCI_REG24(0x262)
> > +#define MAX96717_VTX_DE_HIGH           CCI_REG16(0x265)
> > +#define MAX96717_VTX_DE_LOW            CCI_REG16(0x267)
> > +#define MAX96717_VTX_DE_CNT            CCI_REG16(0x269)
> > +#define MAX96717_VTX29                 CCI_REG8(0x26b)
> > +#define MAX96717_VTX_MODE              GENMASK(1, 0)
> > +#define MAX96717_VTX_GRAD_INC          CCI_REG8(0x26c)
> > +#define MAX96717_VTX_CHKB_COLOR_A      CCI_REG24(0x26d)
> > +#define MAX96717_VTX_CHKB_COLOR_B      CCI_REG24(0x270)
> > +#define MAX96717_VTX_CHKB_RPT_CNT_A    CCI_REG8(0x273)
> > +#define MAX96717_VTX_CHKB_RPT_CNT_B    CCI_REG8(0x274)
> > +#define MAX96717_VTX_CHKB_ALT          CCI_REG8(0x275)
> > +
> >  /* GPIO */
> >  #define MAX96717_NUM_GPIO         11
> >  #define MAX96717_GPIO_REG_A(gpio) CCI_REG8(0x2be + (gpio) * 3)
> > @@ -82,6 +109,12 @@
> >  /* MISC */
> >  #define PIO_SLEW_1 CCI_REG8(0x570)
> >  
> > +enum max96717_vpg_mode {
> > +	MAX96717_VPG_DISABLED = 0,
> > +	MAX96717_VPG_CHECKERBOARD = 1,
> > +	MAX96717_VPG_GRADIENT = 2,
> > +};
> > +
> >  struct max96717_priv {
> >  	struct i2c_client		  *client;
> >  	struct regmap			  *regmap;
> > @@ -89,6 +122,7 @@ struct max96717_priv {
> >  	struct v4l2_mbus_config_mipi_csi2 mipi_csi2;
> >  	struct v4l2_subdev                sd;
> >  	struct media_pad                  pads[MAX96717_PORTS];
> > +	struct v4l2_ctrl_handler          ctrl_handler;
> >  	struct v4l2_async_notifier        notifier;
> >  	struct v4l2_subdev                *source_sd;
> >  	u16                               source_sd_pad;
> > @@ -96,6 +130,7 @@ struct max96717_priv {
> >  	u8                                pll_predef_index;
> >  	struct clk_hw                     clk_hw;
> >  	struct gpio_chip                  gpio_chip;
> > +	enum max96717_vpg_mode            pattern;
> >  };
> >  
> >  static inline struct max96717_priv *sd_to_max96717(struct v4l2_subdev *sd)
> > @@ -131,6 +166,118 @@ static inline int max96717_start_csi(struct max96717_priv *priv, bool start)
> >  			       start ? MAX96717_START_PORT_B : 0, NULL);
> >  }
> >  
> > +static int max96717_apply_patgen_timing(struct max96717_priv *priv,
> > +					struct v4l2_subdev_state *state)
> > +{
> > +	struct v4l2_mbus_framefmt *fmt =
> > +		v4l2_subdev_state_get_format(state, MAX96717_PAD_SOURCE);
> > +	const u32 h_active = fmt->width;
> > +	const u32 h_fp = 88;
> > +	const u32 h_sw = 44;
> > +	const u32 h_bp = 148;
> > +	u32 h_tot;
> > +	const u32 v_active = fmt->height;
> > +	const u32 v_fp = 4;
> > +	const u32 v_sw = 5;
> > +	const u32 v_bp = 36;
> 
> Some comments here would be nice, what do these values signify for
> instance?

Oks, I will send a patch with some comments for this.

> 
> > +	u32 v_tot;
> > +	int ret = 0;
> > +
> > +	h_tot = h_active + h_fp + h_sw + h_bp;
> > +	v_tot = v_active + v_fp + v_sw + v_bp;
> > +
> > +	/* 75 Mhz pixel clock */
> > +	cci_update_bits(priv->regmap, MAX96717_VTX1,
> > +			MAX96717_PATTERN_CLK_FREQ, 0xa, &ret);
> > +
> > +	dev_info(&priv->client->dev, "height: %d width: %d\n", fmt->height,
> > +		 fmt->width);
> > +
> > +	cci_write(priv->regmap, MAX96717_VTX_VS_DLY, 0, &ret);
> > +	cci_write(priv->regmap, MAX96717_VTX_VS_HIGH, v_sw * h_tot, &ret);
> > +	cci_write(priv->regmap, MAX96717_VTX_VS_LOW,
> > +		  (v_active + v_fp + v_bp) * h_tot, &ret);
> > +	cci_write(priv->regmap, MAX96717_VTX_HS_HIGH, h_sw, &ret);
> > +	cci_write(priv->regmap, MAX96717_VTX_HS_LOW, h_active + h_fp + h_bp,
> > +		  &ret);
> > +	cci_write(priv->regmap, MAX96717_VTX_V2D,
> > +		  h_tot * (v_sw + v_bp) + (h_sw + h_bp), &ret);
> > +	cci_write(priv->regmap, MAX96717_VTX_HS_CNT, v_tot, &ret);
> > +	cci_write(priv->regmap, MAX96717_VTX_DE_HIGH, h_active, &ret);
> > +	cci_write(priv->regmap, MAX96717_VTX_DE_LOW, h_fp + h_sw + h_bp,
> > +		  &ret);
> > +	cci_write(priv->regmap, MAX96717_VTX_DE_CNT, v_active, &ret);
> > +	/* B G R */
> > +	cci_write(priv->regmap, MAX96717_VTX_CHKB_COLOR_A, 0xfecc00, &ret);
> > +	/* B G R */
> > +	cci_write(priv->regmap, MAX96717_VTX_CHKB_COLOR_B, 0x006aa7, &ret);
> > +	cci_write(priv->regmap, MAX96717_VTX_CHKB_RPT_CNT_A, 0x3c, &ret);
> > +	cci_write(priv->regmap, MAX96717_VTX_CHKB_RPT_CNT_B, 0x3c, &ret);
> > +	cci_write(priv->regmap, MAX96717_VTX_CHKB_ALT, 0x3c, &ret);
> > +	cci_write(priv->regmap, MAX96717_VTX_GRAD_INC, 0x10, &ret);
> > +
> > +	return ret;
> > +}
> > +
> > +static int max96717_apply_patgen(struct max96717_priv *priv,
> > +				 struct v4l2_subdev_state *state)
> > +{
> > +	unsigned int val;
> > +	int ret = 0;
> > +
> > +	if (priv->pattern)
> > +		ret = max96717_apply_patgen_timing(priv, state);
> > +
> > +	cci_write(priv->regmap, MAX96717_VTX0, priv->pattern ? 0xfb : 0,
> > +		  &ret);
> > +
> > +	val = FIELD_PREP(MAX96717_VTX_MODE, priv->pattern);
> > +	cci_update_bits(priv->regmap, MAX96717_VTX29, MAX96717_VTX_MODE,
> > +			val, &ret);
> > +	return ret;
> > +}
> > +
> > +static int max96717_s_ctrl(struct v4l2_ctrl *ctrl)
> > +{
> > +	struct max96717_priv *priv =
> > +		container_of(ctrl->handler, struct max96717_priv, ctrl_handler);
> > +	int ret;
> > +
> > +	switch (ctrl->id) {
> > +	case V4L2_CID_TEST_PATTERN:
> > +		if (priv->enabled_source_streams)
> > +			return -EBUSY;
> > +		priv->pattern = ctrl->val;
> > +		break;
> > +	default:
> > +		return -EINVAL;
> > +	}
> > +
> > +	/* Use bpp from bpp register */
> > +	ret = cci_update_bits(priv->regmap, MAX96717_VIDEO_TX0,
> > +			      MAX96717_VIDEO_AUTO_BPP,
> > +			      priv->pattern ? 0 : MAX96717_VIDEO_AUTO_BPP,
> > +			      NULL);
> > +
> > +	/*
> > +	 * Pattern generator doesn't work with tunnel mode.
> > +	 * Needs RGB color format and deserializer tunnel mode must be disabled.
> > +	 */
> > +	return cci_update_bits(priv->regmap, MAX96717_MIPI_RX_EXT11,
> > +			       MAX96717_TUN_MODE,
> > +			       priv->pattern ? 0 : MAX96717_TUN_MODE, &ret);
> > +}
> > +
> > +static const char * const max96717_test_pattern[] = {
> > +	"Disabled",
> > +	"Checkerboard",
> > +	"Gradient"
> > +};
> > +
> > +static const struct v4l2_ctrl_ops max96717_ctrl_ops = {
> > +	.s_ctrl = max96717_s_ctrl,
> > +};
> > +
> >  static int max96717_gpiochip_get(struct gpio_chip *gpiochip,
> >  				 unsigned int offset)
> >  {
> > @@ -352,20 +499,28 @@ static int max96717_enable_streams(struct v4l2_subdev *sd,
> >  	u64 sink_streams;
> >  	int ret;
> >  
> > -	sink_streams = v4l2_subdev_state_xlate_streams(state,
> > -						       MAX96717_PAD_SOURCE,
> > -						       MAX96717_PAD_SINK,
> > -						       &streams_mask);
> > -
> >  	if (!priv->enabled_source_streams)
> >  		max96717_start_csi(priv, true);
> >  
> > -	ret = v4l2_subdev_enable_streams(priv->source_sd, priv->source_sd_pad,
> > -					 sink_streams);
> > -	if (ret) {
> > -		dev_err(dev, "Fail to start streams:%llu on remote subdev\n",
> > -			sink_streams);
> > +	ret = max96717_apply_patgen(priv, state);
> > +	if (ret)
> >  		goto stop_csi;
> > +
> > +	if (!priv->pattern) {
> > +		sink_streams =
> > +			v4l2_subdev_state_xlate_streams(state,
> > +							MAX96717_PAD_SOURCE,
> > +							MAX96717_PAD_SINK,
> > +							&streams_mask);
> > +
> > +		ret = v4l2_subdev_enable_streams(priv->source_sd,
> > +						 priv->source_sd_pad,
> > +						 sink_streams);
> > +		if (ret) {
> > +			dev_err(dev, "Fail to start streams:%llu on remote subdev\n",
> > +				sink_streams);
> > +			goto stop_csi;
> > +		}
> >  	}
> >  
> >  	priv->enabled_source_streams |= streams_mask;
> > @@ -394,13 +549,23 @@ static int max96717_disable_streams(struct v4l2_subdev *sd,
> >  	if (!priv->enabled_source_streams)
> >  		max96717_start_csi(priv, false);
> >  
> > -	sink_streams = v4l2_subdev_state_xlate_streams(state,
> > -						       MAX96717_PAD_SOURCE,
> > -						       MAX96717_PAD_SINK,
> > -						       &streams_mask);
> > +	if (!priv->pattern) {
> > +		int ret;
> > +
> > +		sink_streams =
> > +			v4l2_subdev_state_xlate_streams(state,
> > +							MAX96717_PAD_SOURCE,
> > +							MAX96717_PAD_SINK,
> > +							&streams_mask);
> >  
> > -	return v4l2_subdev_disable_streams(priv->source_sd, priv->source_sd_pad,
> > -					   sink_streams);
> > +		ret = v4l2_subdev_disable_streams(priv->source_sd,
> > +						  priv->source_sd_pad,
> > +						  sink_streams);
> > +		if (ret)
> > +			return ret;
> > +	}
> > +
> > +	return 0;
> >  }
> >  
> >  static const struct v4l2_subdev_pad_ops max96717_pad_ops = {
> > @@ -513,6 +678,19 @@ static int max96717_subdev_init(struct max96717_priv *priv)
> >  	v4l2_i2c_subdev_init(&priv->sd, priv->client, &max96717_subdev_ops);
> >  	priv->sd.internal_ops = &max96717_internal_ops;
> >  
> > +	v4l2_ctrl_handler_init(&priv->ctrl_handler, 1);
> > +	priv->sd.ctrl_handler = &priv->ctrl_handler;
> > +
> > +	v4l2_ctrl_new_std_menu_items(&priv->ctrl_handler,
> > +				     &max96717_ctrl_ops,
> > +				     V4L2_CID_TEST_PATTERN,
> > +				     ARRAY_SIZE(max96717_test_pattern) - 1,
> > +				     0, 0, max96717_test_pattern);
> > +	if (priv->ctrl_handler.error) {
> > +		ret = priv->ctrl_handler.error;
> > +		goto err_free_ctrl;
> > +	}
> > +
> >  	priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
> 
> With controls, you should add the HAS_EVENTS flag.
> 
> I'll take this one as we're in rc5 already, please address these in a
> separate patch.


Will send a patch for this. 
I think we should fix that also into max96717 driver or I'm wrong?

> 
> >  	priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> >  	priv->sd.entity.ops = &max96717_entity_ops;
> > @@ -552,6 +730,8 @@ static int max96717_subdev_init(struct max96717_priv *priv)
> >  	v4l2_subdev_cleanup(&priv->sd);
> >  err_entity_cleanup:
> >  	media_entity_cleanup(&priv->sd.entity);
> > +err_free_ctrl:
> > +	v4l2_ctrl_handler_free(&priv->ctrl_handler);
> >  
> >  	return ret;
> >  }
> > @@ -563,6 +743,7 @@ static void max96717_subdev_uninit(struct max96717_priv *priv)
> >  	v4l2_async_nf_cleanup(&priv->notifier);
> >  	v4l2_subdev_cleanup(&priv->sd);
> >  	media_entity_cleanup(&priv->sd.entity);
> > +	v4l2_ctrl_handler_free(&priv->ctrl_handler);
> >  }
> >  
> >  struct max96717_pll_predef_freq {
> 
> -- 
> Kind regards,
> 
> Sakari Ailus


Thanks & Regards,
Tommaso
  
Tommaso Merciai Sept. 4, 2024, 1:01 p.m. UTC | #4
On Wed, Sep 04, 2024 at 02:53:55PM +0200, Tommaso Merciai wrote:
> Hi Sakari,
> Sorry for delay and thanks for reviewing this.
> 
> On Wed, Aug 28, 2024 at 06:44:19AM +0000, Sakari Ailus wrote:
> > Hi Tommaso,
> > 
> > Thanks for the patch.
> > 
> > On Thu, Jun 27, 2024 at 05:18:06PM +0200, Tommaso Merciai wrote:
> > > Add v4l2 test pattern control.
> > > 
> > > Signed-off-by: Tommaso Merciai <tomm.merciai@gmail.com>
> > > ---
> > > Changes since v1:
> > >  - Rename and move pattern regs under VTX section as suggested by JMassot
> > >  - Fix VTX regs order
> > >  - Add comment saying that the deserializer should manage the link in
> > >    pixel mode as suggested by JMassot
> > > 
> > >  drivers/media/i2c/max96717.c | 213 ++++++++++++++++++++++++++++++++---
> > >  1 file changed, 197 insertions(+), 16 deletions(-)
> > > 
> > > diff --git a/drivers/media/i2c/max96717.c b/drivers/media/i2c/max96717.c
> > > index 949306485873..859a439b64d9 100644
> > > --- a/drivers/media/i2c/max96717.c
> > > +++ b/drivers/media/i2c/max96717.c
> > > @@ -16,6 +16,7 @@
> > >  #include <linux/regmap.h>
> > >  
> > >  #include <media/v4l2-cci.h>
> > > +#include <media/v4l2-ctrls.h>
> > >  #include <media/v4l2-fwnode.h>
> > >  #include <media/v4l2-subdev.h>
> > >  
> > > @@ -38,9 +39,35 @@
> > >  #define MAX96717_DEV_REV_MASK GENMASK(3, 0)
> > >  
> > >  /* VID_TX Z */
> > > +#define MAX96717_VIDEO_TX0 CCI_REG8(0x110)
> > > +#define MAX96717_VIDEO_AUTO_BPP BIT(3)
> > >  #define MAX96717_VIDEO_TX2 CCI_REG8(0x112)
> > >  #define MAX96717_VIDEO_PCLKDET BIT(7)
> > >  
> > > +/* VTX_Z */
> > > +#define MAX96717_VTX0                  CCI_REG8(0x24e)
> > > +#define MAX96717_VTX1                  CCI_REG8(0x24f)
> > > +#define MAX96717_PATTERN_CLK_FREQ      GENMASK(3, 1)
> > > +#define MAX96717_VTX_VS_DLY            CCI_REG24(0x250)
> > > +#define MAX96717_VTX_VS_HIGH           CCI_REG24(0x253)
> > > +#define MAX96717_VTX_VS_LOW            CCI_REG24(0x256)
> > > +#define MAX96717_VTX_V2H               CCI_REG24(0x259)
> > > +#define MAX96717_VTX_HS_HIGH           CCI_REG16(0x25c)
> > > +#define MAX96717_VTX_HS_LOW            CCI_REG16(0x25e)
> > > +#define MAX96717_VTX_HS_CNT            CCI_REG16(0x260)
> > > +#define MAX96717_VTX_V2D               CCI_REG24(0x262)
> > > +#define MAX96717_VTX_DE_HIGH           CCI_REG16(0x265)
> > > +#define MAX96717_VTX_DE_LOW            CCI_REG16(0x267)
> > > +#define MAX96717_VTX_DE_CNT            CCI_REG16(0x269)
> > > +#define MAX96717_VTX29                 CCI_REG8(0x26b)
> > > +#define MAX96717_VTX_MODE              GENMASK(1, 0)
> > > +#define MAX96717_VTX_GRAD_INC          CCI_REG8(0x26c)
> > > +#define MAX96717_VTX_CHKB_COLOR_A      CCI_REG24(0x26d)
> > > +#define MAX96717_VTX_CHKB_COLOR_B      CCI_REG24(0x270)
> > > +#define MAX96717_VTX_CHKB_RPT_CNT_A    CCI_REG8(0x273)
> > > +#define MAX96717_VTX_CHKB_RPT_CNT_B    CCI_REG8(0x274)
> > > +#define MAX96717_VTX_CHKB_ALT          CCI_REG8(0x275)
> > > +
> > >  /* GPIO */
> > >  #define MAX96717_NUM_GPIO         11
> > >  #define MAX96717_GPIO_REG_A(gpio) CCI_REG8(0x2be + (gpio) * 3)
> > > @@ -82,6 +109,12 @@
> > >  /* MISC */
> > >  #define PIO_SLEW_1 CCI_REG8(0x570)
> > >  
> > > +enum max96717_vpg_mode {
> > > +	MAX96717_VPG_DISABLED = 0,
> > > +	MAX96717_VPG_CHECKERBOARD = 1,
> > > +	MAX96717_VPG_GRADIENT = 2,
> > > +};
> > > +
> > >  struct max96717_priv {
> > >  	struct i2c_client		  *client;
> > >  	struct regmap			  *regmap;
> > > @@ -89,6 +122,7 @@ struct max96717_priv {
> > >  	struct v4l2_mbus_config_mipi_csi2 mipi_csi2;
> > >  	struct v4l2_subdev                sd;
> > >  	struct media_pad                  pads[MAX96717_PORTS];
> > > +	struct v4l2_ctrl_handler          ctrl_handler;
> > >  	struct v4l2_async_notifier        notifier;
> > >  	struct v4l2_subdev                *source_sd;
> > >  	u16                               source_sd_pad;
> > > @@ -96,6 +130,7 @@ struct max96717_priv {
> > >  	u8                                pll_predef_index;
> > >  	struct clk_hw                     clk_hw;
> > >  	struct gpio_chip                  gpio_chip;
> > > +	enum max96717_vpg_mode            pattern;
> > >  };
> > >  
> > >  static inline struct max96717_priv *sd_to_max96717(struct v4l2_subdev *sd)
> > > @@ -131,6 +166,118 @@ static inline int max96717_start_csi(struct max96717_priv *priv, bool start)
> > >  			       start ? MAX96717_START_PORT_B : 0, NULL);
> > >  }
> > >  
> > > +static int max96717_apply_patgen_timing(struct max96717_priv *priv,
> > > +					struct v4l2_subdev_state *state)
> > > +{
> > > +	struct v4l2_mbus_framefmt *fmt =
> > > +		v4l2_subdev_state_get_format(state, MAX96717_PAD_SOURCE);
> > > +	const u32 h_active = fmt->width;
> > > +	const u32 h_fp = 88;
> > > +	const u32 h_sw = 44;
> > > +	const u32 h_bp = 148;
> > > +	u32 h_tot;
> > > +	const u32 v_active = fmt->height;
> > > +	const u32 v_fp = 4;
> > > +	const u32 v_sw = 5;
> > > +	const u32 v_bp = 36;
> > 
> > Some comments here would be nice, what do these values signify for
> > instance?
> 
> Oks, I will send a patch with some comments for this.
> 
> > 
> > > +	u32 v_tot;
> > > +	int ret = 0;
> > > +
> > > +	h_tot = h_active + h_fp + h_sw + h_bp;
> > > +	v_tot = v_active + v_fp + v_sw + v_bp;
> > > +
> > > +	/* 75 Mhz pixel clock */
> > > +	cci_update_bits(priv->regmap, MAX96717_VTX1,
> > > +			MAX96717_PATTERN_CLK_FREQ, 0xa, &ret);
> > > +
> > > +	dev_info(&priv->client->dev, "height: %d width: %d\n", fmt->height,
> > > +		 fmt->width);
> > > +
> > > +	cci_write(priv->regmap, MAX96717_VTX_VS_DLY, 0, &ret);
> > > +	cci_write(priv->regmap, MAX96717_VTX_VS_HIGH, v_sw * h_tot, &ret);
> > > +	cci_write(priv->regmap, MAX96717_VTX_VS_LOW,
> > > +		  (v_active + v_fp + v_bp) * h_tot, &ret);
> > > +	cci_write(priv->regmap, MAX96717_VTX_HS_HIGH, h_sw, &ret);
> > > +	cci_write(priv->regmap, MAX96717_VTX_HS_LOW, h_active + h_fp + h_bp,
> > > +		  &ret);
> > > +	cci_write(priv->regmap, MAX96717_VTX_V2D,
> > > +		  h_tot * (v_sw + v_bp) + (h_sw + h_bp), &ret);
> > > +	cci_write(priv->regmap, MAX96717_VTX_HS_CNT, v_tot, &ret);
> > > +	cci_write(priv->regmap, MAX96717_VTX_DE_HIGH, h_active, &ret);
> > > +	cci_write(priv->regmap, MAX96717_VTX_DE_LOW, h_fp + h_sw + h_bp,
> > > +		  &ret);
> > > +	cci_write(priv->regmap, MAX96717_VTX_DE_CNT, v_active, &ret);
> > > +	/* B G R */
> > > +	cci_write(priv->regmap, MAX96717_VTX_CHKB_COLOR_A, 0xfecc00, &ret);
> > > +	/* B G R */
> > > +	cci_write(priv->regmap, MAX96717_VTX_CHKB_COLOR_B, 0x006aa7, &ret);
> > > +	cci_write(priv->regmap, MAX96717_VTX_CHKB_RPT_CNT_A, 0x3c, &ret);
> > > +	cci_write(priv->regmap, MAX96717_VTX_CHKB_RPT_CNT_B, 0x3c, &ret);
> > > +	cci_write(priv->regmap, MAX96717_VTX_CHKB_ALT, 0x3c, &ret);
> > > +	cci_write(priv->regmap, MAX96717_VTX_GRAD_INC, 0x10, &ret);
> > > +
> > > +	return ret;
> > > +}
> > > +
> > > +static int max96717_apply_patgen(struct max96717_priv *priv,
> > > +				 struct v4l2_subdev_state *state)
> > > +{
> > > +	unsigned int val;
> > > +	int ret = 0;
> > > +
> > > +	if (priv->pattern)
> > > +		ret = max96717_apply_patgen_timing(priv, state);
> > > +
> > > +	cci_write(priv->regmap, MAX96717_VTX0, priv->pattern ? 0xfb : 0,
> > > +		  &ret);
> > > +
> > > +	val = FIELD_PREP(MAX96717_VTX_MODE, priv->pattern);
> > > +	cci_update_bits(priv->regmap, MAX96717_VTX29, MAX96717_VTX_MODE,
> > > +			val, &ret);
> > > +	return ret;
> > > +}
> > > +
> > > +static int max96717_s_ctrl(struct v4l2_ctrl *ctrl)
> > > +{
> > > +	struct max96717_priv *priv =
> > > +		container_of(ctrl->handler, struct max96717_priv, ctrl_handler);
> > > +	int ret;
> > > +
> > > +	switch (ctrl->id) {
> > > +	case V4L2_CID_TEST_PATTERN:
> > > +		if (priv->enabled_source_streams)
> > > +			return -EBUSY;
> > > +		priv->pattern = ctrl->val;
> > > +		break;
> > > +	default:
> > > +		return -EINVAL;
> > > +	}
> > > +
> > > +	/* Use bpp from bpp register */
> > > +	ret = cci_update_bits(priv->regmap, MAX96717_VIDEO_TX0,
> > > +			      MAX96717_VIDEO_AUTO_BPP,
> > > +			      priv->pattern ? 0 : MAX96717_VIDEO_AUTO_BPP,
> > > +			      NULL);
> > > +
> > > +	/*
> > > +	 * Pattern generator doesn't work with tunnel mode.
> > > +	 * Needs RGB color format and deserializer tunnel mode must be disabled.
> > > +	 */
> > > +	return cci_update_bits(priv->regmap, MAX96717_MIPI_RX_EXT11,
> > > +			       MAX96717_TUN_MODE,
> > > +			       priv->pattern ? 0 : MAX96717_TUN_MODE, &ret);
> > > +}
> > > +
> > > +static const char * const max96717_test_pattern[] = {
> > > +	"Disabled",
> > > +	"Checkerboard",
> > > +	"Gradient"
> > > +};
> > > +
> > > +static const struct v4l2_ctrl_ops max96717_ctrl_ops = {
> > > +	.s_ctrl = max96717_s_ctrl,
> > > +};
> > > +
> > >  static int max96717_gpiochip_get(struct gpio_chip *gpiochip,
> > >  				 unsigned int offset)
> > >  {
> > > @@ -352,20 +499,28 @@ static int max96717_enable_streams(struct v4l2_subdev *sd,
> > >  	u64 sink_streams;
> > >  	int ret;
> > >  
> > > -	sink_streams = v4l2_subdev_state_xlate_streams(state,
> > > -						       MAX96717_PAD_SOURCE,
> > > -						       MAX96717_PAD_SINK,
> > > -						       &streams_mask);
> > > -
> > >  	if (!priv->enabled_source_streams)
> > >  		max96717_start_csi(priv, true);
> > >  
> > > -	ret = v4l2_subdev_enable_streams(priv->source_sd, priv->source_sd_pad,
> > > -					 sink_streams);
> > > -	if (ret) {
> > > -		dev_err(dev, "Fail to start streams:%llu on remote subdev\n",
> > > -			sink_streams);
> > > +	ret = max96717_apply_patgen(priv, state);
> > > +	if (ret)
> > >  		goto stop_csi;
> > > +
> > > +	if (!priv->pattern) {
> > > +		sink_streams =
> > > +			v4l2_subdev_state_xlate_streams(state,
> > > +							MAX96717_PAD_SOURCE,
> > > +							MAX96717_PAD_SINK,
> > > +							&streams_mask);
> > > +
> > > +		ret = v4l2_subdev_enable_streams(priv->source_sd,
> > > +						 priv->source_sd_pad,
> > > +						 sink_streams);
> > > +		if (ret) {
> > > +			dev_err(dev, "Fail to start streams:%llu on remote subdev\n",
> > > +				sink_streams);
> > > +			goto stop_csi;
> > > +		}
> > >  	}
> > >  
> > >  	priv->enabled_source_streams |= streams_mask;
> > > @@ -394,13 +549,23 @@ static int max96717_disable_streams(struct v4l2_subdev *sd,
> > >  	if (!priv->enabled_source_streams)
> > >  		max96717_start_csi(priv, false);
> > >  
> > > -	sink_streams = v4l2_subdev_state_xlate_streams(state,
> > > -						       MAX96717_PAD_SOURCE,
> > > -						       MAX96717_PAD_SINK,
> > > -						       &streams_mask);
> > > +	if (!priv->pattern) {
> > > +		int ret;
> > > +
> > > +		sink_streams =
> > > +			v4l2_subdev_state_xlate_streams(state,
> > > +							MAX96717_PAD_SOURCE,
> > > +							MAX96717_PAD_SINK,
> > > +							&streams_mask);
> > >  
> > > -	return v4l2_subdev_disable_streams(priv->source_sd, priv->source_sd_pad,
> > > -					   sink_streams);
> > > +		ret = v4l2_subdev_disable_streams(priv->source_sd,
> > > +						  priv->source_sd_pad,
> > > +						  sink_streams);
> > > +		if (ret)
> > > +			return ret;
> > > +	}
> > > +
> > > +	return 0;
> > >  }
> > >  
> > >  static const struct v4l2_subdev_pad_ops max96717_pad_ops = {
> > > @@ -513,6 +678,19 @@ static int max96717_subdev_init(struct max96717_priv *priv)
> > >  	v4l2_i2c_subdev_init(&priv->sd, priv->client, &max96717_subdev_ops);
> > >  	priv->sd.internal_ops = &max96717_internal_ops;
> > >  
> > > +	v4l2_ctrl_handler_init(&priv->ctrl_handler, 1);
> > > +	priv->sd.ctrl_handler = &priv->ctrl_handler;
> > > +
> > > +	v4l2_ctrl_new_std_menu_items(&priv->ctrl_handler,
> > > +				     &max96717_ctrl_ops,
> > > +				     V4L2_CID_TEST_PATTERN,
> > > +				     ARRAY_SIZE(max96717_test_pattern) - 1,
> > > +				     0, 0, max96717_test_pattern);
> > > +	if (priv->ctrl_handler.error) {
> > > +		ret = priv->ctrl_handler.error;
> > > +		goto err_free_ctrl;
> > > +	}
> > > +
> > >  	priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
> > 
> > With controls, you should add the HAS_EVENTS flag.
> > 
> > I'll take this one as we're in rc5 already, please address these in a
> > separate patch.
> 
> 
> Will send a patch for this. 
> I think we should fix that also into max96717 driver or I'm wrong?

max96714 sorry.

> 
> > 
> > >  	priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> > >  	priv->sd.entity.ops = &max96717_entity_ops;
> > > @@ -552,6 +730,8 @@ static int max96717_subdev_init(struct max96717_priv *priv)
> > >  	v4l2_subdev_cleanup(&priv->sd);
> > >  err_entity_cleanup:
> > >  	media_entity_cleanup(&priv->sd.entity);
> > > +err_free_ctrl:
> > > +	v4l2_ctrl_handler_free(&priv->ctrl_handler);
> > >  
> > >  	return ret;
> > >  }
> > > @@ -563,6 +743,7 @@ static void max96717_subdev_uninit(struct max96717_priv *priv)
> > >  	v4l2_async_nf_cleanup(&priv->notifier);
> > >  	v4l2_subdev_cleanup(&priv->sd);
> > >  	media_entity_cleanup(&priv->sd.entity);
> > > +	v4l2_ctrl_handler_free(&priv->ctrl_handler);
> > >  }
> > >  
> > >  struct max96717_pll_predef_freq {
> > 
> > -- 
> > Kind regards,
> > 
> > Sakari Ailus
> 
> 
> Thanks & Regards,
> Tommaso
  
Sakari Ailus Sept. 4, 2024, 1:03 p.m. UTC | #5
Hi Tommaso,

On Wed, Sep 04, 2024 at 03:01:36PM +0200, Tommaso Merciai wrote:
> On Wed, Sep 04, 2024 at 02:53:55PM +0200, Tommaso Merciai wrote:
> > Hi Sakari,
> > Sorry for delay and thanks for reviewing this.

No problem.

> > 
> > On Wed, Aug 28, 2024 at 06:44:19AM +0000, Sakari Ailus wrote:
> > > >  	priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
> > > 
> > > With controls, you should add the HAS_EVENTS flag.
> > > 
> > > I'll take this one as we're in rc5 already, please address these in a
> > > separate patch.
> > 
> > 
> > Will send a patch for this. 
> > I think we should fix that also into max96717 driver or I'm wrong?
> 
> max96714 sorry.

Yes, please.
  

Patch

diff --git a/drivers/media/i2c/max96717.c b/drivers/media/i2c/max96717.c
index 949306485873..859a439b64d9 100644
--- a/drivers/media/i2c/max96717.c
+++ b/drivers/media/i2c/max96717.c
@@ -16,6 +16,7 @@ 
 #include <linux/regmap.h>
 
 #include <media/v4l2-cci.h>
+#include <media/v4l2-ctrls.h>
 #include <media/v4l2-fwnode.h>
 #include <media/v4l2-subdev.h>
 
@@ -38,9 +39,35 @@ 
 #define MAX96717_DEV_REV_MASK GENMASK(3, 0)
 
 /* VID_TX Z */
+#define MAX96717_VIDEO_TX0 CCI_REG8(0x110)
+#define MAX96717_VIDEO_AUTO_BPP BIT(3)
 #define MAX96717_VIDEO_TX2 CCI_REG8(0x112)
 #define MAX96717_VIDEO_PCLKDET BIT(7)
 
+/* VTX_Z */
+#define MAX96717_VTX0                  CCI_REG8(0x24e)
+#define MAX96717_VTX1                  CCI_REG8(0x24f)
+#define MAX96717_PATTERN_CLK_FREQ      GENMASK(3, 1)
+#define MAX96717_VTX_VS_DLY            CCI_REG24(0x250)
+#define MAX96717_VTX_VS_HIGH           CCI_REG24(0x253)
+#define MAX96717_VTX_VS_LOW            CCI_REG24(0x256)
+#define MAX96717_VTX_V2H               CCI_REG24(0x259)
+#define MAX96717_VTX_HS_HIGH           CCI_REG16(0x25c)
+#define MAX96717_VTX_HS_LOW            CCI_REG16(0x25e)
+#define MAX96717_VTX_HS_CNT            CCI_REG16(0x260)
+#define MAX96717_VTX_V2D               CCI_REG24(0x262)
+#define MAX96717_VTX_DE_HIGH           CCI_REG16(0x265)
+#define MAX96717_VTX_DE_LOW            CCI_REG16(0x267)
+#define MAX96717_VTX_DE_CNT            CCI_REG16(0x269)
+#define MAX96717_VTX29                 CCI_REG8(0x26b)
+#define MAX96717_VTX_MODE              GENMASK(1, 0)
+#define MAX96717_VTX_GRAD_INC          CCI_REG8(0x26c)
+#define MAX96717_VTX_CHKB_COLOR_A      CCI_REG24(0x26d)
+#define MAX96717_VTX_CHKB_COLOR_B      CCI_REG24(0x270)
+#define MAX96717_VTX_CHKB_RPT_CNT_A    CCI_REG8(0x273)
+#define MAX96717_VTX_CHKB_RPT_CNT_B    CCI_REG8(0x274)
+#define MAX96717_VTX_CHKB_ALT          CCI_REG8(0x275)
+
 /* GPIO */
 #define MAX96717_NUM_GPIO         11
 #define MAX96717_GPIO_REG_A(gpio) CCI_REG8(0x2be + (gpio) * 3)
@@ -82,6 +109,12 @@ 
 /* MISC */
 #define PIO_SLEW_1 CCI_REG8(0x570)
 
+enum max96717_vpg_mode {
+	MAX96717_VPG_DISABLED = 0,
+	MAX96717_VPG_CHECKERBOARD = 1,
+	MAX96717_VPG_GRADIENT = 2,
+};
+
 struct max96717_priv {
 	struct i2c_client		  *client;
 	struct regmap			  *regmap;
@@ -89,6 +122,7 @@  struct max96717_priv {
 	struct v4l2_mbus_config_mipi_csi2 mipi_csi2;
 	struct v4l2_subdev                sd;
 	struct media_pad                  pads[MAX96717_PORTS];
+	struct v4l2_ctrl_handler          ctrl_handler;
 	struct v4l2_async_notifier        notifier;
 	struct v4l2_subdev                *source_sd;
 	u16                               source_sd_pad;
@@ -96,6 +130,7 @@  struct max96717_priv {
 	u8                                pll_predef_index;
 	struct clk_hw                     clk_hw;
 	struct gpio_chip                  gpio_chip;
+	enum max96717_vpg_mode            pattern;
 };
 
 static inline struct max96717_priv *sd_to_max96717(struct v4l2_subdev *sd)
@@ -131,6 +166,118 @@  static inline int max96717_start_csi(struct max96717_priv *priv, bool start)
 			       start ? MAX96717_START_PORT_B : 0, NULL);
 }
 
+static int max96717_apply_patgen_timing(struct max96717_priv *priv,
+					struct v4l2_subdev_state *state)
+{
+	struct v4l2_mbus_framefmt *fmt =
+		v4l2_subdev_state_get_format(state, MAX96717_PAD_SOURCE);
+	const u32 h_active = fmt->width;
+	const u32 h_fp = 88;
+	const u32 h_sw = 44;
+	const u32 h_bp = 148;
+	u32 h_tot;
+	const u32 v_active = fmt->height;
+	const u32 v_fp = 4;
+	const u32 v_sw = 5;
+	const u32 v_bp = 36;
+	u32 v_tot;
+	int ret = 0;
+
+	h_tot = h_active + h_fp + h_sw + h_bp;
+	v_tot = v_active + v_fp + v_sw + v_bp;
+
+	/* 75 Mhz pixel clock */
+	cci_update_bits(priv->regmap, MAX96717_VTX1,
+			MAX96717_PATTERN_CLK_FREQ, 0xa, &ret);
+
+	dev_info(&priv->client->dev, "height: %d width: %d\n", fmt->height,
+		 fmt->width);
+
+	cci_write(priv->regmap, MAX96717_VTX_VS_DLY, 0, &ret);
+	cci_write(priv->regmap, MAX96717_VTX_VS_HIGH, v_sw * h_tot, &ret);
+	cci_write(priv->regmap, MAX96717_VTX_VS_LOW,
+		  (v_active + v_fp + v_bp) * h_tot, &ret);
+	cci_write(priv->regmap, MAX96717_VTX_HS_HIGH, h_sw, &ret);
+	cci_write(priv->regmap, MAX96717_VTX_HS_LOW, h_active + h_fp + h_bp,
+		  &ret);
+	cci_write(priv->regmap, MAX96717_VTX_V2D,
+		  h_tot * (v_sw + v_bp) + (h_sw + h_bp), &ret);
+	cci_write(priv->regmap, MAX96717_VTX_HS_CNT, v_tot, &ret);
+	cci_write(priv->regmap, MAX96717_VTX_DE_HIGH, h_active, &ret);
+	cci_write(priv->regmap, MAX96717_VTX_DE_LOW, h_fp + h_sw + h_bp,
+		  &ret);
+	cci_write(priv->regmap, MAX96717_VTX_DE_CNT, v_active, &ret);
+	/* B G R */
+	cci_write(priv->regmap, MAX96717_VTX_CHKB_COLOR_A, 0xfecc00, &ret);
+	/* B G R */
+	cci_write(priv->regmap, MAX96717_VTX_CHKB_COLOR_B, 0x006aa7, &ret);
+	cci_write(priv->regmap, MAX96717_VTX_CHKB_RPT_CNT_A, 0x3c, &ret);
+	cci_write(priv->regmap, MAX96717_VTX_CHKB_RPT_CNT_B, 0x3c, &ret);
+	cci_write(priv->regmap, MAX96717_VTX_CHKB_ALT, 0x3c, &ret);
+	cci_write(priv->regmap, MAX96717_VTX_GRAD_INC, 0x10, &ret);
+
+	return ret;
+}
+
+static int max96717_apply_patgen(struct max96717_priv *priv,
+				 struct v4l2_subdev_state *state)
+{
+	unsigned int val;
+	int ret = 0;
+
+	if (priv->pattern)
+		ret = max96717_apply_patgen_timing(priv, state);
+
+	cci_write(priv->regmap, MAX96717_VTX0, priv->pattern ? 0xfb : 0,
+		  &ret);
+
+	val = FIELD_PREP(MAX96717_VTX_MODE, priv->pattern);
+	cci_update_bits(priv->regmap, MAX96717_VTX29, MAX96717_VTX_MODE,
+			val, &ret);
+	return ret;
+}
+
+static int max96717_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct max96717_priv *priv =
+		container_of(ctrl->handler, struct max96717_priv, ctrl_handler);
+	int ret;
+
+	switch (ctrl->id) {
+	case V4L2_CID_TEST_PATTERN:
+		if (priv->enabled_source_streams)
+			return -EBUSY;
+		priv->pattern = ctrl->val;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* Use bpp from bpp register */
+	ret = cci_update_bits(priv->regmap, MAX96717_VIDEO_TX0,
+			      MAX96717_VIDEO_AUTO_BPP,
+			      priv->pattern ? 0 : MAX96717_VIDEO_AUTO_BPP,
+			      NULL);
+
+	/*
+	 * Pattern generator doesn't work with tunnel mode.
+	 * Needs RGB color format and deserializer tunnel mode must be disabled.
+	 */
+	return cci_update_bits(priv->regmap, MAX96717_MIPI_RX_EXT11,
+			       MAX96717_TUN_MODE,
+			       priv->pattern ? 0 : MAX96717_TUN_MODE, &ret);
+}
+
+static const char * const max96717_test_pattern[] = {
+	"Disabled",
+	"Checkerboard",
+	"Gradient"
+};
+
+static const struct v4l2_ctrl_ops max96717_ctrl_ops = {
+	.s_ctrl = max96717_s_ctrl,
+};
+
 static int max96717_gpiochip_get(struct gpio_chip *gpiochip,
 				 unsigned int offset)
 {
@@ -352,20 +499,28 @@  static int max96717_enable_streams(struct v4l2_subdev *sd,
 	u64 sink_streams;
 	int ret;
 
-	sink_streams = v4l2_subdev_state_xlate_streams(state,
-						       MAX96717_PAD_SOURCE,
-						       MAX96717_PAD_SINK,
-						       &streams_mask);
-
 	if (!priv->enabled_source_streams)
 		max96717_start_csi(priv, true);
 
-	ret = v4l2_subdev_enable_streams(priv->source_sd, priv->source_sd_pad,
-					 sink_streams);
-	if (ret) {
-		dev_err(dev, "Fail to start streams:%llu on remote subdev\n",
-			sink_streams);
+	ret = max96717_apply_patgen(priv, state);
+	if (ret)
 		goto stop_csi;
+
+	if (!priv->pattern) {
+		sink_streams =
+			v4l2_subdev_state_xlate_streams(state,
+							MAX96717_PAD_SOURCE,
+							MAX96717_PAD_SINK,
+							&streams_mask);
+
+		ret = v4l2_subdev_enable_streams(priv->source_sd,
+						 priv->source_sd_pad,
+						 sink_streams);
+		if (ret) {
+			dev_err(dev, "Fail to start streams:%llu on remote subdev\n",
+				sink_streams);
+			goto stop_csi;
+		}
 	}
 
 	priv->enabled_source_streams |= streams_mask;
@@ -394,13 +549,23 @@  static int max96717_disable_streams(struct v4l2_subdev *sd,
 	if (!priv->enabled_source_streams)
 		max96717_start_csi(priv, false);
 
-	sink_streams = v4l2_subdev_state_xlate_streams(state,
-						       MAX96717_PAD_SOURCE,
-						       MAX96717_PAD_SINK,
-						       &streams_mask);
+	if (!priv->pattern) {
+		int ret;
+
+		sink_streams =
+			v4l2_subdev_state_xlate_streams(state,
+							MAX96717_PAD_SOURCE,
+							MAX96717_PAD_SINK,
+							&streams_mask);
 
-	return v4l2_subdev_disable_streams(priv->source_sd, priv->source_sd_pad,
-					   sink_streams);
+		ret = v4l2_subdev_disable_streams(priv->source_sd,
+						  priv->source_sd_pad,
+						  sink_streams);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
 }
 
 static const struct v4l2_subdev_pad_ops max96717_pad_ops = {
@@ -513,6 +678,19 @@  static int max96717_subdev_init(struct max96717_priv *priv)
 	v4l2_i2c_subdev_init(&priv->sd, priv->client, &max96717_subdev_ops);
 	priv->sd.internal_ops = &max96717_internal_ops;
 
+	v4l2_ctrl_handler_init(&priv->ctrl_handler, 1);
+	priv->sd.ctrl_handler = &priv->ctrl_handler;
+
+	v4l2_ctrl_new_std_menu_items(&priv->ctrl_handler,
+				     &max96717_ctrl_ops,
+				     V4L2_CID_TEST_PATTERN,
+				     ARRAY_SIZE(max96717_test_pattern) - 1,
+				     0, 0, max96717_test_pattern);
+	if (priv->ctrl_handler.error) {
+		ret = priv->ctrl_handler.error;
+		goto err_free_ctrl;
+	}
+
 	priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
 	priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
 	priv->sd.entity.ops = &max96717_entity_ops;
@@ -552,6 +730,8 @@  static int max96717_subdev_init(struct max96717_priv *priv)
 	v4l2_subdev_cleanup(&priv->sd);
 err_entity_cleanup:
 	media_entity_cleanup(&priv->sd.entity);
+err_free_ctrl:
+	v4l2_ctrl_handler_free(&priv->ctrl_handler);
 
 	return ret;
 }
@@ -563,6 +743,7 @@  static void max96717_subdev_uninit(struct max96717_priv *priv)
 	v4l2_async_nf_cleanup(&priv->notifier);
 	v4l2_subdev_cleanup(&priv->sd);
 	media_entity_cleanup(&priv->sd.entity);
+	v4l2_ctrl_handler_free(&priv->ctrl_handler);
 }
 
 struct max96717_pll_predef_freq {