Devicetree
 help / color / mirror / Atom feed
From: Lachlan Michael <Lachlan.Michael@sony.com>
To: Jai Luthra <jai.luthra@ideasonboard.com>,
	devicetree@vger.kernel.org, hverkuil+cisco@kernel.org,
	laurent.pinchart@ideasonboard.com, linux-media@vger.kernel.org,
	mchehab@kernel.org, sakari.ailus@linux.intel.com
Cc: robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org,
	kieran.bingham@ideasonboard.com, Ryuichi.Tadano@sony.com,
	Kengo.Hayasaka@sony.com, Tim.Bird@sony.com,
	Kazumi.A.Sato@sony.com, linux-kernel@vger.kernel.org
Subject: Re: [PATCH v2 2/2] media: i2c: Add Sony IMX908 image sensor driver
Date: Wed, 19 Aug 2026 14:06:57 +0900	[thread overview]
Message-ID: <38ce1e44-2dd6-410d-abc5-554564cb1252@sony.com> (raw)
In-Reply-To: <178608558219.4139729.1786136799159384307@freya>

Dear Jai,

Thank-you for the review,

On 8/7/2026 3:53 PM, Jai Luthra wrote:
> Hi Lachlan, Thank you for the patch! The driver is mostly in good shape, 
> except a few small things and requests for information that Sony is in 
> the best position to help us with. Quoting Lachlan Michael (2026-08-06 
> 12: 39: 34) > The Sony IMX908
> 
> Hi Lachlan,
> 
> Thank you for the patch!
> 
> The driver is mostly in good shape, except a few small things and requests
> for information that Sony is in the best position to help us with.
> 
> Quoting Lachlan Michael (2026-08-06 12:39:34)
>> The Sony IMX908 is an 8.39 megapixel (3856x2176) CMOS image sensor
>> with a MIPI CSI-2 output interface, configurable as either 2 or 4
>> data lanes.
>> 
>> Add a V4L2 sub-device driver for the sensor. The driver supports
>> RAW10 and RAW12 output formats, exposure and analogue gain controls,
>> horizontal and vertical flipping, horizontal and vertical blanking
>> controls, window cropping and test pattern generation.
>> 
>> HDR modes and RAW16 output are not currently supported.
>> 
>> Signed-off-by: Lachlan Michael <lachlan.michael@sony.com>
>> ---
>> Changes in v2:
>> - Treat the pixel rate as a fixed sensor property (594 MHz, 8 px/clock),
>>   read-only.
>> - Compute HMAX from both the array and MIPI link floors.
>> - Express HBLANK in pixels with a step of 8; keep HMAX fixed in crop
>>   mode.
>> - Drop struct imx908_mode; cache hmax/vmax directly.
>> - Change the link-frequency table to s64.
>> - Fix the probe error-unwind ordering.
>> - Be silent on success (chip ID print is now dev_dbg).
>> - Drop redundant comments.
>> - Kconfig: fix a "module will be called" typo.
> 
> [...]
> 
>> +#define IMX908_EXPOSURE_MIN    1
>> +#define IMX908_EXPOSURE_STEP   1
>> +
>> +/* ---- Speed of internal clock */
>> +#define IMX908_XHS_HZ          74250000ULL
>> +
>> +/* Fixed pixel rate: column ADC reads 8 pixels per 74.25 MHz clock */
>> +#define IMX908_PIX_PER_CLK     8U
> 
> Can you confirm if the hardware is actually doing this, or this is just a
> guess like I had made for IMX678?
> 
> It's ideal to know what the hardware does to better calculate minimum HMAX
> value for cropped modes for Starvis2/3 sensors in future.

For v2 I have just aligned with the IMX678.
However, upon further calculation I think this value should be 16 for 
the IMX908.

The reason is that the operating mode table contains valid modes using a 
recording width of 3840 pixels with HMAX values that cannot be achieved 
if the array-side producer limit is based on 8 pixels per internal clock.

With PIX_PER_CLK = 8, the minimum producer-side HMAX would be:
ceil(3840/8) = 480

However, the operating mode table includes the following valid modes:
4-lane, 2376 Mbps, RAW10, 90 fps -> HMAX = 366
4-lane, 2376 Mbps, RAW12, 75 fps -> HMAX = 440

Since both values are below 480, this suggests that sensor must be 
capable of exceeding 8 pixels per clock in at least some operating 
modes. The smallest power-of-two value consistent with all documented 
operating modes is 16 pixels-per-clock, so I have changed PIX_PER_CLK to 
16U in v3.
ceil(3840/16) = 240

>> +#define IMX908_PIXEL_RATE      (IMX908_XHS_HZ * IMX908_PIX_PER_CLK)    /* 594 MHz */
>> +
>> +/* ---- Subdev Pads */
>> +#define IMX908_SOURCE_PAD      0
>> +
>> +#define IMX908_DEFAULT_MBUS_CODE MEDIA_BUS_FMT_SRGGB10_1X10
>> +
>> +/*
>> + * IMX908 total area includes active area height plus
>> + *  4 pixels effective pixel ignored area
>> + * 10 pixels vertical direction effective OB
>> + * 10 pixels OB side ignored area
>> + */
>> +static const struct v4l2_rect imx908_total_area = {
>> +       .top = 0,
>> +       .left = 0,
>> +       .width = 3856,
>> +       .height = 2200,
>> +};
>> +
>> +static const struct v4l2_rect imx908_active_area = {
>> +       .top = 0,
>> +       .left = 0,
>> +       .width = 3856,
>> +       .height = 2176,
>> +};
>> +
>> +/* Recommended 4K recording area centered within the active area */
>> +static const struct v4l2_rect imx908_recording_area = {
>> +       .top = 8,
>> +       .left = 8,
>> +       .width = 3840,
>> +       .height = 2160,
> 
> If I understand correctly it's better if the sensor captures common
> resolutions like 1080p or 2160p with some margin, as some ISPs might need
> to discard a few pixels during de-bayer color processing.

Ok, the main reason I had this here was that the datasheet seems to 
promote 4K rather than the active array area.

I deleted this struct and just use active_area in v3.

> Another comment on this below in imx908_get_selection().
> 
> [...]
> 
>> +struct imx908 {
>> +       struct v4l2_subdev sd;
>> +       struct media_pad pad;
>> +       struct device *dev;
>> +
>> +       struct regmap *cci;
>> +
>> +       struct clk *xclk;
>> +       struct gpio_desc *reset_gpio;
>> +       struct regulator_bulk_data supplies[ARRAY_SIZE(imx908_supply_names)];
>> +
>> +       u8  inck_sel;
> 
> nit: extra space

Fixed.

>> +
>> +       u8 num_lanes;
>> +       unsigned long link_freq_bitmap;
>> +       unsigned int link_freq_idx;
>> +
>> +       /* Cached current sensor timing */
>> +       u16 hmax;       /* clocks per line */
>> +       u32 vmax;       /* lines per frame */
>> +
>> +       struct {
>> +               struct v4l2_ctrl_handler handler;
>> +
>> +               struct v4l2_ctrl *pixel_rate;   /* fixed, read-only */
> 
> You can drop this as it is not used by the driver after init.
> 
>> +               struct v4l2_ctrl *exposure;
>> +               struct v4l2_ctrl *vblank;
>> +               struct v4l2_ctrl *hblank;
>> +               struct v4l2_ctrl *test_pattern;
> 
> Same here.

Ok, dropped pixel_rate and test_pattern and used local in init_controls.

> 
>> +       } ctrls;
>> +};
>> +
>> +static inline struct imx908 *to_imx908(struct v4l2_subdev *_sd)
>> +{
>> +       return container_of(_sd, struct imx908, sd);
>> +}
>> +
> 
> [...]
> 
>> +static u32 imx908_calc_link_min_hmax(struct imx908 *imx, u32 width, u8 bpp)
>> +{
>> +       u64 link_hz = imx908_link_freqs[imx->link_freq_idx];
>> +       u64 num = (u64)width * bpp * IMX908_XHS_HZ;
>> +       u64 den = (u64)imx->num_lanes * link_hz * 2; /* DDR */
>> +
>> +       /*
>> +        * den can exceed 32 bits (e.g. 4 lanes * 720 MHz * 2 = 5.76 GHz), so
>> +        * DIV_ROUND_UP_ULL / do_div would truncate the divisor to u32. Use a
>> +        * full 64/64 division.
>> +        */
>> +       return DIV64_U64_ROUND_UP(num, den);
> 
> Have you tested the lowest values generated here on the sensor? I tend to
> agree with Dave's comment on the missing margin, but I don't know enough
> about the sensor internals.
> 
> It would be helpful if Sony can provide info on the interface between the
> pixel array (producer) and MIPI (consumer), if there is a FIFO in between,
> and if they have separate PLL clock trees and pixel rates, or use the same
> 74.25 Mhz clock as your code suggests. And of course what kind of margins
> are needed on the blankings.
> 
> Does Sony have some internal spreadsheet or tool to figure this out and
> populate the recommended values in the "4. Operating Mode" table in the
> SRM? We could do the same calculation in the driver directly, this would
> help us support free-configuration of crop and HBLANK values for different
> usecases.

At the present time I only have the datasheets.I have not yet done 
extensive testing of the lowest HMAX values generated by the calculation.

> [...]
> 
>> +/* --------------------------- V4L2 controls ------------------------------ */
>> +
>> +static int imx908_set_ctrl(struct v4l2_ctrl *ctrl)
>> +{
>> +       struct imx908 *imx = container_of(ctrl->handler, struct imx908,
>> +                                         ctrls.handler);
>> +       struct v4l2_subdev_state *state;
>> +       const struct v4l2_mbus_framefmt *format;
>> +       int ret = 0;
>> +
>> +       state = v4l2_subdev_get_locked_active_state(&imx->sd);
>> +       format = v4l2_subdev_state_get_format(state, IMX908_SOURCE_PAD);
>> +
>> +       /* Update exposure control limits even if the sensor is not streaming */
>> +       if (ctrl->id == V4L2_CID_VBLANK) {
>> +               const struct v4l2_rect *crop;
>> +
>> +               crop = v4l2_subdev_state_get_crop(state, IMX908_SOURCE_PAD);
>> +
>> +               u32 min_vblank = imx908_calc_min_vblank(crop);
>> +               u32 max_vblank = imx908_calc_max_vblank(format->height);
>> +               u32 vblank = clamp_t(u32, ctrl->val, min_vblank, max_vblank);
>> +
>> +               imx->vmax = imx908_calc_vmax(format->height, vblank);
>> +
>> +               __v4l2_ctrl_modify_range(imx->ctrls.exposure,
>> +                                        IMX908_EXPOSURE_MIN,
>> +                                        imx->vmax - IMX908_MIN_SHR0,
>> +                                        IMX908_EXPOSURE_STEP,
>> +                                        imx->ctrls.exposure->default_value);
>> +       }
>> +
>> +       /* Hardware writes only when powered; cached ctrls applied on resume */
>> +       ret = pm_runtime_get_if_in_use(imx->dev);
>> +       if (ret <= 0)
> 
> This should early return only in the case of ret == 0, as the function
> returns negative error code if the PM framework is disabled (CONFIG_PM=n),
> in which case we should proceed to update the sensor registers below.
> 
> I believe what you did in v1 was correct, but maybe got misled by the LLM
> bot? I have opened a bug report for it:
> https://github.com/sashiko-dev/sashiko/issues/396

Thanks for spotting that. I've corrected this in v3.

>> +               return ret;
>> +       ret = 0;
>> +
>> +       switch (ctrl->id) {
>> +       case V4L2_CID_EXPOSURE:
>> +               ret = imx908_set_exposure_lines(imx, ctrl->val);
>> +               break;
>> +
>> +       case V4L2_CID_ANALOGUE_GAIN:
>> +               cci_write(imx->cci, IMX908_REG_GAIN, ctrl->val, &ret);
>> +               break;
>> +
>> +       case V4L2_CID_VBLANK:
>> +               ret = cci_write(imx->cci, IMX908_REG_VMAX, imx->vmax, NULL);
>> +               /* SHR0 derived from VMAX, re-apply exposure after changes */
>> +               if (!ret)
>> +                       ret = imx908_set_exposure_lines(imx,
>> +                                                       imx->ctrls.exposure->val);
>> +
>> +               break;
>> +
>> +       case V4L2_CID_HBLANK: {
>> +               const struct v4l2_rect *crop;
>> +
>> +               crop = v4l2_subdev_state_get_crop(state, IMX908_SOURCE_PAD);
>> +
>> +               /* HBLANK drives HMAX only in all-pixel; crop HMAX is fixed */
>> +               if (v4l2_rect_equal(crop, &imx908_active_area))
>> +                       imx->hmax = imx908_calc_hmax(format->width, ctrl->val);
>> +
>> +               cci_write(imx->cci, IMX908_REG_HMAX, imx->hmax, &ret);
>> +               break;
>> +       }
>> +
>> +       case V4L2_CID_PIXEL_RATE:
>> +       case V4L2_CID_LINK_FREQ:
>> +               break;
>> +
> 
> You can drop the above block. RO controls should never reach here.

Understood. Removed in v3.

>> +       case V4L2_CID_TEST_PATTERN:
>> +               ret = imx908_update_test_pattern(imx, ctrl->val);
>> +               break;
>> +
>> +       case V4L2_CID_HFLIP:
>> +               cci_write(imx->cci, IMX908_REG_HREVERSE, ctrl->val, &ret);
>> +               break;
>> +
>> +       case V4L2_CID_VFLIP:
>> +               cci_write(imx->cci, IMX908_REG_VREVERSE, ctrl->val, &ret);
>> +               break;
>> +
>> +       default:
>> +               dev_warn(imx->dev,
>> +                        "ctrl(id:0x%x,val:0x%x) is not handled\n",
>> +                        ctrl->id, ctrl->val);
>> +               break;
>> +       }
>> +
>> +       pm_runtime_put(imx->dev);
>> +       return ret;
>> +}
>> +
> 
> [...]
> 
>> +static int imx908_get_selection(struct v4l2_subdev *sd,
>> +                               struct v4l2_subdev_state *sd_state,
>> +                               struct v4l2_subdev_selection *sel)
>> +{
>> +       switch (sel->target) {
>> +       case V4L2_SEL_TGT_CROP:
>> +               sel->r = *v4l2_subdev_state_get_crop(sd_state,
>> +                                                    IMX908_SOURCE_PAD);
>> +               return 0;
>> +
>> +       case V4L2_SEL_TGT_NATIVE_SIZE:
>> +               sel->r = imx908_total_area;
>> +               return 0;
>> +
>> +       case V4L2_SEL_TGT_CROP_DEFAULT:
>> +               sel->r = imx908_recording_area;
>> +               return 0;
> 
> Kernel doc "8.1.1 Selection targets" for V4L2_SEL_TGT_CROP_DEFAULT:
> 
>      Suggested cropping rectangle that covers the “whole picture”. This
>      includes only active pixels and excludes other non-active pixels such
>      as black pixels.
> 
> I believe this means CROP_DEFAULT should include the active non-black
> pixels that are supposed to be used as a margin.
> 
> In init_state() the driver configures the rectangle to imx908_active_area,
> which is the correct thing to do.
> 
> Maybe you can drop imx908_recording_area altogether?

Thanks for the guidance. I have changed to active_area and removed the 
recording_area altogether in v3.

>> +
>> +       case V4L2_SEL_TGT_CROP_BOUNDS:
>> +               sel->r = imx908_active_area;
>> +               return 0;
>> +
>> +       default:
>> +               return -EINVAL;
>> +       }
>> +}
>> +
>> +static int imx908_enable_streams(struct v4l2_subdev *sd,
>> +                                struct v4l2_subdev_state *sd_state,
>> +                                u32 pad,
>> +                                u64 streams_mask)
>> +{
>> +       struct imx908 *imx = to_imx908(sd);
>> +       int ret;
>> +
>> +       ret = pm_runtime_resume_and_get(imx->dev);
>> +       if (ret)
>> +               return ret;
>> +
>> +       ret = imx908_start_streaming(imx, sd_state);
>> +       if (ret) {
>> +               pm_runtime_mark_last_busy(imx->dev);
> 
> Drop.
> 
> pm_runtime_put_autosuspend() calls pm_runtime_mark_last_busy() for you.

Ok, removed.

>> +               pm_runtime_put_autosuspend(imx->dev);
>> +               return ret;
>> +       }
>> +
>> +       return 0;
>> +}
>> +
>> +static int imx908_disable_streams(struct v4l2_subdev *sd,
>> +                                 struct v4l2_subdev_state *sd_state,
>> +                                 u32 pad,
>> +                                 u64 streams_mask)
>> +{
>> +       struct imx908 *imx = to_imx908(sd);
>> +       int ret;
>> +
>> +       ret = imx908_stop_streaming(imx);
>> +
>> +       pm_runtime_mark_last_busy(imx->dev);
> 
> Same here and anywehere else.

Ok, removed.

>> +       pm_runtime_put_autosuspend(imx->dev);
>> +
>> +       return ret;
>> +}
>> +
>> +static int imx908_init_state(struct v4l2_subdev *sd,
>> +                            struct v4l2_subdev_state *sd_state)
>> +{
>> +       struct v4l2_subdev_selection sel = {
>> +               .which  = V4L2_SUBDEV_FORMAT_TRY,
>> +               .pad    = IMX908_SOURCE_PAD,
>> +               .target = V4L2_SEL_TGT_CROP,
>> +               .r      = imx908_active_area,
>> +       };
>> +       struct v4l2_subdev_format fmt = {
>> +               .which = V4L2_SUBDEV_FORMAT_TRY,
>> +               .pad   = IMX908_SOURCE_PAD,
>> +               .format = {
>> +                       .code   = IMX908_DEFAULT_MBUS_CODE,
>> +                       .width  = imx908_active_area.width,
>> +                       .height = imx908_active_area.height,
>> +               },
>> +       };
>> +
>> +       imx908_set_selection(sd, sd_state, &sel);
>> +       imx908_set_pad_format(sd, sd_state, &fmt);
>> +
>> +       return 0;
>> +}
>> +
>> +static const struct v4l2_subdev_core_ops imx908_core_ops = {
>> +       .subscribe_event   = v4l2_ctrl_subdev_subscribe_event,
>> +       .unsubscribe_event = v4l2_event_subdev_unsubscribe,
>> +};
> 
> Drop.
> See 17971a430ff9 ("media: i2c: Drop HAS_EVENTS and event handlers")

Understood. Fixed in v3.

>> +
>> +static const struct v4l2_subdev_video_ops imx908_video_ops = {
>> +       .s_stream = v4l2_subdev_s_stream_helper,
>> +};
>> +
>> +static const struct v4l2_subdev_pad_ops imx908_pad_ops = {
>> +       .enum_mbus_code      = imx908_enum_mbus_code,
>> +       .enum_frame_size     = imx908_enum_frame_size,
>> +       .get_fmt             = v4l2_subdev_get_fmt,
>> +       .set_fmt             = imx908_set_pad_format,
>> +       .get_selection       = imx908_get_selection,
>> +       .set_selection       = imx908_set_selection,
>> +       .enable_streams      = imx908_enable_streams,
>> +       .disable_streams     = imx908_disable_streams,
>> +};
>> +
>> +static const struct v4l2_subdev_internal_ops imx908_internal_ops = {
>> +       .init_state = imx908_init_state,
>> +};
>> +
>> +static const struct v4l2_subdev_ops imx908_subdev_ops = {
>> +       .core  = &imx908_core_ops,
>> +       .video = &imx908_video_ops,
>> +       .pad   = &imx908_pad_ops,
>> +};
>> +
>> +/* ----------------------- Power management ---------------------- */
>> +
>> +static int imx908_power_on(struct imx908 *imx)
>> +{
>> +       int ret;
>> +
>> +       ret = regulator_bulk_enable(ARRAY_SIZE(imx908_supply_names),
>> +                                   imx->supplies);
>> +       if (ret) {
>> +               dev_err(imx->dev, "failed to enable regulators\n");
>> +               return ret;
>> +       }
>> +       msleep(200); /* IMX908 power ok after 200ms */
> 
> If there is a required delay for stabilization after the power supplies
> come up, that delay is usually modeled as `startup-delay-us = <200000>` in
> the device tree node for the regulator(s) so that regulator_bulk_enable()
> includes it.

The 200 ms delay came from T0 in the datasheet power-on sequence. 
Reviewing the timing diagram again, T0 is specified as the maximum 
power-supply rise time rather than a post-power stabilization delay. The 
only explicit delay after power-up is TLOW (500 ns) followed by T1 (20 
us before register communication). I removed the unconditional 
msleep(200) in v3.

>> +
>> +       if (imx->reset_gpio) {
> 
> Not needed as the below function handles optional GPIOs.

Ok.
>> +               gpiod_set_value_cansleep(imx->reset_gpio, 1); /* XCLR low */
> 
> Drop, the reset is asserted because it was initialized as GPIOD_OUT_HIGH.

Ok.
>> +               udelay(1); /* >= 500ns T_low */
>> +               gpiod_set_value_cansleep(imx->reset_gpio, 0); /* Sensor start */
>> +       }
>> +
>> +       ret = clk_prepare_enable(imx->xclk);
>> +       if (ret) {
>> +               dev_err(imx->dev, "failed to enable xclk: %d\n", ret);
>> +               goto err_reset;
>> +       }
>> +
>> +       /* T_1 >=20us delay before initial SDA/SCL */
>> +       usleep_range(20, 25);
>> +
>> +       return 0;
>> +
>> +err_reset:
>> +       gpiod_set_value_cansleep(imx->reset_gpio, 1); /* assert reset */
>> +       regulator_bulk_disable(ARRAY_SIZE(imx908_supply_names), imx->supplies);
>> +       return ret;
>> +}
>> +
> 
> [...]
> 
>> +static int imx908_parse_fwnode(struct imx908 *imx)
>> +{
>> +       struct fwnode_handle *ep;
>> +       struct v4l2_fwnode_endpoint bus_cfg = {
>> +               .bus_type = V4L2_MBUS_CSI2_DPHY
>> +       };
>> +       int ret = 0;
>> +
>> +       ep = fwnode_graph_get_next_endpoint(dev_fwnode(imx->dev), NULL);
>> +       if (!ep) {
>> +               dev_err(imx->dev, "failed to find endpoint in device tree\n");
>> +               return -ENXIO;
>> +       }
>> +
>> +       /* Only data-lanes and link-frequencies are used from the endpoint */
>> +       ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg);
>> +       fwnode_handle_put(ep);
>> +       if (ret)
>> +               return ret;
>> +
>> +       imx->num_lanes = bus_cfg.bus.mipi_csi2.num_data_lanes;
>> +
>> +       if (imx->num_lanes != 2 && imx->num_lanes != 4) {
>> +               dev_err(imx->dev,
>> +                       "only 2 or 4 CSI-2 data lanes are supported (got %u)\n",
>> +                       imx->num_lanes);
>> +               ret = -EINVAL;
>> +               goto out_free;
>> +       }
>> +
>> +       ret = v4l2_link_freq_to_bitmap(imx->dev,
>> +                                      bus_cfg.link_frequencies,
>> +                                      bus_cfg.nr_of_link_frequencies,
>> +                                      imx908_link_freqs,
>> +                                      ARRAY_SIZE(imx908_link_freqs),
>> +                                      &imx->link_freq_bitmap);
>> +       if (ret) {
>> +               dev_err(imx->dev, "failed to parse link frequencies from DT\n");
>> +               goto out_free;
>> +       }
>> +
>> +       if (bitmap_empty(&imx->link_freq_bitmap, ARRAY_SIZE(imx908_link_freqs))) {
> 
> Isn't this handled by -ENOENT retval of v4l2_link_freq_to_bitmap() ?

Yes it is :-) The explicit bitmap_empty() check is redundant because 
v4l2_link_freq_to_bitmap() already returns -ENOENT when no matching 
frequencies are found. I removed the extra check.

>> +               dev_err(imx->dev,
>> +                       "no common link frequencies between driver and DT\n");
>> +               ret = -EINVAL;
>> +               goto out_free;
>> +       }
>> +
>> +       imx->link_freq_idx = __ffs(imx->link_freq_bitmap);
>> +       dev_dbg(imx->dev, "using %u lanes at link freq %llu Hz\n",
>> +               imx->num_lanes, imx908_link_freqs[imx->link_freq_idx]);
>> +
>> +out_free:
>> +       v4l2_fwnode_endpoint_free(&bus_cfg);
>> +       return ret;
>> +}
>> +
>> +static int imx908_init_controls(struct imx908 *imx)
>> +{
>> +       struct v4l2_ctrl_handler *hdl = &imx->ctrls.handler;
>> +       struct v4l2_fwnode_device_properties props;
>> +       struct v4l2_ctrl *link_freq_ctl;
>> +       int ret;
>> +
>> +       ret = v4l2_ctrl_handler_init(hdl, 11);
>> +       if (ret)
>> +               return ret;
>> +
>> +       imx->ctrls.pixel_rate = v4l2_ctrl_new_std(hdl, &imx908_ctrl_ops,
>> +                                                 V4L2_CID_PIXEL_RATE,
>> +                                                 IMX908_PIXEL_RATE,
>> +                                                 IMX908_PIXEL_RATE, 1,
>> +                                                 IMX908_PIXEL_RATE);
>> +       if (imx->ctrls.pixel_rate)
>> +               imx->ctrls.pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> 
> Not needed, PIXEL_RATE is marked as RO by the control framework

Ok. I also removed the redundant READ_ONLY flag from LINK_FREQ.

>> +
>> +       link_freq_ctl = v4l2_ctrl_new_int_menu(hdl, &imx908_ctrl_ops,
>> +                                              V4L2_CID_LINK_FREQ,
>> +                                              ARRAY_SIZE(imx908_link_freqs) - 1,
>> +                                              imx->link_freq_idx,
>> +                                              imx908_link_freqs);
>> +
>> +       if (link_freq_ctl)
>> +               link_freq_ctl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
>> +
>> +       imx->hmax = IMX908_HMAX_DEFAULT;
>> +       imx->vmax = IMX908_VMAX_DEFAULT;
>> +
>> +       u32 min_vblank = IMX908_VMAX_DEFAULT - imx908_active_area.height;
>> +       u32 max_vblank = imx908_calc_max_vblank(imx908_active_area.height);
>> +
>> +       imx->ctrls.vblank = v4l2_ctrl_new_std(hdl, &imx908_ctrl_ops,
>> +                                             V4L2_CID_VBLANK,
>> +                                             min_vblank,
>> +                                             max_vblank,
>> +                                             1,
>> +                                             min_vblank);
>> +
>> +       u8 bpp = imx908_bits_per_pixel(IMX908_DEFAULT_MBUS_CODE);
>> +       u16 min_hmax = imx908_calc_min_hmax(imx, imx908_active_area.width, bpp);
>> +       u32 min_hblank = imx908_hmax_to_hblank(min_hmax,
>> +                                              imx908_active_area.width);
>> +       u32 max_hblank = imx908_hmax_to_hblank(IMX908_HMAX_MAX,
>> +                                              imx908_active_area.width);
>> +       u32 hblank = imx908_hmax_to_hblank(IMX908_HMAX_DEFAULT,
>> +                                          imx908_active_area.width);
>> +
>> +       /* Default HMAX can be infeasible at low link freqs; clamp into range */
>> +       hblank = clamp_t(u32, hblank, min_hblank, max_hblank);
>> +
>> +       /* Keep cached HMAX consistent with the clamped default */
>> +       imx->hmax = imx908_calc_hmax(imx908_active_area.width, hblank);
>> +
>> +       imx->ctrls.hblank = v4l2_ctrl_new_std(hdl, &imx908_ctrl_ops,
>> +                                             V4L2_CID_HBLANK,
>> +                                             min_hblank,
>> +                                             max_hblank,
>> +                                             IMX908_PIX_PER_CLK,
>> +                                             hblank);
>> +       u32 max_exp = IMX908_VMAX_DEFAULT - IMX908_MIN_SHR0;
>> +
>> +       imx->ctrls.exposure = v4l2_ctrl_new_std(hdl, &imx908_ctrl_ops,
>> +                                               V4L2_CID_EXPOSURE,
>> +                                               IMX908_EXPOSURE_MIN,
>> +                                               max_exp,
>> +                                               IMX908_EXPOSURE_STEP,
>> +                                               max_exp / 2);
>> +
>> +       v4l2_ctrl_new_std(hdl, &imx908_ctrl_ops, V4L2_CID_ANALOGUE_GAIN,
>> +                         IMX908_ANA_GAIN_MIN, IMX908_ANA_GAIN_MAX,
>> +                         IMX908_ANA_GAIN_STEP, IMX908_ANA_GAIN_DEFAULT);
>> +
>> +       /* Set test pattern. Menu (13 entries: Disabled + 12 patterns) */
>> +       imx->ctrls.test_pattern = v4l2_ctrl_new_std_menu_items(hdl,
>> +                                                              &imx908_ctrl_ops,
>> +                                                              V4L2_CID_TEST_PATTERN,
>> +                                                              ARRAY_SIZE(imx908_tpg_menu) - 1,
>> +                                                              0,
>> +                                                              0,
>> +                                                              imx908_tpg_menu);
>> +       if (imx->ctrls.test_pattern)
>> +               imx->ctrls.test_pattern->flags |= V4L2_CTRL_FLAG_EXECUTE_ON_WRITE;
>> +
> 
> Any particular reason to use this?
> I've never needed this for test pattern control before.

I don't think it is needed either. Removed in v3.
>> +       v4l2_ctrl_new_std(hdl, &imx908_ctrl_ops, V4L2_CID_HFLIP, 0, 1, 1, 0);
>> +       v4l2_ctrl_new_std(hdl, &imx908_ctrl_ops, V4L2_CID_VFLIP, 0, 1, 1, 0);
>> +
>> +       /* Read rotation and orientation properties from the firmware node */
>> +       ret = v4l2_fwnode_device_parse(imx->dev, &props);
>> +       if (ret)
>> +               goto err_free;
> 
> nit: You could move this section before v4l2_ctrl_handler_init() to
> simplify error handling and get rid of the goto.

Ok. Done in v3.
>> +
>> +       v4l2_ctrl_new_fwnode_properties(hdl, &imx908_ctrl_ops, &props);
>> +       if (hdl->error) {
>> +               ret = hdl->error;
>> +               goto err_free;
>> +       }
>> +
>> +       imx->sd.ctrl_handler = hdl;
>> +
>> +       return 0;
>> +
>> +err_free:
>> +       v4l2_ctrl_handler_free(hdl);
>> +       return ret;
>> +}
> 
> [...]
> 
>> +static int imx908_probe(struct i2c_client *client)
>> +{
>> +       struct imx908 *imx;
>> +       int ret;
>> +
>> +       imx = devm_kzalloc(&client->dev, sizeof(*imx), GFP_KERNEL);
>> +       if (!imx)
>> +               return -ENOMEM;
>> +       imx->dev = &client->dev;
>> +
>> +       v4l2_i2c_subdev_init(&imx->sd, client, &imx908_subdev_ops);
>> +       imx->sd.internal_ops = &imx908_internal_ops;
>> +
>> +       imx->cci = devm_cci_regmap_init_i2c(client, 16);
>> +       if (IS_ERR(imx->cci))
>> +               return dev_err_probe(&client->dev, PTR_ERR(imx->cci),
>> +                                    "CCI regmap init failed\n");
>> +
>> +       imx->xclk = devm_clk_get(imx->dev, NULL);
>> +       if (IS_ERR(imx->xclk))
>> +               return dev_err_probe(imx->dev, PTR_ERR(imx->xclk),
>> +                                    "failed to get clock\n");
>> +
>> +       ret = imx908_get_inck_sel(imx, clk_get_rate(imx->xclk));
>> +       if (ret)
>> +               return ret;
>> +
>> +       imx->reset_gpio = devm_gpiod_get_optional(imx->dev, "reset",
>> +                                                 GPIOD_OUT_HIGH);
>> +
>> +       if (IS_ERR(imx->reset_gpio))
>> +               return dev_err_probe(imx->dev, PTR_ERR(imx->reset_gpio),
>> +                                    "failed to get reset gpio\n");
>> +
>> +       ret = imx908_get_regulators(imx);
>> +       if (ret)
>> +               return dev_err_probe(&client->dev, ret,
>> +                                    "failed to get regulators\n");
>> +
>> +       ret = imx908_parse_fwnode(imx);
>> +       if (ret)
>> +               return dev_err_probe(&client->dev, ret,
>> +                                    "device tree parse failed\n");
>> +
>> +       ret = imx908_power_on(imx);
>> +       if (ret)
>> +               return dev_err_probe(&client->dev, ret, "power-on failed\n");
>> +
>> +       ret = imx908_identify_model(imx);
>> +       if (ret) {
>> +               dev_err(imx->dev, "failed to identify model: %d\n", ret);
>> +               goto err_power_off;
>> +       }
>> +
>> +       pm_runtime_set_active(imx->dev);
>> +       pm_runtime_enable(imx->dev);
>> +
>> +       ret = imx908_init_controls(imx);
>> +       if (ret)
>> +               goto err_pm_disable;
>> +
>> +       imx->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
> 
> Drop.
> See 17971a430ff9 ("media: i2c: Drop HAS_EVENTS and event handlers")

Ok.

>> +       imx->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
>> +       imx->pad.flags = MEDIA_PAD_FL_SOURCE;
>> +       ret = media_entity_pads_init(&imx->sd.entity, 1, &imx->pad);
>> +       if (ret)
>> +               goto err_hdl;
>> +
>> +       /* Share the ctrl handler lock so s_ctrl can access the locked state */
>> +       imx->sd.state_lock = imx->ctrls.handler.lock;
>> +
>> +       ret = v4l2_subdev_init_finalize(&imx->sd);
>> +       if (ret)
>> +               goto err_entity;
>> +
>> +       pm_runtime_set_autosuspend_delay(imx->dev, 1000);
>> +       pm_runtime_use_autosuspend(imx->dev);
>> +       pm_runtime_mark_last_busy(imx->dev);
>> +
> 
> Missing pm_runtime_idle(), and this whole block should probably go just
> before the `return 0`.

Addressed by the runtime-PM rework following Dave's review. Probe now 
holds a PM reference using pm_runtime_get_noresume() and releases it 
with pm_runtime_put_autosuspend() after subdevice registration completes.

Thanks,

Lachlan

  reply	other threads:[~2026-08-19  5:34 UTC|newest]

Thread overview: 13+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-06  7:09 [PATCH v2 0/2] Add bindings and driver for Sony IMX908 Lachlan Michael
2026-08-06  7:09 ` [PATCH v2 1/2] media: dt-bindings: imx908: Add Sony IMX908 sensor Lachlan Michael
2026-08-06  7:20   ` sashiko-bot
2026-08-12  2:16     ` Rob Herring
2026-08-19  9:22       ` Lachlan Michael
2026-08-06  7:09 ` [PATCH v2 2/2] media: i2c: Add Sony IMX908 image sensor driver Lachlan Michael
2026-08-06  7:26   ` sashiko-bot
2026-08-06 16:50   ` Dave Stevenson
2026-08-17  9:21     ` Lachlan.Michael
2026-08-07  6:53   ` Jai Luthra
2026-08-19  5:06     ` Lachlan Michael [this message]
2026-08-19  9:34     ` Jacopo Mondi
2026-08-14 12:38   ` Sakari Ailus

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=38ce1e44-2dd6-410d-abc5-554564cb1252@sony.com \
    --to=lachlan.michael@sony.com \
    --cc=Kazumi.A.Sato@sony.com \
    --cc=Kengo.Hayasaka@sony.com \
    --cc=Ryuichi.Tadano@sony.com \
    --cc=Tim.Bird@sony.com \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=hverkuil+cisco@kernel.org \
    --cc=jai.luthra@ideasonboard.com \
    --cc=kieran.bingham@ideasonboard.com \
    --cc=krzk+dt@kernel.org \
    --cc=laurent.pinchart@ideasonboard.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-media@vger.kernel.org \
    --cc=mchehab@kernel.org \
    --cc=robh@kernel.org \
    --cc=sakari.ailus@linux.intel.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox