Re: [PATCH 20/21] media: i2c: it6625: use centrally managed active state

Sakari Ailus <[email protected]>
Newsgroups gmane.linux.kernel,gmane.linux.drivers.video-input-infrastructure,gmane.linux.drivers.devicetree
Organization Intel Finland Oy - BIC 0357606-4 - c/o Alberga Business Park, 6 krs, Bertel Jungin Aukio 5, 02600 Espoo
Message-ID <[email protected]>
Hi Hermes,

On Fri, Sep 18, 2026 at 04:57:35PM +0800, Hermes Wu via B4 Relay wrote:
> From: Hermes Wu <[email protected]>
> 
> Adopt the subdev active-state model instead of a private driver mutex
> and driver-local format fields:
> 
>  - Share it6625_lock as both sd->state_lock and sd->ctrl_handler->lock
>    (set hdl->lock right after v4l2_ctrl_handler_init(), which resets
>    it, and before any control is created), matching the documented
>    model in Documentation/driver-api/media/v4l2-subdev.rst. Call
>    v4l2_subdev_init_finalize() after media_entity_pads_init() and
>    before any code that touches active state, always before
>    v4l2_async_register_subdev().
> 
>  - Remove csi_format/mbus_fmt_code from struct it6625 and store the
>    complete format -- code, colorspace, and width/height/field derived
>    from the configured DV timings -- in the pad format of each
>    v4l2_subdev_state instead. it6625_set_fmt() now operates directly
>    on the state the core hands it (already locked, and for ACTIVE
>    already sd->active_state) instead of taking it6625_lock itself.
>    it6625_get_fmt() is deleted; .get_fmt is now the core's
>    v4l2_subdev_get_fmt(). it6625_init_state() seeds a state from the
>    current active format when one exists (later TRY-state allocation)
>    or from driver defaults when it doesn't (the very first, pre-
>    assignment call that becomes the active state itself).
> 
>  - Keep it6625->timings as driver-private DV-timings state, but project
>    its width/height/field onto the active pad format every time it
>    changes (it6625_update_timings_if_changed(), it6625_clear_timings())
>    so VIDIOC_SUBDEV_G_FMT stays consistent with
>    VIDIOC_SUBDEV_G_DV_TIMINGS. it6625_initial_setup() and
>    it6625_log_status() now read the media-bus code from the locked
>    active format instead of the removed fields.
> 
>  - Make ACTIVE .set_fmt() transactional: return -EBUSY while streaming,
>    and commit the new code/colorspace to active state only after the
>    MIPI register writes that implement it actually succeed. This
>    requires it6625_enable_stream_locked(), it6625_enable_stream(), and
>    it6625_set_mipi_config_locked() to return int and propagate the
>    first register/config-update failure; all existing callers are
>    updated.

I don't think all the above is relevant in the commit message.

> 
> Every it6625_lock acquisition site was re-audited against the core's
> actual locking contract (which ioctls the core state-locks, what
> v4l2_ctrl_handler_init()/_free() and v4l2_subdev_cleanup() actually
> touch) to confirm none of the driver's own lock-taking helpers are
> reachable from a path where the core already holds it6625_lock, and
> that init/teardown ordering keeps hdl->lock valid whenever
> v4l2_ctrl_handler_free() runs. The full trace is recorded in
> it6625-follow-up-patch/patch19-lock-audit.md (not part of this commit)
> for review -- it is static analysis only, since no IT6625/IT6626
> hardware is available here to exercise this with lockdep enabled.

This paragraph could go to the cover letter.

> 
> Signed-off-by: Hermes Wu <[email protected]>
> ---
>  drivers/media/i2c/it6625.c | 220 ++++++++++++++++++++++++++++-----------------
>  1 file changed, 138 insertions(+), 82 deletions(-)
> 
> diff --git a/drivers/media/i2c/it6625.c b/drivers/media/i2c/it6625.c
> index ec6aaa878471ff210264a35e4aa66dfb1e63ed3b..550eb95c6f74a81fc11cc055b4590159ac50e969 100644
> --- a/drivers/media/i2c/it6625.c
> +++ b/drivers/media/i2c/it6625.c
> @@ -255,7 +255,15 @@ struct it6625 {
>  	struct regmap *it6625_regmap;
>  	enum it6625_chip_type chip_type;
>  
> -	/* protects concurrent access to the chip's registers and state */
> +	/*
> +	 * Protects concurrent access to the chip's registers and state.
> +	 * Also shared as sd.state_lock and hdl.lock (see

This doesn't hold anymore, does it? Typically drivers do without such locks
as the subdev state lock is used instead -- assigning your own lock also
has the effect the same lock is used for try states, too.

> +	 * Documentation/driver-api/media/v4l2-subdev.rst), so the V4L2
> +	 * core already holds it across .get_fmt/.set_fmt/.enable_streams/
> +	 * .disable_streams and control updates -- callers reached only
> +	 * through those paths must use the *_locked() helpers instead of
> +	 * taking it again.
> +	 */
>  	struct mutex it6625_lock;
>  	/* serializes the complete VIDIOC_S_EDID sequence against itself */
>  	struct mutex edid_lock;
> @@ -291,8 +299,6 @@ struct it6625 {
>  	u8 csi_lanes;
>  	u8 port_num;
>  	enum v4l2_mbus_type bus_type;
> -	u8 csi_format;
> -	u32 mbus_fmt_code;
>  	/* number of EDID blocks currently loaded, protected by edid_lock */
>  	u8 edid_blocks;
>  
> @@ -911,10 +917,11 @@ static int it6625_v4l2_sd_ctrl_update(struct v4l2_subdev *sd)
>  	return it6625_s_ctrl_audio_present(sd);
>  }
>  
> -static void it6625_enable_stream_locked(struct it6625 *it6625, bool enable)
> +static int it6625_enable_stream_locked(struct it6625 *it6625, bool enable)
>  {
>  	struct v4l2_subdev *sd = &it6625->sd;
>  	int val;
> +	int err;
>  
>  	lockdep_assert_held(&it6625->it6625_lock);
>  
> @@ -922,27 +929,34 @@ static void it6625_enable_stream_locked(struct it6625 *it6625, bool enable)
>  		 __func__, enable ? "en" : "dis");
>  
>  	val = enable ? B_MIPI_OUTPUT : 0;
> -	it6625_set_bits(it6625, REG_MIPI_CONTROL, B_MIPI_OUTPUT, val);
> -	it6625_update_config(it6625);
> +	err = it6625_set_bits(it6625, REG_MIPI_CONTROL, B_MIPI_OUTPUT, val);
> +	if (err < 0)
> +		return err;
> +
> +	return it6625_update_config(it6625);
>  }
>  
> -static void it6625_enable_stream(struct it6625 *it6625, bool enable)
> +static int it6625_enable_stream(struct it6625 *it6625, bool enable)
>  {
>  	guard(mutex)(&it6625->it6625_lock);
> -	it6625_enable_stream_locked(it6625, enable);
> +	return it6625_enable_stream_locked(it6625, enable);
>  }
>  
> -static void it6625_set_mipi_config_locked(struct it6625 *it6625, u32 cfg_val)
> +static int it6625_set_mipi_config_locked(struct it6625 *it6625, u32 cfg_val)
>  {
>  	u8 mipi_data_type;
> +	int err;
>  
>  	lockdep_assert_held(&it6625->it6625_lock);
>  
>  	dev_dbg(it6625->dev, "mipi_data_type = 0x%x", cfg_val);
>  
>  	mipi_data_type = cfg_val & 0xFF;
> -	it6625_write_byte(it6625, REG_MIPI_DATA_TYPE, mipi_data_type);
> -	it6625_update_config(it6625);
> +	err = it6625_write_byte(it6625, REG_MIPI_DATA_TYPE, mipi_data_type);
> +	if (err < 0)
> +		return err;
> +
> +	return it6625_update_config(it6625);
>  }
>  
>  static inline unsigned int fps_from_bt_timings(const struct v4l2_bt_timings *t)
> @@ -957,10 +971,18 @@ static inline unsigned int fps_from_bt_timings(const struct v4l2_bt_timings *t)
>  
>  static void it6625_initial_setup(struct it6625 *it6625)
>  {
> +	struct v4l2_subdev *sd = &it6625->sd;
> +	struct v4l2_mbus_framefmt *fmt;
> +	int idx;
>  	int val = 0;
>  
>  	guard(mutex)(&it6625->it6625_lock);
>  
> +	fmt = v4l2_subdev_state_get_format(v4l2_subdev_get_locked_active_state(sd), 0);
> +	idx = it6625_csi_mbus_code_idx(fmt->code);
> +	if (idx < 0)
> +		idx = 0;
> +
>  	/*
>  	 * REG_MIPI_CFG[0:2] lane count field: 1 lane -> 0, 2 lanes -> 1,
>  	 * 3 lanes (C-PHY only) -> 3, 4 lanes (D-PHY only) -> 3.
> @@ -984,7 +1006,7 @@ static void it6625_initial_setup(struct it6625 *it6625)
>  		val |= FIELD_PREP(B_MIPI_SPLIT, 1);
>  
>  	it6625_write_byte(it6625, REG_MIPI_CFG, val);
> -	it6625_write_byte(it6625, REG_MIPI_DATA_TYPE, it6625->csi_format);
> +	it6625_write_byte(it6625, REG_MIPI_DATA_TYPE, it6625_formats[idx].csi_format);
>  	it6625_write_byte(it6625, REG_MIPI_CONTROL, 0x00);
>  	it6625_write_byte(it6625, REG_RX_CFG, 0x00);
>  
> @@ -1140,10 +1162,29 @@ static void it6625_get_timings(struct it6625 *it6625,
>  	*timings = it6625->timings;
>  }
>  
> +/*
> + * Project a DV-timings struct's width/height/field onto an active pad
> + * format. Caller must hold it6625_lock (== the active state's lock).
> + */
> +static void it6625_fill_timings_format(const struct v4l2_dv_timings *timings,
> +				       struct v4l2_mbus_framefmt *fmt)
> +{
> +	fmt->width = timings->bt.width;
> +	fmt->height = timings->bt.height;
> +	fmt->field = timings->bt.interlaced == V4L2_DV_INTERLACED ?
> +		     V4L2_FIELD_INTERLACED : V4L2_FIELD_NONE;
> +}
> +
>  static void it6625_clear_timings(struct it6625 *it6625)
>  {
> +	struct v4l2_subdev *sd = &it6625->sd;
> +	struct v4l2_mbus_framefmt *fmt;
> +
>  	guard(mutex)(&it6625->it6625_lock);
>  	memset(&it6625->timings, 0, sizeof(it6625->timings));
> +
> +	fmt = v4l2_subdev_state_get_format(v4l2_subdev_get_locked_active_state(sd), 0);
> +	it6625_fill_timings_format(&it6625->timings, fmt);
>  }
>  
>  static void it6625_irq_hdmi_5v_change(struct it6625 *it6625)
> @@ -1362,7 +1403,10 @@ static int it6625_log_status(struct v4l2_subdev *sd)
>  
>  	/* snapshot together so the reported pair was actually configured together */
>  	scoped_guard(mutex, &it6625->it6625_lock) {
> -		mbus_fmt_code = it6625->mbus_fmt_code;
> +		struct v4l2_mbus_framefmt *fmt =
> +			v4l2_subdev_state_get_format(v4l2_subdev_get_locked_active_state(sd), 0);
> +
> +		mbus_fmt_code = fmt->code;
>  		bt = it6625->timings.bt;
>  	}
>  
> @@ -1415,6 +1459,9 @@ static int
>  it6625_update_timings_if_changed(struct it6625 *it6625,
>  				 const struct v4l2_dv_timings *timings)
>  {
> +	struct v4l2_subdev *sd = &it6625->sd;
> +	struct v4l2_mbus_framefmt *fmt;
> +
>  	guard(mutex)(&it6625->it6625_lock);
>  
>  	if (v4l2_match_dv_timings(&it6625->timings, timings, 0, false))
> @@ -1425,6 +1472,9 @@ it6625_update_timings_if_changed(struct it6625 *it6625,
>  
>  	it6625->timings = *timings;
>  
> +	fmt = v4l2_subdev_state_get_format(v4l2_subdev_get_locked_active_state(sd), 0);
> +	it6625_fill_timings_format(&it6625->timings, fmt);
> +
>  	return 1;
>  }
>  
> @@ -1457,8 +1507,7 @@ static int it6625_s_stream(struct v4l2_subdev *sd, int enable)
>  {
>  	struct it6625 *it6625 = sd_to_6625(sd);
>  
> -	it6625_enable_stream(it6625, enable);
> -	return 0;
> +	return it6625_enable_stream(it6625, enable);
>  }
>  
>  static int it6625_enum_mbus_code(struct v4l2_subdev *sd,
> @@ -1586,84 +1635,56 @@ static inline u32 format_to_colorspace(u8 csi_format)
>  	}
>  }
>  
> -static int it6625_get_fmt(struct v4l2_subdev *sd,
> -			  struct v4l2_subdev_state *sd_state,
> -			  struct v4l2_subdev_format *format)
> -{
> -	struct it6625 *it6625 = sd_to_6625(sd);
> -	struct v4l2_dv_timings timings;
> -
> -	if (format->pad != 0)
> -		return -EINVAL;
> -
> -	it6625_get_timings(it6625, &timings);
> -	format->format.width = timings.bt.width;
> -	format->format.height = timings.bt.height;
> -	format->format.field = timings.bt.interlaced == V4L2_DV_INTERLACED ?
> -			       V4L2_FIELD_INTERLACED : V4L2_FIELD_NONE;
> -
> -	if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
> -		struct v4l2_mbus_framefmt *fmt;
> -
> -		fmt = v4l2_subdev_state_get_format(sd_state, format->pad);
> -		format->format.code = fmt->code;
> -		format->format.colorspace = fmt->colorspace;
> -	} else {
> -		scoped_guard(mutex, &it6625->it6625_lock) {
> -			format->format.colorspace =
> -				format_to_colorspace(it6625->csi_format);
> -			format->format.code = it6625->mbus_fmt_code;
> -		}
> -	}
> -
> -	return 0;
> -}
> -
>  static int it6625_set_fmt(struct v4l2_subdev *sd,
>  			  struct v4l2_subdev_state *sd_state,
>  			  struct v4l2_subdev_format *format)
>  {
>  	struct it6625 *it6625 = sd_to_6625(sd);
> -	u32 mbus_fmt_code = format->format.code;
> +	struct v4l2_mbus_framefmt *fmt;
> +	u32 colorspace;
> +	int idx;
>  	int ret;
>  
> -	ret = it6625_get_fmt(sd, sd_state, format);
> -	format->format.code = mbus_fmt_code;
> -
> -	if (ret)
> -		return ret;
> -
> -	ret = it6625_csi_mbus_code_idx(mbus_fmt_code);
> +	if (format->pad != 0)
> +		return -EINVAL;
>  
> -	if (ret < 0) {
> +	idx = it6625_csi_mbus_code_idx(format->format.code);
> +	if (idx < 0) {
>  		v4l2_dbg(1, debug, sd,
>  			 "%s: unsupported format code 0x%x, falling back to default",
> -			 __func__, mbus_fmt_code);
> -		ret = 0;
> -		mbus_fmt_code = it6625_formats[ret].mbus_fmt_code;
> -		format->format.code = mbus_fmt_code;
> +			 __func__, format->format.code);
> +		idx = 0;
>  	}
>  
> -	if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
> -		struct v4l2_mbus_framefmt *fmt;
> +	colorspace = format_to_colorspace(it6625_formats[idx].csi_format);
>  
> -		fmt = v4l2_subdev_state_get_format(sd_state, format->pad);
> -		fmt->code = format->format.code;
> -		fmt->colorspace = format_to_colorspace(it6625_formats[ret].csi_format);
> -		format->format.colorspace = fmt->colorspace;
> +	/* fmt already carries this state's width/height/field; leave them alone */
> +	fmt = v4l2_subdev_state_get_format(sd_state, format->pad);
> +
> +	if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
> +		fmt->code = it6625_formats[idx].mbus_fmt_code;
> +		fmt->colorspace = colorspace;
> +		format->format = *fmt;

Could you rework the code to keep this for active and try paths?

>  		v4l2_dbg(1, debug, sd, "%s: try format code = 0x%x",
>  			 __func__, format->format.code);
>  		return 0;
>  	}
>  
> -	scoped_guard(mutex, &it6625->it6625_lock) {
> -		it6625->csi_format = it6625_formats[ret].csi_format;
> -		it6625->mbus_fmt_code = format->format.code;
> -		it6625_enable_stream_locked(it6625, false);
> -		it6625_set_mipi_config_locked(it6625, it6625->csi_format);
> -	}
> +	if (v4l2_subdev_is_streaming(sd))
> +		return -EBUSY;
>  
> -	format->format.colorspace = format_to_colorspace(it6625_formats[ret].csi_format);
> +	ret = it6625_enable_stream_locked(it6625, false);
> +	if (ret)
> +		return ret;
> +
> +	ret = it6625_set_mipi_config_locked(it6625, it6625_formats[idx].csi_format);

There are a few lines longer than 80; please split unless there's a
tangible reason to do otherwise.

> +	if (ret)
> +		return ret;
> +
> +	/* commit to active state only after hardware programming succeeded */
> +	fmt->code = it6625_formats[idx].mbus_fmt_code;
> +	fmt->colorspace = colorspace;
> +	format->format = *fmt;
>  
>  	return 0;
>  }
> @@ -1780,7 +1801,7 @@ static const struct v4l2_subdev_video_ops it6625_video_ops = {
>  static const struct v4l2_subdev_pad_ops it6625_pad_ops = {
>  	.enum_mbus_code = it6625_enum_mbus_code,
>  	.set_fmt = it6625_set_fmt,
> -	.get_fmt = it6625_get_fmt,
> +	.get_fmt = v4l2_subdev_get_fmt,
>  	.get_edid = it6625_g_edid,
>  	.set_edid = it6625_s_edid,
>  	.enum_dv_timings = it6625_enum_dv_timings,
> @@ -1800,8 +1821,26 @@ static const struct v4l2_subdev_ops it6625_ops = {
>  static int it6625_init_state(struct v4l2_subdev *sd,
>  			     struct v4l2_subdev_state *sd_state)
>  {
> +	struct it6625 *it6625 = sd_to_6625(sd);
> +	struct v4l2_subdev_state *active = v4l2_subdev_get_locked_active_state(sd);

This function should work the same way independently of whether the state
is active or not.

>  	struct v4l2_mbus_framefmt *fmt = v4l2_subdev_state_get_format(sd_state, 0);
>  
> +	/*
> +	 * The very first call initializes what becomes sd->active_state
> +	 * itself, before it's assigned -- active is NULL then, and this
> +	 * state gets the driver's own defaults. Every later call (opening
> +	 * a new file handle) initializes a fresh TRY state while the
> +	 * active state already exists and is locked by the same mutex
> +	 * (state->lock is assigned before init_state() runs), so seed it
> +	 * from the current active format instead of reverting to boot
> +	 * defaults.
> +	 */
> +	if (active) {
> +		*fmt = *v4l2_subdev_state_get_format(active, 0);
> +		return 0;
> +	}
> +
> +	it6625_fill_timings_format(&it6625->timings, fmt);
>  	fmt->code = it6625_formats[0].mbus_fmt_code;
>  	fmt->colorspace = format_to_colorspace(it6625_formats[0].csi_format);
>  
> @@ -1842,6 +1881,8 @@ static int it6625_v4l2_init_controls(struct v4l2_subdev *sd)
>  			   it6625->csi_lanes == 3;
>  
>  	v4l2_ctrl_handler_init(hdl, 4);
> +	hdl->lock = &it6625->it6625_lock;
> +
>  	it6625->ctrl_5v_detect =
>  		v4l2_ctrl_new_std(hdl, NULL, V4L2_CID_DV_RX_POWER_PRESENT,
>  				  0, 1, 0, 0);
> @@ -2050,8 +2091,6 @@ static void it6625_init_data(struct it6625 *it6625)
>  	static struct v4l2_dv_timings default_timing =
>  			V4L2_DV_BT_CEA_1920X1080P60;
>  
> -	it6625->csi_format = it6625_formats[0].csi_format;
> -	it6625->mbus_fmt_code = it6625_formats[0].mbus_fmt_code;
>  	it6625->timings = default_timing;
>  	/* firmware ships with a verified 2-block default EDID in EDID RAM */
>  	it6625->edid_blocks = 2;
> @@ -2246,9 +2285,16 @@ static int it6625_probe(struct i2c_client *client)
>  		goto err_clean_work_queues;
>  	}
>  
> +	sd->state_lock = &it6625->it6625_lock;
> +	err = v4l2_subdev_init_finalize(sd);
> +	if (err) {
> +		dev_err(it6625->dev, "%s %d err=%d", __func__, __LINE__, err);
> +		goto err_clean_hdl;
> +	}
> +
>  	err = v4l2_ctrl_handler_setup(sd->ctrl_handler);
>  	if (err)
> -		goto err_clean_hdl;
> +		goto err_clean_state;
>  
>  	it6625->cec_adap = cec_allocate_adapter(&it6625_cec_adap_ops,
>  						it6625, dev_name(it6625->dev),
> @@ -2259,7 +2305,7 @@ static int it6625_probe(struct i2c_client *client)
>  	if (IS_ERR(it6625->cec_adap)) {
>  		err = PTR_ERR(it6625->cec_adap);
>  		dev_err(it6625->dev, "%s %d", __func__, __LINE__);
> -		goto err_clean_hdl;
> +		goto err_clean_state;
>  	}
>  
>  	err = cec_register_adapter(it6625->cec_adap, &client->dev);
> @@ -2267,7 +2313,7 @@ static int it6625_probe(struct i2c_client *client)
>  		dev_err(it6625->dev, "%s: failed to register the cec device", __func__);
>  		cec_delete_adapter(it6625->cec_adap);
>  		it6625->cec_adap = NULL;
> -		goto err_clean_hdl;
> +		goto err_clean_state;
>  	}
>  
>  	it6625_debugfs_init(it6625, client);
> @@ -2294,6 +2340,8 @@ static int it6625_probe(struct i2c_client *client)
>  	v4l2_debugfs_if_free(it6625->infoframes);
>  	debugfs_remove_recursive(it6625->debugfs_dir);
>  	cec_unregister_adapter(it6625->cec_adap);
> +err_clean_state:
> +	v4l2_subdev_cleanup(sd);
>  err_clean_hdl:
>  	media_entity_cleanup(&sd->entity);
>  	v4l2_ctrl_handler_free(&it6625->hdl);
> @@ -2330,12 +2378,20 @@ static void it6625_remove(struct i2c_client *client)
>  
>  	debugfs_remove_recursive(it6625->debugfs_dir);
>  	cec_unregister_adapter(it6625->cec_adap);
> +
> +	/*
> +	 * v4l2_subdev_cleanup()/v4l2_ctrl_handler_free() take it6625_lock
> +	 * (shared as state_lock/hdl.lock), so they must run before it's
> +	 * destroyed.
> +	 */

This comment is hardly useful; the same applies to pretty much all drivers
using sub-device state and V4L2 controls.

> +	v4l2_subdev_cleanup(sd);
> +	media_entity_cleanup(&sd->entity);
> +	v4l2_ctrl_handler_free(&it6625->hdl);
> +
>  	mutex_destroy(&it6625->it6625_lock);
>  	mutex_destroy(&it6625->edid_lock);
>  	mutex_destroy(&it6625->if_read_lock);
>  	mutex_destroy(&it6625->if_state_lock);
> -	media_entity_cleanup(&sd->entity);
> -	v4l2_ctrl_handler_free(&it6625->hdl);
>  }
>  
>  static const struct i2c_device_id it6625_id[] = {
> 

-- 
Regards,

Sakari Ailus
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.