* [PATCH v3 0/3] media: i2c: Add os02g10 camera sensor driver
@ 2026-04-24 9:25 Elgin Perumbilly
2026-04-24 9:25 ` [PATCH v3 1/3] dt-bindings: media: i2c: Add os02g10 sensor Elgin Perumbilly
` (5 more replies)
0 siblings, 6 replies; 27+ messages in thread
From: Elgin Perumbilly @ 2026-04-24 9:25 UTC (permalink / raw)
To: sakari.ailus, laurent.pinchart
Cc: Elgin Perumbilly, Tarang Raval, Mauro Carvalho Chehab,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Hans Verkuil,
Hans de Goede, Vladimir Zapolskiy, Mehdi Djait, Benjamin Mugnier,
Sylvain Petinot, Bryan O'Donoghue, Heimir Thor Sverrisson,
Hardevsinh Palaniya, Himanshu Bhavani, Svyatoslav Ryhel,
linux-media, devicetree, linux-kernel
The following features are supported:
- Manual exposure an gain control support.
- vblank/hblank control support.
- vflip/hflip control support
- Test pattern control support.
- Dynamic mode configuration (e.g. up to 1920 x 1080 @ 30 fps, SBGGR10)
The driver is tested on mainline branch v7.0-rc2 on IMX8MP Debix Model a.
v4l2-compliance 1.31.0-5387, 64 bits, 64-bit time_t
v4l2-compliance SHA: 5508bc4301ac 2025-08-25 08:14:22
Compliance test for device /dev/v4l-subdev3:
Driver Info:
Driver version : 7.0.0
Capabilities : 0x00000000
Client Capabilities: 0x0000000000000002
interval-uses-which
Required ioctls:
test VIDIOC_SUDBEV_QUERYCAP: OK
test invalid ioctls: OK
Allow for multiple opens:
test second /dev/v4l-subdev3 open: OK
test VIDIOC_SUBDEV_QUERYCAP: OK
test for unlimited opens: OK
Debug ioctls:
test VIDIOC_LOG_STATUS: OK (Not Supported)
Input ioctls:
test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
test VIDIOC_ENUMAUDIO: OK (Not Supported)
test VIDIOC_G/S/ENUMINPUT: OK (Not Supported)
test VIDIOC_G/S_AUDIO: OK (Not Supported)
Inputs: 0 Audio Inputs: 0 Tuners: 0
Output ioctls:
test VIDIOC_G/S_MODULATOR: OK (Not Supported)
test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
test VIDIOC_ENUMAUDOUT: OK (Not Supported)
test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
test VIDIOC_G/S_AUDOUT: OK (Not Supported)
Outputs: 0 Audio Outputs: 0 Modulators: 0
Input/Output configuration ioctls:
test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported)
test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported)
test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported)
test VIDIOC_G/S_EDID: OK (Not Supported)
Control ioctls:
test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK
test VIDIOC_QUERYCTRL: OK
test VIDIOC_G/S_CTRL: OK
test VIDIOC_G/S/TRY_EXT_CTRLS: OK
test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK
test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
Standard Controls: 12 Private Controls: 0
Format ioctls:
test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK (Not Supported)
test VIDIOC_G/S_PARM: OK (Not Supported)
test VIDIOC_G_FBUF: OK (Not Supported)
test VIDIOC_G_FMT: OK (Not Supported)
test VIDIOC_TRY_FMT: OK (Not Supported)
test VIDIOC_S_FMT: OK (Not Supported)
test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
test Cropping: OK (Not Supported)
test Composing: OK (Not Supported)
test Scaling: OK (Not Supported)
Codec ioctls:
test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
test VIDIOC_G_ENC_INDEX: OK (Not Supported)
test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)
Buffer ioctls:
test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK (Not Supported)
test CREATE_BUFS maximum buffers: OK
test VIDIOC_REMOVE_BUFS: OK
test VIDIOC_EXPBUF: OK (Not Supported)
test Requests: OK (Not Supported)
test blocking wait: OK (Not Supported)
Total for device /dev/v4l-subdev3: 46, Succeeded: 46, Failed: 0, Warnings: 0
V2 -> V3
Patch 1/3:
- Drop the fixed data-lanes property, as the sensor supports only a 2-lane MIPI interface.
Patch 2/3:
- Use regmap paging features for page selection.
- Use sd instead of _sd.
- Remove an extra blank line.
- Remove hblank control, as it is not used to set hblank on this sensor.
- Remove an unnecessary error check.
- Reduce the scope of the ret variable in os02g10_set_pad_format().
- Rely on the control value for link_freq_index.
- Call os02g10_parse_endpoint() before initializing regulators, GPIOs, and the clock in probe().
Add new Patch 3/3:
- Add dynamic mode configuration instead of using a hardcoded specific mode, as suggested by Laurent.
V1 -> V2
patch 1/2:
- Corrected $id to ovti,os02g10.yaml
- Fixed data-lanes property structure to use proper items format
patch 2/2:
- Mode-specific register writes handled dynamically.
- Moved page 2 registers into a dedicated page 2 block.
- Used pm_runtime_get_if_active() in set_ctrls.
- Resolved negative hblank issue.
- Improved error messages in disable_stream.
- Updated V4L2_SEL_TGT_CROP and V4L2_SEL_TGT_CROP_BOUNDS.
- Added my name to the author list.
Elgin Perumbilly (2):
dt-bindings: media: i2c: Add os02g10 sensor
media: i2c: add os02g10 image sensor driver
Tarang Raval (1):
media: i2c: os02g10: implement crop handling with set_selection
.../bindings/media/i2c/ovti,os02g10.yaml | 91 ++
MAINTAINERS | 8 +
drivers/media/i2c/Kconfig | 10 +
drivers/media/i2c/Makefile | 1 +
drivers/media/i2c/os02g10.c | 989 ++++++++++++++++++
5 files changed, 1099 insertions(+)
create mode 100644 Documentation/devicetree/bindings/media/i2c/ovti,os02g10.yaml
create mode 100644 drivers/media/i2c/os02g10.c
--
2.34.1
^ permalink raw reply [flat|nested] 27+ messages in thread* [PATCH v3 1/3] dt-bindings: media: i2c: Add os02g10 sensor 2026-04-24 9:25 [PATCH v3 0/3] media: i2c: Add os02g10 camera sensor driver Elgin Perumbilly @ 2026-04-24 9:25 ` Elgin Perumbilly 2026-04-25 9:43 ` Krzysztof Kozlowski 2026-06-12 9:35 ` Vladimir Zapolskiy 2026-04-24 9:25 ` [PATCH v3 2/3] media: i2c: add os02g10 image sensor driver Elgin Perumbilly ` (4 subsequent siblings) 5 siblings, 2 replies; 27+ messages in thread From: Elgin Perumbilly @ 2026-04-24 9:25 UTC (permalink / raw) To: sakari.ailus, laurent.pinchart Cc: Elgin Perumbilly, Tarang Raval, Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Hans Verkuil, Hans de Goede, Vladimir Zapolskiy, Mehdi Djait, Sylvain Petinot, Benjamin Mugnier, Svyatoslav Ryhel, Hardevsinh Palaniya, Himanshu Bhavani, linux-media, devicetree, linux-kernel Add bindings for Omnivision OS02G10 sensor. Signed-off-by: Elgin Perumbilly <elgin.perumbilly@siliconsignals.io> --- .../bindings/media/i2c/ovti,os02g10.yaml | 91 +++++++++++++++++++ MAINTAINERS | 7 ++ 2 files changed, 98 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/i2c/ovti,os02g10.yaml diff --git a/Documentation/devicetree/bindings/media/i2c/ovti,os02g10.yaml b/Documentation/devicetree/bindings/media/i2c/ovti,os02g10.yaml new file mode 100644 index 000000000000..252059459390 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/ovti,os02g10.yaml @@ -0,0 +1,91 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/ovti,os02g10.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: OmniVision OS02G10 Image Sensor + +maintainers: + - Tarang Raval <tarang.raval@siliconsignals.io> + +description: + The OmniVision OS02G10 is a 2MP (1920x1080) color CMOS image sensor controlled + through an I2C-compatible SCCB bus. It outputs RAW10 format data and supports + a 2-lane MIPI interface. + +properties: + compatible: + const: ovti,os02g10 + + reg: + maxItems: 1 + + clocks: + items: + - description: XCLK clock + + avdd-supply: + description: Analog Domain Power Supply (2.8v) + + dovdd-supply: + description: I/O Domain Power Supply (1.8v) + + dvdd-supply: + description: Digital core Power Supply (1.5v) + + reset-gpios: + maxItems: 1 + description: Reset Pin GPIO Control (active low) + + port: + description: MIPI CSI-2 transmitter port + $ref: /schemas/graph.yaml#/$defs/port-base + additionalProperties: false + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + required: + - link-frequencies + +required: + - compatible + - reg + - clocks + - avdd-supply + - dovdd-supply + - dvdd-supply + - port + +additionalProperties: false + +examples: + - | + #include <dt-bindings/gpio/gpio.h> + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + camera-sensor@3c { + compatible = "ovti,os02g10"; + reg = <0x3c>; + clocks = <&os02g10_clk>; + reset-gpios = <&gpio1 7 GPIO_ACTIVE_LOW>; + + avdd-supply = <&os02g10_avdd_2v8>; + dvdd-supply = <&os02g10_dvdd_1v2>; + dovdd-supply = <&os2gb10_dovdd_1v8>; + + port { + cam_out: endpoint { + remote-endpoint = <&mipi_in_cam>; + data-lanes = <1 2>; + link-frequencies = /bits/ 64 <720000000>; + }; + }; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 900fc00b73e6..8a0a55073c30 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19443,6 +19443,13 @@ T: git git://linuxtv.org/media_tree.git F: Documentation/devicetree/bindings/media/i2c/ovti,og0ve1b.yaml F: drivers/media/i2c/og0ve1b.c +OMNIVISION OS02G10 SENSOR DRIVER +M: Tarang Raval <tarang.raval@siliconsignals.io> +M: Elgin Perumbilly <elgin.perumbilly@siliconsignals.io> +L: linux-media@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/media/i2c/ovti,os02g10.yaml + OMNIVISION OS05B10 SENSOR DRIVER M: Himanshu Bhavani <himanshu.bhavani@siliconsignals.io> M: Elgin Perumbilly <elgin.perumbilly@siliconsignals.io> -- 2.34.1 ^ permalink raw reply related [flat|nested] 27+ messages in thread
* Re: [PATCH v3 1/3] dt-bindings: media: i2c: Add os02g10 sensor 2026-04-24 9:25 ` [PATCH v3 1/3] dt-bindings: media: i2c: Add os02g10 sensor Elgin Perumbilly @ 2026-04-25 9:43 ` Krzysztof Kozlowski 2026-06-12 9:35 ` Vladimir Zapolskiy 1 sibling, 0 replies; 27+ messages in thread From: Krzysztof Kozlowski @ 2026-04-25 9:43 UTC (permalink / raw) To: Elgin Perumbilly Cc: sakari.ailus, laurent.pinchart, Tarang Raval, Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Hans Verkuil, Hans de Goede, Vladimir Zapolskiy, Mehdi Djait, Sylvain Petinot, Benjamin Mugnier, Svyatoslav Ryhel, Hardevsinh Palaniya, Himanshu Bhavani, linux-media, devicetree, linux-kernel On Fri, Apr 24, 2026 at 02:55:45PM +0530, Elgin Perumbilly wrote: > Add bindings for Omnivision OS02G10 sensor. > > Signed-off-by: Elgin Perumbilly <elgin.perumbilly@siliconsignals.io> > --- > .../bindings/media/i2c/ovti,os02g10.yaml | 91 +++++++++++++++++++ > MAINTAINERS | 7 ++ > 2 files changed, 98 insertions(+) > create mode 100644 Documentation/devicetree/bindings/media/i2c/ovti,os02g10.yaml Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com> <form letter> This is an automated instruction, just in case, because many review tags are being ignored. If you know the process, just skip it entirely (please do not feel offended by me posting it here - no bad intentions intended, no patronizing, I just want to avoid wasted efforts). If you do not know the process, here is a short explanation: Please add Acked-by/Reviewed-by/Tested-by tags when posting new versions of patchset, under or above your Signed-off-by tag, unless patch changed significantly (e.g. new properties added to the DT bindings). Tag is "received", when provided in a message replied to you on the mailing list. Tools like b4 can help here ('b4 trailers -u ...'). However, there's no need to repost patches *only* to add the tags. The upstream maintainer will do that for tags received on the version they apply. https://elixir.bootlin.com/linux/v6.15/source/Documentation/process/submitting-patches.rst#L591 </form letter> Best regards, Krzysztof ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v3 1/3] dt-bindings: media: i2c: Add os02g10 sensor 2026-04-24 9:25 ` [PATCH v3 1/3] dt-bindings: media: i2c: Add os02g10 sensor Elgin Perumbilly 2026-04-25 9:43 ` Krzysztof Kozlowski @ 2026-06-12 9:35 ` Vladimir Zapolskiy 1 sibling, 0 replies; 27+ messages in thread From: Vladimir Zapolskiy @ 2026-06-12 9:35 UTC (permalink / raw) To: Elgin Perumbilly, sakari.ailus, laurent.pinchart Cc: Tarang Raval, Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Hans Verkuil, Hans de Goede, Mehdi Djait, Sylvain Petinot, Benjamin Mugnier, Svyatoslav Ryhel, Hardevsinh Palaniya, Himanshu Bhavani, linux-media, devicetree, linux-kernel On 4/24/26 12:25, Elgin Perumbilly wrote: > Add bindings for Omnivision OS02G10 sensor. > > Signed-off-by: Elgin Perumbilly <elgin.perumbilly@siliconsignals.io> > --- > .../bindings/media/i2c/ovti,os02g10.yaml | 91 +++++++++++++++++++ > MAINTAINERS | 7 ++ > 2 files changed, 98 insertions(+) > create mode 100644 Documentation/devicetree/bindings/media/i2c/ovti,os02g10.yaml > > diff --git a/Documentation/devicetree/bindings/media/i2c/ovti,os02g10.yaml b/Documentation/devicetree/bindings/media/i2c/ovti,os02g10.yaml > new file mode 100644 > index 000000000000..252059459390 > --- /dev/null > +++ b/Documentation/devicetree/bindings/media/i2c/ovti,os02g10.yaml > @@ -0,0 +1,91 @@ > +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) > +%YAML 1.2 > +--- > +$id: http://devicetree.org/schemas/media/i2c/ovti,os02g10.yaml# > +$schema: http://devicetree.org/meta-schemas/core.yaml# > + > +title: OmniVision OS02G10 Image Sensor > + > +maintainers: > + - Tarang Raval <tarang.raval@siliconsignals.io> > + > +description: > + The OmniVision OS02G10 is a 2MP (1920x1080) color CMOS image sensor controlled > + through an I2C-compatible SCCB bus. It outputs RAW10 format data and supports > + a 2-lane MIPI interface. > + > +properties: > + compatible: > + const: ovti,os02g10 > + > + reg: > + maxItems: 1 > + > + clocks: > + items: > + - description: XCLK clock > + > + avdd-supply: > + description: Analog Domain Power Supply (2.8v) > + > + dovdd-supply: > + description: I/O Domain Power Supply (1.8v) > + > + dvdd-supply: > + description: Digital core Power Supply (1.5v) > + > + reset-gpios: > + maxItems: 1 > + description: Reset Pin GPIO Control (active low) > + > + port: > + description: MIPI CSI-2 transmitter port > + $ref: /schemas/graph.yaml#/$defs/port-base > + additionalProperties: false > + > + properties: > + endpoint: > + $ref: /schemas/media/video-interfaces.yaml# > + unevaluatedProperties: false > + > + required: > + - link-frequencies > + > +required: > + - compatible > + - reg > + - clocks > + - avdd-supply > + - dovdd-supply > + - dvdd-supply > + - port > + > +additionalProperties: false > + > +examples: > + - | > + #include <dt-bindings/gpio/gpio.h> > + > + i2c { > + #address-cells = <1>; > + #size-cells = <0>; > + > + camera-sensor@3c { > + compatible = "ovti,os02g10"; > + reg = <0x3c>; > + clocks = <&os02g10_clk>; > + reset-gpios = <&gpio1 7 GPIO_ACTIVE_LOW>; > + > + avdd-supply = <&os02g10_avdd_2v8>; > + dvdd-supply = <&os02g10_dvdd_1v2>; > + dovdd-supply = <&os2gb10_dovdd_1v8>; > + > + port { > + cam_out: endpoint { > + remote-endpoint = <&mipi_in_cam>; > + data-lanes = <1 2>; > + link-frequencies = /bits/ 64 <720000000>; > + }; > + }; > + }; > + }; > diff --git a/MAINTAINERS b/MAINTAINERS > index 900fc00b73e6..8a0a55073c30 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -19443,6 +19443,13 @@ T: git git://linuxtv.org/media_tree.git > F: Documentation/devicetree/bindings/media/i2c/ovti,og0ve1b.yaml > F: drivers/media/i2c/og0ve1b.c > > +OMNIVISION OS02G10 SENSOR DRIVER > +M: Tarang Raval <tarang.raval@siliconsignals.io> > +M: Elgin Perumbilly <elgin.perumbilly@siliconsignals.io> > +L: linux-media@vger.kernel.org > +S: Maintained > +F: Documentation/devicetree/bindings/media/i2c/ovti,os02g10.yaml > + > OMNIVISION OS05B10 SENSOR DRIVER > M: Himanshu Bhavani <himanshu.bhavani@siliconsignals.io> > M: Elgin Perumbilly <elgin.perumbilly@siliconsignals.io> > -- > 2.34.1 > Reviewed-by: Vladimir Zapolskiy <vladimir.zapolskiy@linaro.org> -- Best wishes, Vladimir ^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH v3 2/3] media: i2c: add os02g10 image sensor driver 2026-04-24 9:25 [PATCH v3 0/3] media: i2c: Add os02g10 camera sensor driver Elgin Perumbilly 2026-04-24 9:25 ` [PATCH v3 1/3] dt-bindings: media: i2c: Add os02g10 sensor Elgin Perumbilly @ 2026-04-24 9:25 ` Elgin Perumbilly 2026-06-12 10:12 ` Vladimir Zapolskiy 2026-06-18 11:43 ` Sakari Ailus 2026-04-24 9:25 ` [PATCH v3 3/3] media: i2c: os02g10: implement crop handling with set_selection Elgin Perumbilly ` (3 subsequent siblings) 5 siblings, 2 replies; 27+ messages in thread From: Elgin Perumbilly @ 2026-04-24 9:25 UTC (permalink / raw) To: sakari.ailus, laurent.pinchart Cc: Elgin Perumbilly, Tarang Raval, Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Hans Verkuil, Hans de Goede, Vladimir Zapolskiy, Mehdi Djait, Sylvain Petinot, Benjamin Mugnier, Bryan O'Donoghue, Himanshu Bhavani, Heimir Thor Sverrisson, Jingjing Xiong, Svyatoslav Ryhel, linux-media, devicetree, linux-kernel Add a v4l2 subdevice driver for the Omnivision os02g10 sensor. The Omnivision os02g10 is a CMOS image sensor with an active array size of 1920 x 1080. The following features are supported: - Manual exposure an gain control support - vblank/hblank control support - vflip/hflip control support - Test pattern control support - Supported resolution: 1920 x 1080 @ 30fps (SBGGR10) Signed-off-by: Elgin Perumbilly <elgin.perumbilly@siliconsignals.io> Reviewed-by: Tarang Raval <tarang.raval@siliconsignals.io> --- MAINTAINERS | 1 + drivers/media/i2c/Kconfig | 10 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/os02g10.c | 949 ++++++++++++++++++++++++++++++++++++ 4 files changed, 961 insertions(+) create mode 100644 drivers/media/i2c/os02g10.c diff --git a/MAINTAINERS b/MAINTAINERS index 8a0a55073c30..693e71b51926 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19449,6 +19449,7 @@ M: Elgin Perumbilly <elgin.perumbilly@siliconsignals.io> L: linux-media@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/media/i2c/ovti,os02g10.yaml +F: drivers/media/i2c/os02g10.c OMNIVISION OS05B10 SENSOR DRIVER M: Himanshu Bhavani <himanshu.bhavani@siliconsignals.io> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 5eb1e0e0a87a..dd6e9562acf6 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -372,6 +372,16 @@ config VIDEO_OG0VE1B To compile this driver as a module, choose M here: the module will be called og0ve1b. +config VIDEO_OS02G10 + tristate "OmniVision OS02G10 sensor support" + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for Omnivision + OS02G10 camera sensor. + + To compile this driver as a module, choose M here: the + module will be called os02g10. + config VIDEO_OS05B10 tristate "OmniVision OS05B10 sensor support" select V4L2_CCI_I2C diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index a3a6396df3c4..a7554d2eb140 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -84,6 +84,7 @@ obj-$(CONFIG_VIDEO_MT9V032) += mt9v032.o obj-$(CONFIG_VIDEO_MT9V111) += mt9v111.o obj-$(CONFIG_VIDEO_OG01A1B) += og01a1b.o obj-$(CONFIG_VIDEO_OG0VE1B) += og0ve1b.o +obj-$(CONFIG_VIDEO_OS02G10) += os02g10.o obj-$(CONFIG_VIDEO_OS05B10) += os05b10.o obj-$(CONFIG_VIDEO_OV01A10) += ov01a10.o obj-$(CONFIG_VIDEO_OV02A10) += ov02a10.o diff --git a/drivers/media/i2c/os02g10.c b/drivers/media/i2c/os02g10.c new file mode 100644 index 000000000000..fad2dd0ad7aa --- /dev/null +++ b/drivers/media/i2c/os02g10.c @@ -0,0 +1,949 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * V4L2 Support for the OS02G10 + * + * Copyright (C) 2026 Silicon Signals Pvt. Ltd. + * + */ + +#include <linux/array_size.h> +#include <linux/bitops.h> +#include <linux/cleanup.h> +#include <linux/clk.h> +#include <linux/container_of.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/pm_runtime.h> +#include <linux/property.h> +#include <linux/regulator/consumer.h> +#include <linux/units.h> +#include <linux/types.h> +#include <linux/time.h> +#include <linux/regmap.h> + +#include <media/v4l2-cci.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> +#include <media/v4l2-mediabus.h> + +#define OS02G10_XCLK_FREQ (24 * HZ_PER_MHZ) + +/* Page 0 */ +#define OS02G10_REG_CHIPID CCI_REG24(0x002) +#define OS02G10_CHIPID 0x560247 + +#define OS02G10_REG_PLL_DIV_CTRL CCI_REG8(0x030) +#define OS02G10_REG_PLL_DCTL_BIAS_CTRL CCI_REG8(0x035) +#define OS02G10_REG_GATE_EN_CTRL CCI_REG8(0x038) +#define OS02G10_REG_DPLL_NC CCI_REG8(0x041) +#define OS02G10_REG_MP_PHASE_CTRL CCI_REG8(0x044) + +/* Page 1 */ +#define OS02G10_REG_STREAM_CTRL CCI_REG8(0x1b1) +#define OS02G10_STREAM_CTRL_ON 0x03 +#define OS02G10_STREAM_CTRL_OFF 0x00 + +#define OS02G10_REG_FRAME_SYNC CCI_REG8(0x101) + +#define OS02G10_REG_FRAME_LENGTH CCI_REG16(0x10e) +#define OS02G10_FRAME_LENGTH_MAX 0xffff +#define OS02G10_REG_HBLANK CCI_REG16(0x109) + +#define OS02G10_REG_FRAME_TEST_CTRL CCI_REG8(0x10d) +#define OS02G10_FRAME_EXP_SEPERATE_EN BIT(4) +#define OS02G10_TEST_PATTERN_ENABLE BIT(0) + +#define OS02G10_REG_ULP_PWD_DUMMY_CTRL CCI_REG8(0x13c) +#define OS02G10_REG_DC_LEVEL_LIMIT_EN CCI_REG8(0x146) +#define OS02G10_REG_DC_LEVEL_LIMIT_L CCI_REG8(0x147) +#define OS02G10_REG_BLC_DATA_LIMIT_L CCI_REG8(0x148) +#define OS02G10_REG_DC_BLC_LIMIT_H CCI_REG8(0x149) + +#define OS02G10_REG_HS_LP_CTRL CCI_REG8(0x192) +#define OS02G10_REG_HS_LEVEL CCI_REG8(0x19d) +#define OS02G10_REG_HS_DRV CCI_REG8(0x19e) + +#define OS02G10_REG_GB_SUBOFFSET CCI_REG8(0x1f0) +#define OS02G10_REG_BLUE_SUBOFFSET CCI_REG8(0x1f1) +#define OS02G10_REG_RED_SUBOFFSET CCI_REG8(0x1f2) +#define OS02G10_REG_GR_SUBOFFSET CCI_REG8(0x1f3) + +#define OS02G10_REG_ABL_TRIGGER CCI_REG8(0x1fa) +#define OS02G10_REG_ABL CCI_REG8(0x1fb) + +#define OS02G10_REG_H_SIZE_MIPI CCI_REG16(0x18e) +#define OS02G10_REG_V_SIZE_MIPI CCI_REG16(0x190) +#define OS02G10_REG_MIPI_TX_SPEED_CTRL CCI_REG8(0x1a1) + +#define OS02G10_REG_LONG_EXPOSURE CCI_REG16(0x103) +#define OS02G10_EXPOSURE_MIN 4 +#define OS02G10_EXPOSURE_STEP 1 +#define OS02G10_EXPOSURE_MARGIN 9 + +#define OS02G10_REG_ANALOG_GAIN CCI_REG8(0x124) +#define OS02G10_ANALOG_GAIN_MIN 0x10 +#define OS02G10_ANALOG_GAIN_MAX 0xf8 +#define OS02G10_ANALOG_GAIN_STEP 1 +#define OS02G10_ANALOG_GAIN_DEFAULT 0x10 + +#define OS02G10_REG_DIGITAL_GAIN_H CCI_REG8(0x137) +#define OS02G10_REG_DIGITAL_GAIN_L CCI_REG8(0x139) +#define OS02G10_DIGITAL_GAIN_MIN 0x40 +#define OS02G10_DIGITAL_GAIN_MAX 0x800 +#define OS02G10_DIGITAL_GAIN_STEP 64 +#define OS02G10_DIGITAL_GAIN_DEFAULT 0x40 + +#define OS02G10_REG_FLIP_MIRROR CCI_REG8(0x13f) +#define OS02G10_FLIP BIT(1) +#define OS02G10_MIRROR BIT(0) + +/* Page 2 */ +#define OS02G10_REG_V_START CCI_REG16(0x2a0) +#define OS02G10_REG_V_SIZE CCI_REG16(0x2a2) +#define OS02G10_REG_H_START CCI_REG16(0x2a4) +#define OS02G10_REG_H_SIZE CCI_REG16(0x2a6) + +#define OS02G10_REG_SIF_CTRL CCI_REG8(0x25e) +#define OS02G10_ORIENTATION_BAYER_FIX 0x32 + +#define OS02G10_LINK_FREQ_720MHZ (720 * HZ_PER_MHZ) + +/* OS02G10 native and active pixel array size */ +static const struct v4l2_rect os02g10_native_area = { + .top = 0, + .left = 0, + .width = 1928, + .height = 1088, +}; + +static const struct v4l2_rect os02g10_active_area = { + .top = 4, + .left = 4, + .width = 1920, + .height = 1080, +}; + +static const char * const os02g10_supply_name[] = { + "avdd", /* Analog power */ + "dovdd", /* Digital I/O power */ + "dvdd", /* Digital core power */ +}; + +struct os02g10 { + struct device *dev; + struct regmap *cci; + struct v4l2_subdev sd; + struct media_pad pad; + struct clk *xclk; + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[ARRAY_SIZE(os02g10_supply_name)]; + + /* V4L2 Controls */ + struct v4l2_ctrl_handler handler; + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *hflip; +}; + +struct os02g10_mode { + u32 width; + u32 height; + u32 vts_def; + u32 exp_def; + u32 x_start; + u32 y_start; +}; + +static const struct cci_reg_sequence os02g10_common_regs[] = { + { OS02G10_REG_PLL_DIV_CTRL, 0x0a}, + { OS02G10_REG_PLL_DCTL_BIAS_CTRL, 0x04}, + { OS02G10_REG_GATE_EN_CTRL, 0x11}, + { OS02G10_REG_DPLL_NC, 0x06}, + { OS02G10_REG_MP_PHASE_CTRL, 0x20}, + { CCI_REG8(0x119), 0x50}, + { CCI_REG8(0x11a), 0x0c}, + { CCI_REG8(0x11b), 0x0d}, + { CCI_REG8(0x11c), 0x00}, + { CCI_REG8(0x11d), 0x75}, + { CCI_REG8(0x11e), 0x52}, + { CCI_REG8(0x122), 0x14}, + { CCI_REG8(0x125), 0x44}, + { CCI_REG8(0x126), 0x0f}, + { OS02G10_REG_ULP_PWD_DUMMY_CTRL, 0xca}, + { CCI_REG8(0x13d), 0x4a}, + { CCI_REG8(0x140), 0x0f}, + { CCI_REG8(0x143), 0x38}, + { OS02G10_REG_DC_LEVEL_LIMIT_EN, 0x01}, + { OS02G10_REG_DC_LEVEL_LIMIT_L, 0x00}, + { OS02G10_REG_DC_BLC_LIMIT_H, 0x32}, + { CCI_REG8(0x150), 0x01}, + { CCI_REG8(0x151), 0x28}, + { CCI_REG8(0x152), 0x20}, + { CCI_REG8(0x153), 0x03}, + { CCI_REG8(0x157), 0x16}, + { CCI_REG8(0x159), 0x01}, + { CCI_REG8(0x15a), 0x01}, + { CCI_REG8(0x15d), 0x04}, + { CCI_REG8(0x16a), 0x04}, + { CCI_REG8(0x16b), 0x03}, + { CCI_REG8(0x16e), 0x28}, + { CCI_REG8(0x171), 0xc2}, + { CCI_REG8(0x172), 0x04}, + { CCI_REG8(0x173), 0x38}, + { CCI_REG8(0x174), 0x04}, + { CCI_REG8(0x179), 0x00}, + { CCI_REG8(0x17a), 0xb2}, + { CCI_REG8(0x17b), 0x10}, + { OS02G10_REG_HS_LP_CTRL, 0x02}, + { OS02G10_REG_HS_LEVEL, 0x03}, + { OS02G10_REG_HS_DRV, 0x55}, + { CCI_REG8(0x1b8), 0x70}, + { CCI_REG8(0x1b9), 0x70}, + { CCI_REG8(0x1ba), 0x70}, + { CCI_REG8(0x1bb), 0x70}, + { CCI_REG8(0x1bc), 0x00}, + { CCI_REG8(0x1c4), 0x6d}, + { CCI_REG8(0x1c5), 0x6d}, + { CCI_REG8(0x1c6), 0x6d}, + { CCI_REG8(0x1c7), 0x6d}, + { CCI_REG8(0x1cc), 0x11}, + { CCI_REG8(0x1cd), 0xe0}, + { CCI_REG8(0x1d0), 0x1b}, + { CCI_REG8(0x1d2), 0x76}, + { CCI_REG8(0x1d3), 0x68}, + { CCI_REG8(0x1d4), 0x68}, + { CCI_REG8(0x1d5), 0x73}, + { CCI_REG8(0x1d6), 0x73}, + { CCI_REG8(0x1e8), 0x55}, + { OS02G10_REG_GB_SUBOFFSET, 0x40}, + { OS02G10_REG_BLUE_SUBOFFSET, 0x40}, + { OS02G10_REG_RED_SUBOFFSET, 0x40}, + { OS02G10_REG_GR_SUBOFFSET, 0x40}, + { OS02G10_REG_ABL_TRIGGER, 0x1c}, + { OS02G10_REG_ABL, 0x33}, + { CCI_REG8(0x1fc), 0x80}, + { CCI_REG8(0x1fe), 0x80}, + { CCI_REG8(0x303), 0x67}, + { CCI_REG8(0x300), 0x59}, + { CCI_REG8(0x304), 0x11}, + { CCI_REG8(0x305), 0x04}, + { CCI_REG8(0x306), 0x0c}, + { CCI_REG8(0x307), 0x08}, + { CCI_REG8(0x308), 0x08}, + { CCI_REG8(0x309), 0x4f}, + { CCI_REG8(0x30b), 0x08}, + { CCI_REG8(0x30d), 0x26}, + { CCI_REG8(0x30f), 0x00}, + { CCI_REG8(0x234), 0xfe}, + { OS02G10_REG_MIPI_TX_SPEED_CTRL, 0x05}, +}; + +static const struct os02g10_mode supported_modes[] = { + { + .width = 1920, + .height = 1080, + .vts_def = 1246, + .exp_def = 1100, + .x_start = 2, + .y_start = 6, + }, +}; + +static const s64 link_freq_menu_items[] = { + OS02G10_LINK_FREQ_720MHZ, +}; + +static const char * const os02g10_test_pattern_menu[] = { + "Disabled", + "Colorbar", +}; + +static inline struct os02g10 *to_os02g10(struct v4l2_subdev *sd) +{ + return container_of_const(sd, struct os02g10, sd); +} + +static u32 os02g10_get_format_code(struct os02g10 *os02g10) +{ + static const u32 codes[2][2] = { + { MEDIA_BUS_FMT_SBGGR10_1X10, MEDIA_BUS_FMT_SGBRG10_1X10, }, + { MEDIA_BUS_FMT_SGRBG10_1X10, MEDIA_BUS_FMT_SRGGB10_1X10, }, + }; + u32 code = codes[os02g10->vflip->val][os02g10->hflip->val]; + + return code; +} + +static int os02g10_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct os02g10 *os02g10 = container_of_const(ctrl->handler, + struct os02g10, handler); + struct v4l2_subdev_state *state; + struct v4l2_mbus_framefmt *fmt; + int ret = 0; + + state = v4l2_subdev_get_locked_active_state(&os02g10->sd); + fmt = v4l2_subdev_state_get_format(state, 0); + + if (ctrl->id == V4L2_CID_VBLANK) { + /* Honour the VBLANK limits when setting exposure */ + s64 max = fmt->height + ctrl->val - OS02G10_EXPOSURE_MARGIN; + + ret = __v4l2_ctrl_modify_range(os02g10->exposure, + os02g10->exposure->minimum, max, + os02g10->exposure->step, + os02g10->exposure->default_value); + if (ret) + return ret; + } + + if (pm_runtime_get_if_active(os02g10->dev) == 0) + return 0; + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE: + cci_write(os02g10->cci, OS02G10_REG_LONG_EXPOSURE, + ctrl->val, &ret); + break; + case V4L2_CID_ANALOGUE_GAIN: + cci_write(os02g10->cci, OS02G10_REG_ANALOG_GAIN, + ctrl->val, &ret); + break; + case V4L2_CID_DIGITAL_GAIN: + cci_write(os02g10->cci, OS02G10_REG_DIGITAL_GAIN_L, + (ctrl->val & 0xff), &ret); + cci_write(os02g10->cci, OS02G10_REG_DIGITAL_GAIN_H, + ((ctrl->val >> 8) & 0x7), &ret); + break; + case V4L2_CID_VBLANK: + u64 vts = ctrl->val + fmt->height; + + cci_update_bits(os02g10->cci, OS02G10_REG_FRAME_TEST_CTRL, + OS02G10_FRAME_EXP_SEPERATE_EN, + OS02G10_FRAME_EXP_SEPERATE_EN, &ret); + cci_write(os02g10->cci, OS02G10_REG_FRAME_LENGTH, vts, &ret); + break; + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + cci_write(os02g10->cci, OS02G10_REG_FLIP_MIRROR, + os02g10->hflip->val | os02g10->vflip->val << 1, + &ret); + cci_write(os02g10->cci, OS02G10_REG_SIF_CTRL, + OS02G10_ORIENTATION_BAYER_FIX, &ret); + break; + case V4L2_CID_TEST_PATTERN: + cci_update_bits(os02g10->cci, + OS02G10_REG_FRAME_TEST_CTRL, + OS02G10_TEST_PATTERN_ENABLE, + ctrl->val ? OS02G10_TEST_PATTERN_ENABLE : 0, + &ret); + break; + default: + ret = -EINVAL; + break; + } + cci_write(os02g10->cci, OS02G10_REG_FRAME_SYNC, 0x01, &ret); + + pm_runtime_put(os02g10->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops os02g10_ctrl_ops = { + .s_ctrl = os02g10_set_ctrl, +}; + +static int os02g10_init_controls(struct os02g10 *os02g10) +{ + const struct os02g10_mode *mode = &supported_modes[0]; + struct v4l2_fwnode_device_properties props; + u64 vblank_def, exp_max, pixel_rate; + struct v4l2_ctrl_handler *ctrl_hdlr; + int ret; + + ctrl_hdlr = &os02g10->handler; + v4l2_ctrl_handler_init(ctrl_hdlr, 11); + + /* pixel_rate = link_freq * 2 * nr_of_lanes / bits_per_sample */ + pixel_rate = div_u64(OS02G10_LINK_FREQ_720MHZ * 2 * 2, 10); + v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, V4L2_CID_PIXEL_RATE, 0, + pixel_rate, 1, pixel_rate); + + os02g10->link_freq = + v4l2_ctrl_new_int_menu(ctrl_hdlr, &os02g10_ctrl_ops, + V4L2_CID_LINK_FREQ, + ARRAY_SIZE(link_freq_menu_items) - 1, + 0, link_freq_menu_items); + if (os02g10->link_freq) + os02g10->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + vblank_def = mode->vts_def - mode->height; + os02g10->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, + V4L2_CID_VBLANK, vblank_def, + OS02G10_FRAME_LENGTH_MAX - mode->height, + 1, vblank_def); + + exp_max = mode->vts_def - OS02G10_EXPOSURE_MARGIN; + os02g10->exposure = + v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, + V4L2_CID_EXPOSURE, + OS02G10_EXPOSURE_MIN, exp_max, + OS02G10_EXPOSURE_STEP, mode->exp_def); + + v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, + V4L2_CID_ANALOGUE_GAIN, OS02G10_ANALOG_GAIN_MIN, + OS02G10_ANALOG_GAIN_MAX, OS02G10_ANALOG_GAIN_STEP, + OS02G10_ANALOG_GAIN_DEFAULT); + + v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, + V4L2_CID_DIGITAL_GAIN, OS02G10_DIGITAL_GAIN_MIN, + OS02G10_DIGITAL_GAIN_MAX, OS02G10_DIGITAL_GAIN_STEP, + OS02G10_DIGITAL_GAIN_DEFAULT); + + os02g10->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + if (os02g10->hflip) + os02g10->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + os02g10->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + if (os02g10->vflip) + os02g10->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &os02g10_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(os02g10_test_pattern_menu) - 1, + 0, 0, os02g10_test_pattern_menu); + + ret = v4l2_fwnode_device_parse(os02g10->dev, &props); + if (ret) + goto err_handler_free; + + ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, + &os02g10_ctrl_ops, &props); + if (ret) + goto err_handler_free; + + os02g10->sd.ctrl_handler = ctrl_hdlr; + + return 0; + +err_handler_free: + v4l2_ctrl_handler_free(ctrl_hdlr); + + return ret; +} + +static int os02g10_set_framefmt(struct os02g10 *os02g10, + struct v4l2_subdev_state *state) +{ + const struct v4l2_mbus_framefmt *format; + const struct os02g10_mode *mode; + int ret = 0; + + format = v4l2_subdev_state_get_format(state, 0); + mode = v4l2_find_nearest_size(supported_modes, + ARRAY_SIZE(supported_modes), width, + height, format->width, format->height); + + cci_write(os02g10->cci, OS02G10_REG_V_START, mode->y_start, &ret); + cci_write(os02g10->cci, OS02G10_REG_V_SIZE, mode->height, &ret); + cci_write(os02g10->cci, OS02G10_REG_V_SIZE_MIPI, mode->height, &ret); + cci_write(os02g10->cci, OS02G10_REG_H_START, mode->x_start, &ret); + cci_write(os02g10->cci, OS02G10_REG_H_SIZE, mode->width, &ret); + cci_write(os02g10->cci, OS02G10_REG_H_SIZE_MIPI, mode->width, &ret); + + return ret; +} + +static int os02g10_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct os02g10 *os02g10 = to_os02g10(sd); + int ret; + + ret = pm_runtime_resume_and_get(os02g10->dev); + if (ret < 0) + return ret; + + ret = cci_multi_reg_write(os02g10->cci, os02g10_common_regs, + ARRAY_SIZE(os02g10_common_regs), NULL); + if (ret) { + dev_err(os02g10->dev, "failed to write common registers\n"); + goto err_rpm_put; + } + + ret = os02g10_set_framefmt(os02g10, state); + if (ret) { + dev_err(os02g10->dev, "failed to set frame foramt\n"); + goto err_rpm_put; + } + + /* Apply customized values from user */ + ret = __v4l2_ctrl_handler_setup(os02g10->sd.ctrl_handler); + if (ret) + goto err_rpm_put; + + ret = cci_write(os02g10->cci, OS02G10_REG_STREAM_CTRL, + OS02G10_STREAM_CTRL_ON, NULL); + if (ret) + goto err_rpm_put; + + /* vflip and hflip cannot change during streaming */ + __v4l2_ctrl_grab(os02g10->vflip, true); + __v4l2_ctrl_grab(os02g10->hflip, true); + + return 0; + +err_rpm_put: + pm_runtime_put(os02g10->dev); + return ret; +} + +static int os02g10_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct os02g10 *os02g10 = to_os02g10(sd); + int ret; + + ret = cci_write(os02g10->cci, OS02G10_REG_STREAM_CTRL, + OS02G10_STREAM_CTRL_OFF, NULL); + if (ret) + dev_err(os02g10->dev, "Failed to stop stream\n"); + + __v4l2_ctrl_grab(os02g10->vflip, false); + __v4l2_ctrl_grab(os02g10->hflip, false); + + pm_runtime_put(os02g10->dev); + + return ret; +} + +static int os02g10_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + switch (sel->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_NATIVE_SIZE: + sel->r = os02g10_native_area; + return 0; + case V4L2_SEL_TGT_CROP: + case V4L2_SEL_TGT_CROP_DEFAULT: + sel->r = os02g10_active_area; + return 0; + default: + return -EINVAL; + } +} + +static int os02g10_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct os02g10 *os02g10 = to_os02g10(sd); + + if (code->index) + return -EINVAL; + + code->code = os02g10_get_format_code(os02g10); + + return 0; +} + +static int os02g10_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct os02g10 *os02g10 = to_os02g10(sd); + + if (fse->index >= ARRAY_SIZE(supported_modes)) + return -EINVAL; + + if (fse->code != os02g10_get_format_code(os02g10)) + 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 int os02g10_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct os02g10 *os02g10 = to_os02g10(sd); + struct v4l2_mbus_framefmt *format; + const struct os02g10_mode *mode; + + format = v4l2_subdev_state_get_format(sd_state, 0); + + mode = v4l2_find_nearest_size(supported_modes, + ARRAY_SIZE(supported_modes), + width, height, + fmt->format.width, fmt->format.height); + + fmt->format.code = os02g10_get_format_code(os02g10); + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.field = V4L2_FIELD_NONE; + fmt->format.colorspace = V4L2_COLORSPACE_RAW; + fmt->format.quantization = V4L2_QUANTIZATION_FULL_RANGE; + fmt->format.xfer_func = V4L2_XFER_FUNC_NONE; + + *format = fmt->format; + + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + u32 vblank_def = mode->vts_def - mode->height; + + int ret = __v4l2_ctrl_modify_range(os02g10->vblank, vblank_def, + OS02G10_FRAME_LENGTH_MAX - + mode->height, 1, vblank_def); + if (ret) + return ret; + } + + return 0; +} + +static int os02g10_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct os02g10 *os02g10 = to_os02g10(sd); + struct v4l2_subdev_format fmt = { + .which = V4L2_SUBDEV_FORMAT_TRY, + .format = { + .code = os02g10_get_format_code(os02g10), + .width = supported_modes[0].width, + .height = supported_modes[0].height, + }, + }; + + os02g10_set_pad_format(sd, state, &fmt); + + return 0; +} + +static const struct v4l2_subdev_video_ops os02g10_video_ops = { + .s_stream = v4l2_subdev_s_stream_helper, +}; + +static const struct v4l2_subdev_pad_ops os02g10_pad_ops = { + .enum_mbus_code = os02g10_enum_mbus_code, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = os02g10_set_pad_format, + .get_selection = os02g10_get_selection, + .enum_frame_size = os02g10_enum_frame_size, + .enable_streams = os02g10_enable_streams, + .disable_streams = os02g10_disable_streams, +}; + +static const struct v4l2_subdev_ops os02g10_subdev_ops = { + .video = &os02g10_video_ops, + .pad = &os02g10_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops os02g10_internal_ops = { + .init_state = os02g10_init_state, +}; + +static int os02g10_power_on(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct os02g10 *os02g10 = to_os02g10(sd); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(os02g10_supply_name), + os02g10->supplies); + if (ret) { + dev_err(os02g10->dev, "failed to enable regulators\n"); + return ret; + } + + /* T4: delay from DOVDD stable to MCLK on */ + fsleep(5 * USEC_PER_MSEC); + + ret = clk_prepare_enable(os02g10->xclk); + if (ret) { + dev_err(os02g10->dev, "failed to enable clock\n"); + goto err_regulator_off; + } + + /* T3: delay from DVDD stable to sensor power up stable */ + fsleep(5 * USEC_PER_MSEC); + + gpiod_set_value_cansleep(os02g10->reset_gpio, 0); + + /* T5: delay from sensor power up stable to SCCB initialization */ + fsleep(5 * USEC_PER_MSEC); + + return 0; + +err_regulator_off: + regulator_bulk_disable(ARRAY_SIZE(os02g10_supply_name), os02g10->supplies); + return ret; +} + +static int os02g10_power_off(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct os02g10 *os02g10 = to_os02g10(sd); + + clk_disable_unprepare(os02g10->xclk); + gpiod_set_value_cansleep(os02g10->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(os02g10_supply_name), os02g10->supplies); + + return 0; +} + +static int os02g10_identify_module(struct os02g10 *os02g10) +{ + u64 chip_id; + int ret; + + ret = cci_read(os02g10->cci, OS02G10_REG_CHIPID, &chip_id, NULL); + if (ret) + return dev_err_probe(os02g10->dev, ret, + "failed to read chip id %x\n", + OS02G10_CHIPID); + + if (chip_id != OS02G10_CHIPID) + return dev_err_probe(os02g10->dev, -EIO, + "chip id mismatch: %x!=%llx\n", + OS02G10_CHIPID, chip_id); + + return 0; +} + +static int os02g10_parse_endpoint(struct os02g10 *os02g10) +{ + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY, + }; + unsigned long link_freq_bitmap; + struct fwnode_handle *ep; + int ret; + + ep = fwnode_graph_get_next_endpoint(dev_fwnode(os02g10->dev), NULL); + 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 != 2) { + ret = dev_err_probe(os02g10->dev, -EINVAL, + "only 2 data lanes are supported\n"); + goto error_out; + } + + ret = v4l2_link_freq_to_bitmap(os02g10->dev, bus_cfg.link_frequencies, + bus_cfg.nr_of_link_frequencies, + link_freq_menu_items, + ARRAY_SIZE(link_freq_menu_items), + &link_freq_bitmap); + if (ret) { + ret = dev_err_probe(os02g10->dev, -EINVAL, + "only 720MHz frequency is available\n"); + goto error_out; + } + +error_out: + v4l2_fwnode_endpoint_free(&bus_cfg); + + return ret; +}; + +static const struct regmap_range_cfg os02g10_ranges[] = { + { + .range_min = 0x0000, + .range_max = 0x03ff, + .selector_reg = 0xfd, + .selector_mask = 0x03, + .selector_shift = 0, + .window_start = 0x00, + .window_len = 0x100, + }, +}; + +static const struct regmap_config os02g10_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .reg_format_endian = REGMAP_ENDIAN_BIG, + .max_register = 0x3ff, + .ranges = os02g10_ranges, + .num_ranges = ARRAY_SIZE(os02g10_ranges), + .disable_locking = true, +}; + +static int os02g10_probe(struct i2c_client *client) +{ + struct os02g10 *os02g10; + unsigned int xclk_freq; + int ret; + + os02g10 = devm_kzalloc(&client->dev, sizeof(*os02g10), GFP_KERNEL); + if (!os02g10) + return -ENOMEM; + + os02g10->dev = &client->dev; + + v4l2_i2c_subdev_init(&os02g10->sd, client, &os02g10_subdev_ops); + os02g10->sd.internal_ops = &os02g10_internal_ops; + + /* + * This is not using devm_cci_regmap_init_i2c(), because the driver + * makes use of regmap's pagination feature. The chosen settings are + * compatible with the CCI helpers. + */ + os02g10->cci = devm_regmap_init_i2c(client, &os02g10_regmap_config); + if (IS_ERR(os02g10->cci)) + return dev_err_probe(os02g10->dev, PTR_ERR(os02g10->cci), + "failed to initialize CCI\n"); + + ret = os02g10_parse_endpoint(os02g10); + if (ret) + return dev_err_probe(os02g10->dev, ret, + "failed to parse endpoint configuration\n"); + + /* Get system clock (xvclk) */ + os02g10->xclk = devm_v4l2_sensor_clk_get(os02g10->dev, NULL); + if (IS_ERR(os02g10->xclk)) + return dev_err_probe(os02g10->dev, PTR_ERR(os02g10->xclk), + "failed to get xclk\n"); + + xclk_freq = clk_get_rate(os02g10->xclk); + if (xclk_freq != OS02G10_XCLK_FREQ) + return dev_err_probe(os02g10->dev, -EINVAL, + "xclk frequency not supported: %u Hz\n", + xclk_freq); + + for (unsigned int i = 0; i < ARRAY_SIZE(os02g10_supply_name); i++) + os02g10->supplies[i].supply = os02g10_supply_name[i]; + + ret = devm_regulator_bulk_get(os02g10->dev, + ARRAY_SIZE(os02g10_supply_name), + os02g10->supplies); + if (ret) + return dev_err_probe(os02g10->dev, ret, + "failed to get regulators\n"); + + os02g10->reset_gpio = devm_gpiod_get_optional(os02g10->dev, + "reset", GPIOD_OUT_HIGH); + if (IS_ERR(os02g10->reset_gpio)) + return dev_err_probe(os02g10->dev, PTR_ERR(os02g10->reset_gpio), + "failed to get reset GPIO\n"); + + ret = os02g10_power_on(os02g10->dev); + if (ret) + return ret; + + ret = os02g10_identify_module(os02g10); + if (ret) + goto error_power_off; + + ret = os02g10_init_controls(os02g10); + if (ret) + goto error_power_off; + + /* Initialize subdev */ + os02g10->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + os02g10->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + os02g10->pad.flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&os02g10->sd.entity, 1, &os02g10->pad); + if (ret) { + dev_err_probe(os02g10->dev, ret, "failed to init entity pads\n"); + goto error_handler_free; + } + + os02g10->sd.state_lock = os02g10->handler.lock; + ret = v4l2_subdev_init_finalize(&os02g10->sd); + if (ret) { + dev_err_probe(os02g10->dev, ret, "subdev init error\n"); + goto error_media_entity; + } + + pm_runtime_set_active(os02g10->dev); + pm_runtime_enable(os02g10->dev); + + ret = v4l2_async_register_subdev_sensor(&os02g10->sd); + if (ret) { + dev_err_probe(os02g10->dev, ret, + "failed to register os02g10 sub-device\n"); + goto error_subdev_cleanup; + } + + pm_runtime_idle(os02g10->dev); + + return 0; + +error_subdev_cleanup: + v4l2_subdev_cleanup(&os02g10->sd); + pm_runtime_disable(os02g10->dev); + pm_runtime_set_suspended(os02g10->dev); + +error_media_entity: + media_entity_cleanup(&os02g10->sd.entity); + +error_handler_free: + v4l2_ctrl_handler_free(os02g10->sd.ctrl_handler); + +error_power_off: + os02g10_power_off(os02g10->dev); + + return ret; +} + +static void os02g10_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct os02g10 *os02g10 = to_os02g10(sd); + + v4l2_async_unregister_subdev(sd); + v4l2_subdev_cleanup(&os02g10->sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(os02g10->sd.ctrl_handler); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) { + os02g10_power_off(&client->dev); + pm_runtime_set_suspended(&client->dev); + } +} + +static DEFINE_RUNTIME_DEV_PM_OPS(os02g10_pm_ops, + os02g10_power_off, os02g10_power_on, NULL); + +static const struct of_device_id os02g10_id[] = { + { .compatible = "ovti,os02g10" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, os02g10_id); + +static struct i2c_driver os02g10_driver = { + .driver = { + .name = "os02g10", + .pm = pm_ptr(&os02g10_pm_ops), + .of_match_table = os02g10_id, + }, + .probe = os02g10_probe, + .remove = os02g10_remove, +}; +module_i2c_driver(os02g10_driver); + +MODULE_DESCRIPTION("OS02G10 Camera Sensor Driver"); +MODULE_AUTHOR("Tarang Raval <tarang.raval@siliconsignals.io>"); +MODULE_AUTHOR("Elgin Perumbilly <elgin.perumbilly@siliconsignals.io>"); +MODULE_LICENSE("GPL"); -- 2.34.1 ^ permalink raw reply related [flat|nested] 27+ messages in thread
* Re: [PATCH v3 2/3] media: i2c: add os02g10 image sensor driver 2026-04-24 9:25 ` [PATCH v3 2/3] media: i2c: add os02g10 image sensor driver Elgin Perumbilly @ 2026-06-12 10:12 ` Vladimir Zapolskiy 2026-06-18 6:22 ` Elgin Perumbilly 2026-06-18 11:43 ` Sakari Ailus 1 sibling, 1 reply; 27+ messages in thread From: Vladimir Zapolskiy @ 2026-06-12 10:12 UTC (permalink / raw) To: Elgin Perumbilly, sakari.ailus, laurent.pinchart Cc: Tarang Raval, Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Hans Verkuil, Hans de Goede, Mehdi Djait, Sylvain Petinot, Benjamin Mugnier, Bryan O'Donoghue, Himanshu Bhavani, Heimir Thor Sverrisson, Jingjing Xiong, Svyatoslav Ryhel, linux-media, devicetree, linux-kernel On 4/24/26 12:25, Elgin Perumbilly wrote: > Add a v4l2 subdevice driver for the Omnivision os02g10 sensor. > > The Omnivision os02g10 is a CMOS image sensor with an active array size of > 1920 x 1080. > > The following features are supported: > - Manual exposure an gain control support > - vblank/hblank control support > - vflip/hflip control support > - Test pattern control support > - Supported resolution: 1920 x 1080 @ 30fps (SBGGR10) > > Signed-off-by: Elgin Perumbilly <elgin.perumbilly@siliconsignals.io> > Reviewed-by: Tarang Raval <tarang.raval@siliconsignals.io> > --- > MAINTAINERS | 1 + > drivers/media/i2c/Kconfig | 10 + > drivers/media/i2c/Makefile | 1 + > drivers/media/i2c/os02g10.c | 949 ++++++++++++++++++++++++++++++++++++ > 4 files changed, 961 insertions(+) > create mode 100644 drivers/media/i2c/os02g10.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index 8a0a55073c30..693e71b51926 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -19449,6 +19449,7 @@ M: Elgin Perumbilly <elgin.perumbilly@siliconsignals.io> > L: linux-media@vger.kernel.org > S: Maintained > F: Documentation/devicetree/bindings/media/i2c/ovti,os02g10.yaml > +F: drivers/media/i2c/os02g10.c > > OMNIVISION OS05B10 SENSOR DRIVER > M: Himanshu Bhavani <himanshu.bhavani@siliconsignals.io> > diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig > index 5eb1e0e0a87a..dd6e9562acf6 100644 > --- a/drivers/media/i2c/Kconfig > +++ b/drivers/media/i2c/Kconfig > @@ -372,6 +372,16 @@ config VIDEO_OG0VE1B > To compile this driver as a module, choose M here: the > module will be called og0ve1b. > > +config VIDEO_OS02G10 > + tristate "OmniVision OS02G10 sensor support" > + select V4L2_CCI_I2C > + help > + This is a Video4Linux2 sensor driver for Omnivision > + OS02G10 camera sensor. > + > + To compile this driver as a module, choose M here: the > + module will be called os02g10. > + > config VIDEO_OS05B10 > tristate "OmniVision OS05B10 sensor support" > select V4L2_CCI_I2C > diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile > index a3a6396df3c4..a7554d2eb140 100644 > --- a/drivers/media/i2c/Makefile > +++ b/drivers/media/i2c/Makefile > @@ -84,6 +84,7 @@ obj-$(CONFIG_VIDEO_MT9V032) += mt9v032.o > obj-$(CONFIG_VIDEO_MT9V111) += mt9v111.o > obj-$(CONFIG_VIDEO_OG01A1B) += og01a1b.o > obj-$(CONFIG_VIDEO_OG0VE1B) += og0ve1b.o > +obj-$(CONFIG_VIDEO_OS02G10) += os02g10.o > obj-$(CONFIG_VIDEO_OS05B10) += os05b10.o > obj-$(CONFIG_VIDEO_OV01A10) += ov01a10.o > obj-$(CONFIG_VIDEO_OV02A10) += ov02a10.o > diff --git a/drivers/media/i2c/os02g10.c b/drivers/media/i2c/os02g10.c > new file mode 100644 > index 000000000000..fad2dd0ad7aa > --- /dev/null > +++ b/drivers/media/i2c/os02g10.c > @@ -0,0 +1,949 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * V4L2 Support for the OS02G10 > + * > + * Copyright (C) 2026 Silicon Signals Pvt. Ltd. > + * > + */ > + > +#include <linux/array_size.h> > +#include <linux/bitops.h> > +#include <linux/cleanup.h> > +#include <linux/clk.h> > +#include <linux/container_of.h> > +#include <linux/delay.h> > +#include <linux/err.h> > +#include <linux/gpio/consumer.h> > +#include <linux/i2c.h> > +#include <linux/module.h> > +#include <linux/mutex.h> > +#include <linux/pm_runtime.h> > +#include <linux/property.h> > +#include <linux/regulator/consumer.h> > +#include <linux/units.h> > +#include <linux/types.h> > +#include <linux/time.h> > +#include <linux/regmap.h> Please sort the list of includes in alphabetical order, also you may consider to shrink the list by removing quite many inherited includes. > + > +#include <media/v4l2-cci.h> > +#include <media/v4l2-ctrls.h> > +#include <media/v4l2-device.h> > +#include <media/v4l2-fwnode.h> > +#include <media/v4l2-mediabus.h> > + > +#define OS02G10_XCLK_FREQ (24 * HZ_PER_MHZ) Please put this "24MHZ" information into the macro name, like it to be OS02G10_XCLK_FREQ_24MHZ or so. > + > +/* Page 0 */ > +#define OS02G10_REG_CHIPID CCI_REG24(0x002) > +#define OS02G10_CHIPID 0x560247 > + > +#define OS02G10_REG_PLL_DIV_CTRL CCI_REG8(0x030) > +#define OS02G10_REG_PLL_DCTL_BIAS_CTRL CCI_REG8(0x035) > +#define OS02G10_REG_GATE_EN_CTRL CCI_REG8(0x038) > +#define OS02G10_REG_DPLL_NC CCI_REG8(0x041) > +#define OS02G10_REG_MP_PHASE_CTRL CCI_REG8(0x044) > + > +/* Page 1 */ > +#define OS02G10_REG_STREAM_CTRL CCI_REG8(0x1b1) > +#define OS02G10_STREAM_CTRL_ON 0x03 > +#define OS02G10_STREAM_CTRL_OFF 0x00 > + > +#define OS02G10_REG_FRAME_SYNC CCI_REG8(0x101) > + > +#define OS02G10_REG_FRAME_LENGTH CCI_REG16(0x10e) > +#define OS02G10_FRAME_LENGTH_MAX 0xffff > +#define OS02G10_REG_HBLANK CCI_REG16(0x109) > + > +#define OS02G10_REG_FRAME_TEST_CTRL CCI_REG8(0x10d) > +#define OS02G10_FRAME_EXP_SEPERATE_EN BIT(4) > +#define OS02G10_TEST_PATTERN_ENABLE BIT(0) > + > +#define OS02G10_REG_ULP_PWD_DUMMY_CTRL CCI_REG8(0x13c) > +#define OS02G10_REG_DC_LEVEL_LIMIT_EN CCI_REG8(0x146) > +#define OS02G10_REG_DC_LEVEL_LIMIT_L CCI_REG8(0x147) > +#define OS02G10_REG_BLC_DATA_LIMIT_L CCI_REG8(0x148) > +#define OS02G10_REG_DC_BLC_LIMIT_H CCI_REG8(0x149) > + > +#define OS02G10_REG_HS_LP_CTRL CCI_REG8(0x192) > +#define OS02G10_REG_HS_LEVEL CCI_REG8(0x19d) > +#define OS02G10_REG_HS_DRV CCI_REG8(0x19e) > + > +#define OS02G10_REG_GB_SUBOFFSET CCI_REG8(0x1f0) > +#define OS02G10_REG_BLUE_SUBOFFSET CCI_REG8(0x1f1) > +#define OS02G10_REG_RED_SUBOFFSET CCI_REG8(0x1f2) > +#define OS02G10_REG_GR_SUBOFFSET CCI_REG8(0x1f3) > + > +#define OS02G10_REG_ABL_TRIGGER CCI_REG8(0x1fa) > +#define OS02G10_REG_ABL CCI_REG8(0x1fb) > + > +#define OS02G10_REG_H_SIZE_MIPI CCI_REG16(0x18e) > +#define OS02G10_REG_V_SIZE_MIPI CCI_REG16(0x190) > +#define OS02G10_REG_MIPI_TX_SPEED_CTRL CCI_REG8(0x1a1) > + > +#define OS02G10_REG_LONG_EXPOSURE CCI_REG16(0x103) > +#define OS02G10_EXPOSURE_MIN 4 > +#define OS02G10_EXPOSURE_STEP 1 > +#define OS02G10_EXPOSURE_MARGIN 9 > + > +#define OS02G10_REG_ANALOG_GAIN CCI_REG8(0x124) > +#define OS02G10_ANALOG_GAIN_MIN 0x10 > +#define OS02G10_ANALOG_GAIN_MAX 0xf8 > +#define OS02G10_ANALOG_GAIN_STEP 1 > +#define OS02G10_ANALOG_GAIN_DEFAULT 0x10 > + > +#define OS02G10_REG_DIGITAL_GAIN_H CCI_REG8(0x137) > +#define OS02G10_REG_DIGITAL_GAIN_L CCI_REG8(0x139) > +#define OS02G10_DIGITAL_GAIN_MIN 0x40 > +#define OS02G10_DIGITAL_GAIN_MAX 0x800 > +#define OS02G10_DIGITAL_GAIN_STEP 64 > +#define OS02G10_DIGITAL_GAIN_DEFAULT 0x40 > + > +#define OS02G10_REG_FLIP_MIRROR CCI_REG8(0x13f) > +#define OS02G10_FLIP BIT(1) > +#define OS02G10_MIRROR BIT(0) Please sort the registers by address value, then it will make clear which position to insert any new added macro definitions. > + > +/* Page 2 */ > +#define OS02G10_REG_V_START CCI_REG16(0x2a0) > +#define OS02G10_REG_V_SIZE CCI_REG16(0x2a2) > +#define OS02G10_REG_H_START CCI_REG16(0x2a4) > +#define OS02G10_REG_H_SIZE CCI_REG16(0x2a6) > + > +#define OS02G10_REG_SIF_CTRL CCI_REG8(0x25e) > +#define OS02G10_ORIENTATION_BAYER_FIX 0x32 The same applies here. > + > +#define OS02G10_LINK_FREQ_720MHZ (720 * HZ_PER_MHZ) > + > +/* OS02G10 native and active pixel array size */ > +static const struct v4l2_rect os02g10_native_area = { > + .top = 0, > + .left = 0, > + .width = 1928, > + .height = 1088, > +}; > + > +static const struct v4l2_rect os02g10_active_area = { > + .top = 4, > + .left = 4, > + .width = 1920, > + .height = 1080, > +}; > + > +static const char * const os02g10_supply_name[] = { > + "avdd", /* Analog power */ > + "dovdd", /* Digital I/O power */ > + "dvdd", /* Digital core power */ > +}; > + > +struct os02g10 { > + struct device *dev; > + struct regmap *cci; > + struct v4l2_subdev sd; > + struct media_pad pad; > + struct clk *xclk; > + struct gpio_desc *reset_gpio; > + struct regulator_bulk_data supplies[ARRAY_SIZE(os02g10_supply_name)]; > + > + /* V4L2 Controls */ > + struct v4l2_ctrl_handler handler; > + struct v4l2_ctrl *link_freq; > + struct v4l2_ctrl *vblank; > + struct v4l2_ctrl *exposure; > + struct v4l2_ctrl *vflip; > + struct v4l2_ctrl *hflip; > +}; > + > +struct os02g10_mode { > + u32 width; > + u32 height; > + u32 vts_def; > + u32 exp_def; > + u32 x_start; > + u32 y_start; > +}; > + > +static const struct cci_reg_sequence os02g10_common_regs[] = { > + { OS02G10_REG_PLL_DIV_CTRL, 0x0a}, > + { OS02G10_REG_PLL_DCTL_BIAS_CTRL, 0x04}, > + { OS02G10_REG_GATE_EN_CTRL, 0x11}, > + { OS02G10_REG_DPLL_NC, 0x06}, > + { OS02G10_REG_MP_PHASE_CTRL, 0x20}, > + { CCI_REG8(0x119), 0x50}, > + { CCI_REG8(0x11a), 0x0c}, > + { CCI_REG8(0x11b), 0x0d}, > + { CCI_REG8(0x11c), 0x00}, > + { CCI_REG8(0x11d), 0x75}, > + { CCI_REG8(0x11e), 0x52}, > + { CCI_REG8(0x122), 0x14}, > + { CCI_REG8(0x125), 0x44}, > + { CCI_REG8(0x126), 0x0f}, > + { OS02G10_REG_ULP_PWD_DUMMY_CTRL, 0xca}, > + { CCI_REG8(0x13d), 0x4a}, > + { CCI_REG8(0x140), 0x0f}, > + { CCI_REG8(0x143), 0x38}, > + { OS02G10_REG_DC_LEVEL_LIMIT_EN, 0x01}, > + { OS02G10_REG_DC_LEVEL_LIMIT_L, 0x00}, > + { OS02G10_REG_DC_BLC_LIMIT_H, 0x32}, > + { CCI_REG8(0x150), 0x01}, > + { CCI_REG8(0x151), 0x28}, > + { CCI_REG8(0x152), 0x20}, > + { CCI_REG8(0x153), 0x03}, > + { CCI_REG8(0x157), 0x16}, > + { CCI_REG8(0x159), 0x01}, > + { CCI_REG8(0x15a), 0x01}, > + { CCI_REG8(0x15d), 0x04}, > + { CCI_REG8(0x16a), 0x04}, > + { CCI_REG8(0x16b), 0x03}, > + { CCI_REG8(0x16e), 0x28}, > + { CCI_REG8(0x171), 0xc2}, > + { CCI_REG8(0x172), 0x04}, > + { CCI_REG8(0x173), 0x38}, > + { CCI_REG8(0x174), 0x04}, > + { CCI_REG8(0x179), 0x00}, > + { CCI_REG8(0x17a), 0xb2}, > + { CCI_REG8(0x17b), 0x10}, > + { OS02G10_REG_HS_LP_CTRL, 0x02}, > + { OS02G10_REG_HS_LEVEL, 0x03}, > + { OS02G10_REG_HS_DRV, 0x55}, > + { CCI_REG8(0x1b8), 0x70}, > + { CCI_REG8(0x1b9), 0x70}, > + { CCI_REG8(0x1ba), 0x70}, > + { CCI_REG8(0x1bb), 0x70}, > + { CCI_REG8(0x1bc), 0x00}, > + { CCI_REG8(0x1c4), 0x6d}, > + { CCI_REG8(0x1c5), 0x6d}, > + { CCI_REG8(0x1c6), 0x6d}, > + { CCI_REG8(0x1c7), 0x6d}, > + { CCI_REG8(0x1cc), 0x11}, > + { CCI_REG8(0x1cd), 0xe0}, > + { CCI_REG8(0x1d0), 0x1b}, > + { CCI_REG8(0x1d2), 0x76}, > + { CCI_REG8(0x1d3), 0x68}, > + { CCI_REG8(0x1d4), 0x68}, > + { CCI_REG8(0x1d5), 0x73}, > + { CCI_REG8(0x1d6), 0x73}, > + { CCI_REG8(0x1e8), 0x55}, > + { OS02G10_REG_GB_SUBOFFSET, 0x40}, > + { OS02G10_REG_BLUE_SUBOFFSET, 0x40}, > + { OS02G10_REG_RED_SUBOFFSET, 0x40}, > + { OS02G10_REG_GR_SUBOFFSET, 0x40}, > + { OS02G10_REG_ABL_TRIGGER, 0x1c}, > + { OS02G10_REG_ABL, 0x33}, > + { CCI_REG8(0x1fc), 0x80}, > + { CCI_REG8(0x1fe), 0x80}, > + { CCI_REG8(0x303), 0x67}, > + { CCI_REG8(0x300), 0x59}, > + { CCI_REG8(0x304), 0x11}, > + { CCI_REG8(0x305), 0x04}, > + { CCI_REG8(0x306), 0x0c}, > + { CCI_REG8(0x307), 0x08}, > + { CCI_REG8(0x308), 0x08}, > + { CCI_REG8(0x309), 0x4f}, > + { CCI_REG8(0x30b), 0x08}, > + { CCI_REG8(0x30d), 0x26}, > + { CCI_REG8(0x30f), 0x00}, > + { CCI_REG8(0x234), 0xfe}, > + { OS02G10_REG_MIPI_TX_SPEED_CTRL, 0x05}, > +}; > + > +static const struct os02g10_mode supported_modes[] = { > + { > + .width = 1920, > + .height = 1080, > + .vts_def = 1246, > + .exp_def = 1100, > + .x_start = 2, > + .y_start = 6, > + }, > +}; > + > +static const s64 link_freq_menu_items[] = { > + OS02G10_LINK_FREQ_720MHZ, > +}; > + > +static const char * const os02g10_test_pattern_menu[] = { > + "Disabled", > + "Colorbar", > +}; > + > +static inline struct os02g10 *to_os02g10(struct v4l2_subdev *sd) > +{ > + return container_of_const(sd, struct os02g10, sd); > +} > + > +static u32 os02g10_get_format_code(struct os02g10 *os02g10) > +{ > + static const u32 codes[2][2] = { > + { MEDIA_BUS_FMT_SBGGR10_1X10, MEDIA_BUS_FMT_SGBRG10_1X10, }, > + { MEDIA_BUS_FMT_SGRBG10_1X10, MEDIA_BUS_FMT_SRGGB10_1X10, }, > + }; > + u32 code = codes[os02g10->vflip->val][os02g10->hflip->val]; > + > + return code; > +} > + > +static int os02g10_set_ctrl(struct v4l2_ctrl *ctrl) > +{ > + struct os02g10 *os02g10 = container_of_const(ctrl->handler, > + struct os02g10, handler); > + struct v4l2_subdev_state *state; > + struct v4l2_mbus_framefmt *fmt; > + int ret = 0; > + > + state = v4l2_subdev_get_locked_active_state(&os02g10->sd); > + fmt = v4l2_subdev_state_get_format(state, 0); > + > + if (ctrl->id == V4L2_CID_VBLANK) { > + /* Honour the VBLANK limits when setting exposure */ > + s64 max = fmt->height + ctrl->val - OS02G10_EXPOSURE_MARGIN; > + > + ret = __v4l2_ctrl_modify_range(os02g10->exposure, > + os02g10->exposure->minimum, max, > + os02g10->exposure->step, > + os02g10->exposure->default_value); > + if (ret) > + return ret; > + } > + > + if (pm_runtime_get_if_active(os02g10->dev) == 0) > + return 0; > + > + switch (ctrl->id) { > + case V4L2_CID_EXPOSURE: > + cci_write(os02g10->cci, OS02G10_REG_LONG_EXPOSURE, > + ctrl->val, &ret); > + break; > + case V4L2_CID_ANALOGUE_GAIN: > + cci_write(os02g10->cci, OS02G10_REG_ANALOG_GAIN, > + ctrl->val, &ret); > + break; > + case V4L2_CID_DIGITAL_GAIN: > + cci_write(os02g10->cci, OS02G10_REG_DIGITAL_GAIN_L, > + (ctrl->val & 0xff), &ret); > + cci_write(os02g10->cci, OS02G10_REG_DIGITAL_GAIN_H, > + ((ctrl->val >> 8) & 0x7), &ret); > + break; > + case V4L2_CID_VBLANK: > + u64 vts = ctrl->val + fmt->height; > + > + cci_update_bits(os02g10->cci, OS02G10_REG_FRAME_TEST_CTRL, > + OS02G10_FRAME_EXP_SEPERATE_EN, > + OS02G10_FRAME_EXP_SEPERATE_EN, &ret); > + cci_write(os02g10->cci, OS02G10_REG_FRAME_LENGTH, vts, &ret); > + break; > + case V4L2_CID_HFLIP: > + case V4L2_CID_VFLIP: > + cci_write(os02g10->cci, OS02G10_REG_FLIP_MIRROR, > + os02g10->hflip->val | os02g10->vflip->val << 1, > + &ret); > + cci_write(os02g10->cci, OS02G10_REG_SIF_CTRL, > + OS02G10_ORIENTATION_BAYER_FIX, &ret); > + break; > + case V4L2_CID_TEST_PATTERN: > + cci_update_bits(os02g10->cci, > + OS02G10_REG_FRAME_TEST_CTRL, > + OS02G10_TEST_PATTERN_ENABLE, > + ctrl->val ? OS02G10_TEST_PATTERN_ENABLE : 0, > + &ret); > + break; > + default: > + ret = -EINVAL; > + break; > + } > + cci_write(os02g10->cci, OS02G10_REG_FRAME_SYNC, 0x01, &ret); > + > + pm_runtime_put(os02g10->dev); > + > + return ret; > +} > + > +static const struct v4l2_ctrl_ops os02g10_ctrl_ops = { > + .s_ctrl = os02g10_set_ctrl, > +}; > + > +static int os02g10_init_controls(struct os02g10 *os02g10) > +{ > + const struct os02g10_mode *mode = &supported_modes[0]; > + struct v4l2_fwnode_device_properties props; > + u64 vblank_def, exp_max, pixel_rate; > + struct v4l2_ctrl_handler *ctrl_hdlr; > + int ret; > + > + ctrl_hdlr = &os02g10->handler; > + v4l2_ctrl_handler_init(ctrl_hdlr, 11); > + > + /* pixel_rate = link_freq * 2 * nr_of_lanes / bits_per_sample */ > + pixel_rate = div_u64(OS02G10_LINK_FREQ_720MHZ * 2 * 2, 10); Please define a macro representing the fixed number of data lanes, it'll be used in os02g10_parse_endpoint() also. > + v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, V4L2_CID_PIXEL_RATE, 0, > + pixel_rate, 1, pixel_rate); > + > + os02g10->link_freq = > + v4l2_ctrl_new_int_menu(ctrl_hdlr, &os02g10_ctrl_ops, > + V4L2_CID_LINK_FREQ, > + ARRAY_SIZE(link_freq_menu_items) - 1, > + 0, link_freq_menu_items); > + if (os02g10->link_freq) > + os02g10->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; > + > + vblank_def = mode->vts_def - mode->height; > + os02g10->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, > + V4L2_CID_VBLANK, vblank_def, > + OS02G10_FRAME_LENGTH_MAX - mode->height, > + 1, vblank_def); > + > + exp_max = mode->vts_def - OS02G10_EXPOSURE_MARGIN; > + os02g10->exposure = > + v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, > + V4L2_CID_EXPOSURE, > + OS02G10_EXPOSURE_MIN, exp_max, > + OS02G10_EXPOSURE_STEP, mode->exp_def); > + > + v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, > + V4L2_CID_ANALOGUE_GAIN, OS02G10_ANALOG_GAIN_MIN, > + OS02G10_ANALOG_GAIN_MAX, OS02G10_ANALOG_GAIN_STEP, > + OS02G10_ANALOG_GAIN_DEFAULT); > + > + v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, > + V4L2_CID_DIGITAL_GAIN, OS02G10_DIGITAL_GAIN_MIN, > + OS02G10_DIGITAL_GAIN_MAX, OS02G10_DIGITAL_GAIN_STEP, > + OS02G10_DIGITAL_GAIN_DEFAULT); > + > + os02g10->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, > + V4L2_CID_HFLIP, 0, 1, 1, 0); > + if (os02g10->hflip) > + os02g10->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; > + > + os02g10->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, > + V4L2_CID_VFLIP, 0, 1, 1, 0); > + if (os02g10->vflip) > + os02g10->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; > + > + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &os02g10_ctrl_ops, > + V4L2_CID_TEST_PATTERN, > + ARRAY_SIZE(os02g10_test_pattern_menu) - 1, > + 0, 0, os02g10_test_pattern_menu); > + > + ret = v4l2_fwnode_device_parse(os02g10->dev, &props); > + if (ret) > + goto err_handler_free; > + > + ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, > + &os02g10_ctrl_ops, &props); > + if (ret) > + goto err_handler_free; > + > + os02g10->sd.ctrl_handler = ctrl_hdlr; > + > + return 0; > + > +err_handler_free: > + v4l2_ctrl_handler_free(ctrl_hdlr); > + > + return ret; > +} > + > +static int os02g10_set_framefmt(struct os02g10 *os02g10, > + struct v4l2_subdev_state *state) > +{ > + const struct v4l2_mbus_framefmt *format; > + const struct os02g10_mode *mode; > + int ret = 0; > + > + format = v4l2_subdev_state_get_format(state, 0); > + mode = v4l2_find_nearest_size(supported_modes, > + ARRAY_SIZE(supported_modes), width, > + height, format->width, format->height); > + > + cci_write(os02g10->cci, OS02G10_REG_V_START, mode->y_start, &ret); > + cci_write(os02g10->cci, OS02G10_REG_V_SIZE, mode->height, &ret); > + cci_write(os02g10->cci, OS02G10_REG_V_SIZE_MIPI, mode->height, &ret); > + cci_write(os02g10->cci, OS02G10_REG_H_START, mode->x_start, &ret); > + cci_write(os02g10->cci, OS02G10_REG_H_SIZE, mode->width, &ret); > + cci_write(os02g10->cci, OS02G10_REG_H_SIZE_MIPI, mode->width, &ret); > + > + return ret; Just "return 0" here, and remove the local variable. > +} > + > +static int os02g10_enable_streams(struct v4l2_subdev *sd, > + struct v4l2_subdev_state *state, u32 pad, > + u64 streams_mask) > +{ > + struct os02g10 *os02g10 = to_os02g10(sd); > + int ret; > + > + ret = pm_runtime_resume_and_get(os02g10->dev); > + if (ret < 0) > + return ret; > + > + ret = cci_multi_reg_write(os02g10->cci, os02g10_common_regs, > + ARRAY_SIZE(os02g10_common_regs), NULL); > + if (ret) { > + dev_err(os02g10->dev, "failed to write common registers\n"); > + goto err_rpm_put; > + } > + > + ret = os02g10_set_framefmt(os02g10, state); > + if (ret) { > + dev_err(os02g10->dev, "failed to set frame foramt\n"); > + goto err_rpm_put; > + } > + > + /* Apply customized values from user */ > + ret = __v4l2_ctrl_handler_setup(os02g10->sd.ctrl_handler); > + if (ret) > + goto err_rpm_put; > + > + ret = cci_write(os02g10->cci, OS02G10_REG_STREAM_CTRL, > + OS02G10_STREAM_CTRL_ON, NULL); > + if (ret) > + goto err_rpm_put; > + > + /* vflip and hflip cannot change during streaming */ > + __v4l2_ctrl_grab(os02g10->vflip, true); > + __v4l2_ctrl_grab(os02g10->hflip, true); > + > + return 0; > + > +err_rpm_put: > + pm_runtime_put(os02g10->dev); > + return ret; > +} > + > +static int os02g10_disable_streams(struct v4l2_subdev *sd, > + struct v4l2_subdev_state *state, u32 pad, > + u64 streams_mask) > +{ > + struct os02g10 *os02g10 = to_os02g10(sd); > + int ret; > + > + ret = cci_write(os02g10->cci, OS02G10_REG_STREAM_CTRL, > + OS02G10_STREAM_CTRL_OFF, NULL); > + if (ret) > + dev_err(os02g10->dev, "Failed to stop stream\n"); > + > + __v4l2_ctrl_grab(os02g10->vflip, false); > + __v4l2_ctrl_grab(os02g10->hflip, false); > + > + pm_runtime_put(os02g10->dev); > + > + return ret; > +} > + > +static int os02g10_get_selection(struct v4l2_subdev *sd, > + struct v4l2_subdev_state *sd_state, > + struct v4l2_subdev_selection *sel) > +{ > + switch (sel->target) { > + case V4L2_SEL_TGT_CROP_BOUNDS: > + case V4L2_SEL_TGT_NATIVE_SIZE: > + sel->r = os02g10_native_area; > + return 0; > + case V4L2_SEL_TGT_CROP: > + case V4L2_SEL_TGT_CROP_DEFAULT: > + sel->r = os02g10_active_area; > + return 0; > + default: > + return -EINVAL; > + } > +} > + > +static int os02g10_enum_mbus_code(struct v4l2_subdev *sd, > + struct v4l2_subdev_state *sd_state, > + struct v4l2_subdev_mbus_code_enum *code) > +{ > + struct os02g10 *os02g10 = to_os02g10(sd); > + > + if (code->index) > + return -EINVAL; > + > + code->code = os02g10_get_format_code(os02g10); > + > + return 0; > +} > + > +static int os02g10_enum_frame_size(struct v4l2_subdev *sd, > + struct v4l2_subdev_state *sd_state, > + struct v4l2_subdev_frame_size_enum *fse) > +{ > + struct os02g10 *os02g10 = to_os02g10(sd); > + > + if (fse->index >= ARRAY_SIZE(supported_modes)) > + return -EINVAL; > + > + if (fse->code != os02g10_get_format_code(os02g10)) > + 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 int os02g10_set_pad_format(struct v4l2_subdev *sd, > + struct v4l2_subdev_state *sd_state, > + struct v4l2_subdev_format *fmt) > +{ > + struct os02g10 *os02g10 = to_os02g10(sd); > + struct v4l2_mbus_framefmt *format; > + const struct os02g10_mode *mode; > + > + format = v4l2_subdev_state_get_format(sd_state, 0); > + > + mode = v4l2_find_nearest_size(supported_modes, > + ARRAY_SIZE(supported_modes), > + width, height, > + fmt->format.width, fmt->format.height); > + > + fmt->format.code = os02g10_get_format_code(os02g10); > + fmt->format.width = mode->width; > + fmt->format.height = mode->height; > + fmt->format.field = V4L2_FIELD_NONE; > + fmt->format.colorspace = V4L2_COLORSPACE_RAW; > + fmt->format.quantization = V4L2_QUANTIZATION_FULL_RANGE; > + fmt->format.xfer_func = V4L2_XFER_FUNC_NONE; > + > + *format = fmt->format; > + > + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { > + u32 vblank_def = mode->vts_def - mode->height; > + > + int ret = __v4l2_ctrl_modify_range(os02g10->vblank, vblank_def, > + OS02G10_FRAME_LENGTH_MAX - > + mode->height, 1, vblank_def); > + if (ret) > + return ret; > + } > + > + return 0; > +} > + > +static int os02g10_init_state(struct v4l2_subdev *sd, > + struct v4l2_subdev_state *state) > +{ > + struct os02g10 *os02g10 = to_os02g10(sd); > + struct v4l2_subdev_format fmt = { > + .which = V4L2_SUBDEV_FORMAT_TRY, > + .format = { > + .code = os02g10_get_format_code(os02g10), I'm uncertain that the default zero values of vflip/hflip represent the initial state of a sensor correctly, it requires runtime testing. Anyway you may consider to drop it, its immediately rewritten by os02g10_set_pad_format(). > + .width = supported_modes[0].width, > + .height = supported_modes[0].height, > + }, > + }; > + > + os02g10_set_pad_format(sd, state, &fmt); > + > + return 0; > +} > + > +static const struct v4l2_subdev_video_ops os02g10_video_ops = { > + .s_stream = v4l2_subdev_s_stream_helper, > +}; > + > +static const struct v4l2_subdev_pad_ops os02g10_pad_ops = { > + .enum_mbus_code = os02g10_enum_mbus_code, > + .get_fmt = v4l2_subdev_get_fmt, > + .set_fmt = os02g10_set_pad_format, > + .get_selection = os02g10_get_selection, > + .enum_frame_size = os02g10_enum_frame_size, Please group two enum callbacks on the list. > + .enable_streams = os02g10_enable_streams, > + .disable_streams = os02g10_disable_streams, > +}; > + > +static const struct v4l2_subdev_ops os02g10_subdev_ops = { > + .video = &os02g10_video_ops, > + .pad = &os02g10_pad_ops, > +}; > + > +static const struct v4l2_subdev_internal_ops os02g10_internal_ops = { > + .init_state = os02g10_init_state, > +}; > + > +static int os02g10_power_on(struct device *dev) > +{ > + struct v4l2_subdev *sd = dev_get_drvdata(dev); > + struct os02g10 *os02g10 = to_os02g10(sd); > + int ret; > + > + ret = regulator_bulk_enable(ARRAY_SIZE(os02g10_supply_name), > + os02g10->supplies); > + if (ret) { > + dev_err(os02g10->dev, "failed to enable regulators\n"); > + return ret; > + } > + > + /* T4: delay from DOVDD stable to MCLK on */ > + fsleep(5 * USEC_PER_MSEC); > + > + ret = clk_prepare_enable(os02g10->xclk); > + if (ret) { > + dev_err(os02g10->dev, "failed to enable clock\n"); > + goto err_regulator_off; > + } > + > + /* T3: delay from DVDD stable to sensor power up stable */ > + fsleep(5 * USEC_PER_MSEC); > + > + gpiod_set_value_cansleep(os02g10->reset_gpio, 0); > + > + /* T5: delay from sensor power up stable to SCCB initialization */ > + fsleep(5 * USEC_PER_MSEC); > + > + return 0; > + > +err_regulator_off: > + regulator_bulk_disable(ARRAY_SIZE(os02g10_supply_name), os02g10->supplies); > + return ret; > +} > + > +static int os02g10_power_off(struct device *dev) > +{ > + struct v4l2_subdev *sd = dev_get_drvdata(dev); > + struct os02g10 *os02g10 = to_os02g10(sd); > + > + clk_disable_unprepare(os02g10->xclk); > + gpiod_set_value_cansleep(os02g10->reset_gpio, 1); > + regulator_bulk_disable(ARRAY_SIZE(os02g10_supply_name), os02g10->supplies); > + > + return 0; > +} > + > +static int os02g10_identify_module(struct os02g10 *os02g10) > +{ > + u64 chip_id; > + int ret; > + > + ret = cci_read(os02g10->cci, OS02G10_REG_CHIPID, &chip_id, NULL); > + if (ret) > + return dev_err_probe(os02g10->dev, ret, > + "failed to read chip id %x\n", > + OS02G10_CHIPID); > + > + if (chip_id != OS02G10_CHIPID) > + return dev_err_probe(os02g10->dev, -EIO, > + "chip id mismatch: %x!=%llx\n", > + OS02G10_CHIPID, chip_id); > + > + return 0; > +} > + > +static int os02g10_parse_endpoint(struct os02g10 *os02g10) > +{ > + struct v4l2_fwnode_endpoint bus_cfg = { > + .bus_type = V4L2_MBUS_CSI2_DPHY, > + }; > + unsigned long link_freq_bitmap; > + struct fwnode_handle *ep; > + int ret; > + > + ep = fwnode_graph_get_next_endpoint(dev_fwnode(os02g10->dev), NULL); > + 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 != 2) { Macro for the number of data lanes is wanted. > + ret = dev_err_probe(os02g10->dev, -EINVAL, > + "only 2 data lanes are supported\n"); > + goto error_out; > + } > + > + ret = v4l2_link_freq_to_bitmap(os02g10->dev, bus_cfg.link_frequencies, > + bus_cfg.nr_of_link_frequencies, > + link_freq_menu_items, > + ARRAY_SIZE(link_freq_menu_items), > + &link_freq_bitmap); > + if (ret) { > + ret = dev_err_probe(os02g10->dev, -EINVAL, > + "only 720MHz frequency is available\n"); > + goto error_out; > + } > + > +error_out: > + v4l2_fwnode_endpoint_free(&bus_cfg); > + > + return ret; > +}; > + > +static const struct regmap_range_cfg os02g10_ranges[] = { > + { > + .range_min = 0x0000, > + .range_max = 0x03ff, > + .selector_reg = 0xfd, > + .selector_mask = 0x03, > + .selector_shift = 0, > + .window_start = 0x00, > + .window_len = 0x100, > + }, > +}; > + > +static const struct regmap_config os02g10_regmap_config = { > + .reg_bits = 8, > + .val_bits = 8, > + .reg_format_endian = REGMAP_ENDIAN_BIG, > + .max_register = 0x3ff, > + .ranges = os02g10_ranges, > + .num_ranges = ARRAY_SIZE(os02g10_ranges), > + .disable_locking = true, > +}; > + > +static int os02g10_probe(struct i2c_client *client) > +{ > + struct os02g10 *os02g10; > + unsigned int xclk_freq; > + int ret; > + > + os02g10 = devm_kzalloc(&client->dev, sizeof(*os02g10), GFP_KERNEL); > + if (!os02g10) > + return -ENOMEM; > + > + os02g10->dev = &client->dev; > + > + v4l2_i2c_subdev_init(&os02g10->sd, client, &os02g10_subdev_ops); > + os02g10->sd.internal_ops = &os02g10_internal_ops; > + > + /* > + * This is not using devm_cci_regmap_init_i2c(), because the driver > + * makes use of regmap's pagination feature. The chosen settings are > + * compatible with the CCI helpers. > + */ > + os02g10->cci = devm_regmap_init_i2c(client, &os02g10_regmap_config); > + if (IS_ERR(os02g10->cci)) > + return dev_err_probe(os02g10->dev, PTR_ERR(os02g10->cci), > + "failed to initialize CCI\n"); > + > + ret = os02g10_parse_endpoint(os02g10); > + if (ret) > + return dev_err_probe(os02g10->dev, ret, > + "failed to parse endpoint configuration\n"); > + > + /* Get system clock (xvclk) */ > + os02g10->xclk = devm_v4l2_sensor_clk_get(os02g10->dev, NULL); > + if (IS_ERR(os02g10->xclk)) > + return dev_err_probe(os02g10->dev, PTR_ERR(os02g10->xclk), > + "failed to get xclk\n"); > + > + xclk_freq = clk_get_rate(os02g10->xclk); > + if (xclk_freq != OS02G10_XCLK_FREQ) > + return dev_err_probe(os02g10->dev, -EINVAL, > + "xclk frequency not supported: %u Hz\n", > + xclk_freq); > + > + for (unsigned int i = 0; i < ARRAY_SIZE(os02g10_supply_name); i++) > + os02g10->supplies[i].supply = os02g10_supply_name[i]; > + > + ret = devm_regulator_bulk_get(os02g10->dev, > + ARRAY_SIZE(os02g10_supply_name), > + os02g10->supplies); > + if (ret) > + return dev_err_probe(os02g10->dev, ret, > + "failed to get regulators\n"); > + > + os02g10->reset_gpio = devm_gpiod_get_optional(os02g10->dev, > + "reset", GPIOD_OUT_HIGH); > + if (IS_ERR(os02g10->reset_gpio)) > + return dev_err_probe(os02g10->dev, PTR_ERR(os02g10->reset_gpio), > + "failed to get reset GPIO\n"); > + > + ret = os02g10_power_on(os02g10->dev); > + if (ret) > + return ret; > + > + ret = os02g10_identify_module(os02g10); > + if (ret) > + goto error_power_off; > + > + ret = os02g10_init_controls(os02g10); > + if (ret) > + goto error_power_off; > + > + /* Initialize subdev */ > + os02g10->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; > + os02g10->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; > + os02g10->pad.flags = MEDIA_PAD_FL_SOURCE; > + > + ret = media_entity_pads_init(&os02g10->sd.entity, 1, &os02g10->pad); > + if (ret) { > + dev_err_probe(os02g10->dev, ret, "failed to init entity pads\n"); > + goto error_handler_free; > + } > + > + os02g10->sd.state_lock = os02g10->handler.lock; > + ret = v4l2_subdev_init_finalize(&os02g10->sd); > + if (ret) { > + dev_err_probe(os02g10->dev, ret, "subdev init error\n"); > + goto error_media_entity; > + } > + > + pm_runtime_set_active(os02g10->dev); > + pm_runtime_enable(os02g10->dev); > + > + ret = v4l2_async_register_subdev_sensor(&os02g10->sd); > + if (ret) { > + dev_err_probe(os02g10->dev, ret, > + "failed to register os02g10 sub-device\n"); > + goto error_subdev_cleanup; > + } > + > + pm_runtime_idle(os02g10->dev); > + > + return 0; > + > +error_subdev_cleanup: > + v4l2_subdev_cleanup(&os02g10->sd); > + pm_runtime_disable(os02g10->dev); > + pm_runtime_set_suspended(os02g10->dev); > + > +error_media_entity: > + media_entity_cleanup(&os02g10->sd.entity); > + > +error_handler_free: > + v4l2_ctrl_handler_free(os02g10->sd.ctrl_handler); > + > +error_power_off: > + os02g10_power_off(os02g10->dev); > + > + return ret; > +} > + > +static void os02g10_remove(struct i2c_client *client) > +{ > + struct v4l2_subdev *sd = i2c_get_clientdata(client); > + struct os02g10 *os02g10 = to_os02g10(sd); > + > + v4l2_async_unregister_subdev(sd); > + v4l2_subdev_cleanup(&os02g10->sd); > + media_entity_cleanup(&sd->entity); > + v4l2_ctrl_handler_free(os02g10->sd.ctrl_handler); > + > + pm_runtime_disable(&client->dev); > + if (!pm_runtime_status_suspended(&client->dev)) { > + os02g10_power_off(&client->dev); > + pm_runtime_set_suspended(&client->dev); > + } > +} > + > +static DEFINE_RUNTIME_DEV_PM_OPS(os02g10_pm_ops, > + os02g10_power_off, os02g10_power_on, NULL); > + > +static const struct of_device_id os02g10_id[] = { > + { .compatible = "ovti,os02g10" }, > + { /* sentinel */ } > +}; > +MODULE_DEVICE_TABLE(of, os02g10_id); > + > +static struct i2c_driver os02g10_driver = { > + .driver = { > + .name = "os02g10", > + .pm = pm_ptr(&os02g10_pm_ops), > + .of_match_table = os02g10_id, > + }, > + .probe = os02g10_probe, > + .remove = os02g10_remove, > +}; > +module_i2c_driver(os02g10_driver); > + > +MODULE_DESCRIPTION("OS02G10 Camera Sensor Driver"); > +MODULE_AUTHOR("Tarang Raval <tarang.raval@siliconsignals.io>"); > +MODULE_AUTHOR("Elgin Perumbilly <elgin.perumbilly@siliconsignals.io>"); > +MODULE_LICENSE("GPL"); In my opinion the driver looks good, nothing beyond cosmetic changes is needed. -- Best wishes, Vladimir ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v3 2/3] media: i2c: add os02g10 image sensor driver 2026-06-12 10:12 ` Vladimir Zapolskiy @ 2026-06-18 6:22 ` Elgin Perumbilly 2026-06-18 10:56 ` sakari.ailus 2026-06-18 11:06 ` Vladimir Zapolskiy 0 siblings, 2 replies; 27+ messages in thread From: Elgin Perumbilly @ 2026-06-18 6:22 UTC (permalink / raw) To: Vladimir Zapolskiy, sakari.ailus@linux.intel.com, laurent.pinchart@ideasonboard.com Cc: Tarang Raval, Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Hans Verkuil, Hans de Goede, Mehdi Djait, Sylvain Petinot, Benjamin Mugnier, Bryan O'Donoghue, Himanshu Bhavani, Heimir Thor Sverrisson, Jingjing Xiong, Svyatoslav Ryhel, linux-media@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org Hi Vladimir, Thank you for the review. I have addressed all of the comments except for two, where I am not entirely sure about the requested changes. Could you please take a look at the points below and let me know your opinion? > On 4/24/26 12:25, Elgin Perumbilly wrote: > > Add a v4l2 subdevice driver for the Omnivision os02g10 sensor. > > > > The Omnivision os02g10 is a CMOS image sensor with an active array size of > > 1920 x 1080. > > > > The following features are supported: > > - Manual exposure an gain control support > > - vblank/hblank control support > > - vflip/hflip control support > > - Test pattern control support > > - Supported resolution: 1920 x 1080 @ 30fps (SBGGR10) > > > > Signed-off-by: Elgin Perumbilly <elgin.perumbilly@siliconsignals.io> > > Reviewed-by: Tarang Raval <tarang.raval@siliconsignals.io> ... > > +#include <linux/array_size.h> > > +#include <linux/bitops.h> > > +#include <linux/cleanup.h> > > +#include <linux/clk.h> > > +#include <linux/container_of.h> > > +#include <linux/delay.h> > > +#include <linux/err.h> > > +#include <linux/gpio/consumer.h> > > +#include <linux/i2c.h> > > +#include <linux/module.h> > > +#include <linux/mutex.h> > > +#include <linux/pm_runtime.h> > > +#include <linux/property.h> > > +#include <linux/regulator/consumer.h> > > +#include <linux/units.h> > > +#include <linux/types.h> > > +#include <linux/time.h> > > +#include <linux/regmap.h> > > Please sort the list of includes in alphabetical order, also you > may consider to shrink the list by removing quite many inherited > includes. Some maintainers prefer the "include what you use" approach, like Andy, so I added all the headers that are directly used. Should I now remove any inherited includes? > > +#include <media/v4l2-cci.h> > > +#include <media/v4l2-ctrls.h> > > +#include <media/v4l2-device.h> > > +#include <media/v4l2-fwnode.h> > > +#include <media/v4l2-mediabus.h> ... > > +static int os02g10_set_framefmt(struct os02g10 *os02g10, > > + struct v4l2_subdev_state *state) > > +{ > > + const struct v4l2_mbus_framefmt *format; > > + const struct os02g10_mode *mode; > > + int ret = 0; > > + > > + format = v4l2_subdev_state_get_format(state, 0); > > + mode = v4l2_find_nearest_size(supported_modes, > > + ARRAY_SIZE(supported_modes), width, > > + height, format->width, format->height); > > + > > + cci_write(os02g10->cci, OS02G10_REG_V_START, mode->y_start, &ret); > > + cci_write(os02g10->cci, OS02G10_REG_V_SIZE, mode->height, &ret); > > + cci_write(os02g10->cci, OS02G10_REG_V_SIZE_MIPI, mode->height, &ret); > > + cci_write(os02g10->cci, OS02G10_REG_H_START, mode->x_start, &ret); > > + cci_write(os02g10->cci, OS02G10_REG_H_SIZE, mode->width, &ret); > > + cci_write(os02g10->cci, OS02G10_REG_H_SIZE_MIPI, mode->width, &ret); > > + > > + return ret; > > Just "return 0" here, and remove the local variable. Could you clarify why this should return 0? The local ret is passed to all cci_write() calls so that any write error is propagated. Returning 0 here would appear to suppress those errors and always report success. Best Regards, Elgin ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v3 2/3] media: i2c: add os02g10 image sensor driver 2026-06-18 6:22 ` Elgin Perumbilly @ 2026-06-18 10:56 ` sakari.ailus 2026-06-18 11:06 ` Vladimir Zapolskiy 1 sibling, 0 replies; 27+ messages in thread From: sakari.ailus @ 2026-06-18 10:56 UTC (permalink / raw) To: Elgin Perumbilly Cc: Vladimir Zapolskiy, laurent.pinchart@ideasonboard.com, Tarang Raval, Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Hans Verkuil, Hans de Goede, Mehdi Djait, Sylvain Petinot, Benjamin Mugnier, Bryan O'Donoghue, Himanshu Bhavani, Heimir Thor Sverrisson, Jingjing Xiong, Svyatoslav Ryhel, linux-media@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org Hi Elgin, Vladimir, On Thu, Jun 18, 2026 at 06:22:14AM +0000, Elgin Perumbilly wrote: > Hi Vladimir, > > Thank you for the review. > > I have addressed all of the comments except for two, where I am not entirely > sure about the requested changes. Could you please take a look at the points > below and let me know your opinion? > > > On 4/24/26 12:25, Elgin Perumbilly wrote: > > > Add a v4l2 subdevice driver for the Omnivision os02g10 sensor. > > > > > > The Omnivision os02g10 is a CMOS image sensor with an active array size of > > > 1920 x 1080. > > > > > > The following features are supported: > > > - Manual exposure an gain control support > > > - vblank/hblank control support > > > - vflip/hflip control support > > > - Test pattern control support > > > - Supported resolution: 1920 x 1080 @ 30fps (SBGGR10) > > > > > > Signed-off-by: Elgin Perumbilly <elgin.perumbilly@siliconsignals.io> > > > Reviewed-by: Tarang Raval <tarang.raval@siliconsignals.io> > > ... > > > > +#include <linux/array_size.h> > > > +#include <linux/bitops.h> > > > +#include <linux/cleanup.h> > > > +#include <linux/clk.h> > > > +#include <linux/container_of.h> > > > +#include <linux/delay.h> > > > +#include <linux/err.h> > > > +#include <linux/gpio/consumer.h> > > > +#include <linux/i2c.h> > > > +#include <linux/module.h> > > > +#include <linux/mutex.h> > > > +#include <linux/pm_runtime.h> > > > +#include <linux/property.h> > > > +#include <linux/regulator/consumer.h> > > > +#include <linux/units.h> > > > +#include <linux/types.h> > > > +#include <linux/time.h> > > > +#include <linux/regmap.h> > > > > Please sort the list of includes in alphabetical order, also you > > may consider to shrink the list by removing quite many inherited > > includes. > > Some maintainers prefer the "include what you use" approach, like Andy, > so I added all the headers that are directly used. Should I now remove > any inherited includes? Andy has been indeed asking to include what you use; this way cleaning up the headers in the future becomes possible. > > > > +#include <media/v4l2-cci.h> > > > +#include <media/v4l2-ctrls.h> > > > +#include <media/v4l2-device.h> > > > +#include <media/v4l2-fwnode.h> > > > +#include <media/v4l2-mediabus.h> > > ... > > > > +static int os02g10_set_framefmt(struct os02g10 *os02g10, > > > + struct v4l2_subdev_state *state) > > > +{ > > > + const struct v4l2_mbus_framefmt *format; > > > + const struct os02g10_mode *mode; > > > + int ret = 0; > > > + > > > + format = v4l2_subdev_state_get_format(state, 0); > > > + mode = v4l2_find_nearest_size(supported_modes, > > > + ARRAY_SIZE(supported_modes), width, > > > + height, format->width, format->height); > > > + > > > + cci_write(os02g10->cci, OS02G10_REG_V_START, mode->y_start, &ret); > > > + cci_write(os02g10->cci, OS02G10_REG_V_SIZE, mode->height, &ret); > > > + cci_write(os02g10->cci, OS02G10_REG_V_SIZE_MIPI, mode->height, &ret); > > > + cci_write(os02g10->cci, OS02G10_REG_H_START, mode->x_start, &ret); > > > + cci_write(os02g10->cci, OS02G10_REG_H_SIZE, mode->width, &ret); > > > + cci_write(os02g10->cci, OS02G10_REG_H_SIZE_MIPI, mode->width, &ret); > > > + > > > + return ret; > > > > Just "return 0" here, and remove the local variable. > > Could you clarify why this should return 0? The local ret is passed to all > cci_write() calls so that any write error is propagated. Returning 0 here > would appear to suppress those errors and always report success. The code seems fine to me. -- Regards, Sakari Ailus ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v3 2/3] media: i2c: add os02g10 image sensor driver 2026-06-18 6:22 ` Elgin Perumbilly 2026-06-18 10:56 ` sakari.ailus @ 2026-06-18 11:06 ` Vladimir Zapolskiy 2026-06-18 11:46 ` Laurent Pinchart 1 sibling, 1 reply; 27+ messages in thread From: Vladimir Zapolskiy @ 2026-06-18 11:06 UTC (permalink / raw) To: Elgin Perumbilly, sakari.ailus@linux.intel.com, laurent.pinchart@ideasonboard.com Cc: Tarang Raval, Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Hans Verkuil, Hans de Goede, Mehdi Djait, Sylvain Petinot, Benjamin Mugnier, Bryan O'Donoghue, Himanshu Bhavani, Heimir Thor Sverrisson, Jingjing Xiong, Svyatoslav Ryhel, linux-media@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org On 6/18/26 09:22, Elgin Perumbilly wrote: > Hi Vladimir, > > Thank you for the review. > > I have addressed all of the comments except for two, where I am not entirely > sure about the requested changes. Could you please take a look at the points > below and let me know your opinion? > >> On 4/24/26 12:25, Elgin Perumbilly wrote: >>> Add a v4l2 subdevice driver for the Omnivision os02g10 sensor. >>> >>> The Omnivision os02g10 is a CMOS image sensor with an active array size of >>> 1920 x 1080. >>> >>> The following features are supported: >>> - Manual exposure an gain control support >>> - vblank/hblank control support >>> - vflip/hflip control support >>> - Test pattern control support >>> - Supported resolution: 1920 x 1080 @ 30fps (SBGGR10) >>> >>> Signed-off-by: Elgin Perumbilly <elgin.perumbilly@siliconsignals.io> >>> Reviewed-by: Tarang Raval <tarang.raval@siliconsignals.io> > > ... > >>> +#include <linux/array_size.h> >>> +#include <linux/bitops.h> >>> +#include <linux/cleanup.h> >>> +#include <linux/clk.h> >>> +#include <linux/container_of.h> >>> +#include <linux/delay.h> >>> +#include <linux/err.h> >>> +#include <linux/gpio/consumer.h> >>> +#include <linux/i2c.h> >>> +#include <linux/module.h> >>> +#include <linux/mutex.h> >>> +#include <linux/pm_runtime.h> >>> +#include <linux/property.h> >>> +#include <linux/regulator/consumer.h> >>> +#include <linux/units.h> >>> +#include <linux/types.h> >>> +#include <linux/time.h> >>> +#include <linux/regmap.h> >> >> Please sort the list of includes in alphabetical order, also you >> may consider to shrink the list by removing quite many inherited >> includes. > > Some maintainers prefer the "include what you use" approach, like Andy, > so I added all the headers that are directly used. Should I now remove > any inherited includes? Yes, here opinions may vary, that's why I asked for sorting and to consider to remove some of the redundant headers. In my personal opinion this type of excessive information is not needed, especially if it is justified only by probable and far future trivial clean-up work. >>> +#include <media/v4l2-cci.h> >>> +#include <media/v4l2-ctrls.h> >>> +#include <media/v4l2-device.h> >>> +#include <media/v4l2-fwnode.h> >>> +#include <media/v4l2-mediabus.h> > > ... > >>> +static int os02g10_set_framefmt(struct os02g10 *os02g10, >>> + struct v4l2_subdev_state *state) >>> +{ >>> + const struct v4l2_mbus_framefmt *format; >>> + const struct os02g10_mode *mode; >>> + int ret = 0; >>> + >>> + format = v4l2_subdev_state_get_format(state, 0); >>> + mode = v4l2_find_nearest_size(supported_modes, >>> + ARRAY_SIZE(supported_modes), width, >>> + height, format->width, format->height); >>> + >>> + cci_write(os02g10->cci, OS02G10_REG_V_START, mode->y_start, &ret); >>> + cci_write(os02g10->cci, OS02G10_REG_V_SIZE, mode->height, &ret); >>> + cci_write(os02g10->cci, OS02G10_REG_V_SIZE_MIPI, mode->height, &ret); >>> + cci_write(os02g10->cci, OS02G10_REG_H_START, mode->x_start, &ret); >>> + cci_write(os02g10->cci, OS02G10_REG_H_SIZE, mode->width, &ret); >>> + cci_write(os02g10->cci, OS02G10_REG_H_SIZE_MIPI, mode->width, &ret); >>> + >>> + return ret; >> >> Just "return 0" here, and remove the local variable. > > Could you clarify why this should return 0? The local ret is passed to all > cci_write() calls so that any write error is propagated. Returning 0 here > would appear to suppress those errors and always report success. > My bad, yes, here please leave 'return ret' as is, I was confused and misleaded by initialization of the local variable to zero, which is redundant, and I'd suggest to remove this initialization. -- Best wishes, Vladimir ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v3 2/3] media: i2c: add os02g10 image sensor driver 2026-06-18 11:06 ` Vladimir Zapolskiy @ 2026-06-18 11:46 ` Laurent Pinchart 0 siblings, 0 replies; 27+ messages in thread From: Laurent Pinchart @ 2026-06-18 11:46 UTC (permalink / raw) To: Vladimir Zapolskiy Cc: Elgin Perumbilly, sakari.ailus@linux.intel.com, Tarang Raval, Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Hans Verkuil, Hans de Goede, Mehdi Djait, Sylvain Petinot, Benjamin Mugnier, Bryan O'Donoghue, Himanshu Bhavani, Heimir Thor Sverrisson, Jingjing Xiong, Svyatoslav Ryhel, linux-media@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org On Thu, Jun 18, 2026 at 02:06:20PM +0300, Vladimir Zapolskiy wrote: > On 6/18/26 09:22, Elgin Perumbilly wrote: > > Hi Vladimir, > > > > Thank you for the review. > > > > I have addressed all of the comments except for two, where I am not entirely > > sure about the requested changes. Could you please take a look at the points > > below and let me know your opinion? > > > >> On 4/24/26 12:25, Elgin Perumbilly wrote: > >>> Add a v4l2 subdevice driver for the Omnivision os02g10 sensor. > >>> > >>> The Omnivision os02g10 is a CMOS image sensor with an active array size of > >>> 1920 x 1080. > >>> > >>> The following features are supported: > >>> - Manual exposure an gain control support > >>> - vblank/hblank control support > >>> - vflip/hflip control support > >>> - Test pattern control support > >>> - Supported resolution: 1920 x 1080 @ 30fps (SBGGR10) > >>> > >>> Signed-off-by: Elgin Perumbilly <elgin.perumbilly@siliconsignals.io> > >>> Reviewed-by: Tarang Raval <tarang.raval@siliconsignals.io> > > > > ... > > > >>> +#include <linux/array_size.h> > >>> +#include <linux/bitops.h> > >>> +#include <linux/cleanup.h> > >>> +#include <linux/clk.h> > >>> +#include <linux/container_of.h> > >>> +#include <linux/delay.h> > >>> +#include <linux/err.h> > >>> +#include <linux/gpio/consumer.h> > >>> +#include <linux/i2c.h> > >>> +#include <linux/module.h> > >>> +#include <linux/mutex.h> > >>> +#include <linux/pm_runtime.h> > >>> +#include <linux/property.h> > >>> +#include <linux/regulator/consumer.h> > >>> +#include <linux/units.h> > >>> +#include <linux/types.h> > >>> +#include <linux/time.h> > >>> +#include <linux/regmap.h> > >> > >> Please sort the list of includes in alphabetical order, also you > >> may consider to shrink the list by removing quite many inherited > >> includes. > > > > Some maintainers prefer the "include what you use" approach, like Andy, > > so I added all the headers that are directly used. Should I now remove > > any inherited includes? > > Yes, here opinions may vary, that's why I asked for sorting and to Sorting is a good idea. > consider to remove some of the redundant headers. In my personal opinion > this type of excessive information is not needed, especially if it is > justified only by probable and far future trivial clean-up work. I typically ask for a "include what you use" approach too, to avoid build breakages. It's not only a matter of future work, but indirect includes can also vary based on the kernel configuration (and the architecture). > >>> +#include <media/v4l2-cci.h> > >>> +#include <media/v4l2-ctrls.h> > >>> +#include <media/v4l2-device.h> > >>> +#include <media/v4l2-fwnode.h> > >>> +#include <media/v4l2-mediabus.h> > > > > ... > > > >>> +static int os02g10_set_framefmt(struct os02g10 *os02g10, > >>> + struct v4l2_subdev_state *state) > >>> +{ > >>> + const struct v4l2_mbus_framefmt *format; > >>> + const struct os02g10_mode *mode; > >>> + int ret = 0; > >>> + > >>> + format = v4l2_subdev_state_get_format(state, 0); > >>> + mode = v4l2_find_nearest_size(supported_modes, > >>> + ARRAY_SIZE(supported_modes), width, > >>> + height, format->width, format->height); > >>> + > >>> + cci_write(os02g10->cci, OS02G10_REG_V_START, mode->y_start, &ret); > >>> + cci_write(os02g10->cci, OS02G10_REG_V_SIZE, mode->height, &ret); > >>> + cci_write(os02g10->cci, OS02G10_REG_V_SIZE_MIPI, mode->height, &ret); > >>> + cci_write(os02g10->cci, OS02G10_REG_H_START, mode->x_start, &ret); > >>> + cci_write(os02g10->cci, OS02G10_REG_H_SIZE, mode->width, &ret); > >>> + cci_write(os02g10->cci, OS02G10_REG_H_SIZE_MIPI, mode->width, &ret); > >>> + > >>> + return ret; > >> > >> Just "return 0" here, and remove the local variable. > > > > Could you clarify why this should return 0? The local ret is passed to all > > cci_write() calls so that any write error is propagated. Returning 0 here > > would appear to suppress those errors and always report success. > > My bad, yes, here please leave 'return ret' as is, I was confused and > misleaded by initialization of the local variable to zero, which is > redundant, and I'd suggest to remove this initialization. -- Regards, Laurent Pinchart ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v3 2/3] media: i2c: add os02g10 image sensor driver 2026-04-24 9:25 ` [PATCH v3 2/3] media: i2c: add os02g10 image sensor driver Elgin Perumbilly 2026-06-12 10:12 ` Vladimir Zapolskiy @ 2026-06-18 11:43 ` Sakari Ailus 2026-06-18 12:27 ` Tarang Raval 2026-06-18 12:58 ` Laurent Pinchart 1 sibling, 2 replies; 27+ messages in thread From: Sakari Ailus @ 2026-06-18 11:43 UTC (permalink / raw) To: Elgin Perumbilly Cc: laurent.pinchart, Tarang Raval, Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Hans Verkuil, Hans de Goede, Vladimir Zapolskiy, Mehdi Djait, Sylvain Petinot, Benjamin Mugnier, Bryan O'Donoghue, Himanshu Bhavani, Heimir Thor Sverrisson, Jingjing Xiong, Svyatoslav Ryhel, linux-media, devicetree, linux-kernel Hi Elgin, Thanks for the update. A few minor comments below... On Fri, Apr 24, 2026 at 02:55:46PM +0530, Elgin Perumbilly wrote: > Add a v4l2 subdevice driver for the Omnivision os02g10 sensor. > > The Omnivision os02g10 is a CMOS image sensor with an active array size of > 1920 x 1080. > > The following features are supported: > - Manual exposure an gain control support > - vblank/hblank control support > - vflip/hflip control support > - Test pattern control support > - Supported resolution: 1920 x 1080 @ 30fps (SBGGR10) > > Signed-off-by: Elgin Perumbilly <elgin.perumbilly@siliconsignals.io> > Reviewed-by: Tarang Raval <tarang.raval@siliconsignals.io> > --- > MAINTAINERS | 1 + > drivers/media/i2c/Kconfig | 10 + > drivers/media/i2c/Makefile | 1 + > drivers/media/i2c/os02g10.c | 949 ++++++++++++++++++++++++++++++++++++ > 4 files changed, 961 insertions(+) > create mode 100644 drivers/media/i2c/os02g10.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index 8a0a55073c30..693e71b51926 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -19449,6 +19449,7 @@ M: Elgin Perumbilly <elgin.perumbilly@siliconsignals.io> > L: linux-media@vger.kernel.org > S: Maintained > F: Documentation/devicetree/bindings/media/i2c/ovti,os02g10.yaml > +F: drivers/media/i2c/os02g10.c > > OMNIVISION OS05B10 SENSOR DRIVER > M: Himanshu Bhavani <himanshu.bhavani@siliconsignals.io> > diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig > index 5eb1e0e0a87a..dd6e9562acf6 100644 > --- a/drivers/media/i2c/Kconfig > +++ b/drivers/media/i2c/Kconfig > @@ -372,6 +372,16 @@ config VIDEO_OG0VE1B > To compile this driver as a module, choose M here: the > module will be called og0ve1b. > > +config VIDEO_OS02G10 > + tristate "OmniVision OS02G10 sensor support" > + select V4L2_CCI_I2C > + help > + This is a Video4Linux2 sensor driver for Omnivision > + OS02G10 camera sensor. > + > + To compile this driver as a module, choose M here: the > + module will be called os02g10. > + > config VIDEO_OS05B10 > tristate "OmniVision OS05B10 sensor support" > select V4L2_CCI_I2C > diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile > index a3a6396df3c4..a7554d2eb140 100644 > --- a/drivers/media/i2c/Makefile > +++ b/drivers/media/i2c/Makefile > @@ -84,6 +84,7 @@ obj-$(CONFIG_VIDEO_MT9V032) += mt9v032.o > obj-$(CONFIG_VIDEO_MT9V111) += mt9v111.o > obj-$(CONFIG_VIDEO_OG01A1B) += og01a1b.o > obj-$(CONFIG_VIDEO_OG0VE1B) += og0ve1b.o > +obj-$(CONFIG_VIDEO_OS02G10) += os02g10.o > obj-$(CONFIG_VIDEO_OS05B10) += os05b10.o > obj-$(CONFIG_VIDEO_OV01A10) += ov01a10.o > obj-$(CONFIG_VIDEO_OV02A10) += ov02a10.o > diff --git a/drivers/media/i2c/os02g10.c b/drivers/media/i2c/os02g10.c > new file mode 100644 > index 000000000000..fad2dd0ad7aa > --- /dev/null > +++ b/drivers/media/i2c/os02g10.c > @@ -0,0 +1,949 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * V4L2 Support for the OS02G10 > + * > + * Copyright (C) 2026 Silicon Signals Pvt. Ltd. > + * > + */ > + > +#include <linux/array_size.h> > +#include <linux/bitops.h> > +#include <linux/cleanup.h> > +#include <linux/clk.h> > +#include <linux/container_of.h> > +#include <linux/delay.h> > +#include <linux/err.h> > +#include <linux/gpio/consumer.h> > +#include <linux/i2c.h> > +#include <linux/module.h> > +#include <linux/mutex.h> > +#include <linux/pm_runtime.h> > +#include <linux/property.h> > +#include <linux/regulator/consumer.h> > +#include <linux/units.h> > +#include <linux/types.h> > +#include <linux/time.h> > +#include <linux/regmap.h> > + > +#include <media/v4l2-cci.h> > +#include <media/v4l2-ctrls.h> > +#include <media/v4l2-device.h> > +#include <media/v4l2-fwnode.h> > +#include <media/v4l2-mediabus.h> > + > +#define OS02G10_XCLK_FREQ (24 * HZ_PER_MHZ) > + > +/* Page 0 */ > +#define OS02G10_REG_CHIPID CCI_REG24(0x002) > +#define OS02G10_CHIPID 0x560247 > + > +#define OS02G10_REG_PLL_DIV_CTRL CCI_REG8(0x030) > +#define OS02G10_REG_PLL_DCTL_BIAS_CTRL CCI_REG8(0x035) > +#define OS02G10_REG_GATE_EN_CTRL CCI_REG8(0x038) > +#define OS02G10_REG_DPLL_NC CCI_REG8(0x041) > +#define OS02G10_REG_MP_PHASE_CTRL CCI_REG8(0x044) > + > +/* Page 1 */ > +#define OS02G10_REG_STREAM_CTRL CCI_REG8(0x1b1) > +#define OS02G10_STREAM_CTRL_ON 0x03 > +#define OS02G10_STREAM_CTRL_OFF 0x00 > + > +#define OS02G10_REG_FRAME_SYNC CCI_REG8(0x101) > + > +#define OS02G10_REG_FRAME_LENGTH CCI_REG16(0x10e) > +#define OS02G10_FRAME_LENGTH_MAX 0xffff > +#define OS02G10_REG_HBLANK CCI_REG16(0x109) > + > +#define OS02G10_REG_FRAME_TEST_CTRL CCI_REG8(0x10d) > +#define OS02G10_FRAME_EXP_SEPERATE_EN BIT(4) > +#define OS02G10_TEST_PATTERN_ENABLE BIT(0) > + > +#define OS02G10_REG_ULP_PWD_DUMMY_CTRL CCI_REG8(0x13c) > +#define OS02G10_REG_DC_LEVEL_LIMIT_EN CCI_REG8(0x146) > +#define OS02G10_REG_DC_LEVEL_LIMIT_L CCI_REG8(0x147) > +#define OS02G10_REG_BLC_DATA_LIMIT_L CCI_REG8(0x148) > +#define OS02G10_REG_DC_BLC_LIMIT_H CCI_REG8(0x149) > + > +#define OS02G10_REG_HS_LP_CTRL CCI_REG8(0x192) > +#define OS02G10_REG_HS_LEVEL CCI_REG8(0x19d) > +#define OS02G10_REG_HS_DRV CCI_REG8(0x19e) > + > +#define OS02G10_REG_GB_SUBOFFSET CCI_REG8(0x1f0) > +#define OS02G10_REG_BLUE_SUBOFFSET CCI_REG8(0x1f1) > +#define OS02G10_REG_RED_SUBOFFSET CCI_REG8(0x1f2) > +#define OS02G10_REG_GR_SUBOFFSET CCI_REG8(0x1f3) > + > +#define OS02G10_REG_ABL_TRIGGER CCI_REG8(0x1fa) > +#define OS02G10_REG_ABL CCI_REG8(0x1fb) > + > +#define OS02G10_REG_H_SIZE_MIPI CCI_REG16(0x18e) > +#define OS02G10_REG_V_SIZE_MIPI CCI_REG16(0x190) > +#define OS02G10_REG_MIPI_TX_SPEED_CTRL CCI_REG8(0x1a1) > + > +#define OS02G10_REG_LONG_EXPOSURE CCI_REG16(0x103) > +#define OS02G10_EXPOSURE_MIN 4 > +#define OS02G10_EXPOSURE_STEP 1 > +#define OS02G10_EXPOSURE_MARGIN 9 > + > +#define OS02G10_REG_ANALOG_GAIN CCI_REG8(0x124) > +#define OS02G10_ANALOG_GAIN_MIN 0x10 > +#define OS02G10_ANALOG_GAIN_MAX 0xf8 > +#define OS02G10_ANALOG_GAIN_STEP 1 > +#define OS02G10_ANALOG_GAIN_DEFAULT 0x10 > + > +#define OS02G10_REG_DIGITAL_GAIN_H CCI_REG8(0x137) > +#define OS02G10_REG_DIGITAL_GAIN_L CCI_REG8(0x139) > +#define OS02G10_DIGITAL_GAIN_MIN 0x40 > +#define OS02G10_DIGITAL_GAIN_MAX 0x800 > +#define OS02G10_DIGITAL_GAIN_STEP 64 > +#define OS02G10_DIGITAL_GAIN_DEFAULT 0x40 > + > +#define OS02G10_REG_FLIP_MIRROR CCI_REG8(0x13f) > +#define OS02G10_FLIP BIT(1) > +#define OS02G10_MIRROR BIT(0) > + > +/* Page 2 */ > +#define OS02G10_REG_V_START CCI_REG16(0x2a0) > +#define OS02G10_REG_V_SIZE CCI_REG16(0x2a2) > +#define OS02G10_REG_H_START CCI_REG16(0x2a4) > +#define OS02G10_REG_H_SIZE CCI_REG16(0x2a6) > + > +#define OS02G10_REG_SIF_CTRL CCI_REG8(0x25e) > +#define OS02G10_ORIENTATION_BAYER_FIX 0x32 > + > +#define OS02G10_LINK_FREQ_720MHZ (720 * HZ_PER_MHZ) > + > +/* OS02G10 native and active pixel array size */ > +static const struct v4l2_rect os02g10_native_area = { > + .top = 0, > + .left = 0, > + .width = 1928, > + .height = 1088, > +}; > + > +static const struct v4l2_rect os02g10_active_area = { > + .top = 4, > + .left = 4, > + .width = 1920, > + .height = 1080, > +}; > + > +static const char * const os02g10_supply_name[] = { > + "avdd", /* Analog power */ > + "dovdd", /* Digital I/O power */ > + "dvdd", /* Digital core power */ > +}; > + > +struct os02g10 { > + struct device *dev; > + struct regmap *cci; > + struct v4l2_subdev sd; > + struct media_pad pad; > + struct clk *xclk; > + struct gpio_desc *reset_gpio; > + struct regulator_bulk_data supplies[ARRAY_SIZE(os02g10_supply_name)]; > + > + /* V4L2 Controls */ > + struct v4l2_ctrl_handler handler; > + struct v4l2_ctrl *link_freq; > + struct v4l2_ctrl *vblank; > + struct v4l2_ctrl *exposure; > + struct v4l2_ctrl *vflip; > + struct v4l2_ctrl *hflip; > +}; > + > +struct os02g10_mode { > + u32 width; > + u32 height; > + u32 vts_def; > + u32 exp_def; > + u32 x_start; > + u32 y_start; > +}; > + > +static const struct cci_reg_sequence os02g10_common_regs[] = { > + { OS02G10_REG_PLL_DIV_CTRL, 0x0a}, > + { OS02G10_REG_PLL_DCTL_BIAS_CTRL, 0x04}, > + { OS02G10_REG_GATE_EN_CTRL, 0x11}, > + { OS02G10_REG_DPLL_NC, 0x06}, > + { OS02G10_REG_MP_PHASE_CTRL, 0x20}, > + { CCI_REG8(0x119), 0x50}, > + { CCI_REG8(0x11a), 0x0c}, > + { CCI_REG8(0x11b), 0x0d}, > + { CCI_REG8(0x11c), 0x00}, > + { CCI_REG8(0x11d), 0x75}, > + { CCI_REG8(0x11e), 0x52}, > + { CCI_REG8(0x122), 0x14}, > + { CCI_REG8(0x125), 0x44}, > + { CCI_REG8(0x126), 0x0f}, > + { OS02G10_REG_ULP_PWD_DUMMY_CTRL, 0xca}, > + { CCI_REG8(0x13d), 0x4a}, > + { CCI_REG8(0x140), 0x0f}, > + { CCI_REG8(0x143), 0x38}, > + { OS02G10_REG_DC_LEVEL_LIMIT_EN, 0x01}, > + { OS02G10_REG_DC_LEVEL_LIMIT_L, 0x00}, > + { OS02G10_REG_DC_BLC_LIMIT_H, 0x32}, > + { CCI_REG8(0x150), 0x01}, > + { CCI_REG8(0x151), 0x28}, > + { CCI_REG8(0x152), 0x20}, > + { CCI_REG8(0x153), 0x03}, > + { CCI_REG8(0x157), 0x16}, > + { CCI_REG8(0x159), 0x01}, > + { CCI_REG8(0x15a), 0x01}, > + { CCI_REG8(0x15d), 0x04}, > + { CCI_REG8(0x16a), 0x04}, > + { CCI_REG8(0x16b), 0x03}, > + { CCI_REG8(0x16e), 0x28}, > + { CCI_REG8(0x171), 0xc2}, > + { CCI_REG8(0x172), 0x04}, > + { CCI_REG8(0x173), 0x38}, > + { CCI_REG8(0x174), 0x04}, > + { CCI_REG8(0x179), 0x00}, > + { CCI_REG8(0x17a), 0xb2}, > + { CCI_REG8(0x17b), 0x10}, > + { OS02G10_REG_HS_LP_CTRL, 0x02}, > + { OS02G10_REG_HS_LEVEL, 0x03}, > + { OS02G10_REG_HS_DRV, 0x55}, > + { CCI_REG8(0x1b8), 0x70}, > + { CCI_REG8(0x1b9), 0x70}, > + { CCI_REG8(0x1ba), 0x70}, > + { CCI_REG8(0x1bb), 0x70}, > + { CCI_REG8(0x1bc), 0x00}, > + { CCI_REG8(0x1c4), 0x6d}, > + { CCI_REG8(0x1c5), 0x6d}, > + { CCI_REG8(0x1c6), 0x6d}, > + { CCI_REG8(0x1c7), 0x6d}, > + { CCI_REG8(0x1cc), 0x11}, > + { CCI_REG8(0x1cd), 0xe0}, > + { CCI_REG8(0x1d0), 0x1b}, > + { CCI_REG8(0x1d2), 0x76}, > + { CCI_REG8(0x1d3), 0x68}, > + { CCI_REG8(0x1d4), 0x68}, > + { CCI_REG8(0x1d5), 0x73}, > + { CCI_REG8(0x1d6), 0x73}, > + { CCI_REG8(0x1e8), 0x55}, > + { OS02G10_REG_GB_SUBOFFSET, 0x40}, > + { OS02G10_REG_BLUE_SUBOFFSET, 0x40}, > + { OS02G10_REG_RED_SUBOFFSET, 0x40}, > + { OS02G10_REG_GR_SUBOFFSET, 0x40}, > + { OS02G10_REG_ABL_TRIGGER, 0x1c}, > + { OS02G10_REG_ABL, 0x33}, > + { CCI_REG8(0x1fc), 0x80}, > + { CCI_REG8(0x1fe), 0x80}, > + { CCI_REG8(0x303), 0x67}, > + { CCI_REG8(0x300), 0x59}, > + { CCI_REG8(0x304), 0x11}, > + { CCI_REG8(0x305), 0x04}, > + { CCI_REG8(0x306), 0x0c}, > + { CCI_REG8(0x307), 0x08}, > + { CCI_REG8(0x308), 0x08}, > + { CCI_REG8(0x309), 0x4f}, > + { CCI_REG8(0x30b), 0x08}, > + { CCI_REG8(0x30d), 0x26}, > + { CCI_REG8(0x30f), 0x00}, > + { CCI_REG8(0x234), 0xfe}, > + { OS02G10_REG_MIPI_TX_SPEED_CTRL, 0x05}, > +}; > + > +static const struct os02g10_mode supported_modes[] = { > + { > + .width = 1920, > + .height = 1080, > + .vts_def = 1246, > + .exp_def = 1100, > + .x_start = 2, > + .y_start = 6, > + }, > +}; > + > +static const s64 link_freq_menu_items[] = { > + OS02G10_LINK_FREQ_720MHZ, > +}; > + > +static const char * const os02g10_test_pattern_menu[] = { > + "Disabled", > + "Colorbar", > +}; > + > +static inline struct os02g10 *to_os02g10(struct v4l2_subdev *sd) > +{ > + return container_of_const(sd, struct os02g10, sd); > +} > + > +static u32 os02g10_get_format_code(struct os02g10 *os02g10) > +{ > + static const u32 codes[2][2] = { > + { MEDIA_BUS_FMT_SBGGR10_1X10, MEDIA_BUS_FMT_SGBRG10_1X10, }, > + { MEDIA_BUS_FMT_SGRBG10_1X10, MEDIA_BUS_FMT_SRGGB10_1X10, }, > + }; > + u32 code = codes[os02g10->vflip->val][os02g10->hflip->val]; No need for a local variable for this. > + > + return code; > +} > + > +static int os02g10_set_ctrl(struct v4l2_ctrl *ctrl) > +{ > + struct os02g10 *os02g10 = container_of_const(ctrl->handler, > + struct os02g10, handler); > + struct v4l2_subdev_state *state; > + struct v4l2_mbus_framefmt *fmt; > + int ret = 0; > + > + state = v4l2_subdev_get_locked_active_state(&os02g10->sd); > + fmt = v4l2_subdev_state_get_format(state, 0); > + > + if (ctrl->id == V4L2_CID_VBLANK) { > + /* Honour the VBLANK limits when setting exposure */ > + s64 max = fmt->height + ctrl->val - OS02G10_EXPOSURE_MARGIN; > + > + ret = __v4l2_ctrl_modify_range(os02g10->exposure, > + os02g10->exposure->minimum, max, > + os02g10->exposure->step, > + os02g10->exposure->default_value); > + if (ret) > + return ret; > + } > + > + if (pm_runtime_get_if_active(os02g10->dev) == 0) > + return 0; > + > + switch (ctrl->id) { > + case V4L2_CID_EXPOSURE: > + cci_write(os02g10->cci, OS02G10_REG_LONG_EXPOSURE, > + ctrl->val, &ret); > + break; > + case V4L2_CID_ANALOGUE_GAIN: > + cci_write(os02g10->cci, OS02G10_REG_ANALOG_GAIN, > + ctrl->val, &ret); > + break; > + case V4L2_CID_DIGITAL_GAIN: > + cci_write(os02g10->cci, OS02G10_REG_DIGITAL_GAIN_L, > + (ctrl->val & 0xff), &ret); > + cci_write(os02g10->cci, OS02G10_REG_DIGITAL_GAIN_H, > + ((ctrl->val >> 8) & 0x7), &ret); > + break; > + case V4L2_CID_VBLANK: > + u64 vts = ctrl->val + fmt->height; > + > + cci_update_bits(os02g10->cci, OS02G10_REG_FRAME_TEST_CTRL, > + OS02G10_FRAME_EXP_SEPERATE_EN, > + OS02G10_FRAME_EXP_SEPERATE_EN, &ret); > + cci_write(os02g10->cci, OS02G10_REG_FRAME_LENGTH, vts, &ret); > + break; > + case V4L2_CID_HFLIP: > + case V4L2_CID_VFLIP: > + cci_write(os02g10->cci, OS02G10_REG_FLIP_MIRROR, > + os02g10->hflip->val | os02g10->vflip->val << 1, > + &ret); Fits on previous line. > + cci_write(os02g10->cci, OS02G10_REG_SIF_CTRL, > + OS02G10_ORIENTATION_BAYER_FIX, &ret); > + break; > + case V4L2_CID_TEST_PATTERN: > + cci_update_bits(os02g10->cci, > + OS02G10_REG_FRAME_TEST_CTRL, > + OS02G10_TEST_PATTERN_ENABLE, > + ctrl->val ? OS02G10_TEST_PATTERN_ENABLE : 0, > + &ret); > + break; > + default: > + ret = -EINVAL; > + break; > + } > + cci_write(os02g10->cci, OS02G10_REG_FRAME_SYNC, 0x01, &ret); > + > + pm_runtime_put(os02g10->dev); > + > + return ret; > +} > + > +static const struct v4l2_ctrl_ops os02g10_ctrl_ops = { > + .s_ctrl = os02g10_set_ctrl, > +}; > + > +static int os02g10_init_controls(struct os02g10 *os02g10) > +{ > + const struct os02g10_mode *mode = &supported_modes[0]; > + struct v4l2_fwnode_device_properties props; > + u64 vblank_def, exp_max, pixel_rate; > + struct v4l2_ctrl_handler *ctrl_hdlr; > + int ret; > + > + ctrl_hdlr = &os02g10->handler; > + v4l2_ctrl_handler_init(ctrl_hdlr, 11); > + > + /* pixel_rate = link_freq * 2 * nr_of_lanes / bits_per_sample */ > + pixel_rate = div_u64(OS02G10_LINK_FREQ_720MHZ * 2 * 2, 10); > + v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, V4L2_CID_PIXEL_RATE, 0, > + pixel_rate, 1, pixel_rate); > + > + os02g10->link_freq = > + v4l2_ctrl_new_int_menu(ctrl_hdlr, &os02g10_ctrl_ops, > + V4L2_CID_LINK_FREQ, > + ARRAY_SIZE(link_freq_menu_items) - 1, > + 0, link_freq_menu_items); > + if (os02g10->link_freq) > + os02g10->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; > + > + vblank_def = mode->vts_def - mode->height; > + os02g10->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, > + V4L2_CID_VBLANK, vblank_def, > + OS02G10_FRAME_LENGTH_MAX - mode->height, > + 1, vblank_def); > + > + exp_max = mode->vts_def - OS02G10_EXPOSURE_MARGIN; > + os02g10->exposure = > + v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, > + V4L2_CID_EXPOSURE, > + OS02G10_EXPOSURE_MIN, exp_max, > + OS02G10_EXPOSURE_STEP, mode->exp_def); > + > + v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, > + V4L2_CID_ANALOGUE_GAIN, OS02G10_ANALOG_GAIN_MIN, > + OS02G10_ANALOG_GAIN_MAX, OS02G10_ANALOG_GAIN_STEP, > + OS02G10_ANALOG_GAIN_DEFAULT); > + > + v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, > + V4L2_CID_DIGITAL_GAIN, OS02G10_DIGITAL_GAIN_MIN, > + OS02G10_DIGITAL_GAIN_MAX, OS02G10_DIGITAL_GAIN_STEP, > + OS02G10_DIGITAL_GAIN_DEFAULT); > + > + os02g10->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, > + V4L2_CID_HFLIP, 0, 1, 1, 0); > + if (os02g10->hflip) > + os02g10->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; > + > + os02g10->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, > + V4L2_CID_VFLIP, 0, 1, 1, 0); > + if (os02g10->vflip) > + os02g10->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; There are quite a few flags being modified in the function. You can drop the checks if you do this after the control handler's error check. > + > + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &os02g10_ctrl_ops, > + V4L2_CID_TEST_PATTERN, > + ARRAY_SIZE(os02g10_test_pattern_menu) - 1, > + 0, 0, os02g10_test_pattern_menu); > + > + ret = v4l2_fwnode_device_parse(os02g10->dev, &props); > + if (ret) > + goto err_handler_free; Do this first in the function and error handling is simplified. You can rely on the handler's error value only, for instance (apart from the above call). > + > + ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, > + &os02g10_ctrl_ops, &props); > + if (ret) > + goto err_handler_free; > + > + os02g10->sd.ctrl_handler = ctrl_hdlr; > + > + return 0; > + > +err_handler_free: > + v4l2_ctrl_handler_free(ctrl_hdlr); > + > + return ret; > +} > + > +static int os02g10_set_framefmt(struct os02g10 *os02g10, > + struct v4l2_subdev_state *state) > +{ > + const struct v4l2_mbus_framefmt *format; > + const struct os02g10_mode *mode; > + int ret = 0; > + > + format = v4l2_subdev_state_get_format(state, 0); > + mode = v4l2_find_nearest_size(supported_modes, > + ARRAY_SIZE(supported_modes), width, > + height, format->width, format->height); > + > + cci_write(os02g10->cci, OS02G10_REG_V_START, mode->y_start, &ret); > + cci_write(os02g10->cci, OS02G10_REG_V_SIZE, mode->height, &ret); > + cci_write(os02g10->cci, OS02G10_REG_V_SIZE_MIPI, mode->height, &ret); > + cci_write(os02g10->cci, OS02G10_REG_H_START, mode->x_start, &ret); > + cci_write(os02g10->cci, OS02G10_REG_H_SIZE, mode->width, &ret); > + cci_write(os02g10->cci, OS02G10_REG_H_SIZE_MIPI, mode->width, &ret); > + > + return ret; > +} > + > +static int os02g10_enable_streams(struct v4l2_subdev *sd, > + struct v4l2_subdev_state *state, u32 pad, > + u64 streams_mask) > +{ > + struct os02g10 *os02g10 = to_os02g10(sd); > + int ret; > + > + ret = pm_runtime_resume_and_get(os02g10->dev); > + if (ret < 0) > + return ret; > + > + ret = cci_multi_reg_write(os02g10->cci, os02g10_common_regs, > + ARRAY_SIZE(os02g10_common_regs), NULL); > + if (ret) { > + dev_err(os02g10->dev, "failed to write common registers\n"); > + goto err_rpm_put; > + } > + > + ret = os02g10_set_framefmt(os02g10, state); > + if (ret) { > + dev_err(os02g10->dev, "failed to set frame foramt\n"); > + goto err_rpm_put; > + } > + > + /* Apply customized values from user */ > + ret = __v4l2_ctrl_handler_setup(os02g10->sd.ctrl_handler); > + if (ret) > + goto err_rpm_put; > + > + ret = cci_write(os02g10->cci, OS02G10_REG_STREAM_CTRL, > + OS02G10_STREAM_CTRL_ON, NULL); > + if (ret) > + goto err_rpm_put; > + > + /* vflip and hflip cannot change during streaming */ > + __v4l2_ctrl_grab(os02g10->vflip, true); > + __v4l2_ctrl_grab(os02g10->hflip, true); > + > + return 0; > + > +err_rpm_put: > + pm_runtime_put(os02g10->dev); > + return ret; > +} > + > +static int os02g10_disable_streams(struct v4l2_subdev *sd, > + struct v4l2_subdev_state *state, u32 pad, > + u64 streams_mask) > +{ > + struct os02g10 *os02g10 = to_os02g10(sd); > + int ret; > + > + ret = cci_write(os02g10->cci, OS02G10_REG_STREAM_CTRL, > + OS02G10_STREAM_CTRL_OFF, NULL); > + if (ret) > + dev_err(os02g10->dev, "Failed to stop stream\n"); > + > + __v4l2_ctrl_grab(os02g10->vflip, false); > + __v4l2_ctrl_grab(os02g10->hflip, false); > + > + pm_runtime_put(os02g10->dev); > + > + return ret; > +} > + > +static int os02g10_get_selection(struct v4l2_subdev *sd, > + struct v4l2_subdev_state *sd_state, > + struct v4l2_subdev_selection *sel) > +{ > + switch (sel->target) { > + case V4L2_SEL_TGT_CROP_BOUNDS: > + case V4L2_SEL_TGT_NATIVE_SIZE: > + sel->r = os02g10_native_area; > + return 0; > + case V4L2_SEL_TGT_CROP: > + case V4L2_SEL_TGT_CROP_DEFAULT: > + sel->r = os02g10_active_area; > + return 0; > + default: > + return -EINVAL; > + } > +} > + > +static int os02g10_enum_mbus_code(struct v4l2_subdev *sd, > + struct v4l2_subdev_state *sd_state, > + struct v4l2_subdev_mbus_code_enum *code) > +{ > + struct os02g10 *os02g10 = to_os02g10(sd); > + > + if (code->index) > + return -EINVAL; > + > + code->code = os02g10_get_format_code(os02g10); > + > + return 0; > +} > + > +static int os02g10_enum_frame_size(struct v4l2_subdev *sd, > + struct v4l2_subdev_state *sd_state, > + struct v4l2_subdev_frame_size_enum *fse) > +{ > + struct os02g10 *os02g10 = to_os02g10(sd); > + > + if (fse->index >= ARRAY_SIZE(supported_modes)) > + return -EINVAL; > + > + if (fse->code != os02g10_get_format_code(os02g10)) > + 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 int os02g10_set_pad_format(struct v4l2_subdev *sd, > + struct v4l2_subdev_state *sd_state, > + struct v4l2_subdev_format *fmt) > +{ > + struct os02g10 *os02g10 = to_os02g10(sd); > + struct v4l2_mbus_framefmt *format; > + const struct os02g10_mode *mode; > + > + format = v4l2_subdev_state_get_format(sd_state, 0); > + > + mode = v4l2_find_nearest_size(supported_modes, > + ARRAY_SIZE(supported_modes), > + width, height, > + fmt->format.width, fmt->format.height); > + > + fmt->format.code = os02g10_get_format_code(os02g10); > + fmt->format.width = mode->width; > + fmt->format.height = mode->height; > + fmt->format.field = V4L2_FIELD_NONE; > + fmt->format.colorspace = V4L2_COLORSPACE_RAW; > + fmt->format.quantization = V4L2_QUANTIZATION_FULL_RANGE; > + fmt->format.xfer_func = V4L2_XFER_FUNC_NONE; > + > + *format = fmt->format; > + > + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { > + u32 vblank_def = mode->vts_def - mode->height; > + > + int ret = __v4l2_ctrl_modify_range(os02g10->vblank, vblank_def, > + OS02G10_FRAME_LENGTH_MAX - > + mode->height, 1, vblank_def); return ...; > + if (ret) > + return ret; > + } > + > + return 0; > +} > + > +static int os02g10_init_state(struct v4l2_subdev *sd, > + struct v4l2_subdev_state *state) > +{ > + struct os02g10 *os02g10 = to_os02g10(sd); > + struct v4l2_subdev_format fmt = { > + .which = V4L2_SUBDEV_FORMAT_TRY, > + .format = { > + .code = os02g10_get_format_code(os02g10), > + .width = supported_modes[0].width, > + .height = supported_modes[0].height, > + }, > + }; > + > + os02g10_set_pad_format(sd, state, &fmt); > + > + return 0; > +} > + > +static const struct v4l2_subdev_video_ops os02g10_video_ops = { > + .s_stream = v4l2_subdev_s_stream_helper, > +}; > + > +static const struct v4l2_subdev_pad_ops os02g10_pad_ops = { > + .enum_mbus_code = os02g10_enum_mbus_code, > + .get_fmt = v4l2_subdev_get_fmt, > + .set_fmt = os02g10_set_pad_format, > + .get_selection = os02g10_get_selection, > + .enum_frame_size = os02g10_enum_frame_size, > + .enable_streams = os02g10_enable_streams, > + .disable_streams = os02g10_disable_streams, > +}; > + > +static const struct v4l2_subdev_ops os02g10_subdev_ops = { > + .video = &os02g10_video_ops, > + .pad = &os02g10_pad_ops, > +}; > + > +static const struct v4l2_subdev_internal_ops os02g10_internal_ops = { > + .init_state = os02g10_init_state, > +}; > + > +static int os02g10_power_on(struct device *dev) > +{ > + struct v4l2_subdev *sd = dev_get_drvdata(dev); > + struct os02g10 *os02g10 = to_os02g10(sd); > + int ret; > + > + ret = regulator_bulk_enable(ARRAY_SIZE(os02g10_supply_name), > + os02g10->supplies); > + if (ret) { > + dev_err(os02g10->dev, "failed to enable regulators\n"); > + return ret; > + } > + > + /* T4: delay from DOVDD stable to MCLK on */ > + fsleep(5 * USEC_PER_MSEC); Does the sensor really require this? Typically no delays are required before lifting xshutdown. > + > + ret = clk_prepare_enable(os02g10->xclk); > + if (ret) { > + dev_err(os02g10->dev, "failed to enable clock\n"); > + goto err_regulator_off; > + } > + > + /* T3: delay from DVDD stable to sensor power up stable */ > + fsleep(5 * USEC_PER_MSEC); The supplies were enabled before the clock so right now there's already 5 ms delay here. Consider with the above comment. > + > + gpiod_set_value_cansleep(os02g10->reset_gpio, 0); > + > + /* T5: delay from sensor power up stable to SCCB initialization */ > + fsleep(5 * USEC_PER_MSEC); > + > + return 0; > + > +err_regulator_off: > + regulator_bulk_disable(ARRAY_SIZE(os02g10_supply_name), os02g10->supplies); A newline here would be nice. > + return ret; > +} > + > +static int os02g10_power_off(struct device *dev) > +{ > + struct v4l2_subdev *sd = dev_get_drvdata(dev); > + struct os02g10 *os02g10 = to_os02g10(sd); > + > + clk_disable_unprepare(os02g10->xclk); > + gpiod_set_value_cansleep(os02g10->reset_gpio, 1); > + regulator_bulk_disable(ARRAY_SIZE(os02g10_supply_name), os02g10->supplies); > + > + return 0; > +} > + > +static int os02g10_identify_module(struct os02g10 *os02g10) > +{ > + u64 chip_id; > + int ret; > + > + ret = cci_read(os02g10->cci, OS02G10_REG_CHIPID, &chip_id, NULL); > + if (ret) > + return dev_err_probe(os02g10->dev, ret, > + "failed to read chip id %x\n", > + OS02G10_CHIPID); > + > + if (chip_id != OS02G10_CHIPID) > + return dev_err_probe(os02g10->dev, -EIO, > + "chip id mismatch: %x!=%llx\n", > + OS02G10_CHIPID, chip_id); > + > + return 0; > +} > + > +static int os02g10_parse_endpoint(struct os02g10 *os02g10) > +{ > + struct v4l2_fwnode_endpoint bus_cfg = { > + .bus_type = V4L2_MBUS_CSI2_DPHY, > + }; > + unsigned long link_freq_bitmap; > + struct fwnode_handle *ep; > + int ret; > + > + ep = fwnode_graph_get_next_endpoint(dev_fwnode(os02g10->dev), NULL); > + 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 != 2) { > + ret = dev_err_probe(os02g10->dev, -EINVAL, > + "only 2 data lanes are supported\n"); > + goto error_out; > + } > + > + ret = v4l2_link_freq_to_bitmap(os02g10->dev, bus_cfg.link_frequencies, > + bus_cfg.nr_of_link_frequencies, > + link_freq_menu_items, > + ARRAY_SIZE(link_freq_menu_items), > + &link_freq_bitmap); > + if (ret) { > + ret = dev_err_probe(os02g10->dev, -EINVAL, > + "only 720MHz frequency is available\n"); > + goto error_out; > + } > + > +error_out: > + v4l2_fwnode_endpoint_free(&bus_cfg); > + > + return ret; > +}; > + > +static const struct regmap_range_cfg os02g10_ranges[] = { > + { > + .range_min = 0x0000, > + .range_max = 0x03ff, > + .selector_reg = 0xfd, > + .selector_mask = 0x03, > + .selector_shift = 0, > + .window_start = 0x00, > + .window_len = 0x100, > + }, > +}; > + > +static const struct regmap_config os02g10_regmap_config = { > + .reg_bits = 8, > + .val_bits = 8, > + .reg_format_endian = REGMAP_ENDIAN_BIG, > + .max_register = 0x3ff, > + .ranges = os02g10_ranges, > + .num_ranges = ARRAY_SIZE(os02g10_ranges), > + .disable_locking = true, > +}; Nice! > + > +static int os02g10_probe(struct i2c_client *client) > +{ > + struct os02g10 *os02g10; > + unsigned int xclk_freq; > + int ret; > + > + os02g10 = devm_kzalloc(&client->dev, sizeof(*os02g10), GFP_KERNEL); > + if (!os02g10) > + return -ENOMEM; > + > + os02g10->dev = &client->dev; > + > + v4l2_i2c_subdev_init(&os02g10->sd, client, &os02g10_subdev_ops); > + os02g10->sd.internal_ops = &os02g10_internal_ops; > + > + /* > + * This is not using devm_cci_regmap_init_i2c(), because the driver > + * makes use of regmap's pagination feature. The chosen settings are > + * compatible with the CCI helpers. > + */ > + os02g10->cci = devm_regmap_init_i2c(client, &os02g10_regmap_config); > + if (IS_ERR(os02g10->cci)) > + return dev_err_probe(os02g10->dev, PTR_ERR(os02g10->cci), > + "failed to initialize CCI\n"); > + > + ret = os02g10_parse_endpoint(os02g10); > + if (ret) > + return dev_err_probe(os02g10->dev, ret, > + "failed to parse endpoint configuration\n"); > + > + /* Get system clock (xvclk) */ > + os02g10->xclk = devm_v4l2_sensor_clk_get(os02g10->dev, NULL); > + if (IS_ERR(os02g10->xclk)) > + return dev_err_probe(os02g10->dev, PTR_ERR(os02g10->xclk), > + "failed to get xclk\n"); > + > + xclk_freq = clk_get_rate(os02g10->xclk); > + if (xclk_freq != OS02G10_XCLK_FREQ) > + return dev_err_probe(os02g10->dev, -EINVAL, > + "xclk frequency not supported: %u Hz\n", > + xclk_freq); > + > + for (unsigned int i = 0; i < ARRAY_SIZE(os02g10_supply_name); i++) > + os02g10->supplies[i].supply = os02g10_supply_name[i]; > + > + ret = devm_regulator_bulk_get(os02g10->dev, > + ARRAY_SIZE(os02g10_supply_name), > + os02g10->supplies); > + if (ret) > + return dev_err_probe(os02g10->dev, ret, > + "failed to get regulators\n"); > + > + os02g10->reset_gpio = devm_gpiod_get_optional(os02g10->dev, > + "reset", GPIOD_OUT_HIGH); > + if (IS_ERR(os02g10->reset_gpio)) > + return dev_err_probe(os02g10->dev, PTR_ERR(os02g10->reset_gpio), > + "failed to get reset GPIO\n"); > + > + ret = os02g10_power_on(os02g10->dev); > + if (ret) > + return ret; > + > + ret = os02g10_identify_module(os02g10); > + if (ret) > + goto error_power_off; > + > + ret = os02g10_init_controls(os02g10); > + if (ret) > + goto error_power_off; > + > + /* Initialize subdev */ > + os02g10->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; > + os02g10->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; > + os02g10->pad.flags = MEDIA_PAD_FL_SOURCE; > + > + ret = media_entity_pads_init(&os02g10->sd.entity, 1, &os02g10->pad); > + if (ret) { > + dev_err_probe(os02g10->dev, ret, "failed to init entity pads\n"); > + goto error_handler_free; > + } > + > + os02g10->sd.state_lock = os02g10->handler.lock; > + ret = v4l2_subdev_init_finalize(&os02g10->sd); > + if (ret) { > + dev_err_probe(os02g10->dev, ret, "subdev init error\n"); > + goto error_media_entity; > + } > + > + pm_runtime_set_active(os02g10->dev); > + pm_runtime_enable(os02g10->dev); > + > + ret = v4l2_async_register_subdev_sensor(&os02g10->sd); > + if (ret) { > + dev_err_probe(os02g10->dev, ret, > + "failed to register os02g10 sub-device\n"); > + goto error_subdev_cleanup; > + } > + > + pm_runtime_idle(os02g10->dev); > + > + return 0; > + > +error_subdev_cleanup: > + v4l2_subdev_cleanup(&os02g10->sd); > + pm_runtime_disable(os02g10->dev); > + pm_runtime_set_suspended(os02g10->dev); > + > +error_media_entity: > + media_entity_cleanup(&os02g10->sd.entity); > + > +error_handler_free: > + v4l2_ctrl_handler_free(os02g10->sd.ctrl_handler); > + > +error_power_off: > + os02g10_power_off(os02g10->dev); > + > + return ret; > +} > + > +static void os02g10_remove(struct i2c_client *client) > +{ > + struct v4l2_subdev *sd = i2c_get_clientdata(client); > + struct os02g10 *os02g10 = to_os02g10(sd); > + > + v4l2_async_unregister_subdev(sd); > + v4l2_subdev_cleanup(&os02g10->sd); > + media_entity_cleanup(&sd->entity); > + v4l2_ctrl_handler_free(os02g10->sd.ctrl_handler); > + > + pm_runtime_disable(&client->dev); > + if (!pm_runtime_status_suspended(&client->dev)) { > + os02g10_power_off(&client->dev); > + pm_runtime_set_suspended(&client->dev); > + } > +} > + > +static DEFINE_RUNTIME_DEV_PM_OPS(os02g10_pm_ops, > + os02g10_power_off, os02g10_power_on, NULL); > + > +static const struct of_device_id os02g10_id[] = { > + { .compatible = "ovti,os02g10" }, > + { /* sentinel */ } > +}; > +MODULE_DEVICE_TABLE(of, os02g10_id); > + > +static struct i2c_driver os02g10_driver = { > + .driver = { > + .name = "os02g10", > + .pm = pm_ptr(&os02g10_pm_ops), > + .of_match_table = os02g10_id, > + }, > + .probe = os02g10_probe, > + .remove = os02g10_remove, > +}; > +module_i2c_driver(os02g10_driver); > + > +MODULE_DESCRIPTION("OS02G10 Camera Sensor Driver"); > +MODULE_AUTHOR("Tarang Raval <tarang.raval@siliconsignals.io>"); > +MODULE_AUTHOR("Elgin Perumbilly <elgin.perumbilly@siliconsignals.io>"); > +MODULE_LICENSE("GPL"); -- Kind regards, Sakari Ailus ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v3 2/3] media: i2c: add os02g10 image sensor driver 2026-06-18 11:43 ` Sakari Ailus @ 2026-06-18 12:27 ` Tarang Raval 2026-06-18 12:58 ` Laurent Pinchart 1 sibling, 0 replies; 27+ messages in thread From: Tarang Raval @ 2026-06-18 12:27 UTC (permalink / raw) To: Sakari Ailus, Elgin Perumbilly Cc: laurent.pinchart@ideasonboard.com, Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Hans Verkuil, Hans de Goede, Vladimir Zapolskiy, Mehdi Djait, Sylvain Petinot, Benjamin Mugnier, Bryan O'Donoghue, Himanshu Bhavani, Heimir Thor Sverrisson, Jingjing Xiong, Svyatoslav Ryhel, linux-media@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org Hi Sakari, Elgin > > +static int os02g10_power_on(struct device *dev) > > +{ > > + struct v4l2_subdev *sd = dev_get_drvdata(dev); > > + struct os02g10 *os02g10 = to_os02g10(sd); > > + int ret; > > + > > + ret = regulator_bulk_enable(ARRAY_SIZE(os02g10_supply_name), > > + os02g10->supplies); > > + if (ret) { > > + dev_err(os02g10->dev, "failed to enable regulators\n"); > > + return ret; > > + } > > + > > + /* T4: delay from DOVDD stable to MCLK on */ > > + fsleep(5 * USEC_PER_MSEC); > > Does the sensor really require this? Typically no delays are required > before lifting xshutdown. The 5 ms delay for T4 (DOVDD stable to ECLK on) is required by the datasheet. However, T3 and T4 are independent timing requirements, not sequential ones. The current implementation introduces an unnecessary extra 5 ms delay before deasserting XSHUTDN. The comment could also be updated to: /* Wait for T3/T4 timing requirements after supplies become stable */ Power-up Sequence: DOVDD ________/───────────────────────────────────────────────────── | |<------ T4 = 5 ms ------>| | | | +------ ECLK ON AVDD ________________/───────────────────────────────────────────── ^ | T1 >= 0 ms DVDD ________________________/───────────────────────────────────── | |<------ T3 = 5 ms ------>| | | | +------ XSHUTDN Release XSHUTDN ____________________________________/───────────────────────── ECLK __________________________________________/\/\/\/\/\/\/\/\/─── SCCB ____________________________________________________XXXXXXXXXX ^ | T5 = 5 ms ------+ > > + > > + ret = clk_prepare_enable(os02g10->xclk); > > + if (ret) { > > + dev_err(os02g10->dev, "failed to enable clock\n"); > > + goto err_regulator_off; > > + } > > + > > + /* T3: delay from DVDD stable to sensor power up stable */ > > + fsleep(5 * USEC_PER_MSEC); > > The supplies were enabled before the clock so right now there's already 5 > ms delay here. Consider with the above comment. > > > + > > + gpiod_set_value_cansleep(os02g10->reset_gpio, 0); > > + > > + /* T5: delay from sensor power up stable to SCCB initialization */ > > + fsleep(5 * USEC_PER_MSEC); > > + > > + return 0; > > + > > +err_regulator_off: > > + regulator_bulk_disable(ARRAY_SIZE(os02g10_supply_name), os02g10->supplies); > > A newline here would be nice. > > > + return ret; > > +} Best Regards, Tarang ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v3 2/3] media: i2c: add os02g10 image sensor driver 2026-06-18 11:43 ` Sakari Ailus 2026-06-18 12:27 ` Tarang Raval @ 2026-06-18 12:58 ` Laurent Pinchart 2026-06-18 13:01 ` Laurent Pinchart 1 sibling, 1 reply; 27+ messages in thread From: Laurent Pinchart @ 2026-06-18 12:58 UTC (permalink / raw) To: Sakari Ailus Cc: Elgin Perumbilly, Tarang Raval, Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Hans Verkuil, Hans de Goede, Vladimir Zapolskiy, Mehdi Djait, Sylvain Petinot, Benjamin Mugnier, Bryan O'Donoghue, Himanshu Bhavani, Heimir Thor Sverrisson, Jingjing Xiong, Svyatoslav Ryhel, linux-media, devicetree, linux-kernel On Thu, Jun 18, 2026 at 02:43:58PM +0300, Sakari Ailus wrote: > Hi Elgin, > > Thanks for the update. A few minor comments below... > > On Fri, Apr 24, 2026 at 02:55:46PM +0530, Elgin Perumbilly wrote: > > Add a v4l2 subdevice driver for the Omnivision os02g10 sensor. > > > > The Omnivision os02g10 is a CMOS image sensor with an active array size of > > 1920 x 1080. > > > > The following features are supported: > > - Manual exposure an gain control support > > - vblank/hblank control support > > - vflip/hflip control support > > - Test pattern control support > > - Supported resolution: 1920 x 1080 @ 30fps (SBGGR10) > > > > Signed-off-by: Elgin Perumbilly <elgin.perumbilly@siliconsignals.io> > > Reviewed-by: Tarang Raval <tarang.raval@siliconsignals.io> > > --- > > MAINTAINERS | 1 + > > drivers/media/i2c/Kconfig | 10 + > > drivers/media/i2c/Makefile | 1 + > > drivers/media/i2c/os02g10.c | 949 ++++++++++++++++++++++++++++++++++++ > > 4 files changed, 961 insertions(+) > > create mode 100644 drivers/media/i2c/os02g10.c > > > > diff --git a/MAINTAINERS b/MAINTAINERS > > index 8a0a55073c30..693e71b51926 100644 > > --- a/MAINTAINERS > > +++ b/MAINTAINERS > > @@ -19449,6 +19449,7 @@ M: Elgin Perumbilly <elgin.perumbilly@siliconsignals.io> > > L: linux-media@vger.kernel.org > > S: Maintained > > F: Documentation/devicetree/bindings/media/i2c/ovti,os02g10.yaml > > +F: drivers/media/i2c/os02g10.c > > > > OMNIVISION OS05B10 SENSOR DRIVER > > M: Himanshu Bhavani <himanshu.bhavani@siliconsignals.io> > > diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig > > index 5eb1e0e0a87a..dd6e9562acf6 100644 > > --- a/drivers/media/i2c/Kconfig > > +++ b/drivers/media/i2c/Kconfig > > @@ -372,6 +372,16 @@ config VIDEO_OG0VE1B > > To compile this driver as a module, choose M here: the > > module will be called og0ve1b. > > > > +config VIDEO_OS02G10 > > + tristate "OmniVision OS02G10 sensor support" > > + select V4L2_CCI_I2C > > + help > > + This is a Video4Linux2 sensor driver for Omnivision > > + OS02G10 camera sensor. > > + > > + To compile this driver as a module, choose M here: the > > + module will be called os02g10. > > + > > config VIDEO_OS05B10 > > tristate "OmniVision OS05B10 sensor support" > > select V4L2_CCI_I2C > > diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile > > index a3a6396df3c4..a7554d2eb140 100644 > > --- a/drivers/media/i2c/Makefile > > +++ b/drivers/media/i2c/Makefile > > @@ -84,6 +84,7 @@ obj-$(CONFIG_VIDEO_MT9V032) += mt9v032.o > > obj-$(CONFIG_VIDEO_MT9V111) += mt9v111.o > > obj-$(CONFIG_VIDEO_OG01A1B) += og01a1b.o > > obj-$(CONFIG_VIDEO_OG0VE1B) += og0ve1b.o > > +obj-$(CONFIG_VIDEO_OS02G10) += os02g10.o > > obj-$(CONFIG_VIDEO_OS05B10) += os05b10.o > > obj-$(CONFIG_VIDEO_OV01A10) += ov01a10.o > > obj-$(CONFIG_VIDEO_OV02A10) += ov02a10.o > > diff --git a/drivers/media/i2c/os02g10.c b/drivers/media/i2c/os02g10.c > > new file mode 100644 > > index 000000000000..fad2dd0ad7aa > > --- /dev/null > > +++ b/drivers/media/i2c/os02g10.c > > @@ -0,0 +1,949 @@ > > +// SPDX-License-Identifier: GPL-2.0 > > +/* > > + * V4L2 Support for the OS02G10 > > + * > > + * Copyright (C) 2026 Silicon Signals Pvt. Ltd. > > + * > > + */ > > + > > +#include <linux/array_size.h> > > +#include <linux/bitops.h> > > +#include <linux/cleanup.h> > > +#include <linux/clk.h> > > +#include <linux/container_of.h> > > +#include <linux/delay.h> > > +#include <linux/err.h> > > +#include <linux/gpio/consumer.h> > > +#include <linux/i2c.h> > > +#include <linux/module.h> > > +#include <linux/mutex.h> > > +#include <linux/pm_runtime.h> > > +#include <linux/property.h> > > +#include <linux/regulator/consumer.h> > > +#include <linux/units.h> > > +#include <linux/types.h> > > +#include <linux/time.h> > > +#include <linux/regmap.h> > > + > > +#include <media/v4l2-cci.h> > > +#include <media/v4l2-ctrls.h> > > +#include <media/v4l2-device.h> > > +#include <media/v4l2-fwnode.h> > > +#include <media/v4l2-mediabus.h> > > + > > +#define OS02G10_XCLK_FREQ (24 * HZ_PER_MHZ) > > + > > +/* Page 0 */ > > +#define OS02G10_REG_CHIPID CCI_REG24(0x002) > > +#define OS02G10_CHIPID 0x560247 > > + > > +#define OS02G10_REG_PLL_DIV_CTRL CCI_REG8(0x030) > > +#define OS02G10_REG_PLL_DCTL_BIAS_CTRL CCI_REG8(0x035) > > +#define OS02G10_REG_GATE_EN_CTRL CCI_REG8(0x038) > > +#define OS02G10_REG_DPLL_NC CCI_REG8(0x041) > > +#define OS02G10_REG_MP_PHASE_CTRL CCI_REG8(0x044) > > + > > +/* Page 1 */ > > +#define OS02G10_REG_STREAM_CTRL CCI_REG8(0x1b1) > > +#define OS02G10_STREAM_CTRL_ON 0x03 > > +#define OS02G10_STREAM_CTRL_OFF 0x00 > > + > > +#define OS02G10_REG_FRAME_SYNC CCI_REG8(0x101) > > + > > +#define OS02G10_REG_FRAME_LENGTH CCI_REG16(0x10e) > > +#define OS02G10_FRAME_LENGTH_MAX 0xffff > > +#define OS02G10_REG_HBLANK CCI_REG16(0x109) > > + > > +#define OS02G10_REG_FRAME_TEST_CTRL CCI_REG8(0x10d) > > +#define OS02G10_FRAME_EXP_SEPERATE_EN BIT(4) > > +#define OS02G10_TEST_PATTERN_ENABLE BIT(0) > > + > > +#define OS02G10_REG_ULP_PWD_DUMMY_CTRL CCI_REG8(0x13c) > > +#define OS02G10_REG_DC_LEVEL_LIMIT_EN CCI_REG8(0x146) > > +#define OS02G10_REG_DC_LEVEL_LIMIT_L CCI_REG8(0x147) > > +#define OS02G10_REG_BLC_DATA_LIMIT_L CCI_REG8(0x148) > > +#define OS02G10_REG_DC_BLC_LIMIT_H CCI_REG8(0x149) > > + > > +#define OS02G10_REG_HS_LP_CTRL CCI_REG8(0x192) > > +#define OS02G10_REG_HS_LEVEL CCI_REG8(0x19d) > > +#define OS02G10_REG_HS_DRV CCI_REG8(0x19e) > > + > > +#define OS02G10_REG_GB_SUBOFFSET CCI_REG8(0x1f0) > > +#define OS02G10_REG_BLUE_SUBOFFSET CCI_REG8(0x1f1) > > +#define OS02G10_REG_RED_SUBOFFSET CCI_REG8(0x1f2) > > +#define OS02G10_REG_GR_SUBOFFSET CCI_REG8(0x1f3) > > + > > +#define OS02G10_REG_ABL_TRIGGER CCI_REG8(0x1fa) > > +#define OS02G10_REG_ABL CCI_REG8(0x1fb) > > + > > +#define OS02G10_REG_H_SIZE_MIPI CCI_REG16(0x18e) > > +#define OS02G10_REG_V_SIZE_MIPI CCI_REG16(0x190) > > +#define OS02G10_REG_MIPI_TX_SPEED_CTRL CCI_REG8(0x1a1) > > + > > +#define OS02G10_REG_LONG_EXPOSURE CCI_REG16(0x103) > > +#define OS02G10_EXPOSURE_MIN 4 > > +#define OS02G10_EXPOSURE_STEP 1 > > +#define OS02G10_EXPOSURE_MARGIN 9 > > + > > +#define OS02G10_REG_ANALOG_GAIN CCI_REG8(0x124) > > +#define OS02G10_ANALOG_GAIN_MIN 0x10 > > +#define OS02G10_ANALOG_GAIN_MAX 0xf8 > > +#define OS02G10_ANALOG_GAIN_STEP 1 > > +#define OS02G10_ANALOG_GAIN_DEFAULT 0x10 > > + > > +#define OS02G10_REG_DIGITAL_GAIN_H CCI_REG8(0x137) > > +#define OS02G10_REG_DIGITAL_GAIN_L CCI_REG8(0x139) > > +#define OS02G10_DIGITAL_GAIN_MIN 0x40 > > +#define OS02G10_DIGITAL_GAIN_MAX 0x800 > > +#define OS02G10_DIGITAL_GAIN_STEP 64 > > +#define OS02G10_DIGITAL_GAIN_DEFAULT 0x40 > > + > > +#define OS02G10_REG_FLIP_MIRROR CCI_REG8(0x13f) > > +#define OS02G10_FLIP BIT(1) > > +#define OS02G10_MIRROR BIT(0) > > + > > +/* Page 2 */ > > +#define OS02G10_REG_V_START CCI_REG16(0x2a0) > > +#define OS02G10_REG_V_SIZE CCI_REG16(0x2a2) > > +#define OS02G10_REG_H_START CCI_REG16(0x2a4) > > +#define OS02G10_REG_H_SIZE CCI_REG16(0x2a6) > > + > > +#define OS02G10_REG_SIF_CTRL CCI_REG8(0x25e) > > +#define OS02G10_ORIENTATION_BAYER_FIX 0x32 > > + > > +#define OS02G10_LINK_FREQ_720MHZ (720 * HZ_PER_MHZ) > > + > > +/* OS02G10 native and active pixel array size */ > > +static const struct v4l2_rect os02g10_native_area = { > > + .top = 0, > > + .left = 0, > > + .width = 1928, > > + .height = 1088, > > +}; > > + > > +static const struct v4l2_rect os02g10_active_area = { > > + .top = 4, > > + .left = 4, > > + .width = 1920, > > + .height = 1080, > > +}; > > + > > +static const char * const os02g10_supply_name[] = { > > + "avdd", /* Analog power */ > > + "dovdd", /* Digital I/O power */ > > + "dvdd", /* Digital core power */ > > +}; > > + > > +struct os02g10 { > > + struct device *dev; > > + struct regmap *cci; > > + struct v4l2_subdev sd; > > + struct media_pad pad; > > + struct clk *xclk; > > + struct gpio_desc *reset_gpio; > > + struct regulator_bulk_data supplies[ARRAY_SIZE(os02g10_supply_name)]; > > + > > + /* V4L2 Controls */ > > + struct v4l2_ctrl_handler handler; > > + struct v4l2_ctrl *link_freq; > > + struct v4l2_ctrl *vblank; > > + struct v4l2_ctrl *exposure; > > + struct v4l2_ctrl *vflip; > > + struct v4l2_ctrl *hflip; > > +}; > > + > > +struct os02g10_mode { > > + u32 width; > > + u32 height; > > + u32 vts_def; > > + u32 exp_def; > > + u32 x_start; > > + u32 y_start; > > +}; > > + > > +static const struct cci_reg_sequence os02g10_common_regs[] = { > > + { OS02G10_REG_PLL_DIV_CTRL, 0x0a}, > > + { OS02G10_REG_PLL_DCTL_BIAS_CTRL, 0x04}, > > + { OS02G10_REG_GATE_EN_CTRL, 0x11}, > > + { OS02G10_REG_DPLL_NC, 0x06}, > > + { OS02G10_REG_MP_PHASE_CTRL, 0x20}, > > + { CCI_REG8(0x119), 0x50}, > > + { CCI_REG8(0x11a), 0x0c}, > > + { CCI_REG8(0x11b), 0x0d}, > > + { CCI_REG8(0x11c), 0x00}, > > + { CCI_REG8(0x11d), 0x75}, > > + { CCI_REG8(0x11e), 0x52}, > > + { CCI_REG8(0x122), 0x14}, > > + { CCI_REG8(0x125), 0x44}, > > + { CCI_REG8(0x126), 0x0f}, > > + { OS02G10_REG_ULP_PWD_DUMMY_CTRL, 0xca}, > > + { CCI_REG8(0x13d), 0x4a}, > > + { CCI_REG8(0x140), 0x0f}, > > + { CCI_REG8(0x143), 0x38}, > > + { OS02G10_REG_DC_LEVEL_LIMIT_EN, 0x01}, > > + { OS02G10_REG_DC_LEVEL_LIMIT_L, 0x00}, > > + { OS02G10_REG_DC_BLC_LIMIT_H, 0x32}, > > + { CCI_REG8(0x150), 0x01}, > > + { CCI_REG8(0x151), 0x28}, > > + { CCI_REG8(0x152), 0x20}, > > + { CCI_REG8(0x153), 0x03}, > > + { CCI_REG8(0x157), 0x16}, > > + { CCI_REG8(0x159), 0x01}, > > + { CCI_REG8(0x15a), 0x01}, > > + { CCI_REG8(0x15d), 0x04}, > > + { CCI_REG8(0x16a), 0x04}, > > + { CCI_REG8(0x16b), 0x03}, > > + { CCI_REG8(0x16e), 0x28}, > > + { CCI_REG8(0x171), 0xc2}, > > + { CCI_REG8(0x172), 0x04}, > > + { CCI_REG8(0x173), 0x38}, > > + { CCI_REG8(0x174), 0x04}, > > + { CCI_REG8(0x179), 0x00}, > > + { CCI_REG8(0x17a), 0xb2}, > > + { CCI_REG8(0x17b), 0x10}, > > + { OS02G10_REG_HS_LP_CTRL, 0x02}, > > + { OS02G10_REG_HS_LEVEL, 0x03}, > > + { OS02G10_REG_HS_DRV, 0x55}, > > + { CCI_REG8(0x1b8), 0x70}, > > + { CCI_REG8(0x1b9), 0x70}, > > + { CCI_REG8(0x1ba), 0x70}, > > + { CCI_REG8(0x1bb), 0x70}, > > + { CCI_REG8(0x1bc), 0x00}, > > + { CCI_REG8(0x1c4), 0x6d}, > > + { CCI_REG8(0x1c5), 0x6d}, > > + { CCI_REG8(0x1c6), 0x6d}, > > + { CCI_REG8(0x1c7), 0x6d}, > > + { CCI_REG8(0x1cc), 0x11}, > > + { CCI_REG8(0x1cd), 0xe0}, > > + { CCI_REG8(0x1d0), 0x1b}, > > + { CCI_REG8(0x1d2), 0x76}, > > + { CCI_REG8(0x1d3), 0x68}, > > + { CCI_REG8(0x1d4), 0x68}, > > + { CCI_REG8(0x1d5), 0x73}, > > + { CCI_REG8(0x1d6), 0x73}, > > + { CCI_REG8(0x1e8), 0x55}, > > + { OS02G10_REG_GB_SUBOFFSET, 0x40}, > > + { OS02G10_REG_BLUE_SUBOFFSET, 0x40}, > > + { OS02G10_REG_RED_SUBOFFSET, 0x40}, > > + { OS02G10_REG_GR_SUBOFFSET, 0x40}, > > + { OS02G10_REG_ABL_TRIGGER, 0x1c}, > > + { OS02G10_REG_ABL, 0x33}, > > + { CCI_REG8(0x1fc), 0x80}, > > + { CCI_REG8(0x1fe), 0x80}, > > + { CCI_REG8(0x303), 0x67}, > > + { CCI_REG8(0x300), 0x59}, > > + { CCI_REG8(0x304), 0x11}, > > + { CCI_REG8(0x305), 0x04}, > > + { CCI_REG8(0x306), 0x0c}, > > + { CCI_REG8(0x307), 0x08}, > > + { CCI_REG8(0x308), 0x08}, > > + { CCI_REG8(0x309), 0x4f}, > > + { CCI_REG8(0x30b), 0x08}, > > + { CCI_REG8(0x30d), 0x26}, > > + { CCI_REG8(0x30f), 0x00}, > > + { CCI_REG8(0x234), 0xfe}, > > + { OS02G10_REG_MIPI_TX_SPEED_CTRL, 0x05}, > > +}; > > + > > +static const struct os02g10_mode supported_modes[] = { > > + { > > + .width = 1920, > > + .height = 1080, > > + .vts_def = 1246, > > + .exp_def = 1100, > > + .x_start = 2, > > + .y_start = 6, > > + }, > > +}; > > + > > +static const s64 link_freq_menu_items[] = { > > + OS02G10_LINK_FREQ_720MHZ, > > +}; > > + > > +static const char * const os02g10_test_pattern_menu[] = { > > + "Disabled", > > + "Colorbar", > > +}; > > + > > +static inline struct os02g10 *to_os02g10(struct v4l2_subdev *sd) > > +{ > > + return container_of_const(sd, struct os02g10, sd); > > +} > > + > > +static u32 os02g10_get_format_code(struct os02g10 *os02g10) > > +{ > > + static const u32 codes[2][2] = { > > + { MEDIA_BUS_FMT_SBGGR10_1X10, MEDIA_BUS_FMT_SGBRG10_1X10, }, > > + { MEDIA_BUS_FMT_SGRBG10_1X10, MEDIA_BUS_FMT_SRGGB10_1X10, }, > > + }; > > + u32 code = codes[os02g10->vflip->val][os02g10->hflip->val]; > > No need for a local variable for this. > > > + > > + return code; > > +} > > + > > +static int os02g10_set_ctrl(struct v4l2_ctrl *ctrl) > > +{ > > + struct os02g10 *os02g10 = container_of_const(ctrl->handler, > > + struct os02g10, handler); > > + struct v4l2_subdev_state *state; > > + struct v4l2_mbus_framefmt *fmt; > > + int ret = 0; > > + > > + state = v4l2_subdev_get_locked_active_state(&os02g10->sd); > > + fmt = v4l2_subdev_state_get_format(state, 0); > > + > > + if (ctrl->id == V4L2_CID_VBLANK) { > > + /* Honour the VBLANK limits when setting exposure */ > > + s64 max = fmt->height + ctrl->val - OS02G10_EXPOSURE_MARGIN; > > + > > + ret = __v4l2_ctrl_modify_range(os02g10->exposure, > > + os02g10->exposure->minimum, max, > > + os02g10->exposure->step, > > + os02g10->exposure->default_value); > > + if (ret) > > + return ret; > > + } > > + > > + if (pm_runtime_get_if_active(os02g10->dev) == 0) > > + return 0; > > + > > + switch (ctrl->id) { > > + case V4L2_CID_EXPOSURE: > > + cci_write(os02g10->cci, OS02G10_REG_LONG_EXPOSURE, > > + ctrl->val, &ret); > > + break; > > + case V4L2_CID_ANALOGUE_GAIN: > > + cci_write(os02g10->cci, OS02G10_REG_ANALOG_GAIN, > > + ctrl->val, &ret); > > + break; > > + case V4L2_CID_DIGITAL_GAIN: > > + cci_write(os02g10->cci, OS02G10_REG_DIGITAL_GAIN_L, > > + (ctrl->val & 0xff), &ret); > > + cci_write(os02g10->cci, OS02G10_REG_DIGITAL_GAIN_H, > > + ((ctrl->val >> 8) & 0x7), &ret); > > + break; > > + case V4L2_CID_VBLANK: > > + u64 vts = ctrl->val + fmt->height; > > + > > + cci_update_bits(os02g10->cci, OS02G10_REG_FRAME_TEST_CTRL, > > + OS02G10_FRAME_EXP_SEPERATE_EN, > > + OS02G10_FRAME_EXP_SEPERATE_EN, &ret); > > + cci_write(os02g10->cci, OS02G10_REG_FRAME_LENGTH, vts, &ret); > > + break; > > + case V4L2_CID_HFLIP: > > + case V4L2_CID_VFLIP: > > + cci_write(os02g10->cci, OS02G10_REG_FLIP_MIRROR, > > + os02g10->hflip->val | os02g10->vflip->val << 1, > > + &ret); > > Fits on previous line. > > > + cci_write(os02g10->cci, OS02G10_REG_SIF_CTRL, > > + OS02G10_ORIENTATION_BAYER_FIX, &ret); > > + break; > > + case V4L2_CID_TEST_PATTERN: > > + cci_update_bits(os02g10->cci, > > + OS02G10_REG_FRAME_TEST_CTRL, > > + OS02G10_TEST_PATTERN_ENABLE, > > + ctrl->val ? OS02G10_TEST_PATTERN_ENABLE : 0, > > + &ret); > > + break; > > + default: > > + ret = -EINVAL; > > + break; > > + } > > + cci_write(os02g10->cci, OS02G10_REG_FRAME_SYNC, 0x01, &ret); > > + > > + pm_runtime_put(os02g10->dev); > > + > > + return ret; > > +} > > + > > +static const struct v4l2_ctrl_ops os02g10_ctrl_ops = { > > + .s_ctrl = os02g10_set_ctrl, > > +}; > > + > > +static int os02g10_init_controls(struct os02g10 *os02g10) > > +{ > > + const struct os02g10_mode *mode = &supported_modes[0]; > > + struct v4l2_fwnode_device_properties props; > > + u64 vblank_def, exp_max, pixel_rate; > > + struct v4l2_ctrl_handler *ctrl_hdlr; > > + int ret; > > + > > + ctrl_hdlr = &os02g10->handler; > > + v4l2_ctrl_handler_init(ctrl_hdlr, 11); > > + > > + /* pixel_rate = link_freq * 2 * nr_of_lanes / bits_per_sample */ > > + pixel_rate = div_u64(OS02G10_LINK_FREQ_720MHZ * 2 * 2, 10); > > + v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, V4L2_CID_PIXEL_RATE, 0, > > + pixel_rate, 1, pixel_rate); > > + > > + os02g10->link_freq = > > + v4l2_ctrl_new_int_menu(ctrl_hdlr, &os02g10_ctrl_ops, > > + V4L2_CID_LINK_FREQ, > > + ARRAY_SIZE(link_freq_menu_items) - 1, > > + 0, link_freq_menu_items); > > + if (os02g10->link_freq) > > + os02g10->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; > > + > > + vblank_def = mode->vts_def - mode->height; > > + os02g10->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, > > + V4L2_CID_VBLANK, vblank_def, > > + OS02G10_FRAME_LENGTH_MAX - mode->height, > > + 1, vblank_def); > > + > > + exp_max = mode->vts_def - OS02G10_EXPOSURE_MARGIN; > > + os02g10->exposure = > > + v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, > > + V4L2_CID_EXPOSURE, > > + OS02G10_EXPOSURE_MIN, exp_max, > > + OS02G10_EXPOSURE_STEP, mode->exp_def); > > + > > + v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, > > + V4L2_CID_ANALOGUE_GAIN, OS02G10_ANALOG_GAIN_MIN, > > + OS02G10_ANALOG_GAIN_MAX, OS02G10_ANALOG_GAIN_STEP, > > + OS02G10_ANALOG_GAIN_DEFAULT); > > + > > + v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, > > + V4L2_CID_DIGITAL_GAIN, OS02G10_DIGITAL_GAIN_MIN, > > + OS02G10_DIGITAL_GAIN_MAX, OS02G10_DIGITAL_GAIN_STEP, > > + OS02G10_DIGITAL_GAIN_DEFAULT); > > + > > + os02g10->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, > > + V4L2_CID_HFLIP, 0, 1, 1, 0); > > + if (os02g10->hflip) > > + os02g10->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; > > + > > + os02g10->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, > > + V4L2_CID_VFLIP, 0, 1, 1, 0); > > + if (os02g10->vflip) > > + os02g10->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; > > There are quite a few flags being modified in the function. > > You can drop the checks if you do this after the control handler's error > check. That's an interesting pattern I never thought of, I'll remember it. > > + > > + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &os02g10_ctrl_ops, > > + V4L2_CID_TEST_PATTERN, > > + ARRAY_SIZE(os02g10_test_pattern_menu) - 1, > > + 0, 0, os02g10_test_pattern_menu); > > + > > + ret = v4l2_fwnode_device_parse(os02g10->dev, &props); > > + if (ret) > > + goto err_handler_free; > > Do this first in the function and error handling is simplified. You can > rely on the handler's error value only, for instance (apart from the above > call). > > > + > > + ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, > > + &os02g10_ctrl_ops, &props); > > + if (ret) > > + goto err_handler_free; > > + > > + os02g10->sd.ctrl_handler = ctrl_hdlr; > > + > > + return 0; > > + > > +err_handler_free: > > + v4l2_ctrl_handler_free(ctrl_hdlr); > > + > > + return ret; > > +} > > + > > +static int os02g10_set_framefmt(struct os02g10 *os02g10, > > + struct v4l2_subdev_state *state) > > +{ > > + const struct v4l2_mbus_framefmt *format; > > + const struct os02g10_mode *mode; > > + int ret = 0; > > + > > + format = v4l2_subdev_state_get_format(state, 0); > > + mode = v4l2_find_nearest_size(supported_modes, > > + ARRAY_SIZE(supported_modes), width, > > + height, format->width, format->height); > > + > > + cci_write(os02g10->cci, OS02G10_REG_V_START, mode->y_start, &ret); > > + cci_write(os02g10->cci, OS02G10_REG_V_SIZE, mode->height, &ret); > > + cci_write(os02g10->cci, OS02G10_REG_V_SIZE_MIPI, mode->height, &ret); > > + cci_write(os02g10->cci, OS02G10_REG_H_START, mode->x_start, &ret); > > + cci_write(os02g10->cci, OS02G10_REG_H_SIZE, mode->width, &ret); > > + cci_write(os02g10->cci, OS02G10_REG_H_SIZE_MIPI, mode->width, &ret); As the sensor supports analog crop, let's implement that and get rid of supported_modes. You can delay implementation of binning. > > + > > + return ret; > > +} > > + > > +static int os02g10_enable_streams(struct v4l2_subdev *sd, > > + struct v4l2_subdev_state *state, u32 pad, > > + u64 streams_mask) > > +{ > > + struct os02g10 *os02g10 = to_os02g10(sd); > > + int ret; > > + > > + ret = pm_runtime_resume_and_get(os02g10->dev); > > + if (ret < 0) > > + return ret; > > + > > + ret = cci_multi_reg_write(os02g10->cci, os02g10_common_regs, > > + ARRAY_SIZE(os02g10_common_regs), NULL); > > + if (ret) { > > + dev_err(os02g10->dev, "failed to write common registers\n"); > > + goto err_rpm_put; > > + } > > + > > + ret = os02g10_set_framefmt(os02g10, state); > > + if (ret) { > > + dev_err(os02g10->dev, "failed to set frame foramt\n"); > > + goto err_rpm_put; > > + } > > + > > + /* Apply customized values from user */ > > + ret = __v4l2_ctrl_handler_setup(os02g10->sd.ctrl_handler); > > + if (ret) > > + goto err_rpm_put; > > + > > + ret = cci_write(os02g10->cci, OS02G10_REG_STREAM_CTRL, > > + OS02G10_STREAM_CTRL_ON, NULL); > > + if (ret) > > + goto err_rpm_put; > > + > > + /* vflip and hflip cannot change during streaming */ > > + __v4l2_ctrl_grab(os02g10->vflip, true); > > + __v4l2_ctrl_grab(os02g10->hflip, true); > > + > > + return 0; > > + > > +err_rpm_put: > > + pm_runtime_put(os02g10->dev); > > + return ret; > > +} > > + > > +static int os02g10_disable_streams(struct v4l2_subdev *sd, > > + struct v4l2_subdev_state *state, u32 pad, > > + u64 streams_mask) > > +{ > > + struct os02g10 *os02g10 = to_os02g10(sd); > > + int ret; > > + > > + ret = cci_write(os02g10->cci, OS02G10_REG_STREAM_CTRL, > > + OS02G10_STREAM_CTRL_OFF, NULL); > > + if (ret) > > + dev_err(os02g10->dev, "Failed to stop stream\n"); > > + > > + __v4l2_ctrl_grab(os02g10->vflip, false); > > + __v4l2_ctrl_grab(os02g10->hflip, false); > > + > > + pm_runtime_put(os02g10->dev); > > + > > + return ret; > > +} > > + > > +static int os02g10_get_selection(struct v4l2_subdev *sd, > > + struct v4l2_subdev_state *sd_state, > > + struct v4l2_subdev_selection *sel) > > +{ > > + switch (sel->target) { > > + case V4L2_SEL_TGT_CROP_BOUNDS: > > + case V4L2_SEL_TGT_NATIVE_SIZE: > > + sel->r = os02g10_native_area; > > + return 0; > > + case V4L2_SEL_TGT_CROP: > > + case V4L2_SEL_TGT_CROP_DEFAULT: > > + sel->r = os02g10_active_area; > > + return 0; > > + default: > > + return -EINVAL; > > + } > > +} > > + > > +static int os02g10_enum_mbus_code(struct v4l2_subdev *sd, > > + struct v4l2_subdev_state *sd_state, > > + struct v4l2_subdev_mbus_code_enum *code) > > +{ > > + struct os02g10 *os02g10 = to_os02g10(sd); > > + > > + if (code->index) > > + return -EINVAL; > > + > > + code->code = os02g10_get_format_code(os02g10); > > + > > + return 0; > > +} > > + > > +static int os02g10_enum_frame_size(struct v4l2_subdev *sd, > > + struct v4l2_subdev_state *sd_state, > > + struct v4l2_subdev_frame_size_enum *fse) > > +{ > > + struct os02g10 *os02g10 = to_os02g10(sd); > > + > > + if (fse->index >= ARRAY_SIZE(supported_modes)) > > + return -EINVAL; > > + > > + if (fse->code != os02g10_get_format_code(os02g10)) > > + 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 int os02g10_set_pad_format(struct v4l2_subdev *sd, > > + struct v4l2_subdev_state *sd_state, > > + struct v4l2_subdev_format *fmt) > > +{ > > + struct os02g10 *os02g10 = to_os02g10(sd); > > + struct v4l2_mbus_framefmt *format; > > + const struct os02g10_mode *mode; > > + > > + format = v4l2_subdev_state_get_format(sd_state, 0); > > + > > + mode = v4l2_find_nearest_size(supported_modes, > > + ARRAY_SIZE(supported_modes), > > + width, height, > > + fmt->format.width, fmt->format.height); > > + > > + fmt->format.code = os02g10_get_format_code(os02g10); > > + fmt->format.width = mode->width; > > + fmt->format.height = mode->height; > > + fmt->format.field = V4L2_FIELD_NONE; > > + fmt->format.colorspace = V4L2_COLORSPACE_RAW; > > + fmt->format.quantization = V4L2_QUANTIZATION_FULL_RANGE; > > + fmt->format.xfer_func = V4L2_XFER_FUNC_NONE; > > + > > + *format = fmt->format; > > + > > + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { > > + u32 vblank_def = mode->vts_def - mode->height; > > + > > + int ret = __v4l2_ctrl_modify_range(os02g10->vblank, vblank_def, > > + OS02G10_FRAME_LENGTH_MAX - > > + mode->height, 1, vblank_def); > > return ...; > > > + if (ret) > > + return ret; > > + } > > + > > + return 0; > > +} > > + > > +static int os02g10_init_state(struct v4l2_subdev *sd, > > + struct v4l2_subdev_state *state) > > +{ > > + struct os02g10 *os02g10 = to_os02g10(sd); > > + struct v4l2_subdev_format fmt = { > > + .which = V4L2_SUBDEV_FORMAT_TRY, > > + .format = { > > + .code = os02g10_get_format_code(os02g10), > > + .width = supported_modes[0].width, > > + .height = supported_modes[0].height, > > + }, > > + }; > > + > > + os02g10_set_pad_format(sd, state, &fmt); > > + > > + return 0; > > +} > > + > > +static const struct v4l2_subdev_video_ops os02g10_video_ops = { > > + .s_stream = v4l2_subdev_s_stream_helper, > > +}; > > + > > +static const struct v4l2_subdev_pad_ops os02g10_pad_ops = { > > + .enum_mbus_code = os02g10_enum_mbus_code, > > + .get_fmt = v4l2_subdev_get_fmt, > > + .set_fmt = os02g10_set_pad_format, > > + .get_selection = os02g10_get_selection, > > + .enum_frame_size = os02g10_enum_frame_size, > > + .enable_streams = os02g10_enable_streams, > > + .disable_streams = os02g10_disable_streams, > > +}; > > + > > +static const struct v4l2_subdev_ops os02g10_subdev_ops = { > > + .video = &os02g10_video_ops, > > + .pad = &os02g10_pad_ops, > > +}; > > + > > +static const struct v4l2_subdev_internal_ops os02g10_internal_ops = { > > + .init_state = os02g10_init_state, > > +}; > > + > > +static int os02g10_power_on(struct device *dev) > > +{ > > + struct v4l2_subdev *sd = dev_get_drvdata(dev); > > + struct os02g10 *os02g10 = to_os02g10(sd); > > + int ret; > > + > > + ret = regulator_bulk_enable(ARRAY_SIZE(os02g10_supply_name), > > + os02g10->supplies); > > + if (ret) { > > + dev_err(os02g10->dev, "failed to enable regulators\n"); > > + return ret; > > + } > > + > > + /* T4: delay from DOVDD stable to MCLK on */ > > + fsleep(5 * USEC_PER_MSEC); > > Does the sensor really require this? Typically no delays are required > before lifting xshutdown. > > > + > > + ret = clk_prepare_enable(os02g10->xclk); > > + if (ret) { > > + dev_err(os02g10->dev, "failed to enable clock\n"); > > + goto err_regulator_off; > > + } > > + > > + /* T3: delay from DVDD stable to sensor power up stable */ > > + fsleep(5 * USEC_PER_MSEC); > > The supplies were enabled before the clock so right now there's already 5 > ms delay here. Consider with the above comment. > > > + > > + gpiod_set_value_cansleep(os02g10->reset_gpio, 0); > > + > > + /* T5: delay from sensor power up stable to SCCB initialization */ > > + fsleep(5 * USEC_PER_MSEC); > > + > > + return 0; > > + > > +err_regulator_off: > > + regulator_bulk_disable(ARRAY_SIZE(os02g10_supply_name), os02g10->supplies); > > A newline here would be nice. > > > + return ret; > > +} > > + > > +static int os02g10_power_off(struct device *dev) > > +{ > > + struct v4l2_subdev *sd = dev_get_drvdata(dev); > > + struct os02g10 *os02g10 = to_os02g10(sd); > > + > > + clk_disable_unprepare(os02g10->xclk); > > + gpiod_set_value_cansleep(os02g10->reset_gpio, 1); > > + regulator_bulk_disable(ARRAY_SIZE(os02g10_supply_name), os02g10->supplies); > > + > > + return 0; > > +} > > + > > +static int os02g10_identify_module(struct os02g10 *os02g10) > > +{ > > + u64 chip_id; > > + int ret; > > + > > + ret = cci_read(os02g10->cci, OS02G10_REG_CHIPID, &chip_id, NULL); > > + if (ret) > > + return dev_err_probe(os02g10->dev, ret, > > + "failed to read chip id %x\n", > > + OS02G10_CHIPID); > > + > > + if (chip_id != OS02G10_CHIPID) > > + return dev_err_probe(os02g10->dev, -EIO, > > + "chip id mismatch: %x!=%llx\n", > > + OS02G10_CHIPID, chip_id); > > + > > + return 0; > > +} > > + > > +static int os02g10_parse_endpoint(struct os02g10 *os02g10) > > +{ > > + struct v4l2_fwnode_endpoint bus_cfg = { > > + .bus_type = V4L2_MBUS_CSI2_DPHY, > > + }; > > + unsigned long link_freq_bitmap; > > + struct fwnode_handle *ep; > > + int ret; > > + > > + ep = fwnode_graph_get_next_endpoint(dev_fwnode(os02g10->dev), NULL); > > + 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 != 2) { > > + ret = dev_err_probe(os02g10->dev, -EINVAL, > > + "only 2 data lanes are supported\n"); > > + goto error_out; > > + } > > + > > + ret = v4l2_link_freq_to_bitmap(os02g10->dev, bus_cfg.link_frequencies, > > + bus_cfg.nr_of_link_frequencies, > > + link_freq_menu_items, > > + ARRAY_SIZE(link_freq_menu_items), > > + &link_freq_bitmap); > > + if (ret) { > > + ret = dev_err_probe(os02g10->dev, -EINVAL, > > + "only 720MHz frequency is available\n"); > > + goto error_out; > > + } > > + > > +error_out: > > + v4l2_fwnode_endpoint_free(&bus_cfg); > > + > > + return ret; > > +}; > > + > > +static const struct regmap_range_cfg os02g10_ranges[] = { > > + { > > + .range_min = 0x0000, > > + .range_max = 0x03ff, > > + .selector_reg = 0xfd, > > + .selector_mask = 0x03, > > + .selector_shift = 0, > > + .window_start = 0x00, > > + .window_len = 0x100, > > + }, > > +}; > > + > > +static const struct regmap_config os02g10_regmap_config = { > > + .reg_bits = 8, > > + .val_bits = 8, > > + .reg_format_endian = REGMAP_ENDIAN_BIG, > > + .max_register = 0x3ff, > > + .ranges = os02g10_ranges, > > + .num_ranges = ARRAY_SIZE(os02g10_ranges), > > + .disable_locking = true, > > +}; > > Nice! > > > + > > +static int os02g10_probe(struct i2c_client *client) > > +{ > > + struct os02g10 *os02g10; > > + unsigned int xclk_freq; > > + int ret; > > + > > + os02g10 = devm_kzalloc(&client->dev, sizeof(*os02g10), GFP_KERNEL); > > + if (!os02g10) > > + return -ENOMEM; > > + > > + os02g10->dev = &client->dev; > > + > > + v4l2_i2c_subdev_init(&os02g10->sd, client, &os02g10_subdev_ops); > > + os02g10->sd.internal_ops = &os02g10_internal_ops; > > + > > + /* > > + * This is not using devm_cci_regmap_init_i2c(), because the driver > > + * makes use of regmap's pagination feature. The chosen settings are > > + * compatible with the CCI helpers. > > + */ > > + os02g10->cci = devm_regmap_init_i2c(client, &os02g10_regmap_config); > > + if (IS_ERR(os02g10->cci)) > > + return dev_err_probe(os02g10->dev, PTR_ERR(os02g10->cci), > > + "failed to initialize CCI\n"); > > + > > + ret = os02g10_parse_endpoint(os02g10); > > + if (ret) > > + return dev_err_probe(os02g10->dev, ret, > > + "failed to parse endpoint configuration\n"); > > + > > + /* Get system clock (xvclk) */ > > + os02g10->xclk = devm_v4l2_sensor_clk_get(os02g10->dev, NULL); > > + if (IS_ERR(os02g10->xclk)) > > + return dev_err_probe(os02g10->dev, PTR_ERR(os02g10->xclk), > > + "failed to get xclk\n"); > > + > > + xclk_freq = clk_get_rate(os02g10->xclk); > > + if (xclk_freq != OS02G10_XCLK_FREQ) > > + return dev_err_probe(os02g10->dev, -EINVAL, > > + "xclk frequency not supported: %u Hz\n", > > + xclk_freq); > > + > > + for (unsigned int i = 0; i < ARRAY_SIZE(os02g10_supply_name); i++) > > + os02g10->supplies[i].supply = os02g10_supply_name[i]; > > + > > + ret = devm_regulator_bulk_get(os02g10->dev, > > + ARRAY_SIZE(os02g10_supply_name), > > + os02g10->supplies); > > + if (ret) > > + return dev_err_probe(os02g10->dev, ret, > > + "failed to get regulators\n"); > > + > > + os02g10->reset_gpio = devm_gpiod_get_optional(os02g10->dev, > > + "reset", GPIOD_OUT_HIGH); > > + if (IS_ERR(os02g10->reset_gpio)) > > + return dev_err_probe(os02g10->dev, PTR_ERR(os02g10->reset_gpio), > > + "failed to get reset GPIO\n"); > > + > > + ret = os02g10_power_on(os02g10->dev); > > + if (ret) > > + return ret; > > + > > + ret = os02g10_identify_module(os02g10); > > + if (ret) > > + goto error_power_off; > > + > > + ret = os02g10_init_controls(os02g10); > > + if (ret) > > + goto error_power_off; > > + > > + /* Initialize subdev */ > > + os02g10->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; > > + os02g10->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; > > + os02g10->pad.flags = MEDIA_PAD_FL_SOURCE; > > + > > + ret = media_entity_pads_init(&os02g10->sd.entity, 1, &os02g10->pad); > > + if (ret) { > > + dev_err_probe(os02g10->dev, ret, "failed to init entity pads\n"); > > + goto error_handler_free; > > + } > > + > > + os02g10->sd.state_lock = os02g10->handler.lock; > > + ret = v4l2_subdev_init_finalize(&os02g10->sd); > > + if (ret) { > > + dev_err_probe(os02g10->dev, ret, "subdev init error\n"); > > + goto error_media_entity; > > + } > > + > > + pm_runtime_set_active(os02g10->dev); > > + pm_runtime_enable(os02g10->dev); > > + > > + ret = v4l2_async_register_subdev_sensor(&os02g10->sd); > > + if (ret) { > > + dev_err_probe(os02g10->dev, ret, > > + "failed to register os02g10 sub-device\n"); > > + goto error_subdev_cleanup; > > + } > > + > > + pm_runtime_idle(os02g10->dev); > > + > > + return 0; > > + > > +error_subdev_cleanup: > > + v4l2_subdev_cleanup(&os02g10->sd); > > + pm_runtime_disable(os02g10->dev); > > + pm_runtime_set_suspended(os02g10->dev); > > + > > +error_media_entity: > > + media_entity_cleanup(&os02g10->sd.entity); > > + > > +error_handler_free: > > + v4l2_ctrl_handler_free(os02g10->sd.ctrl_handler); > > + > > +error_power_off: > > + os02g10_power_off(os02g10->dev); > > + > > + return ret; > > +} > > + > > +static void os02g10_remove(struct i2c_client *client) > > +{ > > + struct v4l2_subdev *sd = i2c_get_clientdata(client); > > + struct os02g10 *os02g10 = to_os02g10(sd); > > + > > + v4l2_async_unregister_subdev(sd); > > + v4l2_subdev_cleanup(&os02g10->sd); > > + media_entity_cleanup(&sd->entity); > > + v4l2_ctrl_handler_free(os02g10->sd.ctrl_handler); > > + > > + pm_runtime_disable(&client->dev); > > + if (!pm_runtime_status_suspended(&client->dev)) { > > + os02g10_power_off(&client->dev); > > + pm_runtime_set_suspended(&client->dev); > > + } > > +} > > + > > +static DEFINE_RUNTIME_DEV_PM_OPS(os02g10_pm_ops, > > + os02g10_power_off, os02g10_power_on, NULL); > > + > > +static const struct of_device_id os02g10_id[] = { > > + { .compatible = "ovti,os02g10" }, > > + { /* sentinel */ } > > +}; > > +MODULE_DEVICE_TABLE(of, os02g10_id); > > + > > +static struct i2c_driver os02g10_driver = { > > + .driver = { > > + .name = "os02g10", > > + .pm = pm_ptr(&os02g10_pm_ops), > > + .of_match_table = os02g10_id, > > + }, > > + .probe = os02g10_probe, > > + .remove = os02g10_remove, > > +}; > > +module_i2c_driver(os02g10_driver); > > + > > +MODULE_DESCRIPTION("OS02G10 Camera Sensor Driver"); > > +MODULE_AUTHOR("Tarang Raval <tarang.raval@siliconsignals.io>"); > > +MODULE_AUTHOR("Elgin Perumbilly <elgin.perumbilly@siliconsignals.io>"); > > +MODULE_LICENSE("GPL"); -- Regards, Laurent Pinchart ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v3 2/3] media: i2c: add os02g10 image sensor driver 2026-06-18 12:58 ` Laurent Pinchart @ 2026-06-18 13:01 ` Laurent Pinchart 0 siblings, 0 replies; 27+ messages in thread From: Laurent Pinchart @ 2026-06-18 13:01 UTC (permalink / raw) To: Sakari Ailus Cc: Elgin Perumbilly, Tarang Raval, Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Hans Verkuil, Hans de Goede, Vladimir Zapolskiy, Mehdi Djait, Sylvain Petinot, Benjamin Mugnier, Bryan O'Donoghue, Himanshu Bhavani, Heimir Thor Sverrisson, Jingjing Xiong, Svyatoslav Ryhel, linux-media, devicetree, linux-kernel On Thu, Jun 18, 2026 at 03:58:42PM +0300, Laurent Pinchart wrote: > On Thu, Jun 18, 2026 at 02:43:58PM +0300, Sakari Ailus wrote: > > Hi Elgin, > > > > Thanks for the update. A few minor comments below... > > > > On Fri, Apr 24, 2026 at 02:55:46PM +0530, Elgin Perumbilly wrote: > > > Add a v4l2 subdevice driver for the Omnivision os02g10 sensor. > > > > > > The Omnivision os02g10 is a CMOS image sensor with an active array size of > > > 1920 x 1080. > > > > > > The following features are supported: > > > - Manual exposure an gain control support > > > - vblank/hblank control support > > > - vflip/hflip control support > > > - Test pattern control support > > > - Supported resolution: 1920 x 1080 @ 30fps (SBGGR10) > > > > > > Signed-off-by: Elgin Perumbilly <elgin.perumbilly@siliconsignals.io> > > > Reviewed-by: Tarang Raval <tarang.raval@siliconsignals.io> > > > --- > > > MAINTAINERS | 1 + > > > drivers/media/i2c/Kconfig | 10 + > > > drivers/media/i2c/Makefile | 1 + > > > drivers/media/i2c/os02g10.c | 949 ++++++++++++++++++++++++++++++++++++ > > > 4 files changed, 961 insertions(+) > > > create mode 100644 drivers/media/i2c/os02g10.c > > > > > > diff --git a/MAINTAINERS b/MAINTAINERS > > > index 8a0a55073c30..693e71b51926 100644 > > > --- a/MAINTAINERS > > > +++ b/MAINTAINERS > > > @@ -19449,6 +19449,7 @@ M: Elgin Perumbilly <elgin.perumbilly@siliconsignals.io> > > > L: linux-media@vger.kernel.org > > > S: Maintained > > > F: Documentation/devicetree/bindings/media/i2c/ovti,os02g10.yaml > > > +F: drivers/media/i2c/os02g10.c > > > > > > OMNIVISION OS05B10 SENSOR DRIVER > > > M: Himanshu Bhavani <himanshu.bhavani@siliconsignals.io> > > > diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig > > > index 5eb1e0e0a87a..dd6e9562acf6 100644 > > > --- a/drivers/media/i2c/Kconfig > > > +++ b/drivers/media/i2c/Kconfig > > > @@ -372,6 +372,16 @@ config VIDEO_OG0VE1B > > > To compile this driver as a module, choose M here: the > > > module will be called og0ve1b. > > > > > > +config VIDEO_OS02G10 > > > + tristate "OmniVision OS02G10 sensor support" > > > + select V4L2_CCI_I2C > > > + help > > > + This is a Video4Linux2 sensor driver for Omnivision > > > + OS02G10 camera sensor. > > > + > > > + To compile this driver as a module, choose M here: the > > > + module will be called os02g10. > > > + > > > config VIDEO_OS05B10 > > > tristate "OmniVision OS05B10 sensor support" > > > select V4L2_CCI_I2C > > > diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile > > > index a3a6396df3c4..a7554d2eb140 100644 > > > --- a/drivers/media/i2c/Makefile > > > +++ b/drivers/media/i2c/Makefile > > > @@ -84,6 +84,7 @@ obj-$(CONFIG_VIDEO_MT9V032) += mt9v032.o > > > obj-$(CONFIG_VIDEO_MT9V111) += mt9v111.o > > > obj-$(CONFIG_VIDEO_OG01A1B) += og01a1b.o > > > obj-$(CONFIG_VIDEO_OG0VE1B) += og0ve1b.o > > > +obj-$(CONFIG_VIDEO_OS02G10) += os02g10.o > > > obj-$(CONFIG_VIDEO_OS05B10) += os05b10.o > > > obj-$(CONFIG_VIDEO_OV01A10) += ov01a10.o > > > obj-$(CONFIG_VIDEO_OV02A10) += ov02a10.o > > > diff --git a/drivers/media/i2c/os02g10.c b/drivers/media/i2c/os02g10.c > > > new file mode 100644 > > > index 000000000000..fad2dd0ad7aa > > > --- /dev/null > > > +++ b/drivers/media/i2c/os02g10.c > > > @@ -0,0 +1,949 @@ > > > +// SPDX-License-Identifier: GPL-2.0 > > > +/* > > > + * V4L2 Support for the OS02G10 > > > + * > > > + * Copyright (C) 2026 Silicon Signals Pvt. Ltd. > > > + * > > > + */ > > > + > > > +#include <linux/array_size.h> > > > +#include <linux/bitops.h> > > > +#include <linux/cleanup.h> > > > +#include <linux/clk.h> > > > +#include <linux/container_of.h> > > > +#include <linux/delay.h> > > > +#include <linux/err.h> > > > +#include <linux/gpio/consumer.h> > > > +#include <linux/i2c.h> > > > +#include <linux/module.h> > > > +#include <linux/mutex.h> > > > +#include <linux/pm_runtime.h> > > > +#include <linux/property.h> > > > +#include <linux/regulator/consumer.h> > > > +#include <linux/units.h> > > > +#include <linux/types.h> > > > +#include <linux/time.h> > > > +#include <linux/regmap.h> > > > + > > > +#include <media/v4l2-cci.h> > > > +#include <media/v4l2-ctrls.h> > > > +#include <media/v4l2-device.h> > > > +#include <media/v4l2-fwnode.h> > > > +#include <media/v4l2-mediabus.h> > > > + > > > +#define OS02G10_XCLK_FREQ (24 * HZ_PER_MHZ) > > > + > > > +/* Page 0 */ > > > +#define OS02G10_REG_CHIPID CCI_REG24(0x002) > > > +#define OS02G10_CHIPID 0x560247 > > > + > > > +#define OS02G10_REG_PLL_DIV_CTRL CCI_REG8(0x030) > > > +#define OS02G10_REG_PLL_DCTL_BIAS_CTRL CCI_REG8(0x035) > > > +#define OS02G10_REG_GATE_EN_CTRL CCI_REG8(0x038) > > > +#define OS02G10_REG_DPLL_NC CCI_REG8(0x041) > > > +#define OS02G10_REG_MP_PHASE_CTRL CCI_REG8(0x044) > > > + > > > +/* Page 1 */ > > > +#define OS02G10_REG_STREAM_CTRL CCI_REG8(0x1b1) > > > +#define OS02G10_STREAM_CTRL_ON 0x03 > > > +#define OS02G10_STREAM_CTRL_OFF 0x00 > > > + > > > +#define OS02G10_REG_FRAME_SYNC CCI_REG8(0x101) > > > + > > > +#define OS02G10_REG_FRAME_LENGTH CCI_REG16(0x10e) > > > +#define OS02G10_FRAME_LENGTH_MAX 0xffff > > > +#define OS02G10_REG_HBLANK CCI_REG16(0x109) > > > + > > > +#define OS02G10_REG_FRAME_TEST_CTRL CCI_REG8(0x10d) > > > +#define OS02G10_FRAME_EXP_SEPERATE_EN BIT(4) > > > +#define OS02G10_TEST_PATTERN_ENABLE BIT(0) > > > + > > > +#define OS02G10_REG_ULP_PWD_DUMMY_CTRL CCI_REG8(0x13c) > > > +#define OS02G10_REG_DC_LEVEL_LIMIT_EN CCI_REG8(0x146) > > > +#define OS02G10_REG_DC_LEVEL_LIMIT_L CCI_REG8(0x147) > > > +#define OS02G10_REG_BLC_DATA_LIMIT_L CCI_REG8(0x148) > > > +#define OS02G10_REG_DC_BLC_LIMIT_H CCI_REG8(0x149) > > > + > > > +#define OS02G10_REG_HS_LP_CTRL CCI_REG8(0x192) > > > +#define OS02G10_REG_HS_LEVEL CCI_REG8(0x19d) > > > +#define OS02G10_REG_HS_DRV CCI_REG8(0x19e) > > > + > > > +#define OS02G10_REG_GB_SUBOFFSET CCI_REG8(0x1f0) > > > +#define OS02G10_REG_BLUE_SUBOFFSET CCI_REG8(0x1f1) > > > +#define OS02G10_REG_RED_SUBOFFSET CCI_REG8(0x1f2) > > > +#define OS02G10_REG_GR_SUBOFFSET CCI_REG8(0x1f3) > > > + > > > +#define OS02G10_REG_ABL_TRIGGER CCI_REG8(0x1fa) > > > +#define OS02G10_REG_ABL CCI_REG8(0x1fb) > > > + > > > +#define OS02G10_REG_H_SIZE_MIPI CCI_REG16(0x18e) > > > +#define OS02G10_REG_V_SIZE_MIPI CCI_REG16(0x190) > > > +#define OS02G10_REG_MIPI_TX_SPEED_CTRL CCI_REG8(0x1a1) > > > + > > > +#define OS02G10_REG_LONG_EXPOSURE CCI_REG16(0x103) > > > +#define OS02G10_EXPOSURE_MIN 4 > > > +#define OS02G10_EXPOSURE_STEP 1 > > > +#define OS02G10_EXPOSURE_MARGIN 9 > > > + > > > +#define OS02G10_REG_ANALOG_GAIN CCI_REG8(0x124) > > > +#define OS02G10_ANALOG_GAIN_MIN 0x10 > > > +#define OS02G10_ANALOG_GAIN_MAX 0xf8 > > > +#define OS02G10_ANALOG_GAIN_STEP 1 > > > +#define OS02G10_ANALOG_GAIN_DEFAULT 0x10 > > > + > > > +#define OS02G10_REG_DIGITAL_GAIN_H CCI_REG8(0x137) > > > +#define OS02G10_REG_DIGITAL_GAIN_L CCI_REG8(0x139) > > > +#define OS02G10_DIGITAL_GAIN_MIN 0x40 > > > +#define OS02G10_DIGITAL_GAIN_MAX 0x800 > > > +#define OS02G10_DIGITAL_GAIN_STEP 64 > > > +#define OS02G10_DIGITAL_GAIN_DEFAULT 0x40 > > > + > > > +#define OS02G10_REG_FLIP_MIRROR CCI_REG8(0x13f) > > > +#define OS02G10_FLIP BIT(1) > > > +#define OS02G10_MIRROR BIT(0) > > > + > > > +/* Page 2 */ > > > +#define OS02G10_REG_V_START CCI_REG16(0x2a0) > > > +#define OS02G10_REG_V_SIZE CCI_REG16(0x2a2) > > > +#define OS02G10_REG_H_START CCI_REG16(0x2a4) > > > +#define OS02G10_REG_H_SIZE CCI_REG16(0x2a6) > > > + > > > +#define OS02G10_REG_SIF_CTRL CCI_REG8(0x25e) > > > +#define OS02G10_ORIENTATION_BAYER_FIX 0x32 > > > + > > > +#define OS02G10_LINK_FREQ_720MHZ (720 * HZ_PER_MHZ) > > > + > > > +/* OS02G10 native and active pixel array size */ > > > +static const struct v4l2_rect os02g10_native_area = { > > > + .top = 0, > > > + .left = 0, > > > + .width = 1928, > > > + .height = 1088, > > > +}; > > > + > > > +static const struct v4l2_rect os02g10_active_area = { > > > + .top = 4, > > > + .left = 4, > > > + .width = 1920, > > > + .height = 1080, > > > +}; > > > + > > > +static const char * const os02g10_supply_name[] = { > > > + "avdd", /* Analog power */ > > > + "dovdd", /* Digital I/O power */ > > > + "dvdd", /* Digital core power */ > > > +}; > > > + > > > +struct os02g10 { > > > + struct device *dev; > > > + struct regmap *cci; > > > + struct v4l2_subdev sd; > > > + struct media_pad pad; > > > + struct clk *xclk; > > > + struct gpio_desc *reset_gpio; > > > + struct regulator_bulk_data supplies[ARRAY_SIZE(os02g10_supply_name)]; > > > + > > > + /* V4L2 Controls */ > > > + struct v4l2_ctrl_handler handler; > > > + struct v4l2_ctrl *link_freq; > > > + struct v4l2_ctrl *vblank; > > > + struct v4l2_ctrl *exposure; > > > + struct v4l2_ctrl *vflip; > > > + struct v4l2_ctrl *hflip; > > > +}; > > > + > > > +struct os02g10_mode { > > > + u32 width; > > > + u32 height; > > > + u32 vts_def; > > > + u32 exp_def; > > > + u32 x_start; > > > + u32 y_start; > > > +}; > > > + > > > +static const struct cci_reg_sequence os02g10_common_regs[] = { > > > + { OS02G10_REG_PLL_DIV_CTRL, 0x0a}, > > > + { OS02G10_REG_PLL_DCTL_BIAS_CTRL, 0x04}, > > > + { OS02G10_REG_GATE_EN_CTRL, 0x11}, > > > + { OS02G10_REG_DPLL_NC, 0x06}, > > > + { OS02G10_REG_MP_PHASE_CTRL, 0x20}, > > > + { CCI_REG8(0x119), 0x50}, > > > + { CCI_REG8(0x11a), 0x0c}, > > > + { CCI_REG8(0x11b), 0x0d}, > > > + { CCI_REG8(0x11c), 0x00}, > > > + { CCI_REG8(0x11d), 0x75}, > > > + { CCI_REG8(0x11e), 0x52}, > > > + { CCI_REG8(0x122), 0x14}, > > > + { CCI_REG8(0x125), 0x44}, > > > + { CCI_REG8(0x126), 0x0f}, > > > + { OS02G10_REG_ULP_PWD_DUMMY_CTRL, 0xca}, > > > + { CCI_REG8(0x13d), 0x4a}, > > > + { CCI_REG8(0x140), 0x0f}, > > > + { CCI_REG8(0x143), 0x38}, > > > + { OS02G10_REG_DC_LEVEL_LIMIT_EN, 0x01}, > > > + { OS02G10_REG_DC_LEVEL_LIMIT_L, 0x00}, > > > + { OS02G10_REG_DC_BLC_LIMIT_H, 0x32}, > > > + { CCI_REG8(0x150), 0x01}, > > > + { CCI_REG8(0x151), 0x28}, > > > + { CCI_REG8(0x152), 0x20}, > > > + { CCI_REG8(0x153), 0x03}, > > > + { CCI_REG8(0x157), 0x16}, > > > + { CCI_REG8(0x159), 0x01}, > > > + { CCI_REG8(0x15a), 0x01}, > > > + { CCI_REG8(0x15d), 0x04}, > > > + { CCI_REG8(0x16a), 0x04}, > > > + { CCI_REG8(0x16b), 0x03}, > > > + { CCI_REG8(0x16e), 0x28}, > > > + { CCI_REG8(0x171), 0xc2}, > > > + { CCI_REG8(0x172), 0x04}, > > > + { CCI_REG8(0x173), 0x38}, > > > + { CCI_REG8(0x174), 0x04}, > > > + { CCI_REG8(0x179), 0x00}, > > > + { CCI_REG8(0x17a), 0xb2}, > > > + { CCI_REG8(0x17b), 0x10}, > > > + { OS02G10_REG_HS_LP_CTRL, 0x02}, > > > + { OS02G10_REG_HS_LEVEL, 0x03}, > > > + { OS02G10_REG_HS_DRV, 0x55}, > > > + { CCI_REG8(0x1b8), 0x70}, > > > + { CCI_REG8(0x1b9), 0x70}, > > > + { CCI_REG8(0x1ba), 0x70}, > > > + { CCI_REG8(0x1bb), 0x70}, > > > + { CCI_REG8(0x1bc), 0x00}, > > > + { CCI_REG8(0x1c4), 0x6d}, > > > + { CCI_REG8(0x1c5), 0x6d}, > > > + { CCI_REG8(0x1c6), 0x6d}, > > > + { CCI_REG8(0x1c7), 0x6d}, > > > + { CCI_REG8(0x1cc), 0x11}, > > > + { CCI_REG8(0x1cd), 0xe0}, > > > + { CCI_REG8(0x1d0), 0x1b}, > > > + { CCI_REG8(0x1d2), 0x76}, > > > + { CCI_REG8(0x1d3), 0x68}, > > > + { CCI_REG8(0x1d4), 0x68}, > > > + { CCI_REG8(0x1d5), 0x73}, > > > + { CCI_REG8(0x1d6), 0x73}, > > > + { CCI_REG8(0x1e8), 0x55}, > > > + { OS02G10_REG_GB_SUBOFFSET, 0x40}, > > > + { OS02G10_REG_BLUE_SUBOFFSET, 0x40}, > > > + { OS02G10_REG_RED_SUBOFFSET, 0x40}, > > > + { OS02G10_REG_GR_SUBOFFSET, 0x40}, > > > + { OS02G10_REG_ABL_TRIGGER, 0x1c}, > > > + { OS02G10_REG_ABL, 0x33}, > > > + { CCI_REG8(0x1fc), 0x80}, > > > + { CCI_REG8(0x1fe), 0x80}, > > > + { CCI_REG8(0x303), 0x67}, > > > + { CCI_REG8(0x300), 0x59}, > > > + { CCI_REG8(0x304), 0x11}, > > > + { CCI_REG8(0x305), 0x04}, > > > + { CCI_REG8(0x306), 0x0c}, > > > + { CCI_REG8(0x307), 0x08}, > > > + { CCI_REG8(0x308), 0x08}, > > > + { CCI_REG8(0x309), 0x4f}, > > > + { CCI_REG8(0x30b), 0x08}, > > > + { CCI_REG8(0x30d), 0x26}, > > > + { CCI_REG8(0x30f), 0x00}, > > > + { CCI_REG8(0x234), 0xfe}, > > > + { OS02G10_REG_MIPI_TX_SPEED_CTRL, 0x05}, > > > +}; > > > + > > > +static const struct os02g10_mode supported_modes[] = { > > > + { > > > + .width = 1920, > > > + .height = 1080, > > > + .vts_def = 1246, > > > + .exp_def = 1100, > > > + .x_start = 2, > > > + .y_start = 6, > > > + }, > > > +}; > > > + > > > +static const s64 link_freq_menu_items[] = { > > > + OS02G10_LINK_FREQ_720MHZ, > > > +}; > > > + > > > +static const char * const os02g10_test_pattern_menu[] = { > > > + "Disabled", > > > + "Colorbar", > > > +}; > > > + > > > +static inline struct os02g10 *to_os02g10(struct v4l2_subdev *sd) > > > +{ > > > + return container_of_const(sd, struct os02g10, sd); > > > +} > > > + > > > +static u32 os02g10_get_format_code(struct os02g10 *os02g10) > > > +{ > > > + static const u32 codes[2][2] = { > > > + { MEDIA_BUS_FMT_SBGGR10_1X10, MEDIA_BUS_FMT_SGBRG10_1X10, }, > > > + { MEDIA_BUS_FMT_SGRBG10_1X10, MEDIA_BUS_FMT_SRGGB10_1X10, }, > > > + }; > > > + u32 code = codes[os02g10->vflip->val][os02g10->hflip->val]; > > > > No need for a local variable for this. > > > > > + > > > + return code; > > > +} > > > + > > > +static int os02g10_set_ctrl(struct v4l2_ctrl *ctrl) > > > +{ > > > + struct os02g10 *os02g10 = container_of_const(ctrl->handler, > > > + struct os02g10, handler); > > > + struct v4l2_subdev_state *state; > > > + struct v4l2_mbus_framefmt *fmt; > > > + int ret = 0; > > > + > > > + state = v4l2_subdev_get_locked_active_state(&os02g10->sd); > > > + fmt = v4l2_subdev_state_get_format(state, 0); > > > + > > > + if (ctrl->id == V4L2_CID_VBLANK) { > > > + /* Honour the VBLANK limits when setting exposure */ > > > + s64 max = fmt->height + ctrl->val - OS02G10_EXPOSURE_MARGIN; > > > + > > > + ret = __v4l2_ctrl_modify_range(os02g10->exposure, > > > + os02g10->exposure->minimum, max, > > > + os02g10->exposure->step, > > > + os02g10->exposure->default_value); > > > + if (ret) > > > + return ret; > > > + } > > > + > > > + if (pm_runtime_get_if_active(os02g10->dev) == 0) > > > + return 0; > > > + > > > + switch (ctrl->id) { > > > + case V4L2_CID_EXPOSURE: > > > + cci_write(os02g10->cci, OS02G10_REG_LONG_EXPOSURE, > > > + ctrl->val, &ret); > > > + break; > > > + case V4L2_CID_ANALOGUE_GAIN: > > > + cci_write(os02g10->cci, OS02G10_REG_ANALOG_GAIN, > > > + ctrl->val, &ret); > > > + break; > > > + case V4L2_CID_DIGITAL_GAIN: > > > + cci_write(os02g10->cci, OS02G10_REG_DIGITAL_GAIN_L, > > > + (ctrl->val & 0xff), &ret); > > > + cci_write(os02g10->cci, OS02G10_REG_DIGITAL_GAIN_H, > > > + ((ctrl->val >> 8) & 0x7), &ret); > > > + break; > > > + case V4L2_CID_VBLANK: > > > + u64 vts = ctrl->val + fmt->height; > > > + > > > + cci_update_bits(os02g10->cci, OS02G10_REG_FRAME_TEST_CTRL, > > > + OS02G10_FRAME_EXP_SEPERATE_EN, > > > + OS02G10_FRAME_EXP_SEPERATE_EN, &ret); > > > + cci_write(os02g10->cci, OS02G10_REG_FRAME_LENGTH, vts, &ret); > > > + break; > > > + case V4L2_CID_HFLIP: > > > + case V4L2_CID_VFLIP: > > > + cci_write(os02g10->cci, OS02G10_REG_FLIP_MIRROR, > > > + os02g10->hflip->val | os02g10->vflip->val << 1, > > > + &ret); > > > > Fits on previous line. > > > > > + cci_write(os02g10->cci, OS02G10_REG_SIF_CTRL, > > > + OS02G10_ORIENTATION_BAYER_FIX, &ret); > > > + break; > > > + case V4L2_CID_TEST_PATTERN: > > > + cci_update_bits(os02g10->cci, > > > + OS02G10_REG_FRAME_TEST_CTRL, > > > + OS02G10_TEST_PATTERN_ENABLE, > > > + ctrl->val ? OS02G10_TEST_PATTERN_ENABLE : 0, > > > + &ret); > > > + break; > > > + default: > > > + ret = -EINVAL; > > > + break; > > > + } > > > + cci_write(os02g10->cci, OS02G10_REG_FRAME_SYNC, 0x01, &ret); > > > + > > > + pm_runtime_put(os02g10->dev); > > > + > > > + return ret; > > > +} > > > + > > > +static const struct v4l2_ctrl_ops os02g10_ctrl_ops = { > > > + .s_ctrl = os02g10_set_ctrl, > > > +}; > > > + > > > +static int os02g10_init_controls(struct os02g10 *os02g10) > > > +{ > > > + const struct os02g10_mode *mode = &supported_modes[0]; > > > + struct v4l2_fwnode_device_properties props; > > > + u64 vblank_def, exp_max, pixel_rate; > > > + struct v4l2_ctrl_handler *ctrl_hdlr; > > > + int ret; > > > + > > > + ctrl_hdlr = &os02g10->handler; > > > + v4l2_ctrl_handler_init(ctrl_hdlr, 11); > > > + > > > + /* pixel_rate = link_freq * 2 * nr_of_lanes / bits_per_sample */ > > > + pixel_rate = div_u64(OS02G10_LINK_FREQ_720MHZ * 2 * 2, 10); > > > + v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, V4L2_CID_PIXEL_RATE, 0, > > > + pixel_rate, 1, pixel_rate); > > > + > > > + os02g10->link_freq = > > > + v4l2_ctrl_new_int_menu(ctrl_hdlr, &os02g10_ctrl_ops, > > > + V4L2_CID_LINK_FREQ, > > > + ARRAY_SIZE(link_freq_menu_items) - 1, > > > + 0, link_freq_menu_items); > > > + if (os02g10->link_freq) > > > + os02g10->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; > > > + > > > + vblank_def = mode->vts_def - mode->height; > > > + os02g10->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, > > > + V4L2_CID_VBLANK, vblank_def, > > > + OS02G10_FRAME_LENGTH_MAX - mode->height, > > > + 1, vblank_def); > > > + > > > + exp_max = mode->vts_def - OS02G10_EXPOSURE_MARGIN; > > > + os02g10->exposure = > > > + v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, > > > + V4L2_CID_EXPOSURE, > > > + OS02G10_EXPOSURE_MIN, exp_max, > > > + OS02G10_EXPOSURE_STEP, mode->exp_def); > > > + > > > + v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, > > > + V4L2_CID_ANALOGUE_GAIN, OS02G10_ANALOG_GAIN_MIN, > > > + OS02G10_ANALOG_GAIN_MAX, OS02G10_ANALOG_GAIN_STEP, > > > + OS02G10_ANALOG_GAIN_DEFAULT); > > > + > > > + v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, > > > + V4L2_CID_DIGITAL_GAIN, OS02G10_DIGITAL_GAIN_MIN, > > > + OS02G10_DIGITAL_GAIN_MAX, OS02G10_DIGITAL_GAIN_STEP, > > > + OS02G10_DIGITAL_GAIN_DEFAULT); > > > + > > > + os02g10->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, > > > + V4L2_CID_HFLIP, 0, 1, 1, 0); > > > + if (os02g10->hflip) > > > + os02g10->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; > > > + > > > + os02g10->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, > > > + V4L2_CID_VFLIP, 0, 1, 1, 0); > > > + if (os02g10->vflip) > > > + os02g10->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; > > > > There are quite a few flags being modified in the function. > > > > You can drop the checks if you do this after the control handler's error > > check. > > That's an interesting pattern I never thought of, I'll remember it. > > > > + > > > + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &os02g10_ctrl_ops, > > > + V4L2_CID_TEST_PATTERN, > > > + ARRAY_SIZE(os02g10_test_pattern_menu) - 1, > > > + 0, 0, os02g10_test_pattern_menu); > > > + > > > + ret = v4l2_fwnode_device_parse(os02g10->dev, &props); > > > + if (ret) > > > + goto err_handler_free; > > > > Do this first in the function and error handling is simplified. You can > > rely on the handler's error value only, for instance (apart from the above > > call). > > > > > + > > > + ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, > > > + &os02g10_ctrl_ops, &props); > > > + if (ret) > > > + goto err_handler_free; > > > + > > > + os02g10->sd.ctrl_handler = ctrl_hdlr; > > > + > > > + return 0; > > > + > > > +err_handler_free: > > > + v4l2_ctrl_handler_free(ctrl_hdlr); > > > + > > > + return ret; > > > +} > > > + > > > +static int os02g10_set_framefmt(struct os02g10 *os02g10, > > > + struct v4l2_subdev_state *state) > > > +{ > > > + const struct v4l2_mbus_framefmt *format; > > > + const struct os02g10_mode *mode; > > > + int ret = 0; > > > + > > > + format = v4l2_subdev_state_get_format(state, 0); > > > + mode = v4l2_find_nearest_size(supported_modes, > > > + ARRAY_SIZE(supported_modes), width, > > > + height, format->width, format->height); > > > + > > > + cci_write(os02g10->cci, OS02G10_REG_V_START, mode->y_start, &ret); > > > + cci_write(os02g10->cci, OS02G10_REG_V_SIZE, mode->height, &ret); > > > + cci_write(os02g10->cci, OS02G10_REG_V_SIZE_MIPI, mode->height, &ret); > > > + cci_write(os02g10->cci, OS02G10_REG_H_START, mode->x_start, &ret); > > > + cci_write(os02g10->cci, OS02G10_REG_H_SIZE, mode->width, &ret); > > > + cci_write(os02g10->cci, OS02G10_REG_H_SIZE_MIPI, mode->width, &ret); > > As the sensor supports analog crop, let's implement that and get rid of > supported_modes. You can delay implementation of binning. My bad, I replied without noticing patch 3/3. I think 3/3 should be squashed with this patch, it's hard to review and assess 2/3 given that parts of it are then changed. > > > + > > > + return ret; > > > +} > > > + > > > +static int os02g10_enable_streams(struct v4l2_subdev *sd, > > > + struct v4l2_subdev_state *state, u32 pad, > > > + u64 streams_mask) > > > +{ > > > + struct os02g10 *os02g10 = to_os02g10(sd); > > > + int ret; > > > + > > > + ret = pm_runtime_resume_and_get(os02g10->dev); > > > + if (ret < 0) > > > + return ret; > > > + > > > + ret = cci_multi_reg_write(os02g10->cci, os02g10_common_regs, > > > + ARRAY_SIZE(os02g10_common_regs), NULL); > > > + if (ret) { > > > + dev_err(os02g10->dev, "failed to write common registers\n"); > > > + goto err_rpm_put; > > > + } > > > + > > > + ret = os02g10_set_framefmt(os02g10, state); > > > + if (ret) { > > > + dev_err(os02g10->dev, "failed to set frame foramt\n"); > > > + goto err_rpm_put; > > > + } > > > + > > > + /* Apply customized values from user */ > > > + ret = __v4l2_ctrl_handler_setup(os02g10->sd.ctrl_handler); > > > + if (ret) > > > + goto err_rpm_put; > > > + > > > + ret = cci_write(os02g10->cci, OS02G10_REG_STREAM_CTRL, > > > + OS02G10_STREAM_CTRL_ON, NULL); > > > + if (ret) > > > + goto err_rpm_put; > > > + > > > + /* vflip and hflip cannot change during streaming */ > > > + __v4l2_ctrl_grab(os02g10->vflip, true); > > > + __v4l2_ctrl_grab(os02g10->hflip, true); > > > + > > > + return 0; > > > + > > > +err_rpm_put: > > > + pm_runtime_put(os02g10->dev); > > > + return ret; > > > +} > > > + > > > +static int os02g10_disable_streams(struct v4l2_subdev *sd, > > > + struct v4l2_subdev_state *state, u32 pad, > > > + u64 streams_mask) > > > +{ > > > + struct os02g10 *os02g10 = to_os02g10(sd); > > > + int ret; > > > + > > > + ret = cci_write(os02g10->cci, OS02G10_REG_STREAM_CTRL, > > > + OS02G10_STREAM_CTRL_OFF, NULL); > > > + if (ret) > > > + dev_err(os02g10->dev, "Failed to stop stream\n"); > > > + > > > + __v4l2_ctrl_grab(os02g10->vflip, false); > > > + __v4l2_ctrl_grab(os02g10->hflip, false); > > > + > > > + pm_runtime_put(os02g10->dev); > > > + > > > + return ret; > > > +} > > > + > > > +static int os02g10_get_selection(struct v4l2_subdev *sd, > > > + struct v4l2_subdev_state *sd_state, > > > + struct v4l2_subdev_selection *sel) > > > +{ > > > + switch (sel->target) { > > > + case V4L2_SEL_TGT_CROP_BOUNDS: > > > + case V4L2_SEL_TGT_NATIVE_SIZE: > > > + sel->r = os02g10_native_area; > > > + return 0; > > > + case V4L2_SEL_TGT_CROP: > > > + case V4L2_SEL_TGT_CROP_DEFAULT: > > > + sel->r = os02g10_active_area; > > > + return 0; > > > + default: > > > + return -EINVAL; > > > + } > > > +} > > > + > > > +static int os02g10_enum_mbus_code(struct v4l2_subdev *sd, > > > + struct v4l2_subdev_state *sd_state, > > > + struct v4l2_subdev_mbus_code_enum *code) > > > +{ > > > + struct os02g10 *os02g10 = to_os02g10(sd); > > > + > > > + if (code->index) > > > + return -EINVAL; > > > + > > > + code->code = os02g10_get_format_code(os02g10); > > > + > > > + return 0; > > > +} > > > + > > > +static int os02g10_enum_frame_size(struct v4l2_subdev *sd, > > > + struct v4l2_subdev_state *sd_state, > > > + struct v4l2_subdev_frame_size_enum *fse) > > > +{ > > > + struct os02g10 *os02g10 = to_os02g10(sd); > > > + > > > + if (fse->index >= ARRAY_SIZE(supported_modes)) > > > + return -EINVAL; > > > + > > > + if (fse->code != os02g10_get_format_code(os02g10)) > > > + 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 int os02g10_set_pad_format(struct v4l2_subdev *sd, > > > + struct v4l2_subdev_state *sd_state, > > > + struct v4l2_subdev_format *fmt) > > > +{ > > > + struct os02g10 *os02g10 = to_os02g10(sd); > > > + struct v4l2_mbus_framefmt *format; > > > + const struct os02g10_mode *mode; > > > + > > > + format = v4l2_subdev_state_get_format(sd_state, 0); > > > + > > > + mode = v4l2_find_nearest_size(supported_modes, > > > + ARRAY_SIZE(supported_modes), > > > + width, height, > > > + fmt->format.width, fmt->format.height); > > > + > > > + fmt->format.code = os02g10_get_format_code(os02g10); > > > + fmt->format.width = mode->width; > > > + fmt->format.height = mode->height; > > > + fmt->format.field = V4L2_FIELD_NONE; > > > + fmt->format.colorspace = V4L2_COLORSPACE_RAW; > > > + fmt->format.quantization = V4L2_QUANTIZATION_FULL_RANGE; > > > + fmt->format.xfer_func = V4L2_XFER_FUNC_NONE; > > > + > > > + *format = fmt->format; > > > + > > > + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { > > > + u32 vblank_def = mode->vts_def - mode->height; > > > + > > > + int ret = __v4l2_ctrl_modify_range(os02g10->vblank, vblank_def, > > > + OS02G10_FRAME_LENGTH_MAX - > > > + mode->height, 1, vblank_def); > > > > return ...; > > > > > + if (ret) > > > + return ret; > > > + } > > > + > > > + return 0; > > > +} > > > + > > > +static int os02g10_init_state(struct v4l2_subdev *sd, > > > + struct v4l2_subdev_state *state) > > > +{ > > > + struct os02g10 *os02g10 = to_os02g10(sd); > > > + struct v4l2_subdev_format fmt = { > > > + .which = V4L2_SUBDEV_FORMAT_TRY, > > > + .format = { > > > + .code = os02g10_get_format_code(os02g10), > > > + .width = supported_modes[0].width, > > > + .height = supported_modes[0].height, > > > + }, > > > + }; > > > + > > > + os02g10_set_pad_format(sd, state, &fmt); > > > + > > > + return 0; > > > +} > > > + > > > +static const struct v4l2_subdev_video_ops os02g10_video_ops = { > > > + .s_stream = v4l2_subdev_s_stream_helper, > > > +}; > > > + > > > +static const struct v4l2_subdev_pad_ops os02g10_pad_ops = { > > > + .enum_mbus_code = os02g10_enum_mbus_code, > > > + .get_fmt = v4l2_subdev_get_fmt, > > > + .set_fmt = os02g10_set_pad_format, > > > + .get_selection = os02g10_get_selection, > > > + .enum_frame_size = os02g10_enum_frame_size, > > > + .enable_streams = os02g10_enable_streams, > > > + .disable_streams = os02g10_disable_streams, > > > +}; > > > + > > > +static const struct v4l2_subdev_ops os02g10_subdev_ops = { > > > + .video = &os02g10_video_ops, > > > + .pad = &os02g10_pad_ops, > > > +}; > > > + > > > +static const struct v4l2_subdev_internal_ops os02g10_internal_ops = { > > > + .init_state = os02g10_init_state, > > > +}; > > > + > > > +static int os02g10_power_on(struct device *dev) > > > +{ > > > + struct v4l2_subdev *sd = dev_get_drvdata(dev); > > > + struct os02g10 *os02g10 = to_os02g10(sd); > > > + int ret; > > > + > > > + ret = regulator_bulk_enable(ARRAY_SIZE(os02g10_supply_name), > > > + os02g10->supplies); > > > + if (ret) { > > > + dev_err(os02g10->dev, "failed to enable regulators\n"); > > > + return ret; > > > + } > > > + > > > + /* T4: delay from DOVDD stable to MCLK on */ > > > + fsleep(5 * USEC_PER_MSEC); > > > > Does the sensor really require this? Typically no delays are required > > before lifting xshutdown. > > > > > + > > > + ret = clk_prepare_enable(os02g10->xclk); > > > + if (ret) { > > > + dev_err(os02g10->dev, "failed to enable clock\n"); > > > + goto err_regulator_off; > > > + } > > > + > > > + /* T3: delay from DVDD stable to sensor power up stable */ > > > + fsleep(5 * USEC_PER_MSEC); > > > > The supplies were enabled before the clock so right now there's already 5 > > ms delay here. Consider with the above comment. > > > > > + > > > + gpiod_set_value_cansleep(os02g10->reset_gpio, 0); > > > + > > > + /* T5: delay from sensor power up stable to SCCB initialization */ > > > + fsleep(5 * USEC_PER_MSEC); > > > + > > > + return 0; > > > + > > > +err_regulator_off: > > > + regulator_bulk_disable(ARRAY_SIZE(os02g10_supply_name), os02g10->supplies); > > > > A newline here would be nice. > > > > > + return ret; > > > +} > > > + > > > +static int os02g10_power_off(struct device *dev) > > > +{ > > > + struct v4l2_subdev *sd = dev_get_drvdata(dev); > > > + struct os02g10 *os02g10 = to_os02g10(sd); > > > + > > > + clk_disable_unprepare(os02g10->xclk); > > > + gpiod_set_value_cansleep(os02g10->reset_gpio, 1); > > > + regulator_bulk_disable(ARRAY_SIZE(os02g10_supply_name), os02g10->supplies); > > > + > > > + return 0; > > > +} > > > + > > > +static int os02g10_identify_module(struct os02g10 *os02g10) > > > +{ > > > + u64 chip_id; > > > + int ret; > > > + > > > + ret = cci_read(os02g10->cci, OS02G10_REG_CHIPID, &chip_id, NULL); > > > + if (ret) > > > + return dev_err_probe(os02g10->dev, ret, > > > + "failed to read chip id %x\n", > > > + OS02G10_CHIPID); > > > + > > > + if (chip_id != OS02G10_CHIPID) > > > + return dev_err_probe(os02g10->dev, -EIO, > > > + "chip id mismatch: %x!=%llx\n", > > > + OS02G10_CHIPID, chip_id); > > > + > > > + return 0; > > > +} > > > + > > > +static int os02g10_parse_endpoint(struct os02g10 *os02g10) > > > +{ > > > + struct v4l2_fwnode_endpoint bus_cfg = { > > > + .bus_type = V4L2_MBUS_CSI2_DPHY, > > > + }; > > > + unsigned long link_freq_bitmap; > > > + struct fwnode_handle *ep; > > > + int ret; > > > + > > > + ep = fwnode_graph_get_next_endpoint(dev_fwnode(os02g10->dev), NULL); > > > + 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 != 2) { > > > + ret = dev_err_probe(os02g10->dev, -EINVAL, > > > + "only 2 data lanes are supported\n"); > > > + goto error_out; > > > + } > > > + > > > + ret = v4l2_link_freq_to_bitmap(os02g10->dev, bus_cfg.link_frequencies, > > > + bus_cfg.nr_of_link_frequencies, > > > + link_freq_menu_items, > > > + ARRAY_SIZE(link_freq_menu_items), > > > + &link_freq_bitmap); > > > + if (ret) { > > > + ret = dev_err_probe(os02g10->dev, -EINVAL, > > > + "only 720MHz frequency is available\n"); > > > + goto error_out; > > > + } > > > + > > > +error_out: > > > + v4l2_fwnode_endpoint_free(&bus_cfg); > > > + > > > + return ret; > > > +}; > > > + > > > +static const struct regmap_range_cfg os02g10_ranges[] = { > > > + { > > > + .range_min = 0x0000, > > > + .range_max = 0x03ff, > > > + .selector_reg = 0xfd, > > > + .selector_mask = 0x03, > > > + .selector_shift = 0, > > > + .window_start = 0x00, > > > + .window_len = 0x100, > > > + }, > > > +}; > > > + > > > +static const struct regmap_config os02g10_regmap_config = { > > > + .reg_bits = 8, > > > + .val_bits = 8, > > > + .reg_format_endian = REGMAP_ENDIAN_BIG, > > > + .max_register = 0x3ff, > > > + .ranges = os02g10_ranges, > > > + .num_ranges = ARRAY_SIZE(os02g10_ranges), > > > + .disable_locking = true, > > > +}; > > > > Nice! > > > > > + > > > +static int os02g10_probe(struct i2c_client *client) > > > +{ > > > + struct os02g10 *os02g10; > > > + unsigned int xclk_freq; > > > + int ret; > > > + > > > + os02g10 = devm_kzalloc(&client->dev, sizeof(*os02g10), GFP_KERNEL); > > > + if (!os02g10) > > > + return -ENOMEM; > > > + > > > + os02g10->dev = &client->dev; > > > + > > > + v4l2_i2c_subdev_init(&os02g10->sd, client, &os02g10_subdev_ops); > > > + os02g10->sd.internal_ops = &os02g10_internal_ops; > > > + > > > + /* > > > + * This is not using devm_cci_regmap_init_i2c(), because the driver > > > + * makes use of regmap's pagination feature. The chosen settings are > > > + * compatible with the CCI helpers. > > > + */ > > > + os02g10->cci = devm_regmap_init_i2c(client, &os02g10_regmap_config); > > > + if (IS_ERR(os02g10->cci)) > > > + return dev_err_probe(os02g10->dev, PTR_ERR(os02g10->cci), > > > + "failed to initialize CCI\n"); > > > + > > > + ret = os02g10_parse_endpoint(os02g10); > > > + if (ret) > > > + return dev_err_probe(os02g10->dev, ret, > > > + "failed to parse endpoint configuration\n"); > > > + > > > + /* Get system clock (xvclk) */ > > > + os02g10->xclk = devm_v4l2_sensor_clk_get(os02g10->dev, NULL); > > > + if (IS_ERR(os02g10->xclk)) > > > + return dev_err_probe(os02g10->dev, PTR_ERR(os02g10->xclk), > > > + "failed to get xclk\n"); > > > + > > > + xclk_freq = clk_get_rate(os02g10->xclk); > > > + if (xclk_freq != OS02G10_XCLK_FREQ) > > > + return dev_err_probe(os02g10->dev, -EINVAL, > > > + "xclk frequency not supported: %u Hz\n", > > > + xclk_freq); > > > + > > > + for (unsigned int i = 0; i < ARRAY_SIZE(os02g10_supply_name); i++) > > > + os02g10->supplies[i].supply = os02g10_supply_name[i]; > > > + > > > + ret = devm_regulator_bulk_get(os02g10->dev, > > > + ARRAY_SIZE(os02g10_supply_name), > > > + os02g10->supplies); > > > + if (ret) > > > + return dev_err_probe(os02g10->dev, ret, > > > + "failed to get regulators\n"); > > > + > > > + os02g10->reset_gpio = devm_gpiod_get_optional(os02g10->dev, > > > + "reset", GPIOD_OUT_HIGH); > > > + if (IS_ERR(os02g10->reset_gpio)) > > > + return dev_err_probe(os02g10->dev, PTR_ERR(os02g10->reset_gpio), > > > + "failed to get reset GPIO\n"); > > > + > > > + ret = os02g10_power_on(os02g10->dev); > > > + if (ret) > > > + return ret; > > > + > > > + ret = os02g10_identify_module(os02g10); > > > + if (ret) > > > + goto error_power_off; > > > + > > > + ret = os02g10_init_controls(os02g10); > > > + if (ret) > > > + goto error_power_off; > > > + > > > + /* Initialize subdev */ > > > + os02g10->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; > > > + os02g10->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; > > > + os02g10->pad.flags = MEDIA_PAD_FL_SOURCE; > > > + > > > + ret = media_entity_pads_init(&os02g10->sd.entity, 1, &os02g10->pad); > > > + if (ret) { > > > + dev_err_probe(os02g10->dev, ret, "failed to init entity pads\n"); > > > + goto error_handler_free; > > > + } > > > + > > > + os02g10->sd.state_lock = os02g10->handler.lock; > > > + ret = v4l2_subdev_init_finalize(&os02g10->sd); > > > + if (ret) { > > > + dev_err_probe(os02g10->dev, ret, "subdev init error\n"); > > > + goto error_media_entity; > > > + } > > > + > > > + pm_runtime_set_active(os02g10->dev); > > > + pm_runtime_enable(os02g10->dev); > > > + > > > + ret = v4l2_async_register_subdev_sensor(&os02g10->sd); > > > + if (ret) { > > > + dev_err_probe(os02g10->dev, ret, > > > + "failed to register os02g10 sub-device\n"); > > > + goto error_subdev_cleanup; > > > + } > > > + > > > + pm_runtime_idle(os02g10->dev); > > > + > > > + return 0; > > > + > > > +error_subdev_cleanup: > > > + v4l2_subdev_cleanup(&os02g10->sd); > > > + pm_runtime_disable(os02g10->dev); > > > + pm_runtime_set_suspended(os02g10->dev); > > > + > > > +error_media_entity: > > > + media_entity_cleanup(&os02g10->sd.entity); > > > + > > > +error_handler_free: > > > + v4l2_ctrl_handler_free(os02g10->sd.ctrl_handler); > > > + > > > +error_power_off: > > > + os02g10_power_off(os02g10->dev); > > > + > > > + return ret; > > > +} > > > + > > > +static void os02g10_remove(struct i2c_client *client) > > > +{ > > > + struct v4l2_subdev *sd = i2c_get_clientdata(client); > > > + struct os02g10 *os02g10 = to_os02g10(sd); > > > + > > > + v4l2_async_unregister_subdev(sd); > > > + v4l2_subdev_cleanup(&os02g10->sd); > > > + media_entity_cleanup(&sd->entity); > > > + v4l2_ctrl_handler_free(os02g10->sd.ctrl_handler); > > > + > > > + pm_runtime_disable(&client->dev); > > > + if (!pm_runtime_status_suspended(&client->dev)) { > > > + os02g10_power_off(&client->dev); > > > + pm_runtime_set_suspended(&client->dev); > > > + } > > > +} > > > + > > > +static DEFINE_RUNTIME_DEV_PM_OPS(os02g10_pm_ops, > > > + os02g10_power_off, os02g10_power_on, NULL); > > > + > > > +static const struct of_device_id os02g10_id[] = { > > > + { .compatible = "ovti,os02g10" }, > > > + { /* sentinel */ } > > > +}; > > > +MODULE_DEVICE_TABLE(of, os02g10_id); > > > + > > > +static struct i2c_driver os02g10_driver = { > > > + .driver = { > > > + .name = "os02g10", > > > + .pm = pm_ptr(&os02g10_pm_ops), > > > + .of_match_table = os02g10_id, > > > + }, > > > + .probe = os02g10_probe, > > > + .remove = os02g10_remove, > > > +}; > > > +module_i2c_driver(os02g10_driver); > > > + > > > +MODULE_DESCRIPTION("OS02G10 Camera Sensor Driver"); > > > +MODULE_AUTHOR("Tarang Raval <tarang.raval@siliconsignals.io>"); > > > +MODULE_AUTHOR("Elgin Perumbilly <elgin.perumbilly@siliconsignals.io>"); > > > +MODULE_LICENSE("GPL"); > > -- > Regards, > > Laurent Pinchart -- Regards, Laurent Pinchart ^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH v3 3/3] media: i2c: os02g10: implement crop handling with set_selection 2026-04-24 9:25 [PATCH v3 0/3] media: i2c: Add os02g10 camera sensor driver Elgin Perumbilly 2026-04-24 9:25 ` [PATCH v3 1/3] dt-bindings: media: i2c: Add os02g10 sensor Elgin Perumbilly 2026-04-24 9:25 ` [PATCH v3 2/3] media: i2c: add os02g10 image sensor driver Elgin Perumbilly @ 2026-04-24 9:25 ` Elgin Perumbilly 2026-06-12 10:34 ` Vladimir Zapolskiy 2026-06-18 11:47 ` Sakari Ailus 2026-04-24 12:14 ` [PATCH v3 0/3] media: i2c: Add os02g10 camera sensor driver Sakari Ailus ` (2 subsequent siblings) 5 siblings, 2 replies; 27+ messages in thread From: Elgin Perumbilly @ 2026-04-24 9:25 UTC (permalink / raw) To: sakari.ailus, laurent.pinchart Cc: Tarang Raval, Elgin Perumbilly, Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Hans Verkuil, Hans de Goede, Vladimir Zapolskiy, Mehdi Djait, Sylvain Petinot, Benjamin Mugnier, Bryan O'Donoghue, Heimir Thor Sverrisson, Hardevsinh Palaniya, linux-media, devicetree, linux-kernel From: Tarang Raval <tarang.raval@siliconsignals.io> Add crop support to os02g10 by implementing .set_selection() and storing the crop rectangle in subdev state. Initialize the default crop to the active area, make set_fmt() use the current crop, and update the output format when the crop size changes. Also program the sensor window from the active crop/format state instead of using the fixed supported_modes entry. This allows userspace to configure the sensor crop window explicitly. Signed-off-by: Tarang Raval <tarang.raval@siliconsignals.io> Signed-off-by: Elgin Perumbilly <elgin.perumbilly@siliconsignals.io> --- drivers/media/i2c/os02g10.c | 166 ++++++++++++++++++++++-------------- 1 file changed, 103 insertions(+), 63 deletions(-) diff --git a/drivers/media/i2c/os02g10.c b/drivers/media/i2c/os02g10.c index fad2dd0ad7aa..9bf8f5d1caea 100644 --- a/drivers/media/i2c/os02g10.c +++ b/drivers/media/i2c/os02g10.c @@ -112,6 +112,11 @@ #define OS02G10_ORIENTATION_BAYER_FIX 0x32 #define OS02G10_LINK_FREQ_720MHZ (720 * HZ_PER_MHZ) +#define OS02G10_WINDOW_WIDTH_MIN 2 +#define OS02G10_WINDOW_HEIGHT_MIN 2 +#define OS02G10_VBLANK_DEF 166 +#define OS02G10_VBLANK_MIN 25 +#define OS02G10_EXPOSURE_DEF 1100 /* OS02G10 native and active pixel array size */ static const struct v4l2_rect os02g10_native_area = { @@ -152,15 +157,6 @@ struct os02g10 { struct v4l2_ctrl *hflip; }; -struct os02g10_mode { - u32 width; - u32 height; - u32 vts_def; - u32 exp_def; - u32 x_start; - u32 y_start; -}; - static const struct cci_reg_sequence os02g10_common_regs[] = { { OS02G10_REG_PLL_DIV_CTRL, 0x0a}, { OS02G10_REG_PLL_DCTL_BIAS_CTRL, 0x04}, @@ -245,17 +241,6 @@ static const struct cci_reg_sequence os02g10_common_regs[] = { { OS02G10_REG_MIPI_TX_SPEED_CTRL, 0x05}, }; -static const struct os02g10_mode supported_modes[] = { - { - .width = 1920, - .height = 1080, - .vts_def = 1246, - .exp_def = 1100, - .x_start = 2, - .y_start = 6, - }, -}; - static const s64 link_freq_menu_items[] = { OS02G10_LINK_FREQ_720MHZ, }; @@ -295,11 +280,12 @@ static int os02g10_set_ctrl(struct v4l2_ctrl *ctrl) if (ctrl->id == V4L2_CID_VBLANK) { /* Honour the VBLANK limits when setting exposure */ s64 max = fmt->height + ctrl->val - OS02G10_EXPOSURE_MARGIN; + s64 def = (max < OS02G10_EXPOSURE_DEF) ? max + : OS02G10_EXPOSURE_DEF; ret = __v4l2_ctrl_modify_range(os02g10->exposure, os02g10->exposure->minimum, max, - os02g10->exposure->step, - os02g10->exposure->default_value); + os02g10->exposure->step, def); if (ret) return ret; } @@ -362,10 +348,9 @@ static const struct v4l2_ctrl_ops os02g10_ctrl_ops = { static int os02g10_init_controls(struct os02g10 *os02g10) { - const struct os02g10_mode *mode = &supported_modes[0]; struct v4l2_fwnode_device_properties props; - u64 vblank_def, exp_max, pixel_rate; struct v4l2_ctrl_handler *ctrl_hdlr; + u64 exp_max, pixel_rate; int ret; ctrl_hdlr = &os02g10->handler; @@ -384,18 +369,19 @@ static int os02g10_init_controls(struct os02g10 *os02g10) if (os02g10->link_freq) os02g10->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; - vblank_def = mode->vts_def - mode->height; os02g10->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, - V4L2_CID_VBLANK, vblank_def, - OS02G10_FRAME_LENGTH_MAX - mode->height, - 1, vblank_def); + V4L2_CID_VBLANK, OS02G10_VBLANK_MIN, + OS02G10_FRAME_LENGTH_MAX - + os02g10_active_area.height, + 1, OS02G10_VBLANK_DEF); - exp_max = mode->vts_def - OS02G10_EXPOSURE_MARGIN; + exp_max = OS02G10_VBLANK_DEF + os02g10_active_area.height + - OS02G10_EXPOSURE_MARGIN; os02g10->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, V4L2_CID_EXPOSURE, OS02G10_EXPOSURE_MIN, exp_max, - OS02G10_EXPOSURE_STEP, mode->exp_def); + OS02G10_EXPOSURE_STEP, OS02G10_EXPOSURE_DEF); v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, OS02G10_ANALOG_GAIN_MIN, @@ -445,20 +431,18 @@ static int os02g10_set_framefmt(struct os02g10 *os02g10, struct v4l2_subdev_state *state) { const struct v4l2_mbus_framefmt *format; - const struct os02g10_mode *mode; + const struct v4l2_rect *crop; int ret = 0; + crop = v4l2_subdev_state_get_crop(state, 0); format = v4l2_subdev_state_get_format(state, 0); - mode = v4l2_find_nearest_size(supported_modes, - ARRAY_SIZE(supported_modes), width, - height, format->width, format->height); - cci_write(os02g10->cci, OS02G10_REG_V_START, mode->y_start, &ret); - cci_write(os02g10->cci, OS02G10_REG_V_SIZE, mode->height, &ret); - cci_write(os02g10->cci, OS02G10_REG_V_SIZE_MIPI, mode->height, &ret); - cci_write(os02g10->cci, OS02G10_REG_H_START, mode->x_start, &ret); - cci_write(os02g10->cci, OS02G10_REG_H_SIZE, mode->width, &ret); - cci_write(os02g10->cci, OS02G10_REG_H_SIZE_MIPI, mode->width, &ret); + cci_write(os02g10->cci, OS02G10_REG_V_START, crop->top, &ret); + cci_write(os02g10->cci, OS02G10_REG_V_SIZE, crop->height, &ret); + cci_write(os02g10->cci, OS02G10_REG_V_SIZE_MIPI, format->height, &ret); + cci_write(os02g10->cci, OS02G10_REG_H_START, crop->left, &ret); + cci_write(os02g10->cci, OS02G10_REG_H_SIZE, crop->width, &ret); + cci_write(os02g10->cci, OS02G10_REG_H_SIZE_MIPI, format->width, &ret); return ret; } @@ -528,16 +512,67 @@ static int os02g10_disable_streams(struct v4l2_subdev *sd, return ret; } +static int os02g10_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct v4l2_mbus_framefmt *format; + struct v4l2_rect *crop; + struct v4l2_rect rect; + + if (sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + rect.left = clamp_t(unsigned int, ALIGN(sel->r.left, 2), + os02g10_active_area.left, + os02g10_active_area.left + + os02g10_active_area.width - + OS02G10_WINDOW_WIDTH_MIN); + rect.top = clamp_t(unsigned int, ALIGN(sel->r.top, 2), + os02g10_active_area.top, + os02g10_active_area.top + + os02g10_active_area.height - + OS02G10_WINDOW_HEIGHT_MIN); + rect.width = clamp_t(unsigned int, ALIGN(sel->r.width, 2), + OS02G10_WINDOW_WIDTH_MIN, + os02g10_active_area.width); + rect.height = clamp_t(unsigned int, ALIGN(sel->r.height, 2), + OS02G10_WINDOW_HEIGHT_MIN, + os02g10_active_area.height); + + rect.width = min_t(unsigned int, rect.width, + os02g10_active_area.left + + os02g10_active_area.width - rect.left); + rect.height = min_t(unsigned int, rect.height, + os02g10_active_area.top + + os02g10_active_area.height - rect.top); + + crop = v4l2_subdev_state_get_crop(sd_state, sel->pad); + + if (rect.width != crop->width || rect.height != crop->height) { + format = v4l2_subdev_state_get_format(sd_state, sel->pad); + format->width = rect.width; + format->height = rect.height; + } + + *crop = rect; + sel->r = rect; + + return 0; +} + static int os02g10_get_selection(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { switch (sel->target) { + case V4L2_SEL_TGT_CROP: + sel->r = *v4l2_subdev_state_get_crop(sd_state, sel->pad); + return 0; case V4L2_SEL_TGT_CROP_BOUNDS: case V4L2_SEL_TGT_NATIVE_SIZE: sel->r = os02g10_native_area; return 0; - case V4L2_SEL_TGT_CROP: case V4L2_SEL_TGT_CROP_DEFAULT: sel->r = os02g10_active_area; return 0; @@ -566,16 +601,16 @@ static int os02g10_enum_frame_size(struct v4l2_subdev *sd, { struct os02g10 *os02g10 = to_os02g10(sd); - if (fse->index >= ARRAY_SIZE(supported_modes)) + if (fse->index) return -EINVAL; if (fse->code != os02g10_get_format_code(os02g10)) 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; + fse->min_width = OS02G10_WINDOW_WIDTH_MIN; + fse->max_width = os02g10_active_area.width; + fse->min_height = OS02G10_WINDOW_HEIGHT_MIN; + fse->max_height = os02g10_active_area.height; return 0; } @@ -586,18 +621,14 @@ static int os02g10_set_pad_format(struct v4l2_subdev *sd, { struct os02g10 *os02g10 = to_os02g10(sd); struct v4l2_mbus_framefmt *format; - const struct os02g10_mode *mode; + struct v4l2_rect *crop; + crop = v4l2_subdev_state_get_crop(sd_state, 0); format = v4l2_subdev_state_get_format(sd_state, 0); - mode = v4l2_find_nearest_size(supported_modes, - ARRAY_SIZE(supported_modes), - width, height, - fmt->format.width, fmt->format.height); - fmt->format.code = os02g10_get_format_code(os02g10); - fmt->format.width = mode->width; - fmt->format.height = mode->height; + fmt->format.width = crop->width; + fmt->format.height = crop->height; fmt->format.field = V4L2_FIELD_NONE; fmt->format.colorspace = V4L2_COLORSPACE_RAW; fmt->format.quantization = V4L2_QUANTIZATION_FULL_RANGE; @@ -606,11 +637,19 @@ static int os02g10_set_pad_format(struct v4l2_subdev *sd, *format = fmt->format; if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { - u32 vblank_def = mode->vts_def - mode->height; + int ret, vblank; - int ret = __v4l2_ctrl_modify_range(os02g10->vblank, vblank_def, - OS02G10_FRAME_LENGTH_MAX - - mode->height, 1, vblank_def); + ret = __v4l2_ctrl_modify_range(os02g10->vblank, OS02G10_VBLANK_MIN, + OS02G10_FRAME_LENGTH_MAX - + fmt->format.height, 1, + OS02G10_VBLANK_DEF); + if (ret) + return ret; + + /* Set VBLANK to maintain 30 fps for the selected format. */ + vblank = os02g10_active_area.height - fmt->format.height + + OS02G10_VBLANK_DEF; + ret = __v4l2_ctrl_s_ctrl(os02g10->vblank, vblank); if (ret) return ret; } @@ -626,14 +665,14 @@ static int os02g10_init_state(struct v4l2_subdev *sd, .which = V4L2_SUBDEV_FORMAT_TRY, .format = { .code = os02g10_get_format_code(os02g10), - .width = supported_modes[0].width, - .height = supported_modes[0].height, + .width = os02g10_active_area.width, + .height = os02g10_active_area.height, }, }; + struct v4l2_rect *crop = v4l2_subdev_state_get_crop(state, 0); + *crop = os02g10_active_area; - os02g10_set_pad_format(sd, state, &fmt); - - return 0; + return os02g10_set_pad_format(sd, state, &fmt); } static const struct v4l2_subdev_video_ops os02g10_video_ops = { @@ -645,6 +684,7 @@ static const struct v4l2_subdev_pad_ops os02g10_pad_ops = { .get_fmt = v4l2_subdev_get_fmt, .set_fmt = os02g10_set_pad_format, .get_selection = os02g10_get_selection, + .set_selection = os02g10_set_selection, .enum_frame_size = os02g10_enum_frame_size, .enable_streams = os02g10_enable_streams, .disable_streams = os02g10_disable_streams, -- 2.34.1 ^ permalink raw reply related [flat|nested] 27+ messages in thread
* Re: [PATCH v3 3/3] media: i2c: os02g10: implement crop handling with set_selection 2026-04-24 9:25 ` [PATCH v3 3/3] media: i2c: os02g10: implement crop handling with set_selection Elgin Perumbilly @ 2026-06-12 10:34 ` Vladimir Zapolskiy 2026-06-12 11:41 ` Tarang Raval 2026-06-18 11:47 ` Sakari Ailus 1 sibling, 1 reply; 27+ messages in thread From: Vladimir Zapolskiy @ 2026-06-12 10:34 UTC (permalink / raw) To: Elgin Perumbilly, sakari.ailus, laurent.pinchart Cc: Tarang Raval, Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Hans Verkuil, Hans de Goede, Mehdi Djait, Sylvain Petinot, Benjamin Mugnier, Bryan O'Donoghue, Heimir Thor Sverrisson, Hardevsinh Palaniya, linux-media, devicetree, linux-kernel On 4/24/26 12:25, Elgin Perumbilly wrote: > From: Tarang Raval <tarang.raval@siliconsignals.io> > > Add crop support to os02g10 by implementing .set_selection() and > storing the crop rectangle in subdev state. > > Initialize the default crop to the active area, make set_fmt() use the > current crop, and update the output format when the crop size changes. > Also program the sensor window from the active crop/format state instead > of using the fixed supported_modes entry. > > This allows userspace to configure the sensor crop window explicitly. > > Signed-off-by: Tarang Raval <tarang.raval@siliconsignals.io> > Signed-off-by: Elgin Perumbilly <elgin.perumbilly@siliconsignals.io> > --- > drivers/media/i2c/os02g10.c | 166 ++++++++++++++++++++++-------------- > 1 file changed, 103 insertions(+), 63 deletions(-) > > diff --git a/drivers/media/i2c/os02g10.c b/drivers/media/i2c/os02g10.c > index fad2dd0ad7aa..9bf8f5d1caea 100644 > --- a/drivers/media/i2c/os02g10.c > +++ b/drivers/media/i2c/os02g10.c > @@ -112,6 +112,11 @@ > #define OS02G10_ORIENTATION_BAYER_FIX 0x32 > > #define OS02G10_LINK_FREQ_720MHZ (720 * HZ_PER_MHZ) > +#define OS02G10_WINDOW_WIDTH_MIN 2 > +#define OS02G10_WINDOW_HEIGHT_MIN 2 Add a blank line before the new group of macro. > +#define OS02G10_VBLANK_DEF 166 This one is computable, and it can be dropped. > +#define OS02G10_VBLANK_MIN 25 This macro shall be added to the group of OS02G10_REG_FRAME_LENGTH register, and it should be included into the previous change. > +#define OS02G10_EXPOSURE_DEF 1100 This macro shall be added to the group of OS02G10_REG_LONG_EXPOSURE register, and it should be included into the previous change. > > /* OS02G10 native and active pixel array size */ > static const struct v4l2_rect os02g10_native_area = { > @@ -152,15 +157,6 @@ struct os02g10 { > struct v4l2_ctrl *hflip; > }; > > -struct os02g10_mode { > - u32 width; > - u32 height; > - u32 vts_def; > - u32 exp_def; > - u32 x_start; > - u32 y_start; > -}; > - > static const struct cci_reg_sequence os02g10_common_regs[] = { > { OS02G10_REG_PLL_DIV_CTRL, 0x0a}, > { OS02G10_REG_PLL_DCTL_BIAS_CTRL, 0x04}, > @@ -245,17 +241,6 @@ static const struct cci_reg_sequence os02g10_common_regs[] = { > { OS02G10_REG_MIPI_TX_SPEED_CTRL, 0x05}, > }; > > -static const struct os02g10_mode supported_modes[] = { > - { > - .width = 1920, > - .height = 1080, > - .vts_def = 1246, > - .exp_def = 1100, > - .x_start = 2, > - .y_start = 6, > - }, > -}; > - > static const s64 link_freq_menu_items[] = { > OS02G10_LINK_FREQ_720MHZ, > }; > @@ -295,11 +280,12 @@ static int os02g10_set_ctrl(struct v4l2_ctrl *ctrl) > if (ctrl->id == V4L2_CID_VBLANK) { > /* Honour the VBLANK limits when setting exposure */ > s64 max = fmt->height + ctrl->val - OS02G10_EXPOSURE_MARGIN; > + s64 def = (max < OS02G10_EXPOSURE_DEF) ? max > + : OS02G10_EXPOSURE_DEF; > > ret = __v4l2_ctrl_modify_range(os02g10->exposure, > os02g10->exposure->minimum, max, > - os02g10->exposure->step, > - os02g10->exposure->default_value); > + os02g10->exposure->step, def); > if (ret) > return ret; > } > @@ -362,10 +348,9 @@ static const struct v4l2_ctrl_ops os02g10_ctrl_ops = { > > static int os02g10_init_controls(struct os02g10 *os02g10) > { > - const struct os02g10_mode *mode = &supported_modes[0]; > struct v4l2_fwnode_device_properties props; > - u64 vblank_def, exp_max, pixel_rate; > struct v4l2_ctrl_handler *ctrl_hdlr; > + u64 exp_max, pixel_rate; > int ret; > > ctrl_hdlr = &os02g10->handler; > @@ -384,18 +369,19 @@ static int os02g10_init_controls(struct os02g10 *os02g10) > if (os02g10->link_freq) > os02g10->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; > > - vblank_def = mode->vts_def - mode->height; > os02g10->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, > - V4L2_CID_VBLANK, vblank_def, > - OS02G10_FRAME_LENGTH_MAX - mode->height, > - 1, vblank_def); > + V4L2_CID_VBLANK, OS02G10_VBLANK_MIN, > + OS02G10_FRAME_LENGTH_MAX - > + os02g10_active_area.height, > + 1, OS02G10_VBLANK_DEF); > > - exp_max = mode->vts_def - OS02G10_EXPOSURE_MARGIN; > + exp_max = OS02G10_VBLANK_DEF + os02g10_active_area.height > + - OS02G10_EXPOSURE_MARGIN; > os02g10->exposure = > v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, > V4L2_CID_EXPOSURE, > OS02G10_EXPOSURE_MIN, exp_max, > - OS02G10_EXPOSURE_STEP, mode->exp_def); > + OS02G10_EXPOSURE_STEP, OS02G10_EXPOSURE_DEF); > > v4l2_ctrl_new_std(ctrl_hdlr, &os02g10_ctrl_ops, > V4L2_CID_ANALOGUE_GAIN, OS02G10_ANALOG_GAIN_MIN, > @@ -445,20 +431,18 @@ static int os02g10_set_framefmt(struct os02g10 *os02g10, > struct v4l2_subdev_state *state) > { > const struct v4l2_mbus_framefmt *format; > - const struct os02g10_mode *mode; > + const struct v4l2_rect *crop; > int ret = 0; > > + crop = v4l2_subdev_state_get_crop(state, 0); > format = v4l2_subdev_state_get_format(state, 0); > - mode = v4l2_find_nearest_size(supported_modes, > - ARRAY_SIZE(supported_modes), width, > - height, format->width, format->height); > > - cci_write(os02g10->cci, OS02G10_REG_V_START, mode->y_start, &ret); > - cci_write(os02g10->cci, OS02G10_REG_V_SIZE, mode->height, &ret); > - cci_write(os02g10->cci, OS02G10_REG_V_SIZE_MIPI, mode->height, &ret); > - cci_write(os02g10->cci, OS02G10_REG_H_START, mode->x_start, &ret); > - cci_write(os02g10->cci, OS02G10_REG_H_SIZE, mode->width, &ret); > - cci_write(os02g10->cci, OS02G10_REG_H_SIZE_MIPI, mode->width, &ret); > + cci_write(os02g10->cci, OS02G10_REG_V_START, crop->top, &ret); > + cci_write(os02g10->cci, OS02G10_REG_V_SIZE, crop->height, &ret); > + cci_write(os02g10->cci, OS02G10_REG_V_SIZE_MIPI, format->height, &ret); > + cci_write(os02g10->cci, OS02G10_REG_H_START, crop->left, &ret); > + cci_write(os02g10->cci, OS02G10_REG_H_SIZE, crop->width, &ret); > + cci_write(os02g10->cci, OS02G10_REG_H_SIZE_MIPI, format->width, &ret); > > return ret; > } > @@ -528,16 +512,67 @@ static int os02g10_disable_streams(struct v4l2_subdev *sd, > return ret; > } > > +static int os02g10_set_selection(struct v4l2_subdev *sd, > + struct v4l2_subdev_state *sd_state, > + struct v4l2_subdev_selection *sel) > +{ > + struct v4l2_mbus_framefmt *format; > + struct v4l2_rect *crop; > + struct v4l2_rect rect; > + > + if (sel->target != V4L2_SEL_TGT_CROP) > + return -EINVAL; > + > + rect.left = clamp_t(unsigned int, ALIGN(sel->r.left, 2), > + os02g10_active_area.left, > + os02g10_active_area.left + > + os02g10_active_area.width - > + OS02G10_WINDOW_WIDTH_MIN); > + rect.top = clamp_t(unsigned int, ALIGN(sel->r.top, 2), > + os02g10_active_area.top, > + os02g10_active_area.top + > + os02g10_active_area.height - > + OS02G10_WINDOW_HEIGHT_MIN); > + rect.width = clamp_t(unsigned int, ALIGN(sel->r.width, 2), > + OS02G10_WINDOW_WIDTH_MIN, > + os02g10_active_area.width); > + rect.height = clamp_t(unsigned int, ALIGN(sel->r.height, 2), > + OS02G10_WINDOW_HEIGHT_MIN, > + os02g10_active_area.height); > + > + rect.width = min_t(unsigned int, rect.width, > + os02g10_active_area.left + > + os02g10_active_area.width - rect.left); > + rect.height = min_t(unsigned int, rect.height, > + os02g10_active_area.top + > + os02g10_active_area.height - rect.top); > + > + crop = v4l2_subdev_state_get_crop(sd_state, sel->pad); > + > + if (rect.width != crop->width || rect.height != crop->height) { > + format = v4l2_subdev_state_get_format(sd_state, sel->pad); > + format->width = rect.width; > + format->height = rect.height; > + } > + > + *crop = rect; > + sel->r = rect; > + > + return 0; > +} > + > static int os02g10_get_selection(struct v4l2_subdev *sd, > struct v4l2_subdev_state *sd_state, > struct v4l2_subdev_selection *sel) > { > switch (sel->target) { > + case V4L2_SEL_TGT_CROP: > + sel->r = *v4l2_subdev_state_get_crop(sd_state, sel->pad); > + return 0; > case V4L2_SEL_TGT_CROP_BOUNDS: > case V4L2_SEL_TGT_NATIVE_SIZE: > sel->r = os02g10_native_area; > return 0; > - case V4L2_SEL_TGT_CROP: > case V4L2_SEL_TGT_CROP_DEFAULT: > sel->r = os02g10_active_area; > return 0; > @@ -566,16 +601,16 @@ static int os02g10_enum_frame_size(struct v4l2_subdev *sd, > { > struct os02g10 *os02g10 = to_os02g10(sd); > > - if (fse->index >= ARRAY_SIZE(supported_modes)) > + if (fse->index) > return -EINVAL; > > if (fse->code != os02g10_get_format_code(os02g10)) > 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; > + fse->min_width = OS02G10_WINDOW_WIDTH_MIN; > + fse->max_width = os02g10_active_area.width; > + fse->min_height = OS02G10_WINDOW_HEIGHT_MIN; > + fse->max_height = os02g10_active_area.height; > > return 0; > } > @@ -586,18 +621,14 @@ static int os02g10_set_pad_format(struct v4l2_subdev *sd, > { > struct os02g10 *os02g10 = to_os02g10(sd); > struct v4l2_mbus_framefmt *format; > - const struct os02g10_mode *mode; > + struct v4l2_rect *crop; > > + crop = v4l2_subdev_state_get_crop(sd_state, 0); > format = v4l2_subdev_state_get_format(sd_state, 0); > > - mode = v4l2_find_nearest_size(supported_modes, > - ARRAY_SIZE(supported_modes), > - width, height, > - fmt->format.width, fmt->format.height); > - > fmt->format.code = os02g10_get_format_code(os02g10); > - fmt->format.width = mode->width; > - fmt->format.height = mode->height; > + fmt->format.width = crop->width; > + fmt->format.height = crop->height; > fmt->format.field = V4L2_FIELD_NONE; > fmt->format.colorspace = V4L2_COLORSPACE_RAW; > fmt->format.quantization = V4L2_QUANTIZATION_FULL_RANGE; > @@ -606,11 +637,19 @@ static int os02g10_set_pad_format(struct v4l2_subdev *sd, > *format = fmt->format; > > if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { > - u32 vblank_def = mode->vts_def - mode->height; > + int ret, vblank; > > - int ret = __v4l2_ctrl_modify_range(os02g10->vblank, vblank_def, > - OS02G10_FRAME_LENGTH_MAX - > - mode->height, 1, vblank_def); > + ret = __v4l2_ctrl_modify_range(os02g10->vblank, OS02G10_VBLANK_MIN, > + OS02G10_FRAME_LENGTH_MAX - > + fmt->format.height, 1, > + OS02G10_VBLANK_DEF); > + if (ret) > + return ret; > + > + /* Set VBLANK to maintain 30 fps for the selected format. */ > + vblank = os02g10_active_area.height - fmt->format.height > + + OS02G10_VBLANK_DEF; > + ret = __v4l2_ctrl_s_ctrl(os02g10->vblank, vblank); > if (ret) > return ret; > } > @@ -626,14 +665,14 @@ static int os02g10_init_state(struct v4l2_subdev *sd, > .which = V4L2_SUBDEV_FORMAT_TRY, > .format = { > .code = os02g10_get_format_code(os02g10), > - .width = supported_modes[0].width, > - .height = supported_modes[0].height, > + .width = os02g10_active_area.width, > + .height = os02g10_active_area.height, > }, > }; > + struct v4l2_rect *crop = v4l2_subdev_state_get_crop(state, 0); > + *crop = os02g10_active_area; > > - os02g10_set_pad_format(sd, state, &fmt); > - > - return 0; > + return os02g10_set_pad_format(sd, state, &fmt); > } > > static const struct v4l2_subdev_video_ops os02g10_video_ops = { > @@ -645,6 +684,7 @@ static const struct v4l2_subdev_pad_ops os02g10_pad_ops = { > .get_fmt = v4l2_subdev_get_fmt, > .set_fmt = os02g10_set_pad_format, > .get_selection = os02g10_get_selection, > + .set_selection = os02g10_set_selection, > .enum_frame_size = os02g10_enum_frame_size, > .enable_streams = os02g10_enable_streams, > .disable_streams = os02g10_disable_streams, I understand that this change is written by another person, and likely it is not squashed with the previous one to preserve authorship, however it significantly rewrites the change already found in the series. I don't see information about the maximum supported frame height/width or default VTS setting etc. anymore, for me it's hard to say, if this kind of information can be dropped with no consequences in runtime. Probably this 3/3 change will break a quick inclusion of the sensor driver, you may consider to exlcude it from the series now, and publish it afterwards. -- Best wishes, Vladimir ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v3 3/3] media: i2c: os02g10: implement crop handling with set_selection 2026-06-12 10:34 ` Vladimir Zapolskiy @ 2026-06-12 11:41 ` Tarang Raval 2026-06-12 13:16 ` Vladimir Zapolskiy 0 siblings, 1 reply; 27+ messages in thread From: Tarang Raval @ 2026-06-12 11:41 UTC (permalink / raw) To: Vladimir Zapolskiy Cc: Elgin Perumbilly, laurent.pinchart@ideasonboard.com, sakari.ailus@linux.intel.com, Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Hans Verkuil, Hans de Goede, Mehdi Djait, Sylvain Petinot, Benjamin Mugnier, Bryan O'Donoghue, Heimir Thor Sverrisson, Hardevsinh Palaniya, linux-media@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org Hi Vladimir, Thank you for the review. Since I authored this patch, I will try to address the comments below. > On 4/24/26 12:25, Elgin Perumbilly wrote: > > From: Tarang Raval <tarang.raval@siliconsignals.io> > > > > Add crop support to os02g10 by implementing .set_selection() and > > storing the crop rectangle in subdev state. > > > > Initialize the default crop to the active area, make set_fmt() use the > > current crop, and update the output format when the crop size changes. > > Also program the sensor window from the active crop/format state instead > > of using the fixed supported_modes entry. > > > > This allows userspace to configure the sensor crop window explicitly. > > > > Signed-off-by: Tarang Raval <tarang.raval@siliconsignals.io> > > Signed-off-by: Elgin Perumbilly <elgin.perumbilly@siliconsignals.io> > > --- > > drivers/media/i2c/os02g10.c | 166 ++++++++++++++++++++++-------------- > > 1 file changed, 103 insertions(+), 63 deletions(-) > > > > diff --git a/drivers/media/i2c/os02g10.c b/drivers/media/i2c/os02g10.c > > index fad2dd0ad7aa..9bf8f5d1caea 100644 > > --- a/drivers/media/i2c/os02g10.c > > +++ b/drivers/media/i2c/os02g10.c > > @@ -112,6 +112,11 @@ > > #define OS02G10_ORIENTATION_BAYER_FIX 0x32 > > > > #define OS02G10_LINK_FREQ_720MHZ (720 * HZ_PER_MHZ) > > +#define OS02G10_WINDOW_WIDTH_MIN 2 > > +#define OS02G10_WINDOW_HEIGHT_MIN 2 > > Add a blank line before the new group of macro. > > > +#define OS02G10_VBLANK_DEF 166 > > This one is computable, and it can be dropped. Can you explain how this value can be computed ? > > +#define OS02G10_VBLANK_MIN 25 > > This macro shall be added to the group of OS02G10_REG_FRAME_LENGTH > register, and it should be included into the previous change. > > > +#define OS02G10_EXPOSURE_DEF 1100 > > This macro shall be added to the group of OS02G10_REG_LONG_EXPOSURE > register, and it should be included into the previous change. I would prefer to introduce these macros here only, as they are related to this patch. There is no use of these macros in the previous patch, so moving them there would not provide any benefit. I will group them in the appropriate place within this patch as per your suggestion. > > > > /* OS02G10 native and active pixel array size */ > > static const struct v4l2_rect os02g10_native_area = { > > @@ -152,15 +157,6 @@ struct os02g10 { > > struct v4l2_ctrl *hflip; > > }; > > > > -struct os02g10_mode { > > - u32 width; > > - u32 height; > > - u32 vts_def; > > - u32 exp_def; > > - u32 x_start; > > - u32 y_start; > > -}; > > - > > static const struct cci_reg_sequence os02g10_common_regs[] = { > > { OS02G10_REG_PLL_DIV_CTRL, 0x0a}, > > { OS02G10_REG_PLL_DCTL_BIAS_CTRL, 0x04}, ... > > static const struct v4l2_subdev_video_ops os02g10_video_ops = { > > @@ -645,6 +684,7 @@ static const struct v4l2_subdev_pad_ops os02g10_pad_ops = { > > .get_fmt = v4l2_subdev_get_fmt, > > .set_fmt = os02g10_set_pad_format, > > .get_selection = os02g10_get_selection, > > + .set_selection = os02g10_set_selection, > > .enum_frame_size = os02g10_enum_frame_size, > > .enable_streams = os02g10_enable_streams, > > .disable_streams = os02g10_disable_streams, > > I understand that this change is written by another person, and likely > it is not squashed with the previous one to preserve authorship, however > it significantly rewrites the change already found in the series. I don't think this patch significantly rewrites the previous changes. Its main purpose is to introduce a crop rectangle and implement set_selection(), allowing userspace to stream arbitrary resolutions within the sensor limits instead of being restricted to 1920x1080. > I don't see information about the maximum supported frame height/width > or default VTS setting etc. anymore, for me it's hard to say, if > this kind of information can be dropped with no consequences in runtime. The maximum supported width and height are still defined and enforced in os02g10_set_selection(). The default VTS handling is implemented in os02g10_set_pad_format(), where VBLANK is adjusted to maintain 30 fps for the selected resolution. So I don't believe any information has been dropped. > Probably this 3/3 change will break a quick inclusion of the sensor > driver, you may consider to exlcude it from the series now, and publish > it afterwards. Regarding whether patch 3/3 should be included in the current series, I am happy to leave that decision to Sakari. If needed, this patch can be merged separately once the new raw sensor model is finalized. This patch was created based on Laurent's suggestion. Link: https://lore.kernel.org/linux-media/20260414084952.217215-1-elgin.perumbilly@siliconsignals.io/T/#t Best Regards, Tarang ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v3 3/3] media: i2c: os02g10: implement crop handling with set_selection 2026-06-12 11:41 ` Tarang Raval @ 2026-06-12 13:16 ` Vladimir Zapolskiy 2026-06-12 14:20 ` Tarang Raval 0 siblings, 1 reply; 27+ messages in thread From: Vladimir Zapolskiy @ 2026-06-12 13:16 UTC (permalink / raw) To: Tarang Raval Cc: Elgin Perumbilly, laurent.pinchart@ideasonboard.com, sakari.ailus@linux.intel.com, Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Hans Verkuil, Hans de Goede, Mehdi Djait, Sylvain Petinot, Benjamin Mugnier, Bryan O'Donoghue, Heimir Thor Sverrisson, Hardevsinh Palaniya, linux-media@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org On 6/12/26 14:41, Tarang Raval wrote: > Hi Vladimir, > > Thank you for the review. > > Since I authored this patch, I will try to address the comments below. > >> On 4/24/26 12:25, Elgin Perumbilly wrote: >>> From: Tarang Raval <tarang.raval@siliconsignals.io> >>> >>> Add crop support to os02g10 by implementing .set_selection() and >>> storing the crop rectangle in subdev state. >>> >>> Initialize the default crop to the active area, make set_fmt() use the >>> current crop, and update the output format when the crop size changes. >>> Also program the sensor window from the active crop/format state instead >>> of using the fixed supported_modes entry. >>> >>> This allows userspace to configure the sensor crop window explicitly. >>> >>> Signed-off-by: Tarang Raval <tarang.raval@siliconsignals.io> >>> Signed-off-by: Elgin Perumbilly <elgin.perumbilly@siliconsignals.io> >>> --- >>> drivers/media/i2c/os02g10.c | 166 ++++++++++++++++++++++-------------- >>> 1 file changed, 103 insertions(+), 63 deletions(-) >>> >>> diff --git a/drivers/media/i2c/os02g10.c b/drivers/media/i2c/os02g10.c >>> index fad2dd0ad7aa..9bf8f5d1caea 100644 >>> --- a/drivers/media/i2c/os02g10.c >>> +++ b/drivers/media/i2c/os02g10.c >>> @@ -112,6 +112,11 @@ >>> #define OS02G10_ORIENTATION_BAYER_FIX 0x32 >>> >>> #define OS02G10_LINK_FREQ_720MHZ (720 * HZ_PER_MHZ) >>> +#define OS02G10_WINDOW_WIDTH_MIN 2 >>> +#define OS02G10_WINDOW_HEIGHT_MIN 2 >> >> Add a blank line before the new group of macro. >> >>> +#define OS02G10_VBLANK_DEF 166 >> >> This one is computable, and it can be dropped. > > > Can you explain how this value can be computed ? It is (supported_modes[0].vts_def - supported_modes[0].height) on the base of 2/3 change. > >>> +#define OS02G10_VBLANK_MIN 25 >> >> This macro shall be added to the group of OS02G10_REG_FRAME_LENGTH >> register, and it should be included into the previous change. >> >>> +#define OS02G10_EXPOSURE_DEF 1100 >> >> This macro shall be added to the group of OS02G10_REG_LONG_EXPOSURE >> register, and it should be included into the previous change. > > > I would prefer to introduce these macros here only, as they are related to > this patch. There is no use of these macros in the previous patch, so moving > them there would not provide any benefit. Unavoidably there shall be a user of "default exposure" in the change 2/3, and therefore the value is present, please reference to supported_modes[0].exp_def. So, this macro goes to the 2/3 patch. > > I will group them in the appropriate place within this patch as per your > suggestion. > > >>> >>> /* OS02G10 native and active pixel array size */ >>> static const struct v4l2_rect os02g10_native_area = { >>> @@ -152,15 +157,6 @@ struct os02g10 { >>> struct v4l2_ctrl *hflip; >>> }; >>> >>> -struct os02g10_mode { >>> - u32 width; >>> - u32 height; >>> - u32 vts_def; >>> - u32 exp_def; >>> - u32 x_start; >>> - u32 y_start; >>> -}; >>> - >>> static const struct cci_reg_sequence os02g10_common_regs[] = { >>> { OS02G10_REG_PLL_DIV_CTRL, 0x0a}, >>> { OS02G10_REG_PLL_DCTL_BIAS_CTRL, 0x04}, > > ... > >>> static const struct v4l2_subdev_video_ops os02g10_video_ops = { >>> @@ -645,6 +684,7 @@ static const struct v4l2_subdev_pad_ops os02g10_pad_ops = { >>> .get_fmt = v4l2_subdev_get_fmt, >>> .set_fmt = os02g10_set_pad_format, >>> .get_selection = os02g10_get_selection, >>> + .set_selection = os02g10_set_selection, >>> .enum_frame_size = os02g10_enum_frame_size, >>> .enable_streams = os02g10_enable_streams, >>> .disable_streams = os02g10_disable_streams, >> >> I understand that this change is written by another person, and likely >> it is not squashed with the previous one to preserve authorship, however >> it significantly rewrites the change already found in the series. > > I don't think this patch significantly rewrites the previous changes. Its > main purpose is to introduce a crop rectangle and implement set_selection(), > allowing userspace to stream arbitrary resolutions within the sensor limits > instead of being restricted to 1920x1080. What are these "sensor limits" number? Are they defined in this 3/3 change? For whatever reason I can't find it, but I may be blind. > >> I don't see information about the maximum supported frame height/width >> or default VTS setting etc. anymore, for me it's hard to say, if >> this kind of information can be dropped with no consequences in runtime. > > The maximum supported width and height are still defined and enforced in > os02g10_set_selection(). The default VTS handling is implemented in > os02g10_set_pad_format(), where VBLANK is adjusted to maintain 30 fps for > the selected resolution. So I don't believe any information has been dropped. > >> Probably this 3/3 change will break a quick inclusion of the sensor >> driver, you may consider to exlcude it from the series now, and publish >> it afterwards. > > Regarding whether patch 3/3 should be included in the current series, I am > happy to leave that decision to Sakari. If needed, this patch can be merged > separately once the new raw sensor model is finalized. > > This patch was created based on Laurent's suggestion. > > Link: https://lore.kernel.org/linux-media/20260414084952.217215-1-elgin.perumbilly@siliconsignals.io/T/#t > That's a helpful reference to the previous patch review, thank you. Likely I don't have the whole picture of the new advances in linux-media. -- Best wishes, Vladimir ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v3 3/3] media: i2c: os02g10: implement crop handling with set_selection 2026-06-12 13:16 ` Vladimir Zapolskiy @ 2026-06-12 14:20 ` Tarang Raval 0 siblings, 0 replies; 27+ messages in thread From: Tarang Raval @ 2026-06-12 14:20 UTC (permalink / raw) To: Vladimir Zapolskiy Cc: Elgin Perumbilly, laurent.pinchart@ideasonboard.com, sakari.ailus@linux.intel.com, Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Hans Verkuil, Hans de Goede, Mehdi Djait, Sylvain Petinot, Benjamin Mugnier, Bryan O'Donoghue, Heimir Thor Sverrisson, Hardevsinh Palaniya, linux-media@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org > On 6/12/26 14:41, Tarang Raval wrote: > > Hi Vladimir, > > > > Thank you for the review. > > > > Since I authored this patch, I will try to address the comments below. > > > >> On 4/24/26 12:25, Elgin Perumbilly wrote: > >>> From: Tarang Raval <tarang.raval@siliconsignals.io> > >>> > >>> Add crop support to os02g10 by implementing .set_selection() and > >>> storing the crop rectangle in subdev state. > >>> > >>> Initialize the default crop to the active area, make set_fmt() use the > >>> current crop, and update the output format when the crop size changes. > >>> Also program the sensor window from the active crop/format state instead > >>> of using the fixed supported_modes entry. > >>> > >>> This allows userspace to configure the sensor crop window explicitly. > >>> > >>> Signed-off-by: Tarang Raval <tarang.raval@siliconsignals.io> > >>> Signed-off-by: Elgin Perumbilly <elgin.perumbilly@siliconsignals.io> > >>> --- > >>> drivers/media/i2c/os02g10.c | 166 ++++++++++++++++++++++-------------- > >>> 1 file changed, 103 insertions(+), 63 deletions(-) > >>> > >>> diff --git a/drivers/media/i2c/os02g10.c b/drivers/media/i2c/os02g10.c > >>> index fad2dd0ad7aa..9bf8f5d1caea 100644 > >>> --- a/drivers/media/i2c/os02g10.c > >>> +++ b/drivers/media/i2c/os02g10.c > >>> @@ -112,6 +112,11 @@ > >>> #define OS02G10_ORIENTATION_BAYER_FIX 0x32 > >>> > >>> #define OS02G10_LINK_FREQ_720MHZ (720 * HZ_PER_MHZ) > >>> +#define OS02G10_WINDOW_WIDTH_MIN 2 > >>> +#define OS02G10_WINDOW_HEIGHT_MIN 2 > >> > >> Add a blank line before the new group of macro. > >> > >>> +#define OS02G10_VBLANK_DEF 166 > >> > >> This one is computable, and it can be dropped. > > > > > > Can you explain how this value can be computed ? > > It is (supported_modes[0].vts_def - supported_modes[0].height) on > the base of 2/3 change. In this patch, the supported_modes structure has been removed, so the value can no longer be computed in that way. > >>> +#define OS02G10_VBLANK_MIN 25 > >> > >> This macro shall be added to the group of OS02G10_REG_FRAME_LENGTH > >> register, and it should be included into the previous change. > >> > >>> +#define OS02G10_EXPOSURE_DEF 1100 > >> > >> This macro shall be added to the group of OS02G10_REG_LONG_EXPOSURE > >> register, and it should be included into the previous change. > > > > > > I would prefer to introduce these macros here only, as they are related to > > this patch. There is no use of these macros in the previous patch, so moving > > them there would not provide any benefit. > > Unavoidably there shall be a user of "default exposure" in the change 2/3, and > therefore the value is present, please reference to supported_modes[0].exp_def. > > So, this macro goes to the 2/3 patch. In patch 2/3, the supported_modes structure is still present, so I don't think introducing a separate macro there provides much benefit. Using: vts_def = height + OS02G10_VBLANK_DEF; exp_def = OS02G10_VBLANK_DEF; doesn't look particularly readable to me either. However, I'd be interested to hear your preference on this. > > > > I will group them in the appropriate place within this patch as per your > > suggestion. > > > > > >>> > >>> /* OS02G10 native and active pixel array size */ > >>> static const struct v4l2_rect os02g10_native_area = { > >>> @@ -152,15 +157,6 @@ struct os02g10 { > >>> struct v4l2_ctrl *hflip; > >>> }; > >>> > >>> -struct os02g10_mode { > >>> - u32 width; > >>> - u32 height; > >>> - u32 vts_def; > >>> - u32 exp_def; > >>> - u32 x_start; > >>> - u32 y_start; > >>> -}; > >>> - > >>> static const struct cci_reg_sequence os02g10_common_regs[] = { > >>> { OS02G10_REG_PLL_DIV_CTRL, 0x0a}, > >>> { OS02G10_REG_PLL_DCTL_BIAS_CTRL, 0x04}, > > > > ... > > > >>> static const struct v4l2_subdev_video_ops os02g10_video_ops = { > >>> @@ -645,6 +684,7 @@ static const struct v4l2_subdev_pad_ops os02g10_pad_ops = { > >>> .get_fmt = v4l2_subdev_get_fmt, > >>> .set_fmt = os02g10_set_pad_format, > >>> .get_selection = os02g10_get_selection, > >>> + .set_selection = os02g10_set_selection, > >>> .enum_frame_size = os02g10_enum_frame_size, > >>> .enable_streams = os02g10_enable_streams, > >>> .disable_streams = os02g10_disable_streams, > >> > >> I understand that this change is written by another person, and likely > >> it is not squashed with the previous one to preserve authorship, however > >> it significantly rewrites the change already found in the series. > > > > I don't think this patch significantly rewrites the previous changes. Its > > main purpose is to introduce a crop rectangle and implement set_selection(), > > allowing userspace to stream arbitrary resolutions within the sensor limits > > instead of being restricted to 1920x1080. > > What are these "sensor limits" number? Are they defined in this 3/3 change? > For whatever reason I can't find it, but I may be blind. The sensor limits are defined by os02g10_active_area and enforced in os02g10_set_selection(). The crop rectangle cannot extend beyond the active sensor area, and the minimum crop size is also constrained in the same function. + rect.width = clamp_t(unsigned int, ALIGN(sel->r.width, 2), + OS02G10_WINDOW_WIDTH_MIN, + os02g10_active_area.width); + rect.height = clamp_t(unsigned int, ALIGN(sel->r.height, 2), + OS02G10_WINDOW_HEIGHT_MIN, + os02g10_active_area.height); > >> I don't see information about the maximum supported frame height/width > >> or default VTS setting etc. anymore, for me it's hard to say, if > >> this kind of information can be dropped with no consequences in runtime. > > > > The maximum supported width and height are still defined and enforced in > > os02g10_set_selection(). The default VTS handling is implemented in > > os02g10_set_pad_format(), where VBLANK is adjusted to maintain 30 fps for > > the selected resolution. So I don't believe any information has been dropped. > > > >> Probably this 3/3 change will break a quick inclusion of the sensor > >> driver, you may consider to exlcude it from the series now, and publish > >> it afterwards. > > > > Regarding whether patch 3/3 should be included in the current series, I am > > happy to leave that decision to Sakari. If needed, this patch can be merged > > separately once the new raw sensor model is finalized. > > > > This patch was created based on Laurent's suggestion. > > > > Link: https://lore.kernel.org/linux-media/20260414084952.217215-1-elgin.perumbilly@siliconsignals.io/T/#t > > > > That's a helpful reference to the previous patch review, thank you. > Likely I don't have the whole picture of the new advances in linux-media. No worries, and thank you for taking the time to review the series and provide feedback. Best Regards, Tarang ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v3 3/3] media: i2c: os02g10: implement crop handling with set_selection 2026-04-24 9:25 ` [PATCH v3 3/3] media: i2c: os02g10: implement crop handling with set_selection Elgin Perumbilly 2026-06-12 10:34 ` Vladimir Zapolskiy @ 2026-06-18 11:47 ` Sakari Ailus 2026-06-18 13:02 ` Laurent Pinchart 1 sibling, 1 reply; 27+ messages in thread From: Sakari Ailus @ 2026-06-18 11:47 UTC (permalink / raw) To: Elgin Perumbilly Cc: laurent.pinchart, Tarang Raval, Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Hans Verkuil, Hans de Goede, Vladimir Zapolskiy, Mehdi Djait, Sylvain Petinot, Benjamin Mugnier, Bryan O'Donoghue, Heimir Thor Sverrisson, Hardevsinh Palaniya, linux-media, devicetree, linux-kernel Hi Elgin, On Fri, Apr 24, 2026 at 02:55:47PM +0530, Elgin Perumbilly wrote: > From: Tarang Raval <tarang.raval@siliconsignals.io> > > Add crop support to os02g10 by implementing .set_selection() and > storing the crop rectangle in subdev state. > > Initialize the default crop to the active area, make set_fmt() use the > current crop, and update the output format when the crop size changes. > Also program the sensor window from the active crop/format state instead > of using the fixed supported_modes entry. > > This allows userspace to configure the sensor crop window explicitly. Please wait for the Common Raw Sensor Model patches to be merged before adding this -- we don't have an established way to configure cropping before that. Some drivers might do something but it's all a bit haphazard. -- Kind regards, Sakari Ailus ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v3 3/3] media: i2c: os02g10: implement crop handling with set_selection 2026-06-18 11:47 ` Sakari Ailus @ 2026-06-18 13:02 ` Laurent Pinchart 2026-06-18 13:36 ` Sakari Ailus 0 siblings, 1 reply; 27+ messages in thread From: Laurent Pinchart @ 2026-06-18 13:02 UTC (permalink / raw) To: Sakari Ailus Cc: Elgin Perumbilly, Tarang Raval, Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Hans Verkuil, Hans de Goede, Vladimir Zapolskiy, Mehdi Djait, Sylvain Petinot, Benjamin Mugnier, Bryan O'Donoghue, Heimir Thor Sverrisson, Hardevsinh Palaniya, linux-media, devicetree, linux-kernel On Thu, Jun 18, 2026 at 02:47:23PM +0300, Sakari Ailus wrote: > Hi Elgin, > > On Fri, Apr 24, 2026 at 02:55:47PM +0530, Elgin Perumbilly wrote: > > From: Tarang Raval <tarang.raval@siliconsignals.io> > > > > Add crop support to os02g10 by implementing .set_selection() and > > storing the crop rectangle in subdev state. > > > > Initialize the default crop to the active area, make set_fmt() use the > > current crop, and update the output format when the crop size changes. > > Also program the sensor window from the active crop/format state instead > > of using the fixed supported_modes entry. > > > > This allows userspace to configure the sensor crop window explicitly. > > Please wait for the Common Raw Sensor Model patches to be merged before > adding this -- we don't have an established way to configure cropping > before that. Some drivers might do something but it's all a bit haphazard. Does it mean we should wait for the raw sensor model to merge this driver ? Many existing drivers configure analog crop the same way, through the crop selection rectangle on the source pad. Is there harm in doing so already ? I wouldn't enable binning and skipping yet, that I agree with. -- Regards, Laurent Pinchart ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v3 3/3] media: i2c: os02g10: implement crop handling with set_selection 2026-06-18 13:02 ` Laurent Pinchart @ 2026-06-18 13:36 ` Sakari Ailus 0 siblings, 0 replies; 27+ messages in thread From: Sakari Ailus @ 2026-06-18 13:36 UTC (permalink / raw) To: Laurent Pinchart Cc: Elgin Perumbilly, Tarang Raval, Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Hans Verkuil, Hans de Goede, Vladimir Zapolskiy, Mehdi Djait, Sylvain Petinot, Benjamin Mugnier, Bryan O'Donoghue, Heimir Thor Sverrisson, Hardevsinh Palaniya, linux-media, devicetree, linux-kernel Hi Laurent, On Thu, Jun 18, 2026 at 04:02:53PM +0300, Laurent Pinchart wrote: > On Thu, Jun 18, 2026 at 02:47:23PM +0300, Sakari Ailus wrote: > > Hi Elgin, > > > > On Fri, Apr 24, 2026 at 02:55:47PM +0530, Elgin Perumbilly wrote: > > > From: Tarang Raval <tarang.raval@siliconsignals.io> > > > > > > Add crop support to os02g10 by implementing .set_selection() and > > > storing the crop rectangle in subdev state. > > > > > > Initialize the default crop to the active area, make set_fmt() use the > > > current crop, and update the output format when the crop size changes. > > > Also program the sensor window from the active crop/format state instead > > > of using the fixed supported_modes entry. > > > > > > This allows userspace to configure the sensor crop window explicitly. > > > > Please wait for the Common Raw Sensor Model patches to be merged before > > adding this -- we don't have an established way to configure cropping > > before that. Some drivers might do something but it's all a bit haphazard. > > Does it mean we should wait for the raw sensor model to merge this > driver ? I only suggested this for the 3rd patch. > > Many existing drivers configure analog crop the same way, through the > crop selection rectangle on the source pad. Is there harm in doing so > already ? I wouldn't enable binning and skipping yet, that I agree with. Which ones? -- Regards, Sakari Ailus ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v3 0/3] media: i2c: Add os02g10 camera sensor driver 2026-04-24 9:25 [PATCH v3 0/3] media: i2c: Add os02g10 camera sensor driver Elgin Perumbilly ` (2 preceding siblings ...) 2026-04-24 9:25 ` [PATCH v3 3/3] media: i2c: os02g10: implement crop handling with set_selection Elgin Perumbilly @ 2026-04-24 12:14 ` Sakari Ailus 2026-04-24 13:28 ` Elgin Perumbilly 2026-05-21 5:15 ` Elgin Perumbilly 2026-06-11 7:39 ` Elgin Perumbilly 5 siblings, 1 reply; 27+ messages in thread From: Sakari Ailus @ 2026-04-24 12:14 UTC (permalink / raw) To: Elgin Perumbilly Cc: laurent.pinchart, Tarang Raval, Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Hans Verkuil, Hans de Goede, Vladimir Zapolskiy, Mehdi Djait, Benjamin Mugnier, Sylvain Petinot, Bryan O'Donoghue, Heimir Thor Sverrisson, Hardevsinh Palaniya, Himanshu Bhavani, Svyatoslav Ryhel, linux-media, devicetree, linux-kernel Hi Elgin, On Fri, Apr 24, 2026 at 02:55:44PM +0530, Elgin Perumbilly wrote: > Patch 1/3: > - Drop the fixed data-lanes property, as the sensor supports only a 2-lane MIPI interface. Does it really say that in the datasheet? To me this looks like a driver property. -- Regards, Sakari Ailus ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v3 0/3] media: i2c: Add os02g10 camera sensor driver 2026-04-24 12:14 ` [PATCH v3 0/3] media: i2c: Add os02g10 camera sensor driver Sakari Ailus @ 2026-04-24 13:28 ` Elgin Perumbilly 2026-04-25 9:42 ` Krzysztof Kozlowski 0 siblings, 1 reply; 27+ messages in thread From: Elgin Perumbilly @ 2026-04-24 13:28 UTC (permalink / raw) To: Sakari Ailus Cc: laurent.pinchart@ideasonboard.com, Tarang Raval, Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Hans Verkuil, Hans de Goede, Vladimir Zapolskiy, Mehdi Djait, Benjamin Mugnier, Sylvain Petinot, Bryan O'Donoghue, Heimir Thor Sverrisson, Hardevsinh Palaniya, Himanshu Bhavani, Svyatoslav Ryhel, linux-media@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org Hi Sakari, > On Fri, Apr 24, 2026 at 02:55:44PM +0530, Elgin Perumbilly wrote: > > Patch 1/3: > > - Drop the fixed data-lanes property, as the sensor supports only a 2-lane MIPI interface. > > Does it really say that in the datasheet? To me this looks like a driver > property. Yes, I have checked this. The sensor supports only a 2-lane MIPI interface. There is no explicit statement in the datasheet saying that 1-lane mode is unsupported. However, after reviewing all available registers, I did not find any configuration that allows switching to a 1-lane mode. Additionally, the datasheet provides no indication or guidance for 1-lane operation. Based on this, I conclude that the sensor does not support a 1-lane configuration. If you still prefer, I can keep the data-lanes property as is and not drop it. Best Regards, Elgin ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v3 0/3] media: i2c: Add os02g10 camera sensor driver 2026-04-24 13:28 ` Elgin Perumbilly @ 2026-04-25 9:42 ` Krzysztof Kozlowski 0 siblings, 0 replies; 27+ messages in thread From: Krzysztof Kozlowski @ 2026-04-25 9:42 UTC (permalink / raw) To: Elgin Perumbilly Cc: Sakari Ailus, laurent.pinchart@ideasonboard.com, Tarang Raval, Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Hans Verkuil, Hans de Goede, Vladimir Zapolskiy, Mehdi Djait, Benjamin Mugnier, Sylvain Petinot, Bryan O'Donoghue, Heimir Thor Sverrisson, Hardevsinh Palaniya, Himanshu Bhavani, Svyatoslav Ryhel, linux-media@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org On Fri, Apr 24, 2026 at 01:28:43PM +0000, Elgin Perumbilly wrote: > Hi Sakari, > > > On Fri, Apr 24, 2026 at 02:55:44PM +0530, Elgin Perumbilly wrote: > > > Patch 1/3: > > > - Drop the fixed data-lanes property, as the sensor supports only a 2-lane MIPI interface. > > > > Does it really say that in the datasheet? To me this looks like a driver > > property. > > Yes, I have checked this. The sensor supports only a 2-lane MIPI interface. > > There is no explicit statement in the datasheet saying that 1-lane mode is > unsupported. However, after reviewing all available registers, I did not find > any configuration that allows switching to a 1-lane mode. > > Additionally, the datasheet provides no indication or guidance for 1-lane > operation. Based on this, I conclude that the sensor does not support a > 1-lane configuration. > > If you still prefer, I can keep the data-lanes property as is and not drop it. To me it feels like only 2-lane is supported, so fine by me. Best regards, Krzysztof ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v3 0/3] media: i2c: Add os02g10 camera sensor driver 2026-04-24 9:25 [PATCH v3 0/3] media: i2c: Add os02g10 camera sensor driver Elgin Perumbilly ` (3 preceding siblings ...) 2026-04-24 12:14 ` [PATCH v3 0/3] media: i2c: Add os02g10 camera sensor driver Sakari Ailus @ 2026-05-21 5:15 ` Elgin Perumbilly 2026-06-11 7:39 ` Elgin Perumbilly 5 siblings, 0 replies; 27+ messages in thread From: Elgin Perumbilly @ 2026-05-21 5:15 UTC (permalink / raw) To: laurent.pinchart@ideasonboard.com Cc: Tarang Raval, Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Hans Verkuil, Hans de Goede, Vladimir Zapolskiy, Mehdi Djait, Benjamin Mugnier, Sylvain Petinot, Bryan O'Donoghue, Heimir Thor Sverrisson, Hardevsinh Palaniya, Himanshu Bhavani, Svyatoslav Ryhel, linux-media@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org Hi Laurent, >The following features are supported: >- Manual exposure an gain control support. >- vblank/hblank control support. >- vflip/hflip control support >- Test pattern control support. >- Dynamic mode configuration (e.g. up to 1920 x 1080 @ 30 fps, SBGGR10) > >The driver is tested on mainline branch v7.0-rc2 on IMX8MP Debix Model a. > >v4l2-compliance 1.31.0-5387, 64 bits, 64-bit time_t >v4l2-compliance SHA: 5508bc4301ac 2025-08-25 08:14:22 > >Compliance test for device /dev/v4l-subdev3: > >Driver Info: > Driver version : 7.0.0 > Capabilities : 0x00000000 > Client Capabilities: 0x0000000000000002 >interval-uses-which >Required ioctls: > test VIDIOC_SUDBEV_QUERYCAP: OK > test invalid ioctls: OK > >Allow for multiple opens: > test second /dev/v4l-subdev3 open: OK > test VIDIOC_SUBDEV_QUERYCAP: OK > test for unlimited opens: OK > >Debug ioctls: > test VIDIOC_LOG_STATUS: OK (Not Supported) > >Input ioctls: > test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported) > test VIDIOC_G/S_FREQUENCY: OK (Not Supported) > test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported) > test VIDIOC_ENUMAUDIO: OK (Not Supported) > test VIDIOC_G/S/ENUMINPUT: OK (Not Supported) > test VIDIOC_G/S_AUDIO: OK (Not Supported) > Inputs: 0 Audio Inputs: 0 Tuners: 0 > >Output ioctls: > test VIDIOC_G/S_MODULATOR: OK (Not Supported) > test VIDIOC_G/S_FREQUENCY: OK (Not Supported) > test VIDIOC_ENUMAUDOUT: OK (Not Supported) > test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported) > test VIDIOC_G/S_AUDOUT: OK (Not Supported) > Outputs: 0 Audio Outputs: 0 Modulators: 0 > >Input/Output configuration ioctls: > test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported) > test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported) > test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported) > test VIDIOC_G/S_EDID: OK (Not Supported) > >Control ioctls: > test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK > test VIDIOC_QUERYCTRL: OK > test VIDIOC_G/S_CTRL: OK > test VIDIOC_G/S/TRY_EXT_CTRLS: OK > test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK > test VIDIOC_G/S_JPEGCOMP: OK (Not Supported) > Standard Controls: 12 Private Controls: 0 > >Format ioctls: > test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK (Not Supported) > test VIDIOC_G/S_PARM: OK (Not Supported) > test VIDIOC_G_FBUF: OK (Not Supported) > test VIDIOC_G_FMT: OK (Not Supported) > test VIDIOC_TRY_FMT: OK (Not Supported) > test VIDIOC_S_FMT: OK (Not Supported) > test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported) > test Cropping: OK (Not Supported) > test Composing: OK (Not Supported) > test Scaling: OK (Not Supported) > >Codec ioctls: > test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported) > test VIDIOC_G_ENC_INDEX: OK (Not Supported) > test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported) > >Buffer ioctls: > test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK (Not Supported) > test CREATE_BUFS maximum buffers: OK > test VIDIOC_REMOVE_BUFS: OK > test VIDIOC_EXPBUF: OK (Not Supported) > test Requests: OK (Not Supported) > test blocking wait: OK (Not Supported) > >Total for device /dev/v4l-subdev3: 46, Succeeded: 46, Failed: 0, Warnings: 0 > >V2 -> V3 > >Patch 1/3: >- Drop the fixed data-lanes property, as the sensor supports only a 2-lane MIPI interface. > >Patch 2/3: >- Use regmap paging features for page selection. >- Use sd instead of _sd. >- Remove an extra blank line. >- Remove hblank control, as it is not used to set hblank on this sensor. >- Remove an unnecessary error check. >- Reduce the scope of the ret variable in os02g10_set_pad_format(). >- Rely on the control value for link_freq_index. >- Call os02g10_parse_endpoint() before initializing regulators, GPIOs, and the clock in probe(). > >Add new Patch 3/3: >- Add dynamic mode configuration instead of using a hardcoded specific mode, as suggested by Laurent. > >V1 -> V2 > >patch 1/2: >- Corrected $id to ovti,os02g10.yaml >- Fixed data-lanes property structure to use proper items format > >patch 2/2: >- Mode-specific register writes handled dynamically. >- Moved page 2 registers into a dedicated page 2 block. >- Used pm_runtime_get_if_active() in set_ctrls. >- Resolved negative hblank issue. >- Improved error messages in disable_stream. >- Updated V4L2_SEL_TGT_CROP and V4L2_SEL_TGT_CROP_BOUNDS. >- Added my name to the author list. Could you please review our patch, when you have some time? Best Regards, Elgin ^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v3 0/3] media: i2c: Add os02g10 camera sensor driver 2026-04-24 9:25 [PATCH v3 0/3] media: i2c: Add os02g10 camera sensor driver Elgin Perumbilly ` (4 preceding siblings ...) 2026-05-21 5:15 ` Elgin Perumbilly @ 2026-06-11 7:39 ` Elgin Perumbilly 5 siblings, 0 replies; 27+ messages in thread From: Elgin Perumbilly @ 2026-06-11 7:39 UTC (permalink / raw) To: sakari.ailus@linux.intel.com, laurent.pinchart@ideasonboard.com Cc: Tarang Raval, Mauro Carvalho Chehab, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Hans Verkuil, Hans de Goede, Vladimir Zapolskiy, Mehdi Djait, Benjamin Mugnier, Sylvain Petinot, Bryan O'Donoghue, Heimir Thor Sverrisson, Hardevsinh Palaniya, Himanshu Bhavani, Svyatoslav Ryhel, linux-media@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org Hi Laurent, Sakari > The following features are supported: > - Manual exposure an gain control support. > - vblank/hblank control support. > - vflip/hflip control support > - Test pattern control support. > - Dynamic mode configuration (e.g. up to 1920 x 1080 @ 30 fps, SBGGR10) > > The driver is tested on mainline branch v7.0-rc2 on IMX8MP Debix Model a. Could you please review the driver now ?? Best Regards, Elgin ^ permalink raw reply [flat|nested] 27+ messages in thread
end of thread, other threads:[~2026-06-18 13:37 UTC | newest] Thread overview: 27+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-04-24 9:25 [PATCH v3 0/3] media: i2c: Add os02g10 camera sensor driver Elgin Perumbilly 2026-04-24 9:25 ` [PATCH v3 1/3] dt-bindings: media: i2c: Add os02g10 sensor Elgin Perumbilly 2026-04-25 9:43 ` Krzysztof Kozlowski 2026-06-12 9:35 ` Vladimir Zapolskiy 2026-04-24 9:25 ` [PATCH v3 2/3] media: i2c: add os02g10 image sensor driver Elgin Perumbilly 2026-06-12 10:12 ` Vladimir Zapolskiy 2026-06-18 6:22 ` Elgin Perumbilly 2026-06-18 10:56 ` sakari.ailus 2026-06-18 11:06 ` Vladimir Zapolskiy 2026-06-18 11:46 ` Laurent Pinchart 2026-06-18 11:43 ` Sakari Ailus 2026-06-18 12:27 ` Tarang Raval 2026-06-18 12:58 ` Laurent Pinchart 2026-06-18 13:01 ` Laurent Pinchart 2026-04-24 9:25 ` [PATCH v3 3/3] media: i2c: os02g10: implement crop handling with set_selection Elgin Perumbilly 2026-06-12 10:34 ` Vladimir Zapolskiy 2026-06-12 11:41 ` Tarang Raval 2026-06-12 13:16 ` Vladimir Zapolskiy 2026-06-12 14:20 ` Tarang Raval 2026-06-18 11:47 ` Sakari Ailus 2026-06-18 13:02 ` Laurent Pinchart 2026-06-18 13:36 ` Sakari Ailus 2026-04-24 12:14 ` [PATCH v3 0/3] media: i2c: Add os02g10 camera sensor driver Sakari Ailus 2026-04-24 13:28 ` Elgin Perumbilly 2026-04-25 9:42 ` Krzysztof Kozlowski 2026-05-21 5:15 ` Elgin Perumbilly 2026-06-11 7:39 ` Elgin Perumbilly
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox