media: Add OmniVision OV32C4 sensor driver
Subsystems (3)
v2 cover media: Add OmniVision OV32C4 sensor driver
Hi,
v2 of the OV32C4 sensor driver. v1 is at
https://lore.kernel.org/linux-media/[email protected]/
Changes in v2:
- dt-bindings: drop the frequency from the xvclk description - 19.2 MHz is a constraint of the platform and of this driver, not of the sensor
- drop the sentence about the modes the driver supports, and make the supplies plain "true" without descriptions (Conor Dooley).
- driver: use pm_ptr() rather than pm_sleep_ptr() for dev_pm_ops, and free the embedded control handler on the probe error path (both found by the Sashiko review bot; details in the per-patch changelogs).
- all three patches: Signed-off-by placed last in the trailer block (Krzysztof Kozlowski).
No functional change on the hardware: the pm_ptr() difference is only visible with CONFIG_PM=y and CONFIG_PM_SLEEP=n, and the other fix is on an error path. The driver was rebuilt and the camera re-tested after the change.
The series adds a driver for the OmniVision OV32C4, a 32 megapixel RGBC CMOS image sensor. It ships as the under-display camera in the Lenovo Yoga Slim 9 14ILL10, where it is enumerated through ACPI (_HID "OVTI32C4") behind an INT3472 discrete control logic node and feeds an Intel IPU7. The last patch adds the sensor to ipu-bridge; without it the bridge builds no fwnode graph for the sensor and the driver never binds.
The driver supports 3264x1840 at 30 fps, 10-bit Bayer, 4 CSI-2 lanes at a 400 MHz link frequency, with exposure, analogue gain, digital gain, vblank, hblank and flip controls, runtime PM and .get_selection.
Tested on the machine above: the sensor probes, streams continuously at a measured 30.00 fps, and the frames arrive complete (60 frames = 720691200 bytes = 60 * 3264 * 1840 * 2, V4L2_PIX_FMT_SGRBG10 carrying one 16-bit sample per pixel). The full path up to a processed image was exercised with libcamera's software ISP.
There is no public datasheet for this sensor, so a note on where the numbers come from, since that is the first thing a reviewer will want to know:
- The mode register table is the verbatim initialisation sequence from the vendor Windows driver: 1787 writes, strictly ascending, copied 1:1 with nothing added or reordered.
- The register meanings the controls depend on (exposure 0x3500, analogue gain 0x3508, digital gain 0x350a, VTS 0x380e, and the rule exposure_max = VTS - 32) were read out of the same binary and then confirmed against the values the chip reports.
- The derived timings were checked against reality: the computed
320000000 / (4080 * 2614) = 30.005 fps matches the measured 30.00.
- The 6560x4928 pixel array and the 6528x4896 active area reported by .get_selection follow from the window registers of the mode table and agree with the vendor's published product brief.
The gain ranges in this series were measured on the sensor, not
inherited: the vendor driver clamps gain a layer above and carries no limits of its own, and the obvious donor - ov13b10, same registers - puts analogue unity at 0x80, which turned out to be wrong here. Analogue response is exactly proportional between 0x100 and 0x7c0 (1x to 7.75x, 0x100 also being the power-up value); digital gain is proportional with 1024 as unity and clips to black one step above 16383. I mention it because these are the kind of constants that get copied between OmniVision drivers unchecked.
Flip handling was measured the same way. This sensor preserves the Bayer order across mirror and flip, so the driver only toggles the bits and the media bus code never changes. I mention it because ov13b10 compensates the crop window by one pixel on the same registers to undo a Bayer shift; doing that here introduces one rather than removing it, which is easy to copy across by accident.
Two things I would like reviewers to look at, because I am not confident they are right:
1) The sensor core rail is gated by a companion chip that ACPI lists as the second I2C resource of _CRS and that ipu-bridge instantiates as a VCM. Without a single write of 0x04 to register 0x1001 on that chip, the sensor does not answer on I2C at all. The driver currently does that write itself with a bare i2c_transfer(), deliberately not claiming the address so the VCM driver can still have it.
I am aware this bypasses both the I2C device model and the regulator framework, and that the architecturally correct answer is probably a regulator provided by the VCM driver, consumed here as dvdd-supply. I did not want to redesign ipu-bridge's VCM handling as part of an initial sensor submission, so I am asking rather than assuming. If the bare write is not acceptable, I am happy to do it properly - I would just like guidance on the shape.
2) While streaming, the IPU7 receiver reports exactly one
csi2-0 error: Received packet is too long
per frame. It is not a lost or corrupted frame: bit 2 of the D-PHY error register ("unrecognised data type") never fires, the image data is complete, and the byte count above is exact.
I could not make it go away from the sensor side. All of 0x4800-0x48FF and 0x3800-0x38FF were swept register by register - 512 measurements - and no value silences it while streaming continues. The vendor driver does not suppress it either; it exposes EnableEmbeddedData next to the MIPI link parameters and the Windows IPU stack has a matching GetEmbeddedData path, i.e. Windows receives that extra packet rather than turning it off.
Receiving it on Linux would need metadata capture in the IPU7 driver, which is marked as a TODO there today, plus a second frame descriptor entry - and adding that entry alone makes the IPU7 wait for a second capture node that does not exist in the graph, so streaming stops. I therefore left the driver reporting a single stream and am documenting the warning here rather than papering over it.
Tooling disclosure, as asked for by Documentation/process/generated-content.rst:
this series was written with the help of an AI coding assistant (Claude, Anthropic; claude-opus-5 for the early work, claude-fable-5 for the rest) in an extended interactive session. The assistant drafted the driver source, the binding and this cover letter from my descriptions of the hardware and of the vendor driver; every register meaning, gain range and timing in it was measured by me on the sensor as described above, I ran all of the tests, and I have reviewed and understand all of the code and take responsibility for it. The mode register table was copied 1:1 from the vendor driver, not generated. Static checks used: checkpatch.pl --strict, sparse (C=1), W=1 and dt_binding_check. The individual patches carry Assisted-by tags.
The series applies to media_stage.git; base-commit is below.
Thanks,
Robert
Robert Bozik (3):
dt-bindings: media: i2c: Add OmniVision OV32C4
media: i2c: Add driver for OmniVision OV32C4
media: ipu-bridge: Add OmniVision OV32C4
.../bindings/media/i2c/ovti,ov32c4.yaml | 105 + MAINTAINERS | 8 + drivers/media/i2c/Kconfig | 11 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/ov32c4.c | 2839 +++++++++++++++++ drivers/media/pci/intel/ipu-bridge.c | 10 + 6 files changed, 2974 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/i2c/ovti,ov32c4.yaml create mode 100644 drivers/media/i2c/ov32c4.c
base-commit: 4900cad020c0580dfb1be27776ff10a4ef110cfa
Signature · 1 line
2.53.0
PATCH v2 1/3 dt-bindings: media: i2c: Add OmniVision OV32C4
Add a binding for the OmniVision OV32C4, a 32 megapixel RGBC CMOS image sensor with on-chip 4-cell fusion, a 4-lane MIPI CSI-2 D-PHY transmitter and the standard SCCB command interface.
The sensor ships in laptops as an under-display camera, where it is described by ACPI; the binding follows ovti,ov08x40.yaml, which covers the closest relative already in tree.
---
Changes in v2:
- Drop the frequency from the xvclk description. 19.2 MHz is what this platform provides and what the driver's register tables assume, not a requirement of the sensor; the example keeps it in assigned-clock-rates (Conor Dooley).
- avdd-supply, dovdd-supply and dvdd-supply are now plain "true"; the names already say which rail is which (Conor Dooley).
- Drop the sentence listing the modes the driver supports. The binding describes the hardware.
- Signed-off-by placed last in the trailer block (Krzysztof Kozlowski).
.../bindings/media/i2c/ovti,ov32c4.yaml | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/i2c/ovti,ov32c4.yaml
Documentation/devicetree/bindings/media/i2c/ovti,ov32c4.yaml new file +105 -0
@@ -0,0 +1,105 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/ovti,ov32c4.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: OmniVision OV32C4 Image Sensor + +maintainers: + - Robert Bozik <[email protected]> + +description: + The OmniVision OV32C4 is a 32 megapixel RGBC (red, green, blue and clear) + CMOS image sensor in a 4-cell arrangement, with on-chip 4-cell fusion and + binning. It has a 6528x4896 active pixel array, a 4-lane MIPI CSI-2 D-PHY + transmitter and the standard SCCB command interface. + +allOf: + - $ref: /schemas/media/video-interface-devices.yaml# + +properties: + compatible: + const: ovti,ov32c4 + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + description: Input clock (XVCLK). + + avdd-supply: true + + dovdd-supply: true + + dvdd-supply: true + + reset-gpios: + maxItems: 1 + description: Active low GPIO connected to the XSHUTDOWN pad of the sensor. + + port: + $ref: /schemas/graph.yaml#/$defs/port-base + additionalProperties: false + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + additionalProperties: false + + properties: + data-lanes: + items: + - const: 1 + - const: 2 + - const: 3 + - const: 4 + link-frequencies: true + remote-endpoint: true + + required: + - data-lanes + - link-frequencies + - remote-endpoint + +required: + - compatible + - reg + - clocks + - port + +unevaluatedProperties: false + +examples: + - | + #include <dt-bindings/gpio/gpio.h> + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + camera@36 { + compatible = "ovti,ov32c4"; + reg = <0x36>; + + clocks = <&ov32c4_clk>; + assigned-clocks = <&ov32c4_clk>; + assigned-clock-rates = <19200000>; + + avdd-supply = <&vreg_2p8>; + dovdd-supply = <&vreg_1p8>; + dvdd-supply = <&vreg_1p1>; + + reset-gpios = <&tlmm 111 GPIO_ACTIVE_LOW>; + + port { + ov32c4_ep: endpoint { + remote-endpoint = <&csiphy0_ep>; + data-lanes = <1 2 3 4>; + link-frequencies = /bits/ 64 <400000000>; + }; + }; + }; + }; +...
Signature · 1 line
2.53.0
Re: [PATCH v2 1/3] dt-bindings: media: i2c: Add OmniVision OV32C4
pw-bot: not-applicable
PATCH v2 2/3 media: i2c: Add driver for OmniVision OV32C4
In reply to Robert BozikThe OV32C4 is a 32 megapixel RGBC CMOS image sensor with on-chip 4-cell fusion and binning, connected over 4 MIPI CSI-2 D-PHY lanes. It is used as an under-display camera in laptops, where it is enumerated through ACPI (_HID "OVTI32C4") and paired with an Intel IPU.
The driver supports 3264x1840 at 30 fps in 10-bit Bayer at a link frequency of 400 MHz, with exposure, analogue gain, digital gain, vblank, hblank and horizontal and vertical flip controls, runtime PM and .get_selection reporting the 6560x4928 pixel array.
There is no public datasheet for this sensor. The mode register table is the verbatim initialisation sequence from the vendor driver; the meaning of the individual registers used by the controls was established by correlating that binary with the values read back from the chip, and the derived timings were verified against a measured 30.00 fps.
Three things were measured on the sensor rather than inherited from the OmniVision drivers already in tree, because they turned out to differ:
- The analogue gain range. Response is proportional to the register value from 0x100 to 0x7c0, so 0x100 is unity gain, which is also what the chip powers up with. Below 0x100 the output stops tracking the value written - 0xe0 and 0xff give the same result - and above 0x7c0 it no longer increases. ov13b10 uses the same register with unity at 0x80; that does not hold here.
- The digital gain range. The register is proportional with 1024 as unity - writing 2x, 4x and 8x of the power-up value gives 1.99x, 3.86x and 7.62x, the shortfall at the top being sensor clipping - and the field is 14 bits wide: one step above 16383 produces a black frame, so the control stops there.
- The effect of flipping on the Bayer order. This sensor keeps the order across both mirror and flip, so the driver only toggles the bits and the media bus code stays SGRBG10. ov13b10 shifts the crop window by one pixel to undo a Bayer shift on the same registers; doing that here introduces one instead of removing it.
---
Changes in v2:
- Use pm_ptr() instead of pm_sleep_ptr() for dev_pm_ops. DEFINE_RUNTIME_DEV_PM_OPS also fills in the runtime callbacks, and pm_sleep_ptr() is gated on CONFIG_PM_SLEEP, so with CONFIG_PM=y and CONFIG_PM_SLEEP=n .pm would be NULL and the sensor would never be powered down after probe. ov13b10, ov08d10 and ov2735 use pm_ptr() here; ov02c10, which this driver was modelled on, does not.
- Free the embedded control handler on the probe error path. sd.ctrl_handler is only assigned at the end of probe, so an error out of ov32c4_init_controls() reached v4l2_ctrl_handler_free(NULL); the label now frees &ov32c4->ctrl_handler, which is the same pointer once probe has succeeded.
- Signed-off-by placed last in the trailer block (Krzysztof Kozlowski).
Both fixes were reported by the Sashiko review bot on v1. Neither changes behaviour on the machine this was tested on (CONFIG_PM_SLEEP=y, and the second is an error path), but both are real.
MAINTAINERS | 8 + drivers/media/i2c/Kconfig | 11 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/ov32c4.c | 2839 ++++++++++++++++++++++++++++++++++++ 4 files changed, 2859 insertions(+) create mode 100644 drivers/media/i2c/ov32c4.c
MAINTAINERS +8 -0
@@ -20008,6 +20008,14 @@ S: Maintained T: git git://linuxtv.org/media.git F: drivers/media/i2c/ov2740.c +OMNIVISION OV32C4 SENSOR DRIVER +M: Robert Bozik <[email protected]> +L: [email protected] +S: Maintained +T: git git://linuxtv.org/media.git +F: Documentation/devicetree/bindings/media/i2c/ovti,ov32c4.yaml +F: drivers/media/i2c/ov32c4.c + OMNIVISION OV4689 SENSOR DRIVER M: Mikhail Rudenko <[email protected]> L: [email protected]
drivers/media/i2c/Kconfig +11 -0
@@ -547,6 +547,17 @@ config VIDEO_OV2740 To compile this driver as a module, choose M here: the module will be called ov2740. +config VIDEO_OV32C4 + tristate "OmniVision OV32C4 sensor support" + depends on ACPI || COMPILE_TEST + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the OmniVision + OV32C4 camera. + + To compile this driver as a module, choose M here: the + module will be called ov32c4. + config VIDEO_OV4689 tristate "OmniVision OV4689 sensor support" depends on GPIOLIB
drivers/media/i2c/Makefile +1 -0
@@ -102,6 +102,7 @@ obj-$(CONFIG_VIDEO_OV2685) += ov2685.o obj-$(CONFIG_VIDEO_OV2732) += ov2732.o obj-$(CONFIG_VIDEO_OV2735) += ov2735.o obj-$(CONFIG_VIDEO_OV2740) += ov2740.o +obj-$(CONFIG_VIDEO_OV32C4) += ov32c4.o obj-$(CONFIG_VIDEO_OV4689) += ov4689.o obj-$(CONFIG_VIDEO_OV5640) += ov5640.o obj-$(CONFIG_VIDEO_OV5645) += ov5645.o
drivers/media/i2c/ov32c4.c new file +2839 -0
2,839 lines not on this page · raw message
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH v2 2/3] media: i2c: Add driver for OmniVision OV32C4
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] Potential NULL pointer dereference in ov32c4_set_format() when sd_state is NULL. --
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Potential NULL pointer dereference in ov32c4_set_format() when sd_state is NULL. --
commit 1f7b42ee8c447c7e85f922c97bb8dbc3b6847261
Author: Robert Bozik <[email protected]>
media: i2c: Add driver for OmniVision OV32C4
This commit adds a Video4Linux2 I2C sensor driver for the OmniVision OV32C4 camera. The driver supports basic streaming functionality, analogue and digital gain, and flip controls.
Quoted 5 lines
drivers/media/i2c/ov32c4.c new file +0
-0
[ ... ]
drivers/media/i2c/ov32c4.c line 2431
Quoted 10 lines
drivers/media/i2c/ov32c4.c new file +10
-0+static int ov32c4_set_format (struct v4l2_subdev *sd, +struct v4l2_subdev_state *sd_state, +struct v4l2_subdev_format *fmt) +{ +const struct ov32c4_mode *mode = &supported_modes[0 ]; +struct ov32c4 *ov32c4 =to_ov32c4 (sd); +s32 vblank_def, h_blank; + +ov32c4_update_pad_format (mode, &fmt->format); + *v4l2_subdev_state_get_format (sd_state, fmt->pad) = fmt->format;
[Severity: High]
Can this code cause a NULL pointer dereference?
If the driver is compiled without CONFIG_MEDIA_CONTROLLER, the V4L2 core does not allocate a fallback active state. A legacy bridge driver invoking v4l2_subdev_call(sd, pad, set_fmt, NULL, &fmt) can pass sd_state == NULL to ov32c4_set_format().
Because the core check_format() guard permits state == NULL when which == V4L2_SUBDEV_FORMAT_ACTIVE, this function will still execute.
When v4l2_subdev_state_get_format() is passed a NULL state, it returns NULL. The subsequent structure assignment then unconditionally dereferences it, which would lead to a kernel panic.
Quoted 5 lines
drivers/media/i2c/ov32c4.c new file +5
-0+ +if (fmt->which ==V4L2_SUBDEV_FORMAT_TRY ) +return 0 ; + + vblank_def = mode->vts_min - mode->height;
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
Re: [PATCH v2 2/3] media: i2c: Add driver for OmniVision OV32C4
Quoted 3 lines · On Fri, Aug 28, 2026, [email protected] wrote:
[Severity: High]
Potential NULL pointer dereference inov32c4_set_format()whensd_stateis NULL.
I checked this and I do not believe it is a defect here, so I have not changed the code. Please tell me if you disagree.
The mechanism is described accurately as far as it goes: check_state() only rejects a NULL state for V4L2_SUBDEV_FORMAT_TRY, so an ACTIVE call passes the guard, and __v4l2_subdev_state_get_format() does return NULL for a NULL state.
But that function opens with
if (WARN_ON_ONCE(!state)) return NULL;
so a NULL state there is treated as a bug to be reported, not as an input to be handled. And 37 drivers under drivers/media/i2c/ assign through it exactly the way this one does, none of them checking the result; the line here is identical to ov02c10.c:712, which is the driver this one was modelled on.
So if that path is reachable, it is a core problem affecting all of them and the fix belongs in the core rather than in a new sensor driver - adding a check only here would make this the one driver in the subsystem that does it. I am happy to add it if you would prefer.
Thanks,
Robert
PATCH v2 3/3 media: ipu-bridge: Add OmniVision OV32C4
In reply to Robert BozikAdd the sensor configuration for the OV32C4, matching the _HID the sensor enumerates with. Without this entry the bridge does not build a software node graph for the sensor, so its driver defers probing on the missing fwnode endpoint forever and the camera never binds.
---
Changes in v2:
- Signed-off-by placed last in the trailer block (Krzysztof Kozlowski).
- No other changes.
drivers/media/pci/intel/ipu-bridge.c | 10 ++++++++++ 1 file changed, 10 insertions(+)
drivers/media/pci/intel/ipu-bridge.c +10 -0
@@ -93,6 +93,16 @@ static const struct ipu_sensor_config ipu_supported_sensors[] = {IPU_SENSOR_CONFIG ("OVTIDB10" ,1 ,560000000 ),/* Omnivision OV2680 */ IPU_SENSOR_CONFIG ("OVTI2680" ,1 ,331200000 ), +/* +* OmniVision OV32C4. +* +* 400 MHz link frequency, i.e. 800 Mbps per lane. There is no public +* datasheet; the value comes from the vendor Windows driver, which +* carries it as MipiBps, and it is confirmed on hardware: the IPU7 +* D-PHY locks and the sensor streams 3264x1840 at a measured +* 30.00 fps, matching pixel_rate / (ppl * vts) exactly. +*/ +IPU_SENSOR_CONFIG ("OVTI32C4" ,1 ,400000000 ),/* Omnivision OV5675 */ IPU_SENSOR_CONFIG("OVTI5675", 1, 450000000),/* Omnivision OV8856 */
Signature · 1 line
2.53.0
Sakari Ailus
maintainerRe: [PATCH v2 3/3] media: ipu-bridge: Add OmniVision OV32C4
Hi Robert,
Thanks for the update.
Quoted 22 lines · Robert Bozik
Add the sensor configuration for the OV32C4, matching the
_HIDthe sensor enumerates with. Without this entry the bridge does not build a software node graph for the sensor, so its driver defers probing on the missing fwnode endpoint forever and the camera never binds.Assisted-by Claude:claude-opus-5 sparseAssisted-by Claude:claude-fable-5 sparseSigned-off-by Robert Bozik <[email protected]>---
Changes in v2:
- Signed-off-by placed last in the trailer block (Krzysztof Kozlowski).
- No other changes.drivers/media/pci/intel/ipu-bridge.c | 10 ++++++++++ 1 file changed, 10 insertions(+)drivers/media/pci/intel/ipu-bridge.c +0
-0@@ -93,6 +93,16 @@ static const struct ipu_sensor_config ipu_supported_sensors[] = { IPU_SENSOR_CONFIG("OVTIDB10", 1, 560000000),
drivers/media/pci/intel/ipu-bridge.c static const struct ipu_sensor_config ipu_supported_sensors[] = { · line 103
Quoted 10 lines
drivers/media/pci/intel/ipu-bridge.c +8
-0/* Omnivision OV2680 */ IPU_SENSOR_CONFIG ("OVTI2680" ,1 ,331200000 ), +/* +* OmniVision OV32C4. +* +* 400 MHz link frequency, i.e. 800 Mbps per lane. There is no public +* datasheet; the value comes from the vendor Windows driver, which +* carries it as MipiBps, and it is confirmed on hardware: the IPU7 +* D-PHY locks and the sensor streams 3264x1840 at a measured +* 30.00 fps, matching pixel_rate / (ppl * vts) exactly.
This bit could go to the driver but the IPU bridge isn't the right place to document this.
Quoted 5 lines
drivers/media/pci/intel/ipu-bridge.c +2
-0+*/ +IPU_SENSOR_CONFIG ("OVTI32C4" ,1 ,400000000 ),/* Omnivision OV5675 */ IPU_SENSOR_CONFIG("OVTI5675", 1, 450000000),/* Omnivision OV8856 */
Signature · 3 lines
Regards, Sakari Ailus
Re: [PATCH v2 3/3] media: ipu-bridge: Add OmniVision OV32C4
Hi Sakari,
Quoted 2 lines
This bit could go to the driver but the IPU bridge isn't the right place to document this.
Agreed. In v3 the entry gets a plain
/* Omnivision OV32C4 */
like its neighbours, and where the link frequency comes from is documented in the sensor driver instead.
Thanks,
Robert