Re: [PATCH v10 2/2] media: i2c: Add driver for AMS-OSRAM Mira220
Sakari Ailus <[email protected]> Mon, 3 Aug 2026 13:28:43 +0300
| Newsgroups | org.kernel.vger.linux-media,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-kernel |
|---|---|
| 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 Jacopo,
On Fri, Jul 31, 2026 at 04:15:53PM +0200, Jacopo Mondi wrote:
...
> > > + /* Sensor control mode */
> > > + { CCI_REG8(0x0043), 0x0 }, // Sensor Control Mode.SLEEP_POWER_MODE(0)
> > > + { CCI_REG8(0x0043), 0x0 }, // Sensor Control Mode.IDLE_POWER_MODE(0)
> > > + { CCI_REG8(0x0043), 0x4 }, // Sensor Control Mode.SYSTEM_CLOCK_ENABLE(0)
> > > + { CCI_REG8(0x0043), 0xC }, // Sensor Control Mode.SRAM_CLOCK_ENABLE(0)
> > > + { CCI_REG8(0x1001), 0x41 }, // Sensor Control Mode.EXT_EVENT_SEL(0)
> > > + { CCI_REG8(0x10f2), 0x1 }, // Sensor Control Mode.NB_OF_FRAMES_A(0)
> > > + { CCI_REG8(0x10f3), 0x0 }, // Sensor Control Mode.NB_OF_FRAMES_A(1)
> > > + { CCI_REG8(0x0012), 0x0 }, // IO Drive Strength.DIG_DRIVE_STRENGTH(0)
> > > + { CCI_REG8(0x0012), 0x0 }, // IO Drive Strength.CCI_DRIVE_STRENGTH(0)
> > > + { CCI_REG8(0x1001), 0x41 }, // Readout && Exposure.EXT_EXP_PW_SEL(0)
> > > + { CCI_REG8(0x10d0), 0x0 }, // Readout && Exposure.EXT_EXP_PW_DELAY(0)
> > > + { CCI_REG8(0x10d1), 0x0 }, // Readout && Exposure.EXT_EXP_PW_DELAY(1)
> > > + /* MIPI */
> > > + { CCI_REG8(0x6006), 0x0 }, // MIPI.TX_CTRL_EN(0)
> > > + { CCI_REG8(0x5004), 0x1 }, // MIPI.datarate
> > > + { CCI_REG8(0x5086), 0x2 }, // MIPI.datarate
> > > + { CCI_REG8(0x5087), 0x4e }, // MIPI.datarate
> > > + { CCI_REG8(0x5088), 0x0 }, // MIPI.datarate
> > > + { CCI_REG8(0x5090), 0x0 }, // MIPI.datarate
> >
> > How many of these registers are actually 16 or 32 bits? Does the sensor
> > support wider than single-octet writes -- few don't?
>
> The sensor is said to support both single read/write and burst read/write
> sequences.
>
> There is room for improvements here, but as the startup latency is not
> a concern for the time being, I would leave this as an exercize for
> later
Related to the previous comment, it'd be nice to have human-readable names
also for these registers. I'm fine with that if you promise to address this
later. :-)
...
> > > + ret = pm_runtime_resume_and_get(&client->dev);
> > > + if (ret < 0)
> > > + return ret;
> > > +
> > > + /*
> > > + * Apply default values of current mode. Stop streaming before
> > > + * uploading register sequence.
> > > + */
> > > + ret = mira220_write_stop_streaming_regs(mira220);
> > > + if (ret)
> > > + goto err_rpm_put;
> > > +
> > > + ret = cci_multi_reg_write(mira220->regmap, mira220_init_reg_list,
> > > + ARRAY_SIZE(mira220_init_reg_list), NULL);
> > > + if (ret)
> > > + goto err_rpm_put;
> > > +
> > > + ret = mira220_otp_restore(mira220);
> > > + if (ret)
> > > + goto err_rpm_put;
> > > +
> > > + ret = mira220_set_bus_config(mira220);
> > > + if (ret)
> > > + goto err_rpm_put;
> > > +
> > > + ret = mira220_set_framefmt(mira220, state);
> > > + if (ret)
> > > + goto err_rpm_put;
> >
> > Instead of a series of ifs and gotos, you could do
> >
> > if (!ret)
> > ...;
> >
> > Up to you.
> >
>
> ret = pm_runtime_resume_and_get(&client->dev);
> if (ret < 0)
> return ret;
>
> /*
> * Apply default values of current mode. Stop streaming before
> * uploading register sequence.
> */
> if (!ret)
> ret = mira220_write_stop_streaming_regs(mira220);
> if (!ret)
> ret = cci_multi_reg_write(mira220->regmap, mira220_init_reg_list,
> ARRAY_SIZE(mira220_init_reg_list), NULL);
> if (!ret)
> ret = mira220_otp_restore(mira220);
> if (!ret)
> ret = mira220_set_bus_config(mira220);
> if (!ret)
> ret = mira220_set_framefmt(mira220, state);
>
> /* Apply customized values from user */
> if (!ret)
> ret = __v4l2_ctrl_handler_setup(mira220->sd.ctrl_handler);
> if (!ret)
> ret = mira220_write_start_streaming_regs(mira220);
>
> if (ret)
> goto err_rpm_put;
>
> /* vflip and hflip cannot change during streaming */
> __v4l2_ctrl_grab(mira220->hflip, true);
> __v4l2_ctrl_grab(mira220->vflip, true);
>
> return 0;
>
> Looks a bit unusual, but I think I can do that
Such a pattern is used in quite a few sensor drivers.
...
> > > + /* By default, PIXEL_RATE is read only */
> > > + v4l2_ctrl_new_std(ctrl_hdlr, &mira220_ctrl_ops, V4L2_CID_PIXEL_RATE,
> > > + MIRA220_PIXEL_RATE, MIRA220_PIXEL_RATE, 1,
> > > + MIRA220_PIXEL_RATE);
> > > +
> > > + min_vblank = mira220_calc_min_vblank(mira220);
> > > + mira220->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &mira220_ctrl_ops,
> > > + V4L2_CID_VBLANK,
> > > + min_vblank, MIRA220_MAX_VBLANK, 1,
> > > + min_vblank);
> > > +
> > > + ctrl = v4l2_ctrl_new_int_menu(ctrl_hdlr, NULL, V4L2_CID_LINK_FREQ,
> > > + 0, 0, &mira220_link_freqs[0]);
> > > + if (ctrl)
> > > + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> > > +
> > > + /*
> > > + * Scale hblank according to the number of enabled data lanes to match
> > > + * row_length.
> > > + */
> > > + hblank_val = MIRA220_LLP_1600x1400_304 * (2 / mira220->lanes)
> > > + - MIRA220_PIXEL_ARRAY_WIDTH;
> > > + ctrl = v4l2_ctrl_new_std(ctrl_hdlr, NULL, V4L2_CID_HBLANK, hblank_val,
> > > + hblank_val, 1, hblank_val);
> > > + if (ctrl)
> > > + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> > > +
> > > + /* Max exposure is determined by vblank + vsize and Tglob. */
> > > + max_exposure = mira220_calc_exposure(mira220,
> > > + MIRA220_PIXEL_ARRAY_HEIGHT,
> > > + min_vblank);
> > > +
> > > + mira220->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &mira220_ctrl_ops,
> > > + V4L2_CID_EXPOSURE,
> > > + MIRA220_EXPOSURE_MIN,
> > > + max_exposure, 1,
> > > + MIRA220_DEFAULT_EXPOSURE);
> > > +
> > > + v4l2_ctrl_new_std(ctrl_hdlr, NULL, V4L2_CID_ANALOGUE_GAIN,
> > > + MIRA220_ANALOG_GAIN_MIN, MIRA220_ANALOG_GAIN_MAX,
> > > + MIRA220_ANALOG_GAIN_STEP,
> > > + MIRA220_ANALOG_GAIN_DEFAULT);
> > > +
> > > + mira220->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &mira220_ctrl_ops,
> > > + V4L2_CID_HFLIP, 0, 1, 1, 0);
> > > + if (mira220->hflip)
> > > + mira220->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT;
> >
> > You can omit these checks if you move setting the control flags after
>
> ah yes
>
> > checking for the handler's error state. I'd use different variables for the
> > hblank and vblank controls for this reason.
>
> vblank is writable and stored in the driver structure
>
> link_freq and hblank are RO and re-use the local 'ctrl' variable.
I indeed meant link_freq indeed, not vblank.
>
> if I move everything below, I'll have to use 2 different variables for
> sure
Yes, that'd be nice.
...
> > > + ret = v4l2_async_register_subdev_sensor(&mira220->sd);
> > > + if (ret < 0) {
> > > + dev_err_probe(dev, ret,
> > > + "failed to register sensor sub-device\n");
> > > + goto error_subdev_cleanup;
> > > + }
> > > +
> > > + pm_runtime_idle(dev);
> >
> > You probably want to call pm_runtime_idle() after setting autosuspend
> > delay.
> >
> To be honest I copied this from imx219 but I can certainly change it.
That could be fixed as well. I'll write a patch for it. :-)
>
>
>
> > > + pm_runtime_set_autosuspend_delay(dev, 1000);
> > > + pm_runtime_use_autosuspend(dev);
> > > +
> > > + return 0;
> > > +
> > > +error_subdev_cleanup:
> > > + v4l2_subdev_cleanup(&mira220->sd);
> > > +error_media_entity:
> > > + media_entity_cleanup(&mira220->sd.entity);
> > > +error_handler_free:
> > > + v4l2_ctrl_handler_free(mira220->sd.ctrl_handler);
> > > +error_power_off:
> > > + pm_runtime_disable(dev);
> > > + mira220_power_off(dev);
> > > + pm_runtime_set_suspended(dev);
> >
> > A newline here perhaps?
> >
>
> Usually I like to keep error path returns compact
>
> Thanks, I'll send a new version soon.
> It's mostly minor stuff, do you think I can get this in for this cycle
> (a bit last minute, I know)
>
> Thanks
> j
>
>
> > > + return ret;
> > > +}
> > > +
> > > +static void mira220_remove(struct i2c_client *client)
> > > +{
> > > + struct v4l2_subdev *sd = i2c_get_clientdata(client);
> > > + struct mira220 *mira220 = to_mira220(sd);
> > > +
> > > + v4l2_async_unregister_subdev(sd);
> > > + v4l2_subdev_cleanup(&mira220->sd);
> > > + media_entity_cleanup(&sd->entity);
> > > +
> > > + v4l2_ctrl_handler_free(mira220->sd.ctrl_handler);
> > > +
> > > + pm_runtime_disable(&client->dev);
> > > + if (!pm_runtime_status_suspended(&client->dev))
> > > + mira220_power_off(&client->dev);
> > > + pm_runtime_set_suspended(&client->dev);
> > > +}
> > > +
> > > +static const struct dev_pm_ops mira220_pm_ops = {
> > > + SET_RUNTIME_PM_OPS(mira220_power_off, mira220_power_on, NULL)
> > > +};
> > > +
> > > +static const struct of_device_id mira220_dt_ids[] = {
> > > + { .compatible = "ams,mira220" },
> > > + { /* sentinel */ }
> > > +};
> > > +MODULE_DEVICE_TABLE(of, mira220_dt_ids);
> > > +
> > > +static struct i2c_driver mira220_i2c_driver = {
> > > + .driver = {
> > > + .name = "mira220",
> > > + .of_match_table = mira220_dt_ids,
> > > + .pm = pm_ptr(&mira220_pm_ops),
If CONFIG_PM isn't defined, mira220_pm_ops needs to use __maybe_unused. I'd
just drop pm_ops() here.
--
Regards,
Sakari Ailus