Re: [PATCH v13 3/3] media: i2c: Add support for alvium camera

From: Tommaso Merciai
Date: Wed Nov 22 2023 - 03:57:30 EST


Hi Sakary,
Thanks for your quick reply :)

On Wed, Nov 22, 2023 at 08:46:42AM +0000, Sakari Ailus wrote:
> Hi Tommaso,
>
> On Wed, Nov 22, 2023 at 09:36:21AM +0100, Tommaso Merciai wrote:
> > > > > > > > +static int alvium_get_bcrm_vers(struct alvium_dev *alvium)
> > > > > > > > +{
> > > > > > > > + struct device *dev = &alvium->i2c_client->dev;
> > > > > > > > + struct alvium_bcrm_vers *v;
> > > > > > > > + u64 val;
> > > > > > > > + int ret;
> > > > > > > > +
> > > > > > > > + ret = alvium_read(alvium, REG_BCRM_VERSION_R, &val, NULL);
> > > > > > > > + if (ret)
> > > > > > > > + return ret;
> > > > > > > > +
> > > > > > > > + v = (struct alvium_bcrm_vers *) &val;
> > > > > > >
> > > > > > > No space before "&" in type cast, please. The same elsewhere.
> > > > > > >
> > > > > > > As you cast a single value to a struct, I think the struct field values
> > > > > > > will be swapped on BE systems. You'll need to convert each value
> > > > > > > separately. Same for struct alvium_fw_vers below.
> > > > > >
> > > > > > What about:
> > > > > >
> > > > > > v->minor = le16_to_cpu(v->minor);
> > > > > > v->major = le16_to_cpu(v->major);
> > > > > >
> > > > > > here. I posted this solution in some previous v :)
> > > > >
> > > > > You shouldn't assign it to a field marked little endian. Instead, use
> > > > > le16_to_cpu() when you access the data below.
> > > > >
> > > > > If you want to access the struct in the driver without using the conversion
> > > > > macros, you should read the data one field at a time (and use u16 instead
> > > > > of __le16 type for the fields).
> > > >
> > > > It's fine with your suggestion thanks.
> > > > I'm moving to use the following to prints those values:
> > > >
> > > > v = (struct alvium_bcrm_vers *)&val;
> > > >
> > > > dev_info(dev, "bcrm version: %u.%u\n",
> > > > le16_to_cpu(v->minor), le16_to_cpu(v->major));
> > > >
> > > > thanks for the hint.
> > >
> > > Sorry, I forgot alvium_read(), via cci_read(), already does endianness
> > > conversion here, from big endian to CPU endianness. Is there a need to do
> > > further conversion here? Noting that le16_to_cpu() does nothing on little
> > > endian systems, is it necessary here?
> > >
> > > The options here are either changing the struct fields to CPU endianness
> > > and reading them individually or accessing the register as a single 32-bit
> > > value.
> > >
> > > I'd do the former, it's easier to access them that way in the driver.
> > >
> > > The same applies to BCRM version below.
> > >
> > > struct alvium_bcrm_vers {
> > > u16 minor;
> > > u16 major;
> > > };
> >
> > Understood, thanks.
> >
> > Then to resume just compared to v13 I just need to
> > revert the alvium_bcrm_vers/alvium_fw_vers struct
> > to:
> >
> > struct alvium_bcrm_vers {
> > u16 minor;
> > u16 major;
> > };
> >
> > struct alvium_fw_vers {
> > u8 special;
> > u8 major;
> > u16 minor;
> > u16 patch;
> > };
>
> Yes, and accessing the registers one field at a time.

I'm doing that into dev_info or I'm wrong?

v = (struct alvium_bcrm_vers *)&val;

dev_info(dev, "bcrm version: %u.%u\n", v->minor, v->major);

same for:

fw_v = (struct alvium_fw_vers *)&val;

dev_info(dev, "fw version: %u.%u.%u.%u\n", fw_v->special, fw_v->major,
fw_v->minor, fw_v->patch);

Sorry but I want be sure to be aligned with you :)

Thanks & Regards,
Tommaso

>
> > > > > > > > +static int alvium_init_cfg(struct v4l2_subdev *sd,
> > > > > > > > + struct v4l2_subdev_state *state)
> > > > > > > > +{
> > > > > > > > + struct alvium_dev *alvium = sd_to_alvium(sd);
> > > > > > > > + struct alvium_mode *mode = &alvium->mode;
> > > > > > > > + struct v4l2_subdev_format sd_fmt = {
> > > > > > > > + .which = V4L2_SUBDEV_FORMAT_TRY,
> > > > > > > > + .format = alvium_csi2_default_fmt,
> > > > > > > > + };
> > > > > > > > + struct v4l2_subdev_crop sd_crop = {
> > > > > > > > + .which = V4L2_SUBDEV_FORMAT_TRY,
> > > > > > > > + .rect = {
> > > > > > > > + .left = mode->crop.left,
> > > > > > > > + .top = mode->crop.top,
> > > > > > > > + .width = mode->crop.width,
> > > > > > > > + .height = mode->crop.height,
> > > > > > > > + },
> > > > > > > > + };
> > > > > > > > +
> > > > > > > > + *v4l2_subdev_get_pad_crop(sd, state, 0) = sd_crop.rect;
> > > > > > > > + *v4l2_subdev_get_pad_format(sd, state, 0) = sd_fmt.format;
> > > > > > >
> > > > > > > Shouldn't the format have same width and height as crop? What about the
> > > > > > > mbus code?
> > > >
> > > > Can you clarify to me this comment pls :)
> > >
> > > The purpose of the init_cfg operation is to initialise the sub-device
> > > state, including format and crop rectangle (if applicable). The width and
> > > height fields of the format are not initialised above, leaving them zeros.
> >
> > Mmmmm.
> > Why zeros?
> >
> > .format = alvium_csi2_default_fmt
> >
> > where:
> >
> > static const struct v4l2_mbus_framefmt alvium_csi2_default_fmt = {
> > .code = MEDIA_BUS_FMT_UYVY8_1X16,
> > .width = 640,
> > .height = 480,
> > .colorspace = V4L2_COLORSPACE_SRGB,
> > .ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(V4L2_COLORSPACE_SRGB),
> > .quantization = V4L2_QUANTIZATION_FULL_RANGE,
> > .xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(V4L2_COLORSPACE_SRGB),
> > .field = V4L2_FIELD_NONE,
> > };
>
> Ah, I missed this one. I think this part should be fine then.
>
> > > That doesn't seem to be a valid configuration, given that the crop
> > > rectangle is initiliased with different values.
> >
> > Why different values?
> > crop is initialized into subdev_init
> >
> > static int alvium_subdev_init(struct alvium_dev *alvium)
> > {
> > struct i2c_client *client = alvium->i2c_client;
> > struct device *dev = &alvium->i2c_client->dev;
> > struct v4l2_subdev *sd = &alvium->sd;
> > int ret;
> >
> > /* Setup initial frame interval*/
> > alvium->frame_interval.numerator = 1;
> > alvium->frame_interval.denominator = ALVIUM_DEFAULT_FR_HZ;
> > alvium->fr = ALVIUM_DEFAULT_FR_HZ;
> >
> > /* Setup the initial mode */
> > alvium->mode.fmt = alvium_csi2_default_fmt;
> > alvium->mode.width = alvium_csi2_default_fmt.width;
> > alvium->mode.height = alvium_csi2_default_fmt.height;
> > alvium->mode.crop.left = alvium->min_offx;
> > alvium->mode.crop.top = alvium->min_offy;
> > alvium->mode.crop.width = alvium_csi2_default_fmt.width;
> > alvium->mode.crop.height = alvium_csi2_default_fmt.height;
> > ...
> >
> > Then:
> >
> > static int alvium_init_cfg(struct v4l2_subdev *sd,
> > struct v4l2_subdev_state *state)
> > {
> > struct alvium_dev *alvium = sd_to_alvium(sd);
> > struct alvium_mode *mode = &alvium->mode;
> > struct v4l2_subdev_format sd_fmt = {
> > .which = V4L2_SUBDEV_FORMAT_TRY,
> > .format = alvium_csi2_default_fmt,
> > };
> > struct v4l2_subdev_crop sd_crop = {
> > .which = V4L2_SUBDEV_FORMAT_TRY,
> > .rect = {
> > .left = mode->crop.left,
> > .top = mode->crop.top,
> > .width = mode->crop.width,
> > .height = mode->crop.height,
> > },
> > };
> > ...
> >
> > Seems that crop and format are using the same init values
> > or I'm wrong?
> > Mmm.. maybe I'm missing something?
> >
> > Let me know
> >
> > >
> > > >
> > > > > > >
> > > > > > > > +
> > > > > > > > + return 0;
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +static int alvium_set_fmt(struct v4l2_subdev *sd,
> > > > > > > > + struct v4l2_subdev_state *sd_state,
> > > > > > > > + struct v4l2_subdev_format *format)
> > > > > > > > +{
> > > > > > > > + struct alvium_dev *alvium = sd_to_alvium(sd);
> > > > > > > > + const struct alvium_pixfmt *alvium_csi2_fmt;
> > > > > > > > + struct v4l2_mbus_framefmt *fmt;
> > > > > > > > + struct v4l2_rect *crop;
> > > > > > > > +
> > > > > > > > + fmt = v4l2_subdev_get_pad_format(sd, sd_state, 0);
> > > > > > > > + crop = v4l2_subdev_get_pad_crop(sd, sd_state, 0);
> > > > > > > > +
> > > > > > > > + fmt->width = clamp(format->format.width, alvium->img_min_width,
> > > > > > > > + alvium->img_max_width);
> > > > > > > > + fmt->height = clamp(format->format.height, alvium->img_min_height,
> > > > > > > > + alvium->img_max_height);
> > > > > > > > +
> > > > > > > > + /* Adjust left and top to prevent roll over sensor area */
> > > > > > > > + crop->left = clamp((u32)crop->left, (u32)0,
> > > > > > > > + (alvium->img_max_width - fmt->width));
> > > > > > > > + crop->top = clamp((u32)crop->top, (u32)0,
> > > > > > > > + (alvium->img_max_height - fmt->height));
> > > > > > > > +
> > > > > > > > + /* Set also the crop width and height when set a new fmt */
> > > > > > > > + crop->width = fmt->width;
> > > > > > > > + crop->height = fmt->height;
> > > > > > > > +
> > > > > > > > + alvium_csi2_fmt = alvium_code_to_pixfmt(alvium, format->format.code);
> > > > > > > > + fmt->code = alvium_csi2_fmt->code;
> > > > > > > > +
> > > > > > > > + *fmt = format->format;
> > > > > > > > +
> > > > > > > > + return 0;
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +static int alvium_set_selection(struct v4l2_subdev *sd,
> > > > > > > > + struct v4l2_subdev_state *sd_state,
> > > > > > > > + struct v4l2_subdev_selection *sel)
> > > > > > > > +{
> > > > > > > > + struct alvium_dev *alvium = sd_to_alvium(sd);
> > > > > > > > + struct v4l2_mbus_framefmt *fmt;
> > > > > > > > + struct v4l2_rect *crop;
> > > > > > > > +
> > > > > > > > + crop = v4l2_subdev_get_pad_crop(sd, sd_state, 0);
> > > > > > > > + fmt = v4l2_subdev_get_pad_format(sd, sd_state, 0);
> > > > > > > > +
> > > > > > > > + /*
> > > > > > > > + * Alvium can only shift the origin of the img
> > > > > > > > + * then we accept only value with the same value of the actual fmt
> > > > > > > > + */
> > > > > > > > + if (sel->r.width != fmt->width)
> > > > > > > > + sel->r.width = fmt->width;
> > > > > > > > +
> > > > > > > > + if (sel->r.height != fmt->height)
> > > > > > > > + sel->r.height = fmt->height;
> > > > > > > > +
> > > > > > > > + if (sel->target != V4L2_SEL_TGT_CROP)
> > > > > > > > + return -EINVAL;
> > > > > > >
> > > > > > > This should be the first thing to test.
> > > > > >
> > > > > > Oks
> > > > > >
> > > > > > >
> > > > > > > > +
> > > > > > > > + /* alvium don't accept negative crop left/top */
> > > > > > > > + crop->left = clamp((u32)max(0, sel->r.left), alvium->min_offx,
> > > > > > > > + alvium->img_max_width - sel->r.width);
> > > > > > > > + crop->top = clamp((u32)max(0, sel->r.top), alvium->min_offy,
> > > > > > > > + alvium->img_max_height - sel->r.height);
> > > > > > > > +
> > > > > > > > + sel->r = *crop;
> > > > > > > > +
> > > > > > > > + return 0;
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +static int alvium_get_selection(struct v4l2_subdev *sd,
> > > > > > > > + struct v4l2_subdev_state *sd_state,
> > > > > > > > + struct v4l2_subdev_selection *sel)
> > > > > > > > +{
> > > > > > > > + struct alvium_dev *alvium = sd_to_alvium(sd);
> > > > > > > > +
> > > > > > > > + switch (sel->target) {
> > > > > > > > + /* Current cropping area */
> > > > > > > > + case V4L2_SEL_TGT_CROP:
> > > > > > > > + sel->r = *v4l2_subdev_get_pad_crop(sd, sd_state, 0);
> > > > > > > > + break;
> > > > > > > > + /* Cropping bounds */
> > > > > > > > + case V4L2_SEL_TGT_NATIVE_SIZE:
> > > > > > > > + sel->r.top = 0;
> > > > > > > > + sel->r.left = 0;
> > > > > > > > + sel->r.width = alvium->img_max_width;
> > > > > > > > + sel->r.height = alvium->img_max_height;
> > > > > > > > + break;
> > > > > > > > + /* Default cropping area */
> > > > > > > > + case V4L2_SEL_TGT_CROP_BOUNDS:
> > > > > > > > + case V4L2_SEL_TGT_CROP_DEFAULT:
> > > > > > > > + sel->r.top = alvium->min_offy;
> > > > > > > > + sel->r.left = alvium->min_offx;
> > > > > > > > + sel->r.width = alvium->img_max_width;
> > > > > > > > + sel->r.height = alvium->img_max_height;
> > > > > > > > + break;
> > > > > > > > + default:
> > > > > > > > + return -EINVAL;
> > > > > > > > + }
> > > > > > > > +
> > > > > > > > + return 0;
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +static int alvium_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
> > > > > > > > +{
> > > > > > > > + struct v4l2_subdev *sd = ctrl_to_sd(ctrl);
> > > > > > > > + struct alvium_dev *alvium = sd_to_alvium(sd);
> > > > > > > > + int val;
> > > > > > > > +
> > > > > > > > + switch (ctrl->id) {
> > > > > > > > + case V4L2_CID_GAIN:
> > > > > > > > + val = alvium_get_gain(alvium);
> > > > > > > > + if (val < 0)
> > > > > > > > + return val;
> > > > > > > > + alvium->ctrls.gain->val = val;
> > > > > > > > + break;
> > > > > > > > + case V4L2_CID_EXPOSURE:
> > > > > > > > + val = alvium_get_exposure(alvium);
> > > > > > > > + if (val < 0)
> > > > > > > > + return val;
> > > > > > > > + alvium->ctrls.exposure->val = val;
> > > > > > > > + break;
> > > > > > > > + }
> > > > > > > > +
> > > > > > > > + return 0;
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +static int alvium_s_ctrl(struct v4l2_ctrl *ctrl)
> > > > > > > > +{
> > > > > > > > + struct v4l2_subdev *sd = ctrl_to_sd(ctrl);
> > > > > > > > + struct alvium_dev *alvium = sd_to_alvium(sd);
> > > > > > > > + struct i2c_client *client = v4l2_get_subdevdata(&alvium->sd);
> > > > > > > > + int ret;
> > > > > > > > +
> > > > > > > > + /*
> > > > > > > > + * Applying V4L2 control value only happens
> > > > > > > > + * when power is up for streaming
> > > > > > > > + */
> > > > > > > > + if (!pm_runtime_get_if_in_use(&client->dev))
> > > > > > > > + return 0;
> > > > > > > > +
> > > > > > > > + switch (ctrl->id) {
> > > > > > > > + case V4L2_CID_AUTOGAIN:
> > > > > > > > + ret = alvium_set_ctrl_gain(alvium, ctrl->val);
> > > > > > >
> > > > > > > ret = alvium_set_autogain(alvium, ctrl->val);
> > > > > > >
> > > > > >
> > > > > > Pls check [1]
> > > > > >
> > > > > > > Where do you set the manual gain value? What about the manual exposure
> > > > > > > value? Both appear to be missing here.
> > > > > > >
> > > > > > > How have you tested this?
> > > > > > >
> > > > > > > > + break;
> > > > > > > > + case V4L2_CID_EXPOSURE_AUTO:
> > > > > > > > + ret = alvium_set_ctrl_exposure(alvium, ctrl->val);
> > > > > > >
> > > > > > > ret = alvium_set_autoexposure(alvium, ctrl->val);
> > > > > > >
> > > > > > > You're still missing grabbing the manual controls when the corresponding
> > > > > > > automatic control is enabled. I've commented on the same matter previously.
> > > > > >
> > > > > > Same comment in [1]
> > > > > >
> > > > > > >
> > > > > > > > + break;
> > > > > > > > + case V4L2_CID_AUTO_WHITE_BALANCE:
> > > > > > > > + ret = alvium_set_ctrl_white_balance(alvium, ctrl->val);
> > > > > > > > + break;
> > > > > > > > + case V4L2_CID_HUE:
> > > > > > > > + ret = alvium_set_ctrl_hue(alvium, ctrl->val);
> > > > > > > > + break;
> > > > > > > > + case V4L2_CID_CONTRAST:
> > > > > > > > + ret = alvium_set_ctrl_contrast(alvium, ctrl->val);
> > > > > > > > + break;
> > > > > > > > + case V4L2_CID_SATURATION:
> > > > > > > > + ret = alvium_set_ctrl_saturation(alvium, ctrl->val);
> > > > > > > > + break;
> > > > > > > > + case V4L2_CID_GAMMA:
> > > > > > > > + ret = alvium_set_ctrl_gamma(alvium, ctrl->val);
> > > > > > > > + break;
> > > > > > > > + case V4L2_CID_SHARPNESS:
> > > > > > > > + ret = alvium_set_ctrl_sharpness(alvium, ctrl->val);
> > > > > > > > + break;
> > > > > > > > + case V4L2_CID_HFLIP:
> > > > > > > > + ret = alvium_set_ctrl_hflip(alvium, ctrl->val);
> > > > > > > > + break;
> > > > > > > > + case V4L2_CID_VFLIP:
> > > > > > > > + ret = alvium_set_ctrl_vflip(alvium, ctrl->val);
> > > > > > > > + break;
> > > > > > > > + default:
> > > > > > > > + ret = -EINVAL;
> > > > > > > > + break;
> > > > > > > > + }
> > > > > > > > +
> > > > > > > > + pm_runtime_put(&client->dev);
> > > > > > > > +
> > > > > > > > + return ret;
> > > > > > > > +}
> >
> > Here I plan to switch to:
> >
> > static int alvium_s_ctrl(struct v4l2_ctrl *ctrl)
> > {
> > struct v4l2_subdev *sd = ctrl_to_sd(ctrl);
> > struct alvium_dev *alvium = sd_to_alvium(sd);
> > struct i2c_client *client = v4l2_get_subdevdata(&alvium->sd);
> > int ret;
> >
> > /*
> > * Applying V4L2 control value only happens
> > * when power is up for streaming
> > */
> > if (!pm_runtime_get_if_in_use(&client->dev))
> > return 0;
> >
> > switch (ctrl->id) {
> > case V4L2_CID_GAIN:
> > ret = alvium_set_ctrl_gain(alvium, ctrl->val);
> > break;
> > case V4L2_CID_AUTOGAIN:
> > ret = alvium_set_ctrl_auto_gain(alvium, ctrl->val);
> > break;
> > case V4L2_CID_EXPOSURE:
> > ret = alvium_set_ctrl_exposure(alvium, ctrl->val);
> > break;
> > case V4L2_CID_EXPOSURE_AUTO:
> > ret = alvium_set_ctrl_auto_exposure(alvium, ctrl->val);
> > break;
> > case V4L2_CID_RED_BALANCE:
> > ret = alvium_set_ctrl_red_balance_ratio(alvium, ctrl->val);
> > break;
> > case V4L2_CID_BLUE_BALANCE:
> > ret = alvium_set_ctrl_blue_balance_ratio(alvium, ctrl->val);
> > break;
> > case V4L2_CID_AUTO_WHITE_BALANCE:
> > ret = alvium_set_ctrl_awb(alvium, ctrl->val);
> > break;
> > case V4L2_CID_HUE:
> > ret = alvium_set_ctrl_hue(alvium, ctrl->val);
> > break;
> > case V4L2_CID_CONTRAST:
> > ret = alvium_set_ctrl_contrast(alvium, ctrl->val);
> > break;
> > case V4L2_CID_SATURATION:
> > ret = alvium_set_ctrl_saturation(alvium, ctrl->val);
> > break;
> > case V4L2_CID_GAMMA:
> > ret = alvium_set_ctrl_gamma(alvium, ctrl->val);
> > break;
> > case V4L2_CID_SHARPNESS:
> > ret = alvium_set_ctrl_sharpness(alvium, ctrl->val);
> > break;
> > case V4L2_CID_HFLIP:
> > ret = alvium_set_ctrl_hflip(alvium, ctrl->val);
> > break;
> > case V4L2_CID_VFLIP:
> > ret = alvium_set_ctrl_vflip(alvium, ctrl->val);
> > break;
> > default:
> > ret = -EINVAL;
> > break;
> > }
> >
> > pm_runtime_put(&client->dev);
> >
> > return ret;
> > }
> >
> > Like you suggested.
> > I think is more clean/understandable.
> > Thanks for your comment.
>
> Ack.
>
> --
> Regards,
>
> Sakari Ailus