Hi Laurent, On 22-09-16, Laurent Pinchart wrote: > Hi Marco, > > On Thu, Sep 15, 2022 at 06:54:04PM +0200, Marco Felsch wrote: > > On 22-09-05, Laurent Pinchart wrote: > > > On Thu, Aug 18, 2022 at 04:33:07PM +0200, Marco Felsch wrote: > > > > Adding support for the TC358746 parallel <-> MIPI CSI bridge. This chip > > > > supports two operating modes: > > > > 1st) parallel-in -> mipi-csi out > > > > 2nd) mipi-csi in -> parallel out > > > > > > > > This patch only adds the support for the 1st mode. > > > > > > > > Signed-off-by: Marco Felsch <m.felsch@xxxxxxxxxxxxxx> > > > > --- > > > > drivers/media/i2c/Kconfig | 17 + > > > > drivers/media/i2c/Makefile | 1 + > > > > drivers/media/i2c/tc358746.c | 1645 ++++++++++++++++++++++++++++++++++ > > > > 3 files changed, 1663 insertions(+) > > > > create mode 100644 drivers/media/i2c/tc358746.c > > [snip] > > > > > diff --git a/drivers/media/i2c/tc358746.c b/drivers/media/i2c/tc358746.c > > > > new file mode 100644 > > > > index 000000000000..7b71d0cf72a9 > > > > --- /dev/null > > > > +++ b/drivers/media/i2c/tc358746.c > > [snip] > > > > > +struct tc358746 { > > > > + struct v4l2_subdev sd; > > > > + struct media_pad pads[TC358746_NR_PADS]; > > > > + struct v4l2_async_notifier notifier; > > > > + struct v4l2_mbus_framefmt mbusfmt; > > > > > > Could you use the active state API instead of manually storing mbusfmt > > > here ? It involves > > > > I will do that, thanks for the hint and it also elimates a few basic > > checks like the pad-num check. > > Even nicer :-) > > > > - Implementing the .init_cfg() operation > > > - Calling v4l2_subdev_init_finalize() in probe > > > - Replacing calls to __tc358746_get_pad_format() with > > > v4l2_subdev_get_pad_format() > > > - Propagating the format from sink to source in tc358746_set_fmt() > > > - Dropping tc358746_src_mbus_code() from tc358746_get_fmt() and simply > > > retrieving the format using v4l2_subdev_get_pad_format() there > > > - Dropping this field > > > > > > The formats for both the ACTIVE and TRY states will then be stored in > > > the subdev state, unconditionally. You can retrieve the active state > > > with v4l2_subdev_get_locked_active_state() in tc358746_s_stream(). > > > > > > > + struct v4l2_fwnode_endpoint csi_vep; > > > > + > > > > + struct v4l2_ctrl_handler ctrl_hdl; > > > > + > > > > + struct regmap *regmap; > > > > + struct clk *refclk; > > > > + struct gpio_desc *reset_gpio; > > > > + struct regulator_bulk_data supplies[ARRAY_SIZE(tc358746_supplies)]; > > > > + > > > > + struct clk_hw mclk_hw; > > > > + unsigned long mclk_rate; > > > > + u8 mclk_prediv; > > > > + u16 mclk_postdiv; > > > > + > > > > + unsigned long pll_rate; > > > > + u8 pll_post_div; > > > > + u16 pll_pre_div; > > > > + u16 pll_mul; > > > > + > > > > + struct v4l2_ctrl *sensor_pclk_ctrl; > > > > + > > > > +#define TC358746_VB_MAX_SIZE (511 * 32) > > > > +#define TC358746_VB_DEFAULT_SIZE (1 * 32) > > > > + unsigned int vb_size; /* Video buffer size in bits */ > > > > + > > > > + struct phy_configure_opts_mipi_dphy dphy_cfg; > > > > +}; > > [snip] > > > > > +#ifndef MHZ > > > > > > Where would it be previously defined ? This sounds like asking for > > > trouble later. > > > > Sorry I don't get that, do you mean that I should define it at the very > > beginning? > > I mean that either MHZ is defined somewhere in the kernel already, in > case we should include the corresponding header, or it's not, and I > wouldn't add an #ifndev here. Otherwise, if someone adds a MHZ macro > later that gets pulled in through one of the kernel headers used by the > driver, it may cause subtle bugs if the definition is different. Of > course nobody will > > #define MHZ 42 > > but we could get > > #define MHZ 1000 * 1000 > > and something could break here. I'd drop the #ifndef to get the compiler > to complain if there's a redefinition. Argh.. sorry, now I see what you meant. I have read it like "I'm asking for troubles" because I'm defining it. Didn't saw that your comment was only related to the ifndef guard, sure I can drop it. > > > > +#define MHZ (1000 * 1000) > > > > +#endif > > [snip] > > > > > +static int > > > > +tc358746_link_validate(struct v4l2_subdev *sd, struct media_link *link, > > > > + struct v4l2_subdev_format *source_fmt, > > > > + struct v4l2_subdev_format *sink_fmt) > > > > +{ > > > > + struct tc358746 *tc358746 = to_tc358746(sd); > > > > + unsigned long csi_bitrate, sensor_bitrate; > > > > + const struct tc358746_format *fmt; > > > > + unsigned int fifo_sz, tmp, n; > > > > + s64 sensor_pclk_rate; > > > > + > > > > + /* Check the FIFO settings */ > > > > + fmt = tc358746_get_format_by_code(TC358746_SINK, tc358746->mbusfmt.code); > > > > + if (IS_ERR(fmt)) > > > > + return PTR_ERR(fmt); > > > > + > > > > + sensor_pclk_rate = v4l2_ctrl_g_ctrl_int64(tc358746->sensor_pclk_ctrl); > > > > + sensor_bitrate = sensor_pclk_rate * fmt->bus_width; > > > > + > > > > + csi_bitrate = tc358746->dphy_cfg.lanes * tc358746->pll_rate; > > > > + > > > > + dev_dbg(tc358746->sd.dev, > > > > + "Fifo settings params: sensor-bitrate:%lu csi-bitrate:%lu", > > > > + sensor_bitrate, csi_bitrate); > > > > + > > > > + /* Avoid possible FIFO overflows */ > > > > + if (csi_bitrate < sensor_bitrate) { > > > > + dev_err(sd->dev, > > > > + "Link validation failed csi-bitrate:%lu < sensor-bitrate:%lu\n", > > > > + csi_bitrate, sensor_bitrate); > > > > + return -EINVAL; > > > > + } > > > > + > > > > + /* Best case */ > > > > + if (csi_bitrate == sensor_bitrate) { > > > > + tc358746->vb_size = TC358746_VB_DEFAULT_SIZE; > > > > + goto out; > > > > + } > > > > + > > > > + /* > > > > + * Avoid possible FIFO underflow in case of > > > > + * csi_bitrate > sensor_bitrate. For such case the chip has a internal > > > > + * fifo which can be used to delay the line output. > > > > + * > > > > + * Fifo size calculation: > > > > + * > > > > + * fifo-sz, image-width - in bits > > > > + * sbr - sensor_bitrate in bits/s > > > > + * csir - csi_bitrate in bits/s > > > > + * > > > > + * 1 1 1 > > > > + * image-width * --- + fifo-sz --- >= ---- * image-width > > > > + * sbr sbr csir > > > > + * > > > > + * fifo-sz >= abs(sbr/csir * image-width - image-width) > > > > + * `-----´ > > > > + * n > > > > + * > > > > + */ > > > > + > > > > + sensor_bitrate /= TC358746_PRECISION; > > > > + n = csi_bitrate / sensor_bitrate; > > > > + tmp = (tc358746->mbusfmt.width * TC358746_PRECISION) / n; > > > > + fifo_sz = tc358746->mbusfmt.width - tmp; > > > > + fifo_sz *= fmt->bpp; > > > > + tc358746->vb_size = round_up(fifo_sz, 32); > > > > + > > > > > > Please also call v4l2_subdev_link_validate_default() here. > > > > Did it in my internal prepared v2 ^^ I call it now at the very > > beginning of this function. > > > > > I wonder if the above calculation wouldn't be better performed in > > > .s_stream() (or rather in a function being called by .s_stream()) when > > > enabling streaming. > > > > No I wouldn't shift that, since the link validation is in IMHO the > > correct place to inform the user that the pipeline can't negotiate if > > the fifo can't be configured correctly. Therefore I placed it here. > > The issue is that the user could enable the link first, and then change > the V4L2_CID_LINK_FREQ of the sensor and push the pixel rate above what > the TC358746 can support. Given the model that configures subdevs > independently, you can only validate the pipeline at stream on time. The .link_validate() gets called during the media-pipeline .link_validate() call which is called by __media_pipeline_start(). The function description for this function is: "Mark a pipeline as streaming". So I assume that it is called right in front of the .s_stream() call and there are no further adaptions on the V4L2_CID_LINK_FREQ allowed. > > > > +out: > > > > + dev_dbg(tc358746->sd.dev, > > > > + "Found FIFO size[bits]:%u -> aligned to size[bits]:%u\n", > > > > + fifo_sz, tc358746->vb_size); > > > > + > > > > + return tc358746->vb_size > TC358746_VB_MAX_SIZE ? -EINVAL : 0; > > > > +} > > [snip] > > > > > +static int tc358746_probe(struct i2c_client *client) > > > > +{ > > > > + struct clk_init_data mclk_initdata = { }; > > > > + struct device *dev = &client->dev; > > > > + struct v4l2_fwnode_endpoint *vep; > > > > + unsigned long csi_link_rate; > > > > + struct tc358746 *tc358746; > > > > + struct fwnode_handle *ep; > > > > + unsigned char csi_lanes; > > > > + struct v4l2_subdev *sd; > > > > + struct v4l2_ctrl *ctrl; > > > > + unsigned long refclk; > > > > + unsigned int i; > > > > + int err; > > > > + u32 val; > > > > + > > > > + tc358746 = devm_kzalloc(&client->dev, sizeof(*tc358746), GFP_KERNEL); > > > > + if (!tc358746) > > > > + return -ENOMEM; > > > > + > > > > + tc358746->regmap = devm_regmap_init_i2c(client, &tc358746_regmap_config); > > > > + if (IS_ERR(tc358746->regmap)) > > > > + return dev_err_probe(dev, PTR_ERR(tc358746->regmap), > > > > + "Failed to init regmap\n"); > > > > + > > > > + tc358746->refclk = devm_clk_get(dev, "refclk"); > > > > + if (IS_ERR(tc358746->refclk)) > > > > + return dev_err_probe(dev, PTR_ERR(tc358746->refclk), > > > > + "Failed to get refclk\n"); > > > > + > > > > + err = clk_prepare_enable(tc358746->refclk); > > > > + if (err) > > > > + return dev_err_probe(dev, err, > > > > + "Failed to enable refclk\n"); > > > > + > > > > + refclk = clk_get_rate(tc358746->refclk); > > > > + clk_disable_unprepare(tc358746->refclk); > > > > + > > > > + if (refclk < 6 * MHZ || refclk > 40 * MHZ) > > > > + return dev_err_probe(dev, -EINVAL, "Invalid refclk range\n"); > > > > + > > > > + for (i = 0; i < ARRAY_SIZE(tc358746_supplies); i++) > > > > + tc358746->supplies[i].supply = tc358746_supplies[i]; > > > > + > > > > + err = devm_regulator_bulk_get(dev, ARRAY_SIZE(tc358746_supplies), > > > > + tc358746->supplies); > > > > + if (err) > > > > + return dev_err_probe(dev, err, "Failed to get supplies\n"); > > > > + > > > > + tc358746->reset_gpio = devm_gpiod_get_optional(dev, "reset", > > > > + GPIOD_OUT_HIGH); > > > > + if (IS_ERR(tc358746->reset_gpio)) > > > > + return dev_err_probe(dev, PTR_ERR(tc358746->reset_gpio), > > > > + "Failed to get reset-gpios\n"); > > > > + > > > > + sd = &tc358746->sd; > > > > + v4l2_i2c_subdev_init(sd, client, &tc358746_ops); > > > > + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; > > > > + sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; > > > > + sd->entity.ops = &tc358746_entity_ops; > > > > + > > > > + tc358746->pads[TC358746_SINK].flags = MEDIA_PAD_FL_SINK; > > > > + tc358746->pads[TC358746_SOURCE].flags = MEDIA_PAD_FL_SOURCE; > > > > + err = media_entity_pads_init(&sd->entity, TC358746_NR_PADS, > > > > + tc358746->pads); > > > > + if (err) > > > > + return dev_err_probe(dev, err, > > > > + "Failed to setup media-entity pads\n"); > > > > + > > > > + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), TC358746_SOURCE, > > > > + 0, 0); > > > > + if (!ep) { > > > > + dev_err(dev, "Missing endpoint node\n"); > > > > + err = -EINVAL; > > > > + goto err_mc; > > > > + } > > > > + > > > > + /* Currently we only support 'parallel in' -> 'csi out' */ > > > > + vep = &tc358746->csi_vep; > > > > + vep->bus_type = V4L2_MBUS_CSI2_DPHY; > > > > + err = v4l2_fwnode_endpoint_alloc_parse(ep, vep); > > > > + fwnode_handle_put(ep); > > > > + if (err) { > > > > + dev_err(dev, "Failed to parse source endpoint\n"); > > > > + goto err_mc; > > > > + } > > > > + > > > > + csi_lanes = vep->bus.mipi_csi2.num_data_lanes; > > > > + if (csi_lanes == 0 || csi_lanes > 4 || > > > > + vep->nr_of_link_frequencies == 0) { > > > > + dev_err(dev, "error: Invalid CSI-2 settings\n"); > > > > + err = -EINVAL; > > > > + goto err_vep; > > > > + } > > > > + > > > > + /* TODO: Add support to handle multiple link frequencies */ > > > > + csi_link_rate = (unsigned long)vep->link_frequencies[0]; > > > > + tc358746->pll_rate = tc358746_find_pll_settings(tc358746, refclk, > > > > + csi_link_rate * 2); > > > > + if (!tc358746->pll_rate) { > > > > + err = -EINVAL; > > > > + goto err_vep; > > > > + } > > > > + > > > > + err = phy_mipi_dphy_get_default_config_for_hsclk(tc358746->pll_rate, > > > > + csi_lanes, > > > > + &tc358746->dphy_cfg); > > > > + if (err) > > > > + goto err_vep; > > > > + > > > > + tc358746->mbusfmt = tc358746_def_fmt; > > > > + tc358746->vb_size = TC358746_VB_DEFAULT_SIZE; > > > > + > > > > + dev_set_drvdata(dev, tc358746); > > > > + pm_runtime_set_autosuspend_delay(dev, 200); > > > > + pm_runtime_use_autosuspend(dev); > > > > + pm_runtime_enable(dev); > > > > + > > > > + err = pm_runtime_resume_and_get(dev); > > > > + if (err < 0) { > > > > + dev_err(dev, "Failed to resume the device\n"); > > > > + goto err_vep; > > > > + } > > > > + > > > > + /* Ensure that CSI interface is put into LP-11 state */ > > > > + err = tc358746_sw_reset(tc358746); > > > > + if (err) { > > > > + pm_runtime_put_noidle(dev); > > > > + dev_err(dev, "Failed to reset the device\n"); > > > > + goto err_pm; > > > > + } > > > > + > > > > + err = tc358746_read(tc358746, CHIPID_REG, &val); > > > > + pm_runtime_mark_last_busy(dev); > > > > + pm_runtime_put_sync_autosuspend(dev); > > > > + if (err) { > > > > + dev_err(dev, "Failed to read chipid\n"); > > > > + err = -ENODEV; > > > > + goto err_pm; > > > > + } > > > > + > > > > + if (FIELD_GET(CHIPID, val) != 0x44) { > > > > + dev_err(dev, "Invalid chipid 0x%02x\n", > > > > + (u32)FIELD_GET(CHIPID, val)); > > > > + err = -ENODEV; > > > > + goto err_pm; > > > > + } > > > > + > > > > + /* Optional MCLK provider support */ > > > > + if (device_property_present(dev, "#clock-cells")) { > > > > + const char *mclk_name; > > > > + > > > > + /* Init to highest possibel MCLK */ > > > > + tc358746->mclk_postdiv = 512; > > > > + tc358746->mclk_prediv = 8; > > > > + > > > > + mclk_name = "tc358746-mclk"; > > > > + device_property_read_string(dev, "clock-output-names", > > > > + &mclk_name); > > > > + > > > > + mclk_initdata.name = mclk_name; > > > > + mclk_initdata.ops = &tc358746_mclk_ops; > > > > + tc358746->mclk_hw.init = &mclk_initdata; > > > > + > > > > + err = devm_clk_hw_register(dev, &tc358746->mclk_hw); > > > > + if (err) { > > > > + dev_err(dev, "Failed to register mclk provider\n"); > > > > + goto err_pm; > > > > + } > > > > + > > > > + err = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, > > > > + &tc358746->mclk_hw); > > > > + if (err) { > > > > + dev_err(dev, "Failed to add mclk provider\n"); > > > > + goto err_pm; > > > > + } > > > > + } > > > > + > > > > + v4l2_ctrl_handler_init(&tc358746->ctrl_hdl, 1); > > > > + > > > > + ctrl = v4l2_ctrl_new_int_menu(&tc358746->ctrl_hdl, NULL, > > > > + V4L2_CID_LINK_FREQ, 0, 0, > > > > + vep->link_frequencies); > > > > + if (ctrl) > > > > + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; > > > > + > > > > + tc358746->sd.ctrl_handler = &tc358746->ctrl_hdl; > > > > + if (tc358746->ctrl_hdl.error) { > > > > + err = tc358746->ctrl_hdl.error; > > > > + goto err_pm; > > > > + } > > > > + > > > > + v4l2_ctrl_handler_setup(&tc358746->ctrl_hdl); > > > > + > > > > + err = tc358746_async_register(tc358746); > > > > + if (err < 0) > > > > + goto err_ctrl; > > > > + > > > > + dev_info(dev, "%s found @ 0x%x (%s)\n", client->name, > > > > + client->addr, client->adapter->name); > > > > > > I'd skip this to avoid adding noise to the kernel log at boot time. I > > > would also probably split some of the code out of the probe function as > > > it's fairly large. Up to you. > > > > Yes it is large but most of it is just requesting stuff and so.. > > Therefore I kept it here since I don't need it elsewhere. What about > > setting it to dev_dbg()? Sometimes it can be useful e.g. to find the > > problem why /dev/media is not comming up.. > > I find that enabling dev_dbg() messages from drivers/base/dd.c are > helpful for that, but a dev_dbg() message here is fine too. Ah okay, didn't checked that deep. I can remove it if you want, in my v2 I changed it to dev_dbg(). > > > > + > > > > + return 0; > > > > + > > > > +err_ctrl: > > > > + v4l2_ctrl_handler_free(&tc358746->ctrl_hdl); > > > > +err_pm: > > > > + pm_runtime_disable(dev); > > > > + pm_runtime_set_suspended(dev); > > > > + pm_runtime_dont_use_autosuspend(dev); > > > > +err_vep: > > > > + v4l2_fwnode_endpoint_free(vep); > > > > +err_mc: > > > > + media_entity_cleanup(&sd->entity); > > > > + > > > > + return err; > > > > +} > > > > + > > > > +static int tc358746_remove(struct i2c_client *client) > > > > +{ > > > > + struct v4l2_subdev *sd = i2c_get_clientdata(client); > > > > + struct tc358746 *tc358746 = to_tc358746(sd); > > > > + > > > > + v4l2_ctrl_handler_free(&tc358746->ctrl_hdl); > > > > + v4l2_fwnode_endpoint_free(&tc358746->csi_vep); > > > > + v4l2_async_nf_unregister(&tc358746->notifier); > > > > + v4l2_async_nf_cleanup(&tc358746->notifier); > > > > + v4l2_async_unregister_subdev(sd); > > > > + v4l2_device_unregister_subdev(sd); > > > > > > This shouldn't be needed v4l2_async_unregister_subdev() should be > > > enough. > > > > Okay, thanks. There are a lot of unregister helpers in the v4l2 space.. > > Too many of them indeed. I'd like to simplify all that. *Thumbs up* I already pushed my v2 which needs little rework due to kernel-test-robot findings so I will send a v3. So if there are any further findings let's move on to the v2. Thanks, Marco > > > > + media_entity_cleanup(&sd->entity); > > > > + > > > > + pm_runtime_disable(sd->dev); > > > > + pm_runtime_set_suspended(sd->dev); > > > > + pm_runtime_dont_use_autosuspend(sd->dev); > > > > + > > > > + return 0; > > > > +} > > > > + > > > > +static int tc358746_suspend(struct device *dev) > > > > +{ > > > > + struct tc358746 *tc358746 = dev_get_drvdata(dev); > > > > + > > > > + clk_disable_unprepare(tc358746->refclk); > > > > + > > > > + return regulator_bulk_disable(ARRAY_SIZE(tc358746_supplies), > > > > + tc358746->supplies); > > > > > > Shouldn't you reenable the clock if this fails ? > > > > Hm.. if this fails I could end in a undefined chip state. But I got the > > point that the framework would be in a bad state if it tries again to > > suspend the device. > > It would be, and without a way to recover, so that could be an issue. > > > > > +} > > > > + > > > > +static int tc358746_resume(struct device *dev) > > > > +{ > > > > + struct tc358746 *tc358746 = dev_get_drvdata(dev); > > > > + int err; > > > > + > > > > + gpiod_set_value(tc358746->reset_gpio, 1); > > > > + > > > > + err = regulator_bulk_enable(ARRAY_SIZE(tc358746_supplies), > > > > + tc358746->supplies); > > > > + if (err) > > > > + return err; > > > > + > > > > + /* min. 200ns */ > > > > + usleep_range(10, 20); > > > > + > > > > + gpiod_set_value(tc358746->reset_gpio, 0); > > > > + > > > > + err = clk_prepare_enable(tc358746->refclk); > > > > + if (err) > > > > + return err; > > > > > > The regulators need to be disabled in case of failure here and below > > > (and so does the clock below). > > > > Yes, you're right. > > > > > > + > > > > + /* min. 700us ... 1ms */ > > > > + usleep_range(1000, 1500); > > > > + > > > > + /* Sync state */ > > > > + err = tc358746_apply_pll_config(tc358746); > > > > + if (err) > > > > + return err; > > > > + > > > > + err = tc358746_apply_dphy_config(tc358746); > > > > + if (err) > > > > + return err; > > > > + > > > > + return tc358746_apply_misc_config(tc358746); > > > > > > Does all this belong to the PM resume handler ? It seems configuration > > > could be handled in .s_stream() instead. > > > > This gets called by the s_stream() during getting the power state but I > > can move the dphy/misc_config to the .s_stream() of course. > > That would be nice, thanks. > > > The pll > > needs to be there since the device can be a clock provider e.g. for the > > sensor. > > OK. > > > Thanks for the review :) > > > > > > +} > > > > + > > > > +DEFINE_RUNTIME_DEV_PM_OPS(tc358746_pm_ops, tc358746_suspend, > > > > + tc358746_resume, NULL); > > > > + > > > > +static const struct of_device_id __maybe_unused tc358746_of_match[] = { > > > > + { .compatible = "toshiba,tc358746" }, > > > > + { }, > > > > +}; > > > > +MODULE_DEVICE_TABLE(of, tc358746_of_match); > > > > + > > > > +static struct i2c_driver tc358746_driver = { > > > > + .driver = { > > > > + .name = "tc358746", > > > > + .pm = pm_ptr(&tc358746_pm_ops), > > > > + .of_match_table = tc358746_of_match, > > > > + }, > > > > + .probe_new = tc358746_probe, > > > > + .remove = tc358746_remove, > > > > +}; > > > > + > > > > +module_i2c_driver(tc358746_driver); > > > > + > > > > +MODULE_DESCRIPTION("Toshiba TC358746 Parallel to CSI-2 bridge driver"); > > > > +MODULE_AUTHOR("Marco Felsch <kernel@xxxxxxxxxxxxxx>"); > > > > +MODULE_LICENSE("GPL"); > > -- > Regards, > > Laurent Pinchart >