From dc608edf7d45ba0c2ad14c06eccd66474fec7847 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Thu, 8 Sep 2022 00:44:09 +0200 Subject: [PATCH 01/47] ipu3-imgu: Fix NULL pointer dereference in imgu_subdev_set_selection() Calling v4l2_subdev_get_try_crop() and v4l2_subdev_get_try_compose() with a subdev state of NULL leads to a NULL pointer dereference. This can currently happen in imgu_subdev_set_selection() when the state passed in is NULL, as this method first gets pointers to both the "try" and "active" states and only then decides which to use. The same issue has been addressed for imgu_subdev_get_selection() with commit 30d03a0de650 ("ipu3-imgu: Fix NULL pointer dereference in active selection access"). However the issue still persists in imgu_subdev_set_selection(). Therefore, apply a similar fix as done in the aforementioned commit to imgu_subdev_set_selection(). To keep things a bit cleaner, introduce helper functions for "crop" and "compose" access and use them in both imgu_subdev_set_selection() and imgu_subdev_get_selection(). Fixes: 0d346d2a6f54 ("media: v4l2-subdev: add subdev-wide state struct") Cc: stable@vger.kernel.org # for v5.14 and later Signed-off-by: Maximilian Luz Signed-off-by: Sakari Ailus --- drivers/staging/media/ipu3/ipu3-v4l2.c | 57 +++++++++++++++----------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/drivers/staging/media/ipu3/ipu3-v4l2.c b/drivers/staging/media/ipu3/ipu3-v4l2.c index ce13e746c15f..e530767e80a5 100644 --- a/drivers/staging/media/ipu3/ipu3-v4l2.c +++ b/drivers/staging/media/ipu3/ipu3-v4l2.c @@ -188,6 +188,28 @@ static int imgu_subdev_set_fmt(struct v4l2_subdev *sd, return 0; } +static struct v4l2_rect * +imgu_subdev_get_crop(struct imgu_v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, unsigned int pad, + enum v4l2_subdev_format_whence which) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_crop(&sd->subdev, sd_state, pad); + else + return &sd->rect.eff; +} + +static struct v4l2_rect * +imgu_subdev_get_compose(struct imgu_v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, unsigned int pad, + enum v4l2_subdev_format_whence which) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_compose(&sd->subdev, sd_state, pad); + else + return &sd->rect.bds; +} + static int imgu_subdev_get_selection(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) @@ -200,18 +222,12 @@ static int imgu_subdev_get_selection(struct v4l2_subdev *sd, switch (sel->target) { case V4L2_SEL_TGT_CROP: - if (sel->which == V4L2_SUBDEV_FORMAT_TRY) - sel->r = *v4l2_subdev_get_try_crop(sd, sd_state, - sel->pad); - else - sel->r = imgu_sd->rect.eff; + sel->r = *imgu_subdev_get_crop(imgu_sd, sd_state, sel->pad, + sel->which); return 0; case V4L2_SEL_TGT_COMPOSE: - if (sel->which == V4L2_SUBDEV_FORMAT_TRY) - sel->r = *v4l2_subdev_get_try_compose(sd, sd_state, - sel->pad); - else - sel->r = imgu_sd->rect.bds; + sel->r = *imgu_subdev_get_compose(imgu_sd, sd_state, sel->pad, + sel->which); return 0; default: return -EINVAL; @@ -223,10 +239,9 @@ static int imgu_subdev_set_selection(struct v4l2_subdev *sd, struct v4l2_subdev_selection *sel) { struct imgu_device *imgu = v4l2_get_subdevdata(sd); - struct imgu_v4l2_subdev *imgu_sd = container_of(sd, - struct imgu_v4l2_subdev, - subdev); - struct v4l2_rect *rect, *try_sel; + struct imgu_v4l2_subdev *imgu_sd = + container_of(sd, struct imgu_v4l2_subdev, subdev); + struct v4l2_rect *rect; dev_dbg(&imgu->pci_dev->dev, "set subdev %u sel which %u target 0x%4x rect [%ux%u]", @@ -238,22 +253,18 @@ static int imgu_subdev_set_selection(struct v4l2_subdev *sd, switch (sel->target) { case V4L2_SEL_TGT_CROP: - try_sel = v4l2_subdev_get_try_crop(sd, sd_state, sel->pad); - rect = &imgu_sd->rect.eff; + rect = imgu_subdev_get_crop(imgu_sd, sd_state, sel->pad, + sel->which); break; case V4L2_SEL_TGT_COMPOSE: - try_sel = v4l2_subdev_get_try_compose(sd, sd_state, sel->pad); - rect = &imgu_sd->rect.bds; + rect = imgu_subdev_get_compose(imgu_sd, sd_state, sel->pad, + sel->which); break; default: return -EINVAL; } - if (sel->which == V4L2_SUBDEV_FORMAT_TRY) - *try_sel = sel->r; - else - *rect = sel->r; - + *rect = sel->r; return 0; } From a8278ad796edc1ea0409abcb4e9c6453da87cef8 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Fri, 9 Sep 2022 00:40:09 +0300 Subject: [PATCH 02/47] media: Fix documentation typos in media-entity.h Signed-off-by: Laurent Pinchart Signed-off-by: Sakari Ailus --- include/media/media-entity.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/media/media-entity.h b/include/media/media-entity.h index 28c9de8a1f34..85ed08ddee9d 100644 --- a/include/media/media-entity.h +++ b/include/media/media-entity.h @@ -237,7 +237,7 @@ struct media_pad { * @link_validate: Return whether a link is valid from the entity point of * view. The media_pipeline_start() function * validates all links by calling this operation. Optional. - * @has_pad_interdep: Return whether a two pads inside the entity are + * @has_pad_interdep: Return whether two pads of the entity are * interdependent. If two pads are interdependent they are * part of the same pipeline and enabling one of the pads * means that the other pad will become "locked" and @@ -1144,7 +1144,7 @@ __must_check int __media_pipeline_start(struct media_pad *pad, * media_pipeline_stop - Mark a pipeline as not streaming * @pad: Starting pad * - * Mark all pads connected to a given pads through enabled links, either + * Mark all pads connected to a given pad through enabled links, either * directly or indirectly, as not streaming. The media_pad pipe field is * reset to %NULL. * From c2079f3e220e2f0b551715ee748a0cc936a4a7f6 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Mon, 19 Sep 2022 13:07:30 +0300 Subject: [PATCH 03/47] media: v4l: subdev: Document s_power() callback is deprecated Runtime PM has been around for a decade or more, there's hardly a need to use the V4L2 specific s_power() callback in drivers anymore. Document this in s_power() callback documentation as well. Signed-off-by: Sakari Ailus Reviewed-by: Laurent Pinchart --- include/media/v4l2-subdev.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/media/v4l2-subdev.h b/include/media/v4l2-subdev.h index 2f80c9c818ed..54566d139da7 100644 --- a/include/media/v4l2-subdev.h +++ b/include/media/v4l2-subdev.h @@ -176,7 +176,10 @@ struct v4l2_subdev_io_pin_config { * @s_register: callback for VIDIOC_DBG_S_REGISTER() ioctl handler code. * * @s_power: puts subdevice in power saving mode (on == 0) or normal operation - * mode (on == 1). + * mode (on == 1). DEPRECATED. See + * Documentation/driver-api/media/camera-sensor.rst . pre_streamon and + * post_streamoff callbacks can be used for e.g. setting the bus to LP-11 + * mode before s_stream is called. * * @interrupt_service_routine: Called by the bridge chip's interrupt service * handler, when an interrupt status has be raised due to this subdev, From 80113026d415e27483669db7a88b548d1ec3d3d1 Mon Sep 17 00:00:00 2001 From: Rafael Mendonca Date: Sun, 18 Sep 2022 23:12:51 -0300 Subject: [PATCH 04/47] media: i2c: hi846: Fix memory leak in hi846_parse_dt() If any of the checks related to the supported link frequencies fail, then the V4L2 fwnode resources don't get released before returning, which leads to a memleak. Fix this by properly freeing the V4L2 fwnode data in a designated label. Fixes: e8c0882685f9 ("media: i2c: add driver for the SK Hynix Hi-846 8M pixel camera") Signed-off-by: Rafael Mendonca Reviewed-by: Tommaso Merciai Reviewed-by: Martin Kepplinger Signed-off-by: Sakari Ailus --- drivers/media/i2c/hi846.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/media/i2c/hi846.c b/drivers/media/i2c/hi846.c index c5b69823f257..7c61873b7198 100644 --- a/drivers/media/i2c/hi846.c +++ b/drivers/media/i2c/hi846.c @@ -2008,22 +2008,24 @@ static int hi846_parse_dt(struct hi846 *hi846, struct device *dev) bus_cfg.bus.mipi_csi2.num_data_lanes != 4) { dev_err(dev, "number of CSI2 data lanes %d is not supported", bus_cfg.bus.mipi_csi2.num_data_lanes); - v4l2_fwnode_endpoint_free(&bus_cfg); - return -EINVAL; + ret = -EINVAL; + goto check_hwcfg_error; } hi846->nr_lanes = bus_cfg.bus.mipi_csi2.num_data_lanes; if (!bus_cfg.nr_of_link_frequencies) { dev_err(dev, "link-frequency property not found in DT\n"); - return -EINVAL; + ret = -EINVAL; + goto check_hwcfg_error; } /* Check that link frequences for all the modes are in device tree */ fq = hi846_check_link_freqs(hi846, &bus_cfg); if (fq) { dev_err(dev, "Link frequency of %lld is not supported\n", fq); - return -EINVAL; + ret = -EINVAL; + goto check_hwcfg_error; } v4l2_fwnode_endpoint_free(&bus_cfg); @@ -2044,6 +2046,10 @@ static int hi846_parse_dt(struct hi846 *hi846, struct device *dev) } return 0; + +check_hwcfg_error: + v4l2_fwnode_endpoint_free(&bus_cfg); + return ret; } static int hi846_probe(struct i2c_client *client) From 9084e2c8617a63c478b48810ff7a8985de55f308 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Mon, 19 Sep 2022 15:33:50 +0100 Subject: [PATCH 05/47] media: i2c: ov5645: Drop fetching the clk reference by name The OV5645 sensor has a single clock source, so just drop fetching the clk reference by name. This is in preparation to drop the "clock-names" property from the DT binding. Suggested-by: Laurent Pinchart Signed-off-by: Lad Prabhakar Reviewed-by: Laurent Pinchart Signed-off-by: Sakari Ailus --- drivers/media/i2c/ov5645.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/i2c/ov5645.c b/drivers/media/i2c/ov5645.c index 81e4e87e1821..47451238ca05 100644 --- a/drivers/media/i2c/ov5645.c +++ b/drivers/media/i2c/ov5645.c @@ -1090,7 +1090,7 @@ static int ov5645_probe(struct i2c_client *client) } /* get system clock (xclk) */ - ov5645->xclk = devm_clk_get(dev, "xclk"); + ov5645->xclk = devm_clk_get(dev, NULL); if (IS_ERR(ov5645->xclk)) { dev_err(dev, "could not get xclk"); return PTR_ERR(ov5645->xclk); From 9fce241660f37d9e95e93c0ae6fba8cfefa5797b Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Wed, 21 Sep 2022 13:38:00 +0200 Subject: [PATCH 06/47] media: i2c: ad5820: Fix error path Error path seems to be swaped. Fix the order and provide some meaningful names. Fixes: bee3d5115611 ("[media] ad5820: Add driver for auto-focus coil") Signed-off-by: Ricardo Ribalda Signed-off-by: Sakari Ailus --- drivers/media/i2c/ad5820.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/media/i2c/ad5820.c b/drivers/media/i2c/ad5820.c index 516de278cc49..a12fedcc3a1c 100644 --- a/drivers/media/i2c/ad5820.c +++ b/drivers/media/i2c/ad5820.c @@ -327,18 +327,18 @@ static int ad5820_probe(struct i2c_client *client, ret = media_entity_pads_init(&coil->subdev.entity, 0, NULL); if (ret < 0) - goto cleanup2; + goto clean_mutex; ret = v4l2_async_register_subdev(&coil->subdev); if (ret < 0) - goto cleanup; + goto clean_entity; return ret; -cleanup2: - mutex_destroy(&coil->power_lock); -cleanup: +clean_entity: media_entity_cleanup(&coil->subdev.entity); +clean_mutex: + mutex_destroy(&coil->power_lock); return ret; } From 08304923d31781f52281290b8248f4b9a8afe29c Mon Sep 17 00:00:00 2001 From: Hidenori Kobayashi Date: Wed, 21 Sep 2022 18:24:17 +0900 Subject: [PATCH 07/47] media: ov8856: Add runtime PM callbacks There were no runtime PM callbacks registered, leaving regulators being enabled while the device is suspended on DT systems. Adjust and register existing power controlling functions to turn them off/on. Signed-off-by: Hidenori Kobayashi Reviewed-by: Tomasz Figa Signed-off-by: Sakari Ailus --- drivers/media/i2c/ov8856.c | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/drivers/media/i2c/ov8856.c b/drivers/media/i2c/ov8856.c index efa18d026ac3..cf8384e09413 100644 --- a/drivers/media/i2c/ov8856.c +++ b/drivers/media/i2c/ov8856.c @@ -2110,17 +2110,18 @@ static int ov8856_set_stream(struct v4l2_subdev *sd, int enable) return ret; } -static int __ov8856_power_on(struct ov8856 *ov8856) +static int ov8856_power_on(struct device *dev) { - struct i2c_client *client = v4l2_get_subdevdata(&ov8856->sd); + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ov8856 *ov8856 = to_ov8856(sd); int ret; - if (is_acpi_node(dev_fwnode(&client->dev))) + if (is_acpi_node(dev_fwnode(dev))) return 0; ret = clk_prepare_enable(ov8856->xvclk); if (ret < 0) { - dev_err(&client->dev, "failed to enable xvclk\n"); + dev_err(dev, "failed to enable xvclk\n"); return ret; } @@ -2132,7 +2133,7 @@ static int __ov8856_power_on(struct ov8856 *ov8856) ret = regulator_bulk_enable(ARRAY_SIZE(ov8856_supply_names), ov8856->supplies); if (ret < 0) { - dev_err(&client->dev, "failed to enable regulators\n"); + dev_err(dev, "failed to enable regulators\n"); goto disable_clk; } @@ -2148,17 +2149,20 @@ static int __ov8856_power_on(struct ov8856 *ov8856) return ret; } -static void __ov8856_power_off(struct ov8856 *ov8856) +static int ov8856_power_off(struct device *dev) { - struct i2c_client *client = v4l2_get_subdevdata(&ov8856->sd); + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ov8856 *ov8856 = to_ov8856(sd); - if (is_acpi_node(dev_fwnode(&client->dev))) - return; + if (is_acpi_node(dev_fwnode(dev))) + return 0; gpiod_set_value_cansleep(ov8856->reset_gpio, 1); regulator_bulk_disable(ARRAY_SIZE(ov8856_supply_names), ov8856->supplies); clk_disable_unprepare(ov8856->xvclk); + + return 0; } static int __maybe_unused ov8856_suspend(struct device *dev) @@ -2170,7 +2174,7 @@ static int __maybe_unused ov8856_suspend(struct device *dev) if (ov8856->streaming) ov8856_stop_streaming(ov8856); - __ov8856_power_off(ov8856); + ov8856_power_off(dev); mutex_unlock(&ov8856->mutex); return 0; @@ -2184,7 +2188,7 @@ static int __maybe_unused ov8856_resume(struct device *dev) mutex_lock(&ov8856->mutex); - __ov8856_power_on(ov8856); + ov8856_power_on(dev); if (ov8856->streaming) { ret = ov8856_start_streaming(ov8856); if (ret) { @@ -2451,7 +2455,7 @@ static void ov8856_remove(struct i2c_client *client) pm_runtime_disable(&client->dev); mutex_destroy(&ov8856->mutex); - __ov8856_power_off(ov8856); + ov8856_power_off(&client->dev); } static int ov8856_probe(struct i2c_client *client) @@ -2475,7 +2479,7 @@ static int ov8856_probe(struct i2c_client *client) full_power = acpi_dev_state_d0(&client->dev); if (full_power) { - ret = __ov8856_power_on(ov8856); + ret = ov8856_power_on(&client->dev); if (ret) { dev_err(&client->dev, "failed to power on\n"); return ret; @@ -2531,13 +2535,14 @@ static int ov8856_probe(struct i2c_client *client) mutex_destroy(&ov8856->mutex); probe_power_off: - __ov8856_power_off(ov8856); + ov8856_power_off(&client->dev); return ret; } static const struct dev_pm_ops ov8856_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(ov8856_suspend, ov8856_resume) + SET_RUNTIME_PM_OPS(ov8856_power_off, ov8856_power_on, NULL) }; #ifdef CONFIG_ACPI From c95770e4fc172696dcb1450893cda7d6324d96fc Mon Sep 17 00:00:00 2001 From: Rafael Mendonca Date: Tue, 20 Sep 2022 11:27:48 -0300 Subject: [PATCH 08/47] media: i2c: ov5648: Free V4L2 fwnode data on unbind The V4L2 fwnode data structure doesn't get freed on unbind, which leads to a memleak. Fixes: e43ccb0a045f ("media: i2c: Add support for the OV5648 image sensor") Signed-off-by: Rafael Mendonca Reviewed-by: Tommaso Merciai Reviewed-by: Paul Kocialkowski Signed-off-by: Sakari Ailus --- drivers/media/i2c/ov5648.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/media/i2c/ov5648.c b/drivers/media/i2c/ov5648.c index 84604ea7bdf9..17465fcf28e3 100644 --- a/drivers/media/i2c/ov5648.c +++ b/drivers/media/i2c/ov5648.c @@ -2597,6 +2597,7 @@ static void ov5648_remove(struct i2c_client *client) v4l2_ctrl_handler_free(&sensor->ctrls.handler); mutex_destroy(&sensor->mutex); media_entity_cleanup(&subdev->entity); + v4l2_fwnode_endpoint_free(&sensor->endpoint); } static const struct dev_pm_ops ov5648_pm_ops = { From f98a5c2e1c4396488c27274ba82afc11725a4bcc Mon Sep 17 00:00:00 2001 From: Marek Szyprowski Date: Fri, 23 Sep 2022 11:42:01 +0200 Subject: [PATCH 09/47] media: exynos4-is: don't rely on the v4l2_async_subdev internals Commit 1f391df44607 ("media: v4l2-async: Use endpoints in __v4l2_async_nf_add_fwnode_remote()") changed the data that is stored in the v4l2_async_subdev internals from the fwnode pointer to the parent device to the fwnode pointer to the matched endpoint. This broke the sensor matching code, which relied on the particular fwnode data in the v4l2_async_subdev internals. Fix this by simply matching the v4l2_async_subdev pointer, which is already available there. Reported-by: Daniel Scally Fixes: fa91f1056f17 ("[media] exynos4-is: Add support for asynchronous subdevices registration") Signed-off-by: Marek Szyprowski Reviewed-by: Daniel Scally Signed-off-by: Sakari Ailus --- drivers/media/platform/samsung/exynos4-is/media-dev.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/media/platform/samsung/exynos4-is/media-dev.c b/drivers/media/platform/samsung/exynos4-is/media-dev.c index 52b43ea04030..412213b0c384 100644 --- a/drivers/media/platform/samsung/exynos4-is/media-dev.c +++ b/drivers/media/platform/samsung/exynos4-is/media-dev.c @@ -1380,9 +1380,7 @@ static int subdev_notifier_bound(struct v4l2_async_notifier *notifier, /* Find platform data for this sensor subdev */ for (i = 0; i < ARRAY_SIZE(fmd->sensor); i++) - if (fmd->sensor[i].asd && - fmd->sensor[i].asd->match.fwnode == - of_fwnode_handle(subdev->dev->of_node)) + if (fmd->sensor[i].asd == asd) si = &fmd->sensor[i]; if (si == NULL) From 38fc5136ac16fe395577996d860ee55abb963922 Mon Sep 17 00:00:00 2001 From: Shawn Tu Date: Tue, 25 Oct 2022 10:27:38 +0800 Subject: [PATCH 10/47] media: i2c: Add ov08x40 image sensor driver Add a V4L2 sub-device driver for Omnivision ov08X40 image sensor. This is a camera sensor using the I2C bus for control and the CSI-2 bus for data. This driver supports following features: - manual exposure and analog/digital gain control support - vblank/hblank control support - test pattern support - media controller support - runtime PM support - support following resolutions: + 3856x2464 at 30FPS + 1928x1208 at 30FPS Signed-off-by: Jason Chen Signed-off-by: Shawn Tu Signed-off-by: Sakari Ailus --- MAINTAINERS | 7 + drivers/media/i2c/Kconfig | 13 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/ov08x40.c | 3327 +++++++++++++++++++++++++++++++++++ 4 files changed, 3348 insertions(+) create mode 100644 drivers/media/i2c/ov08x40.c diff --git a/MAINTAINERS b/MAINTAINERS index e04d944005ba..f3ac270d9fe8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15176,6 +15176,13 @@ S: Maintained T: git git://linuxtv.org/media_tree.git F: drivers/media/i2c/ov08d10.c +OMNIVISION OV08X40 SENSOR DRIVER +M: Jason Chen +L: linux-media@vger.kernel.org +S: Maintained +T: git git://linuxtv.org/media_tree.git +F: drivers/media/i2c/ov08x40.c + OMNIVISION OV13858 SENSOR DRIVER M: Sakari Ailus L: linux-media@vger.kernel.org diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 7806d4b81716..8d749c01df70 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -364,6 +364,19 @@ config VIDEO_OV08D10 To compile this driver as a module, choose M here: the module will be called ov08d10. +config VIDEO_OV08X40 + tristate "OmniVision OV08X40 sensor support" + depends on VIDEO_V4L2 && I2C + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + This is a Video4Linux2 sensor driver for the OmniVision + OV08X40 camera. + + To compile this driver as a module, choose M here: the + module will be called ov08x40. + config VIDEO_OV13858 tristate "OmniVision OV13858 sensor support" depends on I2C && VIDEO_DEV diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 0a2933103dd9..83be451ef326 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -72,6 +72,7 @@ obj-$(CONFIG_VIDEO_NOON010PC30) += noon010pc30.o obj-$(CONFIG_VIDEO_OG01A1B) += og01a1b.o obj-$(CONFIG_VIDEO_OV02A10) += ov02a10.o obj-$(CONFIG_VIDEO_OV08D10) += ov08d10.o +obj-$(CONFIG_VIDEO_OV08X40) += ov08x40.o obj-$(CONFIG_VIDEO_OV13858) += ov13858.o obj-$(CONFIG_VIDEO_OV13B10) += ov13b10.o obj-$(CONFIG_VIDEO_OV2640) += ov2640.o diff --git a/drivers/media/i2c/ov08x40.c b/drivers/media/i2c/ov08x40.c new file mode 100644 index 000000000000..b4ade17a83f5 --- /dev/null +++ b/drivers/media/i2c/ov08x40.c @@ -0,0 +1,3327 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2022 Intel Corporation. + +#include +#include +#include +#include +#include +#include +#include +#include + +#define OV08X40_REG_VALUE_08BIT 1 +#define OV08X40_REG_VALUE_16BIT 2 +#define OV08X40_REG_VALUE_24BIT 3 + +#define OV08X40_REG_MODE_SELECT 0x0100 +#define OV08X40_MODE_STANDBY 0x00 +#define OV08X40_MODE_STREAMING 0x01 + +#define OV08X40_REG_AO_STANDBY 0x1000 +#define OV08X40_AO_STREAMING 0x04 + +#define OV08X40_REG_MS_SELECT 0x1001 +#define OV08X40_MS_STANDBY 0x00 +#define OV08X40_MS_STREAMING 0x04 + +#define OV08X40_REG_SOFTWARE_RST 0x0103 +#define OV08X40_SOFTWARE_RST 0x01 + +/* Chip ID */ +#define OV08X40_REG_CHIP_ID 0x300a +#define OV08X40_CHIP_ID 0x560858 + +/* V_TIMING internal */ +#define OV08X40_REG_VTS 0x380e +#define OV08X40_VTS_30FPS 0x1388 +#define OV08X40_VTS_BIN_30FPS 0x115c +#define OV08X40_VTS_MAX 0x7fff + +/* H TIMING internal */ +#define OV08X40_REG_HTS 0x380c +#define OV08X40_HTS_30FPS 0x0280 + +/* Exposure control */ +#define OV08X40_REG_EXPOSURE 0x3500 +#define OV08X40_EXPOSURE_MAX_MARGIN 31 +#define OV08X40_EXPOSURE_MIN 1 +#define OV08X40_EXPOSURE_STEP 1 +#define OV08X40_EXPOSURE_DEFAULT 0x40 + +/* Short Exposure control */ +#define OV08X40_REG_SHORT_EXPOSURE 0x3540 + +/* Analog gain control */ +#define OV08X40_REG_ANALOG_GAIN 0x3508 +#define OV08X40_ANA_GAIN_MIN 0x80 +#define OV08X40_ANA_GAIN_MAX 0x07c0 +#define OV08X40_ANA_GAIN_STEP 1 +#define OV08X40_ANA_GAIN_DEFAULT 0x80 + +/* Digital gain control */ +#define OV08X40_REG_DGTL_GAIN_H 0x350a +#define OV08X40_REG_DGTL_GAIN_M 0x350b +#define OV08X40_REG_DGTL_GAIN_L 0x350c + +#define OV08X40_DGTL_GAIN_MIN 1024 /* Min = 1 X */ +#define OV08X40_DGTL_GAIN_MAX (4096 - 1) /* Max = 4 X */ +#define OV08X40_DGTL_GAIN_DEFAULT 2560 /* Default gain = 2.5 X */ +#define OV08X40_DGTL_GAIN_STEP 1 /* Each step = 1/1024 */ + +#define OV08X40_DGTL_GAIN_L_SHIFT 6 +#define OV08X40_DGTL_GAIN_L_MASK 0x3 +#define OV08X40_DGTL_GAIN_M_SHIFT 2 +#define OV08X40_DGTL_GAIN_M_MASK 0xff +#define OV08X40_DGTL_GAIN_H_SHIFT 10 +#define OV08X40_DGTL_GAIN_H_MASK 0x1F + +/* Test Pattern Control */ +#define OV08X40_REG_TEST_PATTERN 0x50C1 +#define OV08X40_REG_ISP 0x5000 +#define OV08X40_REG_SHORT_TEST_PATTERN 0x53C1 +#define OV08X40_TEST_PATTERN_ENABLE BIT(0) +#define OV08X40_TEST_PATTERN_MASK 0xcf +#define OV08X40_TEST_PATTERN_BAR_SHIFT 4 + +/* Flip Control */ +#define OV08X40_REG_VFLIP 0x3820 +#define OV08X40_REG_MIRROR 0x3821 + +/* Horizontal Window Offset */ +#define OV08X40_REG_H_WIN_OFFSET 0x3811 + +/* Vertical Window Offset */ +#define OV08X40_REG_V_WIN_OFFSET 0x3813 + +enum { + OV08X40_LINK_FREQ_400MHZ_INDEX, +}; + +struct ov08x40_reg { + u16 address; + u8 val; +}; + +struct ov08x40_reg_list { + u32 num_of_regs; + const struct ov08x40_reg *regs; +}; + +/* Link frequency config */ +struct ov08x40_link_freq_config { + u32 pixels_per_line; + + /* registers for this link frequency */ + struct ov08x40_reg_list reg_list; +}; + +/* Mode : resolution and related config&values */ +struct ov08x40_mode { + /* Frame width */ + u32 width; + /* Frame height */ + u32 height; + + u32 lanes; + /* V-timing */ + u32 vts_def; + u32 vts_min; + + /* Index of Link frequency config to be used */ + u32 link_freq_index; + /* Default register values */ + struct ov08x40_reg_list reg_list; +}; + +static const struct ov08x40_reg mipi_data_rate_800mbps[] = { + {0x0103, 0x01}, + {0x1000, 0x00}, + {0x1601, 0xd0}, + {0x1001, 0x04}, + {0x5004, 0x53}, + {0x5110, 0x00}, + {0x5111, 0x14}, + {0x5112, 0x01}, + {0x5113, 0x7b}, + {0x5114, 0x00}, + {0x5152, 0xa3}, + {0x5a52, 0x1f}, + {0x5a1a, 0x0e}, + {0x5a1b, 0x10}, + {0x5a1f, 0x0e}, + {0x5a27, 0x0e}, + {0x6002, 0x2e}, +}; + +static const struct ov08x40_reg mode_3856x2416_regs[] = { + {0x5000, 0x5d}, + {0x5001, 0x20}, + {0x5008, 0xb0}, + {0x50c1, 0x00}, + {0x53c1, 0x00}, + {0x5f40, 0x00}, + {0x5f41, 0x40}, + {0x0300, 0x3a}, + {0x0301, 0xc8}, + {0x0302, 0x31}, + {0x0303, 0x03}, + {0x0304, 0x01}, + {0x0305, 0xa1}, + {0x0306, 0x04}, + {0x0307, 0x01}, + {0x0308, 0x03}, + {0x0309, 0x03}, + {0x0310, 0x0a}, + {0x0311, 0x02}, + {0x0312, 0x01}, + {0x0313, 0x08}, + {0x0314, 0x66}, + {0x0315, 0x00}, + {0x0316, 0x34}, + {0x0320, 0x02}, + {0x0321, 0x03}, + {0x0323, 0x05}, + {0x0324, 0x01}, + {0x0325, 0xb8}, + {0x0326, 0x4a}, + {0x0327, 0x04}, + {0x0329, 0x00}, + {0x032a, 0x05}, + {0x032b, 0x00}, + {0x032c, 0x00}, + {0x032d, 0x00}, + {0x032e, 0x02}, + {0x032f, 0xa0}, + {0x0350, 0x00}, + {0x0360, 0x01}, + {0x1216, 0x60}, + {0x1217, 0x5b}, + {0x1218, 0x00}, + {0x1220, 0x24}, + {0x198a, 0x00}, + {0x198b, 0x01}, + {0x198e, 0x00}, + {0x198f, 0x01}, + {0x3009, 0x04}, + {0x3012, 0x41}, + {0x3015, 0x00}, + {0x3016, 0xb0}, + {0x3017, 0xf0}, + {0x3018, 0xf0}, + {0x3019, 0xd2}, + {0x301a, 0xb0}, + {0x301c, 0x81}, + {0x301d, 0x02}, + {0x301e, 0x80}, + {0x3022, 0xf0}, + {0x3025, 0x89}, + {0x3030, 0x03}, + {0x3044, 0xc2}, + {0x3050, 0x35}, + {0x3051, 0x60}, + {0x3052, 0x25}, + {0x3053, 0x00}, + {0x3054, 0x00}, + {0x3055, 0x02}, + {0x3056, 0x80}, + {0x3057, 0x80}, + {0x3058, 0x80}, + {0x3059, 0x00}, + {0x3107, 0x86}, + {0x3400, 0x1c}, + {0x3401, 0x80}, + {0x3402, 0x8c}, + {0x3419, 0x13}, + {0x341a, 0x89}, + {0x341b, 0x30}, + {0x3420, 0x00}, + {0x3421, 0x00}, + {0x3422, 0x00}, + {0x3423, 0x00}, + {0x3424, 0x00}, + {0x3425, 0x00}, + {0x3426, 0x00}, + {0x3427, 0x00}, + {0x3428, 0x0f}, + {0x3429, 0x00}, + {0x342a, 0x00}, + {0x342b, 0x00}, + {0x342c, 0x00}, + {0x342d, 0x00}, + {0x342e, 0x00}, + {0x342f, 0x11}, + {0x3430, 0x11}, + {0x3431, 0x10}, + {0x3432, 0x00}, + {0x3433, 0x00}, + {0x3434, 0x00}, + {0x3435, 0x00}, + {0x3436, 0x00}, + {0x3437, 0x00}, + {0x3442, 0x02}, + {0x3443, 0x02}, + {0x3444, 0x07}, + {0x3450, 0x00}, + {0x3451, 0x00}, + {0x3452, 0x18}, + {0x3453, 0x18}, + {0x3454, 0x00}, + {0x3455, 0x80}, + {0x3456, 0x08}, + {0x3500, 0x00}, + {0x3501, 0x02}, + {0x3502, 0x00}, + {0x3504, 0x4c}, + {0x3506, 0x30}, + {0x3507, 0x00}, + {0x3508, 0x01}, + {0x3509, 0x00}, + {0x350a, 0x01}, + {0x350b, 0x00}, + {0x350c, 0x00}, + {0x3540, 0x00}, + {0x3541, 0x01}, + {0x3542, 0x00}, + {0x3544, 0x4c}, + {0x3546, 0x30}, + {0x3547, 0x00}, + {0x3548, 0x01}, + {0x3549, 0x00}, + {0x354a, 0x01}, + {0x354b, 0x00}, + {0x354c, 0x00}, + {0x3688, 0x02}, + {0x368a, 0x2e}, + {0x368e, 0x71}, + {0x3696, 0xd1}, + {0x3699, 0x00}, + {0x369a, 0x00}, + {0x36a4, 0x00}, + {0x36a6, 0x00}, + {0x3711, 0x00}, + {0x3712, 0x51}, + {0x3713, 0x00}, + {0x3714, 0x24}, + {0x3716, 0x00}, + {0x3718, 0x07}, + {0x371a, 0x1c}, + {0x371b, 0x00}, + {0x3720, 0x08}, + {0x3725, 0x32}, + {0x3727, 0x05}, + {0x3760, 0x02}, + {0x3761, 0x17}, + {0x3762, 0x02}, + {0x3763, 0x02}, + {0x3764, 0x02}, + {0x3765, 0x2c}, + {0x3766, 0x04}, + {0x3767, 0x2c}, + {0x3768, 0x02}, + {0x3769, 0x00}, + {0x376b, 0x20}, + {0x376e, 0x03}, + {0x37b0, 0x00}, + {0x37b1, 0xab}, + {0x37b2, 0x01}, + {0x37b3, 0x82}, + {0x37b4, 0x00}, + {0x37b5, 0xe4}, + {0x37b6, 0x01}, + {0x37b7, 0xee}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x00}, + {0x3804, 0x0f}, + {0x3805, 0x1f}, + {0x3806, 0x09}, + {0x3807, 0x7f}, + {0x3808, 0x0f}, + {0x3809, 0x10}, + {0x380a, 0x09}, + {0x380b, 0x70}, + {0x380c, 0x02}, + {0x380d, 0x80}, + {0x380e, 0x13}, + {0x380f, 0x88}, + {0x3810, 0x00}, + {0x3811, 0x08}, + {0x3812, 0x00}, + {0x3813, 0x07}, + {0x3814, 0x11}, + {0x3815, 0x11}, + {0x3820, 0x00}, + {0x3821, 0x04}, + {0x3822, 0x00}, + {0x3823, 0x04}, + {0x3828, 0x0f}, + {0x382a, 0x80}, + {0x382e, 0x41}, + {0x3837, 0x08}, + {0x383a, 0x81}, + {0x383b, 0x81}, + {0x383c, 0x11}, + {0x383d, 0x11}, + {0x383e, 0x00}, + {0x383f, 0x38}, + {0x3840, 0x00}, + {0x3847, 0x00}, + {0x384a, 0x00}, + {0x384c, 0x02}, + {0x384d, 0x80}, + {0x3856, 0x50}, + {0x3857, 0x30}, + {0x3858, 0x80}, + {0x3859, 0x40}, + {0x3860, 0x00}, + {0x3888, 0x00}, + {0x3889, 0x00}, + {0x388a, 0x00}, + {0x388b, 0x00}, + {0x388c, 0x00}, + {0x388d, 0x00}, + {0x388e, 0x00}, + {0x388f, 0x00}, + {0x3894, 0x00}, + {0x3895, 0x00}, + {0x3c84, 0x00}, + {0x3d85, 0x8b}, + {0x3daa, 0x80}, + {0x3dab, 0x14}, + {0x3dac, 0x80}, + {0x3dad, 0xc8}, + {0x3dae, 0x81}, + {0x3daf, 0x7b}, + {0x3f00, 0x10}, + {0x3f01, 0x11}, + {0x3f06, 0x0d}, + {0x3f07, 0x0b}, + {0x3f08, 0x0d}, + {0x3f09, 0x0b}, + {0x3f0a, 0x01}, + {0x3f0b, 0x11}, + {0x3f0c, 0x33}, + {0x4001, 0x07}, + {0x4007, 0x20}, + {0x4008, 0x00}, + {0x4009, 0x05}, + {0x400a, 0x00}, + {0x400b, 0x08}, + {0x400c, 0x00}, + {0x400d, 0x08}, + {0x400e, 0x14}, + {0x4010, 0xf4}, + {0x4011, 0x03}, + {0x4012, 0x55}, + {0x4015, 0x00}, + {0x4016, 0x2d}, + {0x4017, 0x00}, + {0x4018, 0x0f}, + {0x401b, 0x08}, + {0x401c, 0x00}, + {0x401d, 0x10}, + {0x401e, 0x02}, + {0x401f, 0x00}, + {0x4050, 0x06}, + {0x4051, 0xff}, + {0x4052, 0xff}, + {0x4053, 0xff}, + {0x4054, 0xff}, + {0x4055, 0xff}, + {0x4056, 0xff}, + {0x4057, 0x7f}, + {0x4058, 0x00}, + {0x4059, 0x00}, + {0x405a, 0x00}, + {0x405b, 0x00}, + {0x405c, 0x07}, + {0x405d, 0xff}, + {0x405e, 0x07}, + {0x405f, 0xff}, + {0x4080, 0x78}, + {0x4081, 0x78}, + {0x4082, 0x78}, + {0x4083, 0x78}, + {0x4019, 0x00}, + {0x401a, 0x40}, + {0x4020, 0x04}, + {0x4021, 0x00}, + {0x4022, 0x04}, + {0x4023, 0x00}, + {0x4024, 0x04}, + {0x4025, 0x00}, + {0x4026, 0x04}, + {0x4027, 0x00}, + {0x4030, 0x00}, + {0x4031, 0x00}, + {0x4032, 0x00}, + {0x4033, 0x00}, + {0x4034, 0x00}, + {0x4035, 0x00}, + {0x4036, 0x00}, + {0x4037, 0x00}, + {0x4040, 0x00}, + {0x4041, 0x80}, + {0x4042, 0x00}, + {0x4043, 0x80}, + {0x4044, 0x00}, + {0x4045, 0x80}, + {0x4046, 0x00}, + {0x4047, 0x80}, + {0x4060, 0x00}, + {0x4061, 0x00}, + {0x4062, 0x00}, + {0x4063, 0x00}, + {0x4064, 0x00}, + {0x4065, 0x00}, + {0x4066, 0x00}, + {0x4067, 0x00}, + {0x4068, 0x00}, + {0x4069, 0x00}, + {0x406a, 0x00}, + {0x406b, 0x00}, + {0x406c, 0x00}, + {0x406d, 0x00}, + {0x406e, 0x00}, + {0x406f, 0x00}, + {0x4070, 0x00}, + {0x4071, 0x00}, + {0x4072, 0x00}, + {0x4073, 0x00}, + {0x4074, 0x00}, + {0x4075, 0x00}, + {0x4076, 0x00}, + {0x4077, 0x00}, + {0x4078, 0x00}, + {0x4079, 0x00}, + {0x407a, 0x00}, + {0x407b, 0x00}, + {0x407c, 0x00}, + {0x407d, 0x00}, + {0x407e, 0x00}, + {0x407f, 0x00}, + {0x40e0, 0x00}, + {0x40e1, 0x00}, + {0x40e2, 0x00}, + {0x40e3, 0x00}, + {0x40e4, 0x00}, + {0x40e5, 0x00}, + {0x40e6, 0x00}, + {0x40e7, 0x00}, + {0x40e8, 0x00}, + {0x40e9, 0x80}, + {0x40ea, 0x00}, + {0x40eb, 0x80}, + {0x40ec, 0x00}, + {0x40ed, 0x80}, + {0x40ee, 0x00}, + {0x40ef, 0x80}, + {0x40f0, 0x02}, + {0x40f1, 0x04}, + {0x4300, 0x00}, + {0x4301, 0x00}, + {0x4302, 0x00}, + {0x4303, 0x00}, + {0x4304, 0x00}, + {0x4305, 0x00}, + {0x4306, 0x00}, + {0x4307, 0x00}, + {0x4308, 0x00}, + {0x4309, 0x00}, + {0x430a, 0x00}, + {0x430b, 0xff}, + {0x430c, 0xff}, + {0x430d, 0x00}, + {0x430e, 0x00}, + {0x4315, 0x00}, + {0x4316, 0x00}, + {0x4317, 0x00}, + {0x4318, 0x00}, + {0x4319, 0x00}, + {0x431a, 0x00}, + {0x431b, 0x00}, + {0x431c, 0x00}, + {0x4500, 0x07}, + {0x4501, 0x00}, + {0x4502, 0x00}, + {0x4503, 0x0f}, + {0x4504, 0x80}, + {0x4506, 0x01}, + {0x4509, 0x05}, + {0x450c, 0x00}, + {0x450d, 0x20}, + {0x450e, 0x00}, + {0x450f, 0x00}, + {0x4510, 0x00}, + {0x4523, 0x00}, + {0x4526, 0x00}, + {0x4542, 0x00}, + {0x4543, 0x00}, + {0x4544, 0x00}, + {0x4545, 0x00}, + {0x4546, 0x00}, + {0x4547, 0x10}, + {0x4602, 0x00}, + {0x4603, 0x15}, + {0x460b, 0x07}, + {0x4680, 0x11}, + {0x4686, 0x00}, + {0x4687, 0x00}, + {0x4700, 0x00}, + {0x4800, 0x64}, + {0x4806, 0x40}, + {0x480b, 0x10}, + {0x480c, 0x80}, + {0x480f, 0x32}, + {0x4813, 0xe4}, + {0x4837, 0x14}, + {0x4850, 0x42}, + {0x4884, 0x04}, + {0x4c00, 0xf8}, + {0x4c01, 0x44}, + {0x4c03, 0x00}, + {0x4d00, 0x00}, + {0x4d01, 0x16}, + {0x4d04, 0x10}, + {0x4d05, 0x00}, + {0x4d06, 0x0c}, + {0x4d07, 0x00}, + {0x3d84, 0x04}, + {0x3680, 0xa4}, + {0x3682, 0x80}, + {0x3601, 0x40}, + {0x3602, 0x90}, + {0x3608, 0x0a}, + {0x3938, 0x09}, + {0x3a74, 0x84}, + {0x3a99, 0x84}, + {0x3ab9, 0xa6}, + {0x3aba, 0xba}, + {0x3b12, 0x84}, + {0x3b14, 0xbb}, + {0x3b15, 0xbf}, + {0x3a29, 0x26}, + {0x3a1f, 0x8a}, + {0x3a22, 0x91}, + {0x3a25, 0x96}, + {0x3a28, 0xb4}, + {0x3a2b, 0xba}, + {0x3a2e, 0xbf}, + {0x3a31, 0xc1}, + {0x3a20, 0x00}, + {0x3939, 0x9d}, + {0x3902, 0x0e}, + {0x3903, 0x0e}, + {0x3904, 0x0e}, + {0x3905, 0x0e}, + {0x3906, 0x07}, + {0x3907, 0x0d}, + {0x3908, 0x11}, + {0x3909, 0x12}, + {0x360f, 0x99}, + {0x390c, 0x33}, + {0x390d, 0x66}, + {0x390e, 0xaa}, + {0x3911, 0x90}, + {0x3913, 0x90}, + {0x3915, 0x90}, + {0x3917, 0x90}, + {0x3b3f, 0x9d}, + {0x3b45, 0x9d}, + {0x3b1b, 0xc9}, + {0x3b21, 0xc9}, + {0x3440, 0xa4}, + {0x3a23, 0x15}, + {0x3a26, 0x1d}, + {0x3a2c, 0x4a}, + {0x3a2f, 0x18}, + {0x3a32, 0x55}, + {0x3b0a, 0x01}, + {0x3b0b, 0x00}, + {0x3b0e, 0x01}, + {0x3b0f, 0x00}, + {0x392c, 0x02}, + {0x392d, 0x02}, + {0x392e, 0x04}, + {0x392f, 0x03}, + {0x3930, 0x08}, + {0x3931, 0x07}, + {0x3932, 0x10}, + {0x3933, 0x0c}, + {0x3609, 0x08}, + {0x3921, 0x0f}, + {0x3928, 0x15}, + {0x3929, 0x2a}, + {0x392a, 0x54}, + {0x392b, 0xa8}, + {0x3426, 0x10}, + {0x3407, 0x01}, + {0x3404, 0x01}, + {0x3500, 0x00}, + {0x3501, 0x10}, + {0x3502, 0x10}, + {0x3508, 0x0f}, + {0x3509, 0x80}, + {0x5a80, 0x75}, + {0x5a81, 0x75}, + {0x5a82, 0x75}, + {0x5a83, 0x75}, + {0x5a84, 0x75}, + {0x5a85, 0x75}, + {0x5a86, 0x75}, + {0x5a87, 0x75}, + {0x5a88, 0x75}, + {0x5a89, 0x75}, + {0x5a8a, 0x75}, + {0x5a8b, 0x75}, + {0x5a8c, 0x75}, + {0x5a8d, 0x75}, + {0x5a8e, 0x75}, + {0x5a8f, 0x75}, + {0x5a90, 0x75}, + {0x5a91, 0x75}, + {0x5a92, 0x75}, + {0x5a93, 0x75}, + {0x5a94, 0x75}, + {0x5a95, 0x75}, + {0x5a96, 0x75}, + {0x5a97, 0x75}, + {0x5a98, 0x75}, + {0x5a99, 0x75}, + {0x5a9a, 0x75}, + {0x5a9b, 0x75}, + {0x5a9c, 0x75}, + {0x5a9d, 0x75}, + {0x5a9e, 0x75}, + {0x5a9f, 0x75}, + {0x5aa0, 0x75}, + {0x5aa1, 0x75}, + {0x5aa2, 0x75}, + {0x5aa3, 0x75}, + {0x5aa4, 0x75}, + {0x5aa5, 0x75}, + {0x5aa6, 0x75}, + {0x5aa7, 0x75}, + {0x5aa8, 0x75}, + {0x5aa9, 0x75}, + {0x5aaa, 0x75}, + {0x5aab, 0x75}, + {0x5aac, 0x75}, + {0x5aad, 0x75}, + {0x5aae, 0x75}, + {0x5aaf, 0x75}, + {0x5ab0, 0x75}, + {0x5ab1, 0x75}, + {0x5ab2, 0x75}, + {0x5ab3, 0x75}, + {0x5ab4, 0x75}, + {0x5ab5, 0x75}, + {0x5ab6, 0x75}, + {0x5ab7, 0x75}, + {0x5ab8, 0x75}, + {0x5ab9, 0x75}, + {0x5aba, 0x75}, + {0x5abb, 0x75}, + {0x5abc, 0x75}, + {0x5abd, 0x75}, + {0x5abe, 0x75}, + {0x5abf, 0x75}, + {0x5ac0, 0x75}, + {0x5ac1, 0x75}, + {0x5ac2, 0x75}, + {0x5ac3, 0x75}, + {0x5ac4, 0x75}, + {0x5ac5, 0x75}, + {0x5ac6, 0x75}, + {0x5ac7, 0x75}, + {0x5ac8, 0x75}, + {0x5ac9, 0x75}, + {0x5aca, 0x75}, + {0x5acb, 0x75}, + {0x5acc, 0x75}, + {0x5acd, 0x75}, + {0x5ace, 0x75}, + {0x5acf, 0x75}, + {0x5ad0, 0x75}, + {0x5ad1, 0x75}, + {0x5ad2, 0x75}, + {0x5ad3, 0x75}, + {0x5ad4, 0x75}, + {0x5ad5, 0x75}, + {0x5ad6, 0x75}, + {0x5ad7, 0x75}, + {0x5ad8, 0x75}, + {0x5ad9, 0x75}, + {0x5ada, 0x75}, + {0x5adb, 0x75}, + {0x5adc, 0x75}, + {0x5add, 0x75}, + {0x5ade, 0x75}, + {0x5adf, 0x75}, + {0x5ae0, 0x75}, + {0x5ae1, 0x75}, + {0x5ae2, 0x75}, + {0x5ae3, 0x75}, + {0x5ae4, 0x75}, + {0x5ae5, 0x75}, + {0x5ae6, 0x75}, + {0x5ae7, 0x75}, + {0x5ae8, 0x75}, + {0x5ae9, 0x75}, + {0x5aea, 0x75}, + {0x5aeb, 0x75}, + {0x5aec, 0x75}, + {0x5aed, 0x75}, + {0x5aee, 0x75}, + {0x5aef, 0x75}, + {0x5af0, 0x75}, + {0x5af1, 0x75}, + {0x5af2, 0x75}, + {0x5af3, 0x75}, + {0x5af4, 0x75}, + {0x5af5, 0x75}, + {0x5af6, 0x75}, + {0x5af7, 0x75}, + {0x5af8, 0x75}, + {0x5af9, 0x75}, + {0x5afa, 0x75}, + {0x5afb, 0x75}, + {0x5afc, 0x75}, + {0x5afd, 0x75}, + {0x5afe, 0x75}, + {0x5aff, 0x75}, + {0x5b00, 0x75}, + {0x5b01, 0x75}, + {0x5b02, 0x75}, + {0x5b03, 0x75}, + {0x5b04, 0x75}, + {0x5b05, 0x75}, + {0x5b06, 0x75}, + {0x5b07, 0x75}, + {0x5b08, 0x75}, + {0x5b09, 0x75}, + {0x5b0a, 0x75}, + {0x5b0b, 0x75}, + {0x5b0c, 0x75}, + {0x5b0d, 0x75}, + {0x5b0e, 0x75}, + {0x5b0f, 0x75}, + {0x5b10, 0x75}, + {0x5b11, 0x75}, + {0x5b12, 0x75}, + {0x5b13, 0x75}, + {0x5b14, 0x75}, + {0x5b15, 0x75}, + {0x5b16, 0x75}, + {0x5b17, 0x75}, + {0x5b18, 0x75}, + {0x5b19, 0x75}, + {0x5b1a, 0x75}, + {0x5b1b, 0x75}, + {0x5b1c, 0x75}, + {0x5b1d, 0x75}, + {0x5b1e, 0x75}, + {0x5b1f, 0x75}, + {0x5b20, 0x75}, + {0x5b21, 0x75}, + {0x5b22, 0x75}, + {0x5b23, 0x75}, + {0x5b24, 0x75}, + {0x5b25, 0x75}, + {0x5b26, 0x75}, + {0x5b27, 0x75}, + {0x5b28, 0x75}, + {0x5b29, 0x75}, + {0x5b2a, 0x75}, + {0x5b2b, 0x75}, + {0x5b2c, 0x75}, + {0x5b2d, 0x75}, + {0x5b2e, 0x75}, + {0x5b2f, 0x75}, + {0x5b30, 0x75}, + {0x5b31, 0x75}, + {0x5b32, 0x75}, + {0x5b33, 0x75}, + {0x5b34, 0x75}, + {0x5b35, 0x75}, + {0x5b36, 0x75}, + {0x5b37, 0x75}, + {0x5b38, 0x75}, + {0x5b39, 0x75}, + {0x5b3a, 0x75}, + {0x5b3b, 0x75}, + {0x5b3c, 0x75}, + {0x5b3d, 0x75}, + {0x5b3e, 0x75}, + {0x5b3f, 0x75}, + {0x5b40, 0x75}, + {0x5b41, 0x75}, + {0x5b42, 0x75}, + {0x5b43, 0x75}, + {0x5b44, 0x75}, + {0x5b45, 0x75}, + {0x5b46, 0x75}, + {0x5b47, 0x75}, + {0x5b48, 0x75}, + {0x5b49, 0x75}, + {0x5b4a, 0x75}, + {0x5b4b, 0x75}, + {0x5b4c, 0x75}, + {0x5b4d, 0x75}, + {0x5b4e, 0x75}, + {0x5b4f, 0x75}, + {0x5b50, 0x75}, + {0x5b51, 0x75}, + {0x5b52, 0x75}, + {0x5b53, 0x75}, + {0x5b54, 0x75}, + {0x5b55, 0x75}, + {0x5b56, 0x75}, + {0x5b57, 0x75}, + {0x5b58, 0x75}, + {0x5b59, 0x75}, + {0x5b5a, 0x75}, + {0x5b5b, 0x75}, + {0x5b5c, 0x75}, + {0x5b5d, 0x75}, + {0x5b5e, 0x75}, + {0x5b5f, 0x75}, + {0x5b60, 0x75}, + {0x5b61, 0x75}, + {0x5b62, 0x75}, + {0x5b63, 0x75}, + {0x5b64, 0x75}, + {0x5b65, 0x75}, + {0x5b66, 0x75}, + {0x5b67, 0x75}, + {0x5b68, 0x75}, + {0x5b69, 0x75}, + {0x5b6a, 0x75}, + {0x5b6b, 0x75}, + {0x5b6c, 0x75}, + {0x5b6d, 0x75}, + {0x5b6e, 0x75}, + {0x5b6f, 0x75}, + {0x5b70, 0x75}, + {0x5b71, 0x75}, + {0x5b72, 0x75}, + {0x5b73, 0x75}, + {0x5b74, 0x75}, + {0x5b75, 0x75}, + {0x5b76, 0x75}, + {0x5b77, 0x75}, + {0x5b78, 0x75}, + {0x5b79, 0x75}, + {0x5b7a, 0x75}, + {0x5b7b, 0x75}, + {0x5b7c, 0x75}, + {0x5b7d, 0x75}, + {0x5b7e, 0x75}, + {0x5b7f, 0x75}, + {0x5b80, 0x75}, + {0x5b81, 0x75}, + {0x5b82, 0x75}, + {0x5b83, 0x75}, + {0x5b84, 0x75}, + {0x5b85, 0x75}, + {0x5b86, 0x75}, + {0x5b87, 0x75}, + {0x5b88, 0x75}, + {0x5b89, 0x75}, + {0x5b8a, 0x75}, + {0x5b8b, 0x75}, + {0x5b8c, 0x75}, + {0x5b8d, 0x75}, + {0x5b8e, 0x75}, + {0x5b8f, 0x75}, + {0x5b90, 0x75}, + {0x5b91, 0x75}, + {0x5b92, 0x75}, + {0x5b93, 0x75}, + {0x5b94, 0x75}, + {0x5b95, 0x75}, + {0x5b96, 0x75}, + {0x5b97, 0x75}, + {0x5b98, 0x75}, + {0x5b99, 0x75}, + {0x5b9a, 0x75}, + {0x5b9b, 0x75}, + {0x5b9c, 0x75}, + {0x5b9d, 0x75}, + {0x5b9e, 0x75}, + {0x5b9f, 0x75}, + {0x5bc0, 0x75}, + {0x5bc1, 0x75}, + {0x5bc2, 0x75}, + {0x5bc3, 0x75}, + {0x5bc4, 0x75}, + {0x5bc5, 0x75}, + {0x5bc6, 0x75}, + {0x5bc7, 0x75}, + {0x5bc8, 0x75}, + {0x5bc9, 0x75}, + {0x5bca, 0x75}, + {0x5bcb, 0x75}, + {0x5bcc, 0x75}, + {0x5bcd, 0x75}, + {0x5bce, 0x75}, + {0x5bcf, 0x75}, + {0x5bd0, 0x75}, + {0x5bd1, 0x75}, + {0x5bd2, 0x75}, + {0x5bd3, 0x75}, + {0x5bd4, 0x75}, + {0x5bd5, 0x75}, + {0x5bd6, 0x75}, + {0x5bd7, 0x75}, + {0x5bd8, 0x75}, + {0x5bd9, 0x75}, + {0x5bda, 0x75}, + {0x5bdb, 0x75}, + {0x5bdc, 0x75}, + {0x5bdd, 0x75}, + {0x5bde, 0x75}, + {0x5bdf, 0x75}, + {0x5be0, 0x75}, + {0x5be1, 0x75}, + {0x5be2, 0x75}, + {0x5be3, 0x75}, + {0x5be4, 0x75}, + {0x5be5, 0x75}, + {0x5be6, 0x75}, + {0x5be7, 0x75}, + {0x5be8, 0x75}, + {0x5be9, 0x75}, + {0x5bea, 0x75}, + {0x5beb, 0x75}, + {0x5bec, 0x75}, + {0x5bed, 0x75}, + {0x5bee, 0x75}, + {0x5bef, 0x75}, + {0x5bf0, 0x75}, + {0x5bf1, 0x75}, + {0x5bf2, 0x75}, + {0x5bf3, 0x75}, + {0x5bf4, 0x75}, + {0x5bf5, 0x75}, + {0x5bf6, 0x75}, + {0x5bf7, 0x75}, + {0x5bf8, 0x75}, + {0x5bf9, 0x75}, + {0x5bfa, 0x75}, + {0x5bfb, 0x75}, + {0x5bfc, 0x75}, + {0x5bfd, 0x75}, + {0x5bfe, 0x75}, + {0x5bff, 0x75}, + {0x5c00, 0x75}, + {0x5c01, 0x75}, + {0x5c02, 0x75}, + {0x5c03, 0x75}, + {0x5c04, 0x75}, + {0x5c05, 0x75}, + {0x5c06, 0x75}, + {0x5c07, 0x75}, + {0x5c08, 0x75}, + {0x5c09, 0x75}, + {0x5c0a, 0x75}, + {0x5c0b, 0x75}, + {0x5c0c, 0x75}, + {0x5c0d, 0x75}, + {0x5c0e, 0x75}, + {0x5c0f, 0x75}, + {0x5c10, 0x75}, + {0x5c11, 0x75}, + {0x5c12, 0x75}, + {0x5c13, 0x75}, + {0x5c14, 0x75}, + {0x5c15, 0x75}, + {0x5c16, 0x75}, + {0x5c17, 0x75}, + {0x5c18, 0x75}, + {0x5c19, 0x75}, + {0x5c1a, 0x75}, + {0x5c1b, 0x75}, + {0x5c1c, 0x75}, + {0x5c1d, 0x75}, + {0x5c1e, 0x75}, + {0x5c1f, 0x75}, + {0x5c20, 0x75}, + {0x5c21, 0x75}, + {0x5c22, 0x75}, + {0x5c23, 0x75}, + {0x5c24, 0x75}, + {0x5c25, 0x75}, + {0x5c26, 0x75}, + {0x5c27, 0x75}, + {0x5c28, 0x75}, + {0x5c29, 0x75}, + {0x5c2a, 0x75}, + {0x5c2b, 0x75}, + {0x5c2c, 0x75}, + {0x5c2d, 0x75}, + {0x5c2e, 0x75}, + {0x5c2f, 0x75}, + {0x5c30, 0x75}, + {0x5c31, 0x75}, + {0x5c32, 0x75}, + {0x5c33, 0x75}, + {0x5c34, 0x75}, + {0x5c35, 0x75}, + {0x5c36, 0x75}, + {0x5c37, 0x75}, + {0x5c38, 0x75}, + {0x5c39, 0x75}, + {0x5c3a, 0x75}, + {0x5c3b, 0x75}, + {0x5c3c, 0x75}, + {0x5c3d, 0x75}, + {0x5c3e, 0x75}, + {0x5c3f, 0x75}, + {0x5c40, 0x75}, + {0x5c41, 0x75}, + {0x5c42, 0x75}, + {0x5c43, 0x75}, + {0x5c44, 0x75}, + {0x5c45, 0x75}, + {0x5c46, 0x75}, + {0x5c47, 0x75}, + {0x5c48, 0x75}, + {0x5c49, 0x75}, + {0x5c4a, 0x75}, + {0x5c4b, 0x75}, + {0x5c4c, 0x75}, + {0x5c4d, 0x75}, + {0x5c4e, 0x75}, + {0x5c4f, 0x75}, + {0x5c50, 0x75}, + {0x5c51, 0x75}, + {0x5c52, 0x75}, + {0x5c53, 0x75}, + {0x5c54, 0x75}, + {0x5c55, 0x75}, + {0x5c56, 0x75}, + {0x5c57, 0x75}, + {0x5c58, 0x75}, + {0x5c59, 0x75}, + {0x5c5a, 0x75}, + {0x5c5b, 0x75}, + {0x5c5c, 0x75}, + {0x5c5d, 0x75}, + {0x5c5e, 0x75}, + {0x5c5f, 0x75}, + {0x5c60, 0x75}, + {0x5c61, 0x75}, + {0x5c62, 0x75}, + {0x5c63, 0x75}, + {0x5c64, 0x75}, + {0x5c65, 0x75}, + {0x5c66, 0x75}, + {0x5c67, 0x75}, + {0x5c68, 0x75}, + {0x5c69, 0x75}, + {0x5c6a, 0x75}, + {0x5c6b, 0x75}, + {0x5c6c, 0x75}, + {0x5c6d, 0x75}, + {0x5c6e, 0x75}, + {0x5c6f, 0x75}, + {0x5c70, 0x75}, + {0x5c71, 0x75}, + {0x5c72, 0x75}, + {0x5c73, 0x75}, + {0x5c74, 0x75}, + {0x5c75, 0x75}, + {0x5c76, 0x75}, + {0x5c77, 0x75}, + {0x5c78, 0x75}, + {0x5c79, 0x75}, + {0x5c7a, 0x75}, + {0x5c7b, 0x75}, + {0x5c7c, 0x75}, + {0x5c7d, 0x75}, + {0x5c7e, 0x75}, + {0x5c7f, 0x75}, + {0x5c80, 0x75}, + {0x5c81, 0x75}, + {0x5c82, 0x75}, + {0x5c83, 0x75}, + {0x5c84, 0x75}, + {0x5c85, 0x75}, + {0x5c86, 0x75}, + {0x5c87, 0x75}, + {0x5c88, 0x75}, + {0x5c89, 0x75}, + {0x5c8a, 0x75}, + {0x5c8b, 0x75}, + {0x5c8c, 0x75}, + {0x5c8d, 0x75}, + {0x5c8e, 0x75}, + {0x5c8f, 0x75}, + {0x5c90, 0x75}, + {0x5c91, 0x75}, + {0x5c92, 0x75}, + {0x5c93, 0x75}, + {0x5c94, 0x75}, + {0x5c95, 0x75}, + {0x5c96, 0x75}, + {0x5c97, 0x75}, + {0x5c98, 0x75}, + {0x5c99, 0x75}, + {0x5c9a, 0x75}, + {0x5c9b, 0x75}, + {0x5c9c, 0x75}, + {0x5c9d, 0x75}, + {0x5c9e, 0x75}, + {0x5c9f, 0x75}, + {0x5ca0, 0x75}, + {0x5ca1, 0x75}, + {0x5ca2, 0x75}, + {0x5ca3, 0x75}, + {0x5ca4, 0x75}, + {0x5ca5, 0x75}, + {0x5ca6, 0x75}, + {0x5ca7, 0x75}, + {0x5ca8, 0x75}, + {0x5ca9, 0x75}, + {0x5caa, 0x75}, + {0x5cab, 0x75}, + {0x5cac, 0x75}, + {0x5cad, 0x75}, + {0x5cae, 0x75}, + {0x5caf, 0x75}, + {0x5cb0, 0x75}, + {0x5cb1, 0x75}, + {0x5cb2, 0x75}, + {0x5cb3, 0x75}, + {0x5cb4, 0x75}, + {0x5cb5, 0x75}, + {0x5cb6, 0x75}, + {0x5cb7, 0x75}, + {0x5cb8, 0x75}, + {0x5cb9, 0x75}, + {0x5cba, 0x75}, + {0x5cbb, 0x75}, + {0x5cbc, 0x75}, + {0x5cbd, 0x75}, + {0x5cbe, 0x75}, + {0x5cbf, 0x75}, + {0x5cc0, 0x75}, + {0x5cc1, 0x75}, + {0x5cc2, 0x75}, + {0x5cc3, 0x75}, + {0x5cc4, 0x75}, + {0x5cc5, 0x75}, + {0x5cc6, 0x75}, + {0x5cc7, 0x75}, + {0x5cc8, 0x75}, + {0x5cc9, 0x75}, + {0x5cca, 0x75}, + {0x5ccb, 0x75}, + {0x5ccc, 0x75}, + {0x5ccd, 0x75}, + {0x5cce, 0x75}, + {0x5ccf, 0x75}, + {0x5cd0, 0x75}, + {0x5cd1, 0x75}, + {0x5cd2, 0x75}, + {0x5cd3, 0x75}, + {0x5cd4, 0x75}, + {0x5cd5, 0x75}, + {0x5cd6, 0x75}, + {0x5cd7, 0x75}, + {0x5cd8, 0x75}, + {0x5cd9, 0x75}, + {0x5cda, 0x75}, + {0x5cdb, 0x75}, + {0x5cdc, 0x75}, + {0x5cdd, 0x75}, + {0x5cde, 0x75}, + {0x5cdf, 0x75}, + {0x5ce0, 0x75}, + {0x5ce1, 0x75}, + {0x5ce2, 0x75}, + {0x5ce3, 0x75}, + {0x5ce4, 0x75}, + {0x5ce5, 0x75}, + {0x5ce6, 0x75}, + {0x5ce7, 0x75}, + {0x5ce8, 0x75}, + {0x5ce9, 0x75}, + {0x5cea, 0x75}, + {0x5ceb, 0x75}, + {0x5cec, 0x75}, + {0x5ced, 0x75}, + {0x5cee, 0x75}, + {0x5cef, 0x75}, + {0x5cf0, 0x75}, + {0x5cf1, 0x75}, + {0x5cf2, 0x75}, + {0x5cf3, 0x75}, + {0x5cf4, 0x75}, + {0x5cf5, 0x75}, + {0x5cf6, 0x75}, + {0x5cf7, 0x75}, + {0x5cf8, 0x75}, + {0x5cf9, 0x75}, + {0x5cfa, 0x75}, + {0x5cfb, 0x75}, + {0x5cfc, 0x75}, + {0x5cfd, 0x75}, + {0x5cfe, 0x75}, + {0x5cff, 0x75}, + {0x5d00, 0x75}, + {0x5d01, 0x75}, + {0x5d02, 0x75}, + {0x5d03, 0x75}, + {0x5d04, 0x75}, + {0x5d05, 0x75}, + {0x5d06, 0x75}, + {0x5d07, 0x75}, + {0x5d08, 0x75}, + {0x5d09, 0x75}, + {0x5d0a, 0x75}, + {0x5d0b, 0x75}, + {0x5d0c, 0x75}, + {0x5d0d, 0x75}, + {0x5d0e, 0x75}, + {0x5d0f, 0x75}, + {0x5d10, 0x75}, + {0x5d11, 0x75}, + {0x5d12, 0x75}, + {0x5d13, 0x75}, + {0x5d14, 0x75}, + {0x5d15, 0x75}, + {0x5d16, 0x75}, + {0x5d17, 0x75}, + {0x5d18, 0x75}, + {0x5d19, 0x75}, + {0x5d1a, 0x75}, + {0x5d1b, 0x75}, + {0x5d1c, 0x75}, + {0x5d1d, 0x75}, + {0x5d1e, 0x75}, + {0x5d1f, 0x75}, + {0x5d20, 0x75}, + {0x5d21, 0x75}, + {0x5d22, 0x75}, + {0x5d23, 0x75}, + {0x5d24, 0x75}, + {0x5d25, 0x75}, + {0x5d26, 0x75}, + {0x5d27, 0x75}, + {0x5d28, 0x75}, + {0x5d29, 0x75}, + {0x5d2a, 0x75}, + {0x5d2b, 0x75}, + {0x5d2c, 0x75}, + {0x5d2d, 0x75}, + {0x5d2e, 0x75}, + {0x5d2f, 0x75}, + {0x5d30, 0x75}, + {0x5d31, 0x75}, + {0x5d32, 0x75}, + {0x5d33, 0x75}, + {0x5d34, 0x75}, + {0x5d35, 0x75}, + {0x5d36, 0x75}, + {0x5d37, 0x75}, + {0x5d38, 0x75}, + {0x5d39, 0x75}, + {0x5d3a, 0x75}, + {0x5d3b, 0x75}, + {0x5d3c, 0x75}, + {0x5d3d, 0x75}, + {0x5d3e, 0x75}, + {0x5d3f, 0x75}, + {0x5d40, 0x75}, + {0x5d41, 0x75}, + {0x5d42, 0x75}, + {0x5d43, 0x75}, + {0x5d44, 0x75}, + {0x5d45, 0x75}, + {0x5d46, 0x75}, + {0x5d47, 0x75}, + {0x5d48, 0x75}, + {0x5d49, 0x75}, + {0x5d4a, 0x75}, + {0x5d4b, 0x75}, + {0x5d4c, 0x75}, + {0x5d4d, 0x75}, + {0x5d4e, 0x75}, + {0x5d4f, 0x75}, + {0x5d50, 0x75}, + {0x5d51, 0x75}, + {0x5d52, 0x75}, + {0x5d53, 0x75}, + {0x5d54, 0x75}, + {0x5d55, 0x75}, + {0x5d56, 0x75}, + {0x5d57, 0x75}, + {0x5d58, 0x75}, + {0x5d59, 0x75}, + {0x5d5a, 0x75}, + {0x5d5b, 0x75}, + {0x5d5c, 0x75}, + {0x5d5d, 0x75}, + {0x5d5e, 0x75}, + {0x5d5f, 0x75}, + {0x5d60, 0x75}, + {0x5d61, 0x75}, + {0x5d62, 0x75}, + {0x5d63, 0x75}, + {0x5d64, 0x75}, + {0x5d65, 0x75}, + {0x5d66, 0x75}, + {0x5d67, 0x75}, + {0x5d68, 0x75}, + {0x5d69, 0x75}, + {0x5d6a, 0x75}, + {0x5d6b, 0x75}, + {0x5d6c, 0x75}, + {0x5d6d, 0x75}, + {0x5d6e, 0x75}, + {0x5d6f, 0x75}, + {0x5d70, 0x75}, + {0x5d71, 0x75}, + {0x5d72, 0x75}, + {0x5d73, 0x75}, + {0x5d74, 0x75}, + {0x5d75, 0x75}, + {0x5d76, 0x75}, + {0x5d77, 0x75}, + {0x5d78, 0x75}, + {0x5d79, 0x75}, + {0x5d7a, 0x75}, + {0x5d7b, 0x75}, + {0x5d7c, 0x75}, + {0x5d7d, 0x75}, + {0x5d7e, 0x75}, + {0x5d7f, 0x75}, + {0x5d80, 0x75}, + {0x5d81, 0x75}, + {0x5d82, 0x75}, + {0x5d83, 0x75}, + {0x5d84, 0x75}, + {0x5d85, 0x75}, + {0x5d86, 0x75}, + {0x5d87, 0x75}, + {0x5d88, 0x75}, + {0x5d89, 0x75}, + {0x5d8a, 0x75}, + {0x5d8b, 0x75}, + {0x5d8c, 0x75}, + {0x5d8d, 0x75}, + {0x5d8e, 0x75}, + {0x5d8f, 0x75}, + {0x5d90, 0x75}, + {0x5d91, 0x75}, + {0x5d92, 0x75}, + {0x5d93, 0x75}, + {0x5d94, 0x75}, + {0x5d95, 0x75}, + {0x5d96, 0x75}, + {0x5d97, 0x75}, + {0x5d98, 0x75}, + {0x5d99, 0x75}, + {0x5d9a, 0x75}, + {0x5d9b, 0x75}, + {0x5d9c, 0x75}, + {0x5d9d, 0x75}, + {0x5d9e, 0x75}, + {0x5d9f, 0x75}, + {0x5da0, 0x75}, + {0x5da1, 0x75}, + {0x5da2, 0x75}, + {0x5da3, 0x75}, + {0x5da4, 0x75}, + {0x5da5, 0x75}, + {0x5da6, 0x75}, + {0x5da7, 0x75}, + {0x5da8, 0x75}, + {0x5da9, 0x75}, + {0x5daa, 0x75}, + {0x5dab, 0x75}, + {0x5dac, 0x75}, + {0x5dad, 0x75}, + {0x5dae, 0x75}, + {0x5daf, 0x75}, + {0x5db0, 0x75}, + {0x5db1, 0x75}, + {0x5db2, 0x75}, + {0x5db3, 0x75}, + {0x5db4, 0x75}, + {0x5db5, 0x75}, + {0x5db6, 0x75}, + {0x5db7, 0x75}, + {0x5db8, 0x75}, + {0x5db9, 0x75}, + {0x5dba, 0x75}, + {0x5dbb, 0x75}, + {0x5dbc, 0x75}, + {0x5dbd, 0x75}, + {0x5dbe, 0x75}, + {0x5dbf, 0x75}, + {0x5dc0, 0x75}, + {0x5dc1, 0x75}, + {0x5dc2, 0x75}, + {0x5dc3, 0x75}, + {0x5dc4, 0x75}, + {0x5dc5, 0x75}, + {0x5dc6, 0x75}, + {0x5dc7, 0x75}, + {0x5dc8, 0x75}, + {0x5dc9, 0x75}, + {0x5dca, 0x75}, + {0x5dcb, 0x75}, + {0x5dcc, 0x75}, + {0x5dcd, 0x75}, + {0x5dce, 0x75}, + {0x5dcf, 0x75}, + {0x5dd0, 0x75}, + {0x5dd1, 0x75}, + {0x5dd2, 0x75}, + {0x5dd3, 0x75}, + {0x5dd4, 0x75}, + {0x5dd5, 0x75}, + {0x5dd6, 0x75}, + {0x5dd7, 0x75}, + {0x5dd8, 0x75}, + {0x5dd9, 0x75}, + {0x5dda, 0x75}, + {0x5ddb, 0x75}, + {0x5ddc, 0x75}, + {0x5ddd, 0x75}, + {0x5dde, 0x75}, + {0x5ddf, 0x75}, + {0x5de0, 0x75}, + {0x5de1, 0x75}, + {0x5de2, 0x75}, + {0x5de3, 0x75}, + {0x5de4, 0x75}, + {0x5de5, 0x75}, + {0x5de6, 0x75}, + {0x5de7, 0x75}, + {0x5de8, 0x75}, + {0x5de9, 0x75}, + {0x5dea, 0x75}, + {0x5deb, 0x75}, + {0x5dec, 0x75}, + {0x5ded, 0x75}, + {0x5dee, 0x75}, + {0x5def, 0x75}, + {0x5df0, 0x75}, + {0x5df1, 0x75}, + {0x5df2, 0x75}, + {0x5df3, 0x75}, + {0x5df4, 0x75}, + {0x5df5, 0x75}, + {0x5df6, 0x75}, + {0x5df7, 0x75}, + {0x5df8, 0x75}, + {0x5df9, 0x75}, + {0x5dfa, 0x75}, + {0x5dfb, 0x75}, + {0x5dfc, 0x75}, + {0x5dfd, 0x75}, + {0x5dfe, 0x75}, + {0x5dff, 0x75}, + {0x5e00, 0x75}, + {0x5e01, 0x75}, + {0x5e02, 0x75}, + {0x5e03, 0x75}, + {0x5e04, 0x75}, + {0x5e05, 0x75}, + {0x5e06, 0x75}, + {0x5e07, 0x75}, + {0x5e08, 0x75}, + {0x5e09, 0x75}, + {0x5e0a, 0x75}, + {0x5e0b, 0x75}, + {0x5e0c, 0x75}, + {0x5e0d, 0x75}, + {0x5e0e, 0x75}, + {0x5e0f, 0x75}, + {0x5e10, 0x75}, + {0x5e11, 0x75}, + {0x5e12, 0x75}, + {0x5e13, 0x75}, + {0x5e14, 0x75}, + {0x5e15, 0x75}, + {0x5e16, 0x75}, + {0x5e17, 0x75}, + {0x5e18, 0x75}, + {0x5e19, 0x75}, + {0x5e1a, 0x75}, + {0x5e1b, 0x75}, + {0x5e1c, 0x75}, + {0x5e1d, 0x75}, + {0x5e1e, 0x75}, + {0x5e1f, 0x75}, + {0x5e20, 0x75}, + {0x5e21, 0x75}, + {0x5e22, 0x75}, + {0x5e23, 0x75}, + {0x5e24, 0x75}, + {0x5e25, 0x75}, + {0x5e26, 0x75}, + {0x5e27, 0x75}, + {0x5e28, 0x75}, + {0x5e29, 0x75}, + {0x5e2a, 0x75}, + {0x5e2b, 0x75}, + {0x5e2c, 0x75}, + {0x5e2d, 0x75}, + {0x5e2e, 0x75}, + {0x5e2f, 0x75}, + {0x5e30, 0x75}, + {0x5e31, 0x75}, + {0x5e32, 0x75}, + {0x5e33, 0x75}, + {0x5e34, 0x75}, + {0x5e35, 0x75}, + {0x5e36, 0x75}, + {0x5e37, 0x75}, + {0x5e38, 0x75}, + {0x5e39, 0x75}, + {0x5e3a, 0x75}, + {0x5e3b, 0x75}, + {0x5e3c, 0x75}, + {0x5e3d, 0x75}, + {0x5e3e, 0x75}, + {0x5e3f, 0x75}, + {0x5e40, 0x75}, + {0x5e41, 0x75}, + {0x5e42, 0x75}, + {0x5e43, 0x75}, + {0x5e44, 0x75}, + {0x5e45, 0x75}, + {0x5e46, 0x75}, + {0x5e47, 0x75}, + {0x5e48, 0x75}, + {0x5e49, 0x75}, + {0x5e4a, 0x75}, + {0x5e4b, 0x75}, + {0x5e4c, 0x75}, + {0x5e4d, 0x75}, + {0x5e4e, 0x75}, + {0x5e4f, 0x75}, + {0x5e50, 0x75}, + {0x5e51, 0x75}, + {0x5e52, 0x75}, + {0x5e53, 0x75}, + {0x5e54, 0x75}, + {0x5e55, 0x75}, + {0x5e56, 0x75}, + {0x5e57, 0x75}, + {0x5e58, 0x75}, + {0x5e59, 0x75}, + {0x5e5a, 0x75}, + {0x5e5b, 0x75}, + {0x5e5c, 0x75}, + {0x5e5d, 0x75}, + {0x5e5e, 0x75}, + {0x5e5f, 0x75}, + {0x5e60, 0x75}, + {0x5e61, 0x75}, + {0x5e62, 0x75}, + {0x5e63, 0x75}, + {0x5e64, 0x75}, + {0x5e65, 0x75}, + {0x5e66, 0x75}, + {0x5e67, 0x75}, + {0x5e68, 0x75}, + {0x5e69, 0x75}, + {0x5e6a, 0x75}, + {0x5e6b, 0x75}, + {0x5e6c, 0x75}, + {0x5e6d, 0x75}, + {0x5e6e, 0x75}, + {0x5e6f, 0x75}, + {0x5e70, 0x75}, + {0x5e71, 0x75}, + {0x5e72, 0x75}, + {0x5e73, 0x75}, + {0x5e74, 0x75}, + {0x5e75, 0x75}, + {0x5e76, 0x75}, + {0x5e77, 0x75}, + {0x5e78, 0x75}, + {0x5e79, 0x75}, + {0x5e7a, 0x75}, + {0x5e7b, 0x75}, + {0x5e7c, 0x75}, + {0x5e7d, 0x75}, + {0x5e7e, 0x75}, + {0x5e7f, 0x75}, + {0x5e80, 0x75}, + {0x5e81, 0x75}, + {0x5e82, 0x75}, + {0x5e83, 0x75}, + {0x5e84, 0x75}, + {0x5e85, 0x75}, + {0x5e86, 0x75}, + {0x5e87, 0x75}, + {0x5e88, 0x75}, + {0x5e89, 0x75}, + {0x5e8a, 0x75}, + {0x5e8b, 0x75}, + {0x5e8c, 0x75}, + {0x5e8d, 0x75}, + {0x5e8e, 0x75}, + {0x5e8f, 0x75}, + {0x5e90, 0x75}, + {0x5e91, 0x75}, + {0x5e92, 0x75}, + {0x5e93, 0x75}, + {0x5e94, 0x75}, + {0x5e95, 0x75}, + {0x5e96, 0x75}, + {0x5e97, 0x75}, + {0x5e98, 0x75}, + {0x5e99, 0x75}, + {0x5e9a, 0x75}, + {0x5e9b, 0x75}, + {0x5e9c, 0x75}, + {0x5e9d, 0x75}, + {0x5e9e, 0x75}, + {0x5e9f, 0x75}, + {0x5ea0, 0x75}, + {0x5ea1, 0x75}, + {0x5ea2, 0x75}, + {0x5ea3, 0x75}, + {0x5ea4, 0x75}, + {0x5ea5, 0x75}, + {0x5ea6, 0x75}, + {0x5ea7, 0x75}, + {0x5ea8, 0x75}, + {0x5ea9, 0x75}, + {0x5eaa, 0x75}, + {0x5eab, 0x75}, + {0x5eac, 0x75}, + {0x5ead, 0x75}, + {0x5eae, 0x75}, + {0x5eaf, 0x75}, + {0x5eb0, 0x75}, + {0x5eb1, 0x75}, + {0x5eb2, 0x75}, + {0x5eb3, 0x75}, + {0x5eb4, 0x75}, + {0x5eb5, 0x75}, + {0x5eb6, 0x75}, + {0x5eb7, 0x75}, + {0x5eb8, 0x75}, + {0x5eb9, 0x75}, + {0x5eba, 0x75}, + {0x5ebb, 0x75}, + {0x5ebc, 0x75}, + {0x5ebd, 0x75}, + {0x5ebe, 0x75}, + {0x5ebf, 0x75}, + {0x5ec0, 0x75}, + {0x5ec1, 0x75}, + {0x5ec2, 0x75}, + {0x5ec3, 0x75}, + {0x5ec4, 0x75}, + {0x5ec5, 0x75}, + {0x5ec6, 0x75}, + {0x5ec7, 0x75}, + {0x5ec8, 0x75}, + {0x5ec9, 0x75}, + {0x5eca, 0x75}, + {0x5ecb, 0x75}, + {0x5ecc, 0x75}, + {0x5ecd, 0x75}, + {0x5ece, 0x75}, + {0x5ecf, 0x75}, + {0x5ed0, 0x75}, + {0x5ed1, 0x75}, + {0x5ed2, 0x75}, + {0x5ed3, 0x75}, + {0x5ed4, 0x75}, + {0x5ed5, 0x75}, + {0x5ed6, 0x75}, + {0x5ed7, 0x75}, + {0x5ed8, 0x75}, + {0x5ed9, 0x75}, + {0x5eda, 0x75}, + {0x5edb, 0x75}, + {0x5edc, 0x75}, + {0x5edd, 0x75}, + {0x5ede, 0x75}, + {0x5edf, 0x75}, + {0x5ee0, 0x75}, + {0x5ee1, 0x75}, + {0x5ee2, 0x75}, + {0x5ee3, 0x75}, + {0x5ee4, 0x75}, + {0x5ee5, 0x75}, + {0x5ee6, 0x75}, + {0x5ee7, 0x75}, + {0x5ee8, 0x75}, + {0x5ee9, 0x75}, + {0x5eea, 0x75}, + {0x5eeb, 0x75}, + {0x5eec, 0x75}, + {0x5eed, 0x75}, + {0x5eee, 0x75}, + {0x5eef, 0x75}, + {0x5ef0, 0x75}, + {0x5ef1, 0x75}, + {0x5ef2, 0x75}, + {0x5ef3, 0x75}, + {0x5ef4, 0x75}, + {0x5ef5, 0x75}, + {0x5ef6, 0x75}, + {0x5ef7, 0x75}, + {0x5ef8, 0x75}, + {0x5ef9, 0x75}, + {0x5efa, 0x75}, + {0x5efb, 0x75}, + {0x5efc, 0x75}, + {0x5efd, 0x75}, + {0x5efe, 0x75}, + {0x5eff, 0x75}, + {0x5f00, 0x75}, + {0x5f01, 0x75}, + {0x5f02, 0x75}, + {0x5f03, 0x75}, + {0x5f04, 0x75}, + {0x5f05, 0x75}, + {0x5f06, 0x75}, + {0x5f07, 0x75}, + {0x5f08, 0x75}, + {0x5f09, 0x75}, + {0x5f0a, 0x75}, + {0x5f0b, 0x75}, + {0x5f0c, 0x75}, + {0x5f0d, 0x75}, + {0x5f0e, 0x75}, + {0x5f0f, 0x75}, + {0x5f10, 0x75}, + {0x5f11, 0x75}, + {0x5f12, 0x75}, + {0x5f13, 0x75}, + {0x5f14, 0x75}, + {0x5f15, 0x75}, + {0x5f16, 0x75}, + {0x5f17, 0x75}, + {0x5f18, 0x75}, + {0x5f19, 0x75}, + {0x5f1a, 0x75}, + {0x5f1b, 0x75}, + {0x5f1c, 0x75}, + {0x5f1d, 0x75}, + {0x5f1e, 0x75}, + {0x5f1f, 0x75}, +}; + +static const struct ov08x40_reg mode_1928x1208_regs[] = { + {0x5000, 0x55}, + {0x5001, 0x00}, + {0x5008, 0xb0}, + {0x50c1, 0x00}, + {0x53c1, 0x00}, + {0x5f40, 0x00}, + {0x5f41, 0x40}, + {0x0300, 0x3a}, + {0x0301, 0xc8}, + {0x0302, 0x31}, + {0x0303, 0x03}, + {0x0304, 0x01}, + {0x0305, 0xa1}, + {0x0306, 0x04}, + {0x0307, 0x01}, + {0x0308, 0x03}, + {0x0309, 0x03}, + {0x0310, 0x0a}, + {0x0311, 0x02}, + {0x0312, 0x01}, + {0x0313, 0x08}, + {0x0314, 0x66}, + {0x0315, 0x00}, + {0x0316, 0x34}, + {0x0320, 0x02}, + {0x0321, 0x03}, + {0x0323, 0x05}, + {0x0324, 0x01}, + {0x0325, 0xb8}, + {0x0326, 0x4a}, + {0x0327, 0x04}, + {0x0329, 0x00}, + {0x032a, 0x05}, + {0x032b, 0x00}, + {0x032c, 0x00}, + {0x032d, 0x00}, + {0x032e, 0x02}, + {0x032f, 0xa0}, + {0x0350, 0x00}, + {0x0360, 0x01}, + {0x1216, 0x60}, + {0x1217, 0x5b}, + {0x1218, 0x00}, + {0x1220, 0x24}, + {0x198a, 0x00}, + {0x198b, 0x01}, + {0x198e, 0x00}, + {0x198f, 0x01}, + {0x3009, 0x04}, + {0x3012, 0x41}, + {0x3015, 0x00}, + {0x3016, 0xb0}, + {0x3017, 0xf0}, + {0x3018, 0xf0}, + {0x3019, 0xd2}, + {0x301a, 0xb0}, + {0x301c, 0x81}, + {0x301d, 0x02}, + {0x301e, 0x80}, + {0x3022, 0xf0}, + {0x3025, 0x89}, + {0x3030, 0x03}, + {0x3044, 0xc2}, + {0x3050, 0x35}, + {0x3051, 0x60}, + {0x3052, 0x25}, + {0x3053, 0x00}, + {0x3054, 0x00}, + {0x3055, 0x02}, + {0x3056, 0x80}, + {0x3057, 0x80}, + {0x3058, 0x80}, + {0x3059, 0x00}, + {0x3107, 0x86}, + {0x3400, 0x1c}, + {0x3401, 0x80}, + {0x3402, 0x8c}, + {0x3419, 0x08}, + {0x341a, 0xaf}, + {0x341b, 0x30}, + {0x3420, 0x00}, + {0x3421, 0x00}, + {0x3422, 0x00}, + {0x3423, 0x00}, + {0x3424, 0x00}, + {0x3425, 0x00}, + {0x3426, 0x00}, + {0x3427, 0x00}, + {0x3428, 0x0f}, + {0x3429, 0x00}, + {0x342a, 0x00}, + {0x342b, 0x00}, + {0x342c, 0x00}, + {0x342d, 0x00}, + {0x342e, 0x00}, + {0x342f, 0x11}, + {0x3430, 0x11}, + {0x3431, 0x10}, + {0x3432, 0x00}, + {0x3433, 0x00}, + {0x3434, 0x00}, + {0x3435, 0x00}, + {0x3436, 0x00}, + {0x3437, 0x00}, + {0x3442, 0x02}, + {0x3443, 0x02}, + {0x3444, 0x07}, + {0x3450, 0x00}, + {0x3451, 0x00}, + {0x3452, 0x18}, + {0x3453, 0x18}, + {0x3454, 0x00}, + {0x3455, 0x80}, + {0x3456, 0x08}, + {0x3500, 0x00}, + {0x3501, 0x02}, + {0x3502, 0x00}, + {0x3504, 0x4c}, + {0x3506, 0x30}, + {0x3507, 0x00}, + {0x3508, 0x01}, + {0x3509, 0x00}, + {0x350a, 0x01}, + {0x350b, 0x00}, + {0x350c, 0x00}, + {0x3540, 0x00}, + {0x3541, 0x01}, + {0x3542, 0x00}, + {0x3544, 0x4c}, + {0x3546, 0x30}, + {0x3547, 0x00}, + {0x3548, 0x01}, + {0x3549, 0x00}, + {0x354a, 0x01}, + {0x354b, 0x00}, + {0x354c, 0x00}, + {0x3688, 0x02}, + {0x368a, 0x2e}, + {0x368e, 0x71}, + {0x3696, 0xd1}, + {0x3699, 0x00}, + {0x369a, 0x00}, + {0x36a4, 0x00}, + {0x36a6, 0x00}, + {0x3711, 0x00}, + {0x3712, 0x50}, + {0x3713, 0x00}, + {0x3714, 0x21}, + {0x3716, 0x00}, + {0x3718, 0x07}, + {0x371a, 0x1c}, + {0x371b, 0x00}, + {0x3720, 0x08}, + {0x3725, 0x32}, + {0x3727, 0x05}, + {0x3760, 0x02}, + {0x3761, 0x28}, + {0x3762, 0x02}, + {0x3763, 0x02}, + {0x3764, 0x02}, + {0x3765, 0x2c}, + {0x3766, 0x04}, + {0x3767, 0x2c}, + {0x3768, 0x02}, + {0x3769, 0x00}, + {0x376b, 0x20}, + {0x376e, 0x07}, + {0x37b0, 0x01}, + {0x37b1, 0x0f}, + {0x37b2, 0x01}, + {0x37b3, 0xd6}, + {0x37b4, 0x01}, + {0x37b5, 0x48}, + {0x37b6, 0x02}, + {0x37b7, 0x40}, + {0x3800, 0x00}, + {0x3801, 0x00}, + {0x3802, 0x00}, + {0x3803, 0x00}, + {0x3804, 0x0f}, + {0x3805, 0x1f}, + {0x3806, 0x09}, + {0x3807, 0x7f}, + {0x3808, 0x07}, + {0x3809, 0x88}, + {0x380a, 0x04}, + {0x380b, 0xb8}, + {0x380c, 0x02}, + {0x380d, 0xd0}, + {0x380e, 0x11}, + {0x380f, 0x5c}, + {0x3810, 0x00}, + {0x3811, 0x04}, + {0x3812, 0x00}, + {0x3813, 0x03}, + {0x3814, 0x11}, + {0x3815, 0x11}, + {0x3820, 0x02}, + {0x3821, 0x14}, + {0x3822, 0x00}, + {0x3823, 0x04}, + {0x3828, 0x0f}, + {0x382a, 0x80}, + {0x382e, 0x41}, + {0x3837, 0x08}, + {0x383a, 0x81}, + {0x383b, 0x81}, + {0x383c, 0x11}, + {0x383d, 0x11}, + {0x383e, 0x00}, + {0x383f, 0x38}, + {0x3840, 0x00}, + {0x3847, 0x00}, + {0x384a, 0x00}, + {0x384c, 0x02}, + {0x384d, 0xd0}, + {0x3856, 0x50}, + {0x3857, 0x30}, + {0x3858, 0x80}, + {0x3859, 0x40}, + {0x3860, 0x00}, + {0x3888, 0x00}, + {0x3889, 0x00}, + {0x388a, 0x00}, + {0x388b, 0x00}, + {0x388c, 0x00}, + {0x388d, 0x00}, + {0x388e, 0x00}, + {0x388f, 0x00}, + {0x3894, 0x00}, + {0x3895, 0x00}, + {0x3c84, 0x00}, + {0x3d85, 0x8b}, + {0x3daa, 0x80}, + {0x3dab, 0x14}, + {0x3dac, 0x80}, + {0x3dad, 0xc8}, + {0x3dae, 0x81}, + {0x3daf, 0x7b}, + {0x3f00, 0x10}, + {0x3f01, 0x11}, + {0x3f06, 0x0d}, + {0x3f07, 0x0b}, + {0x3f08, 0x0d}, + {0x3f09, 0x0b}, + {0x3f0a, 0x01}, + {0x3f0b, 0x11}, + {0x3f0c, 0x33}, + {0x4001, 0x07}, + {0x4007, 0x20}, + {0x4008, 0x00}, + {0x4009, 0x05}, + {0x400a, 0x00}, + {0x400b, 0x04}, + {0x400c, 0x00}, + {0x400d, 0x04}, + {0x400e, 0x14}, + {0x4010, 0xf4}, + {0x4011, 0x03}, + {0x4012, 0x55}, + {0x4015, 0x00}, + {0x4016, 0x27}, + {0x4017, 0x00}, + {0x4018, 0x0f}, + {0x401b, 0x08}, + {0x401c, 0x00}, + {0x401d, 0x10}, + {0x401e, 0x02}, + {0x401f, 0x00}, + {0x4050, 0x06}, + {0x4051, 0xff}, + {0x4052, 0xff}, + {0x4053, 0xff}, + {0x4054, 0xff}, + {0x4055, 0xff}, + {0x4056, 0xff}, + {0x4057, 0x7f}, + {0x4058, 0x00}, + {0x4059, 0x00}, + {0x405a, 0x00}, + {0x405b, 0x00}, + {0x405c, 0x07}, + {0x405d, 0xff}, + {0x405e, 0x07}, + {0x405f, 0xff}, + {0x4080, 0x78}, + {0x4081, 0x78}, + {0x4082, 0x78}, + {0x4083, 0x78}, + {0x4019, 0x00}, + {0x401a, 0x40}, + {0x4020, 0x04}, + {0x4021, 0x00}, + {0x4022, 0x04}, + {0x4023, 0x00}, + {0x4024, 0x04}, + {0x4025, 0x00}, + {0x4026, 0x04}, + {0x4027, 0x00}, + {0x4030, 0x00}, + {0x4031, 0x00}, + {0x4032, 0x00}, + {0x4033, 0x00}, + {0x4034, 0x00}, + {0x4035, 0x00}, + {0x4036, 0x00}, + {0x4037, 0x00}, + {0x4040, 0x00}, + {0x4041, 0x80}, + {0x4042, 0x00}, + {0x4043, 0x80}, + {0x4044, 0x00}, + {0x4045, 0x80}, + {0x4046, 0x00}, + {0x4047, 0x80}, + {0x4060, 0x00}, + {0x4061, 0x00}, + {0x4062, 0x00}, + {0x4063, 0x00}, + {0x4064, 0x00}, + {0x4065, 0x00}, + {0x4066, 0x00}, + {0x4067, 0x00}, + {0x4068, 0x00}, + {0x4069, 0x00}, + {0x406a, 0x00}, + {0x406b, 0x00}, + {0x406c, 0x00}, + {0x406d, 0x00}, + {0x406e, 0x00}, + {0x406f, 0x00}, + {0x4070, 0x00}, + {0x4071, 0x00}, + {0x4072, 0x00}, + {0x4073, 0x00}, + {0x4074, 0x00}, + {0x4075, 0x00}, + {0x4076, 0x00}, + {0x4077, 0x00}, + {0x4078, 0x00}, + {0x4079, 0x00}, + {0x407a, 0x00}, + {0x407b, 0x00}, + {0x407c, 0x00}, + {0x407d, 0x00}, + {0x407e, 0x00}, + {0x407f, 0x00}, + {0x40e0, 0x00}, + {0x40e1, 0x00}, + {0x40e2, 0x00}, + {0x40e3, 0x00}, + {0x40e4, 0x00}, + {0x40e5, 0x00}, + {0x40e6, 0x00}, + {0x40e7, 0x00}, + {0x40e8, 0x00}, + {0x40e9, 0x80}, + {0x40ea, 0x00}, + {0x40eb, 0x80}, + {0x40ec, 0x00}, + {0x40ed, 0x80}, + {0x40ee, 0x00}, + {0x40ef, 0x80}, + {0x40f0, 0x02}, + {0x40f1, 0x04}, + {0x4300, 0x00}, + {0x4301, 0x00}, + {0x4302, 0x00}, + {0x4303, 0x00}, + {0x4304, 0x00}, + {0x4305, 0x00}, + {0x4306, 0x00}, + {0x4307, 0x00}, + {0x4308, 0x00}, + {0x4309, 0x00}, + {0x430a, 0x00}, + {0x430b, 0xff}, + {0x430c, 0xff}, + {0x430d, 0x00}, + {0x430e, 0x00}, + {0x4315, 0x00}, + {0x4316, 0x00}, + {0x4317, 0x00}, + {0x4318, 0x00}, + {0x4319, 0x00}, + {0x431a, 0x00}, + {0x431b, 0x00}, + {0x431c, 0x00}, + {0x4500, 0x07}, + {0x4501, 0x10}, + {0x4502, 0x00}, + {0x4503, 0x0f}, + {0x4504, 0x80}, + {0x4506, 0x01}, + {0x4509, 0x05}, + {0x450c, 0x00}, + {0x450d, 0x20}, + {0x450e, 0x00}, + {0x450f, 0x00}, + {0x4510, 0x00}, + {0x4523, 0x00}, + {0x4526, 0x00}, + {0x4542, 0x00}, + {0x4543, 0x00}, + {0x4544, 0x00}, + {0x4545, 0x00}, + {0x4546, 0x00}, + {0x4547, 0x10}, + {0x4602, 0x00}, + {0x4603, 0x15}, + {0x460b, 0x07}, + {0x4680, 0x11}, + {0x4686, 0x00}, + {0x4687, 0x00}, + {0x4700, 0x00}, + {0x4800, 0x64}, + {0x4806, 0x40}, + {0x480b, 0x10}, + {0x480c, 0x80}, + {0x480f, 0x32}, + {0x4813, 0xe4}, + {0x4837, 0x14}, + {0x4850, 0x42}, + {0x4884, 0x04}, + {0x4c00, 0xf8}, + {0x4c01, 0x44}, + {0x4c03, 0x00}, + {0x4d00, 0x00}, + {0x4d01, 0x16}, + {0x4d04, 0x10}, + {0x4d05, 0x00}, + {0x4d06, 0x0c}, + {0x4d07, 0x00}, + {0x3d84, 0x04}, + {0x3680, 0xa4}, + {0x3682, 0x80}, + {0x3601, 0x40}, + {0x3602, 0x90}, + {0x3608, 0x0a}, + {0x3938, 0x09}, + {0x3a74, 0x84}, + {0x3a99, 0x84}, + {0x3ab9, 0xa6}, + {0x3aba, 0xba}, + {0x3b12, 0x84}, + {0x3b14, 0xbb}, + {0x3b15, 0xbf}, + {0x3a29, 0x26}, + {0x3a1f, 0x8a}, + {0x3a22, 0x91}, + {0x3a25, 0x96}, + {0x3a28, 0xb4}, + {0x3a2b, 0xba}, + {0x3a2e, 0xbf}, + {0x3a31, 0xc1}, + {0x3a20, 0x05}, + {0x3939, 0x6b}, + {0x3902, 0x10}, + {0x3903, 0x10}, + {0x3904, 0x10}, + {0x3905, 0x10}, + {0x3906, 0x01}, + {0x3907, 0x0b}, + {0x3908, 0x10}, + {0x3909, 0x13}, + {0x360f, 0x99}, + {0x390b, 0x11}, + {0x390c, 0x21}, + {0x390d, 0x32}, + {0x390e, 0x76}, + {0x3911, 0x90}, + {0x3913, 0x90}, + {0x3b3f, 0x9d}, + {0x3b45, 0x9d}, + {0x3b1b, 0xc9}, + {0x3b21, 0xc9}, + {0x3a1a, 0x1c}, + {0x3a23, 0x15}, + {0x3a26, 0x17}, + {0x3a2c, 0x50}, + {0x3a2f, 0x18}, + {0x3a32, 0x4f}, + {0x3ace, 0x01}, + {0x3ad2, 0x01}, + {0x3ad6, 0x01}, + {0x3ada, 0x01}, + {0x3ade, 0x01}, + {0x3ae2, 0x01}, + {0x3aee, 0x01}, + {0x3af2, 0x01}, + {0x3af6, 0x01}, + {0x3afa, 0x01}, + {0x3afe, 0x01}, + {0x3b02, 0x01}, + {0x3b06, 0x01}, + {0x3b0a, 0x01}, + {0x3b0b, 0x00}, + {0x3b0e, 0x01}, + {0x3b0f, 0x00}, + {0x392c, 0x02}, + {0x392d, 0x01}, + {0x392e, 0x04}, + {0x392f, 0x03}, + {0x3930, 0x09}, + {0x3931, 0x07}, + {0x3932, 0x10}, + {0x3933, 0x0d}, + {0x3609, 0x08}, + {0x3921, 0x0f}, + {0x3928, 0x15}, + {0x3929, 0x2a}, + {0x392a, 0x52}, + {0x392b, 0xa3}, + {0x340b, 0x1b}, + {0x3426, 0x10}, + {0x3407, 0x01}, + {0x3404, 0x01}, + {0x3500, 0x00}, + {0x3501, 0x08}, + {0x3502, 0x10}, + {0x3508, 0x04}, + {0x3509, 0x00}, +}; + +static const char * const ov08x40_test_pattern_menu[] = { + "Disabled", + "Vertical Color Bar Type 1", + "Vertical Color Bar Type 2", + "Vertical Color Bar Type 3", + "Vertical Color Bar Type 4" +}; + +/* Configurations for supported link frequencies */ +#define OV08X40_LINK_FREQ_400MHZ 400000000ULL + +#define OV08X40_EXT_CLK 19200000 +#define OV08X40_DATA_LANES 4 + +/* + * pixel_rate = link_freq * data-rate * nr_of_lanes / bits_per_sample + * data rate => double data rate; number of lanes => 4; bits per pixel => 10 + */ +static u64 link_freq_to_pixel_rate(u64 f) +{ + f *= 2 * OV08X40_DATA_LANES; + do_div(f, 10); + + return f; +} + +/* Menu items for LINK_FREQ V4L2 control */ +static const s64 link_freq_menu_items[] = { + OV08X40_LINK_FREQ_400MHZ, +}; + +/* Link frequency configs */ +static const struct ov08x40_link_freq_config link_freq_configs[] = { + [OV08X40_LINK_FREQ_400MHZ_INDEX] = { + .reg_list = { + .num_of_regs = ARRAY_SIZE(mipi_data_rate_800mbps), + .regs = mipi_data_rate_800mbps, + } + }, +}; + +/* Mode configs */ +static const struct ov08x40_mode supported_modes[] = { + { + .width = 3856, + .height = 2416, + .vts_def = OV08X40_VTS_30FPS, + .vts_min = OV08X40_VTS_30FPS, + .lanes = 4, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_3856x2416_regs), + .regs = mode_3856x2416_regs, + }, + .link_freq_index = OV08X40_LINK_FREQ_400MHZ_INDEX, + }, + { + .width = 1928, + .height = 1208, + .vts_def = OV08X40_VTS_BIN_30FPS, + .vts_min = OV08X40_VTS_BIN_30FPS, + .lanes = 4, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_1928x1208_regs), + .regs = mode_1928x1208_regs, + }, + .link_freq_index = OV08X40_LINK_FREQ_400MHZ_INDEX, + }, +}; + +struct ov08x40 { + struct v4l2_subdev sd; + struct media_pad pad; + + struct v4l2_ctrl_handler ctrl_handler; + /* V4L2 Controls */ + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *exposure; + + /* Current mode */ + const struct ov08x40_mode *cur_mode; + + /* Mutex for serialized access */ + struct mutex mutex; + + /* Streaming on/off */ + bool streaming; +}; + +#define to_ov08x40(_sd) container_of(_sd, struct ov08x40, sd) + +/* Read registers up to 4 at a time */ +static int ov08x40_read_reg(struct ov08x40 *ov08x, + u16 reg, u32 len, u32 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov08x->sd); + struct i2c_msg msgs[2]; + u8 *data_be_p; + int ret; + __be32 data_be = 0; + __be16 reg_addr_be = cpu_to_be16(reg); + + if (len > 4) + return -EINVAL; + + data_be_p = (u8 *)&data_be; + /* Write register address */ + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = 2; + msgs[0].buf = (u8 *)®_addr_be; + + /* Read data from register */ + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = &data_be_p[4 - len]; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *val = be32_to_cpu(data_be); + + return 0; +} + +/* Write registers up to 4 at a time */ +static int ov08x40_write_reg(struct ov08x40 *ov08x, + u16 reg, u32 len, u32 __val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov08x->sd); + int buf_i, val_i; + u8 buf[6], *val_p; + __be32 val; + + if (len > 4) + return -EINVAL; + + buf[0] = reg >> 8; + buf[1] = reg & 0xff; + + val = cpu_to_be32(__val); + val_p = (u8 *)&val; + buf_i = 2; + val_i = 4 - len; + + while (val_i < 4) + buf[buf_i++] = val_p[val_i++]; + + if (i2c_master_send(client, buf, len + 2) != len + 2) + return -EIO; + + return 0; +} + +/* Write a list of registers */ +static int ov08x40_write_regs(struct ov08x40 *ov08x, + const struct ov08x40_reg *regs, u32 len) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov08x->sd); + int ret; + u32 i; + + for (i = 0; i < len; i++) { + ret = ov08x40_write_reg(ov08x, regs[i].address, 1, + regs[i].val); + + if (ret) { + dev_err_ratelimited(&client->dev, + "Failed to write reg 0x%4.4x. error = %d\n", + regs[i].address, ret); + + return ret; + } + } + + return 0; +} + +static int ov08x40_write_reg_list(struct ov08x40 *ov08x, + const struct ov08x40_reg_list *r_list) +{ + return ov08x40_write_regs(ov08x, r_list->regs, r_list->num_of_regs); +} + +static int ov08x40_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + const struct ov08x40_mode *default_mode = &supported_modes[0]; + struct ov08x40 *ov08x = to_ov08x40(sd); + struct v4l2_mbus_framefmt *try_fmt = + v4l2_subdev_get_try_format(sd, fh->state, 0); + + mutex_lock(&ov08x->mutex); + + /* Initialize try_fmt */ + try_fmt->width = default_mode->width; + try_fmt->height = default_mode->height; + try_fmt->code = MEDIA_BUS_FMT_SGRBG10_1X10; + try_fmt->field = V4L2_FIELD_NONE; + + /* No crop or compose */ + mutex_unlock(&ov08x->mutex); + + return 0; +} + +static int ov08x40_update_digital_gain(struct ov08x40 *ov08x, u32 d_gain) +{ + int ret; + u32 val; + + /* + * 0x350C[1:0], 0x350B[7:0], 0x350A[4:0] + */ + + val = (d_gain & OV08X40_DGTL_GAIN_L_MASK) << OV08X40_DGTL_GAIN_L_SHIFT; + ret = ov08x40_write_reg(ov08x, OV08X40_REG_DGTL_GAIN_L, + OV08X40_REG_VALUE_08BIT, val); + if (ret) + return ret; + + val = (d_gain >> OV08X40_DGTL_GAIN_M_SHIFT) & OV08X40_DGTL_GAIN_M_MASK; + ret = ov08x40_write_reg(ov08x, OV08X40_REG_DGTL_GAIN_M, + OV08X40_REG_VALUE_08BIT, val); + if (ret) + return ret; + + val = (d_gain >> OV08X40_DGTL_GAIN_H_SHIFT) & OV08X40_DGTL_GAIN_H_MASK; + + return ov08x40_write_reg(ov08x, OV08X40_REG_DGTL_GAIN_H, + OV08X40_REG_VALUE_08BIT, val); +} + +static int ov08x40_enable_test_pattern(struct ov08x40 *ov08x, u32 pattern) +{ + int ret; + u32 val; + + ret = ov08x40_read_reg(ov08x, OV08X40_REG_TEST_PATTERN, + OV08X40_REG_VALUE_08BIT, &val); + if (ret) + return ret; + + if (pattern) { + ret = ov08x40_read_reg(ov08x, OV08X40_REG_ISP, + OV08X40_REG_VALUE_08BIT, &val); + if (ret) + return ret; + + ret = ov08x40_write_reg(ov08x, OV08X40_REG_ISP, + OV08X40_REG_VALUE_08BIT, + val | BIT(1)); + if (ret) + return ret; + + ret = ov08x40_read_reg(ov08x, OV08X40_REG_SHORT_TEST_PATTERN, + OV08X40_REG_VALUE_08BIT, &val); + if (ret) + return ret; + + ret = ov08x40_write_reg(ov08x, OV08X40_REG_SHORT_TEST_PATTERN, + OV08X40_REG_VALUE_08BIT, + val | BIT(0)); + if (ret) + return ret; + + ret = ov08x40_read_reg(ov08x, OV08X40_REG_TEST_PATTERN, + OV08X40_REG_VALUE_08BIT, &val); + if (ret) + return ret; + + val &= OV08X40_TEST_PATTERN_MASK; + val |= ((pattern - 1) << OV08X40_TEST_PATTERN_BAR_SHIFT) | + OV08X40_TEST_PATTERN_ENABLE; + } else { + val &= ~OV08X40_TEST_PATTERN_ENABLE; + } + + return ov08x40_write_reg(ov08x, OV08X40_REG_TEST_PATTERN, + OV08X40_REG_VALUE_08BIT, val); +} + +static int ov08x40_set_ctrl_hflip(struct ov08x40 *ov08x, u32 ctrl_val) +{ + int ret; + u32 val; + + ret = ov08x40_read_reg(ov08x, OV08X40_REG_MIRROR, + OV08X40_REG_VALUE_08BIT, &val); + if (ret) + return ret; + + return ov08x40_write_reg(ov08x, OV08X40_REG_MIRROR, + OV08X40_REG_VALUE_08BIT, + ctrl_val ? val | BIT(2) : val & ~BIT(2)); +} + +static int ov08x40_set_ctrl_vflip(struct ov08x40 *ov08x, u32 ctrl_val) +{ + int ret; + u32 val; + + ret = ov08x40_read_reg(ov08x, OV08X40_REG_VFLIP, + OV08X40_REG_VALUE_08BIT, &val); + if (ret) + return ret; + + return ov08x40_write_reg(ov08x, OV08X40_REG_VFLIP, + OV08X40_REG_VALUE_08BIT, + ctrl_val ? val | BIT(2) : val & ~BIT(2)); +} + +static int ov08x40_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ov08x40 *ov08x = container_of(ctrl->handler, + struct ov08x40, ctrl_handler); + struct i2c_client *client = v4l2_get_subdevdata(&ov08x->sd); + s64 max; + int ret = 0; + + /* Propagate change of current control to all related controls */ + switch (ctrl->id) { + case V4L2_CID_VBLANK: + /* Update max exposure while meeting expected vblanking */ + max = ov08x->cur_mode->height + ctrl->val - OV08X40_EXPOSURE_MAX_MARGIN; + __v4l2_ctrl_modify_range(ov08x->exposure, + ov08x->exposure->minimum, + max, ov08x->exposure->step, max); + break; + } + + /* + * 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_ANALOGUE_GAIN: + ret = ov08x40_write_reg(ov08x, OV08X40_REG_ANALOG_GAIN, + OV08X40_REG_VALUE_16BIT, + ctrl->val << 1); + break; + case V4L2_CID_DIGITAL_GAIN: + ret = ov08x40_update_digital_gain(ov08x, ctrl->val); + break; + case V4L2_CID_EXPOSURE: + ret = ov08x40_write_reg(ov08x, OV08X40_REG_EXPOSURE, + OV08X40_REG_VALUE_24BIT, + ctrl->val); + break; + case V4L2_CID_VBLANK: + ret = ov08x40_write_reg(ov08x, OV08X40_REG_VTS, + OV08X40_REG_VALUE_16BIT, + ov08x->cur_mode->height + + ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: + ret = ov08x40_enable_test_pattern(ov08x, ctrl->val); + break; + case V4L2_CID_HFLIP: + ov08x40_set_ctrl_hflip(ov08x, ctrl->val); + break; + case V4L2_CID_VFLIP: + ov08x40_set_ctrl_vflip(ov08x, ctrl->val); + break; + default: + dev_info(&client->dev, + "ctrl(id:0x%x,val:0x%x) is not handled\n", + ctrl->id, ctrl->val); + break; + } + + pm_runtime_put(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops ov08x40_ctrl_ops = { + .s_ctrl = ov08x40_set_ctrl, +}; + +static int ov08x40_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + /* Only one bayer order(GRBG) is supported */ + if (code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_SGRBG10_1X10; + + return 0; +} + +static int ov08x40_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + if (fse->code != MEDIA_BUS_FMT_SGRBG10_1X10) + return -EINVAL; + + fse->min_width = supported_modes[fse->index].width; + fse->max_width = fse->min_width; + fse->min_height = supported_modes[fse->index].height; + fse->max_height = fse->min_height; + + return 0; +} + +static void ov08x40_update_pad_format(const struct ov08x40_mode *mode, + struct v4l2_subdev_format *fmt) +{ + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10; + fmt->format.field = V4L2_FIELD_NONE; +} + +static int ov08x40_do_get_pad_format(struct ov08x40 *ov08x, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *framefmt; + struct v4l2_subdev *sd = &ov08x->sd; + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + framefmt = v4l2_subdev_get_try_format(sd, sd_state, fmt->pad); + fmt->format = *framefmt; + } else { + ov08x40_update_pad_format(ov08x->cur_mode, fmt); + } + + return 0; +} + +static int ov08x40_get_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct ov08x40 *ov08x = to_ov08x40(sd); + int ret; + + mutex_lock(&ov08x->mutex); + ret = ov08x40_do_get_pad_format(ov08x, sd_state, fmt); + mutex_unlock(&ov08x->mutex); + + return ret; +} + +static int +ov08x40_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct ov08x40 *ov08x = to_ov08x40(sd); + const struct ov08x40_mode *mode; + struct v4l2_mbus_framefmt *framefmt; + s32 vblank_def; + s32 vblank_min; + s64 h_blank; + s64 pixel_rate; + s64 link_freq; + + mutex_lock(&ov08x->mutex); + + /* Only one raw bayer(GRBG) order is supported */ + if (fmt->format.code != MEDIA_BUS_FMT_SGRBG10_1X10) + fmt->format.code = MEDIA_BUS_FMT_SGRBG10_1X10; + + mode = v4l2_find_nearest_size(supported_modes, + ARRAY_SIZE(supported_modes), + width, height, + fmt->format.width, fmt->format.height); + ov08x40_update_pad_format(mode, fmt); + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + framefmt = v4l2_subdev_get_try_format(sd, sd_state, fmt->pad); + *framefmt = fmt->format; + } else { + ov08x->cur_mode = mode; + __v4l2_ctrl_s_ctrl(ov08x->link_freq, mode->link_freq_index); + link_freq = link_freq_menu_items[mode->link_freq_index]; + pixel_rate = link_freq_to_pixel_rate(link_freq); + __v4l2_ctrl_s_ctrl_int64(ov08x->pixel_rate, pixel_rate); + + /* Update limits and set FPS to default */ + vblank_def = ov08x->cur_mode->vts_def - + ov08x->cur_mode->height; + vblank_min = ov08x->cur_mode->vts_min - + ov08x->cur_mode->height; + __v4l2_ctrl_modify_range(ov08x->vblank, vblank_min, + OV08X40_VTS_MAX + - ov08x->cur_mode->height, + 1, + vblank_def); + __v4l2_ctrl_s_ctrl(ov08x->vblank, vblank_def); + h_blank = + link_freq_configs[mode->link_freq_index].pixels_per_line + - ov08x->cur_mode->width; + __v4l2_ctrl_modify_range(ov08x->hblank, h_blank, + h_blank, 1, h_blank); + } + + mutex_unlock(&ov08x->mutex); + + return 0; +} + +static int ov08x40_start_streaming(struct ov08x40 *ov08x) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov08x->sd); + const struct ov08x40_reg_list *reg_list; + int ret, link_freq_index; + + /* Get out of from software reset */ + ret = ov08x40_write_reg(ov08x, OV08X40_REG_SOFTWARE_RST, + OV08X40_REG_VALUE_08BIT, OV08X40_SOFTWARE_RST); + if (ret) { + dev_err(&client->dev, "%s failed to set powerup registers\n", + __func__); + return ret; + } + + link_freq_index = ov08x->cur_mode->link_freq_index; + reg_list = &link_freq_configs[link_freq_index].reg_list; + + ret = ov08x40_write_reg_list(ov08x, reg_list); + if (ret) { + dev_err(&client->dev, "%s failed to set plls\n", __func__); + return ret; + } + + /* Apply default values of current mode */ + reg_list = &ov08x->cur_mode->reg_list; + ret = ov08x40_write_reg_list(ov08x, reg_list); + if (ret) { + dev_err(&client->dev, "%s failed to set mode\n", __func__); + return ret; + } + + /* Apply customized values from user */ + ret = __v4l2_ctrl_handler_setup(ov08x->sd.ctrl_handler); + if (ret) + return ret; + + return ov08x40_write_reg(ov08x, OV08X40_REG_MODE_SELECT, + OV08X40_REG_VALUE_08BIT, + OV08X40_MODE_STREAMING); +} + +/* Stop streaming */ +static int ov08x40_stop_streaming(struct ov08x40 *ov08x) +{ + return ov08x40_write_reg(ov08x, OV08X40_REG_MODE_SELECT, + OV08X40_REG_VALUE_08BIT, OV08X40_MODE_STANDBY); +} + +static int ov08x40_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct ov08x40 *ov08x = to_ov08x40(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret = 0; + + mutex_lock(&ov08x->mutex); + if (ov08x->streaming == enable) { + mutex_unlock(&ov08x->mutex); + return 0; + } + + if (enable) { + ret = pm_runtime_resume_and_get(&client->dev); + if (ret < 0) + goto err_unlock; + + /* + * Apply default & customized values + * and then start streaming. + */ + ret = ov08x40_start_streaming(ov08x); + if (ret) + goto err_rpm_put; + } else { + ov08x40_stop_streaming(ov08x); + pm_runtime_put(&client->dev); + } + + ov08x->streaming = enable; + mutex_unlock(&ov08x->mutex); + + return ret; + +err_rpm_put: + pm_runtime_put(&client->dev); +err_unlock: + mutex_unlock(&ov08x->mutex); + + return ret; +} + +static int __maybe_unused ov08x40_suspend(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ov08x40 *ov08x = to_ov08x40(sd); + + if (ov08x->streaming) + ov08x40_stop_streaming(ov08x); + + return 0; +} + +static int __maybe_unused ov08x40_resume(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ov08x40 *ov08x = to_ov08x40(sd); + int ret; + + if (ov08x->streaming) { + ret = ov08x40_start_streaming(ov08x); + if (ret) + goto error; + } + + return 0; + +error: + ov08x40_stop_streaming(ov08x); + ov08x->streaming = false; + return ret; +} + +/* Verify chip ID */ +static int ov08x40_identify_module(struct ov08x40 *ov08x) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov08x->sd); + int ret; + u32 val; + + ret = ov08x40_read_reg(ov08x, OV08X40_REG_CHIP_ID, + OV08X40_REG_VALUE_24BIT, &val); + if (ret) + return ret; + + if (val != OV08X40_CHIP_ID) { + dev_err(&client->dev, "chip id mismatch: %x!=%x\n", + OV08X40_CHIP_ID, val); + return -EIO; + } + + return 0; +} + +static const struct v4l2_subdev_video_ops ov08x40_video_ops = { + .s_stream = ov08x40_set_stream, +}; + +static const struct v4l2_subdev_pad_ops ov08x40_pad_ops = { + .enum_mbus_code = ov08x40_enum_mbus_code, + .get_fmt = ov08x40_get_pad_format, + .set_fmt = ov08x40_set_pad_format, + .enum_frame_size = ov08x40_enum_frame_size, +}; + +static const struct v4l2_subdev_ops ov08x40_subdev_ops = { + .video = &ov08x40_video_ops, + .pad = &ov08x40_pad_ops, +}; + +static const struct media_entity_operations ov08x40_subdev_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct v4l2_subdev_internal_ops ov08x40_internal_ops = { + .open = ov08x40_open, +}; + +static int ov08x40_init_controls(struct ov08x40 *ov08x) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov08x->sd); + struct v4l2_fwnode_device_properties props; + struct v4l2_ctrl_handler *ctrl_hdlr; + s64 exposure_max; + s64 vblank_def; + s64 vblank_min; + s64 hblank; + s64 pixel_rate_min; + s64 pixel_rate_max; + const struct ov08x40_mode *mode; + u32 max; + int ret; + + ctrl_hdlr = &ov08x->ctrl_handler; + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 10); + if (ret) + return ret; + + mutex_init(&ov08x->mutex); + ctrl_hdlr->lock = &ov08x->mutex; + max = ARRAY_SIZE(link_freq_menu_items) - 1; + ov08x->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, + &ov08x40_ctrl_ops, + V4L2_CID_LINK_FREQ, + max, + 0, + link_freq_menu_items); + if (ov08x->link_freq) + ov08x->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + pixel_rate_max = link_freq_to_pixel_rate(link_freq_menu_items[0]); + pixel_rate_min = 0; + /* By default, PIXEL_RATE is read only */ + ov08x->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &ov08x40_ctrl_ops, + V4L2_CID_PIXEL_RATE, + pixel_rate_min, pixel_rate_max, + 1, pixel_rate_max); + + mode = ov08x->cur_mode; + vblank_def = mode->vts_def - mode->height; + vblank_min = mode->vts_min - mode->height; + ov08x->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov08x40_ctrl_ops, + V4L2_CID_VBLANK, + vblank_min, + OV08X40_VTS_MAX - mode->height, 1, + vblank_def); + + hblank = link_freq_configs[mode->link_freq_index].pixels_per_line - + mode->width; + ov08x->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov08x40_ctrl_ops, + V4L2_CID_HBLANK, + hblank, hblank, 1, hblank); + if (ov08x->hblank) + ov08x->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + exposure_max = mode->vts_def - OV08X40_EXPOSURE_MAX_MARGIN; + ov08x->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &ov08x40_ctrl_ops, + V4L2_CID_EXPOSURE, + OV08X40_EXPOSURE_MIN, + exposure_max, OV08X40_EXPOSURE_STEP, + exposure_max); + + v4l2_ctrl_new_std(ctrl_hdlr, &ov08x40_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + OV08X40_ANA_GAIN_MIN, OV08X40_ANA_GAIN_MAX, + OV08X40_ANA_GAIN_STEP, OV08X40_ANA_GAIN_DEFAULT); + + /* Digital gain */ + v4l2_ctrl_new_std(ctrl_hdlr, &ov08x40_ctrl_ops, V4L2_CID_DIGITAL_GAIN, + OV08X40_DGTL_GAIN_MIN, OV08X40_DGTL_GAIN_MAX, + OV08X40_DGTL_GAIN_STEP, OV08X40_DGTL_GAIN_DEFAULT); + + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &ov08x40_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ov08x40_test_pattern_menu) - 1, + 0, 0, ov08x40_test_pattern_menu); + + v4l2_ctrl_new_std(ctrl_hdlr, &ov08x40_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(ctrl_hdlr, &ov08x40_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + + if (ctrl_hdlr->error) { + ret = ctrl_hdlr->error; + dev_err(&client->dev, "%s control init failed (%d)\n", + __func__, ret); + goto error; + } + + ret = v4l2_fwnode_device_parse(&client->dev, &props); + if (ret) + goto error; + + ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &ov08x40_ctrl_ops, + &props); + if (ret) + goto error; + + ov08x->sd.ctrl_handler = ctrl_hdlr; + + return 0; + +error: + v4l2_ctrl_handler_free(ctrl_hdlr); + mutex_destroy(&ov08x->mutex); + + return ret; +} + +static void ov08x40_free_controls(struct ov08x40 *ov08x) +{ + v4l2_ctrl_handler_free(ov08x->sd.ctrl_handler); + mutex_destroy(&ov08x->mutex); +} + +static int ov08x40_check_hwcfg(struct device *dev) +{ + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + struct fwnode_handle *ep; + struct fwnode_handle *fwnode = dev_fwnode(dev); + unsigned int i, j; + int ret; + u32 ext_clk; + + if (!fwnode) + return -ENXIO; + + ret = fwnode_property_read_u32(dev_fwnode(dev), "clock-frequency", + &ext_clk); + if (ret) { + dev_err(dev, "can't get clock frequency"); + return ret; + } + + if (ext_clk != OV08X40_EXT_CLK) { + dev_err(dev, "external clock %d is not supported", + ext_clk); + return -EINVAL; + } + + ep = fwnode_graph_get_next_endpoint(fwnode, NULL); + if (!ep) + return -ENXIO; + + ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg); + fwnode_handle_put(ep); + if (ret) + return ret; + + if (bus_cfg.bus.mipi_csi2.num_data_lanes != OV08X40_DATA_LANES) { + dev_err(dev, "number of CSI2 data lanes %d is not supported", + bus_cfg.bus.mipi_csi2.num_data_lanes); + ret = -EINVAL; + goto out_err; + } + + if (!bus_cfg.nr_of_link_frequencies) { + dev_err(dev, "no link frequencies defined"); + ret = -EINVAL; + goto out_err; + } + + for (i = 0; i < ARRAY_SIZE(link_freq_menu_items); i++) { + for (j = 0; j < bus_cfg.nr_of_link_frequencies; j++) { + if (link_freq_menu_items[i] == + bus_cfg.link_frequencies[j]) + break; + } + + if (j == bus_cfg.nr_of_link_frequencies) { + dev_err(dev, "no link frequency %lld supported", + link_freq_menu_items[i]); + ret = -EINVAL; + goto out_err; + } + } + +out_err: + v4l2_fwnode_endpoint_free(&bus_cfg); + + return ret; +} + +static int ov08x40_probe(struct i2c_client *client) +{ + struct ov08x40 *ov08x; + int ret; + + /* Check HW config */ + ret = ov08x40_check_hwcfg(&client->dev); + if (ret) { + dev_err(&client->dev, "failed to check hwcfg: %d", ret); + return ret; + } + + ov08x = devm_kzalloc(&client->dev, sizeof(*ov08x), GFP_KERNEL); + if (!ov08x) + return -ENOMEM; + + /* Initialize subdev */ + v4l2_i2c_subdev_init(&ov08x->sd, client, &ov08x40_subdev_ops); + + /* Check module identity */ + ret = ov08x40_identify_module(ov08x); + if (ret) { + dev_err(&client->dev, "failed to find sensor: %d\n", ret); + return ret; + } + + /* Set default mode to max resolution */ + ov08x->cur_mode = &supported_modes[0]; + + ret = ov08x40_init_controls(ov08x); + if (ret) + return ret; + + /* Initialize subdev */ + ov08x->sd.internal_ops = &ov08x40_internal_ops; + ov08x->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + ov08x->sd.entity.ops = &ov08x40_subdev_entity_ops; + ov08x->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + /* Initialize source pad */ + ov08x->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&ov08x->sd.entity, 1, &ov08x->pad); + if (ret) { + dev_err(&client->dev, "%s failed:%d\n", __func__, ret); + goto error_handler_free; + } + + ret = v4l2_async_register_subdev_sensor(&ov08x->sd); + if (ret < 0) + goto error_media_entity; + + /* + * Device is already turned on by i2c-core with ACPI domain PM. + * Enable runtime PM and turn off the device. + */ + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + pm_runtime_idle(&client->dev); + + return 0; + +error_media_entity: + media_entity_cleanup(&ov08x->sd.entity); + +error_handler_free: + ov08x40_free_controls(ov08x); + + return ret; +} + +static int ov08x40_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov08x40 *ov08x = to_ov08x40(sd); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + ov08x40_free_controls(ov08x); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + + return 0; +} + +static const struct dev_pm_ops ov08x40_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(ov08x40_suspend, ov08x40_resume) +}; + +#ifdef CONFIG_ACPI +static const struct acpi_device_id ov08x40_acpi_ids[] = { + {"OVTI08F4"}, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(acpi, ov08x40_acpi_ids); +#endif + +static struct i2c_driver ov08x40_i2c_driver = { + .driver = { + .name = "ov08x40", + .pm = &ov08x40_pm_ops, + .acpi_match_table = ACPI_PTR(ov08x40_acpi_ids), + }, + .probe_new = ov08x40_probe, + .remove = ov08x40_remove, +}; + +module_i2c_driver(ov08x40_i2c_driver); + +MODULE_AUTHOR("Jason Chen "); +MODULE_AUTHOR("Shawn Tu "); +MODULE_DESCRIPTION("OmniVision OV08X40 sensor driver"); +MODULE_LICENSE("GPL"); From 6cbd33e75ec829a2c95ba1f4403c804cf1d85c06 Mon Sep 17 00:00:00 2001 From: Mikhail Rudenko Date: Sat, 22 Oct 2022 19:20:06 +0300 Subject: [PATCH 11/47] media: dt-bindings: i2c: document OV4689 Add device-tree binding documentation for OV4689 image sensor driver, and the relevant MAINTAINERS entries. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Mikhail Rudenko Signed-off-by: Sakari Ailus --- .../bindings/media/i2c/ovti,ov4689.yaml | 134 ++++++++++++++++++ MAINTAINERS | 7 + 2 files changed, 141 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/i2c/ovti,ov4689.yaml diff --git a/Documentation/devicetree/bindings/media/i2c/ovti,ov4689.yaml b/Documentation/devicetree/bindings/media/i2c/ovti,ov4689.yaml new file mode 100644 index 000000000000..50579c947f3c --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/ovti,ov4689.yaml @@ -0,0 +1,134 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/ovti,ov4689.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Omnivision OV4689 CMOS + +maintainers: + - Mikhail Rudenko + +description: | + The Omnivision OV4689 is a high performance, 1/3-inch, 4 megapixel + image sensor. Ihis chip supports high frame rate speeds up to 90 fps + at 2688x1520 resolution. It is programmable through an I2C + interface, and sensor output is sent via 1/2/4 lane MIPI CSI-2 + connection. + +allOf: + - $ref: /schemas/media/video-interface-devices.yaml# + +properties: + compatible: + const: ovti,ov4689 + + reg: + maxItems: 1 + + clocks: + description: + External clock (XVCLK) for the sensor, 6-64 MHz + maxItems: 1 + + dovdd-supply: + description: + Digital I/O voltage supply, 1.7-3.0 V + + avdd-supply: + description: + Analog voltage supply, 2.6-3.0 V + + dvdd-supply: + description: + Digital core voltage supply, 1.1-1.3 V + + powerdown-gpios: + description: + GPIO connected to the powerdown pin (active low) + + reset-gpios: + maxItems: 1 + description: + GPIO connected to the reset pin (active low) + + orientation: true + + rotation: true + + port: + $ref: /schemas/graph.yaml#/$defs/port-base + additionalProperties: false + description: + Output port node, single endpoint describing the CSI-2 transmitter + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + data-lanes: + oneOf: + - items: + - const: 1 + - const: 2 + - const: 3 + - const: 4 + - items: + - const: 1 + - const: 2 + - items: + - const: 1 + link-frequencies: true + + required: + - data-lanes + - link-frequencies + +required: + - compatible + - reg + - clocks + - dovdd-supply + - avdd-supply + - dvdd-supply + - port + +additionalProperties: false + +examples: + - | + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + ov4689: camera@36 { + compatible = "ovti,ov4689"; + reg = <0x36>; + + clocks = <&ov4689_clk>; + + avdd-supply = <&ov4689_avdd>; + dovdd-supply = <&ov4689_dovdd>; + dvdd-supply = <&ov4689_dvdd>; + + powerdown-gpios = <&pio 107 GPIO_ACTIVE_LOW>; + reset-gpios = <&pio 109 GPIO_ACTIVE_LOW>; + + orientation = <2>; + rotation = <0>; + + port { + wcam_out: endpoint { + remote-endpoint = <&mipi_in_wcam>; + data-lanes = <1 2 3 4>; + link-frequencies = /bits/ 64 <504000000>; + }; + }; + }; + }; + +... diff --git a/MAINTAINERS b/MAINTAINERS index f3ac270d9fe8..c736744888c9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15221,6 +15221,13 @@ S: Maintained T: git git://linuxtv.org/media_tree.git F: drivers/media/i2c/ov2740.c +OMNIVISION OV4689 SENSOR DRIVER +M: Mikhail Rudenko +L: linux-media@vger.kernel.org +S: Maintained +T: git git://linuxtv.org/media_tree.git +F: Documentation/devicetree/bindings/media/i2c/ovti,ov4689.yaml + OMNIVISION OV5640 SENSOR DRIVER M: Steve Longerbeam L: linux-media@vger.kernel.org From 32a437db49aae789eb4c53450b7deedc43e397ae Mon Sep 17 00:00:00 2001 From: Mikhail Rudenko Date: Sat, 22 Oct 2022 19:20:07 +0300 Subject: [PATCH 12/47] media: i2c: add support for OV4689 Add a V4L2 sub-device driver for OmniVision OV4689 image sensor. This is a 4 Mpx image sensor using the I2C bus for control and the CSI-2 bus for data. This driver supports following features: - manual exposure and analog gain control support - test pattern support - media controller support - runtime PM support - support following resolutions: + 2688x1520 at 30 fps The driver provides all mandatory V4L2 controls for compatibility with libcamera. The sensor supports 1/2/4-lane CSI-2 modes, but the driver implements 4 lane mode only at this moment. Signed-off-by: Mikhail Rudenko Signed-off-by: Sakari Ailus --- MAINTAINERS | 1 + drivers/media/i2c/Kconfig | 13 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/ov4689.c | 1026 ++++++++++++++++++++++++++++++++++++ 4 files changed, 1041 insertions(+) create mode 100644 drivers/media/i2c/ov4689.c diff --git a/MAINTAINERS b/MAINTAINERS index c736744888c9..518326835156 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15227,6 +15227,7 @@ L: linux-media@vger.kernel.org S: Maintained T: git git://linuxtv.org/media_tree.git F: Documentation/devicetree/bindings/media/i2c/ovti,ov4689.yaml +F: drivers/media/i2c/ov5647.c OMNIVISION OV5640 SENSOR DRIVER M: Steve Longerbeam diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 8d749c01df70..48c0368baaaa 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -458,6 +458,19 @@ config VIDEO_OV2740 To compile this driver as a module, choose M here: the module will be called ov2740. +config VIDEO_OV4689 + tristate "OmniVision OV4689 sensor support" + depends on GPIOLIB && VIDEO_DEV && I2C + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + This is a Video4Linux2 sensor-level driver for the OmniVision + OV4689 camera. + + To compile this driver as a module, choose M here: the + module will be called ov4689. + config VIDEO_OV5640 tristate "OmniVision OV5640 sensor support" depends on OF diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 83be451ef326..c9959fd21b3e 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -80,6 +80,7 @@ obj-$(CONFIG_VIDEO_OV2659) += ov2659.o obj-$(CONFIG_VIDEO_OV2680) += ov2680.o obj-$(CONFIG_VIDEO_OV2685) += ov2685.o obj-$(CONFIG_VIDEO_OV2740) += ov2740.o +obj-$(CONFIG_VIDEO_OV4689) += ov4689.o obj-$(CONFIG_VIDEO_OV5640) += ov5640.o obj-$(CONFIG_VIDEO_OV5645) += ov5645.o obj-$(CONFIG_VIDEO_OV5647) += ov5647.o diff --git a/drivers/media/i2c/ov4689.c b/drivers/media/i2c/ov4689.c new file mode 100644 index 000000000000..419ff7371ba8 --- /dev/null +++ b/drivers/media/i2c/ov4689.c @@ -0,0 +1,1026 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ov4689 driver + * + * Copyright (C) 2017 Fuzhou Rockchip Electronics Co., Ltd. + * Copyright (C) 2022 Mikhail Rudenko + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CHIP_ID 0x004688 +#define OV4689_REG_CHIP_ID 0x300a + +#define OV4689_XVCLK_FREQ 24000000 + +#define OV4689_REG_CTRL_MODE 0x0100 +#define OV4689_MODE_SW_STANDBY 0x0 +#define OV4689_MODE_STREAMING BIT(0) + +#define OV4689_REG_EXPOSURE 0x3500 +#define OV4689_EXPOSURE_MIN 4 +#define OV4689_EXPOSURE_STEP 1 +#define OV4689_VTS_MAX 0x7fff + +#define OV4689_REG_GAIN_H 0x3508 +#define OV4689_REG_GAIN_L 0x3509 +#define OV4689_GAIN_H_MASK 0x07 +#define OV4689_GAIN_H_SHIFT 8 +#define OV4689_GAIN_L_MASK 0xff +#define OV4689_GAIN_STEP 1 +#define OV4689_GAIN_DEFAULT 0x80 + +#define OV4689_REG_TEST_PATTERN 0x5040 +#define OV4689_TEST_PATTERN_ENABLE 0x80 +#define OV4689_TEST_PATTERN_DISABLE 0x0 + +#define OV4689_REG_VTS 0x380e + +#define REG_NULL 0xFFFF + +#define OV4689_REG_VALUE_08BIT 1 +#define OV4689_REG_VALUE_16BIT 2 +#define OV4689_REG_VALUE_24BIT 3 + +#define OV4689_LANES 4 + +static const char *const ov4689_supply_names[] = { + "avdd", /* Analog power */ + "dovdd", /* Digital I/O power */ + "dvdd", /* Digital core power */ +}; + +struct regval { + u16 addr; + u8 val; +}; + +enum ov4689_mode_id { + OV4689_MODE_2688_1520 = 0, + OV4689_NUM_MODES, +}; + +struct ov4689_mode { + enum ov4689_mode_id id; + u32 width; + u32 height; + u32 max_fps; + u32 hts_def; + u32 vts_def; + u32 exp_def; + u32 pixel_rate; + u32 sensor_width; + u32 sensor_height; + u32 crop_top; + u32 crop_left; + const struct regval *reg_list; +}; + +struct ov4689 { + struct i2c_client *client; + struct clk *xvclk; + struct gpio_desc *reset_gpio; + struct gpio_desc *pwdn_gpio; + struct regulator_bulk_data supplies[ARRAY_SIZE(ov4689_supply_names)]; + + struct v4l2_subdev subdev; + struct media_pad pad; + + u32 clock_rate; + + struct mutex mutex; /* lock to protect streaming, ctrls and cur_mode */ + bool streaming; + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_ctrl *exposure; + + const struct ov4689_mode *cur_mode; +}; + +#define to_ov4689(sd) container_of(sd, struct ov4689, subdev) + +struct ov4689_gain_range { + u32 logical_min; + u32 logical_max; + u32 offset; + u32 divider; + u32 physical_min; + u32 physical_max; +}; + +/* + * Xclk 24Mhz + * max_framerate 30fps + * mipi_datarate per lane 1008Mbps + */ +static const struct regval ov4689_2688x1520_regs[] = { + {0x0103, 0x01}, {0x3638, 0x00}, {0x0300, 0x00}, + {0x0302, 0x2a}, {0x0303, 0x00}, {0x0304, 0x03}, + {0x030b, 0x00}, {0x030d, 0x1e}, {0x030e, 0x04}, + {0x030f, 0x01}, {0x0312, 0x01}, {0x031e, 0x00}, + {0x3000, 0x20}, {0x3002, 0x00}, {0x3018, 0x72}, + {0x3020, 0x93}, {0x3021, 0x03}, {0x3022, 0x01}, + {0x3031, 0x0a}, {0x303f, 0x0c}, {0x3305, 0xf1}, + {0x3307, 0x04}, {0x3309, 0x29}, {0x3500, 0x00}, + {0x3501, 0x60}, {0x3502, 0x00}, {0x3503, 0x04}, + {0x3504, 0x00}, {0x3505, 0x00}, {0x3506, 0x00}, + {0x3507, 0x00}, {0x3508, 0x00}, {0x3509, 0x80}, + {0x350a, 0x00}, {0x350b, 0x00}, {0x350c, 0x00}, + {0x350d, 0x00}, {0x350e, 0x00}, {0x350f, 0x80}, + {0x3510, 0x00}, {0x3511, 0x00}, {0x3512, 0x00}, + {0x3513, 0x00}, {0x3514, 0x00}, {0x3515, 0x80}, + {0x3516, 0x00}, {0x3517, 0x00}, {0x3518, 0x00}, + {0x3519, 0x00}, {0x351a, 0x00}, {0x351b, 0x80}, + {0x351c, 0x00}, {0x351d, 0x00}, {0x351e, 0x00}, + {0x351f, 0x00}, {0x3520, 0x00}, {0x3521, 0x80}, + {0x3522, 0x08}, {0x3524, 0x08}, {0x3526, 0x08}, + {0x3528, 0x08}, {0x352a, 0x08}, {0x3602, 0x00}, + {0x3603, 0x40}, {0x3604, 0x02}, {0x3605, 0x00}, + {0x3606, 0x00}, {0x3607, 0x00}, {0x3609, 0x12}, + {0x360a, 0x40}, {0x360c, 0x08}, {0x360f, 0xe5}, + {0x3608, 0x8f}, {0x3611, 0x00}, {0x3613, 0xf7}, + {0x3616, 0x58}, {0x3619, 0x99}, {0x361b, 0x60}, + {0x361c, 0x7a}, {0x361e, 0x79}, {0x361f, 0x02}, + {0x3632, 0x00}, {0x3633, 0x10}, {0x3634, 0x10}, + {0x3635, 0x10}, {0x3636, 0x15}, {0x3646, 0x86}, + {0x364a, 0x0b}, {0x3700, 0x17}, {0x3701, 0x22}, + {0x3703, 0x10}, {0x370a, 0x37}, {0x3705, 0x00}, + {0x3706, 0x63}, {0x3709, 0x3c}, {0x370b, 0x01}, + {0x370c, 0x30}, {0x3710, 0x24}, {0x3711, 0x0c}, + {0x3716, 0x00}, {0x3720, 0x28}, {0x3729, 0x7b}, + {0x372a, 0x84}, {0x372b, 0xbd}, {0x372c, 0xbc}, + {0x372e, 0x52}, {0x373c, 0x0e}, {0x373e, 0x33}, + {0x3743, 0x10}, {0x3744, 0x88}, {0x3745, 0xc0}, + {0x374a, 0x43}, {0x374c, 0x00}, {0x374e, 0x23}, + {0x3751, 0x7b}, {0x3752, 0x84}, {0x3753, 0xbd}, + {0x3754, 0xbc}, {0x3756, 0x52}, {0x375c, 0x00}, + {0x3760, 0x00}, {0x3761, 0x00}, {0x3762, 0x00}, + {0x3763, 0x00}, {0x3764, 0x00}, {0x3767, 0x04}, + {0x3768, 0x04}, {0x3769, 0x08}, {0x376a, 0x08}, + {0x376b, 0x20}, {0x376c, 0x00}, {0x376d, 0x00}, + {0x376e, 0x00}, {0x3773, 0x00}, {0x3774, 0x51}, + {0x3776, 0xbd}, {0x3777, 0xbd}, {0x3781, 0x18}, + {0x3783, 0x25}, {0x3798, 0x1b}, {0x3800, 0x00}, + {0x3801, 0x08}, {0x3802, 0x00}, {0x3803, 0x04}, + {0x3804, 0x0a}, {0x3805, 0x97}, {0x3806, 0x05}, + {0x3807, 0xfb}, {0x3808, 0x0a}, {0x3809, 0x80}, + {0x380a, 0x05}, {0x380b, 0xf0}, {0x380c, 0x0a}, + {0x380d, 0x0e}, {0x380e, 0x06}, {0x380f, 0x12}, + {0x3810, 0x00}, {0x3811, 0x08}, {0x3812, 0x00}, + {0x3813, 0x04}, {0x3814, 0x01}, {0x3815, 0x01}, + {0x3819, 0x01}, {0x3820, 0x00}, {0x3821, 0x06}, + {0x3829, 0x00}, {0x382a, 0x01}, {0x382b, 0x01}, + {0x382d, 0x7f}, {0x3830, 0x04}, {0x3836, 0x01}, + {0x3837, 0x00}, {0x3841, 0x02}, {0x3846, 0x08}, + {0x3847, 0x07}, {0x3d85, 0x36}, {0x3d8c, 0x71}, + {0x3d8d, 0xcb}, {0x3f0a, 0x00}, {0x4000, 0xf1}, + {0x4001, 0x40}, {0x4002, 0x04}, {0x4003, 0x14}, + {0x400e, 0x00}, {0x4011, 0x00}, {0x401a, 0x00}, + {0x401b, 0x00}, {0x401c, 0x00}, {0x401d, 0x00}, + {0x401f, 0x00}, {0x4020, 0x00}, {0x4021, 0x10}, + {0x4022, 0x07}, {0x4023, 0xcf}, {0x4024, 0x09}, + {0x4025, 0x60}, {0x4026, 0x09}, {0x4027, 0x6f}, + {0x4028, 0x00}, {0x4029, 0x02}, {0x402a, 0x06}, + {0x402b, 0x04}, {0x402c, 0x02}, {0x402d, 0x02}, + {0x402e, 0x0e}, {0x402f, 0x04}, {0x4302, 0xff}, + {0x4303, 0xff}, {0x4304, 0x00}, {0x4305, 0x00}, + {0x4306, 0x00}, {0x4308, 0x02}, {0x4500, 0x6c}, + {0x4501, 0xc4}, {0x4502, 0x40}, {0x4503, 0x01}, + {0x4601, 0xa7}, {0x4800, 0x04}, {0x4813, 0x08}, + {0x481f, 0x40}, {0x4829, 0x78}, {0x4837, 0x10}, + {0x4b00, 0x2a}, {0x4b0d, 0x00}, {0x4d00, 0x04}, + {0x4d01, 0x42}, {0x4d02, 0xd1}, {0x4d03, 0x93}, + {0x4d04, 0xf5}, {0x4d05, 0xc1}, {0x5000, 0xf3}, + {0x5001, 0x11}, {0x5004, 0x00}, {0x500a, 0x00}, + {0x500b, 0x00}, {0x5032, 0x00}, {0x5040, 0x00}, + {0x5050, 0x0c}, {0x5500, 0x00}, {0x5501, 0x10}, + {0x5502, 0x01}, {0x5503, 0x0f}, {0x8000, 0x00}, + {0x8001, 0x00}, {0x8002, 0x00}, {0x8003, 0x00}, + {0x8004, 0x00}, {0x8005, 0x00}, {0x8006, 0x00}, + {0x8007, 0x00}, {0x8008, 0x00}, {0x3638, 0x00}, + {REG_NULL, 0x00}, +}; + +static const struct ov4689_mode supported_modes[] = { + { + .id = OV4689_MODE_2688_1520, + .width = 2688, + .height = 1520, + .sensor_width = 2720, + .sensor_height = 1536, + .crop_top = 8, + .crop_left = 16, + .max_fps = 30, + .exp_def = 1536, + .hts_def = 4 * 2574, + .vts_def = 1554, + .pixel_rate = 480000000, + .reg_list = ov4689_2688x1520_regs, + }, +}; + +static const u64 link_freq_menu_items[] = { 504000000 }; + +static const char *const ov4689_test_pattern_menu[] = { + "Disabled", + "Vertical Color Bar Type 1", + "Vertical Color Bar Type 2", + "Vertical Color Bar Type 3", + "Vertical Color Bar Type 4" +}; + +/* + * These coefficients are based on those used in Rockchip's camera + * engine, with minor tweaks for continuity. + */ +static const struct ov4689_gain_range ov4689_gain_ranges[] = { + { + .logical_min = 0, + .logical_max = 255, + .offset = 0, + .divider = 1, + .physical_min = 0, + .physical_max = 255, + }, + { + .logical_min = 256, + .logical_max = 511, + .offset = 252, + .divider = 2, + .physical_min = 376, + .physical_max = 504, + }, + { + .logical_min = 512, + .logical_max = 1023, + .offset = 758, + .divider = 4, + .physical_min = 884, + .physical_max = 1012, + }, + { + .logical_min = 1024, + .logical_max = 2047, + .offset = 1788, + .divider = 8, + .physical_min = 1912, + .physical_max = 2047, + }, +}; + +/* Write registers up to 4 at a time */ +static int ov4689_write_reg(struct i2c_client *client, u16 reg, u32 len, + u32 val) +{ + u32 buf_i, val_i; + __be32 val_be; + u8 *val_p; + u8 buf[6]; + + if (len > 4) + return -EINVAL; + + buf[0] = reg >> 8; + buf[1] = reg & 0xff; + + val_be = cpu_to_be32(val); + val_p = (u8 *)&val_be; + buf_i = 2; + val_i = 4 - len; + + while (val_i < 4) + buf[buf_i++] = val_p[val_i++]; + + if (i2c_master_send(client, buf, len + 2) != len + 2) + return -EIO; + + return 0; +} + +static int ov4689_write_array(struct i2c_client *client, + const struct regval *regs) +{ + int ret = 0; + u32 i; + + for (i = 0; ret == 0 && regs[i].addr != REG_NULL; i++) + ret = ov4689_write_reg(client, regs[i].addr, + OV4689_REG_VALUE_08BIT, regs[i].val); + + return ret; +} + +/* Read registers up to 4 at a time */ +static int ov4689_read_reg(struct i2c_client *client, u16 reg, unsigned int len, + u32 *val) +{ + __be16 reg_addr_be = cpu_to_be16(reg); + struct i2c_msg msgs[2]; + __be32 data_be = 0; + u8 *data_be_p; + int ret; + + if (len > 4 || !len) + return -EINVAL; + + data_be_p = (u8 *)&data_be; + /* Write register address */ + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = 2; + msgs[0].buf = (u8 *)®_addr_be; + + /* Read data from register */ + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = &data_be_p[4 - len]; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *val = be32_to_cpu(data_be); + + return 0; +} + +static void ov4689_fill_fmt(const struct ov4689_mode *mode, + struct v4l2_mbus_framefmt *fmt) +{ + fmt->code = MEDIA_BUS_FMT_SBGGR10_1X10; + fmt->width = mode->width; + fmt->height = mode->height; + fmt->field = V4L2_FIELD_NONE; +} + +static int ov4689_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *mbus_fmt = &fmt->format; + struct ov4689 *ov4689 = to_ov4689(sd); + + /* only one mode supported for now */ + ov4689_fill_fmt(ov4689->cur_mode, mbus_fmt); + + return 0; +} + +static int ov4689_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *mbus_fmt = &fmt->format; + struct ov4689 *ov4689 = to_ov4689(sd); + + /* only one mode supported for now */ + ov4689_fill_fmt(ov4689->cur_mode, mbus_fmt); + + return 0; +} + +static int ov4689_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index != 0) + return -EINVAL; + code->code = MEDIA_BUS_FMT_SBGGR10_1X10; + + return 0; +} + +static int ov4689_enum_frame_sizes(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + if (fse->code != MEDIA_BUS_FMT_SBGGR10_1X10) + return -EINVAL; + + fse->min_width = supported_modes[fse->index].width; + fse->max_width = supported_modes[fse->index].width; + fse->max_height = supported_modes[fse->index].height; + fse->min_height = supported_modes[fse->index].height; + + return 0; +} + +static int ov4689_enable_test_pattern(struct ov4689 *ov4689, u32 pattern) +{ + u32 val; + + if (pattern) + val = (pattern - 1) | OV4689_TEST_PATTERN_ENABLE; + else + val = OV4689_TEST_PATTERN_DISABLE; + + return ov4689_write_reg(ov4689->client, OV4689_REG_TEST_PATTERN, + OV4689_REG_VALUE_08BIT, val); +} + +static int ov4689_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *sel) +{ + const struct ov4689_mode *mode = to_ov4689(sd)->cur_mode; + + if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE) + return -EINVAL; + + switch (sel->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = mode->sensor_width; + sel->r.height = mode->sensor_height; + return 0; + case V4L2_SEL_TGT_CROP: + case V4L2_SEL_TGT_CROP_DEFAULT: + sel->r.top = mode->crop_top; + sel->r.left = mode->crop_left; + sel->r.width = mode->width; + sel->r.height = mode->height; + return 0; + } + + return -EINVAL; +} + +static int ov4689_s_stream(struct v4l2_subdev *sd, int on) +{ + struct ov4689 *ov4689 = to_ov4689(sd); + struct i2c_client *client = ov4689->client; + int ret = 0; + + mutex_lock(&ov4689->mutex); + + on = !!on; + if (on == ov4689->streaming) + goto unlock_and_return; + + if (on) { + ret = pm_runtime_resume_and_get(&client->dev); + if (ret < 0) + goto unlock_and_return; + + ret = ov4689_write_array(ov4689->client, + ov4689->cur_mode->reg_list); + if (ret) { + pm_runtime_put(&client->dev); + goto unlock_and_return; + } + + ret = __v4l2_ctrl_handler_setup(&ov4689->ctrl_handler); + if (ret) { + pm_runtime_put(&client->dev); + goto unlock_and_return; + } + + ret = ov4689_write_reg(ov4689->client, OV4689_REG_CTRL_MODE, + OV4689_REG_VALUE_08BIT, + OV4689_MODE_STREAMING); + if (ret) { + pm_runtime_put(&client->dev); + goto unlock_and_return; + } + } else { + ov4689_write_reg(ov4689->client, OV4689_REG_CTRL_MODE, + OV4689_REG_VALUE_08BIT, + OV4689_MODE_SW_STANDBY); + pm_runtime_put(&client->dev); + } + + ov4689->streaming = on; + +unlock_and_return: + mutex_unlock(&ov4689->mutex); + + return ret; +} + +/* Calculate the delay in us by clock rate and clock cycles */ +static inline u32 ov4689_cal_delay(struct ov4689 *ov4689, u32 cycles) +{ + return DIV_ROUND_UP(cycles * 1000, + DIV_ROUND_UP(ov4689->clock_rate, 1000)); +} + +static int __maybe_unused ov4689_power_on(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ov4689 *ov4689 = to_ov4689(sd); + u32 delay_us; + int ret; + + ret = clk_prepare_enable(ov4689->xvclk); + if (ret < 0) { + dev_err(dev, "Failed to enable xvclk\n"); + return ret; + } + + gpiod_set_value_cansleep(ov4689->reset_gpio, 1); + + ret = regulator_bulk_enable(ARRAY_SIZE(ov4689_supply_names), + ov4689->supplies); + if (ret < 0) { + dev_err(dev, "Failed to enable regulators\n"); + goto disable_clk; + } + + gpiod_set_value_cansleep(ov4689->reset_gpio, 0); + usleep_range(500, 1000); + gpiod_set_value_cansleep(ov4689->pwdn_gpio, 0); + + /* 8192 cycles prior to first SCCB transaction */ + delay_us = ov4689_cal_delay(ov4689, 8192); + usleep_range(delay_us, delay_us * 2); + + return 0; + +disable_clk: + clk_disable_unprepare(ov4689->xvclk); + + return ret; +} + +static int __maybe_unused ov4689_power_off(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct ov4689 *ov4689 = to_ov4689(sd); + + gpiod_set_value_cansleep(ov4689->pwdn_gpio, 1); + clk_disable_unprepare(ov4689->xvclk); + gpiod_set_value_cansleep(ov4689->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(ov4689_supply_names), + ov4689->supplies); + return 0; +} + +static int ov4689_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct ov4689 *ov4689 = to_ov4689(sd); + struct v4l2_mbus_framefmt *try_fmt; + + mutex_lock(&ov4689->mutex); + + try_fmt = v4l2_subdev_get_try_format(sd, fh->state, 0); + /* Initialize try_fmt */ + ov4689_fill_fmt(&supported_modes[OV4689_MODE_2688_1520], try_fmt); + + mutex_unlock(&ov4689->mutex); + + return 0; +} + +static const struct dev_pm_ops ov4689_pm_ops = { + SET_RUNTIME_PM_OPS(ov4689_power_off, ov4689_power_on, NULL) +}; + +static const struct v4l2_subdev_internal_ops ov4689_internal_ops = { + .open = ov4689_open, +}; + +static const struct v4l2_subdev_video_ops ov4689_video_ops = { + .s_stream = ov4689_s_stream, +}; + +static const struct v4l2_subdev_pad_ops ov4689_pad_ops = { + .enum_mbus_code = ov4689_enum_mbus_code, + .enum_frame_size = ov4689_enum_frame_sizes, + .get_fmt = ov4689_get_fmt, + .set_fmt = ov4689_set_fmt, + .get_selection = ov4689_get_selection, +}; + +static const struct v4l2_subdev_ops ov4689_subdev_ops = { + .video = &ov4689_video_ops, + .pad = &ov4689_pad_ops, +}; + +/* + * Map userspace (logical) gain to sensor (physical) gain using + * ov4689_gain_ranges table. + */ +static int ov4689_map_gain(struct ov4689 *ov4689, int logical_gain, int *result) +{ + const struct device *dev = &ov4689->client->dev; + const struct ov4689_gain_range *range; + unsigned int n; + + for (n = 0; n < ARRAY_SIZE(ov4689_gain_ranges); n++) { + if (logical_gain >= ov4689_gain_ranges[n].logical_min && + logical_gain <= ov4689_gain_ranges[n].logical_max) { + break; + } + } + + if (n == ARRAY_SIZE(ov4689_gain_ranges)) { + dev_warn_ratelimited(dev, "no mapping found for gain %d\n", + logical_gain); + return -EINVAL; + } + + range = &ov4689_gain_ranges[n]; + + *result = clamp(range->offset + (logical_gain) / range->divider, + range->physical_min, range->physical_max); + return 0; +} + +static int ov4689_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ov4689 *ov4689 = + container_of(ctrl->handler, struct ov4689, ctrl_handler); + struct i2c_client *client = ov4689->client; + int sensor_gain; + s64 max_expo; + int ret; + + /* Propagate change of current control to all related controls */ + switch (ctrl->id) { + case V4L2_CID_VBLANK: + /* Update max exposure while meeting expected vblanking */ + max_expo = ov4689->cur_mode->height + ctrl->val - 4; + __v4l2_ctrl_modify_range(ov4689->exposure, + ov4689->exposure->minimum, max_expo, + ov4689->exposure->step, + ov4689->exposure->default_value); + break; + } + + if (!pm_runtime_get_if_in_use(&client->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + /* 4 least significant bits of expsoure are fractional part */ + ret = ov4689_write_reg(ov4689->client, OV4689_REG_EXPOSURE, + OV4689_REG_VALUE_24BIT, ctrl->val << 4); + break; + case V4L2_CID_ANALOGUE_GAIN: + ret = ov4689_map_gain(ov4689, ctrl->val, &sensor_gain); + + ret = ret ?: + ov4689_write_reg(ov4689->client, OV4689_REG_GAIN_H, + OV4689_REG_VALUE_08BIT, + (sensor_gain >> OV4689_GAIN_H_SHIFT) & + OV4689_GAIN_H_MASK); + ret = ret ?: + ov4689_write_reg(ov4689->client, OV4689_REG_GAIN_L, + OV4689_REG_VALUE_08BIT, + sensor_gain & OV4689_GAIN_L_MASK); + break; + case V4L2_CID_VBLANK: + ret = ov4689_write_reg(ov4689->client, OV4689_REG_VTS, + OV4689_REG_VALUE_16BIT, + ctrl->val + ov4689->cur_mode->height); + break; + case V4L2_CID_TEST_PATTERN: + ret = ov4689_enable_test_pattern(ov4689, ctrl->val); + break; + default: + dev_warn(&client->dev, "%s Unhandled id:0x%x, val:0x%x\n", + __func__, ctrl->id, ctrl->val); + ret = -EINVAL; + break; + } + + pm_runtime_put(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops ov4689_ctrl_ops = { + .s_ctrl = ov4689_set_ctrl, +}; + +static int ov4689_initialize_controls(struct ov4689 *ov4689) +{ + struct i2c_client *client = v4l2_get_subdevdata(&ov4689->subdev); + struct v4l2_fwnode_device_properties props; + struct v4l2_ctrl_handler *handler; + const struct ov4689_mode *mode; + s64 exposure_max, vblank_def; + struct v4l2_ctrl *ctrl; + s64 h_blank_def; + int ret; + + handler = &ov4689->ctrl_handler; + mode = ov4689->cur_mode; + ret = v4l2_ctrl_handler_init(handler, 10); + if (ret) + return ret; + handler->lock = &ov4689->mutex; + + ctrl = v4l2_ctrl_new_int_menu(handler, NULL, V4L2_CID_LINK_FREQ, 0, 0, + link_freq_menu_items); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + v4l2_ctrl_new_std(handler, NULL, V4L2_CID_PIXEL_RATE, 0, + mode->pixel_rate, 1, mode->pixel_rate); + + h_blank_def = mode->hts_def - mode->width; + ctrl = v4l2_ctrl_new_std(handler, NULL, V4L2_CID_HBLANK, h_blank_def, + h_blank_def, 1, h_blank_def); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + vblank_def = mode->vts_def - mode->height; + v4l2_ctrl_new_std(handler, &ov4689_ctrl_ops, V4L2_CID_VBLANK, + vblank_def, OV4689_VTS_MAX - mode->height, 1, + vblank_def); + + exposure_max = mode->vts_def - 4; + ov4689->exposure = + v4l2_ctrl_new_std(handler, &ov4689_ctrl_ops, V4L2_CID_EXPOSURE, + OV4689_EXPOSURE_MIN, exposure_max, + OV4689_EXPOSURE_STEP, mode->exp_def); + + v4l2_ctrl_new_std(handler, &ov4689_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + ov4689_gain_ranges[0].logical_min, + ov4689_gain_ranges[ARRAY_SIZE(ov4689_gain_ranges) - 1] + .logical_max, + OV4689_GAIN_STEP, OV4689_GAIN_DEFAULT); + + v4l2_ctrl_new_std_menu_items(handler, &ov4689_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ov4689_test_pattern_menu) - 1, + 0, 0, ov4689_test_pattern_menu); + + if (handler->error) { + ret = handler->error; + dev_err(&ov4689->client->dev, "Failed to init controls(%d)\n", + ret); + goto err_free_handler; + } + + ret = v4l2_fwnode_device_parse(&client->dev, &props); + if (ret) + goto err_free_handler; + + ret = v4l2_ctrl_new_fwnode_properties(handler, &ov4689_ctrl_ops, + &props); + if (ret) + goto err_free_handler; + + ov4689->subdev.ctrl_handler = handler; + + return 0; + +err_free_handler: + v4l2_ctrl_handler_free(handler); + + return ret; +} + +static int ov4689_check_sensor_id(struct ov4689 *ov4689, + struct i2c_client *client) +{ + struct device *dev = &ov4689->client->dev; + u32 id = 0; + int ret; + + ret = ov4689_read_reg(client, OV4689_REG_CHIP_ID, + OV4689_REG_VALUE_16BIT, &id); + if (ret) { + dev_err(dev, "Cannot read sensor ID\n"); + return ret; + } + + if (id != CHIP_ID) { + dev_err(dev, "Unexpected sensor ID %06x, expected %06x\n", + id, CHIP_ID); + return -ENODEV; + } + + dev_info(dev, "Detected OV%06x sensor\n", CHIP_ID); + + return 0; +} + +static int ov4689_configure_regulators(struct ov4689 *ov4689) +{ + unsigned int supplies_count = ARRAY_SIZE(ov4689_supply_names); + unsigned int i; + + for (i = 0; i < supplies_count; i++) + ov4689->supplies[i].supply = ov4689_supply_names[i]; + + return devm_regulator_bulk_get(&ov4689->client->dev, supplies_count, + ov4689->supplies); +} + +static u64 ov4689_check_link_frequency(struct v4l2_fwnode_endpoint *ep) +{ + unsigned int freqs_count = ARRAY_SIZE(link_freq_menu_items); + const u64 *freqs = link_freq_menu_items; + unsigned int i, j; + + for (i = 0; i < freqs_count; i++) { + for (j = 0; j < ep->nr_of_link_frequencies; j++) + if (freqs[i] == ep->link_frequencies[j]) + return freqs[i]; + } + + return 0; +} + +static int ov4689_check_hwcfg(struct device *dev) +{ + struct fwnode_handle *fwnode = dev_fwnode(dev); + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY, + }; + struct fwnode_handle *endpoint; + int ret; + + endpoint = fwnode_graph_get_next_endpoint(fwnode, NULL); + if (!endpoint) + return -EINVAL; + + ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &bus_cfg); + fwnode_handle_put(endpoint); + if (ret) + return ret; + + if (bus_cfg.bus.mipi_csi2.num_data_lanes != OV4689_LANES) { + dev_err(dev, "Only a 4-lane CSI2 config is supported"); + ret = -EINVAL; + goto out_free_bus_cfg; + } + + if (!bus_cfg.nr_of_link_frequencies) { + dev_err(dev, "No link frequencies defined\n"); + ret = -EINVAL; + goto out_free_bus_cfg; + } + + if (!ov4689_check_link_frequency(&bus_cfg)) { + dev_err(dev, "No supported link frequency found\n"); + ret = -EINVAL; + } + +out_free_bus_cfg: + v4l2_fwnode_endpoint_free(&bus_cfg); + + return ret; +} + +static int ov4689_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct v4l2_subdev *sd; + struct ov4689 *ov4689; + int ret; + + ret = ov4689_check_hwcfg(dev); + if (ret) + return ret; + + ov4689 = devm_kzalloc(dev, sizeof(*ov4689), GFP_KERNEL); + if (!ov4689) + return -ENOMEM; + + ov4689->client = client; + ov4689->cur_mode = &supported_modes[OV4689_MODE_2688_1520]; + + ov4689->xvclk = devm_clk_get_optional(dev, NULL); + if (IS_ERR(ov4689->xvclk)) + return dev_err_probe(dev, PTR_ERR(ov4689->xvclk), + "Failed to get external clock\n"); + + if (!ov4689->xvclk) { + dev_dbg(dev, + "No clock provided, using clock-frequency property\n"); + device_property_read_u32(dev, "clock-frequency", + &ov4689->clock_rate); + } else { + ov4689->clock_rate = clk_get_rate(ov4689->xvclk); + } + + if (ov4689->clock_rate != OV4689_XVCLK_FREQ) { + dev_err(dev, + "External clock rate mismatch: got %d Hz, expected %d Hz\n", + ov4689->clock_rate, OV4689_XVCLK_FREQ); + return -EINVAL; + } + + ov4689->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(ov4689->reset_gpio)) { + dev_err(dev, "Failed to get reset-gpios\n"); + return PTR_ERR(ov4689->reset_gpio); + } + + ov4689->pwdn_gpio = devm_gpiod_get_optional(dev, "pwdn", GPIOD_OUT_LOW); + if (IS_ERR(ov4689->pwdn_gpio)) { + dev_err(dev, "Failed to get pwdn-gpios\n"); + return PTR_ERR(ov4689->pwdn_gpio); + } + + ret = ov4689_configure_regulators(ov4689); + if (ret) + return dev_err_probe(dev, ret, + "Failed to get power regulators\n"); + + mutex_init(&ov4689->mutex); + + sd = &ov4689->subdev; + v4l2_i2c_subdev_init(sd, client, &ov4689_subdev_ops); + ret = ov4689_initialize_controls(ov4689); + if (ret) + goto err_destroy_mutex; + + ret = ov4689_power_on(dev); + if (ret) + goto err_free_handler; + + ret = ov4689_check_sensor_id(ov4689, client); + if (ret) + goto err_power_off; + + sd->internal_ops = &ov4689_internal_ops; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + ov4689->pad.flags = MEDIA_PAD_FL_SOURCE; + sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; + ret = media_entity_pads_init(&sd->entity, 1, &ov4689->pad); + if (ret < 0) + goto err_power_off; + + ret = v4l2_async_register_subdev_sensor(sd); + if (ret) { + dev_err(dev, "v4l2 async register subdev failed\n"); + goto err_clean_entity; + } + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); + + return 0; + +err_clean_entity: + media_entity_cleanup(&sd->entity); +err_power_off: + ov4689_power_off(dev); +err_free_handler: + v4l2_ctrl_handler_free(&ov4689->ctrl_handler); +err_destroy_mutex: + mutex_destroy(&ov4689->mutex); + + return ret; +} + +static void ov4689_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov4689 *ov4689 = to_ov4689(sd); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + + v4l2_ctrl_handler_free(&ov4689->ctrl_handler); + mutex_destroy(&ov4689->mutex); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + ov4689_power_off(&client->dev); + pm_runtime_set_suspended(&client->dev); +} + +static const struct of_device_id ov4689_of_match[] = { + { .compatible = "ovti,ov4689" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ov4689_of_match); + +static struct i2c_driver ov4689_i2c_driver = { + .driver = { + .name = "ov4689", + .pm = &ov4689_pm_ops, + .of_match_table = ov4689_of_match, + }, + .probe_new = ov4689_probe, + .remove = ov4689_remove, +}; + +module_i2c_driver(ov4689_i2c_driver); + +MODULE_DESCRIPTION("OmniVision ov4689 sensor driver"); +MODULE_LICENSE("GPL"); From 48f750e25ab72c36f2b043370175adf602d4a45e Mon Sep 17 00:00:00 2001 From: Benjamin Mugnier Date: Tue, 11 Oct 2022 14:16:01 +0200 Subject: [PATCH 13/47] media: v4l: Add 1X16 16-bit greyscale media bus code definition This extends the greyscale media bus family originally from MEDIA_BUS_FMT_Y8_1X8 up to MEDIA_BUS_FMT_Y14_1X14 by adding MEDIA_BUS_FMT_Y16_1X16, and behaves the same way with 16 bits. Add its documentation in subdev-formats.rst Signed-off-by: Benjamin Mugnier Reviewed-by: Laurent Pinchart Signed-off-by: Sakari Ailus --- .../media/v4l/subdev-formats.rst | 37 +++++++++++++++++++ include/uapi/linux/media-bus-format.h | 3 +- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/Documentation/userspace-api/media/v4l/subdev-formats.rst b/Documentation/userspace-api/media/v4l/subdev-formats.rst index d21d532eee15..16ef3b41932e 100644 --- a/Documentation/userspace-api/media/v4l/subdev-formats.rst +++ b/Documentation/userspace-api/media/v4l/subdev-formats.rst @@ -6057,6 +6057,43 @@ the following codes. - y\ :sub:`2` - y\ :sub:`1` - y\ :sub:`0` + * .. _MEDIA-BUS-FMT-Y16-1X16: + + - MEDIA_BUS_FMT_Y16_1X16 + - 0x202e + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - y\ :sub:`15` + - y\ :sub:`14` + - y\ :sub:`13` + - y\ :sub:`12` + - y\ :sub:`11` + - y\ :sub:`10` + - y\ :sub:`9` + - y\ :sub:`8` + - y\ :sub:`7` + - y\ :sub:`6` + - y\ :sub:`5` + - y\ :sub:`4` + - y\ :sub:`3` + - y\ :sub:`2` + - y\ :sub:`1` + - y\ :sub:`0` * .. _MEDIA-BUS-FMT-UYVY8-1X16: - MEDIA_BUS_FMT_UYVY8_1X16 diff --git a/include/uapi/linux/media-bus-format.h b/include/uapi/linux/media-bus-format.h index ec3323dbb927..ca9a24c874da 100644 --- a/include/uapi/linux/media-bus-format.h +++ b/include/uapi/linux/media-bus-format.h @@ -69,7 +69,7 @@ #define MEDIA_BUS_FMT_RGB121212_1X36 0x1019 #define MEDIA_BUS_FMT_RGB161616_1X48 0x101a -/* YUV (including grey) - next is 0x202e */ +/* YUV (including grey) - next is 0x202f */ #define MEDIA_BUS_FMT_Y8_1X8 0x2001 #define MEDIA_BUS_FMT_UV8_1X8 0x2015 #define MEDIA_BUS_FMT_UYVY8_1_5X8 0x2002 @@ -92,6 +92,7 @@ #define MEDIA_BUS_FMT_YUYV12_2X12 0x201e #define MEDIA_BUS_FMT_YVYU12_2X12 0x201f #define MEDIA_BUS_FMT_Y14_1X14 0x202d +#define MEDIA_BUS_FMT_Y16_1X16 0x202e #define MEDIA_BUS_FMT_UYVY8_1X16 0x200f #define MEDIA_BUS_FMT_VYUY8_1X16 0x2010 #define MEDIA_BUS_FMT_YUYV8_1X16 0x2011 From 7673f3058bd2511474491d4f82c2ece09176412c Mon Sep 17 00:00:00 2001 From: Benjamin Mugnier Date: Tue, 11 Oct 2022 14:16:02 +0200 Subject: [PATCH 14/47] media: v4l: ctrls: Add a control for HDR mode Add V4L2_CID_HDR_MODE as a menu item control to set the HDR mode of the sensor, and its documentation. Menu items are not standardized as they differ for each sensors. Signed-off-by: Benjamin Mugnier Signed-off-by: Sakari Ailus --- .../userspace-api/media/v4l/ext-ctrls-camera.rst | 8 ++++++++ drivers/media/v4l2-core/v4l2-ctrls-defs.c | 2 ++ include/uapi/linux/v4l2-controls.h | 2 ++ 3 files changed, 12 insertions(+) diff --git a/Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst b/Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst index 4c5061aa9cd4..daa4f40869f8 100644 --- a/Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst +++ b/Documentation/userspace-api/media/v4l/ext-ctrls-camera.rst @@ -661,3 +661,11 @@ enum v4l2_scene_mode - .. [#f1] This control may be changed to a menu control in the future, if more options are required. + +``V4L2_CID_HDR_SENSOR_MODE (menu)`` + Change the sensor HDR mode. A HDR picture is obtained by merging two + captures of the same scene using two different exposure periods. HDR mode + describes the way these two captures are merged in the sensor. + + As modes differ for each sensor, menu items are not standardized by this + control and are left to the programmer. diff --git a/drivers/media/v4l2-core/v4l2-ctrls-defs.c b/drivers/media/v4l2-core/v4l2-ctrls-defs.c index e22921e7ea61..564fedee2c88 100644 --- a/drivers/media/v4l2-core/v4l2-ctrls-defs.c +++ b/drivers/media/v4l2-core/v4l2-ctrls-defs.c @@ -1043,6 +1043,7 @@ const char *v4l2_ctrl_get_name(u32 id) case V4L2_CID_UNIT_CELL_SIZE: return "Unit Cell Size"; case V4L2_CID_CAMERA_ORIENTATION: return "Camera Orientation"; case V4L2_CID_CAMERA_SENSOR_ROTATION: return "Camera Sensor Rotation"; + case V4L2_CID_HDR_SENSOR_MODE: return "HDR Sensor Mode"; /* FM Radio Modulator controls */ /* Keep the order of the 'case's the same as in v4l2-controls.h! */ @@ -1370,6 +1371,7 @@ void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type, case V4L2_CID_STATELESS_H264_START_CODE: case V4L2_CID_CAMERA_ORIENTATION: case V4L2_CID_MPEG_VIDEO_INTRA_REFRESH_PERIOD_TYPE: + case V4L2_CID_HDR_SENSOR_MODE: *type = V4L2_CTRL_TYPE_MENU; break; case V4L2_CID_LINK_FREQ: diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h index b5e7d082b8ad..d27e255ed33b 100644 --- a/include/uapi/linux/v4l2-controls.h +++ b/include/uapi/linux/v4l2-controls.h @@ -1019,6 +1019,8 @@ enum v4l2_auto_focus_range { #define V4L2_CID_CAMERA_SENSOR_ROTATION (V4L2_CID_CAMERA_CLASS_BASE+35) +#define V4L2_CID_HDR_SENSOR_MODE (V4L2_CID_CAMERA_CLASS_BASE+36) + /* FM Modulator class control IDs */ #define V4L2_CID_FM_TX_CLASS_BASE (V4L2_CTRL_CLASS_FM_TX | 0x900) From 38d07a960f9bba5ba362036187f5a4eda385146c Mon Sep 17 00:00:00 2001 From: Benjamin Mugnier Date: Tue, 11 Oct 2022 14:30:28 +0200 Subject: [PATCH 15/47] media: dt-bindings: Add ST VGXY61 camera sensor binding Add device tree binding for the ST VGXY61 camera sensor, and update MAINTAINERS file. Signed-off-by: Benjamin Mugnier Signed-off-by: Sakari Ailus --- .../bindings/media/i2c/st,st-vgxy61.yaml | 113 ++++++++++++++++++ MAINTAINERS | 10 ++ 2 files changed, 123 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml diff --git a/Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml b/Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml new file mode 100644 index 000000000000..6597e1d0e65f --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml @@ -0,0 +1,113 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +# Copyright (c) 2022 STMicroelectronics SA. +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/st,st-vgxy61.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: STMicroelectronics VGxy61 HDR Global Shutter Sensor Family Device Tree Bindings + +maintainers: + - Benjamin Mugnier + - Sylvain Petinot + +description: |- + STMicroelectronics VGxy61 family has a CSI-2 output port. CSI-2 output is a + quad lanes 800Mbps per lane. + Supported formats are RAW8, RAW10, RAW12, RAW14 and RAW16. + Following part number are supported + - VG5661 and VG6661 are 1.6 Mpx (1464 x 1104) monochrome and color sensors. + Maximum frame rate is 75 fps. + - VG5761 and VG6761 are 2.3 Mpx (1944 x 1204) monochrome and color sensors. + Maximum frame rate is 60 fps. + +properties: + compatible: + const: st,st-vgxy61 + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + VCORE-supply: + description: + Sensor digital core supply. Must be 1.2 volts. + + VDDIO-supply: + description: + Sensor digital IO supply. Must be 1.8 volts. + + VANA-supply: + description: + Sensor analog supply. Must be 2.8 volts. + + reset-gpios: + description: + Reference to the GPIO connected to the reset pin, if any. + This is an active low signal to the vgxy61. + + st,strobe-gpios-polarity: + description: + Invert polarity of illuminator's lights strobe GPIOs. + These GPIOs directly drive the illuminator LEDs. + type: boolean + + port: + $ref: /schemas/graph.yaml#/$defs/port-base + additionalProperties: false + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + data-lanes: + description: + CSI lanes to use + items: + - const: 1 + - const: 2 + - const: 3 + - const: 4 + + remote-endpoint: true + + required: + - data-lanes + +required: + - compatible + - clocks + - VCORE-supply + - VDDIO-supply + - VANA-supply + - port + +additionalProperties: false + +examples: + - | + #include + i2c { + #address-cells = <1>; + #size-cells = <0>; + vgxy61: csi2tx@10 { + compatible = "st,st-vgxy61"; + reg = <0x10>; + clocks = <&clk_ext_camera>; + VCORE-supply = <&v1v2>; + VDDIO-supply = <&v1v8>; + VANA-supply = <&v2v8>; + reset-gpios = <&mfxgpio 18 GPIO_ACTIVE_LOW>; + port { + ep0: endpoint { + data-lanes = <1 2 3 4>; + remote-endpoint = <&mipi_csi2_out>; + }; + }; + }; + }; +... diff --git a/MAINTAINERS b/MAINTAINERS index 518326835156..1e4f85bd903e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19522,6 +19522,16 @@ S: Maintained F: Documentation/hwmon/stpddc60.rst F: drivers/hwmon/pmbus/stpddc60.c +ST VGXY61 DRIVER +M: Benjamin Mugnier +M: Sylvain Petinot +L: linux-media@vger.kernel.org +S: Maintained +T: git git://linuxtv.org/media_tree.git +F: Documentation/devicetree/bindings/media/i2c/st,st-vgxy61.yaml +F: Documentation/userspace-api/media/drivers/st-vgxy61.rst +F: drivers/media/i2c/st-vgxy61.c + ST VL53L0X ToF RANGER(I2C) IIO DRIVER M: Song Qiang L: linux-iio@vger.kernel.org From 2378be892b6ff53659a81b703c9c3fe4ead5a6d7 Mon Sep 17 00:00:00 2001 From: Benjamin Mugnier Date: Tue, 11 Oct 2022 14:30:29 +0200 Subject: [PATCH 16/47] media: Documentation: Add ST VGXY61 driver documentation Document V4L2_CID_HDR_MODE possible values for this sensor. Signed-off-by: Benjamin Mugnier Signed-off-by: Sakari Ailus --- .../userspace-api/media/drivers/st-vgxy61.rst | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 Documentation/userspace-api/media/drivers/st-vgxy61.rst diff --git a/Documentation/userspace-api/media/drivers/st-vgxy61.rst b/Documentation/userspace-api/media/drivers/st-vgxy61.rst new file mode 100644 index 000000000000..213b884dcfa6 --- /dev/null +++ b/Documentation/userspace-api/media/drivers/st-vgxy61.rst @@ -0,0 +1,23 @@ +.. SPDX-License-Identifier: GPL-2.0 + +ST VGXY61 camera sensor driver +============================== + +The ST VGXY61 driver implements the following controls: + +``V4L2_CID_HDR_SENSOR_MODE`` +------------------------------- + Change the sensor HDR mode. A HDR picture is obtained by merging two captures of the same scene + using two different exposure periods. + +.. flat-table:: + :header-rows: 0 + :stub-columns: 0 + :widths: 1 4 + + * - HDR linearize + - The merger outputs a long exposure capture as long as it is not saturated. + * - HDR substraction + - This involves subtracting the short exposure frame from the long exposure frame. + * - "No HDR" + - This mode is used for standard dynamic range (SDR) exposures. From 153e4ad44d605cbff3530013b393c01462c54cef Mon Sep 17 00:00:00 2001 From: Benjamin Mugnier Date: Tue, 11 Oct 2022 14:30:30 +0200 Subject: [PATCH 17/47] media: i2c: Add driver for ST VGXY61 camera sensor The VGXY61 has a quad lanes CSI-2 output port running at 800mbps per lane, and supports RAW8, RAW10, RAW12, RAW14 and RAW16 formats. The driver handles both sensor types: - VG5661 and VG6661: 1.6 Mpx (1464 x 1104) 75fps. - VG5761 and VG6761: 2.3 Mpx (1944 x 1204) 60 fps. The driver supports: - HDR linearize mode, HDR substraction mode, and no HDR - GPIOs LEDs strobing - Digital binning and analog subsampling - Horizontal and vertical flip - Manual exposure - Analog and digital gains - Test patterns Signed-off-by: Benjamin Mugnier [Sakari Ailus: remove() now returns void] Signed-off-by: Sakari Ailus --- drivers/media/i2c/Kconfig | 10 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/st-vgxy61.c | 1962 +++++++++++++++++++++++++++++++++ 3 files changed, 1973 insertions(+) create mode 100644 drivers/media/i2c/st-vgxy61.c diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 48c0368baaaa..07b240a990a8 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -795,6 +795,16 @@ config VIDEO_SR030PC30 help This driver supports SR030PC30 VGA camera from Siliconfile +config VIDEO_ST_VGXY61 + tristate "ST VGXY61 sensor support" + depends on OF && GPIOLIB && VIDEO_DEV && I2C + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + This is a Video4Linux2 sensor driver for the ST VGXY61 + camera sensor. + config VIDEO_VS6624 tristate "ST VS6624 sensor support" depends on VIDEO_DEV && I2C diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index c9959fd21b3e..41d11008464c 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -119,6 +119,7 @@ obj-$(CONFIG_VIDEO_SAA7185) += saa7185.o obj-$(CONFIG_VIDEO_SONY_BTF_MPX) += sony-btf-mpx.o obj-$(CONFIG_VIDEO_SR030PC30) += sr030pc30.o obj-$(CONFIG_VIDEO_ST_MIPID02) += st-mipid02.o +obj-$(CONFIG_VIDEO_ST_VGXY61) += st-vgxy61.o obj-$(CONFIG_VIDEO_TC358743) += tc358743.o obj-$(CONFIG_VIDEO_TDA1997X) += tda1997x.o obj-$(CONFIG_VIDEO_TDA7432) += tda7432.o diff --git a/drivers/media/i2c/st-vgxy61.c b/drivers/media/i2c/st-vgxy61.c new file mode 100644 index 000000000000..dfbf25338160 --- /dev/null +++ b/drivers/media/i2c/st-vgxy61.c @@ -0,0 +1,1962 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for VGXY61 global shutter sensor family driver + * + * Copyright (C) 2022 STMicroelectronics SA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define VGXY61_REG_8BIT(n) ((1 << 16) | (n)) +#define VGXY61_REG_16BIT(n) ((2 << 16) | (n)) +#define VGXY61_REG_32BIT(n) ((4 << 16) | (n)) +#define VGXY61_REG_SIZE_SHIFT 16 +#define VGXY61_REG_ADDR_MASK 0xffff + +#define VGXY61_REG_MODEL_ID VGXY61_REG_16BIT(0x0000) +#define VG5661_MODEL_ID 0x5661 +#define VG5761_MODEL_ID 0x5761 +#define VGXY61_REG_REVISION VGXY61_REG_16BIT(0x0002) +#define VGXY61_REG_FWPATCH_REVISION VGXY61_REG_16BIT(0x0014) +#define VGXY61_REG_FWPATCH_START_ADDR VGXY61_REG_8BIT(0x2000) +#define VGXY61_REG_SYSTEM_FSM VGXY61_REG_8BIT(0x0020) +#define VGXY61_SYSTEM_FSM_SW_STBY 0x03 +#define VGXY61_SYSTEM_FSM_STREAMING 0x04 +#define VGXY61_REG_NVM VGXY61_REG_8BIT(0x0023) +#define VGXY61_NVM_OK 0x04 +#define VGXY61_REG_STBY VGXY61_REG_8BIT(0x0201) +#define VGXY61_STBY_NO_REQ 0 +#define VGXY61_STBY_REQ_TMP_READ BIT(2) +#define VGXY61_REG_STREAMING VGXY61_REG_8BIT(0x0202) +#define VGXY61_STREAMING_NO_REQ 0 +#define VGXY61_STREAMING_REQ_STOP BIT(0) +#define VGXY61_STREAMING_REQ_START BIT(1) +#define VGXY61_REG_EXT_CLOCK VGXY61_REG_32BIT(0x0220) +#define VGXY61_REG_CLK_PLL_PREDIV VGXY61_REG_8BIT(0x0224) +#define VGXY61_REG_CLK_SYS_PLL_MULT VGXY61_REG_8BIT(0x0225) +#define VGXY61_REG_GPIO_0_CTRL VGXY61_REG_8BIT(0x0236) +#define VGXY61_REG_GPIO_1_CTRL VGXY61_REG_8BIT(0x0237) +#define VGXY61_REG_GPIO_2_CTRL VGXY61_REG_8BIT(0x0238) +#define VGXY61_REG_GPIO_3_CTRL VGXY61_REG_8BIT(0x0239) +#define VGXY61_REG_SIGNALS_POLARITY_CTRL VGXY61_REG_8BIT(0x023b) +#define VGXY61_REG_LINE_LENGTH VGXY61_REG_16BIT(0x0300) +#define VGXY61_REG_ORIENTATION VGXY61_REG_8BIT(0x0302) +#define VGXY61_REG_VT_CTRL VGXY61_REG_8BIT(0x0304) +#define VGXY61_REG_FORMAT_CTRL VGXY61_REG_8BIT(0x0305) +#define VGXY61_REG_OIF_CTRL VGXY61_REG_16BIT(0x0306) +#define VGXY61_REG_OIF_ROI0_CTRL VGXY61_REG_8BIT(0x030a) +#define VGXY61_REG_ROI0_START_H VGXY61_REG_16BIT(0x0400) +#define VGXY61_REG_ROI0_START_V VGXY61_REG_16BIT(0x0402) +#define VGXY61_REG_ROI0_END_H VGXY61_REG_16BIT(0x0404) +#define VGXY61_REG_ROI0_END_V VGXY61_REG_16BIT(0x0406) +#define VGXY61_REG_PATGEN_CTRL VGXY61_REG_32BIT(0x0440) +#define VGXY61_PATGEN_LONG_ENABLE BIT(16) +#define VGXY61_PATGEN_SHORT_ENABLE BIT(0) +#define VGXY61_PATGEN_LONG_TYPE_SHIFT 18 +#define VGXY61_PATGEN_SHORT_TYPE_SHIFT 4 +#define VGXY61_REG_FRAME_CONTENT_CTRL VGXY61_REG_8BIT(0x0478) +#define VGXY61_REG_COARSE_EXPOSURE_LONG VGXY61_REG_16BIT(0x0500) +#define VGXY61_REG_COARSE_EXPOSURE_SHORT VGXY61_REG_16BIT(0x0504) +#define VGXY61_REG_ANALOG_GAIN VGXY61_REG_8BIT(0x0508) +#define VGXY61_REG_DIGITAL_GAIN_LONG VGXY61_REG_16BIT(0x050a) +#define VGXY61_REG_DIGITAL_GAIN_SHORT VGXY61_REG_16BIT(0x0512) +#define VGXY61_REG_FRAME_LENGTH VGXY61_REG_16BIT(0x051a) +#define VGXY61_REG_SIGNALS_CTRL VGXY61_REG_16BIT(0x0522) +#define VGXY61_SIGNALS_GPIO_ID_SHIFT 4 +#define VGXY61_REG_READOUT_CTRL VGXY61_REG_8BIT(0x0530) +#define VGXY61_REG_HDR_CTRL VGXY61_REG_8BIT(0x0532) +#define VGXY61_REG_PATGEN_LONG_DATA_GR VGXY61_REG_16BIT(0x092c) +#define VGXY61_REG_PATGEN_LONG_DATA_R VGXY61_REG_16BIT(0x092e) +#define VGXY61_REG_PATGEN_LONG_DATA_B VGXY61_REG_16BIT(0x0930) +#define VGXY61_REG_PATGEN_LONG_DATA_GB VGXY61_REG_16BIT(0x0932) +#define VGXY61_REG_PATGEN_SHORT_DATA_GR VGXY61_REG_16BIT(0x0950) +#define VGXY61_REG_PATGEN_SHORT_DATA_R VGXY61_REG_16BIT(0x0952) +#define VGXY61_REG_PATGEN_SHORT_DATA_B VGXY61_REG_16BIT(0x0954) +#define VGXY61_REG_PATGEN_SHORT_DATA_GB VGXY61_REG_16BIT(0x0956) +#define VGXY61_REG_BYPASS_CTRL VGXY61_REG_8BIT(0x0a60) + +#define VGX661_WIDTH 1464 +#define VGX661_HEIGHT 1104 +#define VGX761_WIDTH 1944 +#define VGX761_HEIGHT 1204 +#define VGX661_DEFAULT_MODE 1 +#define VGX761_DEFAULT_MODE 1 +#define VGX661_SHORT_ROT_TERM 93 +#define VGX761_SHORT_ROT_TERM 90 +#define VGXY61_EXPOS_ROT_TERM 66 +#define VGXY61_WRITE_MULTIPLE_CHUNK_MAX 16 +#define VGXY61_NB_GPIOS 4 +#define VGXY61_NB_POLARITIES 5 +#define VGXY61_FRAME_LENGTH_DEF 1313 +#define VGXY61_MIN_FRAME_LENGTH 1288 +#define VGXY61_MIN_EXPOSURE 10 +#define VGXY61_HDR_LINEAR_RATIO 10 +#define VGXY61_TIMEOUT_MS 500 +#define VGXY61_MEDIA_BUS_FMT_DEF MEDIA_BUS_FMT_Y8_1X8 + +#define VGXY61_FWPATCH_REVISION_MAJOR 2 +#define VGXY61_FWPATCH_REVISION_MINOR 0 +#define VGXY61_FWPATCH_REVISION_MICRO 5 + +static const u8 patch_array[] = { + 0xbf, 0x00, 0x05, 0x20, 0x06, 0x01, 0xe0, 0xe0, 0x04, 0x80, 0xe6, 0x45, + 0xed, 0x6f, 0xfe, 0xff, 0x14, 0x80, 0x1f, 0x84, 0x10, 0x42, 0x05, 0x7c, + 0x01, 0xc4, 0x1e, 0x80, 0xb6, 0x42, 0x00, 0xe0, 0x1e, 0x82, 0x1e, 0xc0, + 0x93, 0xdd, 0xc3, 0xc1, 0x0c, 0x04, 0x00, 0xfa, 0x86, 0x0d, 0x70, 0xe1, + 0x04, 0x98, 0x15, 0x00, 0x28, 0xe0, 0x14, 0x02, 0x08, 0xfc, 0x15, 0x40, + 0x28, 0xe0, 0x98, 0x58, 0xe0, 0xef, 0x04, 0x98, 0x0e, 0x04, 0x00, 0xf0, + 0x15, 0x00, 0x28, 0xe0, 0x19, 0xc8, 0x15, 0x40, 0x28, 0xe0, 0xc6, 0x41, + 0xfc, 0xe0, 0x14, 0x80, 0x1f, 0x84, 0x14, 0x02, 0xa0, 0xfc, 0x1e, 0x80, + 0x14, 0x80, 0x14, 0x02, 0x80, 0xfb, 0x14, 0x02, 0xe0, 0xfc, 0x1e, 0x80, + 0x14, 0xc0, 0x1f, 0x84, 0x14, 0x02, 0xa4, 0xfc, 0x1e, 0xc0, 0x14, 0xc0, + 0x14, 0x02, 0x80, 0xfb, 0x14, 0x02, 0xe4, 0xfc, 0x1e, 0xc0, 0x0c, 0x0c, + 0x00, 0xf2, 0x93, 0xdd, 0x86, 0x00, 0xf8, 0xe0, 0x04, 0x80, 0xc6, 0x03, + 0x70, 0xe1, 0x0e, 0x84, 0x93, 0xdd, 0xc3, 0xc1, 0x0c, 0x04, 0x00, 0xfa, + 0x6b, 0x80, 0x06, 0x40, 0x6c, 0xe1, 0x04, 0x80, 0x09, 0x00, 0xe0, 0xe0, + 0x0b, 0xa1, 0x95, 0x84, 0x05, 0x0c, 0x1c, 0xe0, 0x86, 0x02, 0xf9, 0x60, + 0xe0, 0xcf, 0x78, 0x6e, 0x80, 0xef, 0x25, 0x0c, 0x18, 0xe0, 0x05, 0x4c, + 0x1c, 0xe0, 0x86, 0x02, 0xf9, 0x60, 0xe0, 0xcf, 0x0b, 0x84, 0xd8, 0x6d, + 0x80, 0xef, 0x05, 0x4c, 0x18, 0xe0, 0x04, 0xd8, 0x0b, 0xa5, 0x95, 0x84, + 0x05, 0x0c, 0x2c, 0xe0, 0x06, 0x02, 0x01, 0x60, 0xe0, 0xce, 0x18, 0x6d, + 0x80, 0xef, 0x25, 0x0c, 0x30, 0xe0, 0x05, 0x4c, 0x2c, 0xe0, 0x06, 0x02, + 0x01, 0x60, 0xe0, 0xce, 0x0b, 0x84, 0x78, 0x6c, 0x80, 0xef, 0x05, 0x4c, + 0x30, 0xe0, 0x0c, 0x0c, 0x00, 0xf2, 0x93, 0xdd, 0x46, 0x01, 0x70, 0xe1, + 0x08, 0x80, 0x0b, 0xa1, 0x08, 0x5c, 0x00, 0xda, 0x06, 0x01, 0x68, 0xe1, + 0x04, 0x80, 0x4a, 0x40, 0x84, 0xe0, 0x08, 0x5c, 0x00, 0x9a, 0x06, 0x01, + 0xe0, 0xe0, 0x04, 0x80, 0x15, 0x00, 0x60, 0xe0, 0x19, 0xc4, 0x15, 0x40, + 0x60, 0xe0, 0x15, 0x00, 0x78, 0xe0, 0x19, 0xc4, 0x15, 0x40, 0x78, 0xe0, + 0x93, 0xdd, 0xc3, 0xc1, 0x46, 0x01, 0x70, 0xe1, 0x08, 0x80, 0x0b, 0xa1, + 0x08, 0x5c, 0x00, 0xda, 0x06, 0x01, 0x68, 0xe1, 0x04, 0x80, 0x4a, 0x40, + 0x84, 0xe0, 0x08, 0x5c, 0x00, 0x9a, 0x06, 0x01, 0xe0, 0xe0, 0x14, 0x80, + 0x25, 0x02, 0x54, 0xe0, 0x29, 0xc4, 0x25, 0x42, 0x54, 0xe0, 0x24, 0x80, + 0x35, 0x04, 0x6c, 0xe0, 0x39, 0xc4, 0x35, 0x44, 0x6c, 0xe0, 0x25, 0x02, + 0x64, 0xe0, 0x29, 0xc4, 0x25, 0x42, 0x64, 0xe0, 0x04, 0x80, 0x15, 0x00, + 0x7c, 0xe0, 0x19, 0xc4, 0x15, 0x40, 0x7c, 0xe0, 0x93, 0xdd, 0xc3, 0xc1, + 0x4c, 0x04, 0x7c, 0xfa, 0x86, 0x40, 0x98, 0xe0, 0x14, 0x80, 0x1b, 0xa1, + 0x06, 0x00, 0x00, 0xc0, 0x08, 0x42, 0x38, 0xdc, 0x08, 0x64, 0xa0, 0xef, + 0x86, 0x42, 0x3c, 0xe0, 0x68, 0x49, 0x80, 0xef, 0x6b, 0x80, 0x78, 0x53, + 0xc8, 0xef, 0xc6, 0x54, 0x6c, 0xe1, 0x7b, 0x80, 0xb5, 0x14, 0x0c, 0xf8, + 0x05, 0x14, 0x14, 0xf8, 0x1a, 0xac, 0x8a, 0x80, 0x0b, 0x90, 0x38, 0x55, + 0x80, 0xef, 0x1a, 0xae, 0x17, 0xc2, 0x03, 0x82, 0x88, 0x65, 0x80, 0xef, + 0x1b, 0x80, 0x0b, 0x8e, 0x68, 0x65, 0x80, 0xef, 0x9b, 0x80, 0x0b, 0x8c, + 0x08, 0x65, 0x80, 0xef, 0x6b, 0x80, 0x0b, 0x92, 0x1b, 0x8c, 0x98, 0x64, + 0x80, 0xef, 0x1a, 0xec, 0x9b, 0x80, 0x0b, 0x90, 0x95, 0x54, 0x10, 0xe0, + 0xa8, 0x53, 0x80, 0xef, 0x1a, 0xee, 0x17, 0xc2, 0x03, 0x82, 0xf8, 0x63, + 0x80, 0xef, 0x1b, 0x80, 0x0b, 0x8e, 0xd8, 0x63, 0x80, 0xef, 0x1b, 0x8c, + 0x68, 0x63, 0x80, 0xef, 0x6b, 0x80, 0x0b, 0x92, 0x65, 0x54, 0x14, 0xe0, + 0x08, 0x65, 0x84, 0xef, 0x68, 0x63, 0x80, 0xef, 0x7b, 0x80, 0x0b, 0x8c, + 0xa8, 0x64, 0x84, 0xef, 0x08, 0x63, 0x80, 0xef, 0x14, 0xe8, 0x46, 0x44, + 0x94, 0xe1, 0x24, 0x88, 0x4a, 0x4e, 0x04, 0xe0, 0x14, 0xea, 0x1a, 0x04, + 0x08, 0xe0, 0x0a, 0x40, 0x84, 0xed, 0x0c, 0x04, 0x00, 0xe2, 0x4a, 0x40, + 0x04, 0xe0, 0x19, 0x16, 0xc0, 0xe0, 0x0a, 0x40, 0x84, 0xed, 0x21, 0x54, + 0x60, 0xe0, 0x0c, 0x04, 0x00, 0xe2, 0x1b, 0xa5, 0x0e, 0xea, 0x01, 0x89, + 0x21, 0x54, 0x64, 0xe0, 0x7e, 0xe8, 0x65, 0x82, 0x1b, 0xa7, 0x26, 0x00, + 0x00, 0x80, 0xa5, 0x82, 0x1b, 0xa9, 0x65, 0x82, 0x1b, 0xa3, 0x01, 0x85, + 0x16, 0x00, 0x00, 0xc0, 0x01, 0x54, 0x04, 0xf8, 0x06, 0xaa, 0x01, 0x83, + 0x06, 0xa8, 0x65, 0x81, 0x06, 0xa8, 0x01, 0x54, 0x04, 0xf8, 0x01, 0x83, + 0x06, 0xaa, 0x09, 0x14, 0x18, 0xf8, 0x0b, 0xa1, 0x05, 0x84, 0xc6, 0x42, + 0xd4, 0xe0, 0x14, 0x84, 0x01, 0x83, 0x01, 0x54, 0x60, 0xe0, 0x01, 0x54, + 0x64, 0xe0, 0x0b, 0x02, 0x90, 0xe0, 0x10, 0x02, 0x90, 0xe5, 0x01, 0x54, + 0x88, 0xe0, 0xb5, 0x81, 0xc6, 0x40, 0xd4, 0xe0, 0x14, 0x80, 0x0b, 0x02, + 0xe0, 0xe4, 0x10, 0x02, 0x31, 0x66, 0x02, 0xc0, 0x01, 0x54, 0x88, 0xe0, + 0x1a, 0x84, 0x29, 0x14, 0x10, 0xe0, 0x1c, 0xaa, 0x2b, 0xa1, 0xf5, 0x82, + 0x25, 0x14, 0x10, 0xf8, 0x2b, 0x04, 0xa8, 0xe0, 0x20, 0x44, 0x0d, 0x70, + 0x03, 0xc0, 0x2b, 0xa1, 0x04, 0x00, 0x80, 0x9a, 0x02, 0x40, 0x84, 0x90, + 0x03, 0x54, 0x04, 0x80, 0x4c, 0x0c, 0x7c, 0xf2, 0x93, 0xdd, 0x00, 0x00, + 0x02, 0xa9, 0x00, 0x00, 0x64, 0x4a, 0x40, 0x00, 0x08, 0x2d, 0x58, 0xe0, + 0xa8, 0x98, 0x40, 0x00, 0x28, 0x07, 0x34, 0xe0, 0x05, 0xb9, 0x00, 0x00, + 0x28, 0x00, 0x41, 0x05, 0x88, 0x00, 0x41, 0x3c, 0x98, 0x00, 0x41, 0x52, + 0x04, 0x01, 0x41, 0x79, 0x3c, 0x01, 0x41, 0x6a, 0x3d, 0xfe, 0x00, 0x00, +}; + +static const char * const vgxy61_test_pattern_menu[] = { + "Disabled", + "Solid", + "Colorbar", + "Gradbar", + "Hgrey", + "Vgrey", + "Dgrey", + "PN28", +}; + +static const char * const vgxy61_hdr_mode_menu[] = { + "HDR linearize", + "HDR substraction", + "No HDR", +}; + +static const char * const vgxy61_supply_name[] = { + "VCORE", + "VDDIO", + "VANA", +}; + +static const s64 link_freq[] = { + /* + * MIPI output freq is 804Mhz / 2, as it uses both rising edge and + * falling edges to send data + */ + 402000000ULL +}; + +enum vgxy61_bin_mode { + VGXY61_BIN_MODE_NORMAL, + VGXY61_BIN_MODE_DIGITAL_X2, + VGXY61_BIN_MODE_DIGITAL_X4, +}; + +enum vgxy61_hdr_mode { + VGXY61_HDR_LINEAR, + VGXY61_HDR_SUB, + VGXY61_NO_HDR, +}; + +enum vgxy61_strobe_mode { + VGXY61_STROBE_DISABLED, + VGXY61_STROBE_LONG, + VGXY61_STROBE_ENABLED, +}; + +struct vgxy61_mode_info { + u32 width; + u32 height; + enum vgxy61_bin_mode bin_mode; + struct v4l2_rect crop; +}; + +struct vgxy61_fmt_desc { + u32 code; + u8 bpp; + u8 data_type; +}; + +static const struct vgxy61_fmt_desc vgxy61_supported_codes[] = { + { + .code = MEDIA_BUS_FMT_Y8_1X8, + .bpp = 8, + .data_type = MIPI_CSI2_DT_RAW8, + }, + { + .code = MEDIA_BUS_FMT_Y10_1X10, + .bpp = 10, + .data_type = MIPI_CSI2_DT_RAW10, + }, + { + .code = MEDIA_BUS_FMT_Y12_1X12, + .bpp = 12, + .data_type = MIPI_CSI2_DT_RAW12, + }, + { + .code = MEDIA_BUS_FMT_Y14_1X14, + .bpp = 14, + .data_type = MIPI_CSI2_DT_RAW14, + }, + { + .code = MEDIA_BUS_FMT_Y16_1X16, + .bpp = 16, + .data_type = MIPI_CSI2_DT_RAW16, + }, +}; + +static const struct vgxy61_mode_info vgx661_mode_data[] = { + { + .width = VGX661_WIDTH, + .height = VGX661_HEIGHT, + .bin_mode = VGXY61_BIN_MODE_NORMAL, + .crop = { + .left = 0, + .top = 0, + .width = VGX661_WIDTH, + .height = VGX661_HEIGHT, + }, + }, + { + .width = 1280, + .height = 720, + .bin_mode = VGXY61_BIN_MODE_NORMAL, + .crop = { + .left = 92, + .top = 192, + .width = 1280, + .height = 720, + }, + }, + { + .width = 640, + .height = 480, + .bin_mode = VGXY61_BIN_MODE_DIGITAL_X2, + .crop = { + .left = 92, + .top = 72, + .width = 1280, + .height = 960, + }, + }, + { + .width = 320, + .height = 240, + .bin_mode = VGXY61_BIN_MODE_DIGITAL_X4, + .crop = { + .left = 92, + .top = 72, + .width = 1280, + .height = 960, + }, + }, +}; + +static const struct vgxy61_mode_info vgx761_mode_data[] = { + { + .width = VGX761_WIDTH, + .height = VGX761_HEIGHT, + .bin_mode = VGXY61_BIN_MODE_NORMAL, + .crop = { + .left = 0, + .top = 0, + .width = VGX761_WIDTH, + .height = VGX761_HEIGHT, + }, + }, + { + .width = 1920, + .height = 1080, + .bin_mode = VGXY61_BIN_MODE_NORMAL, + .crop = { + .left = 12, + .top = 62, + .width = 1920, + .height = 1080, + }, + }, + { + .width = 1280, + .height = 720, + .bin_mode = VGXY61_BIN_MODE_NORMAL, + .crop = { + .left = 332, + .top = 242, + .width = 1280, + .height = 720, + }, + }, + { + .width = 640, + .height = 480, + .bin_mode = VGXY61_BIN_MODE_DIGITAL_X2, + .crop = { + .left = 332, + .top = 122, + .width = 1280, + .height = 960, + }, + }, + { + .width = 320, + .height = 240, + .bin_mode = VGXY61_BIN_MODE_DIGITAL_X4, + .crop = { + .left = 332, + .top = 122, + .width = 1280, + .height = 960, + }, + }, +}; + +struct vgxy61_dev { + struct i2c_client *i2c_client; + struct v4l2_subdev sd; + struct media_pad pad; + struct regulator_bulk_data supplies[ARRAY_SIZE(vgxy61_supply_name)]; + struct gpio_desc *reset_gpio; + struct clk *xclk; + u32 clk_freq; + u16 id; + u16 sensor_width; + u16 sensor_height; + u16 oif_ctrl; + unsigned int nb_of_lane; + u32 data_rate_in_mbps; + u32 pclk; + u16 line_length; + u16 rot_term; + bool gpios_polarity; + /* Lock to protect all members below */ + struct mutex lock; + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_ctrl *pixel_rate_ctrl; + struct v4l2_ctrl *expo_ctrl; + struct v4l2_ctrl *vblank_ctrl; + struct v4l2_ctrl *vflip_ctrl; + struct v4l2_ctrl *hflip_ctrl; + bool streaming; + struct v4l2_mbus_framefmt fmt; + const struct vgxy61_mode_info *sensor_modes; + unsigned int sensor_modes_nb; + const struct vgxy61_mode_info *default_mode; + const struct vgxy61_mode_info *current_mode; + bool hflip; + bool vflip; + enum vgxy61_hdr_mode hdr; + u16 expo_long; + u16 expo_short; + u16 expo_max; + u16 expo_min; + u16 vblank; + u16 vblank_min; + u16 frame_length; + u16 digital_gain; + u8 analog_gain; + enum vgxy61_strobe_mode strobe_mode; + u32 pattern; +}; + +static u8 get_bpp_by_code(__u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(vgxy61_supported_codes); i++) { + if (vgxy61_supported_codes[i].code == code) + return vgxy61_supported_codes[i].bpp; + } + /* Should never happen */ + WARN(1, "Unsupported code %d. default to 8 bpp", code); + return 8; +} + +static u8 get_data_type_by_code(__u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(vgxy61_supported_codes); i++) { + if (vgxy61_supported_codes[i].code == code) + return vgxy61_supported_codes[i].data_type; + } + /* Should never happen */ + WARN(1, "Unsupported code %d. default to MIPI_CSI2_DT_RAW8 data type", + code); + return MIPI_CSI2_DT_RAW8; +} + +static void compute_pll_parameters_by_freq(u32 freq, u8 *prediv, u8 *mult) +{ + const unsigned int predivs[] = {1, 2, 4}; + unsigned int i; + + /* + * Freq range is [6Mhz-27Mhz] already checked. + * Output of divider should be in [6Mhz-12Mhz[. + */ + for (i = 0; i < ARRAY_SIZE(predivs); i++) { + *prediv = predivs[i]; + if (freq / *prediv < 12 * HZ_PER_MHZ) + break; + } + WARN_ON(i == ARRAY_SIZE(predivs)); + + /* + * Target freq is 804Mhz. Don't change this as it will impact image + * quality. + */ + *mult = ((804 * HZ_PER_MHZ) * (*prediv) + freq / 2) / freq; +} + +static s32 get_pixel_rate(struct vgxy61_dev *sensor) +{ + return div64_u64((u64)sensor->data_rate_in_mbps * sensor->nb_of_lane, + get_bpp_by_code(sensor->fmt.code)); +} + +static inline struct vgxy61_dev *to_vgxy61_dev(struct v4l2_subdev *sd) +{ + return container_of(sd, struct vgxy61_dev, sd); +} + +static inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct vgxy61_dev, + ctrl_handler)->sd; +} + +static unsigned int get_chunk_size(struct vgxy61_dev *sensor) +{ + struct i2c_adapter *adapter = sensor->i2c_client->adapter; + int max_write_len = VGXY61_WRITE_MULTIPLE_CHUNK_MAX; + + if (adapter->quirks && adapter->quirks->max_write_len) + max_write_len = adapter->quirks->max_write_len - 2; + + max_write_len = min(max_write_len, VGXY61_WRITE_MULTIPLE_CHUNK_MAX); + + return max(max_write_len, 1); +} + +static int vgxy61_read_multiple(struct vgxy61_dev *sensor, u32 reg, + unsigned int len) +{ + struct i2c_client *client = sensor->i2c_client; + struct i2c_msg msg[2]; + u8 buf[2]; + u8 val[sizeof(u32)] = {0}; + int ret; + + if (len > sizeof(u32)) + return -EINVAL; + buf[0] = reg >> 8; + buf[1] = reg & 0xff; + + msg[0].addr = client->addr; + msg[0].flags = client->flags; + msg[0].buf = buf; + msg[0].len = sizeof(buf); + + msg[1].addr = client->addr; + msg[1].flags = client->flags | I2C_M_RD; + msg[1].buf = val; + msg[1].len = len; + + ret = i2c_transfer(client->adapter, msg, 2); + if (ret < 0) { + dev_dbg(&client->dev, "%s: %x i2c_transfer, reg: %x => %d\n", + __func__, client->addr, reg, ret); + return ret; + } + + return get_unaligned_le32(val); +} + +static inline int vgxy61_read_reg(struct vgxy61_dev *sensor, u32 reg) +{ + return vgxy61_read_multiple(sensor, reg & VGXY61_REG_ADDR_MASK, + (reg >> VGXY61_REG_SIZE_SHIFT) & 7); +} + +static int vgxy61_write_multiple(struct vgxy61_dev *sensor, u32 reg, + const u8 *data, unsigned int len, int *err) +{ + struct i2c_client *client = sensor->i2c_client; + struct i2c_msg msg; + u8 buf[VGXY61_WRITE_MULTIPLE_CHUNK_MAX + 2]; + unsigned int i; + int ret; + + if (err && *err) + return *err; + + if (len > VGXY61_WRITE_MULTIPLE_CHUNK_MAX) + return -EINVAL; + buf[0] = reg >> 8; + buf[1] = reg & 0xff; + for (i = 0; i < len; i++) + buf[i + 2] = data[i]; + + msg.addr = client->addr; + msg.flags = client->flags; + msg.buf = buf; + msg.len = len + 2; + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret < 0) { + dev_dbg(&client->dev, "%s: i2c_transfer, reg: %x => %d\n", + __func__, reg, ret); + if (err) + *err = ret; + return ret; + } + + return 0; +} + +static int vgxy61_write_array(struct vgxy61_dev *sensor, u32 reg, + unsigned int nb, const u8 *array) +{ + const unsigned int chunk_size = get_chunk_size(sensor); + int ret; + unsigned int sz; + + while (nb) { + sz = min(nb, chunk_size); + ret = vgxy61_write_multiple(sensor, reg, array, sz, NULL); + if (ret < 0) + return ret; + nb -= sz; + reg += sz; + array += sz; + } + + return 0; +} + +static inline int vgxy61_write_reg(struct vgxy61_dev *sensor, u32 reg, u32 val, + int *err) +{ + return vgxy61_write_multiple(sensor, reg & VGXY61_REG_ADDR_MASK, + (u8 *)&val, + (reg >> VGXY61_REG_SIZE_SHIFT) & 7, err); +} + +static int vgxy61_poll_reg(struct vgxy61_dev *sensor, u32 reg, u8 poll_val, + unsigned int timeout_ms) +{ + const unsigned int loop_delay_ms = 10; + int ret; + + return read_poll_timeout(vgxy61_read_reg, ret, + ((ret < 0) || (ret == poll_val)), + loop_delay_ms * 1000, timeout_ms * 1000, + false, sensor, reg); +} + +static int vgxy61_wait_state(struct vgxy61_dev *sensor, int state, + unsigned int timeout_ms) +{ + return vgxy61_poll_reg(sensor, VGXY61_REG_SYSTEM_FSM, state, + timeout_ms); +} + +static int vgxy61_check_bw(struct vgxy61_dev *sensor) +{ + /* + * Simplification of time needed to send short packets and for the MIPI + * to add transition times (EoT, LPS, and SoT packet delimiters) needed + * by the protocol to go in low power between 2 packets of data. This + * is a mipi IP constant for the sensor. + */ + const unsigned int mipi_margin = 1056; + unsigned int binning_scale = sensor->current_mode->crop.height / + sensor->current_mode->height; + u8 bpp = get_bpp_by_code(sensor->fmt.code); + unsigned int max_bit_per_line; + unsigned int bit_per_line; + u64 line_rate; + + line_rate = sensor->nb_of_lane * (u64)sensor->data_rate_in_mbps * + sensor->line_length; + max_bit_per_line = div64_u64(line_rate, sensor->pclk) - mipi_margin; + bit_per_line = (bpp * sensor->current_mode->width) / binning_scale; + + return bit_per_line > max_bit_per_line ? -EINVAL : 0; +} + +static int vgxy61_apply_exposure(struct vgxy61_dev *sensor) +{ + int ret = 0; + + /* We first set expo to zero to avoid forbidden parameters couple */ + vgxy61_write_reg(sensor, VGXY61_REG_COARSE_EXPOSURE_SHORT, 0, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_COARSE_EXPOSURE_LONG, + sensor->expo_long, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_COARSE_EXPOSURE_SHORT, + sensor->expo_short, &ret); + + return ret; +} + +static int vgxy61_get_regulators(struct vgxy61_dev *sensor) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(vgxy61_supply_name); i++) + sensor->supplies[i].supply = vgxy61_supply_name[i]; + + return devm_regulator_bulk_get(&sensor->i2c_client->dev, + ARRAY_SIZE(vgxy61_supply_name), + sensor->supplies); +} + +static int vgxy61_apply_reset(struct vgxy61_dev *sensor) +{ + gpiod_set_value_cansleep(sensor->reset_gpio, 0); + usleep_range(5000, 10000); + gpiod_set_value_cansleep(sensor->reset_gpio, 1); + usleep_range(5000, 10000); + gpiod_set_value_cansleep(sensor->reset_gpio, 0); + usleep_range(40000, 100000); + return vgxy61_wait_state(sensor, VGXY61_SYSTEM_FSM_SW_STBY, + VGXY61_TIMEOUT_MS); +} + +static void vgxy61_fill_framefmt(struct vgxy61_dev *sensor, + const struct vgxy61_mode_info *mode, + struct v4l2_mbus_framefmt *fmt, u32 code) +{ + fmt->code = code; + fmt->width = mode->width; + fmt->height = mode->height; + fmt->colorspace = V4L2_COLORSPACE_RAW; + fmt->field = V4L2_FIELD_NONE; + fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + fmt->quantization = V4L2_QUANTIZATION_DEFAULT; + fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT; +} + +static int vgxy61_try_fmt_internal(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *fmt, + const struct vgxy61_mode_info **new_mode) +{ + struct vgxy61_dev *sensor = to_vgxy61_dev(sd); + const struct vgxy61_mode_info *mode = sensor->sensor_modes; + unsigned int index; + + for (index = 0; index < ARRAY_SIZE(vgxy61_supported_codes); index++) { + if (vgxy61_supported_codes[index].code == fmt->code) + break; + } + if (index == ARRAY_SIZE(vgxy61_supported_codes)) + index = 0; + + mode = v4l2_find_nearest_size(sensor->sensor_modes, + sensor->sensor_modes_nb, width, height, + fmt->width, fmt->height); + if (new_mode) + *new_mode = mode; + + vgxy61_fill_framefmt(sensor, mode, fmt, + vgxy61_supported_codes[index].code); + + return 0; +} + +static int vgxy61_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct vgxy61_dev *sensor = to_vgxy61_dev(sd); + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + sel->r = sensor->current_mode->crop; + return 0; + case V4L2_SEL_TGT_NATIVE_SIZE: + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = sensor->sensor_width; + sel->r.height = sensor->sensor_height; + return 0; + } + + return -EINVAL; +} + +static int vgxy61_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index >= ARRAY_SIZE(vgxy61_supported_codes)) + return -EINVAL; + + code->code = vgxy61_supported_codes[code->index].code; + + return 0; +} + +static int vgxy61_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct vgxy61_dev *sensor = to_vgxy61_dev(sd); + struct v4l2_mbus_framefmt *fmt; + + mutex_lock(&sensor->lock); + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) + fmt = v4l2_subdev_get_try_format(&sensor->sd, sd_state, + format->pad); + else + fmt = &sensor->fmt; + + format->format = *fmt; + + mutex_unlock(&sensor->lock); + + return 0; +} + +static u16 vgxy61_get_vblank_min(struct vgxy61_dev *sensor, + enum vgxy61_hdr_mode hdr) +{ + u16 min_vblank = VGXY61_MIN_FRAME_LENGTH - + sensor->current_mode->crop.height; + /* Ensure the first rule of thumb can't be negative */ + u16 min_vblank_hdr = VGXY61_MIN_EXPOSURE + sensor->rot_term + 1; + + if (hdr != VGXY61_NO_HDR) + return max(min_vblank, min_vblank_hdr); + return min_vblank; +} + +static int vgxy61_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct vgxy61_dev *sensor = to_vgxy61_dev(sd); + + if (fse->index >= sensor->sensor_modes_nb) + return -EINVAL; + + fse->min_width = sensor->sensor_modes[fse->index].width; + fse->max_width = fse->min_width; + fse->min_height = sensor->sensor_modes[fse->index].height; + fse->max_height = fse->min_height; + + return 0; +} + +static int vgxy61_update_analog_gain(struct vgxy61_dev *sensor, u32 target) +{ + sensor->analog_gain = target; + + if (sensor->streaming) + return vgxy61_write_reg(sensor, VGXY61_REG_ANALOG_GAIN, target, + NULL); + return 0; +} + +static int vgxy61_apply_digital_gain(struct vgxy61_dev *sensor, + u32 digital_gain) +{ + int ret = 0; + + /* + * For a monochrome version, configuring DIGITAL_GAIN_LONG_CH0 and + * DIGITAL_GAIN_SHORT_CH0 is enough to configure the gain of all + * four sub pixels. + */ + vgxy61_write_reg(sensor, VGXY61_REG_DIGITAL_GAIN_LONG, digital_gain, + &ret); + vgxy61_write_reg(sensor, VGXY61_REG_DIGITAL_GAIN_SHORT, digital_gain, + &ret); + + return ret; +} + +static int vgxy61_update_digital_gain(struct vgxy61_dev *sensor, u32 target) +{ + sensor->digital_gain = target; + + if (sensor->streaming) + return vgxy61_apply_digital_gain(sensor, sensor->digital_gain); + return 0; +} + +static int vgxy61_apply_patgen(struct vgxy61_dev *sensor, u32 index) +{ + static const u8 index2val[] = { + 0x0, 0x1, 0x2, 0x3, 0x10, 0x11, 0x12, 0x13 + }; + u32 pattern = index2val[index]; + u32 reg = (pattern << VGXY61_PATGEN_LONG_TYPE_SHIFT) | + (pattern << VGXY61_PATGEN_SHORT_TYPE_SHIFT); + + if (pattern) + reg |= VGXY61_PATGEN_LONG_ENABLE | VGXY61_PATGEN_SHORT_ENABLE; + return vgxy61_write_reg(sensor, VGXY61_REG_PATGEN_CTRL, reg, NULL); +} + +static int vgxy61_update_patgen(struct vgxy61_dev *sensor, u32 pattern) +{ + sensor->pattern = pattern; + + if (sensor->streaming) + return vgxy61_apply_patgen(sensor, sensor->pattern); + return 0; +} + +static int vgxy61_apply_gpiox_strobe_mode(struct vgxy61_dev *sensor, + enum vgxy61_strobe_mode mode, + unsigned int idx) +{ + static const u8 index2val[] = {0x0, 0x1, 0x3}; + u16 reg; + + reg = vgxy61_read_reg(sensor, VGXY61_REG_SIGNALS_CTRL); + if (reg < 0) + return reg; + reg &= ~(0xf << (idx * VGXY61_SIGNALS_GPIO_ID_SHIFT)); + reg |= index2val[mode] << (idx * VGXY61_SIGNALS_GPIO_ID_SHIFT); + + return vgxy61_write_reg(sensor, VGXY61_REG_SIGNALS_CTRL, reg, NULL); +} + +static int vgxy61_update_gpios_strobe_mode(struct vgxy61_dev *sensor, + enum vgxy61_hdr_mode hdr) +{ + unsigned int i; + int ret; + + switch (hdr) { + case VGXY61_HDR_LINEAR: + sensor->strobe_mode = VGXY61_STROBE_ENABLED; + break; + case VGXY61_HDR_SUB: + case VGXY61_NO_HDR: + sensor->strobe_mode = VGXY61_STROBE_LONG; + break; + default: + /* Should never happen */ + WARN_ON(true); + break; + } + + if (!sensor->streaming) + return 0; + + for (i = 0; i < VGXY61_NB_GPIOS; i++) { + ret = vgxy61_apply_gpiox_strobe_mode(sensor, + sensor->strobe_mode, + i); + if (ret) + return ret; + } + + return 0; +} + +static int vgxy61_update_gpios_strobe_polarity(struct vgxy61_dev *sensor, + bool polarity) +{ + int ret = 0; + + if (sensor->streaming) + return -EBUSY; + + vgxy61_write_reg(sensor, VGXY61_REG_GPIO_0_CTRL, polarity << 1, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_GPIO_1_CTRL, polarity << 1, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_GPIO_2_CTRL, polarity << 1, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_GPIO_3_CTRL, polarity << 1, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_SIGNALS_POLARITY_CTRL, polarity, + &ret); + + return ret; +} + +static u32 vgxy61_get_expo_long_max(struct vgxy61_dev *sensor, + unsigned int short_expo_ratio) +{ + u32 first_rot_max_expo, second_rot_max_expo, third_rot_max_expo; + + /* Apply sensor's rules of thumb */ + /* + * Short exposure + height must be less than frame length to avoid bad + * pixel line at the botom of the image + */ + first_rot_max_expo = + ((sensor->frame_length - sensor->current_mode->crop.height - + sensor->rot_term) * short_expo_ratio) - 1; + + /* + * Total exposition time must be less than frame length to avoid sensor + * crash + */ + second_rot_max_expo = + (((sensor->frame_length - VGXY61_EXPOS_ROT_TERM) * + short_expo_ratio) / (short_expo_ratio + 1)) - 1; + + /* + * Short exposure times 71 must be less than frame length to avoid + * sensor crash + */ + third_rot_max_expo = (sensor->frame_length / 71) * short_expo_ratio; + + /* Take the minimum from all rules */ + return min(min(first_rot_max_expo, second_rot_max_expo), + third_rot_max_expo); +} + +static int vgxy61_update_exposure(struct vgxy61_dev *sensor, u16 new_expo_long, + enum vgxy61_hdr_mode hdr) +{ + struct i2c_client *client = sensor->i2c_client; + u16 new_expo_short = 0; + u16 expo_short_max = 0; + u16 expo_long_min = VGXY61_MIN_EXPOSURE; + u16 expo_long_max; + + /* Compute short exposure according to hdr mode and long exposure */ + switch (hdr) { + case VGXY61_HDR_LINEAR: + /* + * Take ratio into account for minimal exposures in + * VGXY61_HDR_LINEAR + */ + expo_long_min = VGXY61_MIN_EXPOSURE * VGXY61_HDR_LINEAR_RATIO; + new_expo_long = max(expo_long_min, new_expo_long); + + expo_long_max = + vgxy61_get_expo_long_max(sensor, + VGXY61_HDR_LINEAR_RATIO); + expo_short_max = (expo_long_max + + (VGXY61_HDR_LINEAR_RATIO / 2)) / + VGXY61_HDR_LINEAR_RATIO; + new_expo_short = (new_expo_long + + (VGXY61_HDR_LINEAR_RATIO / 2)) / + VGXY61_HDR_LINEAR_RATIO; + break; + case VGXY61_HDR_SUB: + new_expo_long = max(expo_long_min, new_expo_long); + + expo_long_max = vgxy61_get_expo_long_max(sensor, 1); + /* Short and long are the same in VGXY61_HDR_SUB */ + expo_short_max = expo_long_max; + new_expo_short = new_expo_long; + break; + case VGXY61_NO_HDR: + new_expo_long = max(expo_long_min, new_expo_long); + + /* + * As short expo is 0 here, only the second rule of thumb + * applies, see vgxy61_get_expo_long_max for more + */ + expo_long_max = sensor->frame_length - VGXY61_EXPOS_ROT_TERM; + break; + default: + /* Should never happen */ + WARN_ON(true); + break; + } + + /* If this happens, something is wrong with formulas */ + WARN_ON(expo_long_min > expo_long_max); + + if (new_expo_long > expo_long_max) { + dev_warn(&client->dev, "Exposure %d too high, clamping to %d\n", + new_expo_long, expo_long_max); + new_expo_long = expo_long_max; + new_expo_short = expo_short_max; + } + + sensor->expo_long = new_expo_long; + sensor->expo_short = new_expo_short; + sensor->expo_max = expo_long_max; + sensor->expo_min = expo_long_min; + + if (sensor->streaming) + return vgxy61_apply_exposure(sensor); + return 0; +} + +static int vgxy61_apply_framelength(struct vgxy61_dev *sensor) +{ + return vgxy61_write_reg(sensor, VGXY61_REG_FRAME_LENGTH, + sensor->frame_length, NULL); +} + +static int vgxy61_update_vblank(struct vgxy61_dev *sensor, u16 vblank, + enum vgxy61_hdr_mode hdr) +{ + int ret; + + sensor->vblank_min = vgxy61_get_vblank_min(sensor, hdr); + sensor->vblank = max(sensor->vblank_min, vblank); + sensor->frame_length = sensor->current_mode->crop.height + + sensor->vblank; + + /* Update exposure according to vblank */ + ret = vgxy61_update_exposure(sensor, sensor->expo_long, hdr); + if (ret) + return ret; + + if (sensor->streaming) + return vgxy61_apply_framelength(sensor); + return 0; +} + +static int vgxy61_apply_hdr(struct vgxy61_dev *sensor, + enum vgxy61_hdr_mode index) +{ + static const u8 index2val[] = {0x1, 0x4, 0xa}; + + return vgxy61_write_reg(sensor, VGXY61_REG_HDR_CTRL, index2val[index], + NULL); +} + +static int vgxy61_update_hdr(struct vgxy61_dev *sensor, + enum vgxy61_hdr_mode index) +{ + int ret; + + /* + * vblank and short exposure change according to HDR mode, do it first + * as it can violate sensors 'rule of thumbs' and therefore will require + * to change the long exposure. + */ + ret = vgxy61_update_vblank(sensor, sensor->vblank, index); + if (ret) + return ret; + + /* Update strobe mode according to HDR */ + ret = vgxy61_update_gpios_strobe_mode(sensor, index); + if (ret) + return ret; + + sensor->hdr = index; + + if (sensor->streaming) + return vgxy61_apply_hdr(sensor, sensor->hdr); + return 0; +} + +static int vgxy61_apply_settings(struct vgxy61_dev *sensor) +{ + int ret; + unsigned int i; + + ret = vgxy61_apply_hdr(sensor, sensor->hdr); + if (ret) + return ret; + + ret = vgxy61_apply_framelength(sensor); + if (ret) + return ret; + + ret = vgxy61_apply_exposure(sensor); + if (ret) + return ret; + + ret = vgxy61_write_reg(sensor, VGXY61_REG_ANALOG_GAIN, + sensor->analog_gain, NULL); + if (ret) + return ret; + ret = vgxy61_apply_digital_gain(sensor, sensor->digital_gain); + if (ret) + return ret; + + ret = vgxy61_write_reg(sensor, VGXY61_REG_ORIENTATION, + sensor->hflip | (sensor->vflip << 1), NULL); + if (ret) + return ret; + + ret = vgxy61_apply_patgen(sensor, sensor->pattern); + if (ret) + return ret; + + for (i = 0; i < VGXY61_NB_GPIOS; i++) { + ret = vgxy61_apply_gpiox_strobe_mode(sensor, + sensor->strobe_mode, i); + if (ret) + return ret; + } + + return 0; +} + +static int vgxy61_stream_enable(struct vgxy61_dev *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd); + const struct v4l2_rect *crop = &sensor->current_mode->crop; + int ret = 0; + + ret = vgxy61_check_bw(sensor); + if (ret) + return ret; + + ret = pm_runtime_get_sync(&client->dev); + if (ret < 0) { + pm_runtime_put_autosuspend(&client->dev); + return ret; + } + + vgxy61_write_reg(sensor, VGXY61_REG_FORMAT_CTRL, + get_bpp_by_code(sensor->fmt.code), &ret); + vgxy61_write_reg(sensor, VGXY61_REG_OIF_ROI0_CTRL, + get_data_type_by_code(sensor->fmt.code), &ret); + + vgxy61_write_reg(sensor, VGXY61_REG_READOUT_CTRL, + sensor->current_mode->bin_mode, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_ROI0_START_H, crop->left, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_ROI0_END_H, + crop->left + crop->width - 1, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_ROI0_START_V, crop->top, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_ROI0_END_V, + crop->top + crop->height - 1, &ret); + if (ret) + goto err_rpm_put; + + ret = vgxy61_apply_settings(sensor); + if (ret) + goto err_rpm_put; + + ret = vgxy61_write_reg(sensor, VGXY61_REG_STREAMING, + VGXY61_STREAMING_REQ_START, NULL); + if (ret) + goto err_rpm_put; + + ret = vgxy61_poll_reg(sensor, VGXY61_REG_STREAMING, + VGXY61_STREAMING_NO_REQ, VGXY61_TIMEOUT_MS); + if (ret) + goto err_rpm_put; + + ret = vgxy61_wait_state(sensor, VGXY61_SYSTEM_FSM_STREAMING, + VGXY61_TIMEOUT_MS); + if (ret) + goto err_rpm_put; + + /* vflip and hflip cannot change during streaming */ + __v4l2_ctrl_grab(sensor->vflip_ctrl, true); + __v4l2_ctrl_grab(sensor->hflip_ctrl, true); + + return 0; + +err_rpm_put: + pm_runtime_put(&client->dev); + return ret; +} + +static int vgxy61_stream_disable(struct vgxy61_dev *sensor) +{ + struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd); + int ret; + + ret = vgxy61_write_reg(sensor, VGXY61_REG_STREAMING, + VGXY61_STREAMING_REQ_STOP, NULL); + if (ret) + goto err_str_dis; + + ret = vgxy61_poll_reg(sensor, VGXY61_REG_STREAMING, + VGXY61_STREAMING_NO_REQ, 2000); + if (ret) + goto err_str_dis; + + ret = vgxy61_wait_state(sensor, VGXY61_SYSTEM_FSM_SW_STBY, + VGXY61_TIMEOUT_MS); + if (ret) + goto err_str_dis; + + __v4l2_ctrl_grab(sensor->vflip_ctrl, false); + __v4l2_ctrl_grab(sensor->hflip_ctrl, false); + +err_str_dis: + if (ret) + WARN(1, "Can't disable stream"); + pm_runtime_put(&client->dev); + + return ret; +} + +static int vgxy61_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct vgxy61_dev *sensor = to_vgxy61_dev(sd); + int ret = 0; + + mutex_lock(&sensor->lock); + + ret = enable ? vgxy61_stream_enable(sensor) : + vgxy61_stream_disable(sensor); + if (!ret) + sensor->streaming = enable; + + mutex_unlock(&sensor->lock); + + return ret; +} + +static int vgxy61_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct vgxy61_dev *sensor = to_vgxy61_dev(sd); + const struct vgxy61_mode_info *new_mode; + struct v4l2_mbus_framefmt *fmt; + int ret; + + mutex_lock(&sensor->lock); + + if (sensor->streaming) { + ret = -EBUSY; + goto out; + } + + ret = vgxy61_try_fmt_internal(sd, &format->format, &new_mode); + if (ret) + goto out; + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + fmt = v4l2_subdev_get_try_format(sd, sd_state, 0); + *fmt = format->format; + } else if (sensor->current_mode != new_mode || + sensor->fmt.code != format->format.code) { + fmt = &sensor->fmt; + *fmt = format->format; + + sensor->current_mode = new_mode; + + /* Reset vblank and framelength to default */ + ret = vgxy61_update_vblank(sensor, + VGXY61_FRAME_LENGTH_DEF - + new_mode->crop.height, + sensor->hdr); + + /* Update controls to reflect new mode */ + __v4l2_ctrl_s_ctrl_int64(sensor->pixel_rate_ctrl, + get_pixel_rate(sensor)); + __v4l2_ctrl_modify_range(sensor->vblank_ctrl, + sensor->vblank_min, + 0xffff - new_mode->crop.height, + 1, sensor->vblank); + __v4l2_ctrl_s_ctrl(sensor->vblank_ctrl, sensor->vblank); + __v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min, + sensor->expo_max, 1, + sensor->expo_long); + } + +out: + mutex_unlock(&sensor->lock); + + return ret; +} + +static int vgxy61_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + struct vgxy61_dev *sensor = to_vgxy61_dev(sd); + struct v4l2_subdev_format fmt = { 0 }; + + sensor->current_mode = sensor->default_mode; + vgxy61_fill_framefmt(sensor, sensor->current_mode, &fmt.format, + VGXY61_MEDIA_BUS_FMT_DEF); + + return vgxy61_set_fmt(sd, sd_state, &fmt); +} + +static int vgxy61_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = ctrl_to_sd(ctrl); + struct vgxy61_dev *sensor = to_vgxy61_dev(sd); + const struct vgxy61_mode_info *cur_mode = sensor->current_mode; + int ret; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + ret = vgxy61_update_exposure(sensor, ctrl->val, sensor->hdr); + ctrl->val = sensor->expo_long; + break; + case V4L2_CID_ANALOGUE_GAIN: + ret = vgxy61_update_analog_gain(sensor, ctrl->val); + break; + case V4L2_CID_DIGITAL_GAIN: + ret = vgxy61_update_digital_gain(sensor, ctrl->val); + break; + case V4L2_CID_VFLIP: + case V4L2_CID_HFLIP: + if (sensor->streaming) { + ret = -EBUSY; + break; + } + if (ctrl->id == V4L2_CID_VFLIP) + sensor->vflip = ctrl->val; + if (ctrl->id == V4L2_CID_HFLIP) + sensor->hflip = ctrl->val; + ret = 0; + break; + case V4L2_CID_TEST_PATTERN: + ret = vgxy61_update_patgen(sensor, ctrl->val); + break; + case V4L2_CID_HDR_SENSOR_MODE: + ret = vgxy61_update_hdr(sensor, ctrl->val); + /* Update vblank and exposure controls to match new hdr */ + __v4l2_ctrl_modify_range(sensor->vblank_ctrl, + sensor->vblank_min, + 0xffff - cur_mode->crop.height, + 1, sensor->vblank); + __v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min, + sensor->expo_max, 1, + sensor->expo_long); + break; + case V4L2_CID_VBLANK: + ret = vgxy61_update_vblank(sensor, ctrl->val, sensor->hdr); + /* Update exposure control to match new vblank */ + __v4l2_ctrl_modify_range(sensor->expo_ctrl, sensor->expo_min, + sensor->expo_max, 1, + sensor->expo_long); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static const struct v4l2_ctrl_ops vgxy61_ctrl_ops = { + .s_ctrl = vgxy61_s_ctrl, +}; + +static int vgxy61_init_controls(struct vgxy61_dev *sensor) +{ + const struct v4l2_ctrl_ops *ops = &vgxy61_ctrl_ops; + struct v4l2_ctrl_handler *hdl = &sensor->ctrl_handler; + const struct vgxy61_mode_info *cur_mode = sensor->current_mode; + struct v4l2_ctrl *ctrl; + int ret; + + v4l2_ctrl_handler_init(hdl, 16); + /* We can use our own mutex for the ctrl lock */ + hdl->lock = &sensor->lock; + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN, 0, 0x1c, 1, + sensor->analog_gain); + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_DIGITAL_GAIN, 0, 0xfff, 1, + sensor->digital_gain); + v4l2_ctrl_new_std_menu_items(hdl, ops, V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(vgxy61_test_pattern_menu) - 1, + 0, 0, vgxy61_test_pattern_menu); + ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HBLANK, 0, + sensor->line_length, 1, + sensor->line_length - cur_mode->width); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + ctrl = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ, + ARRAY_SIZE(link_freq) - 1, 0, link_freq); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + v4l2_ctrl_new_std_menu_items(hdl, ops, V4L2_CID_HDR_SENSOR_MODE, + ARRAY_SIZE(vgxy61_hdr_mode_menu) - 1, 0, + VGXY61_NO_HDR, vgxy61_hdr_mode_menu); + + /* + * Keep a pointer to these controls as we need to update them when + * setting the format + */ + sensor->pixel_rate_ctrl = v4l2_ctrl_new_std(hdl, ops, + V4L2_CID_PIXEL_RATE, 1, + INT_MAX, 1, + get_pixel_rate(sensor)); + if (sensor->pixel_rate_ctrl) + sensor->pixel_rate_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + sensor->expo_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, + sensor->expo_min, + sensor->expo_max, 1, + sensor->expo_long); + sensor->vblank_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VBLANK, + sensor->vblank_min, + 0xffff - cur_mode->crop.height, + 1, sensor->vblank); + sensor->vflip_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VFLIP, + 0, 1, 1, sensor->vflip); + sensor->hflip_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HFLIP, + 0, 1, 1, sensor->hflip); + + if (hdl->error) { + ret = hdl->error; + goto free_ctrls; + } + + sensor->sd.ctrl_handler = hdl; + return 0; + +free_ctrls: + v4l2_ctrl_handler_free(hdl); + return ret; +} + +static const struct v4l2_subdev_video_ops vgxy61_video_ops = { + .s_stream = vgxy61_s_stream, +}; + +static const struct v4l2_subdev_pad_ops vgxy61_pad_ops = { + .init_cfg = vgxy61_init_cfg, + .enum_mbus_code = vgxy61_enum_mbus_code, + .get_fmt = vgxy61_get_fmt, + .set_fmt = vgxy61_set_fmt, + .get_selection = vgxy61_get_selection, + .enum_frame_size = vgxy61_enum_frame_size, +}; + +static const struct v4l2_subdev_ops vgxy61_subdev_ops = { + .video = &vgxy61_video_ops, + .pad = &vgxy61_pad_ops, +}; + +static const struct media_entity_operations vgxy61_subdev_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static int vgxy61_tx_from_ep(struct vgxy61_dev *sensor, + struct fwnode_handle *handle) +{ + struct v4l2_fwnode_endpoint ep = { .bus_type = V4L2_MBUS_CSI2_DPHY }; + struct i2c_client *client = sensor->i2c_client; + u32 log2phy[VGXY61_NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0}; + u32 phy2log[VGXY61_NB_POLARITIES] = {~0, ~0, ~0, ~0, ~0}; + int polarities[VGXY61_NB_POLARITIES] = {0, 0, 0, 0, 0}; + int l_nb; + unsigned int p, l, i; + int ret; + + ret = v4l2_fwnode_endpoint_alloc_parse(handle, &ep); + if (ret) + return -EINVAL; + + l_nb = ep.bus.mipi_csi2.num_data_lanes; + if (l_nb != 1 && l_nb != 2 && l_nb != 4) { + dev_err(&client->dev, "invalid data lane number %d\n", l_nb); + goto error_ep; + } + + /* Build log2phy, phy2log and polarities from ep info */ + log2phy[0] = ep.bus.mipi_csi2.clock_lane; + phy2log[log2phy[0]] = 0; + for (l = 1; l < l_nb + 1; l++) { + log2phy[l] = ep.bus.mipi_csi2.data_lanes[l - 1]; + phy2log[log2phy[l]] = l; + } + /* + * Then fill remaining slots for every physical slot to have something + * valid for hardware stuff. + */ + for (p = 0; p < VGXY61_NB_POLARITIES; p++) { + if (phy2log[p] != ~0) + continue; + phy2log[p] = l; + log2phy[l] = p; + l++; + } + for (l = 0; l < l_nb + 1; l++) + polarities[l] = ep.bus.mipi_csi2.lane_polarities[l]; + + if (log2phy[0] != 0) { + dev_err(&client->dev, "clk lane must be map to physical lane 0\n"); + goto error_ep; + } + sensor->oif_ctrl = (polarities[4] << 15) + ((phy2log[4] - 1) << 13) + + (polarities[3] << 12) + ((phy2log[3] - 1) << 10) + + (polarities[2] << 9) + ((phy2log[2] - 1) << 7) + + (polarities[1] << 6) + ((phy2log[1] - 1) << 4) + + (polarities[0] << 3) + + l_nb; + sensor->nb_of_lane = l_nb; + + dev_dbg(&client->dev, "tx uses %d lanes", l_nb); + for (i = 0; i < 5; i++) { + dev_dbg(&client->dev, "log2phy[%d] = %d\n", i, log2phy[i]); + dev_dbg(&client->dev, "phy2log[%d] = %d\n", i, phy2log[i]); + dev_dbg(&client->dev, "polarity[%d] = %d\n", i, polarities[i]); + } + dev_dbg(&client->dev, "oif_ctrl = 0x%04x\n", sensor->oif_ctrl); + + v4l2_fwnode_endpoint_free(&ep); + + return 0; + +error_ep: + v4l2_fwnode_endpoint_free(&ep); + + return -EINVAL; +} + +static int vgxy61_configure(struct vgxy61_dev *sensor) +{ + u32 sensor_freq; + u8 prediv, mult; + u16 line_length; + int ret = 0; + + compute_pll_parameters_by_freq(sensor->clk_freq, &prediv, &mult); + sensor_freq = (mult * sensor->clk_freq) / prediv; + /* Frequency to data rate is 1:1 ratio for MIPI */ + sensor->data_rate_in_mbps = sensor_freq; + /* Video timing ISP path (pixel clock) requires 804/5 mhz = 160 mhz */ + sensor->pclk = sensor_freq / 5; + + line_length = vgxy61_read_reg(sensor, VGXY61_REG_LINE_LENGTH); + if (line_length < 0) + return line_length; + sensor->line_length = line_length; + vgxy61_write_reg(sensor, VGXY61_REG_EXT_CLOCK, sensor->clk_freq, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_CLK_PLL_PREDIV, prediv, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_CLK_SYS_PLL_MULT, mult, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_OIF_CTRL, sensor->oif_ctrl, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_FRAME_CONTENT_CTRL, 0, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_BYPASS_CTRL, 4, &ret); + if (ret) + return ret; + vgxy61_update_gpios_strobe_polarity(sensor, sensor->gpios_polarity); + /* Set pattern generator solid to middle value */ + vgxy61_write_reg(sensor, VGXY61_REG_PATGEN_LONG_DATA_GR, 0x800, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_PATGEN_LONG_DATA_R, 0x800, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_PATGEN_LONG_DATA_B, 0x800, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_PATGEN_LONG_DATA_GB, 0x800, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_PATGEN_SHORT_DATA_GR, 0x800, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_PATGEN_SHORT_DATA_R, 0x800, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_PATGEN_SHORT_DATA_B, 0x800, &ret); + vgxy61_write_reg(sensor, VGXY61_REG_PATGEN_SHORT_DATA_GB, 0x800, &ret); + if (ret) + return ret; + + return 0; +} + +static int vgxy61_patch(struct vgxy61_dev *sensor) +{ + struct i2c_client *client = sensor->i2c_client; + u16 patch; + int ret; + + ret = vgxy61_write_array(sensor, VGXY61_REG_FWPATCH_START_ADDR, + sizeof(patch_array), patch_array); + if (ret) + return ret; + + ret = vgxy61_write_reg(sensor, VGXY61_REG_STBY, 0x10, NULL); + if (ret) + return ret; + + ret = vgxy61_poll_reg(sensor, VGXY61_REG_STBY, 0, VGXY61_TIMEOUT_MS); + if (ret) + return ret; + + patch = vgxy61_read_reg(sensor, VGXY61_REG_FWPATCH_REVISION); + if (patch < 0) + return patch; + + if (patch != (VGXY61_FWPATCH_REVISION_MAJOR << 12) + + (VGXY61_FWPATCH_REVISION_MINOR << 8) + + VGXY61_FWPATCH_REVISION_MICRO) { + dev_err(&client->dev, "bad patch version expected %d.%d.%d got %d.%d.%d\n", + VGXY61_FWPATCH_REVISION_MAJOR, + VGXY61_FWPATCH_REVISION_MINOR, + VGXY61_FWPATCH_REVISION_MICRO, + patch >> 12, (patch >> 8) & 0x0f, patch & 0xff); + return -ENODEV; + } + dev_dbg(&client->dev, "patch %d.%d.%d applied\n", + patch >> 12, (patch >> 8) & 0x0f, patch & 0xff); + + return 0; +} + +static int vgxy61_detect_cut_version(struct vgxy61_dev *sensor) +{ + struct i2c_client *client = sensor->i2c_client; + u16 device_rev; + + device_rev = vgxy61_read_reg(sensor, VGXY61_REG_REVISION); + if (device_rev < 0) + return device_rev; + + switch (device_rev >> 8) { + case 0xA: + dev_dbg(&client->dev, "Cut1 detected\n"); + dev_err(&client->dev, "Cut1 not supported by this driver\n"); + return -ENODEV; + case 0xB: + dev_dbg(&client->dev, "Cut2 detected\n"); + return 0; + case 0xC: + dev_dbg(&client->dev, "Cut3 detected\n"); + return 0; + default: + dev_err(&client->dev, "Unable to detect cut version\n"); + return -ENODEV; + } +} + +static int vgxy61_detect(struct vgxy61_dev *sensor) +{ + struct i2c_client *client = sensor->i2c_client; + u16 id = 0; + int ret; + u8 st; + + id = vgxy61_read_reg(sensor, VGXY61_REG_MODEL_ID); + if (id < 0) + return id; + if (id != VG5661_MODEL_ID && id != VG5761_MODEL_ID) { + dev_warn(&client->dev, "Unsupported sensor id %x\n", id); + return -ENODEV; + } + dev_dbg(&client->dev, "detected sensor id = 0x%04x\n", id); + sensor->id = id; + + ret = vgxy61_wait_state(sensor, VGXY61_SYSTEM_FSM_SW_STBY, + VGXY61_TIMEOUT_MS); + if (ret) + return ret; + + st = vgxy61_read_reg(sensor, VGXY61_REG_NVM); + if (st < 0) + return st; + if (st != VGXY61_NVM_OK) + dev_warn(&client->dev, "Bad nvm state got %d\n", st); + + ret = vgxy61_detect_cut_version(sensor); + if (ret) + return ret; + + return 0; +} + +/* Power/clock management functions */ +static int vgxy61_power_on(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct vgxy61_dev *sensor = to_vgxy61_dev(sd); + int ret; + + ret = clk_prepare_enable(sensor->xclk); + if (ret) { + dev_err(&client->dev, "failed to enable clock %d\n", ret); + goto disable_bulk; + } + + if (sensor->reset_gpio) { + ret = vgxy61_apply_reset(sensor); + if (ret) { + dev_err(&client->dev, "sensor reset failed %d\n", ret); + goto disable_clock; + } + } + + ret = vgxy61_patch(sensor); + if (ret) { + dev_err(&client->dev, "sensor patch failed %d\n", ret); + goto disable_clock; + } + + ret = vgxy61_configure(sensor); + if (ret) { + dev_err(&client->dev, "sensor configuration failed %d\n", ret); + goto disable_clock; + } + + return 0; + +disable_clock: + clk_disable_unprepare(sensor->xclk); +disable_bulk: + regulator_bulk_disable(ARRAY_SIZE(vgxy61_supply_name), + sensor->supplies); + + return ret; +} + +static int vgxy61_power_off(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct vgxy61_dev *sensor = to_vgxy61_dev(sd); + + clk_disable_unprepare(sensor->xclk); + regulator_bulk_disable(ARRAY_SIZE(vgxy61_supply_name), + sensor->supplies); + return 0; +} + +static void vgxy61_fill_sensor_param(struct vgxy61_dev *sensor) +{ + if (sensor->id == VG5761_MODEL_ID) { + sensor->sensor_width = VGX761_WIDTH; + sensor->sensor_height = VGX761_HEIGHT; + sensor->sensor_modes = vgx761_mode_data; + sensor->sensor_modes_nb = ARRAY_SIZE(vgx761_mode_data); + sensor->default_mode = &vgx761_mode_data[VGX761_DEFAULT_MODE]; + sensor->rot_term = VGX761_SHORT_ROT_TERM; + } else if (sensor->id == VG5661_MODEL_ID) { + sensor->sensor_width = VGX661_WIDTH; + sensor->sensor_height = VGX661_HEIGHT; + sensor->sensor_modes = vgx661_mode_data; + sensor->sensor_modes_nb = ARRAY_SIZE(vgx661_mode_data); + sensor->default_mode = &vgx661_mode_data[VGX661_DEFAULT_MODE]; + sensor->rot_term = VGX661_SHORT_ROT_TERM; + } else { + /* Should never happen */ + WARN_ON(true); + } + sensor->current_mode = sensor->default_mode; +} + +static int vgxy61_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct fwnode_handle *handle; + struct vgxy61_dev *sensor; + int ret; + + sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL); + if (!sensor) + return -ENOMEM; + + sensor->i2c_client = client; + sensor->streaming = false; + sensor->hdr = VGXY61_NO_HDR; + sensor->expo_long = 200; + sensor->expo_short = 0; + sensor->hflip = false; + sensor->vflip = false; + sensor->analog_gain = 0; + sensor->digital_gain = 256; + + handle = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, 0); + if (!handle) { + dev_err(dev, "handle node not found\n"); + return -EINVAL; + } + + ret = vgxy61_tx_from_ep(sensor, handle); + fwnode_handle_put(handle); + if (ret) { + dev_err(dev, "Failed to parse handle %d\n", ret); + return ret; + } + + sensor->xclk = devm_clk_get(dev, NULL); + if (IS_ERR(sensor->xclk)) { + dev_err(dev, "failed to get xclk\n"); + return PTR_ERR(sensor->xclk); + } + sensor->clk_freq = clk_get_rate(sensor->xclk); + if (sensor->clk_freq < 6 * HZ_PER_MHZ || + sensor->clk_freq > 27 * HZ_PER_MHZ) { + dev_err(dev, "Only 6Mhz-27Mhz clock range supported. provide %lu MHz\n", + sensor->clk_freq / HZ_PER_MHZ); + return -EINVAL; + } + sensor->gpios_polarity = + device_property_read_bool(dev, "st,strobe-gpios-polarity"); + + v4l2_i2c_subdev_init(&sensor->sd, client, &vgxy61_subdev_ops); + sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + sensor->pad.flags = MEDIA_PAD_FL_SOURCE; + sensor->sd.entity.ops = &vgxy61_subdev_entity_ops; + sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + + ret = vgxy61_get_regulators(sensor); + if (ret) { + dev_err(&client->dev, "failed to get regulators %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(vgxy61_supply_name), + sensor->supplies); + if (ret) { + dev_err(&client->dev, "failed to enable regulators %d\n", ret); + return ret; + } + + ret = vgxy61_power_on(dev); + if (ret) + return ret; + + ret = vgxy61_detect(sensor); + if (ret) { + dev_err(&client->dev, "sensor detect failed %d\n", ret); + return ret; + } + + vgxy61_fill_sensor_param(sensor); + vgxy61_fill_framefmt(sensor, sensor->current_mode, &sensor->fmt, + VGXY61_MEDIA_BUS_FMT_DEF); + + ret = vgxy61_update_hdr(sensor, sensor->hdr); + if (ret) + return ret; + + mutex_init(&sensor->lock); + + ret = vgxy61_init_controls(sensor); + if (ret) { + dev_err(&client->dev, "controls initialization failed %d\n", + ret); + goto error_power_off; + } + + ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad); + if (ret) { + dev_err(&client->dev, "pads init failed %d\n", ret); + goto error_handler_free; + } + + /* Enable runtime PM and turn off the device */ + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); + + ret = v4l2_async_register_subdev(&sensor->sd); + if (ret) { + dev_err(&client->dev, "async subdev register failed %d\n", ret); + goto error_pm_runtime; + } + + pm_runtime_set_autosuspend_delay(&client->dev, 1000); + pm_runtime_use_autosuspend(&client->dev); + + dev_dbg(&client->dev, "vgxy61 probe successfully\n"); + + return 0; + +error_pm_runtime: + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + media_entity_cleanup(&sensor->sd.entity); +error_handler_free: + v4l2_ctrl_handler_free(sensor->sd.ctrl_handler); + mutex_destroy(&sensor->lock); +error_power_off: + vgxy61_power_off(dev); + + return ret; +} + +static void vgxy61_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct vgxy61_dev *sensor = to_vgxy61_dev(sd); + + v4l2_async_unregister_subdev(&sensor->sd); + mutex_destroy(&sensor->lock); + media_entity_cleanup(&sensor->sd.entity); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + vgxy61_power_off(&client->dev); + pm_runtime_set_suspended(&client->dev); +} + +static const struct of_device_id vgxy61_dt_ids[] = { + { .compatible = "st,st-vgxy61" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, vgxy61_dt_ids); + +static const struct dev_pm_ops vgxy61_pm_ops = { + SET_RUNTIME_PM_OPS(vgxy61_power_off, vgxy61_power_on, NULL) +}; + +static struct i2c_driver vgxy61_i2c_driver = { + .driver = { + .name = "st-vgxy61", + .of_match_table = vgxy61_dt_ids, + .pm = &vgxy61_pm_ops, + }, + .probe_new = vgxy61_probe, + .remove = vgxy61_remove, +}; + +module_i2c_driver(vgxy61_i2c_driver); + +MODULE_AUTHOR("Benjamin Mugnier "); +MODULE_AUTHOR("Mickael Guene "); +MODULE_AUTHOR("Sylvain Petinot "); +MODULE_DESCRIPTION("VGXY61 camera subdev driver"); +MODULE_LICENSE("GPL"); From a96dfea1df25bf22f4b02080a85ac87f7a3977d0 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Sun, 16 Oct 2022 09:15:04 +0300 Subject: [PATCH 18/47] media: dt-bindings: Convert imx290.txt to YAML Convert the Sony IMX290 DT binding from text to YAML. Add Manivannan as a maintainer given that he is listed in MAINTAINERS for the file, as volunteering myself. The name of the input clock, "xclk", is wrong as the hardware manual names it INCK. As the device has a single clock, the name could be omitted, but that would require a corresponding change to the driver and is thus a candidate for further patches. Signed-off-by: Laurent Pinchart Reviewed-by: Krzysztof Kozlowski Signed-off-by: Sakari Ailus --- .../devicetree/bindings/media/i2c/imx290.txt | 57 -------- .../bindings/media/i2c/sony,imx290.yaml | 129 ++++++++++++++++++ MAINTAINERS | 2 +- 3 files changed, 130 insertions(+), 58 deletions(-) delete mode 100644 Documentation/devicetree/bindings/media/i2c/imx290.txt create mode 100644 Documentation/devicetree/bindings/media/i2c/sony,imx290.yaml diff --git a/Documentation/devicetree/bindings/media/i2c/imx290.txt b/Documentation/devicetree/bindings/media/i2c/imx290.txt deleted file mode 100644 index a3cc21410f7c..000000000000 --- a/Documentation/devicetree/bindings/media/i2c/imx290.txt +++ /dev/null @@ -1,57 +0,0 @@ -* Sony IMX290 1/2.8-Inch CMOS Image Sensor - -The Sony IMX290 is a 1/2.8-Inch CMOS Solid-state image sensor with -Square Pixel for Color Cameras. It is programmable through I2C and 4-wire -interfaces. The sensor output is available via CMOS logic parallel SDR output, -Low voltage LVDS DDR output and CSI-2 serial data output. The CSI-2 bus is the -default. No bindings have been defined for the other busses. - -Required Properties: -- compatible: Should be "sony,imx290" -- reg: I2C bus address of the device -- clocks: Reference to the xclk clock. -- clock-names: Should be "xclk". -- clock-frequency: Frequency of the xclk clock in Hz. -- vdddo-supply: Sensor digital IO regulator. -- vdda-supply: Sensor analog regulator. -- vddd-supply: Sensor digital core regulator. - -Optional Properties: -- reset-gpios: Sensor reset GPIO - -The imx290 device node should contain one 'port' child node with -an 'endpoint' subnode. For further reading on port node refer to -Documentation/devicetree/bindings/media/video-interfaces.txt. - -Required Properties on endpoint: -- data-lanes: check ../video-interfaces.txt -- link-frequencies: check ../video-interfaces.txt -- remote-endpoint: check ../video-interfaces.txt - -Example: - &i2c1 { - ... - imx290: camera-sensor@1a { - compatible = "sony,imx290"; - reg = <0x1a>; - - reset-gpios = <&msmgpio 35 GPIO_ACTIVE_LOW>; - pinctrl-names = "default"; - pinctrl-0 = <&camera_rear_default>; - - clocks = <&gcc GCC_CAMSS_MCLK0_CLK>; - clock-names = "xclk"; - clock-frequency = <37125000>; - - vdddo-supply = <&camera_vdddo_1v8>; - vdda-supply = <&camera_vdda_2v8>; - vddd-supply = <&camera_vddd_1v5>; - - port { - imx290_ep: endpoint { - data-lanes = <1 2 3 4>; - link-frequencies = /bits/ 64 <445500000>; - remote-endpoint = <&csiphy0_ep>; - }; - }; - }; diff --git a/Documentation/devicetree/bindings/media/i2c/sony,imx290.yaml b/Documentation/devicetree/bindings/media/i2c/sony,imx290.yaml new file mode 100644 index 000000000000..21377daae026 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/sony,imx290.yaml @@ -0,0 +1,129 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/sony,imx290.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Sony IMX290 1/2.8-Inch CMOS Image Sensor + +maintainers: + - Manivannan Sadhasivam + - Laurent Pinchart + +description: |- + The Sony IMX290 is a 1/2.8-Inch CMOS Solid-state image sensor with Square + Pixel for Color Cameras. It is programmable through I2C and 4-wire + interfaces. The sensor output is available via CMOS logic parallel SDR + output, Low voltage LVDS DDR output and CSI-2 serial data output. The CSI-2 + bus is the default. No bindings have been defined for the other busses. + +properties: + compatible: + enum: + - sony,imx290 + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + clock-names: + description: Input clock (37.125 MHz or 74.25 MHz) + items: + - const: xclk + + clock-frequency: + description: Frequency of the xclk clock in Hz + + vdda-supply: + description: Analog power supply (2.9V) + + vddd-supply: + description: Digital core power supply (1.2V) + + vdddo-supply: + description: Digital I/O power supply (1.8V) + + reset-gpios: + description: Sensor reset (XCLR) GPIO + maxItems: 1 + + port: + $ref: /schemas/graph.yaml#/$defs/port-base + description: | + Video output port + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + data-lanes: + anyOf: + - items: + - const: 1 + - const: 2 + - items: + - const: 1 + - const: 2 + - const: 3 + - const: 4 + + link-frequencies: true + + required: + - data-lanes + - link-frequencies + + additionalProperties: false + +required: + - compatible + - reg + - clocks + - clock-names + - clock-frequency + - vdda-supply + - vddd-supply + - vdddo-supply + - port + +additionalProperties: false + +examples: + - | + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + imx290: camera-sensor@1a { + compatible = "sony,imx290"; + reg = <0x1a>; + + pinctrl-names = "default"; + pinctrl-0 = <&camera_rear_default>; + + clocks = <&gcc 90>; + clock-names = "xclk"; + clock-frequency = <37125000>; + + vdddo-supply = <&camera_vdddo_1v8>; + vdda-supply = <&camera_vdda_2v8>; + vddd-supply = <&camera_vddd_1v5>; + + reset-gpios = <&msmgpio 35 GPIO_ACTIVE_LOW>; + + port { + imx290_ep: endpoint { + data-lanes = <1 2 3 4>; + link-frequencies = /bits/ 64 <445500000>; + remote-endpoint = <&csiphy0_ep>; + }; + }; + }; + }; +... diff --git a/MAINTAINERS b/MAINTAINERS index 1e4f85bd903e..498e6dc45efc 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19207,7 +19207,7 @@ M: Manivannan Sadhasivam L: linux-media@vger.kernel.org S: Maintained T: git git://linuxtv.org/media_tree.git -F: Documentation/devicetree/bindings/media/i2c/imx290.txt +F: Documentation/devicetree/bindings/media/i2c/sony,imx290.yaml F: drivers/media/i2c/imx290.c SONY IMX319 SENSOR DRIVER From 08878cbc0cbf69dfc084436449d6e6fb1640796b Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Sun, 16 Oct 2022 09:15:05 +0300 Subject: [PATCH 19/47] media: i2c: imx290: Use device lock for the control handler The link frequency and pixel rate controls are set without holding the control handler lock, resulting in kernel warnings. As the value of those controls depend on the format, the simplest fix is to use the device lock for the control handler. Signed-off-by: Laurent Pinchart Reviewed-by: Alexander Stein Signed-off-by: Sakari Ailus --- drivers/media/i2c/imx290.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/media/i2c/imx290.c b/drivers/media/i2c/imx290.c index 1ce64dcdf7f0..e5b758356a7a 100644 --- a/drivers/media/i2c/imx290.c +++ b/drivers/media/i2c/imx290.c @@ -1043,6 +1043,7 @@ static int imx290_probe(struct i2c_client *client) imx290_entity_init_cfg(&imx290->sd, NULL); v4l2_ctrl_handler_init(&imx290->ctrls, 4); + imx290->ctrls.lock = &imx290->lock; v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops, V4L2_CID_GAIN, 0, 72, 1, 0); From fbe0a89dc7e32a38904da0c149150b52ccf44279 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Sun, 16 Oct 2022 09:15:06 +0300 Subject: [PATCH 20/47] media: i2c: imx290: Print error code when I2C transfer fails Knowing why I2C transfers fail is useful for debugging. Extend the error message to print the error code. Signed-off-by: Laurent Pinchart Reviewed-by: Alexander Stein Signed-off-by: Sakari Ailus --- drivers/media/i2c/imx290.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/media/i2c/imx290.c b/drivers/media/i2c/imx290.c index e5b758356a7a..f6ad4d250feb 100644 --- a/drivers/media/i2c/imx290.c +++ b/drivers/media/i2c/imx290.c @@ -370,7 +370,8 @@ static inline int __always_unused imx290_read_reg(struct imx290 *imx290, u16 add ret = regmap_read(imx290->regmap, addr, ®val); if (ret) { - dev_err(imx290->dev, "I2C read failed for addr: %x\n", addr); + dev_err(imx290->dev, "Failed to read register 0x%04x: %d\n", + addr, ret); return ret; } @@ -385,7 +386,8 @@ static int imx290_write_reg(struct imx290 *imx290, u16 addr, u8 value) ret = regmap_write(imx290->regmap, addr, value); if (ret) { - dev_err(imx290->dev, "I2C write failed for addr: %x\n", addr); + dev_err(imx290->dev, "Failed to write register 0x%04x: %d\n", + addr, ret); return ret; } From 2548df538cdd39b52976545a94529c9b81671a41 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Sun, 16 Oct 2022 09:15:07 +0300 Subject: [PATCH 21/47] media: i2c: imx290: Replace macro with explicit ARRAY_SIZE() Use ARRAY_SIZE(imx290->supplies) for code that needs the size of the array, instead of relying on the IMX290_NUM_SUPPLIES. The result is less error-prone as it ties the size to the array. Signed-off-by: Laurent Pinchart Reviewed-by: Alexander Stein Signed-off-by: Sakari Ailus --- drivers/media/i2c/imx290.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/media/i2c/imx290.c b/drivers/media/i2c/imx290.c index f6ad4d250feb..1d1a6fdc3954 100644 --- a/drivers/media/i2c/imx290.c +++ b/drivers/media/i2c/imx290.c @@ -790,10 +790,10 @@ static int imx290_get_regulators(struct device *dev, struct imx290 *imx290) { unsigned int i; - for (i = 0; i < IMX290_NUM_SUPPLIES; i++) + for (i = 0; i < ARRAY_SIZE(imx290->supplies); i++) imx290->supplies[i].supply = imx290_supply_name[i]; - return devm_regulator_bulk_get(dev, IMX290_NUM_SUPPLIES, + return devm_regulator_bulk_get(dev, ARRAY_SIZE(imx290->supplies), imx290->supplies); } @@ -852,7 +852,8 @@ static int imx290_power_on(struct device *dev) return ret; } - ret = regulator_bulk_enable(IMX290_NUM_SUPPLIES, imx290->supplies); + ret = regulator_bulk_enable(ARRAY_SIZE(imx290->supplies), + imx290->supplies); if (ret) { dev_err(dev, "Failed to enable regulators\n"); clk_disable_unprepare(imx290->xclk); @@ -876,7 +877,7 @@ static int imx290_power_off(struct device *dev) clk_disable_unprepare(imx290->xclk); gpiod_set_value_cansleep(imx290->rst_gpio, 1); - regulator_bulk_disable(IMX290_NUM_SUPPLIES, imx290->supplies); + regulator_bulk_disable(ARRAY_SIZE(imx290->supplies), imx290->supplies); return 0; } From b817888a0c50962d4b51b9107e824f9aac6a9f3c Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Sun, 16 Oct 2022 09:15:08 +0300 Subject: [PATCH 22/47] media: i2c: imx290: Drop imx290_write_buffered_reg() The imx290_write_buffered_reg() function wraps a register write with register hold, to enable changing multiple registers synchronously. It is used for the gain only, which is an 8-bit register, defeating its purpose. The feature is useful, but should be implemented differently. Drop the function for now, to prepare for a rework of register access. Signed-off-by: Laurent Pinchart Acked-by: Alexander Stein Signed-off-by: Sakari Ailus --- drivers/media/i2c/imx290.c | 32 +------------------------------- 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/drivers/media/i2c/imx290.c b/drivers/media/i2c/imx290.c index 1d1a6fdc3954..1219a06fec5f 100644 --- a/drivers/media/i2c/imx290.c +++ b/drivers/media/i2c/imx290.c @@ -413,41 +413,11 @@ static int imx290_set_register_array(struct imx290 *imx290, return 0; } -static int imx290_write_buffered_reg(struct imx290 *imx290, u16 address_low, - u8 nr_regs, u32 value) -{ - unsigned int i; - int ret; - - ret = imx290_write_reg(imx290, IMX290_REGHOLD, 0x01); - if (ret) { - dev_err(imx290->dev, "Error setting hold register\n"); - return ret; - } - - for (i = 0; i < nr_regs; i++) { - ret = imx290_write_reg(imx290, address_low + i, - (u8)(value >> (i * 8))); - if (ret) { - dev_err(imx290->dev, "Error writing buffered registers\n"); - return ret; - } - } - - ret = imx290_write_reg(imx290, IMX290_REGHOLD, 0x00); - if (ret) { - dev_err(imx290->dev, "Error setting hold register\n"); - return ret; - } - - return ret; -} - static int imx290_set_gain(struct imx290 *imx290, u32 value) { int ret; - ret = imx290_write_buffered_reg(imx290, IMX290_GAIN, 1, value); + ret = imx290_write_reg(imx290, IMX290_GAIN, value); if (ret) dev_err(imx290->dev, "Unable to write gain\n"); From 72e4bf6dd1368943c5fdbc339a62e5020cf2514b Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Sun, 16 Oct 2022 09:15:09 +0300 Subject: [PATCH 23/47] media: i2c: imx290: Drop regmap cache Only two registers are ever read, and once only. There's no need to cache values. Signed-off-by: Laurent Pinchart Reviewed-by: Alexander Stein Signed-off-by: Sakari Ailus --- drivers/media/i2c/imx290.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/media/i2c/imx290.c b/drivers/media/i2c/imx290.c index 1219a06fec5f..30e4ab508a74 100644 --- a/drivers/media/i2c/imx290.c +++ b/drivers/media/i2c/imx290.c @@ -97,7 +97,6 @@ static const struct imx290_pixfmt imx290_formats[] = { static const struct regmap_config imx290_regmap_config = { .reg_bits = 16, .val_bits = 8, - .cache_type = REGCACHE_RBTREE, }; static const char * const imx290_test_pattern_menu[] = { From 72825bc6f7f5e75c89e678728c785c5925f4ea7d Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Sun, 16 Oct 2022 09:15:10 +0300 Subject: [PATCH 24/47] media: i2c: imx290: Specify HMAX values in decimal The HMAX value specifies the total line length in pixels. It's thus more readable in decimal than hexadecimal. Fix it. Signed-off-by: Laurent Pinchart Signed-off-by: Sakari Ailus --- drivers/media/i2c/imx290.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/media/i2c/imx290.c b/drivers/media/i2c/imx290.c index 30e4ab508a74..5ca70f80d06a 100644 --- a/drivers/media/i2c/imx290.c +++ b/drivers/media/i2c/imx290.c @@ -307,7 +307,7 @@ static const struct imx290_mode imx290_modes_2lanes[] = { { .width = 1920, .height = 1080, - .hmax = 0x1130, + .hmax = 4400, .link_freq_index = FREQ_INDEX_1080P, .data = imx290_1080p_settings, .data_size = ARRAY_SIZE(imx290_1080p_settings), @@ -315,7 +315,7 @@ static const struct imx290_mode imx290_modes_2lanes[] = { { .width = 1280, .height = 720, - .hmax = 0x19c8, + .hmax = 6600, .link_freq_index = FREQ_INDEX_720P, .data = imx290_720p_settings, .data_size = ARRAY_SIZE(imx290_720p_settings), @@ -326,7 +326,7 @@ static const struct imx290_mode imx290_modes_4lanes[] = { { .width = 1920, .height = 1080, - .hmax = 0x0898, + .hmax = 2200, .link_freq_index = FREQ_INDEX_1080P, .data = imx290_1080p_settings, .data_size = ARRAY_SIZE(imx290_1080p_settings), @@ -334,7 +334,7 @@ static const struct imx290_mode imx290_modes_4lanes[] = { { .width = 1280, .height = 720, - .hmax = 0x0ce4, + .hmax = 3300, .link_freq_index = FREQ_INDEX_720P, .data = imx290_720p_settings, .data_size = ARRAY_SIZE(imx290_720p_settings), From e70abe881463379ef9ab63c09300a5f3651cf9ee Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Sun, 16 Oct 2022 09:15:11 +0300 Subject: [PATCH 25/47] media: i2c: imx290: Support variable-sized registers The IMX290 has registers of different sizes. To simplify the code, handle this in the read/write functions instead of in the callers by encoding the register size in the symbolic name macros. All registers are defined as 8-bit for now, a subsequent change will move to larger registers where applicable. Signed-off-by: Laurent Pinchart Signed-off-by: Sakari Ailus --- drivers/media/i2c/imx290.c | 352 +++++++++++++++++++------------------ 1 file changed, 180 insertions(+), 172 deletions(-) diff --git a/drivers/media/i2c/imx290.c b/drivers/media/i2c/imx290.c index 5ca70f80d06a..dc07413d480e 100644 --- a/drivers/media/i2c/imx290.c +++ b/drivers/media/i2c/imx290.c @@ -22,22 +22,28 @@ #include #include -#define IMX290_STANDBY 0x3000 -#define IMX290_REGHOLD 0x3001 -#define IMX290_XMSTA 0x3002 -#define IMX290_FR_FDG_SEL 0x3009 -#define IMX290_BLKLEVEL_LOW 0x300a -#define IMX290_BLKLEVEL_HIGH 0x300b -#define IMX290_GAIN 0x3014 -#define IMX290_HMAX_LOW 0x301c -#define IMX290_HMAX_HIGH 0x301d -#define IMX290_PGCTRL 0x308c -#define IMX290_PHY_LANE_NUM 0x3407 -#define IMX290_CSI_LANE_MODE 0x3443 +#define IMX290_REG_SIZE_SHIFT 16 +#define IMX290_REG_ADDR_MASK 0xffff +#define IMX290_REG_8BIT(n) ((1U << IMX290_REG_SIZE_SHIFT) | (n)) +#define IMX290_REG_16BIT(n) ((2U << IMX290_REG_SIZE_SHIFT) | (n)) +#define IMX290_REG_24BIT(n) ((3U << IMX290_REG_SIZE_SHIFT) | (n)) -#define IMX290_PGCTRL_REGEN BIT(0) -#define IMX290_PGCTRL_THRU BIT(1) -#define IMX290_PGCTRL_MODE(n) ((n) << 4) +#define IMX290_STANDBY IMX290_REG_8BIT(0x3000) +#define IMX290_REGHOLD IMX290_REG_8BIT(0x3001) +#define IMX290_XMSTA IMX290_REG_8BIT(0x3002) +#define IMX290_FR_FDG_SEL IMX290_REG_8BIT(0x3009) +#define IMX290_BLKLEVEL_LOW IMX290_REG_8BIT(0x300a) +#define IMX290_BLKLEVEL_HIGH IMX290_REG_8BIT(0x300b) +#define IMX290_GAIN IMX290_REG_8BIT(0x3014) +#define IMX290_HMAX_LOW IMX290_REG_8BIT(0x301c) +#define IMX290_HMAX_HIGH IMX290_REG_8BIT(0x301d) +#define IMX290_PGCTRL IMX290_REG_8BIT(0x308c) +#define IMX290_PHY_LANE_NUM IMX290_REG_8BIT(0x3407) +#define IMX290_CSI_LANE_MODE IMX290_REG_8BIT(0x3443) + +#define IMX290_PGCTRL_REGEN BIT(0) +#define IMX290_PGCTRL_THRU BIT(1) +#define IMX290_PGCTRL_MODE(n) ((n) << 4) static const char * const imx290_supply_name[] = { "vdda", @@ -48,7 +54,7 @@ static const char * const imx290_supply_name[] = { #define IMX290_NUM_SUPPLIES ARRAY_SIZE(imx290_supply_name) struct imx290_regval { - u16 reg; + u32 reg; u8 val; }; @@ -111,163 +117,163 @@ static const char * const imx290_test_pattern_menu[] = { }; static const struct imx290_regval imx290_global_init_settings[] = { - { 0x3007, 0x00 }, - { 0x3018, 0x65 }, - { 0x3019, 0x04 }, - { 0x301a, 0x00 }, - { 0x3444, 0x20 }, - { 0x3445, 0x25 }, - { 0x303a, 0x0c }, - { 0x3040, 0x00 }, - { 0x3041, 0x00 }, - { 0x303c, 0x00 }, - { 0x303d, 0x00 }, - { 0x3042, 0x9c }, - { 0x3043, 0x07 }, - { 0x303e, 0x49 }, - { 0x303f, 0x04 }, - { 0x304b, 0x0a }, - { 0x300f, 0x00 }, - { 0x3010, 0x21 }, - { 0x3012, 0x64 }, - { 0x3016, 0x09 }, - { 0x3070, 0x02 }, - { 0x3071, 0x11 }, - { 0x309b, 0x10 }, - { 0x309c, 0x22 }, - { 0x30a2, 0x02 }, - { 0x30a6, 0x20 }, - { 0x30a8, 0x20 }, - { 0x30aa, 0x20 }, - { 0x30ac, 0x20 }, - { 0x30b0, 0x43 }, - { 0x3119, 0x9e }, - { 0x311c, 0x1e }, - { 0x311e, 0x08 }, - { 0x3128, 0x05 }, - { 0x313d, 0x83 }, - { 0x3150, 0x03 }, - { 0x317e, 0x00 }, - { 0x32b8, 0x50 }, - { 0x32b9, 0x10 }, - { 0x32ba, 0x00 }, - { 0x32bb, 0x04 }, - { 0x32c8, 0x50 }, - { 0x32c9, 0x10 }, - { 0x32ca, 0x00 }, - { 0x32cb, 0x04 }, - { 0x332c, 0xd3 }, - { 0x332d, 0x10 }, - { 0x332e, 0x0d }, - { 0x3358, 0x06 }, - { 0x3359, 0xe1 }, - { 0x335a, 0x11 }, - { 0x3360, 0x1e }, - { 0x3361, 0x61 }, - { 0x3362, 0x10 }, - { 0x33b0, 0x50 }, - { 0x33b2, 0x1a }, - { 0x33b3, 0x04 }, + { IMX290_REG_8BIT(0x3007), 0x00 }, + { IMX290_REG_8BIT(0x3018), 0x65 }, + { IMX290_REG_8BIT(0x3019), 0x04 }, + { IMX290_REG_8BIT(0x301a), 0x00 }, + { IMX290_REG_8BIT(0x3444), 0x20 }, + { IMX290_REG_8BIT(0x3445), 0x25 }, + { IMX290_REG_8BIT(0x303a), 0x0c }, + { IMX290_REG_8BIT(0x3040), 0x00 }, + { IMX290_REG_8BIT(0x3041), 0x00 }, + { IMX290_REG_8BIT(0x303c), 0x00 }, + { IMX290_REG_8BIT(0x303d), 0x00 }, + { IMX290_REG_8BIT(0x3042), 0x9c }, + { IMX290_REG_8BIT(0x3043), 0x07 }, + { IMX290_REG_8BIT(0x303e), 0x49 }, + { IMX290_REG_8BIT(0x303f), 0x04 }, + { IMX290_REG_8BIT(0x304b), 0x0a }, + { IMX290_REG_8BIT(0x300f), 0x00 }, + { IMX290_REG_8BIT(0x3010), 0x21 }, + { IMX290_REG_8BIT(0x3012), 0x64 }, + { IMX290_REG_8BIT(0x3016), 0x09 }, + { IMX290_REG_8BIT(0x3070), 0x02 }, + { IMX290_REG_8BIT(0x3071), 0x11 }, + { IMX290_REG_8BIT(0x309b), 0x10 }, + { IMX290_REG_8BIT(0x309c), 0x22 }, + { IMX290_REG_8BIT(0x30a2), 0x02 }, + { IMX290_REG_8BIT(0x30a6), 0x20 }, + { IMX290_REG_8BIT(0x30a8), 0x20 }, + { IMX290_REG_8BIT(0x30aa), 0x20 }, + { IMX290_REG_8BIT(0x30ac), 0x20 }, + { IMX290_REG_8BIT(0x30b0), 0x43 }, + { IMX290_REG_8BIT(0x3119), 0x9e }, + { IMX290_REG_8BIT(0x311c), 0x1e }, + { IMX290_REG_8BIT(0x311e), 0x08 }, + { IMX290_REG_8BIT(0x3128), 0x05 }, + { IMX290_REG_8BIT(0x313d), 0x83 }, + { IMX290_REG_8BIT(0x3150), 0x03 }, + { IMX290_REG_8BIT(0x317e), 0x00 }, + { IMX290_REG_8BIT(0x32b8), 0x50 }, + { IMX290_REG_8BIT(0x32b9), 0x10 }, + { IMX290_REG_8BIT(0x32ba), 0x00 }, + { IMX290_REG_8BIT(0x32bb), 0x04 }, + { IMX290_REG_8BIT(0x32c8), 0x50 }, + { IMX290_REG_8BIT(0x32c9), 0x10 }, + { IMX290_REG_8BIT(0x32ca), 0x00 }, + { IMX290_REG_8BIT(0x32cb), 0x04 }, + { IMX290_REG_8BIT(0x332c), 0xd3 }, + { IMX290_REG_8BIT(0x332d), 0x10 }, + { IMX290_REG_8BIT(0x332e), 0x0d }, + { IMX290_REG_8BIT(0x3358), 0x06 }, + { IMX290_REG_8BIT(0x3359), 0xe1 }, + { IMX290_REG_8BIT(0x335a), 0x11 }, + { IMX290_REG_8BIT(0x3360), 0x1e }, + { IMX290_REG_8BIT(0x3361), 0x61 }, + { IMX290_REG_8BIT(0x3362), 0x10 }, + { IMX290_REG_8BIT(0x33b0), 0x50 }, + { IMX290_REG_8BIT(0x33b2), 0x1a }, + { IMX290_REG_8BIT(0x33b3), 0x04 }, }; static const struct imx290_regval imx290_1080p_settings[] = { /* mode settings */ - { 0x3007, 0x00 }, - { 0x303a, 0x0c }, - { 0x3414, 0x0a }, - { 0x3472, 0x80 }, - { 0x3473, 0x07 }, - { 0x3418, 0x38 }, - { 0x3419, 0x04 }, - { 0x3012, 0x64 }, - { 0x3013, 0x00 }, - { 0x305c, 0x18 }, - { 0x305d, 0x03 }, - { 0x305e, 0x20 }, - { 0x305f, 0x01 }, - { 0x315e, 0x1a }, - { 0x3164, 0x1a }, - { 0x3480, 0x49 }, + { IMX290_REG_8BIT(0x3007), 0x00 }, + { IMX290_REG_8BIT(0x303a), 0x0c }, + { IMX290_REG_8BIT(0x3414), 0x0a }, + { IMX290_REG_8BIT(0x3472), 0x80 }, + { IMX290_REG_8BIT(0x3473), 0x07 }, + { IMX290_REG_8BIT(0x3418), 0x38 }, + { IMX290_REG_8BIT(0x3419), 0x04 }, + { IMX290_REG_8BIT(0x3012), 0x64 }, + { IMX290_REG_8BIT(0x3013), 0x00 }, + { IMX290_REG_8BIT(0x305c), 0x18 }, + { IMX290_REG_8BIT(0x305d), 0x03 }, + { IMX290_REG_8BIT(0x305e), 0x20 }, + { IMX290_REG_8BIT(0x305f), 0x01 }, + { IMX290_REG_8BIT(0x315e), 0x1a }, + { IMX290_REG_8BIT(0x3164), 0x1a }, + { IMX290_REG_8BIT(0x3480), 0x49 }, /* data rate settings */ - { 0x3405, 0x10 }, - { 0x3446, 0x57 }, - { 0x3447, 0x00 }, - { 0x3448, 0x37 }, - { 0x3449, 0x00 }, - { 0x344a, 0x1f }, - { 0x344b, 0x00 }, - { 0x344c, 0x1f }, - { 0x344d, 0x00 }, - { 0x344e, 0x1f }, - { 0x344f, 0x00 }, - { 0x3450, 0x77 }, - { 0x3451, 0x00 }, - { 0x3452, 0x1f }, - { 0x3453, 0x00 }, - { 0x3454, 0x17 }, - { 0x3455, 0x00 }, + { IMX290_REG_8BIT(0x3405), 0x10 }, + { IMX290_REG_8BIT(0x3446), 0x57 }, + { IMX290_REG_8BIT(0x3447), 0x00 }, + { IMX290_REG_8BIT(0x3448), 0x37 }, + { IMX290_REG_8BIT(0x3449), 0x00 }, + { IMX290_REG_8BIT(0x344a), 0x1f }, + { IMX290_REG_8BIT(0x344b), 0x00 }, + { IMX290_REG_8BIT(0x344c), 0x1f }, + { IMX290_REG_8BIT(0x344d), 0x00 }, + { IMX290_REG_8BIT(0x344e), 0x1f }, + { IMX290_REG_8BIT(0x344f), 0x00 }, + { IMX290_REG_8BIT(0x3450), 0x77 }, + { IMX290_REG_8BIT(0x3451), 0x00 }, + { IMX290_REG_8BIT(0x3452), 0x1f }, + { IMX290_REG_8BIT(0x3453), 0x00 }, + { IMX290_REG_8BIT(0x3454), 0x17 }, + { IMX290_REG_8BIT(0x3455), 0x00 }, }; static const struct imx290_regval imx290_720p_settings[] = { /* mode settings */ - { 0x3007, 0x10 }, - { 0x303a, 0x06 }, - { 0x3414, 0x04 }, - { 0x3472, 0x00 }, - { 0x3473, 0x05 }, - { 0x3418, 0xd0 }, - { 0x3419, 0x02 }, - { 0x3012, 0x64 }, - { 0x3013, 0x00 }, - { 0x305c, 0x20 }, - { 0x305d, 0x00 }, - { 0x305e, 0x20 }, - { 0x305f, 0x01 }, - { 0x315e, 0x1a }, - { 0x3164, 0x1a }, - { 0x3480, 0x49 }, + { IMX290_REG_8BIT(0x3007), 0x10 }, + { IMX290_REG_8BIT(0x303a), 0x06 }, + { IMX290_REG_8BIT(0x3414), 0x04 }, + { IMX290_REG_8BIT(0x3472), 0x00 }, + { IMX290_REG_8BIT(0x3473), 0x05 }, + { IMX290_REG_8BIT(0x3418), 0xd0 }, + { IMX290_REG_8BIT(0x3419), 0x02 }, + { IMX290_REG_8BIT(0x3012), 0x64 }, + { IMX290_REG_8BIT(0x3013), 0x00 }, + { IMX290_REG_8BIT(0x305c), 0x20 }, + { IMX290_REG_8BIT(0x305d), 0x00 }, + { IMX290_REG_8BIT(0x305e), 0x20 }, + { IMX290_REG_8BIT(0x305f), 0x01 }, + { IMX290_REG_8BIT(0x315e), 0x1a }, + { IMX290_REG_8BIT(0x3164), 0x1a }, + { IMX290_REG_8BIT(0x3480), 0x49 }, /* data rate settings */ - { 0x3405, 0x10 }, - { 0x3446, 0x4f }, - { 0x3447, 0x00 }, - { 0x3448, 0x2f }, - { 0x3449, 0x00 }, - { 0x344a, 0x17 }, - { 0x344b, 0x00 }, - { 0x344c, 0x17 }, - { 0x344d, 0x00 }, - { 0x344e, 0x17 }, - { 0x344f, 0x00 }, - { 0x3450, 0x57 }, - { 0x3451, 0x00 }, - { 0x3452, 0x17 }, - { 0x3453, 0x00 }, - { 0x3454, 0x17 }, - { 0x3455, 0x00 }, + { IMX290_REG_8BIT(0x3405), 0x10 }, + { IMX290_REG_8BIT(0x3446), 0x4f }, + { IMX290_REG_8BIT(0x3447), 0x00 }, + { IMX290_REG_8BIT(0x3448), 0x2f }, + { IMX290_REG_8BIT(0x3449), 0x00 }, + { IMX290_REG_8BIT(0x344a), 0x17 }, + { IMX290_REG_8BIT(0x344b), 0x00 }, + { IMX290_REG_8BIT(0x344c), 0x17 }, + { IMX290_REG_8BIT(0x344d), 0x00 }, + { IMX290_REG_8BIT(0x344e), 0x17 }, + { IMX290_REG_8BIT(0x344f), 0x00 }, + { IMX290_REG_8BIT(0x3450), 0x57 }, + { IMX290_REG_8BIT(0x3451), 0x00 }, + { IMX290_REG_8BIT(0x3452), 0x17 }, + { IMX290_REG_8BIT(0x3453), 0x00 }, + { IMX290_REG_8BIT(0x3454), 0x17 }, + { IMX290_REG_8BIT(0x3455), 0x00 }, }; static const struct imx290_regval imx290_10bit_settings[] = { - { 0x3005, 0x00}, - { 0x3046, 0x00}, - { 0x3129, 0x1d}, - { 0x317c, 0x12}, - { 0x31ec, 0x37}, - { 0x3441, 0x0a}, - { 0x3442, 0x0a}, - { 0x300a, 0x3c}, - { 0x300b, 0x00}, + { IMX290_REG_8BIT(0x3005), 0x00}, + { IMX290_REG_8BIT(0x3046), 0x00}, + { IMX290_REG_8BIT(0x3129), 0x1d}, + { IMX290_REG_8BIT(0x317c), 0x12}, + { IMX290_REG_8BIT(0x31ec), 0x37}, + { IMX290_REG_8BIT(0x3441), 0x0a}, + { IMX290_REG_8BIT(0x3442), 0x0a}, + { IMX290_REG_8BIT(0x300a), 0x3c}, + { IMX290_REG_8BIT(0x300b), 0x00}, }; static const struct imx290_regval imx290_12bit_settings[] = { - { 0x3005, 0x01 }, - { 0x3046, 0x01 }, - { 0x3129, 0x00 }, - { 0x317c, 0x00 }, - { 0x31ec, 0x0e }, - { 0x3441, 0x0c }, - { 0x3442, 0x0c }, - { 0x300a, 0xf0 }, - { 0x300b, 0x00 }, + { IMX290_REG_8BIT(0x3005), 0x01 }, + { IMX290_REG_8BIT(0x3046), 0x01 }, + { IMX290_REG_8BIT(0x3129), 0x00 }, + { IMX290_REG_8BIT(0x317c), 0x00 }, + { IMX290_REG_8BIT(0x31ec), 0x0e }, + { IMX290_REG_8BIT(0x3441), 0x0c }, + { IMX290_REG_8BIT(0x3442), 0x0c }, + { IMX290_REG_8BIT(0x300a), 0xf0 }, + { IMX290_REG_8BIT(0x300b), 0x00 }, }; /* supported link frequencies */ @@ -362,33 +368,35 @@ static inline struct imx290 *to_imx290(struct v4l2_subdev *_sd) return container_of(_sd, struct imx290, sd); } -static inline int __always_unused imx290_read_reg(struct imx290 *imx290, u16 addr, u8 *value) +static int __always_unused imx290_read_reg(struct imx290 *imx290, u32 addr, u32 *value) { - unsigned int regval; + u8 data[3] = { 0, 0, 0 }; int ret; - ret = regmap_read(imx290->regmap, addr, ®val); - if (ret) { - dev_err(imx290->dev, "Failed to read register 0x%04x: %d\n", - addr, ret); + ret = regmap_raw_read(imx290->regmap, addr & IMX290_REG_ADDR_MASK, + data, (addr >> IMX290_REG_SIZE_SHIFT) & 3); + if (ret < 0) { + dev_err(imx290->dev, "%u-bit read from 0x%04x failed: %d\n", + ((addr >> IMX290_REG_SIZE_SHIFT) & 3) * 8, + addr & IMX290_REG_ADDR_MASK, ret); return ret; } - *value = regval & 0xff; - + *value = (data[2] << 16) | (data[1] << 8) | data[0]; return 0; } -static int imx290_write_reg(struct imx290 *imx290, u16 addr, u8 value) +static int imx290_write_reg(struct imx290 *imx290, u32 addr, u32 value) { + u8 data[3] = { value & 0xff, (value >> 8) & 0xff, value >> 16 }; int ret; - ret = regmap_write(imx290->regmap, addr, value); - if (ret) { - dev_err(imx290->dev, "Failed to write register 0x%04x: %d\n", - addr, ret); - return ret; - } + ret = regmap_raw_write(imx290->regmap, addr & IMX290_REG_ADDR_MASK, + data, (addr >> IMX290_REG_SIZE_SHIFT) & 3); + if (ret < 0) + dev_err(imx290->dev, "%u-bit write to 0x%04x failed: %d\n", + ((addr >> IMX290_REG_SIZE_SHIFT) & 3) * 8, + addr & IMX290_REG_ADDR_MASK, ret); return ret; } From 454a86f33dd0682d6f3b65cd1305643e95257d28 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Sun, 16 Oct 2022 09:15:12 +0300 Subject: [PATCH 26/47] media: i2c: imx290: Correct register sizes Define registers with the appropriate size, using the variable-size register access mechanism that has just been introduced. This simplifies the code. Signed-off-by: Laurent Pinchart Signed-off-by: Sakari Ailus --- drivers/media/i2c/imx290.c | 39 +++++++++----------------------------- 1 file changed, 9 insertions(+), 30 deletions(-) diff --git a/drivers/media/i2c/imx290.c b/drivers/media/i2c/imx290.c index dc07413d480e..1a4782469984 100644 --- a/drivers/media/i2c/imx290.c +++ b/drivers/media/i2c/imx290.c @@ -32,12 +32,11 @@ #define IMX290_REGHOLD IMX290_REG_8BIT(0x3001) #define IMX290_XMSTA IMX290_REG_8BIT(0x3002) #define IMX290_FR_FDG_SEL IMX290_REG_8BIT(0x3009) -#define IMX290_BLKLEVEL_LOW IMX290_REG_8BIT(0x300a) -#define IMX290_BLKLEVEL_HIGH IMX290_REG_8BIT(0x300b) +#define IMX290_BLKLEVEL IMX290_REG_16BIT(0x300a) #define IMX290_GAIN IMX290_REG_8BIT(0x3014) -#define IMX290_HMAX_LOW IMX290_REG_8BIT(0x301c) -#define IMX290_HMAX_HIGH IMX290_REG_8BIT(0x301d) +#define IMX290_HMAX IMX290_REG_16BIT(0x301c) #define IMX290_PGCTRL IMX290_REG_8BIT(0x308c) +#define IMX290_CHIP_ID IMX290_REG_16BIT(0x319a) #define IMX290_PHY_LANE_NUM IMX290_REG_8BIT(0x3407) #define IMX290_CSI_LANE_MODE IMX290_REG_8BIT(0x3443) @@ -461,8 +460,7 @@ static int imx290_set_ctrl(struct v4l2_ctrl *ctrl) break; case V4L2_CID_TEST_PATTERN: if (ctrl->val) { - imx290_write_reg(imx290, IMX290_BLKLEVEL_LOW, 0x00); - imx290_write_reg(imx290, IMX290_BLKLEVEL_HIGH, 0x00); + imx290_write_reg(imx290, IMX290_BLKLEVEL, 0); usleep_range(10000, 11000); imx290_write_reg(imx290, IMX290_PGCTRL, (u8)(IMX290_PGCTRL_REGEN | @@ -472,12 +470,11 @@ static int imx290_set_ctrl(struct v4l2_ctrl *ctrl) imx290_write_reg(imx290, IMX290_PGCTRL, 0x00); usleep_range(10000, 11000); if (imx290->bpp == 10) - imx290_write_reg(imx290, IMX290_BLKLEVEL_LOW, + imx290_write_reg(imx290, IMX290_BLKLEVEL, 0x3c); else /* 12 bits per pixel */ - imx290_write_reg(imx290, IMX290_BLKLEVEL_LOW, + imx290_write_reg(imx290, IMX290_BLKLEVEL, 0xf0); - imx290_write_reg(imx290, IMX290_BLKLEVEL_HIGH, 0x00); } break; default: @@ -669,25 +666,6 @@ static int imx290_write_current_format(struct imx290 *imx290) return 0; } -static int imx290_set_hmax(struct imx290 *imx290, u32 val) -{ - int ret; - - ret = imx290_write_reg(imx290, IMX290_HMAX_LOW, (val & 0xff)); - if (ret) { - dev_err(imx290->dev, "Error setting HMAX register\n"); - return ret; - } - - ret = imx290_write_reg(imx290, IMX290_HMAX_HIGH, ((val >> 8) & 0xff)); - if (ret) { - dev_err(imx290->dev, "Error setting HMAX register\n"); - return ret; - } - - return 0; -} - /* Start streaming */ static int imx290_start_streaming(struct imx290 *imx290) { @@ -716,8 +694,9 @@ static int imx290_start_streaming(struct imx290 *imx290) dev_err(imx290->dev, "Could not set current mode\n"); return ret; } - ret = imx290_set_hmax(imx290, imx290->current_mode->hmax); - if (ret < 0) + + ret = imx290_write_reg(imx290, IMX290_HMAX, imx290->current_mode->hmax); + if (ret) return ret; /* Apply customized values from user */ From e611f3dac54cae6f9a2efa61fb3dc632c399dd2c Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Sun, 16 Oct 2022 09:15:13 +0300 Subject: [PATCH 27/47] media: i2c: imx290: Simplify error handling when writing registers Error handling for register writes requires checking the error status of every single write. This makes the code complex, or incorrect when the checks are omitted. Simplify this by passing a pointer to an error code to the imx290_write_reg() function, which allows writing multiple registers in a row and only checking for errors at the end. While at it, rename imx290_write_reg() to imx290_write() as there's nothing else than registers to write, and rename imx290_read_reg() accordingly. Signed-off-by: Laurent Pinchart Reviewed-by: Alexander Stein Signed-off-by: Sakari Ailus --- drivers/media/i2c/imx290.c | 86 ++++++++++++++------------------------ 1 file changed, 32 insertions(+), 54 deletions(-) diff --git a/drivers/media/i2c/imx290.c b/drivers/media/i2c/imx290.c index 1a4782469984..eb007c40ff55 100644 --- a/drivers/media/i2c/imx290.c +++ b/drivers/media/i2c/imx290.c @@ -367,7 +367,7 @@ static inline struct imx290 *to_imx290(struct v4l2_subdev *_sd) return container_of(_sd, struct imx290, sd); } -static int __always_unused imx290_read_reg(struct imx290 *imx290, u32 addr, u32 *value) +static int __always_unused imx290_read(struct imx290 *imx290, u32 addr, u32 *value) { u8 data[3] = { 0, 0, 0 }; int ret; @@ -385,17 +385,23 @@ static int __always_unused imx290_read_reg(struct imx290 *imx290, u32 addr, u32 return 0; } -static int imx290_write_reg(struct imx290 *imx290, u32 addr, u32 value) +static int imx290_write(struct imx290 *imx290, u32 addr, u32 value, int *err) { u8 data[3] = { value & 0xff, (value >> 8) & 0xff, value >> 16 }; int ret; + if (err && *err) + return *err; + ret = regmap_raw_write(imx290->regmap, addr & IMX290_REG_ADDR_MASK, data, (addr >> IMX290_REG_SIZE_SHIFT) & 3); - if (ret < 0) + if (ret < 0) { dev_err(imx290->dev, "%u-bit write to 0x%04x failed: %d\n", ((addr >> IMX290_REG_SIZE_SHIFT) & 3) * 8, addr & IMX290_REG_ADDR_MASK, ret); + if (err) + *err = ret; + } return ret; } @@ -408,7 +414,7 @@ static int imx290_set_register_array(struct imx290 *imx290, int ret; for (i = 0; i < num_settings; ++i, ++settings) { - ret = imx290_write_reg(imx290, settings->reg, settings->val); + ret = imx290_write(imx290, settings->reg, settings->val, NULL); if (ret < 0) return ret; } @@ -419,29 +425,16 @@ static int imx290_set_register_array(struct imx290 *imx290, return 0; } -static int imx290_set_gain(struct imx290 *imx290, u32 value) -{ - int ret; - - ret = imx290_write_reg(imx290, IMX290_GAIN, value); - if (ret) - dev_err(imx290->dev, "Unable to write gain\n"); - - return ret; -} - /* Stop streaming */ static int imx290_stop_streaming(struct imx290 *imx290) { - int ret; + int ret = 0; - ret = imx290_write_reg(imx290, IMX290_STANDBY, 0x01); - if (ret < 0) - return ret; + imx290_write(imx290, IMX290_STANDBY, 0x01, &ret); msleep(30); - return imx290_write_reg(imx290, IMX290_XMSTA, 0x01); + return imx290_write(imx290, IMX290_XMSTA, 0x01, &ret); } static int imx290_set_ctrl(struct v4l2_ctrl *ctrl) @@ -456,25 +449,25 @@ static int imx290_set_ctrl(struct v4l2_ctrl *ctrl) switch (ctrl->id) { case V4L2_CID_GAIN: - ret = imx290_set_gain(imx290, ctrl->val); + ret = imx290_write(imx290, IMX290_GAIN, ctrl->val, NULL); break; case V4L2_CID_TEST_PATTERN: if (ctrl->val) { - imx290_write_reg(imx290, IMX290_BLKLEVEL, 0); + imx290_write(imx290, IMX290_BLKLEVEL, 0, &ret); usleep_range(10000, 11000); - imx290_write_reg(imx290, IMX290_PGCTRL, - (u8)(IMX290_PGCTRL_REGEN | - IMX290_PGCTRL_THRU | - IMX290_PGCTRL_MODE(ctrl->val))); + imx290_write(imx290, IMX290_PGCTRL, + (u8)(IMX290_PGCTRL_REGEN | + IMX290_PGCTRL_THRU | + IMX290_PGCTRL_MODE(ctrl->val)), &ret); } else { - imx290_write_reg(imx290, IMX290_PGCTRL, 0x00); + imx290_write(imx290, IMX290_PGCTRL, 0x00, &ret); usleep_range(10000, 11000); if (imx290->bpp == 10) - imx290_write_reg(imx290, IMX290_BLKLEVEL, - 0x3c); + imx290_write(imx290, IMX290_BLKLEVEL, 0x3c, + &ret); else /* 12 bits per pixel */ - imx290_write_reg(imx290, IMX290_BLKLEVEL, - 0xf0); + imx290_write(imx290, IMX290_BLKLEVEL, 0xf0, + &ret); } break; default: @@ -695,7 +688,8 @@ static int imx290_start_streaming(struct imx290 *imx290) return ret; } - ret = imx290_write_reg(imx290, IMX290_HMAX, imx290->current_mode->hmax); + ret = imx290_write(imx290, IMX290_HMAX, imx290->current_mode->hmax, + NULL); if (ret) return ret; @@ -706,14 +700,12 @@ static int imx290_start_streaming(struct imx290 *imx290) return ret; } - ret = imx290_write_reg(imx290, IMX290_STANDBY, 0x00); - if (ret < 0) - return ret; + imx290_write(imx290, IMX290_STANDBY, 0x00, &ret); msleep(30); /* Start streaming */ - return imx290_write_reg(imx290, IMX290_XMSTA, 0x00); + return imx290_write(imx290, IMX290_XMSTA, 0x00, &ret); } static int imx290_set_stream(struct v4l2_subdev *sd, int enable) @@ -772,27 +764,13 @@ static int imx290_set_data_lanes(struct imx290 *imx290) * validated in probe itself */ dev_err(imx290->dev, "Lane configuration not supported\n"); - ret = -EINVAL; - goto exit; + return -EINVAL; } - ret = imx290_write_reg(imx290, IMX290_PHY_LANE_NUM, laneval); - if (ret) { - dev_err(imx290->dev, "Error setting Physical Lane number register\n"); - goto exit; - } + imx290_write(imx290, IMX290_PHY_LANE_NUM, laneval, &ret); + imx290_write(imx290, IMX290_CSI_LANE_MODE, laneval, &ret); + imx290_write(imx290, IMX290_FR_FDG_SEL, frsel, &ret); - ret = imx290_write_reg(imx290, IMX290_CSI_LANE_MODE, laneval); - if (ret) { - dev_err(imx290->dev, "Error setting CSI Lane mode register\n"); - goto exit; - } - - ret = imx290_write_reg(imx290, IMX290_FR_FDG_SEL, frsel); - if (ret) - dev_err(imx290->dev, "Error setting FR/FDG SEL register\n"); - -exit: return ret; } From 79d99ae8a77e2f20d580195ba1f23ada97a002aa Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Mon, 17 Oct 2022 11:35:45 +0300 Subject: [PATCH 28/47] media: i2c: imx290: Define more register macros Define macros for all registers programmed by the driver for which documentation is available to increase readability. This starts making use of 16-bit registers in the register arrays, so the value field has to be increased to 32 bits. Signed-off-by: Laurent Pinchart Acked-by: Alexander Stein Signed-off-by: Sakari Ailus --- drivers/media/i2c/imx290.c | 219 +++++++++++++++++++++---------------- 1 file changed, 124 insertions(+), 95 deletions(-) diff --git a/drivers/media/i2c/imx290.c b/drivers/media/i2c/imx290.c index eb007c40ff55..c7b55953f5b1 100644 --- a/drivers/media/i2c/imx290.c +++ b/drivers/media/i2c/imx290.c @@ -31,14 +31,73 @@ #define IMX290_STANDBY IMX290_REG_8BIT(0x3000) #define IMX290_REGHOLD IMX290_REG_8BIT(0x3001) #define IMX290_XMSTA IMX290_REG_8BIT(0x3002) +#define IMX290_ADBIT IMX290_REG_8BIT(0x3005) +#define IMX290_ADBIT_10BIT (0 << 0) +#define IMX290_ADBIT_12BIT (1 << 0) +#define IMX290_CTRL_07 IMX290_REG_8BIT(0x3007) +#define IMX290_VREVERSE BIT(0) +#define IMX290_HREVERSE BIT(1) +#define IMX290_WINMODE_1080P (0 << 4) +#define IMX290_WINMODE_720P (1 << 4) +#define IMX290_WINMODE_CROP (4 << 4) #define IMX290_FR_FDG_SEL IMX290_REG_8BIT(0x3009) #define IMX290_BLKLEVEL IMX290_REG_16BIT(0x300a) #define IMX290_GAIN IMX290_REG_8BIT(0x3014) +#define IMX290_VMAX IMX290_REG_24BIT(0x3018) #define IMX290_HMAX IMX290_REG_16BIT(0x301c) +#define IMX290_SHS1 IMX290_REG_24BIT(0x3020) +#define IMX290_WINWV_OB IMX290_REG_8BIT(0x303a) +#define IMX290_WINPV IMX290_REG_16BIT(0x303c) +#define IMX290_WINWV IMX290_REG_16BIT(0x303e) +#define IMX290_WINPH IMX290_REG_16BIT(0x3040) +#define IMX290_WINWH IMX290_REG_16BIT(0x3042) +#define IMX290_OUT_CTRL IMX290_REG_8BIT(0x3046) +#define IMX290_ODBIT_10BIT (0 << 0) +#define IMX290_ODBIT_12BIT (1 << 0) +#define IMX290_OPORTSEL_PARALLEL (0x0 << 4) +#define IMX290_OPORTSEL_LVDS_2CH (0xd << 4) +#define IMX290_OPORTSEL_LVDS_4CH (0xe << 4) +#define IMX290_OPORTSEL_LVDS_8CH (0xf << 4) +#define IMX290_XSOUTSEL IMX290_REG_8BIT(0x304b) +#define IMX290_XSOUTSEL_XVSOUTSEL_HIGH (0 << 0) +#define IMX290_XSOUTSEL_XVSOUTSEL_VSYNC (2 << 0) +#define IMX290_XSOUTSEL_XHSOUTSEL_HIGH (0 << 2) +#define IMX290_XSOUTSEL_XHSOUTSEL_HSYNC (2 << 2) +#define IMX290_INCKSEL1 IMX290_REG_8BIT(0x305c) +#define IMX290_INCKSEL2 IMX290_REG_8BIT(0x305d) +#define IMX290_INCKSEL3 IMX290_REG_8BIT(0x305e) +#define IMX290_INCKSEL4 IMX290_REG_8BIT(0x305f) #define IMX290_PGCTRL IMX290_REG_8BIT(0x308c) +#define IMX290_ADBIT1 IMX290_REG_8BIT(0x3129) +#define IMX290_ADBIT1_10BIT 0x1d +#define IMX290_ADBIT1_12BIT 0x00 +#define IMX290_INCKSEL5 IMX290_REG_8BIT(0x315e) +#define IMX290_INCKSEL6 IMX290_REG_8BIT(0x3164) +#define IMX290_ADBIT2 IMX290_REG_8BIT(0x317c) +#define IMX290_ADBIT2_10BIT 0x12 +#define IMX290_ADBIT2_12BIT 0x00 #define IMX290_CHIP_ID IMX290_REG_16BIT(0x319a) +#define IMX290_ADBIT3 IMX290_REG_8BIT(0x31ec) +#define IMX290_ADBIT3_10BIT 0x37 +#define IMX290_ADBIT3_12BIT 0x0e +#define IMX290_REPETITION IMX290_REG_8BIT(0x3405) #define IMX290_PHY_LANE_NUM IMX290_REG_8BIT(0x3407) +#define IMX290_OPB_SIZE_V IMX290_REG_8BIT(0x3414) +#define IMX290_Y_OUT_SIZE IMX290_REG_16BIT(0x3418) +#define IMX290_CSI_DT_FMT IMX290_REG_16BIT(0x3441) +#define IMX290_CSI_DT_FMT_RAW10 0x0a0a +#define IMX290_CSI_DT_FMT_RAW12 0x0c0c #define IMX290_CSI_LANE_MODE IMX290_REG_8BIT(0x3443) +#define IMX290_EXTCK_FREQ IMX290_REG_16BIT(0x3444) +#define IMX290_TCLKPOST IMX290_REG_16BIT(0x3446) +#define IMX290_THSZERO IMX290_REG_16BIT(0x3448) +#define IMX290_THSPREPARE IMX290_REG_16BIT(0x344a) +#define IMX290_TCLKTRAIL IMX290_REG_16BIT(0x344c) +#define IMX290_THSTRAIL IMX290_REG_16BIT(0x344e) +#define IMX290_TCLKZERO IMX290_REG_16BIT(0x3450) +#define IMX290_TCLKPREPARE IMX290_REG_16BIT(0x3452) +#define IMX290_TLPX IMX290_REG_16BIT(0x3454) +#define IMX290_X_OUT_SIZE IMX290_REG_16BIT(0x3472) #define IMX290_PGCTRL_REGEN BIT(0) #define IMX290_PGCTRL_THRU BIT(1) @@ -54,7 +113,7 @@ static const char * const imx290_supply_name[] = { struct imx290_regval { u32 reg; - u8 val; + u32 val; }; struct imx290_mode { @@ -116,22 +175,16 @@ static const char * const imx290_test_pattern_menu[] = { }; static const struct imx290_regval imx290_global_init_settings[] = { - { IMX290_REG_8BIT(0x3007), 0x00 }, - { IMX290_REG_8BIT(0x3018), 0x65 }, - { IMX290_REG_8BIT(0x3019), 0x04 }, - { IMX290_REG_8BIT(0x301a), 0x00 }, - { IMX290_REG_8BIT(0x3444), 0x20 }, - { IMX290_REG_8BIT(0x3445), 0x25 }, - { IMX290_REG_8BIT(0x303a), 0x0c }, - { IMX290_REG_8BIT(0x3040), 0x00 }, - { IMX290_REG_8BIT(0x3041), 0x00 }, - { IMX290_REG_8BIT(0x303c), 0x00 }, - { IMX290_REG_8BIT(0x303d), 0x00 }, - { IMX290_REG_8BIT(0x3042), 0x9c }, - { IMX290_REG_8BIT(0x3043), 0x07 }, - { IMX290_REG_8BIT(0x303e), 0x49 }, - { IMX290_REG_8BIT(0x303f), 0x04 }, - { IMX290_REG_8BIT(0x304b), 0x0a }, + { IMX290_CTRL_07, IMX290_WINMODE_1080P }, + { IMX290_VMAX, 1125 }, + { IMX290_EXTCK_FREQ, 0x2520 }, + { IMX290_WINWV_OB, 12 }, + { IMX290_WINPH, 0 }, + { IMX290_WINPV, 0 }, + { IMX290_WINWH, 1948 }, + { IMX290_WINWV, 1097 }, + { IMX290_XSOUTSEL, IMX290_XSOUTSEL_XVSOUTSEL_VSYNC | + IMX290_XSOUTSEL_XHSOUTSEL_HSYNC }, { IMX290_REG_8BIT(0x300f), 0x00 }, { IMX290_REG_8BIT(0x3010), 0x21 }, { IMX290_REG_8BIT(0x3012), 0x64 }, @@ -177,102 +230,78 @@ static const struct imx290_regval imx290_global_init_settings[] = { static const struct imx290_regval imx290_1080p_settings[] = { /* mode settings */ - { IMX290_REG_8BIT(0x3007), 0x00 }, - { IMX290_REG_8BIT(0x303a), 0x0c }, - { IMX290_REG_8BIT(0x3414), 0x0a }, - { IMX290_REG_8BIT(0x3472), 0x80 }, - { IMX290_REG_8BIT(0x3473), 0x07 }, - { IMX290_REG_8BIT(0x3418), 0x38 }, - { IMX290_REG_8BIT(0x3419), 0x04 }, + { IMX290_CTRL_07, IMX290_WINMODE_1080P }, + { IMX290_WINWV_OB, 12 }, + { IMX290_OPB_SIZE_V, 10 }, + { IMX290_X_OUT_SIZE, 1920 }, + { IMX290_Y_OUT_SIZE, 1080 }, { IMX290_REG_8BIT(0x3012), 0x64 }, { IMX290_REG_8BIT(0x3013), 0x00 }, - { IMX290_REG_8BIT(0x305c), 0x18 }, - { IMX290_REG_8BIT(0x305d), 0x03 }, - { IMX290_REG_8BIT(0x305e), 0x20 }, - { IMX290_REG_8BIT(0x305f), 0x01 }, - { IMX290_REG_8BIT(0x315e), 0x1a }, - { IMX290_REG_8BIT(0x3164), 0x1a }, + { IMX290_INCKSEL1, 0x18 }, + { IMX290_INCKSEL2, 0x03 }, + { IMX290_INCKSEL3, 0x20 }, + { IMX290_INCKSEL4, 0x01 }, + { IMX290_INCKSEL5, 0x1a }, + { IMX290_INCKSEL6, 0x1a }, { IMX290_REG_8BIT(0x3480), 0x49 }, /* data rate settings */ - { IMX290_REG_8BIT(0x3405), 0x10 }, - { IMX290_REG_8BIT(0x3446), 0x57 }, - { IMX290_REG_8BIT(0x3447), 0x00 }, - { IMX290_REG_8BIT(0x3448), 0x37 }, - { IMX290_REG_8BIT(0x3449), 0x00 }, - { IMX290_REG_8BIT(0x344a), 0x1f }, - { IMX290_REG_8BIT(0x344b), 0x00 }, - { IMX290_REG_8BIT(0x344c), 0x1f }, - { IMX290_REG_8BIT(0x344d), 0x00 }, - { IMX290_REG_8BIT(0x344e), 0x1f }, - { IMX290_REG_8BIT(0x344f), 0x00 }, - { IMX290_REG_8BIT(0x3450), 0x77 }, - { IMX290_REG_8BIT(0x3451), 0x00 }, - { IMX290_REG_8BIT(0x3452), 0x1f }, - { IMX290_REG_8BIT(0x3453), 0x00 }, - { IMX290_REG_8BIT(0x3454), 0x17 }, - { IMX290_REG_8BIT(0x3455), 0x00 }, + { IMX290_REPETITION, 0x10 }, + { IMX290_TCLKPOST, 87 }, + { IMX290_THSZERO, 55 }, + { IMX290_THSPREPARE, 31 }, + { IMX290_TCLKTRAIL, 31 }, + { IMX290_THSTRAIL, 31 }, + { IMX290_TCLKZERO, 119 }, + { IMX290_TCLKPREPARE, 31 }, + { IMX290_TLPX, 23 }, }; static const struct imx290_regval imx290_720p_settings[] = { /* mode settings */ - { IMX290_REG_8BIT(0x3007), 0x10 }, - { IMX290_REG_8BIT(0x303a), 0x06 }, - { IMX290_REG_8BIT(0x3414), 0x04 }, - { IMX290_REG_8BIT(0x3472), 0x00 }, - { IMX290_REG_8BIT(0x3473), 0x05 }, - { IMX290_REG_8BIT(0x3418), 0xd0 }, - { IMX290_REG_8BIT(0x3419), 0x02 }, + { IMX290_CTRL_07, IMX290_WINMODE_720P }, + { IMX290_WINWV_OB, 6 }, + { IMX290_OPB_SIZE_V, 4 }, + { IMX290_X_OUT_SIZE, 1280 }, + { IMX290_Y_OUT_SIZE, 720 }, { IMX290_REG_8BIT(0x3012), 0x64 }, { IMX290_REG_8BIT(0x3013), 0x00 }, - { IMX290_REG_8BIT(0x305c), 0x20 }, - { IMX290_REG_8BIT(0x305d), 0x00 }, - { IMX290_REG_8BIT(0x305e), 0x20 }, - { IMX290_REG_8BIT(0x305f), 0x01 }, - { IMX290_REG_8BIT(0x315e), 0x1a }, - { IMX290_REG_8BIT(0x3164), 0x1a }, + { IMX290_INCKSEL1, 0x20 }, + { IMX290_INCKSEL2, 0x00 }, + { IMX290_INCKSEL3, 0x20 }, + { IMX290_INCKSEL4, 0x01 }, + { IMX290_INCKSEL5, 0x1a }, + { IMX290_INCKSEL6, 0x1a }, { IMX290_REG_8BIT(0x3480), 0x49 }, /* data rate settings */ - { IMX290_REG_8BIT(0x3405), 0x10 }, - { IMX290_REG_8BIT(0x3446), 0x4f }, - { IMX290_REG_8BIT(0x3447), 0x00 }, - { IMX290_REG_8BIT(0x3448), 0x2f }, - { IMX290_REG_8BIT(0x3449), 0x00 }, - { IMX290_REG_8BIT(0x344a), 0x17 }, - { IMX290_REG_8BIT(0x344b), 0x00 }, - { IMX290_REG_8BIT(0x344c), 0x17 }, - { IMX290_REG_8BIT(0x344d), 0x00 }, - { IMX290_REG_8BIT(0x344e), 0x17 }, - { IMX290_REG_8BIT(0x344f), 0x00 }, - { IMX290_REG_8BIT(0x3450), 0x57 }, - { IMX290_REG_8BIT(0x3451), 0x00 }, - { IMX290_REG_8BIT(0x3452), 0x17 }, - { IMX290_REG_8BIT(0x3453), 0x00 }, - { IMX290_REG_8BIT(0x3454), 0x17 }, - { IMX290_REG_8BIT(0x3455), 0x00 }, + { IMX290_REPETITION, 0x10 }, + { IMX290_TCLKPOST, 79 }, + { IMX290_THSZERO, 47 }, + { IMX290_THSPREPARE, 23 }, + { IMX290_TCLKTRAIL, 23 }, + { IMX290_THSTRAIL, 23 }, + { IMX290_TCLKZERO, 87 }, + { IMX290_TCLKPREPARE, 23 }, + { IMX290_TLPX, 23 }, }; static const struct imx290_regval imx290_10bit_settings[] = { - { IMX290_REG_8BIT(0x3005), 0x00}, - { IMX290_REG_8BIT(0x3046), 0x00}, - { IMX290_REG_8BIT(0x3129), 0x1d}, - { IMX290_REG_8BIT(0x317c), 0x12}, - { IMX290_REG_8BIT(0x31ec), 0x37}, - { IMX290_REG_8BIT(0x3441), 0x0a}, - { IMX290_REG_8BIT(0x3442), 0x0a}, - { IMX290_REG_8BIT(0x300a), 0x3c}, - { IMX290_REG_8BIT(0x300b), 0x00}, + { IMX290_ADBIT, IMX290_ADBIT_10BIT }, + { IMX290_OUT_CTRL, IMX290_ODBIT_10BIT }, + { IMX290_ADBIT1, IMX290_ADBIT1_10BIT }, + { IMX290_ADBIT2, IMX290_ADBIT2_10BIT }, + { IMX290_ADBIT3, IMX290_ADBIT3_10BIT }, + { IMX290_CSI_DT_FMT, IMX290_CSI_DT_FMT_RAW10 }, + { IMX290_BLKLEVEL, 60 }, }; static const struct imx290_regval imx290_12bit_settings[] = { - { IMX290_REG_8BIT(0x3005), 0x01 }, - { IMX290_REG_8BIT(0x3046), 0x01 }, - { IMX290_REG_8BIT(0x3129), 0x00 }, - { IMX290_REG_8BIT(0x317c), 0x00 }, - { IMX290_REG_8BIT(0x31ec), 0x0e }, - { IMX290_REG_8BIT(0x3441), 0x0c }, - { IMX290_REG_8BIT(0x3442), 0x0c }, - { IMX290_REG_8BIT(0x300a), 0xf0 }, - { IMX290_REG_8BIT(0x300b), 0x00 }, + { IMX290_ADBIT, IMX290_ADBIT_12BIT }, + { IMX290_OUT_CTRL, IMX290_ODBIT_12BIT }, + { IMX290_ADBIT1, IMX290_ADBIT1_12BIT }, + { IMX290_ADBIT2, IMX290_ADBIT2_12BIT }, + { IMX290_ADBIT3, IMX290_ADBIT3_12BIT }, + { IMX290_CSI_DT_FMT, IMX290_CSI_DT_FMT_RAW12 }, + { IMX290_BLKLEVEL, 240 }, }; /* supported link frequencies */ From 827c7e69cb2d70bf89b6b735203430a7dffe617a Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Sun, 16 Oct 2022 09:15:15 +0300 Subject: [PATCH 29/47] media: i2c: imx290: Add exposure time control Support configuring the exposure time, which is expressed as the complement of the exposure time (frame period minus integration time). The frame period is currently fixed. Signed-off-by: Laurent Pinchart Acked-by: Alexander Stein Reviewed-by: Dave Stevenson Signed-off-by: Sakari Ailus --- drivers/media/i2c/imx290.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/drivers/media/i2c/imx290.c b/drivers/media/i2c/imx290.c index c7b55953f5b1..8c38119b9208 100644 --- a/drivers/media/i2c/imx290.c +++ b/drivers/media/i2c/imx290.c @@ -103,6 +103,8 @@ #define IMX290_PGCTRL_THRU BIT(1) #define IMX290_PGCTRL_MODE(n) ((n) << 4) +#define IMX290_VMAX_DEFAULT 1125 + static const char * const imx290_supply_name[] = { "vdda", "vddd", @@ -176,7 +178,7 @@ static const char * const imx290_test_pattern_menu[] = { static const struct imx290_regval imx290_global_init_settings[] = { { IMX290_CTRL_07, IMX290_WINMODE_1080P }, - { IMX290_VMAX, 1125 }, + { IMX290_VMAX, IMX290_VMAX_DEFAULT }, { IMX290_EXTCK_FREQ, 0x2520 }, { IMX290_WINWV_OB, 12 }, { IMX290_WINPH, 0 }, @@ -480,6 +482,12 @@ static int imx290_set_ctrl(struct v4l2_ctrl *ctrl) case V4L2_CID_GAIN: ret = imx290_write(imx290, IMX290_GAIN, ctrl->val, NULL); break; + + case V4L2_CID_EXPOSURE: + ret = imx290_write(imx290, IMX290_SHS1, + IMX290_VMAX_DEFAULT - ctrl->val - 1, NULL); + break; + case V4L2_CID_TEST_PATTERN: if (ctrl->val) { imx290_write(imx290, IMX290_BLKLEVEL, 0, &ret); @@ -1008,12 +1016,16 @@ static int imx290_probe(struct i2c_client *client) */ imx290_entity_init_cfg(&imx290->sd, NULL); - v4l2_ctrl_handler_init(&imx290->ctrls, 4); + v4l2_ctrl_handler_init(&imx290->ctrls, 5); imx290->ctrls.lock = &imx290->lock; v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops, V4L2_CID_GAIN, 0, 72, 1, 0); + v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops, + V4L2_CID_EXPOSURE, 1, IMX290_VMAX_DEFAULT - 2, 1, + IMX290_VMAX_DEFAULT - 2); + imx290->link_freq = v4l2_ctrl_new_int_menu(&imx290->ctrls, &imx290_ctrl_ops, V4L2_CID_LINK_FREQ, From 6d7a87f2d3a68349d7d828720345239800949fb7 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Sun, 16 Oct 2022 09:15:16 +0300 Subject: [PATCH 30/47] media: i2c: imx290: Fix max gain value The gain is expressed in multiple of 0.3dB, as a value between 0.0dB and 72.0dB. Gains between 0.0dB and 30.0dB (included) apply analog gain only, higher gains from 30.3dB to 72dB apply additional digital gain. The maximum gain value is erroneously set to 72. Increase it to 100 to cover the whole analog gain range. Support for digital gain can be added separately if needed. The IMX327 and IMX462 are largely compatible with the IMX290, but have an analog gain range of 0.0dB to 29.4dB and 42dB of digital gain. When support for those sensors gets added to the driver, the gain control should be adjusted accordingly. Signed-off-by: Laurent Pinchart Reviewed-by: Alexander Stein Signed-off-by: Sakari Ailus --- drivers/media/i2c/imx290.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/drivers/media/i2c/imx290.c b/drivers/media/i2c/imx290.c index 8c38119b9208..d21b4fd2ff16 100644 --- a/drivers/media/i2c/imx290.c +++ b/drivers/media/i2c/imx290.c @@ -1019,8 +1019,21 @@ static int imx290_probe(struct i2c_client *client) v4l2_ctrl_handler_init(&imx290->ctrls, 5); imx290->ctrls.lock = &imx290->lock; + /* + * The sensor has an analog gain and a digital gain, both controlled + * through a single gain value, expressed in 0.3dB increments. Values + * from 0.0dB (0) to 30.0dB (100) apply analog gain only, higher values + * up to 72.0dB (240) add further digital gain. Limit the range to + * analog gain only, support for digital gain can be added separately + * if needed. + * + * The IMX327 and IMX462 are largely compatible with the IMX290, but + * have an analog gain range of 0.0dB to 29.4dB and 42dB of digital + * gain. When support for those sensors gets added to the driver, the + * gain control should be adjusted accordingly. + */ v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops, - V4L2_CID_GAIN, 0, 72, 1, 0); + V4L2_CID_GAIN, 0, 100, 1, 0); v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops, V4L2_CID_EXPOSURE, 1, IMX290_VMAX_DEFAULT - 2, 1, From 72c87b7ad5602c037fe27b3ad3894f9c70997056 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Sun, 16 Oct 2022 09:15:17 +0300 Subject: [PATCH 31/47] media: i2c: imx290: Split control initialization to separate function The imx290_probe() function is too large. Split control initialzation to a dedicated function to increase code readability. Signed-off-by: Laurent Pinchart Acked-by: Alexander Stein Signed-off-by: Sakari Ailus --- drivers/media/i2c/imx290.c | 109 +++++++++++++++++++++---------------- 1 file changed, 61 insertions(+), 48 deletions(-) diff --git a/drivers/media/i2c/imx290.c b/drivers/media/i2c/imx290.c index d21b4fd2ff16..98ffafd631fa 100644 --- a/drivers/media/i2c/imx290.c +++ b/drivers/media/i2c/imx290.c @@ -878,6 +878,62 @@ static const struct media_entity_operations imx290_subdev_entity_ops = { .link_validate = v4l2_subdev_link_validate, }; +static int imx290_ctrl_init(struct imx290 *imx290) +{ + int ret; + + v4l2_ctrl_handler_init(&imx290->ctrls, 5); + imx290->ctrls.lock = &imx290->lock; + + /* + * The sensor has an analog gain and a digital gain, both controlled + * through a single gain value, expressed in 0.3dB increments. Values + * from 0.0dB (0) to 30.0dB (100) apply analog gain only, higher values + * up to 72.0dB (240) add further digital gain. Limit the range to + * analog gain only, support for digital gain can be added separately + * if needed. + * + * The IMX327 and IMX462 are largely compatible with the IMX290, but + * have an analog gain range of 0.0dB to 29.4dB and 42dB of digital + * gain. When support for those sensors gets added to the driver, the + * gain control should be adjusted accordingly. + */ + v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops, + V4L2_CID_GAIN, 0, 100, 1, 0); + + v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops, + V4L2_CID_EXPOSURE, 1, IMX290_VMAX_DEFAULT - 2, 1, + IMX290_VMAX_DEFAULT - 2); + + imx290->link_freq = + v4l2_ctrl_new_int_menu(&imx290->ctrls, &imx290_ctrl_ops, + V4L2_CID_LINK_FREQ, + imx290_link_freqs_num(imx290) - 1, 0, + imx290_link_freqs_ptr(imx290)); + if (imx290->link_freq) + imx290->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + imx290->pixel_rate = v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops, + V4L2_CID_PIXEL_RATE, + 1, INT_MAX, 1, + imx290_calc_pixel_rate(imx290)); + + v4l2_ctrl_new_std_menu_items(&imx290->ctrls, &imx290_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(imx290_test_pattern_menu) - 1, + 0, 0, imx290_test_pattern_menu); + + imx290->sd.ctrl_handler = &imx290->ctrls; + + if (imx290->ctrls.error) { + ret = imx290->ctrls.error; + v4l2_ctrl_handler_free(&imx290->ctrls); + return ret; + } + + return 0; +} + /* * Returns 0 if all link frequencies used by the driver for the given number * of MIPI data lanes are mentioned in the device tree, or the value of the @@ -1016,54 +1072,10 @@ static int imx290_probe(struct i2c_client *client) */ imx290_entity_init_cfg(&imx290->sd, NULL); - v4l2_ctrl_handler_init(&imx290->ctrls, 5); - imx290->ctrls.lock = &imx290->lock; - - /* - * The sensor has an analog gain and a digital gain, both controlled - * through a single gain value, expressed in 0.3dB increments. Values - * from 0.0dB (0) to 30.0dB (100) apply analog gain only, higher values - * up to 72.0dB (240) add further digital gain. Limit the range to - * analog gain only, support for digital gain can be added separately - * if needed. - * - * The IMX327 and IMX462 are largely compatible with the IMX290, but - * have an analog gain range of 0.0dB to 29.4dB and 42dB of digital - * gain. When support for those sensors gets added to the driver, the - * gain control should be adjusted accordingly. - */ - v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops, - V4L2_CID_GAIN, 0, 100, 1, 0); - - v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops, - V4L2_CID_EXPOSURE, 1, IMX290_VMAX_DEFAULT - 2, 1, - IMX290_VMAX_DEFAULT - 2); - - imx290->link_freq = - v4l2_ctrl_new_int_menu(&imx290->ctrls, &imx290_ctrl_ops, - V4L2_CID_LINK_FREQ, - imx290_link_freqs_num(imx290) - 1, 0, - imx290_link_freqs_ptr(imx290)); - if (imx290->link_freq) - imx290->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; - - imx290->pixel_rate = v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops, - V4L2_CID_PIXEL_RATE, - 1, INT_MAX, 1, - imx290_calc_pixel_rate(imx290)); - - v4l2_ctrl_new_std_menu_items(&imx290->ctrls, &imx290_ctrl_ops, - V4L2_CID_TEST_PATTERN, - ARRAY_SIZE(imx290_test_pattern_menu) - 1, - 0, 0, imx290_test_pattern_menu); - - imx290->sd.ctrl_handler = &imx290->ctrls; - - if (imx290->ctrls.error) { - dev_err(dev, "Control initialization error %d\n", - imx290->ctrls.error); - ret = imx290->ctrls.error; - goto free_ctrl; + ret = imx290_ctrl_init(imx290); + if (ret < 0) { + dev_err(dev, "Control initialization error %d\n", ret); + goto free_mutex; } v4l2_i2c_subdev_init(&imx290->sd, client, &imx290_subdev_ops); @@ -1104,6 +1116,7 @@ static int imx290_probe(struct i2c_client *client) media_entity_cleanup(&imx290->sd.entity); free_ctrl: v4l2_ctrl_handler_free(&imx290->ctrls); +free_mutex: mutex_destroy(&imx290->lock); free_err: v4l2_fwnode_endpoint_free(&ep); From 0c3b56c905e391345d3fdafc53d5ca1d39219b9f Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Sun, 16 Oct 2022 09:15:18 +0300 Subject: [PATCH 32/47] media: i2c: imx290: Implement HBLANK and VBLANK controls Add support for the V4L2_CID_HBLANK and V4L2_CID_VBLANK controls to the imx290 driver. Make the controls read-only to start with, to report the values to userspace for timing calculation. Signed-off-by: Laurent Pinchart Signed-off-by: Sakari Ailus --- drivers/media/i2c/imx290.c | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/drivers/media/i2c/imx290.c b/drivers/media/i2c/imx290.c index 98ffafd631fa..d3acf5519789 100644 --- a/drivers/media/i2c/imx290.c +++ b/drivers/media/i2c/imx290.c @@ -146,6 +146,8 @@ struct imx290 { struct v4l2_ctrl_handler ctrls; struct v4l2_ctrl *link_freq; struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *vblank; struct mutex lock; }; @@ -642,6 +644,20 @@ static int imx290_set_fmt(struct v4l2_subdev *sd, if (imx290->pixel_rate) __v4l2_ctrl_s_ctrl_int64(imx290->pixel_rate, imx290_calc_pixel_rate(imx290)); + + if (imx290->hblank) { + unsigned int hblank = mode->hmax - mode->width; + + __v4l2_ctrl_modify_range(imx290->hblank, hblank, hblank, + 1, hblank); + } + + if (imx290->vblank) { + unsigned int vblank = IMX290_VMAX_DEFAULT - mode->height; + + __v4l2_ctrl_modify_range(imx290->vblank, vblank, vblank, + 1, vblank); + } } *format = fmt->format; @@ -880,9 +896,10 @@ static const struct media_entity_operations imx290_subdev_entity_ops = { static int imx290_ctrl_init(struct imx290 *imx290) { + unsigned int blank; int ret; - v4l2_ctrl_handler_init(&imx290->ctrls, 5); + v4l2_ctrl_handler_init(&imx290->ctrls, 7); imx290->ctrls.lock = &imx290->lock; /* @@ -923,6 +940,20 @@ static int imx290_ctrl_init(struct imx290 *imx290) ARRAY_SIZE(imx290_test_pattern_menu) - 1, 0, 0, imx290_test_pattern_menu); + blank = imx290->current_mode->hmax - imx290->current_mode->width; + imx290->hblank = v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops, + V4L2_CID_HBLANK, blank, blank, 1, + blank); + if (imx290->hblank) + imx290->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + blank = IMX290_VMAX_DEFAULT - imx290->current_mode->height; + imx290->vblank = v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops, + V4L2_CID_VBLANK, blank, blank, 1, + blank); + if (imx290->vblank) + imx290->vblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + imx290->sd.ctrl_handler = &imx290->ctrls; if (imx290->ctrls.error) { From 4c9c93cf8657a66fc8008c12238939df6514f052 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Sun, 16 Oct 2022 09:15:19 +0300 Subject: [PATCH 33/47] media: i2c: imx290: Create controls for fwnode properties Create the V4L2_CID_ORIENTATION and V4L2_CID_ROTATION controls to expose the corresponding fwnode properties. Signed-off-by: Laurent Pinchart Acked-by: Alexander Stein Signed-off-by: Sakari Ailus --- drivers/media/i2c/imx290.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/media/i2c/imx290.c b/drivers/media/i2c/imx290.c index d3acf5519789..d2dfda8de6a8 100644 --- a/drivers/media/i2c/imx290.c +++ b/drivers/media/i2c/imx290.c @@ -896,10 +896,15 @@ static const struct media_entity_operations imx290_subdev_entity_ops = { static int imx290_ctrl_init(struct imx290 *imx290) { + struct v4l2_fwnode_device_properties props; unsigned int blank; int ret; - v4l2_ctrl_handler_init(&imx290->ctrls, 7); + ret = v4l2_fwnode_device_parse(imx290->dev, &props); + if (ret < 0) + return ret; + + v4l2_ctrl_handler_init(&imx290->ctrls, 9); imx290->ctrls.lock = &imx290->lock; /* @@ -954,6 +959,9 @@ static int imx290_ctrl_init(struct imx290 *imx290) if (imx290->vblank) imx290->vblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + v4l2_ctrl_new_fwnode_properties(&imx290->ctrls, &imx290_ctrl_ops, + &props); + imx290->sd.ctrl_handler = &imx290->ctrls; if (imx290->ctrls.error) { From 0b274ef2208dc8fbc09f43cdee99f06e644a9bc5 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Sun, 16 Oct 2022 09:15:20 +0300 Subject: [PATCH 34/47] media: i2c: imx290: Move registers with fixed value to init array Registers 0x3012, 0x3013 and 0x3480 are not documented and are set in the per-mode register arrays with values indentical for all modes. Move them to the common array. Signed-off-by: Laurent Pinchart Acked-by: Alexander Stein Signed-off-by: Sakari Ailus --- drivers/media/i2c/imx290.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/media/i2c/imx290.c b/drivers/media/i2c/imx290.c index d2dfda8de6a8..ba46d321a83a 100644 --- a/drivers/media/i2c/imx290.c +++ b/drivers/media/i2c/imx290.c @@ -192,6 +192,7 @@ static const struct imx290_regval imx290_global_init_settings[] = { { IMX290_REG_8BIT(0x300f), 0x00 }, { IMX290_REG_8BIT(0x3010), 0x21 }, { IMX290_REG_8BIT(0x3012), 0x64 }, + { IMX290_REG_8BIT(0x3013), 0x00 }, { IMX290_REG_8BIT(0x3016), 0x09 }, { IMX290_REG_8BIT(0x3070), 0x02 }, { IMX290_REG_8BIT(0x3071), 0x11 }, @@ -230,6 +231,7 @@ static const struct imx290_regval imx290_global_init_settings[] = { { IMX290_REG_8BIT(0x33b0), 0x50 }, { IMX290_REG_8BIT(0x33b2), 0x1a }, { IMX290_REG_8BIT(0x33b3), 0x04 }, + { IMX290_REG_8BIT(0x3480), 0x49 }, }; static const struct imx290_regval imx290_1080p_settings[] = { @@ -239,15 +241,12 @@ static const struct imx290_regval imx290_1080p_settings[] = { { IMX290_OPB_SIZE_V, 10 }, { IMX290_X_OUT_SIZE, 1920 }, { IMX290_Y_OUT_SIZE, 1080 }, - { IMX290_REG_8BIT(0x3012), 0x64 }, - { IMX290_REG_8BIT(0x3013), 0x00 }, { IMX290_INCKSEL1, 0x18 }, { IMX290_INCKSEL2, 0x03 }, { IMX290_INCKSEL3, 0x20 }, { IMX290_INCKSEL4, 0x01 }, { IMX290_INCKSEL5, 0x1a }, { IMX290_INCKSEL6, 0x1a }, - { IMX290_REG_8BIT(0x3480), 0x49 }, /* data rate settings */ { IMX290_REPETITION, 0x10 }, { IMX290_TCLKPOST, 87 }, @@ -267,15 +266,12 @@ static const struct imx290_regval imx290_720p_settings[] = { { IMX290_OPB_SIZE_V, 4 }, { IMX290_X_OUT_SIZE, 1280 }, { IMX290_Y_OUT_SIZE, 720 }, - { IMX290_REG_8BIT(0x3012), 0x64 }, - { IMX290_REG_8BIT(0x3013), 0x00 }, { IMX290_INCKSEL1, 0x20 }, { IMX290_INCKSEL2, 0x00 }, { IMX290_INCKSEL3, 0x20 }, { IMX290_INCKSEL4, 0x01 }, { IMX290_INCKSEL5, 0x1a }, { IMX290_INCKSEL6, 0x1a }, - { IMX290_REG_8BIT(0x3480), 0x49 }, /* data rate settings */ { IMX290_REPETITION, 0x10 }, { IMX290_TCLKPOST, 79 }, From b25537efeea981b9a3c99c4666e8d240833993f6 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Sun, 16 Oct 2022 09:15:21 +0300 Subject: [PATCH 35/47] media: i2c: imx290: Factor out format retrieval to separate function The driver duplicates the same pattern to access the try or active format in multiple locations. Factor it out to a separate function. Signed-off-by: Laurent Pinchart Signed-off-by: Sakari Ailus --- drivers/media/i2c/imx290.c | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/drivers/media/i2c/imx290.c b/drivers/media/i2c/imx290.c index ba46d321a83a..0af5ad8a824d 100644 --- a/drivers/media/i2c/imx290.c +++ b/drivers/media/i2c/imx290.c @@ -519,6 +519,16 @@ static const struct v4l2_ctrl_ops imx290_ctrl_ops = { .s_ctrl = imx290_set_ctrl, }; +static struct v4l2_mbus_framefmt * +imx290_get_pad_format(struct imx290 *imx290, struct v4l2_subdev_state *state, + u32 which) +{ + if (which == V4L2_SUBDEV_FORMAT_ACTIVE) + return &imx290->current_format; + else + return v4l2_subdev_get_try_format(&imx290->sd, state, 0); +} + static int imx290_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_mbus_code_enum *code) @@ -562,12 +572,7 @@ static int imx290_get_fmt(struct v4l2_subdev *sd, mutex_lock(&imx290->lock); - if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) - framefmt = v4l2_subdev_get_try_format(&imx290->sd, sd_state, - fmt->pad); - else - framefmt = &imx290->current_format; - + framefmt = imx290_get_pad_format(imx290, sd_state, fmt->which); fmt->format = *framefmt; mutex_unlock(&imx290->lock); @@ -627,10 +632,9 @@ static int imx290_set_fmt(struct v4l2_subdev *sd, fmt->format.code = imx290_formats[i].code; fmt->format.field = V4L2_FIELD_NONE; - if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { - format = v4l2_subdev_get_try_format(sd, sd_state, fmt->pad); - } else { - format = &imx290->current_format; + format = imx290_get_pad_format(imx290, sd_state, fmt->which); + + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { imx290->current_mode = mode; imx290->bpp = imx290_formats[i].bpp; From b4ab57b07c5b9cac29f766ffc7453aef7a0ef2da Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Sun, 16 Oct 2022 09:15:22 +0300 Subject: [PATCH 36/47] media: i2c: imx290: Add crop selection targets support Implement read-only access to crop selection rectangles to expose the analogue crop rectangle. The public (leaked) IMX290 documentation is not very clear on how cropping is implemented and configured exactly, so the margins may not be entirely accurate. Signed-off-by: Laurent Pinchart Signed-off-by: Sakari Ailus --- drivers/media/i2c/imx290.c | 94 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/drivers/media/i2c/imx290.c b/drivers/media/i2c/imx290.c index 0af5ad8a824d..0f74a89c9196 100644 --- a/drivers/media/i2c/imx290.c +++ b/drivers/media/i2c/imx290.c @@ -105,6 +105,53 @@ #define IMX290_VMAX_DEFAULT 1125 + +/* + * The IMX290 pixel array is organized as follows: + * + * +------------------------------------+ + * | Optical Black | } Vertical effective optical black (10) + * +---+------------------------------------+---+ + * | | | | } Effective top margin (8) + * | | +----------------------------+ | | \ + * | | | | | | | + * | | | | | | | + * | | | | | | | + * | | | Recording Pixel Area | | | | Recommended height (1080) + * | | | | | | | + * | | | | | | | + * | | | | | | | + * | | +----------------------------+ | | / + * | | | | } Effective bottom margin (9) + * +---+------------------------------------+---+ + * <-> <-> <--------------------------> <-> <-> + * \---- Ignored right margin (4) + * \-------- Effective right margin (9) + * \------------------------- Recommended width (1920) + * \----------------------------------------- Effective left margin (8) + * \--------------------------------------------- Ignored left margin (4) + * + * The optical black lines are output over CSI-2 with a separate data type. + * + * The pixel array is meant to have 1920x1080 usable pixels after image + * processing in an ISP. It has 8 (9) extra active pixels usable for color + * processing in the ISP on the top and left (bottom and right) sides of the + * image. In addition, 4 additional pixels are present on the left and right + * sides of the image, documented as "ignored area". + * + * As far as is understood, all pixels of the pixel array (ignored area, color + * processing margins and recording area) can be output by the sensor. + */ + +#define IMX290_PIXEL_ARRAY_WIDTH 1945 +#define IMX290_PIXEL_ARRAY_HEIGHT 1097 +#define IMX920_PIXEL_ARRAY_MARGIN_LEFT 12 +#define IMX920_PIXEL_ARRAY_MARGIN_RIGHT 13 +#define IMX920_PIXEL_ARRAY_MARGIN_TOP 8 +#define IMX920_PIXEL_ARRAY_MARGIN_BOTTOM 9 +#define IMX290_PIXEL_ARRAY_RECORDING_WIDTH 1920 +#define IMX290_PIXEL_ARRAY_RECORDING_HEIGHT 1080 + static const char * const imx290_supply_name[] = { "vdda", "vddd", @@ -667,6 +714,52 @@ static int imx290_set_fmt(struct v4l2_subdev *sd, return 0; } +static int imx290_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct imx290 *imx290 = to_imx290(sd); + struct v4l2_mbus_framefmt *format; + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: { + format = imx290_get_pad_format(imx290, sd_state, sel->which); + + mutex_lock(&imx290->lock); + + sel->r.top = IMX920_PIXEL_ARRAY_MARGIN_TOP + + (IMX290_PIXEL_ARRAY_RECORDING_HEIGHT - format->height) / 2; + sel->r.left = IMX920_PIXEL_ARRAY_MARGIN_LEFT + + (IMX290_PIXEL_ARRAY_RECORDING_WIDTH - format->width) / 2; + sel->r.width = format->width; + sel->r.height = format->height; + + mutex_unlock(&imx290->lock); + return 0; + } + + case V4L2_SEL_TGT_NATIVE_SIZE: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = IMX290_PIXEL_ARRAY_WIDTH; + sel->r.height = IMX290_PIXEL_ARRAY_HEIGHT; + + return 0; + + case V4L2_SEL_TGT_CROP_DEFAULT: + sel->r.top = IMX920_PIXEL_ARRAY_MARGIN_TOP; + sel->r.left = IMX920_PIXEL_ARRAY_MARGIN_LEFT; + sel->r.width = IMX290_PIXEL_ARRAY_RECORDING_WIDTH; + sel->r.height = IMX290_PIXEL_ARRAY_RECORDING_HEIGHT; + + return 0; + + default: + return -EINVAL; + } +} + static int imx290_entity_init_cfg(struct v4l2_subdev *subdev, struct v4l2_subdev_state *sd_state) { @@ -883,6 +976,7 @@ static const struct v4l2_subdev_pad_ops imx290_pad_ops = { .enum_frame_size = imx290_enum_frame_size, .get_fmt = imx290_get_fmt, .set_fmt = imx290_set_fmt, + .get_selection = imx290_get_selection, }; static const struct v4l2_subdev_ops imx290_subdev_ops = { From 3dd10515a1d9de734e2ec57f837b9f1e510c5363 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Sun, 16 Oct 2022 09:15:23 +0300 Subject: [PATCH 37/47] media: i2c: imx290: Replace GAIN control with ANALOGUE_GAIN The IMX290 gain register controls the analogue gain. Replace the V4L2_CID_GAIN control with V4L2_CID_ANALOGUE_GAIN. Signed-off-by: Laurent Pinchart Reviewed-by: Alexander Stein Signed-off-by: Sakari Ailus --- drivers/media/i2c/imx290.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/media/i2c/imx290.c b/drivers/media/i2c/imx290.c index 0f74a89c9196..218ded13fd80 100644 --- a/drivers/media/i2c/imx290.c +++ b/drivers/media/i2c/imx290.c @@ -524,7 +524,7 @@ static int imx290_set_ctrl(struct v4l2_ctrl *ctrl) return 0; switch (ctrl->id) { - case V4L2_CID_GAIN: + case V4L2_CID_ANALOGUE_GAIN: ret = imx290_write(imx290, IMX290_GAIN, ctrl->val, NULL); break; @@ -1015,7 +1015,7 @@ static int imx290_ctrl_init(struct imx290 *imx290) * gain control should be adjusted accordingly. */ v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops, - V4L2_CID_GAIN, 0, 100, 1, 0); + V4L2_CID_ANALOGUE_GAIN, 0, 100, 1, 0); v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops, V4L2_CID_EXPOSURE, 1, IMX290_VMAX_DEFAULT - 2, 1, From 5f9a089b6de34655318afe8e544d9a9cc0fc1d29 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Mon, 17 Oct 2022 10:23:28 +0300 Subject: [PATCH 38/47] dw9768: Enable low-power probe on ACPI Add support for low-power probe to the driver. Also fix runtime PM API usage in the driver. Much of the hassle comes from different factors affecting device power states during probe for ACPI and DT. Signed-off-by: Sakari Ailus Fixes: 859891228e56 ("media: i2c: dw9768: Add DW9768 VCM driver") --- drivers/media/i2c/dw9768.c | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/drivers/media/i2c/dw9768.c b/drivers/media/i2c/dw9768.c index 0f47ef015a1d..83a3ee275bbe 100644 --- a/drivers/media/i2c/dw9768.c +++ b/drivers/media/i2c/dw9768.c @@ -414,6 +414,7 @@ static int dw9768_probe(struct i2c_client *client) { struct device *dev = &client->dev; struct dw9768 *dw9768; + bool full_power; unsigned int i; int ret; @@ -469,13 +470,23 @@ static int dw9768_probe(struct i2c_client *client) dw9768->sd.entity.function = MEDIA_ENT_F_LENS; + /* + * Figure out whether we're going to power up the device here. Generally + * this is done if CONFIG_PM is disabled in a DT system or the device is + * to be powered on in an ACPI system. Similarly for power off in + * remove. + */ pm_runtime_enable(dev); - if (!pm_runtime_enabled(dev)) { + full_power = (is_acpi_node(dev_fwnode(dev)) && + acpi_dev_state_d0(dev)) || + (is_of_node(dev_fwnode(dev)) && !pm_runtime_enabled(dev)); + if (full_power) { ret = dw9768_runtime_resume(dev); if (ret < 0) { dev_err(dev, "failed to power on: %d\n", ret); goto err_clean_entity; } + pm_runtime_set_active(dev); } ret = v4l2_async_register_subdev(&dw9768->sd); @@ -484,14 +495,17 @@ static int dw9768_probe(struct i2c_client *client) goto err_power_off; } + pm_runtime_idle(dev); + return 0; err_power_off: - if (pm_runtime_enabled(dev)) - pm_runtime_disable(dev); - else + if (full_power) { dw9768_runtime_suspend(dev); + pm_runtime_set_suspended(dev); + } err_clean_entity: + pm_runtime_disable(dev); media_entity_cleanup(&dw9768->sd.entity); err_free_handler: v4l2_ctrl_handler_free(&dw9768->ctrls); @@ -503,14 +517,17 @@ static void dw9768_remove(struct i2c_client *client) { struct v4l2_subdev *sd = i2c_get_clientdata(client); struct dw9768 *dw9768 = sd_to_dw9768(sd); + struct device *dev = &client->dev; v4l2_async_unregister_subdev(&dw9768->sd); v4l2_ctrl_handler_free(&dw9768->ctrls); media_entity_cleanup(&dw9768->sd.entity); - pm_runtime_disable(&client->dev); - if (!pm_runtime_status_suspended(&client->dev)) - dw9768_runtime_suspend(&client->dev); - pm_runtime_set_suspended(&client->dev); + if ((is_acpi_node(dev_fwnode(dev)) && acpi_dev_state_d0(dev)) || + (is_of_node(dev_fwnode(dev)) && !pm_runtime_enabled(dev))) { + dw9768_runtime_suspend(dev); + pm_runtime_set_suspended(dev); + } + pm_runtime_disable(dev); } static const struct of_device_id dw9768_of_table[] = { From 379c258677ccf0dab1032d531829f6d8440713fa Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Mon, 17 Oct 2022 10:04:32 +0300 Subject: [PATCH 39/47] v4l: subdev: Warn if disabling streaming failed, return success Complain in the newly added s_stream video op wrapper if disabling streaming failed. Also return zero in this case as there's nothing the caller can do to return the error. This way drivers also won't need to bother with printing error messages. Signed-off-by: Sakari Ailus Reviewed-by: Lad Prabhakar --- drivers/media/v4l2-core/v4l2-subdev.c | 15 +++++++++++++++ include/media/v4l2-subdev.h | 6 ++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/drivers/media/v4l2-core/v4l2-subdev.c b/drivers/media/v4l2-core/v4l2-subdev.c index 5c27bac772ea..8a4ca2bd1584 100644 --- a/drivers/media/v4l2-core/v4l2-subdev.c +++ b/drivers/media/v4l2-core/v4l2-subdev.c @@ -318,6 +318,20 @@ static int call_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad, sd->ops->pad->get_mbus_config(sd, pad, config); } +static int call_s_stream(struct v4l2_subdev *sd, int enable) +{ + int ret; + + ret = sd->ops->video->s_stream(sd, enable); + + if (!enable && ret < 0) { + dev_warn(sd->dev, "disabling streaming failed (%d)\n", ret); + return 0; + } + + return ret; +} + #ifdef CONFIG_MEDIA_CONTROLLER /* * Create state-management wrapper for pad ops dealing with subdev state. The @@ -377,6 +391,7 @@ static const struct v4l2_subdev_pad_ops v4l2_subdev_call_pad_wrappers = { static const struct v4l2_subdev_video_ops v4l2_subdev_call_video_wrappers = { .g_frame_interval = call_g_frame_interval, .s_frame_interval = call_s_frame_interval, + .s_stream = call_s_stream, }; const struct v4l2_subdev_ops v4l2_subdev_call_wrappers = { diff --git a/include/media/v4l2-subdev.h b/include/media/v4l2-subdev.h index 54566d139da7..b15fa9930f30 100644 --- a/include/media/v4l2-subdev.h +++ b/include/media/v4l2-subdev.h @@ -440,8 +440,10 @@ enum v4l2_subdev_pre_streamon_flags { * @g_input_status: get input status. Same as the status field in the * &struct v4l2_input * - * @s_stream: used to notify the driver that a video stream will start or has - * stopped. + * @s_stream: start (enabled == 1) or stop (enabled == 0) streaming on the + * sub-device. Failure on stop will remove any resources acquired in + * streaming start, while the error code is still returned by the driver. + * Also see call_s_stream wrapper in v4l2-subdev.c. * * @g_pixelaspect: callback to return the pixelaspect ratio. * From 7afa5db0eaae8f6c110690311167d5e5cd728efb Mon Sep 17 00:00:00 2001 From: Marco Felsch Date: Fri, 30 Sep 2022 14:48:09 +0200 Subject: [PATCH 40/47] phy: dphy: refactor get_default_config Factor out the calculation into phy_mipi_dphy_calc_config(). This is needed for the follow up patch which adds the support to calculate the timings based on a given hs clock. No functional changes are done. Signed-off-by: Marco Felsch Acked-by: Vinod Koul Signed-off-by: Sakari Ailus --- drivers/phy/phy-core-mipi-dphy.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/phy/phy-core-mipi-dphy.c b/drivers/phy/phy-core-mipi-dphy.c index 929e86d6558e..ba365bc77407 100644 --- a/drivers/phy/phy-core-mipi-dphy.c +++ b/drivers/phy/phy-core-mipi-dphy.c @@ -17,7 +17,7 @@ * from the valid ranges specified in Section 6.9, Table 14, Page 41 * of the D-PHY specification (v1.2). */ -int phy_mipi_dphy_get_default_config(unsigned long pixel_clock, +static int phy_mipi_dphy_calc_config(unsigned long pixel_clock, unsigned int bpp, unsigned int lanes, struct phy_configure_opts_mipi_dphy *cfg) @@ -75,6 +75,15 @@ int phy_mipi_dphy_get_default_config(unsigned long pixel_clock, return 0; } + +int phy_mipi_dphy_get_default_config(unsigned long pixel_clock, + unsigned int bpp, + unsigned int lanes, + struct phy_configure_opts_mipi_dphy *cfg) +{ + return phy_mipi_dphy_calc_config(pixel_clock, bpp, lanes, cfg); + +} EXPORT_SYMBOL(phy_mipi_dphy_get_default_config); /* From 22168675bae75e158c459ef2dee3b6ebd52a80ed Mon Sep 17 00:00:00 2001 From: Marco Felsch Date: Fri, 30 Sep 2022 14:48:10 +0200 Subject: [PATCH 41/47] phy: dphy: add support to calculate the timing based on hs_clk_rate For MIPI-CSI sender use-case it is common to specify the allowed link-frequencies which should be used for the MIPI link and is half the hs-clock rate. This commit adds a helper to calculate the D-PHY timing based on the hs-clock rate so we don't need to calculate the timings within the driver. Signed-off-by: Marco Felsch Acked-by: Vinod Koul Signed-off-by: Sakari Ailus --- drivers/phy/phy-core-mipi-dphy.c | 22 ++++++++++++++++++---- include/linux/phy/phy-mipi-dphy.h | 3 +++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/drivers/phy/phy-core-mipi-dphy.c b/drivers/phy/phy-core-mipi-dphy.c index ba365bc77407..f4956a417a47 100644 --- a/drivers/phy/phy-core-mipi-dphy.c +++ b/drivers/phy/phy-core-mipi-dphy.c @@ -20,16 +20,18 @@ static int phy_mipi_dphy_calc_config(unsigned long pixel_clock, unsigned int bpp, unsigned int lanes, + unsigned long long hs_clk_rate, struct phy_configure_opts_mipi_dphy *cfg) { - unsigned long long hs_clk_rate; unsigned long long ui; if (!cfg) return -EINVAL; - hs_clk_rate = pixel_clock * bpp; - do_div(hs_clk_rate, lanes); + if (!hs_clk_rate) { + hs_clk_rate = pixel_clock * bpp; + do_div(hs_clk_rate, lanes); + } ui = ALIGN(PSEC_PER_SEC, hs_clk_rate); do_div(ui, hs_clk_rate); @@ -81,11 +83,23 @@ int phy_mipi_dphy_get_default_config(unsigned long pixel_clock, unsigned int lanes, struct phy_configure_opts_mipi_dphy *cfg) { - return phy_mipi_dphy_calc_config(pixel_clock, bpp, lanes, cfg); + return phy_mipi_dphy_calc_config(pixel_clock, bpp, lanes, 0, cfg); } EXPORT_SYMBOL(phy_mipi_dphy_get_default_config); +int phy_mipi_dphy_get_default_config_for_hsclk(unsigned long long hs_clk_rate, + unsigned int lanes, + struct phy_configure_opts_mipi_dphy *cfg) +{ + if (!hs_clk_rate) + return -EINVAL; + + return phy_mipi_dphy_calc_config(0, 0, lanes, hs_clk_rate, cfg); + +} +EXPORT_SYMBOL(phy_mipi_dphy_get_default_config_for_hsclk); + /* * Validate D-PHY configuration according to MIPI D-PHY specification * (v1.2, Section Section 6.9 "Global Operation Timing Parameters"). diff --git a/include/linux/phy/phy-mipi-dphy.h b/include/linux/phy/phy-mipi-dphy.h index a877ffee845d..1ac128d78dfe 100644 --- a/include/linux/phy/phy-mipi-dphy.h +++ b/include/linux/phy/phy-mipi-dphy.h @@ -279,6 +279,9 @@ int phy_mipi_dphy_get_default_config(unsigned long pixel_clock, unsigned int bpp, unsigned int lanes, struct phy_configure_opts_mipi_dphy *cfg); +int phy_mipi_dphy_get_default_config_for_hsclk(unsigned long long hs_clk_rate, + unsigned int lanes, + struct phy_configure_opts_mipi_dphy *cfg); int phy_mipi_dphy_config_validate(struct phy_configure_opts_mipi_dphy *cfg); #endif /* __PHY_MIPI_DPHY_H_ */ From a92fb9442f9aadb8a1e9ae499220595bec82ad7d Mon Sep 17 00:00:00 2001 From: Marco Felsch Date: Fri, 30 Sep 2022 14:48:11 +0200 Subject: [PATCH 42/47] media: dt-bindings: add bindings for Toshiba TC358746 Add the bindings for the Toshiba TC358746 Parallel <-> MIPI-CSI bridge driver. Signed-off-by: Marco Felsch Reviewed-by: Rob Herring Reviewed-by: Laurent Pinchart Signed-off-by: Sakari Ailus --- .../bindings/media/i2c/toshiba,tc358746.yaml | 178 ++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/i2c/toshiba,tc358746.yaml diff --git a/Documentation/devicetree/bindings/media/i2c/toshiba,tc358746.yaml b/Documentation/devicetree/bindings/media/i2c/toshiba,tc358746.yaml new file mode 100644 index 000000000000..b8ba85a2416c --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/toshiba,tc358746.yaml @@ -0,0 +1,178 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/toshiba,tc358746.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Toshiba TC358746 Parallel to MIPI CSI2 Bridge + +maintainers: + - Marco Felsch + +description: |- + The Toshiba TC358746 converts a parallel video stream into a MIPI CSI-2 + stream. The direction can be either parallel-in -> csi-out or csi-in -> + parallel-out The chip is programmable trough I2C and SPI but the SPI + interface is only supported in parallel-in -> csi-out mode. + + Note that the current device tree bindings only support the + parallel-in -> csi-out path. + +properties: + compatible: + const: toshiba,tc358746 + + reg: + maxItems: 1 + + clocks: + description: + The phandle to the reference clock source. This corresponds to the + hardware pin REFCLK. + maxItems: 1 + + clock-names: + const: refclk + + "#clock-cells": + description: | + The bridge can act as clock provider for the sensor. To enable this + support #clock-cells must be specified. Attention if this feature is used + then the mclk rate must be at least: (2 * link-frequency) / 8 + `------------------´ ^ + internal PLL rate smallest possible + mclk-div + const: 0 + + clock-output-names: + description: + The clock name of the MCLK output, the default name is tc358746-mclk. + maxItems: 1 + + vddc-supply: + description: Digital core voltage supply, 1.2 volts + + vddio-supply: + description: Digital I/O voltage supply, 1.8 volts + + vddmipi-supply: + description: MIPI CSI phy voltage supply, 1.2 volts + + reset-gpios: + description: + The phandle and specifier for the GPIO that controls the chip reset. + This corresponds to the hardware pin RESX which is physically active low. + maxItems: 1 + + ports: + $ref: /schemas/graph.yaml#/properties/ports + properties: + port@0: + $ref: /schemas/graph.yaml#/$defs/port-base + description: Input port + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + hsync-active: true + vsync-active: true + bus-type: + enum: [ 5, 6 ] + + required: + - hsync-active + - vsync-active + - bus-type + + port@1: + $ref: /schemas/graph.yaml#/$defs/port-base + description: Output port + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + data-lanes: + minItems: 1 + maxItems: 4 + + clock-noncontinuous: true + link-frequencies: true + + required: + - data-lanes + - link-frequencies + + required: + - port@0 + - port@1 + +required: + - compatible + - reg + - clocks + - clock-names + - vddc-supply + - vddio-supply + - vddmipi-supply + - ports + +additionalProperties: false + +examples: + - | + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + csi-bridge@e { + compatible = "toshiba,tc358746"; + reg = <0xe>; + + clocks = <&refclk>; + clock-names = "refclk"; + + reset-gpios = <&gpio 2 GPIO_ACTIVE_LOW>; + + vddc-supply = <&v1_2d>; + vddio-supply = <&v1_8d>; + vddmipi-supply = <&v1_2d>; + + /* sensor mclk provider */ + #clock-cells = <0>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + /* Input */ + port@0 { + reg = <0>; + tc358746_in: endpoint { + remote-endpoint = <&sensor_out>; + hsync-active = <0>; + vsync-active = <0>; + bus-type = <5>; + }; + }; + + /* Output */ + port@1 { + reg = <1>; + tc358746_out: endpoint { + remote-endpoint = <&mipi_csi2_in>; + data-lanes = <1 2>; + clock-noncontinuous; + link-frequencies = /bits/ 64 <216000000>; + }; + }; + }; + }; + }; From 80a21da360516fa602f3a50eb9792f9dfbfb5fdb Mon Sep 17 00:00:00 2001 From: Marco Felsch Date: Fri, 30 Sep 2022 14:48:12 +0200 Subject: [PATCH 43/47] media: tc358746: add Toshiba TC358746 Parallel to CSI-2 bridge driver 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 Reviewed-by: Laurent Pinchart [Sakari Ailus: remove() now returns void] Signed-off-by: Sakari Ailus --- drivers/media/i2c/Kconfig | 17 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/tc358746.c | 1694 ++++++++++++++++++++++++++++++++++ 3 files changed, 1712 insertions(+) create mode 100644 drivers/media/i2c/tc358746.c diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 07b240a990a8..49c1c27afdc1 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -1308,6 +1308,23 @@ config VIDEO_TC358743_CEC When selected the tc358743 will support the optional HDMI CEC feature. +config VIDEO_TC358746 + tristate "Toshiba TC358746 parallel-CSI2 bridge" + depends on VIDEO_DEV && PM && I2C + select VIDEO_V4L2_SUBDEV_API + select MEDIA_CONTROLLER + select V4L2_FWNODE + select GENERIC_PHY_MIPI_DPHY + select REGMAP_I2C + select COMMON_CLK + help + Support for the Toshiba TC358746 parallel to MIPI CSI-2 bridge. + The bridge can work in both directions but currently only the + parallel-in / csi-out path is supported. + + To compile this driver as a module, choose M here: the + module will be called tc358746. + config VIDEO_TVP514X tristate "Texas Instruments TVP514x video decoder" depends on VIDEO_DEV && I2C diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 41d11008464c..ba28a8f8a07f 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -121,6 +121,7 @@ obj-$(CONFIG_VIDEO_SR030PC30) += sr030pc30.o obj-$(CONFIG_VIDEO_ST_MIPID02) += st-mipid02.o obj-$(CONFIG_VIDEO_ST_VGXY61) += st-vgxy61.o obj-$(CONFIG_VIDEO_TC358743) += tc358743.o +obj-$(CONFIG_VIDEO_TC358746) += tc358746.o obj-$(CONFIG_VIDEO_TDA1997X) += tda1997x.o obj-$(CONFIG_VIDEO_TDA7432) += tda7432.o obj-$(CONFIG_VIDEO_TDA9840) += tda9840.o diff --git a/drivers/media/i2c/tc358746.c b/drivers/media/i2c/tc358746.c new file mode 100644 index 000000000000..171309c62bb8 --- /dev/null +++ b/drivers/media/i2c/tc358746.c @@ -0,0 +1,1694 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * TC358746 - Parallel <-> CSI-2 Bridge + * + * Copyright 2022 Marco Felsch + * + * Notes: + * - Currently only 'Parallel-in -> CSI-out' mode is supported! + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 16-bit registers */ +#define CHIPID_REG 0x0000 +#define CHIPID GENMASK(15, 8) + +#define SYSCTL_REG 0x0002 +#define SRESET BIT(0) + +#define CONFCTL_REG 0x0004 +#define PDATAF_MASK GENMASK(9, 8) +#define PDATAF_MODE0 0 +#define PDATAF_MODE1 1 +#define PDATAF_MODE2 2 +#define PDATAF(val) FIELD_PREP(PDATAF_MASK, (val)) +#define PPEN BIT(6) +#define DATALANE_MASK GENMASK(1, 0) + +#define FIFOCTL_REG 0x0006 +#define DATAFMT_REG 0x0008 +#define PDFMT(val) FIELD_PREP(GENMASK(7, 4), (val)) + +#define MCLKCTL_REG 0x000c +#define MCLK_HIGH_MASK GENMASK(15, 8) +#define MCLK_LOW_MASK GENMASK(7, 0) +#define MCLK_HIGH(val) FIELD_PREP(MCLK_HIGH_MASK, (val)) +#define MCLK_LOW(val) FIELD_PREP(MCLK_LOW_MASK, (val)) + +#define PLLCTL0_REG 0x0016 +#define PLL_PRD_MASK GENMASK(15, 12) +#define PLL_PRD(val) FIELD_PREP(PLL_PRD_MASK, (val)) +#define PLL_FBD_MASK GENMASK(8, 0) +#define PLL_FBD(val) FIELD_PREP(PLL_FBD_MASK, (val)) + +#define PLLCTL1_REG 0x0018 +#define PLL_FRS_MASK GENMASK(11, 10) +#define PLL_FRS(val) FIELD_PREP(PLL_FRS_MASK, (val)) +#define CKEN BIT(4) +#define RESETB BIT(1) +#define PLL_EN BIT(0) + +#define CLKCTL_REG 0x0020 +#define MCLKDIV_MASK GENMASK(3, 2) +#define MCLKDIV(val) FIELD_PREP(MCLKDIV_MASK, (val)) +#define MCLKDIV_8 0 +#define MCLKDIV_4 1 +#define MCLKDIV_2 2 + +#define WORDCNT_REG 0x0022 +#define PP_MISC_REG 0x0032 +#define FRMSTOP BIT(15) +#define RSTPTR BIT(14) + +/* 32-bit registers */ +#define CLW_DPHYCONTTX_REG 0x0100 +#define CLW_CNTRL_REG 0x0140 +#define D0W_CNTRL_REG 0x0144 +#define LANEDISABLE BIT(0) + +#define STARTCNTRL_REG 0x0204 +#define START BIT(0) + +#define LINEINITCNT_REG 0x0210 +#define LPTXTIMECNT_REG 0x0214 +#define TCLK_HEADERCNT_REG 0x0218 +#define TCLK_ZEROCNT(val) FIELD_PREP(GENMASK(15, 8), (val)) +#define TCLK_PREPARECNT(val) FIELD_PREP(GENMASK(6, 0), (val)) + +#define TCLK_TRAILCNT_REG 0x021C +#define THS_HEADERCNT_REG 0x0220 +#define THS_ZEROCNT(val) FIELD_PREP(GENMASK(14, 8), (val)) +#define THS_PREPARECNT(val) FIELD_PREP(GENMASK(6, 0), (val)) + +#define TWAKEUP_REG 0x0224 +#define TCLK_POSTCNT_REG 0x0228 +#define THS_TRAILCNT_REG 0x022C +#define HSTXVREGEN_REG 0x0234 +#define TXOPTIONCNTRL_REG 0x0238 +#define CSI_CONTROL_REG 0x040C +#define CSI_MODE BIT(15) +#define TXHSMD BIT(7) +#define NOL(val) FIELD_PREP(GENMASK(2, 1), (val)) + +#define CSI_CONFW_REG 0x0500 +#define MODE(val) FIELD_PREP(GENMASK(31, 29), (val)) +#define MODE_SET 0x5 +#define ADDRESS(val) FIELD_PREP(GENMASK(28, 24), (val)) +#define CSI_CONTROL_ADDRESS 0x3 +#define DATA(val) FIELD_PREP(GENMASK(15, 0), (val)) + +#define CSI_START_REG 0x0518 +#define STRT BIT(0) + +static const struct v4l2_mbus_framefmt tc358746_def_fmt = { + .width = 640, + .height = 480, + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_DEFAULT, + .ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT, + .quantization = V4L2_QUANTIZATION_DEFAULT, + .xfer_func = V4L2_XFER_FUNC_DEFAULT, +}; + +static const char * const tc358746_supplies[] = { + "vddc", "vddio", "vddmipi" +}; + +enum { + TC358746_SINK, + TC358746_SOURCE, + TC358746_NR_PADS +}; + +struct tc358746 { + struct v4l2_subdev sd; + struct media_pad pads[TC358746_NR_PADS]; + struct v4l2_async_notifier notifier; + 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; + +#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; +}; + +static inline struct tc358746 *to_tc358746(struct v4l2_subdev *sd) +{ + return container_of(sd, struct tc358746, sd); +} + +static inline struct tc358746 *clk_hw_to_tc358746(struct clk_hw *hw) +{ + return container_of(hw, struct tc358746, mclk_hw); +} + +struct tc358746_format { + u32 code; + bool csi_format; + unsigned char bus_width; + unsigned char bpp; + /* Register values */ + u8 pdformat; /* Peripheral Data Format */ + u8 pdataf; /* Parallel Data Format Option */ +}; + +enum { + PDFORMAT_RAW8 = 0, + PDFORMAT_RAW10, + PDFORMAT_RAW12, + PDFORMAT_RGB888, + PDFORMAT_RGB666, + PDFORMAT_RGB565, + PDFORMAT_YUV422_8BIT, + /* RESERVED = 7 */ + PDFORMAT_RAW14 = 8, + PDFORMAT_YUV422_10BIT, + PDFORMAT_YUV444, +}; + +/* Check tc358746_src_mbus_code() if you add new formats */ +static const struct tc358746_format tc358746_formats[] = { + { + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .bus_width = 8, + .bpp = 16, + .pdformat = PDFORMAT_YUV422_8BIT, + .pdataf = PDATAF_MODE0, + }, { + .code = MEDIA_BUS_FMT_UYVY8_1X16, + .csi_format = true, + .bus_width = 16, + .bpp = 16, + .pdformat = PDFORMAT_YUV422_8BIT, + .pdataf = PDATAF_MODE1, + }, { + .code = MEDIA_BUS_FMT_YUYV8_1X16, + .csi_format = true, + .bus_width = 16, + .bpp = 16, + .pdformat = PDFORMAT_YUV422_8BIT, + .pdataf = PDATAF_MODE2, + }, { + .code = MEDIA_BUS_FMT_UYVY10_2X10, + .bus_width = 10, + .bpp = 20, + .pdformat = PDFORMAT_YUV422_10BIT, + .pdataf = PDATAF_MODE0, /* don't care */ + } +}; + +/* Get n-th format for pad */ +static const struct tc358746_format * +tc358746_get_format_by_idx(unsigned int pad, unsigned int index) +{ + unsigned int idx = 0; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(tc358746_formats); i++) { + const struct tc358746_format *fmt = &tc358746_formats[i]; + + if ((pad == TC358746_SOURCE && fmt->csi_format) || + (pad == TC358746_SINK)) { + if (idx == index) + return fmt; + idx++; + } + } + + return ERR_PTR(-EINVAL); +} + +static const struct tc358746_format * +tc358746_get_format_by_code(unsigned int pad, u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(tc358746_formats); i++) { + const struct tc358746_format *fmt = &tc358746_formats[i]; + + if (pad == TC358746_SINK && fmt->code == code) + return fmt; + + if (pad == TC358746_SOURCE && !fmt->csi_format) + continue; + + if (fmt->code == code) + return fmt; + } + + return ERR_PTR(-EINVAL); +} + +static u32 tc358746_src_mbus_code(u32 code) +{ + switch (code) { + case MEDIA_BUS_FMT_UYVY8_2X8: + return MEDIA_BUS_FMT_UYVY8_1X16; + case MEDIA_BUS_FMT_UYVY10_2X10: + return MEDIA_BUS_FMT_UYVY10_1X20; + default: + return code; + } +} + +static bool tc358746_valid_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CHIPID_REG ... CSI_START_REG: + return true; + default: + return false; + } +} + +static const struct regmap_config tc358746_regmap_config = { + .name = "tc358746", + .reg_bits = 16, + .val_bits = 16, + .max_register = CSI_START_REG, + .writeable_reg = tc358746_valid_reg, + .readable_reg = tc358746_valid_reg, + .reg_format_endian = REGMAP_ENDIAN_BIG, + .val_format_endian = REGMAP_ENDIAN_BIG, +}; + +static int tc358746_write(struct tc358746 *tc358746, u32 reg, u32 val) +{ + size_t count; + int err; + + /* 32-bit registers starting from CLW_DPHYCONTTX */ + count = reg < CLW_DPHYCONTTX_REG ? 1 : 2; + + err = regmap_bulk_write(tc358746->regmap, reg, &val, count); + if (err) + dev_err(tc358746->sd.dev, + "Failed to write reg:0x%04x err:%d\n", reg, err); + + return err; +} + +static int tc358746_read(struct tc358746 *tc358746, u32 reg, u32 *val) +{ + size_t count; + int err; + + /* 32-bit registers starting from CLW_DPHYCONTTX */ + count = reg < CLW_DPHYCONTTX_REG ? 1 : 2; + *val = 0; + + err = regmap_bulk_read(tc358746->regmap, reg, val, count); + if (err) + dev_err(tc358746->sd.dev, + "Failed to read reg:0x%04x err:%d\n", reg, err); + + return err; +} + +static int +tc358746_update_bits(struct tc358746 *tc358746, u32 reg, u32 mask, u32 val) +{ + u32 tmp, orig; + int err; + + err = tc358746_read(tc358746, reg, &orig); + if (err) + return err; + + tmp = orig & ~mask; + tmp |= val & mask; + + return tc358746_write(tc358746, reg, tmp); +} + +static int tc358746_set_bits(struct tc358746 *tc358746, u32 reg, u32 bits) +{ + return tc358746_update_bits(tc358746, reg, bits, bits); +} + +static int tc358746_clear_bits(struct tc358746 *tc358746, u32 reg, u32 bits) +{ + return tc358746_update_bits(tc358746, reg, bits, 0); +} + +static int tc358746_sw_reset(struct tc358746 *tc358746) +{ + int err; + + err = tc358746_set_bits(tc358746, SYSCTL_REG, SRESET); + if (err) + return err; + + fsleep(10); + + return tc358746_clear_bits(tc358746, SYSCTL_REG, SRESET); +} + +static int +tc358746_apply_pll_config(struct tc358746 *tc358746) +{ + u8 post = tc358746->pll_post_div; + u16 pre = tc358746->pll_pre_div; + u16 mul = tc358746->pll_mul; + u32 val, mask; + int err; + + err = tc358746_read(tc358746, PLLCTL1_REG, &val); + if (err) + return err; + + /* Don't touch the PLL if running */ + if (FIELD_GET(PLL_EN, val) == 1) + return 0; + + /* Pre-div and Multiplicator have a internal +1 logic */ + val = PLL_PRD(pre - 1) | PLL_FBD(mul - 1); + mask = PLL_PRD_MASK | PLL_FBD_MASK; + err = tc358746_update_bits(tc358746, PLLCTL0_REG, mask, val); + if (err) + return err; + + val = PLL_FRS(ilog2(post)) | RESETB | PLL_EN; + mask = PLL_FRS_MASK | RESETB | PLL_EN; + tc358746_update_bits(tc358746, PLLCTL1_REG, mask, val); + if (err) + return err; + + fsleep(1000); + + return tc358746_set_bits(tc358746, PLLCTL1_REG, CKEN); +} + +static int tc358746_apply_misc_config(struct tc358746 *tc358746) +{ + const struct v4l2_mbus_framefmt *mbusfmt; + struct v4l2_subdev *sd = &tc358746->sd; + struct v4l2_subdev_state *sink_state; + const struct tc358746_format *fmt; + struct device *dev = sd->dev; + u32 val; + int err; + + sink_state = v4l2_subdev_lock_and_get_active_state(sd); + + mbusfmt = v4l2_subdev_get_pad_format(sd, sink_state, TC358746_SINK); + fmt = tc358746_get_format_by_code(TC358746_SINK, mbusfmt->code); + + /* Self defined CSI user data type id's are not supported yet */ + val = PDFMT(fmt->pdformat); + dev_dbg(dev, "DATAFMT: 0x%x\n", val); + err = tc358746_write(tc358746, DATAFMT_REG, val); + if (err) + goto out; + + val = PDATAF(fmt->pdataf); + dev_dbg(dev, "CONFCTL[PDATAF]: 0x%x\n", fmt->pdataf); + err = tc358746_update_bits(tc358746, CONFCTL_REG, PDATAF_MASK, val); + if (err) + goto out; + + val = tc358746->vb_size / 32; + dev_dbg(dev, "FIFOCTL: %u (0x%x)\n", val, val); + err = tc358746_write(tc358746, FIFOCTL_REG, val); + if (err) + goto out; + + /* Total number of bytes for each line/width */ + val = mbusfmt->width * fmt->bpp / 8; + dev_dbg(dev, "WORDCNT: %u (0x%x)\n", val, val); + err = tc358746_write(tc358746, WORDCNT_REG, val); + +out: + v4l2_subdev_unlock_state(sink_state); + + return err; +} + +/* Use MHz as base so the div needs no u64 */ +static u32 tc358746_cfg_to_cnt(unsigned int cfg_val, + unsigned int clk_mhz, + unsigned int time_base) +{ + return DIV_ROUND_UP(cfg_val * clk_mhz, time_base); +} + +static u32 tc358746_ps_to_cnt(unsigned int cfg_val, + unsigned int clk_mhz) +{ + return tc358746_cfg_to_cnt(cfg_val, clk_mhz, USEC_PER_SEC); +} + +static u32 tc358746_us_to_cnt(unsigned int cfg_val, + unsigned int clk_mhz) +{ + return tc358746_cfg_to_cnt(cfg_val, clk_mhz, 1); +} + +static int tc358746_apply_dphy_config(struct tc358746 *tc358746) +{ + struct phy_configure_opts_mipi_dphy *cfg = &tc358746->dphy_cfg; + bool non_cont_clk = !!(tc358746->csi_vep.bus.mipi_csi2.flags & + V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK); + struct device *dev = tc358746->sd.dev; + unsigned long hs_byte_clk, hf_clk; + u32 val, val2, lptxcnt; + int err; + + /* The hs_byte_clk is also called SYSCLK in the excel sheet */ + hs_byte_clk = cfg->hs_clk_rate / 8; + hs_byte_clk /= HZ_PER_MHZ; + hf_clk = hs_byte_clk / 2; + + val = tc358746_us_to_cnt(cfg->init, hf_clk) - 1; + dev_dbg(dev, "LINEINITCNT: %u (0x%x)\n", val, val); + err = tc358746_write(tc358746, LINEINITCNT_REG, val); + if (err) + return err; + + val = tc358746_ps_to_cnt(cfg->lpx, hs_byte_clk) - 1; + lptxcnt = val; + dev_dbg(dev, "LPTXTIMECNT: %u (0x%x)\n", val, val); + err = tc358746_write(tc358746, LPTXTIMECNT_REG, val); + if (err) + return err; + + val = tc358746_ps_to_cnt(cfg->clk_prepare, hs_byte_clk) - 1; + val2 = tc358746_ps_to_cnt(cfg->clk_zero, hs_byte_clk) - 1; + dev_dbg(dev, "TCLK_PREPARECNT: %u (0x%x)\n", val, val); + dev_dbg(dev, "TCLK_ZEROCNT: %u (0x%x)\n", val2, val2); + dev_dbg(dev, "TCLK_HEADERCNT: 0x%x\n", + (u32)(TCLK_PREPARECNT(val) | TCLK_ZEROCNT(val2))); + err = tc358746_write(tc358746, TCLK_HEADERCNT_REG, + TCLK_PREPARECNT(val) | TCLK_ZEROCNT(val2)); + if (err) + return err; + + val = tc358746_ps_to_cnt(cfg->clk_trail, hs_byte_clk); + dev_dbg(dev, "TCLK_TRAILCNT: %u (0x%x)\n", val, val); + err = tc358746_write(tc358746, TCLK_TRAILCNT_REG, val); + if (err) + return err; + + val = tc358746_ps_to_cnt(cfg->hs_prepare, hs_byte_clk) - 1; + val2 = tc358746_ps_to_cnt(cfg->hs_zero, hs_byte_clk) - 1; + dev_dbg(dev, "THS_PREPARECNT: %u (0x%x)\n", val, val); + dev_dbg(dev, "THS_ZEROCNT: %u (0x%x)\n", val2, val2); + dev_dbg(dev, "THS_HEADERCNT: 0x%x\n", + (u32)(THS_PREPARECNT(val) | THS_ZEROCNT(val2))); + err = tc358746_write(tc358746, THS_HEADERCNT_REG, + THS_PREPARECNT(val) | THS_ZEROCNT(val2)); + if (err) + return err; + + /* TWAKEUP > 1ms in lptxcnt steps */ + val = tc358746_us_to_cnt(cfg->wakeup, hs_byte_clk); + val = val / (lptxcnt + 1) - 1; + dev_dbg(dev, "TWAKEUP: %u (0x%x)\n", val, val); + err = tc358746_write(tc358746, TWAKEUP_REG, val); + if (err) + return err; + + val = tc358746_ps_to_cnt(cfg->clk_post, hs_byte_clk); + dev_dbg(dev, "TCLK_POSTCNT: %u (0x%x)\n", val, val); + err = tc358746_write(tc358746, TCLK_POSTCNT_REG, val); + if (err) + return err; + + val = tc358746_ps_to_cnt(cfg->hs_trail, hs_byte_clk); + dev_dbg(dev, "THS_TRAILCNT: %u (0x%x)\n", val, val); + err = tc358746_write(tc358746, THS_TRAILCNT_REG, val); + if (err) + return err; + + dev_dbg(dev, "CONTCLKMODE: %u", non_cont_clk ? 0 : 1); + + return tc358746_write(tc358746, TXOPTIONCNTRL_REG, non_cont_clk ? 0 : 1); +} + +#define MAX_DATA_LANES 4 + +static int tc358746_enable_csi_lanes(struct tc358746 *tc358746, int enable) +{ + unsigned int lanes = tc358746->dphy_cfg.lanes; + unsigned int lane; + u32 reg, val; + int err; + + err = tc358746_update_bits(tc358746, CONFCTL_REG, DATALANE_MASK, + lanes - 1); + if (err) + return err; + + /* Clock lane */ + val = enable ? 0 : LANEDISABLE; + dev_dbg(tc358746->sd.dev, "CLW_CNTRL: 0x%x\n", val); + err = tc358746_write(tc358746, CLW_CNTRL_REG, val); + if (err) + return err; + + for (lane = 0; lane < MAX_DATA_LANES; lane++) { + /* Data lanes */ + reg = D0W_CNTRL_REG + lane * 0x4; + val = (enable && lane < lanes) ? 0 : LANEDISABLE; + + dev_dbg(tc358746->sd.dev, "D%uW_CNTRL: 0x%x\n", lane, val); + err = tc358746_write(tc358746, reg, val); + if (err) + return err; + } + + val = 0; + if (enable) { + /* Clock lane */ + val |= BIT(0); + + /* Data lanes */ + for (lane = 1; lane <= lanes; lane++) + val |= BIT(lane); + } + + dev_dbg(tc358746->sd.dev, "HSTXVREGEN: 0x%x\n", val); + + return tc358746_write(tc358746, HSTXVREGEN_REG, val); +} + +static int tc358746_enable_csi_module(struct tc358746 *tc358746, int enable) +{ + unsigned int lanes = tc358746->dphy_cfg.lanes; + int err; + + /* + * START and STRT are only reseted/disabled by sw reset. This is + * required to put the lane state back into LP-11 state. The sw reset + * don't reset register values. + */ + if (!enable) + return tc358746_sw_reset(tc358746); + + err = tc358746_write(tc358746, STARTCNTRL_REG, START); + if (err) + return err; + + err = tc358746_write(tc358746, CSI_START_REG, STRT); + if (err) + return err; + + /* CSI_CONTROL_REG is only indirect accessible */ + return tc358746_write(tc358746, CSI_CONFW_REG, + MODE(MODE_SET) | + ADDRESS(CSI_CONTROL_ADDRESS) | + DATA(CSI_MODE | TXHSMD | NOL(lanes - 1))); +} + +static int tc358746_enable_parallel_port(struct tc358746 *tc358746, int enable) +{ + int err; + + if (enable) { + err = tc358746_write(tc358746, PP_MISC_REG, 0); + if (err) + return err; + + return tc358746_set_bits(tc358746, CONFCTL_REG, PPEN); + } + + err = tc358746_set_bits(tc358746, PP_MISC_REG, FRMSTOP); + if (err) + return err; + + err = tc358746_clear_bits(tc358746, CONFCTL_REG, PPEN); + if (err) + return err; + + return tc358746_set_bits(tc358746, PP_MISC_REG, RSTPTR); +} + +static inline struct v4l2_subdev *tc358746_get_remote_sd(struct media_pad *pad) +{ + pad = media_pad_remote_pad_first(pad); + if (!pad) + return NULL; + + return media_entity_to_v4l2_subdev(pad->entity); +} + +static int tc358746_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct tc358746 *tc358746 = to_tc358746(sd); + struct v4l2_subdev *src; + int err; + + dev_dbg(sd->dev, "%sable\n", enable ? "en" : "dis"); + + src = tc358746_get_remote_sd(&tc358746->pads[TC358746_SINK]); + if (!src) + return -EPIPE; + + if (enable) { + err = pm_runtime_resume_and_get(sd->dev); + if (err) + return err; + + err = tc358746_apply_dphy_config(tc358746); + if (err) + goto err_out; + + err = tc358746_apply_misc_config(tc358746); + if (err) + goto err_out; + + err = tc358746_enable_csi_lanes(tc358746, 1); + if (err) + goto err_out; + + err = tc358746_enable_csi_module(tc358746, 1); + if (err) + goto err_out; + + err = tc358746_enable_parallel_port(tc358746, 1); + if (err) + goto err_out; + + err = v4l2_subdev_call(src, video, s_stream, 1); + if (err) + goto err_out; + + return 0; + +err_out: + pm_runtime_mark_last_busy(sd->dev); + pm_runtime_put_sync_autosuspend(sd->dev); + + return err; + } + + /* + * The lanes must be disabled first (before the csi module) so the + * LP-11 state is entered correctly. + */ + err = tc358746_enable_csi_lanes(tc358746, 0); + if (err) + return err; + + err = tc358746_enable_csi_module(tc358746, 0); + if (err) + return err; + + err = tc358746_enable_parallel_port(tc358746, 0); + if (err) + return err; + + pm_runtime_mark_last_busy(sd->dev); + pm_runtime_put_sync_autosuspend(sd->dev); + + return v4l2_subdev_call(src, video, s_stream, 0); +} + +static int tc358746_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_get_pad_format(sd, state, TC358746_SINK); + *fmt = tc358746_def_fmt; + + fmt = v4l2_subdev_get_pad_format(sd, state, TC358746_SOURCE); + *fmt = tc358746_def_fmt; + fmt->code = tc358746_src_mbus_code(tc358746_def_fmt.code); + + return 0; +} + +static int tc358746_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + const struct tc358746_format *fmt; + + fmt = tc358746_get_format_by_idx(code->pad, code->index); + if (IS_ERR(fmt)) + return PTR_ERR(fmt); + + code->code = fmt->code; + + return 0; +} + +static int tc358746_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *src_fmt, *sink_fmt; + const struct tc358746_format *fmt; + + /* Source follows the sink */ + if (format->pad == TC358746_SOURCE) + return v4l2_subdev_get_fmt(sd, sd_state, format); + + sink_fmt = v4l2_subdev_get_pad_format(sd, sd_state, TC358746_SINK); + + fmt = tc358746_get_format_by_code(format->pad, format->format.code); + if (IS_ERR(fmt)) + fmt = tc358746_get_format_by_code(format->pad, tc358746_def_fmt.code); + + format->format.code = fmt->code; + format->format.field = V4L2_FIELD_NONE; + + dev_dbg(sd->dev, "Update format: %ux%u code:0x%x -> %ux%u code:0x%x", + sink_fmt->width, sink_fmt->height, sink_fmt->code, + format->format.width, format->format.height, format->format.code); + + *sink_fmt = format->format; + + src_fmt = v4l2_subdev_get_pad_format(sd, sd_state, TC358746_SOURCE); + *src_fmt = *sink_fmt; + src_fmt->code = tc358746_src_mbus_code(sink_fmt->code); + + return 0; +} + +static unsigned long tc358746_find_pll_settings(struct tc358746 *tc358746, + unsigned long refclk, + unsigned long fout) + +{ + struct device *dev = tc358746->sd.dev; + unsigned long best_freq = 0; + u32 min_delta = 0xffffffff; + u16 prediv_max = 17; + u16 prediv_min = 1; + u16 m_best, mul; + u16 p_best, p; + u8 postdiv; + + if (fout > 1000 * HZ_PER_MHZ) { + dev_err(dev, "HS-Clock above 1 Ghz are not supported\n"); + return 0; + } + + if (fout >= 500 * HZ_PER_MHZ) + postdiv = 1; + else if (fout >= 250 * HZ_PER_MHZ) + postdiv = 2; + else if (fout >= 125 * HZ_PER_MHZ) + postdiv = 4; + else + postdiv = 8; + + for (p = prediv_min; p <= prediv_max; p++) { + unsigned long delta, fin; + u64 tmp; + + fin = DIV_ROUND_CLOSEST(refclk, p); + if (fin < 4 * HZ_PER_MHZ || fin > 40 * HZ_PER_MHZ) + continue; + + tmp = fout * p * postdiv; + do_div(tmp, fin); + mul = tmp; + if (mul > 511) + continue; + + tmp = mul * fin; + do_div(tmp, p * postdiv); + + delta = abs(fout - tmp); + if (delta < min_delta) { + p_best = p; + m_best = mul; + min_delta = delta; + best_freq = tmp; + }; + + if (delta == 0) + break; + }; + + if (!best_freq) { + dev_err(dev, "Failed find PLL frequency\n"); + return 0; + } + + tc358746->pll_post_div = postdiv; + tc358746->pll_pre_div = p_best; + tc358746->pll_mul = m_best; + + if (best_freq != fout) + dev_warn(dev, "Request PLL freq:%lu, found PLL freq:%lu\n", + fout, best_freq); + + dev_dbg(dev, "Found PLL settings: freq:%lu prediv:%u multi:%u postdiv:%u\n", + best_freq, p_best, m_best, postdiv); + + return best_freq; +} + +#define TC358746_PRECISION 10 + +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, source_bitrate; + struct v4l2_subdev_state *sink_state; + struct v4l2_mbus_framefmt *mbusfmt; + const struct tc358746_format *fmt; + unsigned int fifo_sz, tmp, n; + struct v4l2_subdev *source; + s64 source_link_freq; + int err; + + err = v4l2_subdev_link_validate_default(sd, link, source_fmt, sink_fmt); + if (err) + return err; + + sink_state = v4l2_subdev_lock_and_get_active_state(sd); + mbusfmt = v4l2_subdev_get_pad_format(sd, sink_state, TC358746_SINK); + + /* Check the FIFO settings */ + fmt = tc358746_get_format_by_code(TC358746_SINK, mbusfmt->code); + + source = media_entity_to_v4l2_subdev(link->source->entity); + source_link_freq = v4l2_get_link_freq(source->ctrl_handler, 0, 0); + if (source_link_freq <= 0) { + dev_err(tc358746->sd.dev, + "Failed to query or invalid source link frequency\n"); + v4l2_subdev_unlock_state(sink_state); + /* Return -EINVAL in case of source_link_freq is 0 */ + return source_link_freq ? : -EINVAL; + } + source_bitrate = source_link_freq * fmt->bus_width; + + csi_bitrate = tc358746->dphy_cfg.lanes * tc358746->pll_rate; + + dev_dbg(tc358746->sd.dev, + "Fifo settings params: source-bitrate:%lu csi-bitrate:%lu", + source_bitrate, csi_bitrate); + + /* Avoid possible FIFO overflows */ + if (csi_bitrate < source_bitrate) { + v4l2_subdev_unlock_state(sink_state); + return -EINVAL; + } + + /* Best case */ + if (csi_bitrate == source_bitrate) { + fifo_sz = TC358746_VB_DEFAULT_SIZE; + tc358746->vb_size = TC358746_VB_DEFAULT_SIZE; + goto out; + } + + /* + * Avoid possible FIFO underflow in case of + * csi_bitrate > source_bitrate. For such case the chip has a internal + * fifo which can be used to delay the line output. + * + * Fifo size calculation (excluding precision): + * + * fifo-sz, image-width - in bits + * sbr - source_bitrate in bits/s + * csir - csi_bitrate in bits/s + * + * image-width / csir >= (image-width - fifo-sz) / sbr + * image-width * sbr / csir >= image-width - fifo-sz + * fifo-sz >= image-width - image-width * sbr / csir; with n = csir/sbr + * fifo-sz >= image-width - image-width / n + */ + + source_bitrate /= TC358746_PRECISION; + n = csi_bitrate / source_bitrate; + tmp = (mbusfmt->width * TC358746_PRECISION) / n; + fifo_sz = mbusfmt->width - tmp; + fifo_sz *= fmt->bpp; + tc358746->vb_size = round_up(fifo_sz, 32); + +out: + dev_dbg(tc358746->sd.dev, + "Found FIFO size[bits]:%u -> aligned to size[bits]:%u\n", + fifo_sz, tc358746->vb_size); + + v4l2_subdev_unlock_state(sink_state); + + return tc358746->vb_size > TC358746_VB_MAX_SIZE ? -EINVAL : 0; +} + +static int tc358746_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_mbus_config *config) +{ + struct tc358746 *tc358746 = to_tc358746(sd); + + if (pad != TC358746_SOURCE) + return -EINVAL; + + config->type = V4L2_MBUS_CSI2_DPHY; + config->bus.mipi_csi2 = tc358746->csi_vep.bus.mipi_csi2; + + return 0; +} + +static int __maybe_unused +tc358746_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) +{ + struct tc358746 *tc358746 = to_tc358746(sd); + + /* 32-bit registers starting from CLW_DPHYCONTTX */ + reg->size = reg->reg < CLW_DPHYCONTTX_REG ? 2 : 4; + + if (!pm_runtime_get_if_in_use(sd->dev)) + return 0; + + tc358746_read(tc358746, reg->reg, (u32 *)®->val); + + pm_runtime_mark_last_busy(sd->dev); + pm_runtime_put_sync_autosuspend(sd->dev); + + return 0; +} + +static int __maybe_unused +tc358746_s_register(struct v4l2_subdev *sd, const struct v4l2_dbg_register *reg) +{ + struct tc358746 *tc358746 = to_tc358746(sd); + + if (!pm_runtime_get_if_in_use(sd->dev)) + return 0; + + tc358746_write(tc358746, (u32)reg->reg, (u32)reg->val); + + pm_runtime_mark_last_busy(sd->dev); + pm_runtime_put_sync_autosuspend(sd->dev); + + return 0; +} + +static const struct v4l2_subdev_core_ops tc358746_core_ops = { +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = tc358746_g_register, + .s_register = tc358746_s_register, +#endif +}; + +static const struct v4l2_subdev_video_ops tc358746_video_ops = { + .s_stream = tc358746_s_stream, +}; + +static const struct v4l2_subdev_pad_ops tc358746_pad_ops = { + .init_cfg = tc358746_init_cfg, + .enum_mbus_code = tc358746_enum_mbus_code, + .set_fmt = tc358746_set_fmt, + .get_fmt = v4l2_subdev_get_fmt, + .link_validate = tc358746_link_validate, + .get_mbus_config = tc358746_get_mbus_config, +}; + +static const struct v4l2_subdev_ops tc358746_ops = { + .core = &tc358746_core_ops, + .video = &tc358746_video_ops, + .pad = &tc358746_pad_ops, +}; + +static const struct media_entity_operations tc358746_entity_ops = { + .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1, + .link_validate = v4l2_subdev_link_validate, +}; + +static int tc358746_mclk_enable(struct clk_hw *hw) +{ + struct tc358746 *tc358746 = clk_hw_to_tc358746(hw); + unsigned int div; + u32 val; + int err; + + div = tc358746->mclk_postdiv / 2; + val = MCLK_HIGH(div - 1) | MCLK_LOW(div - 1); + dev_dbg(tc358746->sd.dev, "MCLKCTL: %u (0x%x)\n", val, val); + err = tc358746_write(tc358746, MCLKCTL_REG, val); + if (err) + return err; + + if (tc358746->mclk_prediv == 8) + val = MCLKDIV(MCLKDIV_8); + else if (tc358746->mclk_prediv == 4) + val = MCLKDIV(MCLKDIV_4); + else + val = MCLKDIV(MCLKDIV_2); + + dev_dbg(tc358746->sd.dev, "CLKCTL[MCLKDIV]: %u (0x%x)\n", val, val); + + return tc358746_update_bits(tc358746, CLKCTL_REG, MCLKDIV_MASK, val); +} + +static void tc358746_mclk_disable(struct clk_hw *hw) +{ + struct tc358746 *tc358746 = clk_hw_to_tc358746(hw); + + tc358746_write(tc358746, MCLKCTL_REG, 0); +} + +static long +tc358746_find_mclk_settings(struct tc358746 *tc358746, unsigned long mclk_rate) +{ + unsigned long pll_rate = tc358746->pll_rate; + const unsigned char prediv[] = { 2, 4, 8 }; + unsigned int mclk_prediv, mclk_postdiv; + struct device *dev = tc358746->sd.dev; + unsigned int postdiv, mclkdiv; + unsigned long best_mclk_rate; + unsigned int i; + + /* + * MCLK-Div + * -------------------´`--------------------- + * ´ ` + * +-------------+ +------------------------+ + * | MCLK-PreDiv | | MCLK-PostDiv | + * PLL --> | (2/4/8) | --> | (mclk_low + mclk_high) | --> MCLK + * +-------------+ +------------------------+ + * + * The register value of mclk_low/high is mclk_low/high+1, i.e.: + * mclk_low/high = 1 --> 2 MCLK-Ref Counts + * mclk_low/high = 255 --> 256 MCLK-Ref Counts == max. + * If mclk_low and mclk_high are 0 then MCLK is disabled. + * + * Keep it simple and support 50/50 duty cycles only for now, + * so the calc will be: + * + * MCLK = PLL / (MCLK-PreDiv * 2 * MCLK-PostDiv) + */ + + if (mclk_rate == tc358746->mclk_rate) + return mclk_rate; + + /* Highest possible rate */ + mclkdiv = pll_rate / mclk_rate; + if (mclkdiv <= 8) { + mclk_prediv = 2; + mclk_postdiv = 4; + best_mclk_rate = pll_rate / (2 * 4); + goto out; + } + + /* First check the prediv */ + for (i = 0; i < ARRAY_SIZE(prediv); i++) { + postdiv = mclkdiv / prediv[i]; + + if (postdiv % 2) + continue; + + if (postdiv >= 4 && postdiv <= 512) { + mclk_prediv = prediv[i]; + mclk_postdiv = postdiv; + best_mclk_rate = pll_rate / (prediv[i] * postdiv); + goto out; + } + } + + /* No suitable prediv found, so try to adjust the postdiv */ + for (postdiv = 4; postdiv <= 512; postdiv += 2) { + unsigned int pre; + + pre = mclkdiv / postdiv; + if (pre == 2 || pre == 4 || pre == 8) { + mclk_prediv = pre; + mclk_postdiv = postdiv; + best_mclk_rate = pll_rate / (pre * postdiv); + goto out; + } + } + + /* The MCLK <-> PLL gap is to high -> use largest possible div */ + mclk_prediv = 8; + mclk_postdiv = 512; + best_mclk_rate = pll_rate / (8 * 512); + +out: + tc358746->mclk_prediv = mclk_prediv; + tc358746->mclk_postdiv = mclk_postdiv; + tc358746->mclk_rate = best_mclk_rate; + + if (best_mclk_rate != mclk_rate) + dev_warn(dev, "Request MCLK freq:%lu, found MCLK freq:%lu\n", + mclk_rate, best_mclk_rate); + + dev_dbg(dev, "Found MCLK settings: freq:%lu prediv:%u postdiv:%u\n", + best_mclk_rate, mclk_prediv, mclk_postdiv); + + return best_mclk_rate; +} + +static unsigned long +tc358746_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) +{ + struct tc358746 *tc358746 = clk_hw_to_tc358746(hw); + unsigned int prediv, postdiv; + u32 val; + int err; + + err = tc358746_read(tc358746, MCLKCTL_REG, &val); + if (err) + return 0; + + postdiv = FIELD_GET(MCLK_LOW_MASK, val) + 1; + postdiv += FIELD_GET(MCLK_HIGH_MASK, val) + 1; + + err = tc358746_read(tc358746, CLKCTL_REG, &val); + if (err) + return 0; + + prediv = FIELD_GET(MCLKDIV_MASK, val); + if (prediv == MCLKDIV_8) + prediv = 8; + else if (prediv == MCLKDIV_4) + prediv = 4; + else + prediv = 2; + + return tc358746->pll_rate / (prediv * postdiv); +} + +static long tc358746_mclk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct tc358746 *tc358746 = clk_hw_to_tc358746(hw); + + *parent_rate = tc358746->pll_rate; + + return tc358746_find_mclk_settings(tc358746, rate); +} + +static int tc358746_mclk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct tc358746 *tc358746 = clk_hw_to_tc358746(hw); + + tc358746_find_mclk_settings(tc358746, rate); + + return tc358746_mclk_enable(hw); +} + +static const struct clk_ops tc358746_mclk_ops = { + .enable = tc358746_mclk_enable, + .disable = tc358746_mclk_disable, + .recalc_rate = tc358746_recalc_rate, + .round_rate = tc358746_mclk_round_rate, + .set_rate = tc358746_mclk_set_rate, +}; + +static int tc358746_setup_mclk_provider(struct tc358746 *tc358746) +{ + struct clk_init_data mclk_initdata = { }; + struct device *dev = tc358746->sd.dev; + const char *mclk_name; + int err; + + /* MCLK clk provider support is optional */ + if (!device_property_present(dev, "#clock-cells")) + return 0; + + /* 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"); + return err; + } + + 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"); + + return err; +} + +static int +tc358746_init_subdev(struct tc358746 *tc358746, struct i2c_client *client) +{ + struct v4l2_subdev *sd = &tc358746->sd; + int err; + + 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 err; + + err = v4l2_subdev_init_finalize(sd); + if (err) + media_entity_cleanup(&sd->entity); + + return err; +} + +static int +tc358746_init_output_port(struct tc358746 *tc358746, unsigned long refclk) +{ + struct device *dev = tc358746->sd.dev; + struct v4l2_fwnode_endpoint *vep; + unsigned long csi_link_rate; + struct fwnode_handle *ep; + unsigned char csi_lanes; + int err; + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), TC358746_SOURCE, + 0, 0); + if (!ep) { + dev_err(dev, "Missing endpoint node\n"); + return -EINVAL; + } + + /* 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"); + return err; + } + + 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; + } + + /* 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; + } + + err = phy_mipi_dphy_get_default_config_for_hsclk(tc358746->pll_rate, + csi_lanes, &tc358746->dphy_cfg); + if (err) + goto err; + + tc358746->vb_size = TC358746_VB_DEFAULT_SIZE; + + return 0; + +err: + v4l2_fwnode_endpoint_free(vep); + + return err; +} + +static int tc358746_init_hw(struct tc358746 *tc358746) +{ + struct device *dev = tc358746->sd.dev; + unsigned int chipid; + u32 val; + int err; + + err = pm_runtime_resume_and_get(dev); + if (err < 0) { + dev_err(dev, "Failed to resume the device\n"); + return err; + } + + /* Ensure that CSI interface is put into LP-11 state */ + err = tc358746_sw_reset(tc358746); + if (err) { + pm_runtime_put_sync(dev); + dev_err(dev, "Failed to reset the device\n"); + return err; + } + + err = tc358746_read(tc358746, CHIPID_REG, &val); + pm_runtime_mark_last_busy(dev); + pm_runtime_put_sync_autosuspend(dev); + if (err) + return -ENODEV; + + chipid = FIELD_GET(CHIPID, val); + if (chipid != 0x44) { + dev_err(dev, "Invalid chipid 0x%02x\n", chipid); + return -ENODEV; + } + + return 0; +} + +static int tc358746_init_controls(struct tc358746 *tc358746) +{ + u64 *link_frequencies = tc358746->csi_vep.link_frequencies; + struct v4l2_ctrl *ctrl; + int err; + + err = v4l2_ctrl_handler_init(&tc358746->ctrl_hdl, 1); + if (err) + return err; + + /* + * The driver currently supports only one link-frequency, regardless of + * the input from the firmware, see: tc358746_init_output_port(). So + * report only the first frequency from the array of possible given + * frequencies. + */ + ctrl = v4l2_ctrl_new_int_menu(&tc358746->ctrl_hdl, NULL, + V4L2_CID_LINK_FREQ, 0, 0, + link_frequencies); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + err = tc358746->ctrl_hdl.error; + if (err) { + v4l2_ctrl_handler_free(&tc358746->ctrl_hdl); + return err; + } + + tc358746->sd.ctrl_handler = &tc358746->ctrl_hdl; + + return 0; +} + +static int tc358746_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd, + struct v4l2_async_subdev *asd) +{ + struct tc358746 *tc358746 = + container_of(notifier, struct tc358746, notifier); + u32 flags = MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE; + struct media_pad *sink = &tc358746->pads[TC358746_SINK]; + + return v4l2_create_fwnode_links_to_pad(sd, sink, flags); +} + +static const struct v4l2_async_notifier_operations tc358746_notify_ops = { + .bound = tc358746_notify_bound, +}; + +static int tc358746_async_register(struct tc358746 *tc358746) +{ + struct v4l2_fwnode_endpoint vep = { + .bus_type = V4L2_MBUS_PARALLEL, + }; + struct v4l2_async_subdev *asd; + struct fwnode_handle *ep; + int err; + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(tc358746->sd.dev), + TC358746_SINK, 0, 0); + if (!ep) + return -ENOTCONN; + + err = v4l2_fwnode_endpoint_parse(ep, &vep); + if (err) { + fwnode_handle_put(ep); + return err; + } + + v4l2_async_nf_init(&tc358746->notifier); + asd = v4l2_async_nf_add_fwnode_remote(&tc358746->notifier, ep, + struct v4l2_async_subdev); + fwnode_handle_put(ep); + + if (IS_ERR(asd)) { + err = PTR_ERR(asd); + goto err_cleanup; + } + + tc358746->notifier.ops = &tc358746_notify_ops; + + err = v4l2_async_subdev_nf_register(&tc358746->sd, &tc358746->notifier); + if (err) + goto err_cleanup; + + tc358746->sd.fwnode = fwnode_graph_get_endpoint_by_id( + dev_fwnode(tc358746->sd.dev), TC358746_SOURCE, 0, 0); + + err = v4l2_async_register_subdev(&tc358746->sd); + if (err) + goto err_unregister; + + return 0; + +err_unregister: + fwnode_handle_put(tc358746->sd.fwnode); + v4l2_async_nf_unregister(&tc358746->notifier); +err_cleanup: + v4l2_async_nf_cleanup(&tc358746->notifier); + + return err; +} + +static int tc358746_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct tc358746 *tc358746; + unsigned long refclk; + unsigned int i; + int err; + + 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 * HZ_PER_MHZ || refclk > 40 * HZ_PER_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"); + + err = tc358746_init_subdev(tc358746, client); + if (err) + return dev_err_probe(dev, err, "Failed to init subdev\n"); + + err = tc358746_init_output_port(tc358746, refclk); + if (err) + goto err_subdev; + + /* + * Keep this order since we need the output port link-frequencies + * information. + */ + err = tc358746_init_controls(tc358746); + if (err) + goto err_fwnode; + + dev_set_drvdata(dev, tc358746); + + /* Set to 1sec to give the stream reconfiguration enough time */ + pm_runtime_set_autosuspend_delay(dev, 1000); + pm_runtime_use_autosuspend(dev); + pm_runtime_enable(dev); + + err = tc358746_init_hw(tc358746); + if (err) + goto err_pm; + + err = tc358746_setup_mclk_provider(tc358746); + if (err) + goto err_pm; + + err = tc358746_async_register(tc358746); + if (err < 0) + goto err_pm; + + dev_dbg(dev, "%s found @ 0x%x (%s)\n", client->name, + client->addr, client->adapter->name); + + return 0; + +err_pm: + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + pm_runtime_dont_use_autosuspend(dev); + v4l2_ctrl_handler_free(&tc358746->ctrl_hdl); +err_fwnode: + v4l2_fwnode_endpoint_free(&tc358746->csi_vep); +err_subdev: + v4l2_subdev_cleanup(&tc358746->sd); + media_entity_cleanup(&tc358746->sd.entity); + + return err; +} + +static void tc358746_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct tc358746 *tc358746 = to_tc358746(sd); + + v4l2_subdev_cleanup(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); + fwnode_handle_put(sd->fwnode); + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + + pm_runtime_disable(sd->dev); + pm_runtime_set_suspended(sd->dev); + pm_runtime_dont_use_autosuspend(sd->dev); +} + +static int tc358746_suspend(struct device *dev) +{ + struct tc358746 *tc358746 = dev_get_drvdata(dev); + int err; + + clk_disable_unprepare(tc358746->refclk); + + err = regulator_bulk_disable(ARRAY_SIZE(tc358746_supplies), + tc358746->supplies); + if (err) + clk_prepare_enable(tc358746->refclk); + + return err; +} + +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) + goto err; + + /* min. 700us ... 1ms */ + usleep_range(1000, 1500); + + /* + * Enable the PLL here since it can be called by the clk-framework or by + * the .s_stream() callback. So this is the common place for both. + */ + err = tc358746_apply_pll_config(tc358746); + if (err) + goto err_clk; + + return 0; + +err_clk: + clk_disable_unprepare(tc358746->refclk); +err: + regulator_bulk_disable(ARRAY_SIZE(tc358746_supplies), + tc358746->supplies); + return err; +} + +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 "); +MODULE_LICENSE("GPL"); From 88b18dba5c9ec84e17c22cdd26e2dbfd75817de2 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 24 Oct 2022 13:12:21 +0200 Subject: [PATCH 44/47] media: ov2640: Drop legacy includes The driver was including legacy headers despite using just . Drop the surplus includes. Cc: Akinobu Mita Signed-off-by: Linus Walleij Signed-off-by: Sakari Ailus --- drivers/media/i2c/ov2640.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/media/i2c/ov2640.c b/drivers/media/i2c/ov2640.c index 29ed0ef8c033..39d56838a4ef 100644 --- a/drivers/media/i2c/ov2640.c +++ b/drivers/media/i2c/ov2640.c @@ -16,9 +16,7 @@ #include #include #include -#include #include -#include #include #include From bee1bc81d3abf5af7bc0e4a39e52f0c4f91d5d36 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 24 Oct 2022 13:12:22 +0200 Subject: [PATCH 45/47] media: ov7670: Drop unused include The driver includes the legacy header but does not use any symbols from it. Drop the include. Cc: Jonathan Corbet Cc: Akinobu Mita Signed-off-by: Linus Walleij Signed-off-by: Sakari Ailus --- drivers/media/i2c/ov7670.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/media/i2c/ov7670.c b/drivers/media/i2c/ov7670.c index 4b9b156b53c7..11d3bef65d43 100644 --- a/drivers/media/i2c/ov7670.c +++ b/drivers/media/i2c/ov7670.c @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include From 27cdfbdb9f37192377b993689ed3235a3f80ac8b Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 24 Oct 2022 13:12:23 +0200 Subject: [PATCH 46/47] media: ov9650: Drop platform data code path Nothing in the kernel uses the platform data code path. Drop it, and drop the use of the old legacy API in the process. Cc: Kieran Bingham Cc: Andrzej Hajda Cc: Akinobu Mita Signed-off-by: Linus Walleij Signed-off-by: Sakari Ailus --- drivers/media/i2c/ov9650.c | 49 ++------------------------------------ include/media/i2c/ov9650.h | 24 ------------------- 2 files changed, 2 insertions(+), 71 deletions(-) delete mode 100644 include/media/i2c/ov9650.h diff --git a/drivers/media/i2c/ov9650.c b/drivers/media/i2c/ov9650.c index 4d458993e6d6..7e7cb1e4520e 100644 --- a/drivers/media/i2c/ov9650.c +++ b/drivers/media/i2c/ov9650.c @@ -10,7 +10,6 @@ */ #include #include -#include #include #include #include @@ -30,7 +29,6 @@ #include #include #include -#include static int debug; module_param(debug, int, 0644); @@ -1402,38 +1400,6 @@ static const struct v4l2_subdev_ops ov965x_subdev_ops = { .video = &ov965x_video_ops, }; -/* - * Reset and power down GPIOs configuration - */ -static int ov965x_configure_gpios_pdata(struct ov965x *ov965x, - const struct ov9650_platform_data *pdata) -{ - int ret, i; - int gpios[NUM_GPIOS]; - struct device *dev = regmap_get_device(ov965x->regmap); - - gpios[GPIO_PWDN] = pdata->gpio_pwdn; - gpios[GPIO_RST] = pdata->gpio_reset; - - for (i = 0; i < ARRAY_SIZE(ov965x->gpios); i++) { - int gpio = gpios[i]; - - if (!gpio_is_valid(gpio)) - continue; - ret = devm_gpio_request_one(dev, gpio, - GPIOF_OUT_INIT_HIGH, "OV965X"); - if (ret < 0) - return ret; - v4l2_dbg(1, debug, &ov965x->sd, "set gpio %d to 1\n", gpio); - - gpio_set_value_cansleep(gpio, 1); - gpio_export(gpio, 0); - ov965x->gpios[i] = gpio_to_desc(gpio); - } - - return 0; -} - static int ov965x_configure_gpios(struct ov965x *ov965x) { struct device *dev = regmap_get_device(ov965x->regmap); @@ -1493,7 +1459,6 @@ static int ov965x_detect_sensor(struct v4l2_subdev *sd) static int ov965x_probe(struct i2c_client *client) { - const struct ov9650_platform_data *pdata = client->dev.platform_data; struct v4l2_subdev *sd; struct ov965x *ov965x; int ret; @@ -1513,17 +1478,7 @@ static int ov965x_probe(struct i2c_client *client) return PTR_ERR(ov965x->regmap); } - if (pdata) { - if (pdata->mclk_frequency == 0) { - dev_err(&client->dev, "MCLK frequency not specified\n"); - return -EINVAL; - } - ov965x->mclk_frequency = pdata->mclk_frequency; - - ret = ov965x_configure_gpios_pdata(ov965x, pdata); - if (ret < 0) - return ret; - } else if (dev_fwnode(&client->dev)) { + if (dev_fwnode(&client->dev)) { ov965x->clk = devm_clk_get(&client->dev, NULL); if (IS_ERR(ov965x->clk)) return PTR_ERR(ov965x->clk); @@ -1534,7 +1489,7 @@ static int ov965x_probe(struct i2c_client *client) return ret; } else { dev_err(&client->dev, - "Neither platform data nor device property specified\n"); + "No device properties specified\n"); return -EINVAL; } diff --git a/include/media/i2c/ov9650.h b/include/media/i2c/ov9650.h deleted file mode 100644 index 3ec7e06955b4..000000000000 --- a/include/media/i2c/ov9650.h +++ /dev/null @@ -1,24 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * OV9650/OV9652 camera sensors driver - * - * Copyright (C) 2013 Sylwester Nawrocki - */ -#ifndef OV9650_H_ -#define OV9650_H_ - -/** - * struct ov9650_platform_data - ov9650 driver platform data - * @mclk_frequency: the sensor's master clock frequency in Hz - * @gpio_pwdn: number of a GPIO connected to OV965X PWDN pin - * @gpio_reset: number of a GPIO connected to OV965X RESET pin - * - * If any of @gpio_pwdn or @gpio_reset are unused then they should be - * set to a negative value. @mclk_frequency must always be specified. - */ -struct ov9650_platform_data { - unsigned long mclk_frequency; - int gpio_pwdn; - int gpio_reset; -}; -#endif /* OV9650_H_ */ From 7336c54a562b479866d2de2abc61487a4e07b0b9 Mon Sep 17 00:00:00 2001 From: Mikhail Rudenko Date: Wed, 26 Oct 2022 15:45:51 +0300 Subject: [PATCH 47/47] media: i2c: ov4689: code cleanup Fix minor nits from the last review round: extra {}, temporary variables for ARRAYS_SIZE(), redundant check in ov4689_check_hwcfg. No functional change intended. Signed-off-by: Mikhail Rudenko Signed-off-by: Sakari Ailus --- drivers/media/i2c/ov4689.c | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/drivers/media/i2c/ov4689.c b/drivers/media/i2c/ov4689.c index 419ff7371ba8..c602e507d42b 100644 --- a/drivers/media/i2c/ov4689.c +++ b/drivers/media/i2c/ov4689.c @@ -623,9 +623,8 @@ static int ov4689_map_gain(struct ov4689 *ov4689, int logical_gain, int *result) for (n = 0; n < ARRAY_SIZE(ov4689_gain_ranges); n++) { if (logical_gain >= ov4689_gain_ranges[n].logical_min && - logical_gain <= ov4689_gain_ranges[n].logical_max) { + logical_gain <= ov4689_gain_ranges[n].logical_max) break; - } } if (n == ARRAY_SIZE(ov4689_gain_ranges)) { @@ -815,23 +814,22 @@ static int ov4689_check_sensor_id(struct ov4689 *ov4689, static int ov4689_configure_regulators(struct ov4689 *ov4689) { - unsigned int supplies_count = ARRAY_SIZE(ov4689_supply_names); unsigned int i; - for (i = 0; i < supplies_count; i++) + for (i = 0; i < ARRAY_SIZE(ov4689_supply_names); i++) ov4689->supplies[i].supply = ov4689_supply_names[i]; - return devm_regulator_bulk_get(&ov4689->client->dev, supplies_count, + return devm_regulator_bulk_get(&ov4689->client->dev, + ARRAY_SIZE(ov4689_supply_names), ov4689->supplies); } static u64 ov4689_check_link_frequency(struct v4l2_fwnode_endpoint *ep) { - unsigned int freqs_count = ARRAY_SIZE(link_freq_menu_items); const u64 *freqs = link_freq_menu_items; unsigned int i, j; - for (i = 0; i < freqs_count; i++) { + for (i = 0; i < ARRAY_SIZE(link_freq_menu_items); i++) { for (j = 0; j < ep->nr_of_link_frequencies; j++) if (freqs[i] == ep->link_frequencies[j]) return freqs[i]; @@ -864,12 +862,6 @@ static int ov4689_check_hwcfg(struct device *dev) goto out_free_bus_cfg; } - if (!bus_cfg.nr_of_link_frequencies) { - dev_err(dev, "No link frequencies defined\n"); - ret = -EINVAL; - goto out_free_bus_cfg; - } - if (!ov4689_check_link_frequency(&bus_cfg)) { dev_err(dev, "No supported link frequency found\n"); ret = -EINVAL;