[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <CAPVz0n3UeH-0d42JAnBQxh57NFEQ4dFVzAP0ycw9resWO7d6uw@mail.gmail.com>
Date: Thu, 30 Oct 2025 17:03:32 +0200
From: Svyatoslav Ryhel <clamor95@...il.com>
To: Tarang Raval <tarang.raval@...iconsignals.io>
Cc: Mauro Carvalho Chehab <mchehab@...nel.org>, Rob Herring <robh@...nel.org>, 
	Krzysztof Kozlowski <krzk+dt@...nel.org>, Conor Dooley <conor+dt@...nel.org>, 
	Sakari Ailus <sakari.ailus@...ux.intel.com>, Hans Verkuil <hverkuil@...all.nl>, 
	Hans de Goede <hansg@...nel.org>, André Apitzsch <git@...tzsch.eu>, 
	Sylvain Petinot <sylvain.petinot@...s.st.com>, 
	Benjamin Mugnier <benjamin.mugnier@...s.st.com>, Dongcheng Yan <dongcheng.yan@...el.com>, 
	Heimir Thor Sverrisson <heimir.sverrisson@...il.com>, 
	"linux-media@...r.kernel.org" <linux-media@...r.kernel.org>, 
	"devicetree@...r.kernel.org" <devicetree@...r.kernel.org>, 
	"linux-kernel@...r.kernel.org" <linux-kernel@...r.kernel.org>
Subject: Re: [PATCH v3 2/2] media: i2c: add Sony IMX111 CMOS camera sensor driver
чт, 30 жовт. 2025 р. о 16:55 Tarang Raval <tarang.raval@...iconsignals.io> пише:
>
> Hi Svyatoslav,
>
> > Add a v4l2 sub-device driver for the Sony IMX111 image sensor. This is a
> > camera sensor using the i2c bus for control and the csi-2 bus for data.
> >
> > The following features are supported:
> > - manual exposure, digital and analog gain control support
> > - pixel rate/link freq control support
> > - supported resolution up to 3280x2464 for single shot capture
> > - supported resolution up to 1920x1080 @ 30fps for video
> > - supported bayer order output SGBRG10 and SGBRG8
> >
> > Camera module seems to be partially compatible with Nokia SMIA but it
> > lacks a few registers required for clock calculations and has different
> > vendor-specific per-mode configurations which makes it incompatible with
> > existing CCS driver.
> >
> > Signed-off-by: Svyatoslav Ryhel <clamor95@...il.com>
>
> ---
>
> > +static int imx111_set_ctrl(struct v4l2_ctrl *ctrl)
> > +{
> > +   struct imx111 *sensor = ctrl_to_imx111(ctrl);
> > +   struct device *dev = regmap_get_device(sensor->regmap);
> > +   s64 max;
> > +   int ret = 0;
> > +
> > +   /* Propagate change of current control to all related controls */
> > +   switch (ctrl->id) {
>
> Do we need the switch statement, since only one case is present?
> You can use an 'if' instead.
>
> > +   case V4L2_CID_VBLANK:
> > +         /* Update max exposure while meeting expected vblanking */
> > +         max = sensor->cur_mode->height + ctrl->val - 5;
>
> You can define a macro for the value 5 to improve readability.
> Also, make this change in the init_control function.
>
> > +         __v4l2_ctrl_modify_range(sensor->exposure,
> > +                            sensor->exposure->minimum,
> > +                            max, sensor->exposure->step, max);
>
> This may fail; consider adding an error check.
>
> > +         break;
> > +   }
> > +
> > +   /*
> > +    * Applying V4L2 control value only happens
> > +    * when power is up for streaming
> > +    */
> > +   if (!pm_runtime_get_if_in_use(dev))
> > +         return 0;
> > +
> > +   switch (ctrl->id) {
> > +   case V4L2_CID_ANALOGUE_GAIN:
> > +         cci_write(sensor->regmap, IMX111_REG_ANALOG_GAIN, ctrl->val, &ret);
> > +         break;
> > +   case V4L2_CID_DIGITAL_GAIN:
> > +         ret = imx111_update_digital_gain(sensor, ctrl->val);
> > +         break;
> > +   case V4L2_CID_EXPOSURE:
> > +         cci_update_bits(sensor->regmap, IMX111_GROUP_WRITE, IMX111_GROUP_WRITE_ON,
> > +                     IMX111_GROUP_WRITE_ON, &ret);
> > +         cci_write(sensor->regmap, IMX111_INTEGRATION_TIME, ctrl->val, &ret);
> > +         cci_update_bits(sensor->regmap, IMX111_GROUP_WRITE, IMX111_GROUP_WRITE_ON,
> > +                     0, &ret);
> > +         break;
> > +   case V4L2_CID_HBLANK:
> > +         cci_update_bits(sensor->regmap, IMX111_GROUP_WRITE, IMX111_GROUP_WRITE_ON,
> > +                     IMX111_GROUP_WRITE_ON, &ret);
> > +         dev_err(dev, "writing 0x%x to HTL\n", sensor->cur_mode->width + ctrl->val);
> > +         cci_write(sensor->regmap, IMX111_HORIZONTAL_TOTAL_LENGTH,
> > +                 sensor->cur_mode->width + ctrl->val, &ret);
> > +         cci_update_bits(sensor->regmap, IMX111_GROUP_WRITE, IMX111_GROUP_WRITE_ON,
> > +                     0, &ret);
> > +         break;
> > +   case V4L2_CID_VBLANK:
> > +         cci_update_bits(sensor->regmap, IMX111_GROUP_WRITE, IMX111_GROUP_WRITE_ON,
> > +                     IMX111_GROUP_WRITE_ON, &ret);
> > +         dev_err(dev, "writing 0x%x to VTL\n", sensor->cur_mode->height + ctrl->val);
> > +         cci_write(sensor->regmap, IMX111_VERTICAL_TOTAL_LENGTH,
> > +                 sensor->cur_mode->height + ctrl->val, &ret);
> > +         cci_update_bits(sensor->regmap, IMX111_GROUP_WRITE, IMX111_GROUP_WRITE_ON,
> > +                     0, &ret);
> > +         break;
> > +   case V4L2_CID_HFLIP:
> > +   case V4L2_CID_VFLIP:
> > +         cci_write(sensor->regmap, IMX111_IMAGE_ORIENTATION,
> > +                 sensor->hflip->val | sensor->vflip->val << 1, &ret);
> > +         break;
> > +   case V4L2_CID_TEST_PATTERN:
> > +         cci_write(sensor->regmap, IMX111_TEST_PATTERN, ctrl->val, &ret);
> > +         break;
> > +   case V4L2_CID_TEST_PATTERN_RED:
> > +         cci_update_bits(sensor->regmap, IMX111_GROUP_WRITE, IMX111_GROUP_WRITE_ON,
> > +                     IMX111_GROUP_WRITE_ON, &ret);
> > +         cci_write(sensor->regmap, IMX111_SOLID_COLOR_RED, ctrl->val, &ret);
> > +         cci_update_bits(sensor->regmap, IMX111_GROUP_WRITE, IMX111_GROUP_WRITE_ON,
> > +                     0, &ret);
> > +         break;
> > +   case V4L2_CID_TEST_PATTERN_GREENR:
> > +         cci_update_bits(sensor->regmap, IMX111_GROUP_WRITE, IMX111_GROUP_WRITE_ON,
> > +                     IMX111_GROUP_WRITE_ON, &ret);
> > +         cci_write(sensor->regmap, IMX111_SOLID_COLOR_GR, ctrl->val, &ret);
> > +         cci_update_bits(sensor->regmap, IMX111_GROUP_WRITE, IMX111_GROUP_WRITE_ON,
> > +                     0, &ret);
> > +         break;
> > +   case V4L2_CID_TEST_PATTERN_BLUE:
> > +         cci_update_bits(sensor->regmap, IMX111_GROUP_WRITE, IMX111_GROUP_WRITE_ON,
> > +                     IMX111_GROUP_WRITE_ON, &ret);
> > +         cci_write(sensor->regmap, IMX111_SOLID_COLOR_BLUE, ctrl->val, &ret);
> > +         cci_update_bits(sensor->regmap, IMX111_GROUP_WRITE, IMX111_GROUP_WRITE_ON,
> > +                     0, &ret);
> > +         break;
> > +   case V4L2_CID_TEST_PATTERN_GREENB:
> > +         cci_update_bits(sensor->regmap, IMX111_GROUP_WRITE, IMX111_GROUP_WRITE_ON,
> > +                     IMX111_GROUP_WRITE_ON, &ret);
> > +         cci_write(sensor->regmap, IMX111_SOLID_COLOR_GB, ctrl->val, &ret);
> > +         cci_update_bits(sensor->regmap, IMX111_GROUP_WRITE, IMX111_GROUP_WRITE_ON,
> > +                     0, &ret);
> > +         break;
> > +   default:
> > +         ret = -EINVAL;
> > +   }
> > +
> > +   pm_runtime_put(dev);
> > +
> > +   return ret;
> > +}
>
> ---
>
> > +static int imx111_init_controls(struct imx111 *sensor)
> > +{
> > +   const struct v4l2_ctrl_ops *ops = &imx111_ctrl_ops;
> > +   struct device *dev = regmap_get_device(sensor->regmap);
> > +   const struct imx111_mode *mode = sensor->cur_mode;
> > +   struct v4l2_fwnode_device_properties props;
> > +   struct v4l2_subdev *sd = &sensor->sd;
>
> No need for a new variable; there is only one user in the function.
>
> > +   struct v4l2_ctrl_handler *hdl = &sensor->hdl;
> > +   s64 pixel_rate_min, pixel_rate_max;
> > +   int i, ret;
> > +
> > +   ret = v4l2_fwnode_device_parse(dev, &props);
> > +   if (ret < 0)
> > +         return ret;
> > +
> > +   ret = v4l2_ctrl_handler_init(hdl, 13);
>
> Now there are 15 controls.
>
> No need for explicit error checking; you can omit the error check if you'd like.
>
> > +   if (ret)
> > +         return ret;
> > +
> > +   pixel_rate_min = div_u64(sensor->pixel_clk_raw, 2 * IMX111_DATA_DEPTH_RAW10);
> > +   pixel_rate_max = div_u64(sensor->pixel_clk_raw, 2 * IMX111_DATA_DEPTH_RAW8);
> > +   sensor->pixel_rate = v4l2_ctrl_new_std(hdl, NULL, V4L2_CID_PIXEL_RATE,
> > +                                  pixel_rate_min, pixel_rate_max,
> > +                                  1, div_u64(sensor->pixel_clk_raw,
> > +                                  2 * sensor->data_depth));
> > +
> > +   sensor->link_freq = v4l2_ctrl_new_int_menu(hdl, NULL, V4L2_CID_LINK_FREQ,
> > +                                    0, 0, &sensor->default_link_freq);
> > +   if (sensor->link_freq)
> > +         sensor->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> > +
> > +   v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN,
> > +                 IMX111_ANA_GAIN_MIN, IMX111_ANA_GAIN_MAX,
> > +                 IMX111_ANA_GAIN_STEP, IMX111_ANA_GAIN_DEFAULT);
> > +
> > +   v4l2_ctrl_new_std(hdl, ops, V4L2_CID_DIGITAL_GAIN,
> > +                 IMX111_DGTL_GAIN_MIN, IMX111_DGTL_GAIN_MAX,
> > +                 IMX111_DGTL_GAIN_STEP, IMX111_DGTL_GAIN_DEFAULT);
> > +
> > +   sensor->hflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, 0, 1, 1, 0);
> > +   if (sensor->hflip)
> > +         sensor->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT;
> > +
> > +   sensor->vflip = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, 0, 1, 1, 0);
> > +   if (sensor->vflip)
> > +         sensor->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT;
> > +
> > +   sensor->vblank = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VBLANK, IMX111_VBLANK_MIN,
> > +                              IMX111_VTL_MAX - mode->height, 1,
> > +                              mode->vtl_def - mode->height);
> > +   sensor->hblank = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HBLANK, IMX111_HBLANK_MIN,
> > +                              IMX111_HTL_MAX - mode->width, 1,
> > +                              mode->htl_def - mode->width);
> > +
> > +   /*
> > +    * The maximum coarse integration time is the frame length in lines
> > +    * minus five.
> > +    */
> > +   sensor->exposure = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE,
> > +                                IMX111_INTEGRATION_TIME_MIN,
> > +                                IMX111_PIXEL_ARRAY_HEIGHT - 5,
> > +                                IMX111_INTEGRATION_TIME_STEP,
> > +                                IMX111_PIXEL_ARRAY_HEIGHT - 5);
> > +
> > +   v4l2_ctrl_new_fwnode_properties(hdl, ops, &props);
> > +
> > +   v4l2_ctrl_new_std_menu_items(hdl, ops, V4L2_CID_TEST_PATTERN,
> > +                          ARRAY_SIZE(test_pattern_menu) - 1, 0, 0,
> > +                          test_pattern_menu);
> > +   for (i = 0; i < 4; i++) {
> > +         /*
> > +          * The assumption is that
> > +          * V4L2_CID_TEST_PATTERN_GREENR == V4L2_CID_TEST_PATTERN_RED + 1
> > +          * V4L2_CID_TEST_PATTERN_BLUE   == V4L2_CID_TEST_PATTERN_RED + 2
> > +          * V4L2_CID_TEST_PATTERN_GREENB == V4L2_CID_TEST_PATTERN_RED + 3
> > +          */
> > +         v4l2_ctrl_new_std(hdl, ops, V4L2_CID_TEST_PATTERN_RED + i,
> > +                       IMX111_TESTP_COLOUR_MIN, IMX111_TESTP_COLOUR_MAX,
> > +                       IMX111_TESTP_COLOUR_STEP, IMX111_TESTP_COLOUR_MAX);
> > +         /* The "Solid color" pattern is white by default */
> > +   }
> > +
> > +   if (hdl->error)
> > +         return hdl->error;
> > +
> > +   sd->ctrl_handler = hdl;
> > +
> > +   return 0;
> > +};
>
> ---
>
> > +static int imx111_initialize(struct imx111 *sensor)
> > +{
> > +   struct device *dev = regmap_get_device(sensor->regmap);
> > +   int ret;
>
> ret = 0;
>
> > +
> > +   /* Configure the PLL. */
> > +   cci_write(sensor->regmap, IMX111_PRE_PLL_CLK_DIVIDER_PLL1,
> > +           sensor->pll->pre_div, &ret);
> > +   cci_write(sensor->regmap, IMX111_PLL_MULTIPLIER_PLL1, sensor->pll->mult, &ret);
> > +   cci_write(sensor->regmap, IMX111_POST_DIVIDER, IMX111_POST_DIVIDER_DIV1, &ret);
> > +   cci_write(sensor->regmap, IMX111_PLL_SETTLING_TIME,
> > +           to_settle_delay(sensor->pll->extclk_rate), &ret);
> > +
> > +   ret = cci_multi_reg_write(sensor->regmap, imx111_global_init,
> > +                       ARRAY_SIZE(imx111_global_init), NULL);
>
> You are overwriting the previous errors.
>
> please use ret |=
>
> > +   if (ret < 0) {
> > +         dev_err(dev, "Failed to initialize the sensor\n");
> > +         return ret;
> > +   }
> > +
> > +   return 0;
> > +}
>
> ---
>
> > +static int imx111_set_format(struct v4l2_subdev *sd,
> > +                    struct v4l2_subdev_state *state,
> > +                    struct v4l2_subdev_format *format)
> > +{
> > +   struct imx111 *sensor = sd_to_imx111(sd);
> > +   struct v4l2_mbus_framefmt *mbus_fmt = &format->format;
> > +   struct v4l2_mbus_framefmt *fmt;
> > +   const struct imx111_mode *mode;
> > +
> > +   mode = v4l2_find_nearest_size(imx111_modes, ARRAY_SIZE(imx111_modes),
> > +                           width, height,
> > +                           mbus_fmt->width, mbus_fmt->height);
> > +
> > +   fmt = v4l2_subdev_state_get_format(state, format->pad);
> > +
> > +   fmt->code = imx111_get_format_code(sensor, mbus_fmt->code, false);
> > +   fmt->width = mode->width;
> > +   fmt->height = mode->height;
> > +   fmt->colorspace = V4L2_COLORSPACE_RAW;
> > +
> > +   *mbus_fmt = *fmt;
> > +
> > +   if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
> > +         sensor->cur_mode = mode;
> > +         sensor->data_depth = imx111_get_format_bpp(fmt);
> > +         __v4l2_ctrl_s_ctrl_int64(sensor->pixel_rate,
> > +                            div_u64(sensor->pixel_clk_raw, 2 * sensor->data_depth));
> > +
> > +         __v4l2_ctrl_modify_range(sensor->vblank, IMX111_VBLANK_MIN,
> > +                            IMX111_VTL_MAX - mode->height, 1,
> > +                            mode->vtl_def - mode->height);
> > +         __v4l2_ctrl_s_ctrl(sensor->vblank, mode->vtl_def - mode->height);
> > +
> > +         __v4l2_ctrl_modify_range(sensor->hblank, IMX111_HBLANK_MIN,
> > +                            IMX111_HTL_MAX - mode->width, 1,
> > +                            mode->htl_def - mode->width);
> > +         __v4l2_ctrl_s_ctrl(sensor->hblank, mode->htl_def - mode->width);
>
> All the above V4L2 operations need to check for errors.
>
> > +   }
> > +
> > +   return 0;
> > +}
>
> ---
>
> > +static int imx111_identify_module(struct imx111 *sensor)
> > +{
> > +   struct device *dev = regmap_get_device(sensor->regmap);
> > +   u64 value, revision, manufacturer;
> > +   int ret;
> > +
> > +   ret = cci_read(sensor->regmap, IMX111_PRODUCT_ID, &value, NULL);
> > +   if (ret)
> > +         return ret;
> > +
> > +   if (value != IMX111_CHIP_ID) {
> > +         dev_err(dev, "chip id mismatch: %x!=%04llx", IMX111_CHIP_ID, value);
> > +         return -ENXIO;
> > +   }
> > +
> > +   cci_read(sensor->regmap, IMX111_REVISION, &revision, NULL);
> > +   cci_read(sensor->regmap, IMX111_MANUFACTURER_ID, &manufacturer, NULL);
>
> Instead of NULL, pass ret for the error code, and return ret at the end.
>
> > +
> > +   dev_dbg(dev, "module IMX%03llx rev. %llu manufacturer %llu\n",
> > +         value, revision, manufacturer);
> > +
> > +   return 0;
> > +}
> > +
> > +static int imx111_clk_init(struct imx111 *sensor)
> > +{
> > +   struct device *dev = regmap_get_device(sensor->regmap);
> > +   u32 ndata_lanes = sensor->bus_cfg.bus.mipi_csi2.num_data_lanes;
> > +   u64 extclk_rate, system_clk;
> > +   unsigned int i;
> > +
> > +   extclk_rate = clk_get_rate(sensor->extclk);
> > +   if (!extclk_rate)
> > +         return dev_err_probe(dev, -EINVAL, "EXTCLK rate unknown\n");
> > +
> > +   for (i = 0; i < ARRAY_SIZE(imx111_pll); i++) {
> > +         if (clk_get_rate(sensor->extclk) == imx111_pll[i].extclk_rate) {
> > +               sensor->pll = &imx111_pll[i];
> > +               break;
> > +         }
> > +   }
> > +   if (!sensor->pll)
> > +         return dev_err_probe(dev, -EINVAL, "Unsupported EXTCLK rate %llu\n", extclk_rate);
>
> Max line length should be 80 columns. This applies everywhere the line
> length exceeds 80 characters.
>
80 character line is not enforced, checkpatch.pl does not complain.
Consider sending patch to checkpatch.pl if you don't like this.
> > +
> > +   system_clk = div_u64(extclk_rate, sensor->pll->pre_div) * sensor->pll->mult;
> > +
> > +   /*
> > +    * Pixel clock or Logic clock is used for internal image processing is
> > +    * generated by dividing into 1/10 or 1/8 frequency according to the
> > +    * word length of the CSI2 interface. This clock is designating the pixel
> > +    * rate and used as the base of integration time, frame rate etc.
> > +    */
> > +   sensor->pixel_clk_raw = system_clk * ndata_lanes;
> > +
> > +   /*
> > +    * The CSI-2 bus is clocked for 16-bit per pixel, transmitted in DDR over n lanes
> > +    * for RAW10 default format.
> > +    */
> > +   sensor->default_link_freq = div_u64(sensor->pixel_clk_raw * 8,
> > +                               2 * IMX111_DATA_DEPTH_RAW10);
> > +
> > +   if (sensor->bus_cfg.nr_of_link_frequencies != 1 ||
> > +       sensor->bus_cfg.link_frequencies[0] != sensor->default_link_freq)
> > +         return dev_err_probe(dev, -EINVAL,
> > +                          "Unsupported DT link-frequencies, expected %llu\n",
> > +                          sensor->default_link_freq);
> > +
> > +   return 0;
> > +}
>
> ---
>
> > +static const struct of_device_id imx111_of_match[] = {
> > +   { .compatible = "sony,imx111" },
> > +   { /* sentinel */ }
> > +};
> > +MODULE_DEVICE_TABLE(of, imx111_of_match);
> > +
> > +static struct i2c_driver imx111_i2c_driver = {
> > +   .driver = {
> > +         .name = "imx111",
> > +         .of_match_table = imx111_of_match,
> > +         .pm = &imx111_pm_ops,
> > +   },
> > +   .probe = imx111_probe,
> > +   .remove = imx111_remove,
> > +};
> > +module_i2c_driver(imx111_i2c_driver);
> > +
> > +MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@...il.com>");
> > +MODULE_DESCRIPTION("Sony IMX111 CMOS Image Sensor driver");
> > +MODULE_LICENSE("GPL");
>
> Best Regards,
> Tarang
Powered by blists - more mailing lists
 
