From c43b38c001a424d4de090a1555bcb352f4669ddc Mon Sep 17 00:00:00 2001 From: Shengming Yuan Date: Sat, 28 Mar 2026 22:22:41 -0700 Subject: [PATCH] kernel: attempt to bring up c4 cam --- kernel/configs/vamos.config | 2 + kernel/dts/sdm845-comma-mici.dts | 93 ++ ...add-OmniVision-OS04C10-sensor-driver.patch | 1172 +++++++++++++++++ ...-cci-support-combined-register-reads.patch | 106 ++ 4 files changed, 1373 insertions(+) create mode 100644 kernel/patches/0012-media-i2c-add-OmniVision-OS04C10-sensor-driver.patch create mode 100644 kernel/patches/0013-i2c-qcom-cci-support-combined-register-reads.patch diff --git a/kernel/configs/vamos.config b/kernel/configs/vamos.config index 5142202..1536663 100644 --- a/kernel/configs/vamos.config +++ b/kernel/configs/vamos.config @@ -92,6 +92,8 @@ CONFIG_I2C_QCOM_CCI=y CONFIG_SDM_CAMCC_845=y CONFIG_VIDEO_QCOM_CAMSS=y CONFIG_VIDEO_OX03C10=y +CONFIG_VIDEO_OS04C10=m + # GPIO # TODO: migrate gpio.sh, lte.sh, and power_drop_monitor.py to chardev (libgpiod) diff --git a/kernel/dts/sdm845-comma-mici.dts b/kernel/dts/sdm845-comma-mici.dts index b94d34f..8307bf1 100644 --- a/kernel/dts/sdm845-comma-mici.dts +++ b/kernel/dts/sdm845-comma-mici.dts @@ -77,3 +77,96 @@ status = "okay"; vdds-supply = <&vdda_mipi_dsi0_pll>; }; + +/* Replace OX03C10 cameras with OS04C10 on cci_i2c0 */ +&cci_i2c0 { + /delete-node/ camera@36; + /delete-node/ camera@10; + + /* Camera 0: wide (OS04C10) */ + camera@36 { + compatible = "ovti,os04c10"; + reg = <0x36>; + pinctrl-names = "default"; + pinctrl-0 = <&cam0_default>; + clocks = <&clock_camcc CAM_CC_MCLK0_CLK>; + clock-names = "extclk"; + reset-gpios = <&tlmm 9 GPIO_ACTIVE_LOW>; + vana-gpios = <&tlmm 8 GPIO_ACTIVE_HIGH>; + avdd-supply = <&vreg_bob>; + dovdd-supply = <&vreg_lvs1a_1p8>; + dvdd-supply = <&camera_rear_ldo>; + + port { + os04c10_wide_ep: endpoint { + data-lanes = <0 1 2 3>; + link-frequencies = /bits/ 64 <388000000>; + remote-endpoint = <&csiphy0_ep>; + }; + }; + }; + + /* Camera 1: road (OS04C10 at different address) */ + camera@10 { + compatible = "ovti,os04c10"; + reg = <0x10>; + pinctrl-names = "default"; + pinctrl-0 = <&cam1_default>; + clocks = <&clock_camcc CAM_CC_MCLK1_CLK>; + clock-names = "extclk"; + reset-gpios = <&tlmm 7 GPIO_ACTIVE_LOW>; + vana-gpios = <&tlmm 8 GPIO_ACTIVE_HIGH>; + avdd-supply = <&vreg_bob>; + dovdd-supply = <&vreg_lvs1a_1p8>; + dvdd-supply = <&camera_rear_ldo>; + + port { + os04c10_road_ep: endpoint { + data-lanes = <0 1 2 3>; + link-frequencies = /bits/ 64 <388000000>; + remote-endpoint = <&csiphy1_ep>; + }; + }; + }; +}; + +/* Replace OX03C10 driver camera with OS04C10 on cci_i2c1 */ +&cci_i2c1 { + /delete-node/ camera@36; + + /* Camera 2: driver (OS04C10) */ + camera@36 { + compatible = "ovti,os04c10"; + reg = <0x36>; + pinctrl-names = "default"; + pinctrl-0 = <&cam2_default>; + clocks = <&clock_camcc CAM_CC_MCLK2_CLK>; + clock-names = "extclk"; + reset-gpios = <&tlmm 12 GPIO_ACTIVE_LOW>; + vana-gpios = <&tlmm 8 GPIO_ACTIVE_HIGH>; + avdd-supply = <&vreg_bob>; + dovdd-supply = <&vreg_lvs1a_1p8>; + dvdd-supply = <&camera_rear_ldo>; + + port { + os04c10_driver_ep: endpoint { + data-lanes = <0 1 2 3>; + link-frequencies = /bits/ 64 <388000000>; + remote-endpoint = <&csiphy2_ep>; + }; + }; + }; +}; + +/* Update CAMSS csiphy endpoints to point at OS04C10 sensors */ +&csiphy0_ep { + remote-endpoint = <&os04c10_wide_ep>; +}; + +&csiphy1_ep { + remote-endpoint = <&os04c10_road_ep>; +}; + +&csiphy2_ep { + remote-endpoint = <&os04c10_driver_ep>; +}; diff --git a/kernel/patches/0012-media-i2c-add-OmniVision-OS04C10-sensor-driver.patch b/kernel/patches/0012-media-i2c-add-OmniVision-OS04C10-sensor-driver.patch new file mode 100644 index 0000000..5de6f78 --- /dev/null +++ b/kernel/patches/0012-media-i2c-add-OmniVision-OS04C10-sensor-driver.patch @@ -0,0 +1,1172 @@ +diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig +index cdd7ba5da0d5..5b9d29bb7dc1 100644 +--- a/drivers/media/i2c/Kconfig ++++ b/drivers/media/i2c/Kconfig +@@ -361,6 +361,20 @@ config VIDEO_OG0VE1B + To compile this driver as a module, choose M here: the + module will be called og0ve1b. + ++config VIDEO_OS04C10 ++ tristate "OmniVision OS04C10 sensor support" ++ depends on I2C && VIDEO_DEV ++ select V4L2_CCI_I2C ++ select V4L2_FWNODE ++ select VIDEO_V4L2_SUBDEV_API ++ select MEDIA_CONTROLLER ++ help ++ This is a Video4Linux2 sensor driver for the OmniVision ++ OS04C10 camera sensor (1344x760, 12-bit, 4-lane MIPI CSI-2). ++ ++ To compile this driver as a module, choose M here: the ++ module will be called os04c10. ++ + config VIDEO_OV01A10 + tristate "OmniVision OV01A10 sensor support" + help +diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile +index 57cdd8dc96f6..034b2d3ef1f6 100644 +--- a/drivers/media/i2c/Makefile ++++ b/drivers/media/i2c/Makefile +@@ -83,6 +83,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_OS04C10) += os04c10.o + obj-$(CONFIG_VIDEO_OV01A10) += ov01a10.o + obj-$(CONFIG_VIDEO_OV02A10) += ov02a10.o + obj-$(CONFIG_VIDEO_OV02C10) += ov02c10.o +diff --git a/drivers/media/i2c/os04c10.c b/drivers/media/i2c/os04c10.c +new file mode 100644 +index 000000000000..4133a7e0d296 +--- /dev/null ++++ b/drivers/media/i2c/os04c10.c +@@ -0,0 +1,1129 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * OmniVision OS04C10 CMOS Image Sensor Driver ++ * ++ * Minimal V4L2 sensor driver for the OS04C10 (1344x760 binned, 12-bit RAW, ++ * 4-lane MIPI CSI-2 at 776 Mbps/lane). Register init sequence from ++ * openpilot camerad os04c10_registers.h. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++ ++/* MIPI link frequency: 776 Mbps/lane DDR → 388 MHz serial clock */ ++#define OS04C10_LINK_FREQ 388000000ULL ++/* Sensor external clock (MCLK) */ ++#define OS04C10_MCLK_RATE 24000000 ++/* Pixel rate = link_freq * lanes * 2 / bpp = 388e6 * 4 * 2 / 12 */ ++#define OS04C10_PIXEL_RATE 258666667ULL ++ ++/* Chip ID: reg 0x300a = high byte (0x53), reg 0x300b = low byte (0x04) */ ++#define OS04C10_REG_CHIP_ID_H CCI_REG8(0x300a) ++#define OS04C10_REG_CHIP_ID_L CCI_REG8(0x300b) ++#define OS04C10_CHIP_ID_H 0x53 ++#define OS04C10_CHIP_ID_L 0x04 ++ ++/* Streaming control */ ++#define OS04C10_REG_MODE_SELECT CCI_REG8(0x0100) ++#define OS04C10_MODE_STANDBY 0x00 ++#define OS04C10_MODE_STREAMING 0x01 ++ ++/* Software reset */ ++#define OS04C10_REG_SW_RESET CCI_REG8(0x0103) ++ ++/* Coarse integration time (long exposure) */ ++#define OS04C10_REG_EXPOSURE_H CCI_REG8(0x3501) ++#define OS04C10_REG_EXPOSURE_L CCI_REG8(0x3502) ++ ++/* Analog gain */ ++#define OS04C10_REG_AGAIN_H CCI_REG8(0x3508) ++#define OS04C10_REG_AGAIN_L CCI_REG8(0x3509) ++ ++/* Frame length (VTS) */ ++#define OS04C10_REG_VTS_H CCI_REG8(0x380e) ++#define OS04C10_REG_VTS_L CCI_REG8(0x380f) ++ ++/* Output dimensions (2x2 binned mode) */ ++#define OS04C10_NATIVE_WIDTH 1344 ++#define OS04C10_NATIVE_HEIGHT 760 ++ ++/* HTS = 0x0bac = 2988, VTS = 0x069c = 1692 (from init registers) */ ++#define OS04C10_HTS_DEFAULT 2988 ++#define OS04C10_VTS_DEFAULT 0x069c ++#define OS04C10_VTS_MAX 0xFFFF ++ ++/* Exposure range: 2 to VTS-8 lines */ ++#define OS04C10_EXPOSURE_MIN 2 ++#define OS04C10_EXPOSURE_MAX 1684 ++#define OS04C10_EXPOSURE_DEFAULT 5 ++ ++/* Analog gain register: 0x080 = 1.0x, 0x7C0 = 15.5x */ ++#define OS04C10_AGAIN_MIN 0x080 ++#define OS04C10_AGAIN_MAX 0x7C0 ++#define OS04C10_AGAIN_DEFAULT 0x080 ++ ++#define to_os04c10(_sd) container_of(_sd, struct os04c10, sd) ++ ++struct os04c10_reg_list { ++ u32 num_of_regs; ++ const struct cci_reg_sequence *regs; ++}; ++ ++struct os04c10_mode { ++ u32 width; ++ u32 height; ++ u32 link_freq_index; ++ u32 code; ++ u32 hblank; ++ u32 vblank; ++ const struct os04c10_reg_list reg_list; ++}; ++ ++/* ++ * Full init register sequence, converted from openpilot ++ * os04c10_registers.h init_array_os04c10[]. ++ * Based on DP_2688X1520_NEWSTG_MIPI0776Mbps_30FPS_FOURLANE ++ * with 2x2 binning → 1344x760 output. ++ * All registers are 8-bit writes (data_word=false). ++ */ ++static const struct cci_reg_sequence os04c10_mode_default[] = { ++ /* Software reset */ ++ { CCI_REG8(0x0103), 0x01 }, ++ ++ /* PLL + clocks */ ++ { CCI_REG8(0x0301), 0xe4 }, ++ { CCI_REG8(0x0303), 0x01 }, ++ { CCI_REG8(0x0305), 0xb6 }, ++ { CCI_REG8(0x0306), 0x01 }, ++ { CCI_REG8(0x0307), 0x17 }, ++ { CCI_REG8(0x0323), 0x04 }, ++ { CCI_REG8(0x0324), 0x01 }, ++ { CCI_REG8(0x0325), 0x62 }, ++ ++ { CCI_REG8(0x3012), 0x06 }, ++ { CCI_REG8(0x3013), 0x02 }, ++ { CCI_REG8(0x3016), 0x72 }, ++ { CCI_REG8(0x3021), 0x03 }, ++ { CCI_REG8(0x3106), 0x21 }, ++ { CCI_REG8(0x3107), 0xa1 }, ++ ++ /* Analog/timing fine-tuning */ ++ { CCI_REG8(0x3624), 0x00 }, ++ { CCI_REG8(0x3625), 0x4c }, ++ { CCI_REG8(0x3660), 0x04 }, ++ { CCI_REG8(0x3666), 0xa5 }, ++ { CCI_REG8(0x3667), 0xa5 }, ++ { CCI_REG8(0x366a), 0x50 }, ++ { CCI_REG8(0x3673), 0x0d }, ++ { CCI_REG8(0x3672), 0x0d }, ++ { CCI_REG8(0x3671), 0x0d }, ++ { CCI_REG8(0x3670), 0x0d }, ++ { CCI_REG8(0x3685), 0x00 }, ++ { CCI_REG8(0x3694), 0x0d }, ++ { CCI_REG8(0x3693), 0x0d }, ++ { CCI_REG8(0x3692), 0x0d }, ++ { CCI_REG8(0x3691), 0x0d }, ++ { CCI_REG8(0x3696), 0x4c }, ++ { CCI_REG8(0x3697), 0x4c }, ++ { CCI_REG8(0x3698), 0x00 }, ++ { CCI_REG8(0x3699), 0x80 }, ++ { CCI_REG8(0x369a), 0x80 }, ++ { CCI_REG8(0x369b), 0x1f }, ++ { CCI_REG8(0x369c), 0x1f }, ++ { CCI_REG8(0x369d), 0x80 }, ++ { CCI_REG8(0x369e), 0x40 }, ++ { CCI_REG8(0x369f), 0x21 }, ++ { CCI_REG8(0x36a0), 0x12 }, ++ { CCI_REG8(0x36a1), 0xdd }, ++ { CCI_REG8(0x36a2), 0x66 }, ++ { CCI_REG8(0x370a), 0x02 }, ++ { CCI_REG8(0x370e), 0x00 }, ++ { CCI_REG8(0x3710), 0x00 }, ++ { CCI_REG8(0x3713), 0x04 }, ++ { CCI_REG8(0x3725), 0x02 }, ++ { CCI_REG8(0x372a), 0x03 }, ++ { CCI_REG8(0x3738), 0xce }, ++ { CCI_REG8(0x3748), 0x02 }, ++ { CCI_REG8(0x374a), 0x02 }, ++ { CCI_REG8(0x374c), 0x02 }, ++ { CCI_REG8(0x374e), 0x02 }, ++ { CCI_REG8(0x3756), 0x00 }, ++ { CCI_REG8(0x3757), 0x00 }, ++ { CCI_REG8(0x3767), 0x00 }, ++ { CCI_REG8(0x3771), 0x00 }, ++ { CCI_REG8(0x377b), 0x28 }, ++ { CCI_REG8(0x377c), 0x00 }, ++ { CCI_REG8(0x377d), 0x0c }, ++ { CCI_REG8(0x3781), 0x03 }, ++ { CCI_REG8(0x3782), 0x00 }, ++ { CCI_REG8(0x3789), 0x14 }, ++ { CCI_REG8(0x3795), 0x02 }, ++ { CCI_REG8(0x379c), 0x00 }, ++ { CCI_REG8(0x379d), 0x00 }, ++ { CCI_REG8(0x37b8), 0x04 }, ++ { CCI_REG8(0x37ba), 0x03 }, ++ { CCI_REG8(0x37bb), 0x00 }, ++ { CCI_REG8(0x37bc), 0x04 }, ++ { CCI_REG8(0x37be), 0x26 }, ++ { CCI_REG8(0x37c4), 0x11 }, ++ { CCI_REG8(0x37c5), 0x80 }, ++ { CCI_REG8(0x37c6), 0x14 }, ++ { CCI_REG8(0x37c7), 0xa8 }, ++ { CCI_REG8(0x37da), 0x11 }, ++ { CCI_REG8(0x381f), 0x08 }, ++ { CCI_REG8(0x3881), 0x00 }, ++ { CCI_REG8(0x3888), 0x04 }, ++ { CCI_REG8(0x388b), 0x00 }, ++ { CCI_REG8(0x3c80), 0x10 }, ++ { CCI_REG8(0x3c86), 0x00 }, ++ { CCI_REG8(0x3c8c), 0x20 }, ++ { CCI_REG8(0x3c9f), 0x01 }, ++ { CCI_REG8(0x3d85), 0x1b }, ++ { CCI_REG8(0x3d8c), 0x71 }, ++ { CCI_REG8(0x3d8d), 0xe2 }, ++ { CCI_REG8(0x3f00), 0x0b }, ++ { CCI_REG8(0x3f06), 0x04 }, ++ ++ /* BLC - black level correction */ ++ { CCI_REG8(0x400a), 0x01 }, ++ { CCI_REG8(0x400b), 0x50 }, ++ { CCI_REG8(0x400e), 0x08 }, ++ { CCI_REG8(0x4043), 0x7e }, ++ { CCI_REG8(0x4045), 0x7e }, ++ { CCI_REG8(0x4047), 0x7e }, ++ { CCI_REG8(0x4049), 0x7e }, ++ { CCI_REG8(0x4090), 0x04 }, ++ { CCI_REG8(0x40b0), 0x00 }, ++ { CCI_REG8(0x40b1), 0x00 }, ++ { CCI_REG8(0x40b2), 0x00 }, ++ { CCI_REG8(0x40b3), 0x00 }, ++ { CCI_REG8(0x40b4), 0x00 }, ++ { CCI_REG8(0x40b5), 0x00 }, ++ { CCI_REG8(0x40b7), 0x00 }, ++ { CCI_REG8(0x40b8), 0x00 }, ++ { CCI_REG8(0x40b9), 0x00 }, ++ { CCI_REG8(0x40ba), 0x01 }, ++ ++ { CCI_REG8(0x4301), 0x00 }, ++ { CCI_REG8(0x4303), 0x00 }, ++ { CCI_REG8(0x4502), 0x04 }, ++ { CCI_REG8(0x4503), 0x00 }, ++ { CCI_REG8(0x4504), 0x06 }, ++ { CCI_REG8(0x4506), 0x00 }, ++ { CCI_REG8(0x4507), 0x47 }, ++ { CCI_REG8(0x4803), 0x00 }, ++ { CCI_REG8(0x480c), 0x32 }, ++ { CCI_REG8(0x480e), 0x04 }, ++ { CCI_REG8(0x4813), 0xe4 }, ++ { CCI_REG8(0x4819), 0x70 }, ++ { CCI_REG8(0x481f), 0x30 }, ++ { CCI_REG8(0x4823), 0x3f }, ++ { CCI_REG8(0x4825), 0x30 }, ++ { CCI_REG8(0x4833), 0x10 }, ++ { CCI_REG8(0x484b), 0x27 }, ++ { CCI_REG8(0x488b), 0x00 }, ++ { CCI_REG8(0x4d00), 0x04 }, ++ { CCI_REG8(0x4d01), 0xad }, ++ { CCI_REG8(0x4d02), 0xbc }, ++ { CCI_REG8(0x4d03), 0xa1 }, ++ { CCI_REG8(0x4d04), 0x1f }, ++ { CCI_REG8(0x4d05), 0x4c }, ++ { CCI_REG8(0x4d0b), 0x01 }, ++ { CCI_REG8(0x4e00), 0x2a }, ++ { CCI_REG8(0x4e0d), 0x00 }, ++ ++ /* ISP */ ++ { CCI_REG8(0x5001), 0x09 }, ++ { CCI_REG8(0x5004), 0x00 }, ++ { CCI_REG8(0x5080), 0x04 }, ++ { CCI_REG8(0x5036), 0x80 }, ++ { CCI_REG8(0x5180), 0x70 }, ++ { CCI_REG8(0x5181), 0x10 }, ++ ++ /* DPC - defective pixel correction */ ++ { CCI_REG8(0x520a), 0x03 }, ++ { CCI_REG8(0x520b), 0x06 }, ++ { CCI_REG8(0x520c), 0x0c }, ++ ++ { CCI_REG8(0x580b), 0x0f }, ++ { CCI_REG8(0x580d), 0x00 }, ++ { CCI_REG8(0x580f), 0x00 }, ++ { CCI_REG8(0x5820), 0x00 }, ++ { CCI_REG8(0x5821), 0x00 }, ++ ++ /* Sensor core */ ++ { CCI_REG8(0x301c), 0xf8 }, ++ { CCI_REG8(0x301e), 0xb4 }, ++ { CCI_REG8(0x301f), 0xf0 }, ++ { CCI_REG8(0x3022), 0x61 }, ++ { CCI_REG8(0x3109), 0xe7 }, ++ { CCI_REG8(0x3600), 0x00 }, ++ { CCI_REG8(0x3610), 0x65 }, ++ { CCI_REG8(0x3611), 0x85 }, ++ { CCI_REG8(0x3613), 0x3a }, ++ { CCI_REG8(0x3615), 0x60 }, ++ { CCI_REG8(0x3621), 0xb0 }, ++ { CCI_REG8(0x3620), 0x0c }, ++ { CCI_REG8(0x3629), 0x00 }, ++ { CCI_REG8(0x3661), 0x04 }, ++ { CCI_REG8(0x3664), 0x70 }, ++ { CCI_REG8(0x3665), 0x00 }, ++ { CCI_REG8(0x3681), 0x80 }, ++ { CCI_REG8(0x3682), 0x40 }, ++ { CCI_REG8(0x3683), 0x21 }, ++ { CCI_REG8(0x3684), 0x12 }, ++ { CCI_REG8(0x3700), 0x2a }, ++ { CCI_REG8(0x3701), 0x12 }, ++ { CCI_REG8(0x3703), 0x28 }, ++ { CCI_REG8(0x3704), 0x0e }, ++ { CCI_REG8(0x3706), 0x9d }, ++ { CCI_REG8(0x3709), 0x4a }, ++ { CCI_REG8(0x370b), 0x48 }, ++ { CCI_REG8(0x370c), 0x01 }, ++ { CCI_REG8(0x370f), 0x00 }, ++ { CCI_REG8(0x3714), 0x28 }, ++ { CCI_REG8(0x3716), 0x04 }, ++ { CCI_REG8(0x3719), 0x11 }, ++ { CCI_REG8(0x371a), 0x1e }, ++ { CCI_REG8(0x3720), 0x00 }, ++ { CCI_REG8(0x3724), 0x13 }, ++ { CCI_REG8(0x373f), 0xb0 }, ++ { CCI_REG8(0x3741), 0x9d }, ++ { CCI_REG8(0x3743), 0x9d }, ++ { CCI_REG8(0x3745), 0x9d }, ++ { CCI_REG8(0x3747), 0x9d }, ++ { CCI_REG8(0x3749), 0x48 }, ++ { CCI_REG8(0x374b), 0x48 }, ++ { CCI_REG8(0x374d), 0x48 }, ++ { CCI_REG8(0x374f), 0x48 }, ++ { CCI_REG8(0x3755), 0x10 }, ++ { CCI_REG8(0x376c), 0x00 }, ++ { CCI_REG8(0x378d), 0x3c }, ++ { CCI_REG8(0x3790), 0x01 }, ++ { CCI_REG8(0x3791), 0x01 }, ++ { CCI_REG8(0x3798), 0x40 }, ++ { CCI_REG8(0x379e), 0x00 }, ++ { CCI_REG8(0x379f), 0x04 }, ++ { CCI_REG8(0x37a1), 0x10 }, ++ { CCI_REG8(0x37a2), 0x1e }, ++ { CCI_REG8(0x37a8), 0x10 }, ++ { CCI_REG8(0x37a9), 0x1e }, ++ { CCI_REG8(0x37ac), 0xa0 }, ++ { CCI_REG8(0x37b9), 0x01 }, ++ { CCI_REG8(0x37bd), 0x01 }, ++ { CCI_REG8(0x37bf), 0x26 }, ++ { CCI_REG8(0x37c0), 0x11 }, ++ { CCI_REG8(0x37c2), 0x14 }, ++ { CCI_REG8(0x37cd), 0x19 }, ++ { CCI_REG8(0x37e0), 0x08 }, ++ { CCI_REG8(0x37e6), 0x04 }, ++ { CCI_REG8(0x37e5), 0x02 }, ++ { CCI_REG8(0x37e1), 0x0c }, ++ { CCI_REG8(0x3737), 0x04 }, ++ { CCI_REG8(0x37d8), 0x02 }, ++ { CCI_REG8(0x37e2), 0x10 }, ++ { CCI_REG8(0x3739), 0x10 }, ++ { CCI_REG8(0x3662), 0x08 }, ++ { CCI_REG8(0x37e4), 0x20 }, ++ { CCI_REG8(0x37e3), 0x08 }, ++ { CCI_REG8(0x37d9), 0x04 }, ++ { CCI_REG8(0x4040), 0x00 }, ++ { CCI_REG8(0x4041), 0x03 }, ++ { CCI_REG8(0x4008), 0x01 }, ++ { CCI_REG8(0x4009), 0x06 }, ++ ++ /* FSIN - frame sync */ ++ { CCI_REG8(0x3002), 0x22 }, ++ { CCI_REG8(0x3663), 0x22 }, ++ { CCI_REG8(0x368a), 0x04 }, ++ { CCI_REG8(0x3822), 0x44 }, ++ { CCI_REG8(0x3823), 0x00 }, ++ { CCI_REG8(0x3829), 0x03 }, ++ { CCI_REG8(0x3832), 0xf8 }, ++ { CCI_REG8(0x382c), 0x00 }, ++ { CCI_REG8(0x3844), 0x06 }, ++ { CCI_REG8(0x3843), 0x00 }, ++ { CCI_REG8(0x382a), 0x00 }, ++ { CCI_REG8(0x382b), 0x0c }, ++ ++ /* Window: 2704x1536 input → 1344x760 binned output */ ++ { CCI_REG8(0x3800), 0x00 }, { CCI_REG8(0x3801), 0x00 }, ++ { CCI_REG8(0x3802), 0x00 }, { CCI_REG8(0x3803), 0x00 }, ++ { CCI_REG8(0x3804), 0x0a }, { CCI_REG8(0x3805), 0x8f }, ++ { CCI_REG8(0x3806), 0x05 }, { CCI_REG8(0x3807), 0xff }, ++ { CCI_REG8(0x3808), 0x05 }, { CCI_REG8(0x3809), 0x40 }, /* width = 1344 */ ++ { CCI_REG8(0x380a), 0x02 }, { CCI_REG8(0x380b), 0xf8 }, /* height = 760 */ ++ { CCI_REG8(0x3811), 0x08 }, ++ { CCI_REG8(0x3813), 0x08 }, ++ { CCI_REG8(0x3814), 0x03 }, /* x binning */ ++ { CCI_REG8(0x3815), 0x01 }, ++ { CCI_REG8(0x3816), 0x03 }, /* y binning */ ++ { CCI_REG8(0x3817), 0x01 }, ++ ++ { CCI_REG8(0x380c), 0x0b }, { CCI_REG8(0x380d), 0xac }, /* HTS = 2988 */ ++ { CCI_REG8(0x380e), 0x06 }, { CCI_REG8(0x380f), 0x9c }, /* VTS = 1692 */ ++ ++ { CCI_REG8(0x3820), 0xb3 }, ++ { CCI_REG8(0x3821), 0x01 }, ++ { CCI_REG8(0x3880), 0x00 }, ++ { CCI_REG8(0x3882), 0x20 }, ++ { CCI_REG8(0x3c91), 0x0b }, ++ { CCI_REG8(0x3c94), 0x45 }, ++ { CCI_REG8(0x3cad), 0x00 }, ++ { CCI_REG8(0x3cae), 0x00 }, ++ { CCI_REG8(0x4000), 0xf3 }, ++ { CCI_REG8(0x4001), 0x60 }, ++ { CCI_REG8(0x4003), 0x40 }, ++ { CCI_REG8(0x4300), 0xff }, ++ { CCI_REG8(0x4302), 0x0f }, ++ { CCI_REG8(0x4305), 0x83 }, ++ { CCI_REG8(0x4505), 0x84 }, ++ { CCI_REG8(0x4809), 0x0e }, ++ { CCI_REG8(0x480a), 0x04 }, ++ { CCI_REG8(0x4837), 0x15 }, ++ { CCI_REG8(0x4c00), 0x08 }, ++ { CCI_REG8(0x4c01), 0x08 }, ++ { CCI_REG8(0x4c04), 0x00 }, ++ { CCI_REG8(0x4c05), 0x00 }, ++ { CCI_REG8(0x5000), 0xf9 }, ++ ++ /* Exposure control mode */ ++ { CCI_REG8(0x3503), 0x88 }, ++ ++ /* Initial long exposure */ ++ { CCI_REG8(0x3500), 0x00 }, { CCI_REG8(0x3501), 0x00 }, { CCI_REG8(0x3502), 0x10 }, ++ { CCI_REG8(0x3508), 0x00 }, { CCI_REG8(0x3509), 0x80 }, ++ { CCI_REG8(0x350a), 0x04 }, { CCI_REG8(0x350b), 0x00 }, ++ ++ /* Initial short exposure */ ++ { CCI_REG8(0x3510), 0x00 }, { CCI_REG8(0x3511), 0x00 }, { CCI_REG8(0x3512), 0x40 }, ++ { CCI_REG8(0x350c), 0x00 }, { CCI_REG8(0x350d), 0x80 }, ++ { CCI_REG8(0x350e), 0x04 }, { CCI_REG8(0x350f), 0x00 }, ++ ++ /* White balance: B, G, R */ ++ { CCI_REG8(0x5100), 0x06 }, { CCI_REG8(0x5101), 0x7e }, ++ { CCI_REG8(0x5140), 0x06 }, { CCI_REG8(0x5141), 0x7e }, ++ { CCI_REG8(0x5102), 0x04 }, { CCI_REG8(0x5103), 0x00 }, ++ { CCI_REG8(0x5142), 0x04 }, { CCI_REG8(0x5143), 0x00 }, ++ { CCI_REG8(0x5104), 0x08 }, { CCI_REG8(0x5105), 0xd6 }, ++ { CCI_REG8(0x5144), 0x08 }, { CCI_REG8(0x5145), 0xd6 }, ++}; ++ ++static const s64 os04c10_link_freq_menu[] = { ++ OS04C10_LINK_FREQ, ++}; ++ ++static const struct os04c10_mode os04c10_modes[] = { ++ { ++ .width = OS04C10_NATIVE_WIDTH, ++ .height = OS04C10_NATIVE_HEIGHT, ++ /* BGBGBG Bayer pattern → SBGGR12 */ ++ .code = MEDIA_BUS_FMT_SBGGR12_1X12, ++ .reg_list = { ++ .num_of_regs = ARRAY_SIZE(os04c10_mode_default), ++ .regs = os04c10_mode_default, ++ }, ++ .link_freq_index = 0, ++ /* hblank = HTS - width = 2988 - 1344 = 1644 */ ++ .hblank = OS04C10_HTS_DEFAULT - OS04C10_NATIVE_WIDTH, ++ /* vblank = VTS - height = 1692 - 760 = 932 */ ++ .vblank = OS04C10_VTS_DEFAULT - OS04C10_NATIVE_HEIGHT, ++ }, ++}; ++ ++static const char * const os04c10_supplies[] = { ++ "avdd", /* analog supply: ~2.8V (vreg_bob) */ ++ "dovdd", /* digital I/O supply: 1.8V (vreg_lvs1a) */ ++ "dvdd", /* digital core supply: 1.05V (camera_rear_ldo) */ ++}; ++ ++/* Shared CAM_VANA switch GPIO8 is common across all three sensors. */ ++static DEFINE_MUTEX(os04c10_vana_lock); ++static unsigned int os04c10_vana_users; ++ ++struct os04c10 { ++ struct v4l2_subdev sd; ++ struct media_pad pad; ++ struct v4l2_ctrl_handler ctrl_handler; ++ ++ struct v4l2_ctrl *link_freq; ++ struct v4l2_ctrl *pixel_rate; ++ struct v4l2_ctrl *hblank; ++ struct v4l2_ctrl *vblank; ++ ++ struct device *dev; ++ struct regmap *cci; ++ struct clk *clk; ++ struct gpio_desc *reset_gpio; ++ struct gpio_desc *vana_gpio; ++ struct regulator_bulk_data supplies[ARRAY_SIZE(os04c10_supplies)]; ++ unsigned long link_freq_bitmap; ++ const struct os04c10_mode *cur_mode; ++}; ++ ++static void os04c10_vana_set(struct os04c10 *sensor, bool enable) ++{ ++ if (!sensor->vana_gpio) ++ return; ++ ++ mutex_lock(&os04c10_vana_lock); ++ ++ if (enable) { ++ if (os04c10_vana_users++ == 0) ++ gpiod_set_value_cansleep(sensor->vana_gpio, 1); ++ } else if (os04c10_vana_users > 0) { ++ if (--os04c10_vana_users == 0) ++ gpiod_set_value_cansleep(sensor->vana_gpio, 0); ++ } ++ ++ mutex_unlock(&os04c10_vana_lock); ++} ++ ++static int os04c10_set_ctrl(struct v4l2_ctrl *ctrl) ++{ ++ struct os04c10 *sensor = ++ container_of(ctrl->handler, struct os04c10, ctrl_handler); ++ struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd); ++ int ret = 0; ++ ++ if (!pm_runtime_get_if_in_use(&client->dev)) ++ return 0; ++ ++ switch (ctrl->id) { ++ case V4L2_CID_ANALOGUE_GAIN: ++ ret = cci_write(sensor->cci, OS04C10_REG_AGAIN_H, ++ (ctrl->val >> 8) & 0xff, NULL); ++ ret = cci_write(sensor->cci, OS04C10_REG_AGAIN_L, ++ ctrl->val & 0xff, &ret); ++ break; ++ case V4L2_CID_EXPOSURE: ++ ret = cci_write(sensor->cci, OS04C10_REG_EXPOSURE_H, ++ (ctrl->val >> 8) & 0xff, NULL); ++ ret = cci_write(sensor->cci, OS04C10_REG_EXPOSURE_L, ++ ctrl->val & 0xff, &ret); ++ break; ++ case V4L2_CID_VBLANK: ++ ret = cci_write(sensor->cci, OS04C10_REG_VTS_H, ++ ((sensor->cur_mode->height + ctrl->val) >> 8) & 0xff, ++ NULL); ++ ret = cci_write(sensor->cci, OS04C10_REG_VTS_L, ++ (sensor->cur_mode->height + ctrl->val) & 0xff, ++ &ret); ++ break; ++ default: ++ ret = -EINVAL; ++ break; ++ } ++ ++ pm_runtime_put(&client->dev); ++ return ret; ++} ++ ++static const struct v4l2_ctrl_ops os04c10_ctrl_ops = { ++ .s_ctrl = os04c10_set_ctrl, ++}; ++ ++static int os04c10_init_controls(struct os04c10 *sensor) ++{ ++ struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd); ++ struct v4l2_fwnode_device_properties props; ++ struct v4l2_ctrl_handler *ctrl_hdlr; ++ s64 vblank; ++ int ret; ++ ++ ctrl_hdlr = &sensor->ctrl_handler; ++ ret = v4l2_ctrl_handler_init(ctrl_hdlr, 8); ++ if (ret) ++ return ret; ++ ++ sensor->link_freq = v4l2_ctrl_new_int_menu( ++ ctrl_hdlr, &os04c10_ctrl_ops, V4L2_CID_LINK_FREQ, ++ ARRAY_SIZE(os04c10_link_freq_menu) - 1, 0, ++ os04c10_link_freq_menu); ++ if (sensor->link_freq) ++ sensor->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; ++ ++ sensor->pixel_rate = v4l2_ctrl_new_std( ++ ctrl_hdlr, &os04c10_ctrl_ops, V4L2_CID_PIXEL_RATE, ++ OS04C10_PIXEL_RATE, OS04C10_PIXEL_RATE, 1, ++ OS04C10_PIXEL_RATE); ++ if (sensor->pixel_rate) ++ sensor->pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY; ++ ++ vblank = OS04C10_VTS_MAX - sensor->cur_mode->height; ++ sensor->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &os04c10_ctrl_ops, ++ V4L2_CID_VBLANK, 0, vblank, 1, ++ sensor->cur_mode->vblank); ++ ++ sensor->hblank = v4l2_ctrl_new_std( ++ ctrl_hdlr, &os04c10_ctrl_ops, V4L2_CID_HBLANK, ++ sensor->cur_mode->hblank, sensor->cur_mode->hblank, 1, ++ sensor->cur_mode->hblank); ++ if (sensor->hblank) ++ sensor->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; ++ ++ v4l2_ctrl_new_std(ctrl_hdlr, &os04c10_ctrl_ops, ++ V4L2_CID_ANALOGUE_GAIN, OS04C10_AGAIN_MIN, ++ OS04C10_AGAIN_MAX, 1, OS04C10_AGAIN_DEFAULT); ++ ++ v4l2_ctrl_new_std(ctrl_hdlr, &os04c10_ctrl_ops, V4L2_CID_EXPOSURE, ++ OS04C10_EXPOSURE_MIN, OS04C10_EXPOSURE_MAX, 1, ++ OS04C10_EXPOSURE_DEFAULT); ++ ++ if (ctrl_hdlr->error) ++ return ctrl_hdlr->error; ++ ++ ret = v4l2_fwnode_device_parse(&client->dev, &props); ++ if (ret) ++ return ret; ++ ++ ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &os04c10_ctrl_ops, ++ &props); ++ if (ret) ++ return ret; ++ ++ sensor->sd.ctrl_handler = ctrl_hdlr; ++ return 0; ++} ++ ++static void os04c10_update_pad_format(const struct os04c10_mode *mode, ++ struct v4l2_mbus_framefmt *fmt) ++{ ++ fmt->width = mode->width; ++ fmt->height = mode->height; ++ fmt->code = mode->code; ++ fmt->field = V4L2_FIELD_NONE; ++} ++ ++static int os04c10_start_streaming(struct os04c10 *sensor) ++{ ++ struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd); ++ const struct os04c10_reg_list *reg_list; ++ int ret; ++ ++ reg_list = &sensor->cur_mode->reg_list; ++ ret = cci_multi_reg_write(sensor->cci, reg_list->regs, ++ reg_list->num_of_regs, NULL); ++ if (ret) { ++ dev_err(&client->dev, "failed to write init registers\n"); ++ return ret; ++ } ++ ++ /* Allow registers to settle after init */ ++ fsleep(10000); ++ ++ ret = __v4l2_ctrl_handler_setup(sensor->sd.ctrl_handler); ++ if (ret) ++ return ret; ++ ++ ret = cci_write(sensor->cci, OS04C10_REG_MODE_SELECT, ++ OS04C10_MODE_STREAMING, NULL); ++ if (ret) { ++ dev_err(&client->dev, "failed to start streaming\n"); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int os04c10_stop_streaming(struct os04c10 *sensor) ++{ ++ return cci_write(sensor->cci, OS04C10_REG_MODE_SELECT, ++ OS04C10_MODE_STANDBY, NULL); ++} ++ ++static int os04c10_set_stream(struct v4l2_subdev *sd, int enable) ++{ ++ struct os04c10 *sensor = to_os04c10(sd); ++ struct i2c_client *client = v4l2_get_subdevdata(sd); ++ int ret = 0; ++ ++ if (enable) { ++ ret = pm_runtime_resume_and_get(&client->dev); ++ if (ret < 0) ++ return ret; ++ ++ ret = os04c10_start_streaming(sensor); ++ if (ret) ++ goto error_rpm_put; ++ } else { ++ os04c10_stop_streaming(sensor); ++ pm_runtime_put(&client->dev); ++ } ++ ++ return ret; ++ ++error_rpm_put: ++ pm_runtime_put(&client->dev); ++ return ret; ++} ++ ++static int os04c10_get_fmt(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_format *fmt) ++{ ++ struct os04c10 *sensor = to_os04c10(sd); ++ ++ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { ++ struct v4l2_mbus_framefmt *framefmt; ++ ++ framefmt = v4l2_subdev_state_get_format(sd_state, fmt->pad); ++ fmt->format = *framefmt; ++ } else { ++ os04c10_update_pad_format(sensor->cur_mode, &fmt->format); ++ } ++ ++ return 0; ++} ++ ++static int os04c10_set_fmt(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_format *fmt) ++{ ++ struct os04c10 *sensor = to_os04c10(sd); ++ const struct os04c10_mode *mode; ++ ++ mode = v4l2_find_nearest_size(os04c10_modes, ++ ARRAY_SIZE(os04c10_modes), width, ++ height, fmt->format.width, ++ fmt->format.height); ++ ++ os04c10_update_pad_format(mode, &fmt->format); ++ ++ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { ++ *v4l2_subdev_state_get_format(sd_state, fmt->pad) = ++ fmt->format; ++ } else { ++ sensor->cur_mode = mode; ++ __v4l2_ctrl_s_ctrl(sensor->link_freq, ++ mode->link_freq_index); ++ __v4l2_ctrl_modify_range(sensor->vblank, 0, ++ OS04C10_VTS_MAX - mode->height, 1, ++ sensor->cur_mode->vblank); ++ __v4l2_ctrl_s_ctrl(sensor->vblank, ++ sensor->cur_mode->vblank); ++ } ++ ++ return 0; ++} ++ ++static int os04c10_enum_mbus_code(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_mbus_code_enum *code) ++{ ++ if (code->index > 0) ++ return -EINVAL; ++ ++ code->code = MEDIA_BUS_FMT_SBGGR12_1X12; ++ return 0; ++} ++ ++static int os04c10_enum_frame_size(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state, ++ struct v4l2_subdev_frame_size_enum *fse) ++{ ++ if (fse->index >= ARRAY_SIZE(os04c10_modes)) ++ return -EINVAL; ++ ++ if (fse->code != MEDIA_BUS_FMT_SBGGR12_1X12) ++ return -EINVAL; ++ ++ fse->min_width = os04c10_modes[fse->index].width; ++ fse->max_width = fse->min_width; ++ fse->min_height = os04c10_modes[fse->index].height; ++ fse->max_height = fse->min_height; ++ ++ return 0; ++} ++ ++static int os04c10_get_selection(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_selection *sel) ++{ ++ switch (sel->target) { ++ case V4L2_SEL_TGT_CROP: ++ case V4L2_SEL_TGT_CROP_DEFAULT: ++ sel->r = *v4l2_subdev_state_get_crop(state, 0); ++ break; ++ case V4L2_SEL_TGT_CROP_BOUNDS: ++ case V4L2_SEL_TGT_NATIVE_SIZE: ++ sel->r.top = 0; ++ sel->r.left = 0; ++ sel->r.width = OS04C10_NATIVE_WIDTH; ++ sel->r.height = OS04C10_NATIVE_HEIGHT; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int os04c10_init_state(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *sd_state) ++{ ++ const struct os04c10_mode *def_mode = &os04c10_modes[0]; ++ struct v4l2_subdev_format fmt = { ++ .which = V4L2_SUBDEV_FORMAT_TRY, ++ .format = { ++ .code = MEDIA_BUS_FMT_SBGGR12_1X12, ++ .width = def_mode->width, ++ .height = def_mode->height, ++ }, ++ }; ++ ++ os04c10_set_fmt(sd, sd_state, &fmt); ++ return 0; ++} ++ ++static const struct v4l2_subdev_video_ops os04c10_video_ops = { ++ .s_stream = os04c10_set_stream, ++}; ++ ++static const struct v4l2_subdev_pad_ops os04c10_pad_ops = { ++ .enum_mbus_code = os04c10_enum_mbus_code, ++ .enum_frame_size = os04c10_enum_frame_size, ++ .get_fmt = os04c10_get_fmt, ++ .set_fmt = os04c10_set_fmt, ++ .get_selection = os04c10_get_selection, ++}; ++ ++static const struct v4l2_subdev_core_ops os04c10_core_ops = { ++ .subscribe_event = v4l2_ctrl_subdev_subscribe_event, ++ .unsubscribe_event = v4l2_event_subdev_unsubscribe, ++}; ++ ++static const struct v4l2_subdev_ops os04c10_subdev_ops = { ++ .core = &os04c10_core_ops, ++ .video = &os04c10_video_ops, ++ .pad = &os04c10_pad_ops, ++}; ++ ++static const struct media_entity_operations os04c10_subdev_entity_ops = { ++ .link_validate = v4l2_subdev_link_validate, ++}; ++ ++static const struct v4l2_subdev_internal_ops os04c10_internal_ops = { ++ .init_state = os04c10_init_state, ++}; ++ ++static int os04c10_parse_hw_config(struct os04c10 *sensor) ++{ ++ struct fwnode_handle *endpoint; ++ struct v4l2_fwnode_endpoint ep_cfg = { ++ .bus_type = V4L2_MBUS_CSI2_DPHY, ++ }; ++ int i, ret; ++ ++ sensor->clk = devm_clk_get(sensor->dev, "extclk"); ++ if (IS_ERR(sensor->clk)) ++ return dev_err_probe(sensor->dev, PTR_ERR(sensor->clk), ++ "Failed to get clock\n"); ++ ++ /* ++ * Reset is active-low in DTS (GPIO_ACTIVE_LOW). ++ * GPIOD_OUT_HIGH → logical high → physical LOW → sensor in reset. ++ */ ++ sensor->reset_gpio = ++ devm_gpiod_get_optional(sensor->dev, "reset", GPIOD_OUT_HIGH); ++ if (IS_ERR(sensor->reset_gpio)) ++ return dev_err_probe(sensor->dev, ++ PTR_ERR(sensor->reset_gpio), ++ "Failed to get reset gpio\n"); ++ ++ /* ++ * Optional shared CAM_VANA switch, modeled explicitly via GPIO ++ * instead of as a fixed-regulator wrapper. ++ */ ++ sensor->vana_gpio = ++ devm_gpiod_get_optional(sensor->dev, "vana", GPIOD_OUT_LOW); ++ if (IS_ERR(sensor->vana_gpio)) ++ return dev_err_probe(sensor->dev, ++ PTR_ERR(sensor->vana_gpio), ++ "Failed to get vana gpio\n"); ++ ++ for (i = 0; i < ARRAY_SIZE(os04c10_supplies); i++) ++ sensor->supplies[i].supply = os04c10_supplies[i]; ++ ++ ret = devm_regulator_bulk_get(sensor->dev, ++ ARRAY_SIZE(os04c10_supplies), ++ sensor->supplies); ++ if (ret) ++ return dev_err_probe(sensor->dev, ret, ++ "Cannot get supplies\n"); ++ ++ endpoint = ++ fwnode_graph_get_next_endpoint(dev_fwnode(sensor->dev), NULL); ++ if (!endpoint) { ++ dev_err(sensor->dev, "endpoint node not found\n"); ++ return -EPROBE_DEFER; ++ } ++ ++ ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep_cfg); ++ if (ret) { ++ dev_err(sensor->dev, "parsing endpoint node failed\n"); ++ goto error_out; ++ } ++ ++ ret = v4l2_link_freq_to_bitmap(sensor->dev, ep_cfg.link_frequencies, ++ ep_cfg.nr_of_link_frequencies, ++ os04c10_link_freq_menu, ++ ARRAY_SIZE(os04c10_link_freq_menu), ++ &sensor->link_freq_bitmap); ++ ++error_out: ++ v4l2_fwnode_endpoint_free(&ep_cfg); ++ fwnode_handle_put(endpoint); ++ return ret; ++} ++ ++static int os04c10_identify_module(struct os04c10 *sensor) ++{ ++ struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd); ++ u64 val_h, val_l; ++ int ret; ++ ++ ret = cci_read(sensor->cci, OS04C10_REG_CHIP_ID_H, &val_h, NULL); ++ if (ret) ++ return ret; ++ ++ ret = cci_read(sensor->cci, OS04C10_REG_CHIP_ID_L, &val_l, NULL); ++ if (ret) ++ return ret; ++ ++ if (val_h != OS04C10_CHIP_ID_H || val_l != OS04C10_CHIP_ID_L) { ++ dev_err(&client->dev, ++ "chip id mismatch: expected %02x%02x, got %02llx%02llx\n", ++ OS04C10_CHIP_ID_H, OS04C10_CHIP_ID_L, val_h, val_l); ++ return -ENXIO; ++ } ++ ++ dev_info(&client->dev, "OS04C10 chip ID verified: 0x%02llx%02llx\n", ++ val_h, val_l); ++ return 0; ++} ++ ++static int os04c10_power_on(struct device *dev) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct v4l2_subdev *sd = i2c_get_clientdata(client); ++ struct os04c10 *sensor = to_os04c10(sd); ++ int ret; ++ ++ /* ++ * Power sequence: ++ * 1. Assert reset (keep sensor in reset during power ramp) ++ * 2. Enable regulators: avdd, dovdd, dvdd ++ * 3. Wait 1 ms ++ * 4. Enable shared VANA GPIO switch ++ * 5. Wait 1 ms ++ * 6. Set and enable MCLK (24 MHz) ++ * 7. Wait 1 ms ++ * 8. De-assert reset ++ * 9. Wait >=34 ms for sensor to come out of reset ++ */ ++ ++ /* Assert reset: logical 1 -> physical LOW (active-low) */ ++ if (sensor->reset_gpio) { ++ gpiod_set_value_cansleep(sensor->reset_gpio, 1); ++ } ++ ++ ret = regulator_bulk_enable(ARRAY_SIZE(os04c10_supplies), ++ sensor->supplies); ++ if (ret) { ++ dev_err(sensor->dev, "Failed to enable regulators\n"); ++ return ret; ++ } ++ ++ fsleep(1000); ++ os04c10_vana_set(sensor, true); ++ fsleep(1000); ++ ++ ret = clk_set_rate(sensor->clk, OS04C10_MCLK_RATE); ++ if (ret) ++ dev_warn(sensor->dev, "Failed to set extclk rate: %d\n", ret); ++ ++ ret = clk_prepare_enable(sensor->clk); ++ if (ret) { ++ dev_err(sensor->dev, "Failed to enable clock\n"); ++ goto error_regulators; ++ } ++ ++ fsleep(1000); ++ ++ /* De-assert reset: logical 0 -> physical HIGH */ ++ if (sensor->reset_gpio) { ++ gpiod_set_value_cansleep(sensor->reset_gpio, 0); ++ } ++ ++ /* OS04C10 requires >=34 ms after reset de-assertion before I2C access */ ++ fsleep(34000); ++ ++ return 0; ++ ++error_regulators: ++ os04c10_vana_set(sensor, false); ++ if (sensor->reset_gpio) ++ gpiod_set_value_cansleep(sensor->reset_gpio, 1); ++ regulator_bulk_disable(ARRAY_SIZE(os04c10_supplies), ++ sensor->supplies); ++ return ret; ++} ++ ++static int os04c10_power_off(struct device *dev) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct v4l2_subdev *sd = i2c_get_clientdata(client); ++ struct os04c10 *sensor = to_os04c10(sd); ++ ++ /* Assert reset before killing power */ ++ if (sensor->reset_gpio) ++ gpiod_set_value_cansleep(sensor->reset_gpio, 1); ++ clk_disable_unprepare(sensor->clk); ++ os04c10_vana_set(sensor, false); ++ regulator_bulk_disable(ARRAY_SIZE(os04c10_supplies), ++ sensor->supplies); ++ ++ return 0; ++} ++ ++static int os04c10_probe(struct i2c_client *client) ++{ ++ struct os04c10 *sensor; ++ int ret; ++ ++ sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL); ++ if (!sensor) ++ return -ENOMEM; ++ ++ sensor->dev = &client->dev; ++ ++ /* OS04C10 uses 16-bit register addresses, 8-bit data */ ++ sensor->cci = devm_cci_regmap_init_i2c(client, 16); ++ if (IS_ERR(sensor->cci)) ++ return dev_err_probe(sensor->dev, PTR_ERR(sensor->cci), ++ "Failed to initialize CCI\n"); ++ ++ v4l2_i2c_subdev_init(&sensor->sd, client, &os04c10_subdev_ops); ++ ++ ret = os04c10_parse_hw_config(sensor); ++ if (ret) ++ return ret; ++ ++ ret = os04c10_power_on(sensor->dev); ++ if (ret) ++ return dev_err_probe(sensor->dev, ret, ++ "Could not power on the device\n"); ++ ++ ret = os04c10_identify_module(sensor); ++ if (ret) { ++ dev_err(&client->dev, "failed to find sensor: %d\n", ret); ++ goto error_power_off; ++ } ++ ++ sensor->cur_mode = &os04c10_modes[0]; ++ ret = os04c10_init_controls(sensor); ++ if (ret) { ++ dev_err(&client->dev, "failed to init controls: %d\n", ret); ++ goto error_power_off; ++ } ++ ++ sensor->sd.internal_ops = &os04c10_internal_ops; ++ sensor->sd.flags |= ++ V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; ++ sensor->sd.entity.ops = &os04c10_subdev_entity_ops; ++ sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; ++ ++ sensor->pad.flags = MEDIA_PAD_FL_SOURCE; ++ ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad); ++ if (ret) { ++ dev_err(&client->dev, "failed to init entity pads: %d\n", ret); ++ goto error_ctrl_handler; ++ } ++ ++ sensor->sd.state_lock = sensor->ctrl_handler.lock; ++ ret = v4l2_subdev_init_finalize(&sensor->sd); ++ if (ret < 0) { ++ dev_err(sensor->dev, "v4l2 subdev init error: %d\n", ret); ++ goto error_media_entity; ++ } ++ ++ ret = v4l2_async_register_subdev_sensor(&sensor->sd); ++ if (ret < 0) { ++ dev_err(&client->dev, ++ "failed to register V4L2 subdev: %d\n", ret); ++ goto error_subdev_cleanup; ++ } ++ ++ pm_runtime_set_active(sensor->dev); ++ pm_runtime_enable(sensor->dev); ++ pm_runtime_idle(sensor->dev); ++ ++ return 0; ++ ++error_subdev_cleanup: ++ v4l2_subdev_cleanup(&sensor->sd); ++ ++error_media_entity: ++ media_entity_cleanup(&sensor->sd.entity); ++ ++error_ctrl_handler: ++ v4l2_ctrl_handler_free(sensor->sd.ctrl_handler); ++ ++error_power_off: ++ os04c10_power_off(sensor->dev); ++ return ret; ++} ++ ++static void os04c10_remove(struct i2c_client *client) ++{ ++ struct v4l2_subdev *sd = i2c_get_clientdata(client); ++ struct os04c10 *sensor = to_os04c10(sd); ++ ++ v4l2_async_unregister_subdev(sd); ++ v4l2_subdev_cleanup(sd); ++ media_entity_cleanup(&sd->entity); ++ v4l2_ctrl_handler_free(sd->ctrl_handler); ++ ++ pm_runtime_disable(&client->dev); ++ if (!pm_runtime_status_suspended(&client->dev)) ++ os04c10_power_off(&client->dev); ++ pm_runtime_set_suspended(&client->dev); ++} ++ ++static const struct of_device_id os04c10_of_match[] = { ++ { .compatible = "ovti,os04c10" }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, os04c10_of_match); ++ ++static const struct dev_pm_ops os04c10_pm_ops = { ++ SET_RUNTIME_PM_OPS(os04c10_power_off, os04c10_power_on, NULL) ++}; ++ ++static struct i2c_driver os04c10_i2c_driver = { ++ .driver = { ++ .name = "os04c10", ++ .of_match_table = of_match_ptr(os04c10_of_match), ++ .pm = &os04c10_pm_ops, ++ }, ++ .probe = os04c10_probe, ++ .remove = os04c10_remove, ++}; ++ ++module_i2c_driver(os04c10_i2c_driver); ++ ++MODULE_DESCRIPTION("OmniVision OS04C10 sensor driver"); ++MODULE_LICENSE("GPL"); diff --git a/kernel/patches/0013-i2c-qcom-cci-support-combined-register-reads.patch b/kernel/patches/0013-i2c-qcom-cci-support-combined-register-reads.patch new file mode 100644 index 0000000..570d915 --- /dev/null +++ b/kernel/patches/0013-i2c-qcom-cci-support-combined-register-reads.patch @@ -0,0 +1,106 @@ +diff --git a/drivers/i2c/busses/i2c-qcom-cci.c b/drivers/i2c/busses/i2c-qcom-cci.c +index e631d79baf14..8f79667cc592 100644 +--- a/drivers/i2c/busses/i2c-qcom-cci.c ++++ b/drivers/i2c/busses/i2c-qcom-cci.c +@@ -422,6 +422,79 @@ static int cci_i2c_write(struct cci *cci, u16 master, + return cci_run_queue(cci, master, queue); + } + ++static int cci_i2c_write_read(struct cci *cci, u16 master, u16 addr, ++ u8 *wbuf, u16 wlen, u8 *rbuf, u16 rlen) ++{ ++ u32 val, words_read, words_exp; ++ u8 queue = QUEUE_1; ++ u8 load[12] = { 0 }; ++ int i = 0, j, index = 0, ret; ++ bool first = true; ++ ++ /* ++ * Match cci_i2c_write() payload packing limits. If callers need a ++ * larger write preamble, fall back to separate write/read transfers. ++ */ ++ if (wlen > ARRAY_SIZE(load) - 1) ++ return -EOPNOTSUPP; ++ ++ ret = cci_validate_queue(cci, master, queue); ++ if (ret < 0) ++ return ret; ++ ++ val = CCI_I2C_SET_PARAM | (addr & 0x7f) << 4; ++ writel(val, cci->base + CCI_I2C_Mm_Qn_LOAD_DATA(master, queue)); ++ ++ load[i++] = CCI_I2C_WRITE | wlen << 4; ++ for (j = 0; j < wlen; j++) ++ load[i++] = wbuf[j]; ++ ++ for (j = 0; j < i; j += 4) { ++ val = load[j]; ++ val |= load[j + 1] << 8; ++ val |= load[j + 2] << 16; ++ val |= load[j + 3] << 24; ++ writel(val, cci->base + CCI_I2C_Mm_Qn_LOAD_DATA(master, queue)); ++ } ++ ++ val = CCI_I2C_READ | rlen << 4; ++ writel(val, cci->base + CCI_I2C_Mm_Qn_LOAD_DATA(master, queue)); ++ ++ val = CCI_I2C_REPORT | CCI_I2C_REPORT_IRQ_EN; ++ writel(val, cci->base + CCI_I2C_Mm_Qn_LOAD_DATA(master, queue)); ++ ++ ret = cci_run_queue(cci, master, queue); ++ if (ret < 0) ++ return ret; ++ ++ words_read = readl(cci->base + CCI_I2C_Mm_READ_BUF_LEVEL(master)); ++ words_exp = rlen / 4 + 1; ++ if (words_read != words_exp) { ++ dev_err(cci->dev, "words read = %d, words expected = %d\n", ++ words_read, words_exp); ++ return -EIO; ++ } ++ ++ do { ++ val = readl(cci->base + CCI_I2C_Mm_READ_DATA(master)); ++ ++ for (i = 0; i < 4 && index < rlen; i++) { ++ if (first) { ++ /* ++ * The LS byte of this register represents ++ * the first byte read from the slave during a ++ * read access. ++ */ ++ first = false; ++ continue; ++ } ++ rbuf[index++] = (val >> (i * 8)) & 0xff; ++ } ++ } while (--words_read); ++ ++ return 0; ++} ++ + static int cci_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) + { + struct cci_master *cci_master = i2c_get_adapdata(adap); +@@ -433,6 +506,21 @@ static int cci_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) + goto err; + + for (i = 0; i < num; i++) { ++ if (i + 1 < num && ++ !(msgs[i].flags & I2C_M_RD) && ++ (msgs[i + 1].flags & I2C_M_RD) && ++ msgs[i].addr == msgs[i + 1].addr) { ++ ret = cci_i2c_write_read(cci, cci_master->master, ++ msgs[i].addr, msgs[i].buf, ++ msgs[i].len, msgs[i + 1].buf, ++ msgs[i + 1].len); ++ if (ret < 0) ++ break; ++ ++ i++; ++ continue; ++ } ++ + if (msgs[i].flags & I2C_M_RD) + ret = cci_i2c_read(cci, cci_master->master, + msgs[i].addr, msgs[i].buf,