From f22030f1e2bb08498bf7efb378449e8a7d95c30e Mon Sep 17 00:00:00 2001 From: Leon Silcott Date: Sat, 29 Aug 2026 09:29:16 +0100 Subject: [PATCH 1/5] dt-bindings: media: add Sony IMX681 Document the Sony IMX681 image sensor and its currently validated 3840x2640 RAW10 profile. The profile uses a single CSI-2 C-PHY trio at 2406 Msymbol/s and an external 19.2 MHz clock. List turbinebmw as maintainer, following the original public camera review series from which the sensor work was developed. Link: https://github.com/turbineBMW/surface-pro-11-linux/blob/05e5335bc72476d44390336701cf03efa5fd0165/kernel/sp11-camera-review.patch Link: https://github.com/ooaklee/linux_ms_dev_kit-sp11/commit/d915d679423e002b27f60e6b27116a03e18a96d0 Signed-off-by: Leon Silcott --- .../bindings/media/i2c/sony,imx681.yaml | 109 ++++++++++++++++++ MAINTAINERS | 8 ++ 2 files changed, 117 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/i2c/sony,imx681.yaml diff --git a/Documentation/devicetree/bindings/media/i2c/sony,imx681.yaml b/Documentation/devicetree/bindings/media/i2c/sony,imx681.yaml new file mode 100644 index 00000000000000..afc476717511e1 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/sony,imx681.yaml @@ -0,0 +1,109 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/sony,imx681.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Sony IMX681 CMOS Image Sensor + +maintainers: + - turbinebmw + +description: + The Sony IMX681 is a CMOS image sensor. The currently supported mode is + 3840x2640 RAW10 at approximately 30 frames per second over one MIPI CSI-2 + C-PHY trio at 2406 Msymbol/s. + +allOf: + - $ref: /schemas/media/video-interface-devices.yaml# + +properties: + compatible: + const: sony,imx681 + + reg: + maxItems: 1 + + clocks: + description: Input clock at 19.2 MHz + maxItems: 1 + + avdd-supply: + description: Analog power supply + + dovdd-supply: + description: Digital I/O power supply + + reset-gpios: + description: Active-low sensor reset (XCLR) GPIO + maxItems: 1 + + leds: true + led-names: true + orientation: true + rotation: true + + port: + $ref: /schemas/graph.yaml#/$defs/port-base + additionalProperties: false + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + additionalProperties: false + + properties: + bus-type: + const: 1 # MIPI CSI-2 C-PHY + data-lanes: + items: + - const: 0 + link-frequencies: true + remote-endpoint: true + + required: + - bus-type + - data-lanes + - link-frequencies + + required: + - endpoint + +required: + - compatible + - reg + - clocks + - avdd-supply + - dovdd-supply + - port + +unevaluatedProperties: false + +examples: + - | + #include + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + camera@10 { + compatible = "sony,imx681"; + reg = <0x10>; + clocks = <&camera_clk>; + avdd-supply = <&camera_avdd>; + dovdd-supply = <&camera_dovdd>; + reset-gpios = <&gpio 10 GPIO_ACTIVE_LOW>; + + port { + imx681_ep: endpoint { + bus-type = ; + data-lanes = <0>; + link-frequencies = /bits/ 64 <1203000000>; + remote-endpoint = <&csi_in>; + }; + }; + }; + }; +... diff --git a/MAINTAINERS b/MAINTAINERS index 70fc03bf3d367d..66e29ffd2ddc6d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -25304,6 +25304,14 @@ T: git git://linuxtv.org/media.git F: Documentation/devicetree/bindings/media/i2c/sony,imx415.yaml F: drivers/media/i2c/imx415.c +SONY IMX681 SENSOR DRIVER +M: turbinebmw +L: linux-media@vger.kernel.org +S: Maintained +T: git git://linuxtv.org/media.git +F: Documentation/devicetree/bindings/media/i2c/sony,imx681.yaml +F: drivers/media/i2c/imx681* + SONY MEMORYSTICK SUBSYSTEM M: Maxim Levitsky M: Alex Dubov From ddc5bc6c2804344bc903b110cffbdca2880bc05b Mon Sep 17 00:00:00 2001 From: Leon Silcott Date: Sat, 29 Aug 2026 09:29:16 +0100 Subject: [PATCH 2/5] media: i2c: add Sony IMX681 sensor driver Add a standalone V4L2 driver for the IMX681 front-camera mode validated on the Surface Pro 11. The driver programs 3840x2640 RAW10 at about 30 frames per second and supports frame length, 24-bit exposure, analog gain, and digital gain controls. Reject firmware descriptions other than the validated one-trio C-PHY topology, 1.203 GHz V4L2 link frequency, and 19.2 MHz input clock before powering the sensor. This keeps the observed register sequence scoped to the hardware profile on which it was tested. The register tables and initial driver were developed from turbinebmw's public camera review series, then corrected and runtime-validated in the SP11 integration branch. Link: https://github.com/turbineBMW/surface-pro-11-linux/blob/05e5335bc72476d44390336701cf03efa5fd0165/kernel/sp11-camera-review.patch Link: https://github.com/ooaklee/linux_ms_dev_kit-sp11/commit/d915d679423e002b27f60e6b27116a03e18a96d0 Signed-off-by: Leon Silcott --- arch/arm64/configs/ubuntu_x1e_defconfig | 1 + debian.qcom-x1e/config/annotations | 1 + drivers/media/i2c/Kconfig | 11 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/imx681-tables.h | 455 ++++++++++++++++++ drivers/media/i2c/imx681.c | 607 ++++++++++++++++++++++++ 6 files changed, 1076 insertions(+) create mode 100644 drivers/media/i2c/imx681-tables.h create mode 100644 drivers/media/i2c/imx681.c diff --git a/arch/arm64/configs/ubuntu_x1e_defconfig b/arch/arm64/configs/ubuntu_x1e_defconfig index 2728f36428bf4e..78ce65c3d6773c 100644 --- a/arch/arm64/configs/ubuntu_x1e_defconfig +++ b/arch/arm64/configs/ubuntu_x1e_defconfig @@ -3903,6 +3903,7 @@ CONFIG_VIDEO_IMX335=m CONFIG_VIDEO_IMX355=m CONFIG_VIDEO_IMX412=m CONFIG_VIDEO_IMX415=m +CONFIG_VIDEO_IMX681=m CONFIG_VIDEO_MT9M001=m CONFIG_VIDEO_MT9M111=m CONFIG_VIDEO_MT9M114=m diff --git a/debian.qcom-x1e/config/annotations b/debian.qcom-x1e/config/annotations index 6dc5c30bdc3d27..05e33fb87a66b7 100644 --- a/debian.qcom-x1e/config/annotations +++ b/debian.qcom-x1e/config/annotations @@ -13050,6 +13050,7 @@ CONFIG_VIDEO_IMX335 policy<{'arm64': 'm'}> CONFIG_VIDEO_IMX355 policy<{'arm64': 'm'}> CONFIG_VIDEO_IMX412 policy<{'arm64': 'm'}> CONFIG_VIDEO_IMX415 policy<{'arm64': 'm'}> +CONFIG_VIDEO_IMX681 policy<{'arm64': 'm'}> CONFIG_VIDEO_IMX7_CSI policy<{'arm64': 'm'}> CONFIG_VIDEO_IMX8MQ_MIPI_CSI2 policy<{'arm64': 'm'}> CONFIG_VIDEO_IMX8_ISI policy<{'arm64': 'm'}> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 5d173e0ecf424f..cfa8c7ac0c17f0 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -287,6 +287,17 @@ config VIDEO_IMX415 To compile this driver as a module, choose M here: the module will be called imx415. +config VIDEO_IMX681 + tristate "Sony IMX681 sensor support" + depends on OF_GPIO + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the Sony IMX681 camera. + It currently supports a 3840x2640 RAW10 C-PHY mode. + + To compile this driver as a module, choose M here: the + module will be called imx681. + config VIDEO_MAX9271_LIB tristate diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index e45359efe0e41e..2c8605c580876e 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -61,6 +61,7 @@ obj-$(CONFIG_VIDEO_IMX335) += imx335.o obj-$(CONFIG_VIDEO_IMX355) += imx355.o obj-$(CONFIG_VIDEO_IMX412) += imx412.o obj-$(CONFIG_VIDEO_IMX415) += imx415.o +obj-$(CONFIG_VIDEO_IMX681) += imx681.o obj-$(CONFIG_VIDEO_IR_I2C) += ir-kbd-i2c.o obj-$(CONFIG_VIDEO_ISL7998X) += isl7998x.o obj-$(CONFIG_VIDEO_KS0127) += ks0127.o diff --git a/drivers/media/i2c/imx681-tables.h b/drivers/media/i2c/imx681-tables.h new file mode 100644 index 00000000000000..b6ec68594490c1 --- /dev/null +++ b/drivers/media/i2c/imx681-tables.h @@ -0,0 +1,455 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Sony IMX681 register tables for the Surface Pro 11 front camera. + * + * These tables are an independently written representation of runtime I2C + * transactions observed on the author's own hardware. The observed + * stream-start order is global initialization, followed by the selected mode. + * Mode: 3840x2640 RAW10, LLP 6752, FLL 3554, C-PHY bus pixel rate + * 548571428 Hz, ONE C-PHY TRIO at 2406 Msym/s. Runtime frame cadence is + * approximately 30 fps; the distinct VT timing clock remains to be measured. + */ +#ifndef __IMX681_TABLES_H +#define __IMX681_TABLES_H + +#include + +static const struct cci_reg_sequence imx681_global_init_table[] = { + { CCI_REG8(0x0136), 0x13 }, + { CCI_REG8(0x0137), 0x33 }, + { CCI_REG8(0x002c), 0x05 }, + { CCI_REG8(0x002d), 0x05 }, + { CCI_REG8(0x0111), 0x03 }, + { CCI_REG8(0x30eb), 0x05 }, + { CCI_REG8(0x30eb), 0x0c }, + { CCI_REG8(0x300a), 0xff }, + { CCI_REG8(0x300b), 0xff }, + { CCI_REG8(0x3532), 0xff }, + { CCI_REG8(0x3533), 0xff }, + { CCI_REG8(0x051e), 0x00 }, + { CCI_REG8(0x0905), 0x04 }, + { CCI_REG8(0x2029), 0x01 }, + { CCI_REG8(0x202a), 0x11 }, + { CCI_REG8(0x20a1), 0x00 }, + { CCI_REG8(0x20a2), 0x02 }, + { CCI_REG8(0x20a3), 0x03 }, + { CCI_REG8(0x20ac), 0x01 }, + { CCI_REG8(0x20ad), 0x01 }, + { CCI_REG8(0x20ae), 0x01 }, + { CCI_REG8(0x20af), 0x01 }, + { CCI_REG8(0x20b0), 0x00 }, + { CCI_REG8(0x20b1), 0x01 }, + { CCI_REG8(0x20b2), 0x02 }, + { CCI_REG8(0x20b3), 0x03 }, + { CCI_REG8(0x706f), 0x00 }, + { CCI_REG8(0x7130), 0x08 }, + { CCI_REG8(0x7131), 0x08 }, + { CCI_REG8(0x7408), 0x89 }, + { CCI_REG8(0x7437), 0x3d }, + { CCI_REG8(0x7439), 0x29 }, + { CCI_REG8(0x7443), 0x38 }, + { CCI_REG8(0x7447), 0x55 }, + { CCI_REG8(0x744b), 0x00 }, + { CCI_REG8(0x7451), 0x8e }, + { CCI_REG8(0x746d), 0x29 }, + { CCI_REG8(0x747d), 0x68 }, + { CCI_REG8(0x7481), 0x60 }, + { CCI_REG8(0x7491), 0x2d }, + { CCI_REG8(0x7493), 0x31 }, + { CCI_REG8(0x74a5), 0x52 }, + { CCI_REG8(0x74af), 0x4a }, + { CCI_REG8(0x74b5), 0x1f }, + { CCI_REG8(0x74b7), 0x31 }, + { CCI_REG8(0x74bd), 0x75 }, + { CCI_REG8(0x74c5), 0x06 }, + { CCI_REG8(0x74c9), 0x52 }, + { CCI_REG8(0x74d3), 0x4a }, + { CCI_REG8(0x74d9), 0x1f }, + { CCI_REG8(0x74db), 0x31 }, + { CCI_REG8(0x74e1), 0x75 }, + { CCI_REG8(0x74e9), 0x06 }, + { CCI_REG8(0x74ed), 0x52 }, + { CCI_REG8(0x74f7), 0x4a }, + { CCI_REG8(0x74fd), 0x1f }, + { CCI_REG8(0x74ff), 0x31 }, + { CCI_REG8(0x7505), 0x75 }, + { CCI_REG8(0x750d), 0x06 }, + { CCI_REG8(0x7537), 0x38 }, + { CCI_REG8(0x753d), 0x4a }, + { CCI_REG8(0x753f), 0x4a }, + { CCI_REG8(0x7541), 0x4a }, + { CCI_REG8(0x7549), 0x8e }, + { CCI_REG8(0x754f), 0x75 }, + { CCI_REG8(0x7551), 0x75 }, + { CCI_REG8(0x7553), 0x75 }, + { CCI_REG8(0x792b), 0x39 }, + { CCI_REG8(0x792d), 0x43 }, + { CCI_REG8(0x79d3), 0x25 }, + { CCI_REG8(0x79d6), 0x8e }, + { CCI_REG8(0x79d7), 0x01 }, + { CCI_REG8(0x79d8), 0xe7 }, + { CCI_REG8(0x79d9), 0x25 }, + { CCI_REG8(0x79db), 0x76 }, + { CCI_REG8(0x79dc), 0x8e }, + { CCI_REG8(0x79dd), 0x01 }, + { CCI_REG8(0x79de), 0xe7 }, + { CCI_REG8(0x79df), 0x25 }, + { CCI_REG8(0x79e1), 0x76 }, + { CCI_REG8(0x79e2), 0x8e }, + { CCI_REG8(0x79e3), 0x01 }, + { CCI_REG8(0x79e4), 0xe7 }, + { CCI_REG8(0x79e5), 0x25 }, + { CCI_REG8(0x79e7), 0x76 }, + { CCI_REG8(0x79e8), 0x8e }, + { CCI_REG8(0x7a01), 0xff }, + { CCI_REG8(0x7a29), 0x6c }, + { CCI_REG8(0x7a2b), 0xda }, + { CCI_REG8(0x7a34), 0x6c }, + { CCI_REG8(0x7a37), 0xda }, + { CCI_REG8(0x7a40), 0x6c }, + { CCI_REG8(0x7a43), 0xda }, + { CCI_REG8(0x7b08), 0x00 }, + { CCI_REG8(0x7b09), 0x01 }, + { CCI_REG8(0x7c03), 0x38 }, + { CCI_REG8(0x7c09), 0x4a }, + { CCI_REG8(0x7c0b), 0x4a }, + { CCI_REG8(0x7c0d), 0x4a }, + { CCI_REG8(0x7c13), 0x8e }, + { CCI_REG8(0x7c19), 0x75 }, + { CCI_REG8(0x7c1b), 0x75 }, + { CCI_REG8(0x7c1d), 0x75 }, + { CCI_REG8(0x7c90), 0x00 }, + { CCI_REG8(0x7c91), 0x00 }, + { CCI_REG8(0x7c92), 0x00 }, + { CCI_REG8(0x7c9d), 0x01 }, + { CCI_REG8(0x7c9e), 0x01 }, + { CCI_REG8(0x7c9f), 0x01 }, + { CCI_REG8(0x7e9b), 0x07 }, + { CCI_REG8(0x7f09), 0x00 }, + { CCI_REG8(0x7f36), 0x00 }, + { CCI_REG8(0x7f4f), 0x0a }, + { CCI_REG8(0x7f50), 0x0a }, + { CCI_REG8(0x7f51), 0x0a }, + { CCI_REG8(0x7f55), 0x05 }, + { CCI_REG8(0x7f56), 0x05 }, + { CCI_REG8(0x7f57), 0x05 }, + { CCI_REG8(0x7f5b), 0x03 }, + { CCI_REG8(0x7f5c), 0x03 }, + { CCI_REG8(0x7f5d), 0x03 }, + { CCI_REG8(0x7f61), 0x03 }, + { CCI_REG8(0x7f62), 0x03 }, + { CCI_REG8(0x7f63), 0x03 }, + { CCI_REG8(0x7f67), 0x03 }, + { CCI_REG8(0x7f68), 0x03 }, + { CCI_REG8(0x7f69), 0x03 }, + { CCI_REG8(0x7f6a), 0x05 }, + { CCI_REG8(0x7f6b), 0x05 }, + { CCI_REG8(0x7f6c), 0x05 }, + { CCI_REG8(0x7f6d), 0x11 }, + { CCI_REG8(0x7f6e), 0x14 }, + { CCI_REG8(0x7f6f), 0x14 }, + { CCI_REG8(0x7f73), 0x14 }, + { CCI_REG8(0x7f74), 0x1c }, + { CCI_REG8(0x7f75), 0x14 }, + { CCI_REG8(0x7f76), 0x08 }, + { CCI_REG8(0x7f79), 0x14 }, + { CCI_REG8(0x7f7a), 0x1c }, + { CCI_REG8(0x7f7b), 0x14 }, + { CCI_REG8(0x7f7f), 0x1c }, + { CCI_REG8(0x7f80), 0x1c }, + { CCI_REG8(0x7f81), 0x1c }, + { CCI_REG8(0x7f85), 0x1c }, + { CCI_REG8(0x7f86), 0x1c }, + { CCI_REG8(0x7f87), 0x1c }, + { CCI_REG8(0x7f9d), 0x09 }, + { CCI_REG8(0x7f9e), 0x09 }, + { CCI_REG8(0x7f9f), 0x09 }, + { CCI_REG8(0x7fa3), 0x09 }, + { CCI_REG8(0x7fa4), 0x09 }, + { CCI_REG8(0x7fa5), 0x09 }, + { CCI_REG8(0x7fac), 0x00 }, + { CCI_REG8(0x7fad), 0x00 }, + { CCI_REG8(0x7fae), 0x00 }, + { CCI_REG8(0x7faf), 0x00 }, + { CCI_REG8(0x7fb0), 0x00 }, + { CCI_REG8(0x7fb1), 0x00 }, + { CCI_REG8(0x7fb2), 0x00 }, + { CCI_REG8(0x7fb3), 0x00 }, + { CCI_REG8(0x7fb4), 0x00 }, + { CCI_REG8(0x7fb5), 0x00 }, + { CCI_REG8(0x7fb6), 0x00 }, + { CCI_REG8(0x7fb7), 0x00 }, + { CCI_REG8(0x7fb8), 0x00 }, + { CCI_REG8(0x7fb9), 0x00 }, + { CCI_REG8(0x7fba), 0x00 }, + { CCI_REG8(0x7fbb), 0x00 }, + { CCI_REG8(0x7fbc), 0x00 }, + { CCI_REG8(0x7fbd), 0x00 }, + { CCI_REG8(0x7fbe), 0x00 }, + { CCI_REG8(0x7fbf), 0x00 }, + { CCI_REG8(0x7fc0), 0x00 }, + { CCI_REG8(0x7fc1), 0x00 }, + { CCI_REG8(0x7fc2), 0x00 }, + { CCI_REG8(0x7fc3), 0x00 }, + { CCI_REG8(0x7fcb), 0x37 }, + { CCI_REG8(0x7fcd), 0x37 }, + { CCI_REG8(0x7fcf), 0x37 }, + { CCI_REG8(0x7fd7), 0x44 }, + { CCI_REG8(0x7fd9), 0x44 }, + { CCI_REG8(0x7fdb), 0x44 }, + { CCI_REG8(0x7fdd), 0x38 }, + { CCI_REG8(0x7fe3), 0x4a }, + { CCI_REG8(0x7fe5), 0x4a }, + { CCI_REG8(0x7fe7), 0x4a }, + { CCI_REG8(0x7fef), 0x4a }, + { CCI_REG8(0x7ff1), 0x4a }, + { CCI_REG8(0x7ff3), 0x4a }, + { CCI_REG8(0x7ffb), 0x4a }, + { CCI_REG8(0x7ffd), 0x4a }, + { CCI_REG8(0x7fff), 0x4a }, + { CCI_REG8(0x8007), 0x62 }, + { CCI_REG8(0x8009), 0x62 }, + { CCI_REG8(0x800b), 0x62 }, + { CCI_REG8(0x8013), 0x6f }, + { CCI_REG8(0x8015), 0x6f }, + { CCI_REG8(0x8017), 0x6f }, + { CCI_REG8(0x8019), 0x8e }, + { CCI_REG8(0x801f), 0x75 }, + { CCI_REG8(0x8021), 0x75 }, + { CCI_REG8(0x8023), 0x75 }, + { CCI_REG8(0x802b), 0x75 }, + { CCI_REG8(0x802d), 0x75 }, + { CCI_REG8(0x802f), 0x75 }, + { CCI_REG8(0x8037), 0x75 }, + { CCI_REG8(0x8039), 0x75 }, + { CCI_REG8(0x803b), 0x75 }, + { CCI_REG8(0x803c), 0x13 }, + { CCI_REG8(0x803d), 0x17 }, + { CCI_REG8(0x803e), 0x15 }, + { CCI_REG8(0x803f), 0x11 }, + { CCI_REG8(0x8040), 0x0a }, + { CCI_REG8(0x8041), 0x08 }, + { CCI_REG8(0x8047), 0x17 }, + { CCI_REG8(0x80f0), 0x24 }, + { CCI_REG8(0x80f1), 0x1b }, + { CCI_REG8(0x80f2), 0x1a }, + { CCI_REG8(0x80f3), 0x14 }, + { CCI_REG8(0x80f4), 0x14 }, + { CCI_REG8(0x80f5), 0x12 }, + { CCI_REG8(0x80f6), 0x25 }, + { CCI_REG8(0x80f7), 0x1c }, + { CCI_REG8(0x80f8), 0x1b }, + { CCI_REG8(0x80f9), 0x18 }, + { CCI_REG8(0x80fa), 0x17 }, + { CCI_REG8(0x80fb), 0x18 }, + { CCI_REG8(0x80fc), 0x26 }, + { CCI_REG8(0x80fd), 0x1e }, + { CCI_REG8(0x80fe), 0x1d }, + { CCI_REG8(0x80ff), 0x1c }, + { CCI_REG8(0x8100), 0x1b }, + { CCI_REG8(0x8101), 0x1c }, + { CCI_REG8(0x8102), 0x27 }, + { CCI_REG8(0x8103), 0x1e }, + { CCI_REG8(0x8104), 0x1d }, + { CCI_REG8(0x8105), 0x1e }, + { CCI_REG8(0x8106), 0x1e }, + { CCI_REG8(0x8107), 0x1e }, + { CCI_REG8(0x8108), 0x27 }, + { CCI_REG8(0x8109), 0x1e }, + { CCI_REG8(0x810a), 0x1e }, + { CCI_REG8(0x810b), 0x1e }, + { CCI_REG8(0x810c), 0x1e }, + { CCI_REG8(0x810d), 0x1f }, + { CCI_REG8(0x810e), 0x00 }, + { CCI_REG8(0x8168), 0x0b }, + { CCI_REG8(0x8169), 0x0b }, + { CCI_REG8(0x816a), 0x09 }, + { CCI_REG8(0x816b), 0x0f }, + { CCI_REG8(0x816c), 0x0f }, + { CCI_REG8(0x816d), 0x0f }, + { CCI_REG8(0x816e), 0x0b }, + { CCI_REG8(0x816f), 0x0b }, + { CCI_REG8(0x8170), 0x0a }, + { CCI_REG8(0x8171), 0x0f }, + { CCI_REG8(0x8172), 0x0f }, + { CCI_REG8(0x8173), 0x0f }, + { CCI_REG8(0x8174), 0x0d }, + { CCI_REG8(0x8175), 0x0c }, + { CCI_REG8(0x8176), 0x09 }, + { CCI_REG8(0x8177), 0x0f }, + { CCI_REG8(0x8178), 0x0f }, + { CCI_REG8(0x8179), 0x0f }, + { CCI_REG8(0x817a), 0x0c }, + { CCI_REG8(0x817b), 0x0d }, + { CCI_REG8(0x817c), 0x09 }, + { CCI_REG8(0x817d), 0x0f }, + { CCI_REG8(0x817e), 0x0f }, + { CCI_REG8(0x817f), 0x0f }, + { CCI_REG8(0x8180), 0x0d }, + { CCI_REG8(0x8181), 0x0d }, + { CCI_REG8(0x8182), 0x09 }, + { CCI_REG8(0x8183), 0x0f }, + { CCI_REG8(0x8184), 0x0f }, + { CCI_REG8(0x8185), 0x0f }, + { CCI_REG8(0x81b0), 0x03 }, + { CCI_REG8(0x81e3), 0x04 }, + { CCI_REG8(0x81e4), 0x04 }, + { CCI_REG8(0x81e9), 0x04 }, + { CCI_REG8(0x81ea), 0x04 }, + { CCI_REG8(0x81ef), 0x04 }, + { CCI_REG8(0x81f0), 0x04 }, + { CCI_REG8(0x9186), 0x00 }, + { CCI_REG8(0xd030), 0x01 }, + { CCI_REG8(0xd04c), 0x10 }, + { CCI_REG8(0xd123), 0x75 }, + { CCI_REG8(0xd144), 0x10 }, + { CCI_REG8(0xd1af), 0x08 }, + { CCI_REG8(0xd1bd), 0x67 }, + { CCI_REG8(0xd1d4), 0x04 }, + { CCI_REG8(0xd1d5), 0x04 }, + { CCI_REG8(0xd1d6), 0x07 }, + { CCI_REG8(0xd1d7), 0x07 }, + { CCI_REG8(0xd1d9), 0x40 }, + { CCI_REG8(0xd1db), 0x58 }, + { CCI_REG8(0xd1dd), 0xd4 }, + { CCI_REG8(0xd1df), 0xd4 }, + { CCI_REG8(0xd1e1), 0xd4 }, + { CCI_REG8(0xd348), 0x0f }, + { CCI_REG8(0xd357), 0x00 }, + { CCI_REG8(0xd3ae), 0x11 }, + { CCI_REG8(0xd3af), 0x44 }, + { CCI_REG8(0xd3b1), 0x7d }, + { CCI_REG8(0xd803), 0xf0 }, + { CCI_REG8(0xd80b), 0xf0 }, + { CCI_REG8(0xd813), 0xf1 }, + { CCI_REG8(0xd81b), 0xf0 }, + { CCI_REG8(0xd843), 0xf1 }, + { CCI_REG8(0xd84f), 0xf0 }, + { CCI_REG8(0xd934), 0x23 }, + { CCI_REG8(0xd935), 0xc8 }, + { CCI_REG8(0xd938), 0x27 }, + { CCI_REG8(0xd939), 0x10 }, + { CCI_REG8(0xd93a), 0x23 }, + { CCI_REG8(0xd93b), 0xc8 }, + { CCI_REG8(0xd955), 0x07 }, + { CCI_REG8(0xd95a), 0x04 }, + { CCI_REG8(0xd95b), 0x0a }, + { CCI_REG8(0xd95c), 0x1e }, + { CCI_REG8(0xd95d), 0x00 }, + { CCI_REG8(0xd95e), 0x14 }, + { CCI_REG8(0xd95f), 0x21 }, + { CCI_REG8(0xd960), 0x00 }, + { CCI_REG8(0xd961), 0x00 }, + { CCI_REG8(0xd962), 0x0a }, + { CCI_REG8(0xd963), 0x50 }, + { CCI_REG8(0xd964), 0x0a }, + { CCI_REG8(0xd965), 0xa0 }, + { CCI_REG8(0xd966), 0x00 }, + { CCI_REG8(0xd967), 0x28 }, + { CCI_REG8(0xd968), 0x0a }, + { CCI_REG8(0xd969), 0x50 }, + { CCI_REG8(0xd96a), 0x0a }, + { CCI_REG8(0xd96b), 0xa0 }, + { CCI_REG8(0xd96c), 0x00 }, + { CCI_REG8(0xd96d), 0x00 }, + { CCI_REG8(0xd96e), 0x0a }, + { CCI_REG8(0xd96f), 0x44 }, + { CCI_REG8(0xd970), 0x0a }, + { CCI_REG8(0xd971), 0x50 }, + { CCI_REG8(0xd972), 0x00 }, + { CCI_REG8(0xd973), 0x00 }, + { CCI_REG8(0xd974), 0x0a }, + { CCI_REG8(0xd975), 0x44 }, + { CCI_REG8(0xd976), 0x0a }, + { CCI_REG8(0xd977), 0x50 }, + { CCI_REG8(0xda10), 0x00 }, + { CCI_REG8(0xda11), 0x14 }, + { CCI_REG8(0xda12), 0x64 }, + { CCI_REG8(0xda13), 0x00 }, + { CCI_REG8(0xda14), 0x14 }, + { CCI_REG8(0xda15), 0xc8 }, + { CCI_REG8(0xda22), 0x00 }, + { CCI_REG8(0xda23), 0x56 }, + { CCI_REG8(0xda24), 0x00 }, + { CCI_REG8(0xda25), 0xb5 }, + { CCI_REG8(0xda26), 0x00 }, + { CCI_REG8(0xda27), 0xe8 }, + { CCI_REG8(0xda28), 0x08 }, + { CCI_REG8(0xda29), 0xa6 }, + { CCI_REG8(0xda2a), 0x00 }, + { CCI_REG8(0xda2b), 0xa2 }, + { CCI_REG8(0xda2f), 0x01 }, +}; + +static const struct cci_reg_sequence imx681_mode_3840x2640[] = { + { CCI_REG8(0x0110), 0x00 }, + { CCI_REG8(0x0112), 0x0a }, + { CCI_REG8(0x0113), 0x0a }, + { CCI_REG8(0x0114), 0x00 }, + { CCI_REG8(0x0342), 0x1a }, + { CCI_REG8(0x0343), 0x60 }, + { CCI_REG8(0x033d), 0x00 }, + { CCI_REG8(0x033e), 0x0d }, + { CCI_REG8(0x033f), 0xe2 }, + { CCI_REG8(0x0344), 0x00 }, + { CCI_REG8(0x0345), 0x68 }, + { CCI_REG8(0x0346), 0x01 }, + { CCI_REG8(0x0347), 0x00 }, + { CCI_REG8(0x0348), 0x0f }, + { CCI_REG8(0x0349), 0x67 }, + { CCI_REG8(0x034a), 0x0b }, + { CCI_REG8(0x034b), 0x4f }, + { CCI_REG8(0x017c), 0x01 }, + { CCI_REG8(0x017d), 0x01 }, + { CCI_REG8(0x017e), 0x00 }, + { CCI_REG8(0x017f), 0x01 }, + { CCI_REG8(0x0180), 0x00 }, + { CCI_REG8(0x038c), 0x13 }, + { CCI_REG8(0x038d), 0x33 }, + { CCI_REG8(0x2000), 0x01 }, + { CCI_REG8(0x0408), 0x00 }, + { CCI_REG8(0x0409), 0x00 }, + { CCI_REG8(0x040a), 0x00 }, + { CCI_REG8(0x040b), 0x00 }, + { CCI_REG8(0x040c), 0x0f }, + { CCI_REG8(0x040d), 0x00 }, + { CCI_REG8(0x040e), 0x0a }, + { CCI_REG8(0x040f), 0x50 }, + { CCI_REG8(0x034c), 0x0f }, + { CCI_REG8(0x034d), 0x00 }, + { CCI_REG8(0x034e), 0x0a }, + { CCI_REG8(0x034f), 0x50 }, + { CCI_REG8(0x0301), 0x06 }, + { CCI_REG8(0x0303), 0x02 }, + { CCI_REG8(0x0305), 0x02 }, + { CCI_REG8(0x0306), 0x00 }, + { CCI_REG8(0x0307), 0xe1 }, + { CCI_REG8(0x030d), 0x03 }, + { CCI_REG8(0x030e), 0x01 }, + { CCI_REG8(0x030f), 0x77 }, + { CCI_REG8(0x0323), 0x00 }, + { CCI_REG8(0x0229), 0x00 }, + { CCI_REG8(0x022a), 0x0d }, + { CCI_REG8(0x022b), 0xda }, + { CCI_REG8(0xd383), 0x01 }, + { CCI_REG8(0x0204), 0x00 }, + { CCI_REG8(0x0205), 0x00 }, + { CCI_REG8(0x020e), 0x01 }, + { CCI_REG8(0x020f), 0x00 }, + { CCI_REG8(0x0210), 0x01 }, + { CCI_REG8(0x0211), 0x00 }, + { CCI_REG8(0x0212), 0x01 }, + { CCI_REG8(0x0213), 0x00 }, + { CCI_REG8(0x0214), 0x01 }, + { CCI_REG8(0x0215), 0x00 }, + { CCI_REG8(0x6a83), 0x03 }, + { CCI_REG8(0x7e9b), 0x02 }, + { CCI_REG8(0xd1ce), 0x00 }, + { CCI_REG8(0xdc3c), 0x01 }, + { CCI_REG8(0x0368), 0x00 }, + { CCI_REG8(0x036a), 0x08 }, + { CCI_REG8(0x036b), 0x70 }, + { CCI_REG8(0x2001), 0x00 }, +}; + +#endif /* __IMX681_TABLES_H */ diff --git a/drivers/media/i2c/imx681.c b/drivers/media/i2c/imx681.c new file mode 100644 index 00000000000000..0ee2a3a3ee5532 --- /dev/null +++ b/drivers/media/i2c/imx681.c @@ -0,0 +1,607 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Sony IMX681 sensor driver. + * + * The SP11 mode is an independently written representation of register + * transactions observed while operating the author's own camera hardware. + * Single supported mode: + * 3840x2640 RAW10 at ~30 fps over ONE C-PHY TRIO at 2406 Msym/s + * (CSI_SIGNALING_MODE 0x0111=3). + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "imx681-tables.h" + +#define IMX681_REG_MODEL_ID CCI_REG16(0x0016) +#define IMX681_MODEL_ID 0x0681 +#define IMX681_REG_MODE_SELECT CCI_REG8(0x0100) +#define IMX681_MODE_STREAMING 1 +#define IMX681_MODE_STANDBY 0 +#define IMX681_REG_GROUP_HOLD CCI_REG8(0x0104) +#define IMX681_REG_EXPOSURE CCI_REG24(0x0229) +#define IMX681_REG_ANALOG_GAIN CCI_REG16(0x0204) +#define IMX681_REG_DIGITAL_GAIN CCI_REG16(0x020e) +#define IMX681_REG_FRAME_LENGTH CCI_REG16(0x0340) + +#define IMX681_WIDTH 3840 +#define IMX681_HEIGHT 2640 +#define IMX681_LINE_LENGTH 6752 +#define IMX681_FRAME_LENGTH_DEF 2708 +#define IMX681_FRAME_LENGTH_MAX 0xffff +#define IMX681_EXPOSURE_MARGIN 48 +#define IMX681_EXPOSURE_DEF 1600 +#define IMX681_AGAIN_MAX 960 /* gain = 1024 / (1024 - code) */ +#define IMX681_DGAIN_DEF 0x0100 +#define IMX681_PIXEL_RATE 548571428 +/* V4L2 C-PHY link frequency is half the 2406-Msym/s symbol rate. */ +#define IMX681_LINK_FREQ 1203000000 +#define IMX681_MBUS_CODE MEDIA_BUS_FMT_SRGGB10_1X10 +#define IMX681_XCLK_RATE 19200000 + +#define IMX681_XCLR_MIN_DELAY_US 6200 +#define IMX681_XCLR_DELAY_RANGE_US 1000 + +static const char * const imx681_supply_name[] = { + "dovdd", /* I/O 1.8 V */ + "avdd", /* analog 2.8 V */ +}; + +static const s64 imx681_link_freq_menu[] = { + IMX681_LINK_FREQ, +}; + +struct imx681 { + struct device *dev; + struct regmap *regmap; + struct clk *xclk; + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[ARRAY_SIZE(imx681_supply_name)]; + + struct v4l2_subdev sd; + struct media_pad pad; + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_ctrl *vblank_ctrl; + struct v4l2_ctrl *expo_ctrl; + struct v4l2_ctrl *again_ctrl; + struct v4l2_ctrl *dgain_ctrl; +}; + +static inline struct imx681 *to_imx681(struct v4l2_subdev *sd) +{ + return container_of(sd, struct imx681, sd); +} + +static int imx681_power_on(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct imx681 *sensor = to_imx681(sd); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(imx681_supply_name), + sensor->supplies); + if (ret) { + dev_err(dev, "failed to enable regulators: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(sensor->xclk); + if (ret) { + dev_err(dev, "failed to enable xclk: %d\n", ret); + goto disable_reg; + } + + gpiod_set_value_cansleep(sensor->reset_gpio, 0); + usleep_range(IMX681_XCLR_MIN_DELAY_US, + IMX681_XCLR_MIN_DELAY_US + IMX681_XCLR_DELAY_RANGE_US); + return 0; + +disable_reg: + regulator_bulk_disable(ARRAY_SIZE(imx681_supply_name), + sensor->supplies); + return ret; +} + +static int imx681_power_off(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct imx681 *sensor = to_imx681(sd); + + gpiod_set_value_cansleep(sensor->reset_gpio, 1); + clk_disable_unprepare(sensor->xclk); + regulator_bulk_disable(ARRAY_SIZE(imx681_supply_name), + sensor->supplies); + return 0; +} + +static int imx681_identify(struct imx681 *sensor) +{ + u64 id = 0; + int ret; + + ret = cci_read(sensor->regmap, IMX681_REG_MODEL_ID, &id, NULL); + if (ret) { + dev_err(sensor->dev, "model ID read failed: %d\n", ret); + return ret; + } + + dev_info(sensor->dev, "detected model id 0x%04llx%s\n", id, + id == IMX681_MODEL_ID ? " (IMX681)" : " (UNEXPECTED)"); + + return id == IMX681_MODEL_ID ? 0 : -ENODEV; +} + +static int imx681_parse_endpoint(struct imx681 *sensor) +{ + struct v4l2_fwnode_endpoint ep = { 0 }; + struct fwnode_handle *endpoint; + int ret; + + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(sensor->dev), + NULL); + if (!endpoint) + return dev_err_probe(sensor->dev, -EINVAL, + "endpoint node not found\n"); + + ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep); + fwnode_handle_put(endpoint); + if (ret) + return dev_err_probe(sensor->dev, ret, + "failed to parse endpoint\n"); + + if (ep.bus_type != V4L2_MBUS_CSI2_CPHY) { + ret = dev_err_probe(sensor->dev, -EINVAL, + "only CSI-2 C-PHY is supported\n"); + goto free_endpoint; + } + + if (ep.bus.mipi_csi2.num_data_lanes != 1 || + ep.bus.mipi_csi2.data_lanes[0] != 0) { + ret = dev_err_probe(sensor->dev, -EINVAL, + "only C-PHY trio 0 is supported\n"); + goto free_endpoint; + } + + if (ep.nr_of_link_frequencies != 1 || + ep.link_frequencies[0] != IMX681_LINK_FREQ) { + ret = dev_err_probe(sensor->dev, -EINVAL, + "only a %u Hz link frequency is supported\n", + IMX681_LINK_FREQ); + goto free_endpoint; + } + + ret = 0; + +free_endpoint: + v4l2_fwnode_endpoint_free(&ep); + return ret; +} + +/* ----------------------------------------------------------- controls */ + +static int imx681_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct imx681 *sensor = + container_of(ctrl->handler, struct imx681, ctrl_handler); + int ret = 0; + + if (ctrl->id == V4L2_CID_VBLANK) { + unsigned int expo_max = IMX681_HEIGHT + ctrl->val - + IMX681_EXPOSURE_MARGIN; + + ret = __v4l2_ctrl_modify_range(sensor->expo_ctrl, + sensor->expo_ctrl->minimum, + expo_max, 1, + min_t(int, IMX681_EXPOSURE_DEF, + expo_max)); + if (ret) + return ret; + } + + if (!pm_runtime_get_if_in_use(sensor->dev)) + return 0; + + cci_write(sensor->regmap, IMX681_REG_GROUP_HOLD, 1, &ret); + switch (ctrl->id) { + case V4L2_CID_VBLANK: + cci_write(sensor->regmap, IMX681_REG_FRAME_LENGTH, + IMX681_HEIGHT + ctrl->val, &ret); + break; + case V4L2_CID_EXPOSURE: + cci_write(sensor->regmap, IMX681_REG_EXPOSURE, ctrl->val, + &ret); + break; + case V4L2_CID_ANALOGUE_GAIN: + cci_write(sensor->regmap, IMX681_REG_ANALOG_GAIN, ctrl->val, + &ret); + break; + case V4L2_CID_DIGITAL_GAIN: + cci_write(sensor->regmap, IMX681_REG_DIGITAL_GAIN, ctrl->val, + &ret); + break; + default: + break; + } + cci_write(sensor->regmap, IMX681_REG_GROUP_HOLD, 0, &ret); + + pm_runtime_put_autosuspend(sensor->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops imx681_ctrl_ops = { + .s_ctrl = imx681_s_ctrl, +}; + +static int imx681_init_controls(struct imx681 *sensor) +{ + struct v4l2_ctrl_handler *hdl = &sensor->ctrl_handler; + const struct v4l2_ctrl_ops *ops = &imx681_ctrl_ops; + struct v4l2_fwnode_device_properties props; + struct v4l2_ctrl *ctrl; + int ret; + + ret = v4l2_fwnode_device_parse(sensor->dev, &props); + if (ret) + return ret; + + /* v4l2_fwnode_device_properties can add two more controls. */ + ret = v4l2_ctrl_handler_init(hdl, 10); + if (ret) + return ret; + + ctrl = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ, + ARRAY_SIZE(imx681_link_freq_menu) - 1, 0, + imx681_link_freq_menu); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_PIXEL_RATE, + IMX681_PIXEL_RATE, IMX681_PIXEL_RATE, 1, + IMX681_PIXEL_RATE); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HBLANK, + IMX681_LINE_LENGTH - IMX681_WIDTH, + IMX681_LINE_LENGTH - IMX681_WIDTH, 1, + IMX681_LINE_LENGTH - IMX681_WIDTH); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + sensor->vblank_ctrl = + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_VBLANK, + IMX681_EXPOSURE_MARGIN, + IMX681_FRAME_LENGTH_MAX - IMX681_HEIGHT, 1, + IMX681_FRAME_LENGTH_DEF - IMX681_HEIGHT); + sensor->expo_ctrl = + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_EXPOSURE, 8, + IMX681_FRAME_LENGTH_DEF - + IMX681_EXPOSURE_MARGIN, 1, + IMX681_EXPOSURE_DEF); + sensor->again_ctrl = + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_ANALOGUE_GAIN, 0, + IMX681_AGAIN_MAX, 1, 0); + sensor->dgain_ctrl = + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_DIGITAL_GAIN, 0x0100, + 0x0fff, 1, IMX681_DGAIN_DEF); + + v4l2_ctrl_new_fwnode_properties(hdl, ops, &props); + + if (hdl->error) { + ret = hdl->error; + v4l2_ctrl_handler_free(hdl); + return ret; + } + + sensor->sd.ctrl_handler = hdl; + return 0; +} + +/* ------------------------------------------------------------- format */ + +static void imx681_fill_fmt(struct v4l2_mbus_framefmt *fmt) +{ + fmt->code = IMX681_MBUS_CODE; + fmt->width = IMX681_WIDTH; + fmt->height = IMX681_HEIGHT; + fmt->field = V4L2_FIELD_NONE; + fmt->colorspace = V4L2_COLORSPACE_RAW; + fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE; + fmt->xfer_func = V4L2_XFER_FUNC_NONE; +} + +static int imx681_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index) + return -EINVAL; + code->code = IMX681_MBUS_CODE; + return 0; +} + +static int imx681_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index || fse->code != IMX681_MBUS_CODE) + return -EINVAL; + fse->min_width = IMX681_WIDTH; + fse->max_width = IMX681_WIDTH; + fse->min_height = IMX681_HEIGHT; + fse->max_height = IMX681_HEIGHT; + return 0; +} + +static int imx681_set_pad_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *fmt) +{ + imx681_fill_fmt(&fmt->format); + *v4l2_subdev_state_get_format(state, 0) = fmt->format; + return 0; +} + +static int imx681_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *sel) +{ + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + sel->r = *v4l2_subdev_state_get_crop(state, sel->pad); + break; + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = IMX681_WIDTH; + sel->r.height = IMX681_HEIGHT; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int imx681_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + imx681_fill_fmt(v4l2_subdev_state_get_format(state, 0)); + *v4l2_subdev_state_get_crop(state, 0) = (struct v4l2_rect) { + .width = IMX681_WIDTH, + .height = IMX681_HEIGHT, + }; + + return 0; +} + +/* ------------------------------------------------------------ streaming */ + +static int imx681_start_streaming(struct imx681 *sensor) +{ + int ret; + + ret = pm_runtime_resume_and_get(sensor->dev); + if (ret < 0) + return ret; + + ret = cci_multi_reg_write(sensor->regmap, imx681_global_init_table, + ARRAY_SIZE(imx681_global_init_table), NULL); + if (!ret) + ret = cci_multi_reg_write(sensor->regmap, + imx681_mode_3840x2640, + ARRAY_SIZE(imx681_mode_3840x2640), + NULL); + if (!ret) + ret = cci_write(sensor->regmap, IMX681_REG_FRAME_LENGTH, + IMX681_HEIGHT + sensor->vblank_ctrl->val, + NULL); + if (ret) + goto err_rpm_put; + + ret = __v4l2_ctrl_handler_setup(&sensor->ctrl_handler); + if (ret) + goto err_rpm_put; + + ret = cci_write(sensor->regmap, IMX681_REG_MODE_SELECT, + IMX681_MODE_STREAMING, NULL); + if (ret) + goto err_rpm_put; + + dev_dbg(sensor->dev, "streaming started\n"); + return 0; + +err_rpm_put: + dev_err(sensor->dev, "failed to start streaming: %d\n", ret); + pm_runtime_put(sensor->dev); + return ret; +} + +static int imx681_stop_streaming(struct imx681 *sensor) +{ + int ret; + + ret = cci_write(sensor->regmap, IMX681_REG_MODE_SELECT, + IMX681_MODE_STANDBY, NULL); + pm_runtime_put_autosuspend(sensor->dev); + return ret; +} + +static int imx681_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct imx681 *sensor = to_imx681(sd); + + return enable ? imx681_start_streaming(sensor) : + imx681_stop_streaming(sensor); +} + +static const struct v4l2_subdev_video_ops imx681_video_ops = { + .s_stream = imx681_s_stream, +}; + +static const struct v4l2_subdev_pad_ops imx681_pad_ops = { + .enum_mbus_code = imx681_enum_mbus_code, + .enum_frame_size = imx681_enum_frame_size, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = imx681_set_pad_fmt, + .get_selection = imx681_get_selection, +}; + +static const struct v4l2_subdev_ops imx681_subdev_ops = { + .video = &imx681_video_ops, + .pad = &imx681_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops imx681_internal_ops = { + .init_state = imx681_init_state, +}; + +static int imx681_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct imx681 *sensor; + unsigned int i; + int ret; + + sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL); + if (!sensor) + return -ENOMEM; + sensor->dev = dev; + + v4l2_i2c_subdev_init(&sensor->sd, client, &imx681_subdev_ops); + sensor->sd.internal_ops = &imx681_internal_ops; + + for (i = 0; i < ARRAY_SIZE(imx681_supply_name); i++) + sensor->supplies[i].supply = imx681_supply_name[i]; + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(imx681_supply_name), + sensor->supplies); + if (ret) + return dev_err_probe(dev, ret, "failed to get regulators\n"); + + sensor->xclk = devm_clk_get(dev, NULL); + if (IS_ERR(sensor->xclk)) + return dev_err_probe(dev, PTR_ERR(sensor->xclk), + "failed to get xclk\n"); + if (clk_get_rate(sensor->xclk) != IMX681_XCLK_RATE) + return dev_err_probe(dev, -EINVAL, + "xclk must be 19.2 MHz\n"); + + sensor->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(sensor->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(sensor->reset_gpio), + "failed to get reset gpio\n"); + + ret = imx681_parse_endpoint(sensor); + if (ret) + return ret; + + sensor->regmap = devm_cci_regmap_init_i2c(client, 16); + if (IS_ERR(sensor->regmap)) + return dev_err_probe(dev, PTR_ERR(sensor->regmap), + "failed to init regmap\n"); + + ret = imx681_power_on(dev); + if (ret) + return ret; + + ret = imx681_identify(sensor); + if (ret) + goto err_power_off; + + ret = imx681_init_controls(sensor); + if (ret) + goto err_power_off; + + sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + sensor->pad.flags = MEDIA_PAD_FL_SOURCE; + sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad); + if (ret) + goto err_ctrls; + + ret = v4l2_subdev_init_finalize(&sensor->sd); + if (ret) + goto err_entity; + + pm_runtime_set_active(dev); + pm_runtime_get_noresume(dev); + pm_runtime_enable(dev); + pm_runtime_set_autosuspend_delay(dev, 2000); + pm_runtime_use_autosuspend(dev); + + ret = v4l2_async_register_subdev_sensor(&sensor->sd); + if (ret) { + dev_err(dev, "async subdev register failed: %d\n", ret); + goto err_pm; + } + + pm_runtime_put_autosuspend(dev); + return 0; + +err_pm: + pm_runtime_disable(dev); + pm_runtime_put_noidle(dev); + v4l2_subdev_cleanup(&sensor->sd); +err_entity: + media_entity_cleanup(&sensor->sd.entity); +err_ctrls: + v4l2_ctrl_handler_free(&sensor->ctrl_handler); +err_power_off: + imx681_power_off(dev); + return ret; +} + +static void imx681_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx681 *sensor = to_imx681(sd); + + v4l2_async_unregister_subdev(sd); + v4l2_subdev_cleanup(sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(&sensor->ctrl_handler); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + imx681_power_off(&client->dev); + pm_runtime_set_suspended(&client->dev); +} + +static const struct dev_pm_ops imx681_pm_ops = { + SET_RUNTIME_PM_OPS(imx681_power_off, imx681_power_on, NULL) +}; + +static const struct of_device_id imx681_dt_ids[] = { + { .compatible = "sony,imx681" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx681_dt_ids); + +static struct i2c_driver imx681_i2c_driver = { + .driver = { + .name = "imx681", + .of_match_table = imx681_dt_ids, + .pm = &imx681_pm_ops, + }, + .probe = imx681_probe, + .remove = imx681_remove, +}; + +module_i2c_driver(imx681_i2c_driver); + +MODULE_DESCRIPTION("Sony IMX681 sensor driver"); +MODULE_LICENSE("GPL"); From 3831e4e6343df465d561093783f2e050a9235c75 Mon Sep 17 00:00:00 2001 From: Leon Silcott Date: Sat, 29 Aug 2026 09:29:16 +0100 Subject: [PATCH 3/5] phy: qcom: mipi-csi2: add X1E80100 C-PHY receive mode Add an explicit MIPI C-PHY mode and teach the X1E80100 combo PHY to accept only the validated one-trio, 2.406-Gsymbol/s receiver profile. Program the bounded X1E register sequence, clear its interrupt state, and apply its shutdown sequence while the PHY clocks remain available. Keep the existing D-PHY configuration and shutdown paths unchanged. The C-PHY clock vote, interrupt, programming, and shutdown paths are selected only after the consumer requests C-PHY and the X1E capability data accepts trio 0 and the exact symbol rate. The register observations were independently transcribed from runtime MMIO activity on the Surface Pro 11. This port builds on turbineBMW's public CAMSS review series and geocausa's Windows/QTI parity analysis. Link: https://github.com/turbineBMW/surface-pro-11-linux/blob/05e5335bc72476d44390336701cf03efa5fd0165/kernel/sp11-camera-review.patch Link: https://github.com/ooaklee/linux_ms_dev_kit-sp11/commit/d915d679423e002b27f60e6b27116a03e18a96d0 Signed-off-by: Leon Silcott --- .../qualcomm/phy-qcom-mipi-csi2-3ph-dphy.c | 114 +++++++++- .../phy/qualcomm/phy-qcom-mipi-csi2-core.c | 81 ++++++- .../phy-qcom-mipi-csi2-cphy-x1e80100.h | 198 ++++++++++++++++++ drivers/phy/qualcomm/phy-qcom-mipi-csi2.h | 5 + include/linux/phy/phy.h | 1 + 5 files changed, 397 insertions(+), 2 deletions(-) create mode 100644 drivers/phy/qualcomm/phy-qcom-mipi-csi2-cphy-x1e80100.h diff --git a/drivers/phy/qualcomm/phy-qcom-mipi-csi2-3ph-dphy.c b/drivers/phy/qualcomm/phy-qcom-mipi-csi2-3ph-dphy.c index 1a99efee88cc94..b2d8e3c1d0c5fc 100644 --- a/drivers/phy/qualcomm/phy-qcom-mipi-csi2-3ph-dphy.c +++ b/drivers/phy/qualcomm/phy-qcom-mipi-csi2-3ph-dphy.c @@ -56,6 +56,17 @@ #define CSIPHY_3PH_REGS 6 #define CSIPHY_SKEW_CAL 7 +#include "phy-qcom-mipi-csi2-cphy-x1e80100.h" + +#define X1E80100_CPHY_SYMBOL_RATE 2406000000UL + +static bool is_x1e80100_cphy(struct mipi_csi2phy_device *csi2phy, + const struct mipi_csi2phy_stream_cfg *cfg); +static void x1e80100_cphy_write(struct mipi_csi2phy_device *csi2phy, + const struct x1e80100_cphy_reg *table, + unsigned int num_entries); +static void x1e80100_cphy_clear_irq(struct mipi_csi2phy_device *csi2phy); + /* 4nm 2PH v 2.1.2 2p5Gbps 4 lane DPHY mode */ static const struct mipi_csi2phy_lane_regs lane_regs_x1e80100[] = { @@ -218,10 +229,15 @@ static void phy_qcom_mipi_csi2_reset(struct mipi_csi2phy_device *csi2phy) static irqreturn_t phy_qcom_mipi_csi2_isr(int irq, void *dev) { - const struct mipi_csi2phy_device *csi2phy = dev; + struct mipi_csi2phy_device *csi2phy = dev; const struct mipi_csi2phy_device_regs *regs = csi2phy_dev_to_regs(csi2phy); int i; + if (is_x1e80100_cphy(csi2phy, &csi2phy->stream_cfg)) { + x1e80100_cphy_clear_irq(csi2phy); + return IRQ_HANDLED; + } + for (i = 0; i < 11; i++) { int c = i + 22; u8 val = readl_relaxed(csi2phy->base + @@ -339,6 +355,61 @@ static void phy_qcom_mipi_csi2_gen1_config_lanes(struct mipi_csi2phy_device *csi writel_relaxed(val, csi2phy->base + CSIPHY_3PH_LNn_MISC1(l)); } +static bool is_x1e80100_cphy(struct mipi_csi2phy_device *csi2phy, + const struct mipi_csi2phy_stream_cfg *cfg) +{ + return csi2phy->soc_cfg == &mipi_csi2_dphy_4nm_x1e && + cfg->mode == PHY_MODE_MIPI_CPHY && + cfg->cphy_trio == 0 && + cfg->num_data_lanes == 1 && + cfg->link_freq == (s64)csi2phy->soc_cfg->cphy_symbol_rate; +} + +static void x1e80100_cphy_write(struct mipi_csi2phy_device *csi2phy, + const struct x1e80100_cphy_reg *table, + unsigned int num_entries) +{ + unsigned int i; + + might_sleep(); + + for (i = 0; i < num_entries; i++) { + u32 delay_us; + + writel(table[i].reg_data, csi2phy->base + table[i].reg_addr); + + if (!table[i].delay_ns) + continue; + + delay_us = max_t(u32, table[i].delay_ns / NSEC_PER_USEC, 1); + if (delay_us <= 50) + udelay(delay_us); + else + usleep_range(delay_us, + delay_us + max(delay_us / 100, 10U)); + } +} + +static void x1e80100_cphy_clear_irq(struct mipi_csi2phy_device *csi2phy) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(x1e80100_cphy_irq_clear); i++) { + u32 delay_us; + + writel(x1e80100_cphy_irq_clear[i].reg_data, + csi2phy->base + x1e80100_cphy_irq_clear[i].reg_addr); + + if (!x1e80100_cphy_irq_clear[i].delay_ns) + continue; + + delay_us = max_t(u32, + x1e80100_cphy_irq_clear[i].delay_ns / + NSEC_PER_USEC, 1); + udelay(delay_us); + } +} + static void phy_qcom_mipi_csi2_gen2_config_lanes(struct mipi_csi2phy_device *csi2phy, u8 settle_cnt) @@ -375,6 +446,32 @@ static bool phy_qcom_mipi_csi2_is_gen2(struct mipi_csi2phy_device *csi2phy) return regs->generation == GEN2; } +static int +phy_qcom_mipi_csi2_cphy_lanes_enable(struct mipi_csi2phy_device *csi2phy, + struct mipi_csi2phy_stream_cfg *cfg) +{ + if (!is_x1e80100_cphy(csi2phy, cfg)) + return -EOPNOTSUPP; + + BUILD_BUG_ON(ARRAY_SIZE(x1e80100_cphy_reset) != 2); + BUILD_BUG_ON(ARRAY_SIZE(x1e80100_cphy_toggle) != 6); + BUILD_BUG_ON(ARRAY_SIZE(x1e80100_cphy_common) != 9); + BUILD_BUG_ON(ARRAY_SIZE(x1e80100_cphy_config) != 121); + BUILD_BUG_ON(ARRAY_SIZE(x1e80100_cphy_irq_clear) != 24); + BUILD_BUG_ON(ARRAY_SIZE(x1e80100_cphy_shutdown) != 3); + + x1e80100_cphy_write(csi2phy, x1e80100_cphy_reset, + ARRAY_SIZE(x1e80100_cphy_reset)); + x1e80100_cphy_write(csi2phy, x1e80100_cphy_toggle, + ARRAY_SIZE(x1e80100_cphy_toggle)); + x1e80100_cphy_write(csi2phy, x1e80100_cphy_common, + ARRAY_SIZE(x1e80100_cphy_common)); + x1e80100_cphy_write(csi2phy, x1e80100_cphy_config, + ARRAY_SIZE(x1e80100_cphy_config)); + + return 0; +} + static int phy_qcom_mipi_csi2_lanes_enable(struct mipi_csi2phy_device *csi2phy, struct mipi_csi2phy_stream_cfg *cfg) { @@ -384,6 +481,13 @@ static int phy_qcom_mipi_csi2_lanes_enable(struct mipi_csi2phy_device *csi2phy, u8 val; int i; + if (cfg->mode == PHY_MODE_MIPI_CPHY) { + if (!phy_qcom_mipi_csi2_is_gen2(csi2phy)) + return -EOPNOTSUPP; + + return phy_qcom_mipi_csi2_cphy_lanes_enable(csi2phy, cfg); + } + settle_cnt = phy_qcom_mipi_csi2_settle_cnt_calc(cfg->link_freq, csi2phy->timer_clk_rate); val = CSIPHY_3PH_CMN_CSI_COMMON_CTRL5_CLK_ENABLE; @@ -425,6 +529,12 @@ phy_qcom_mipi_csi2_lanes_disable(struct mipi_csi2phy_device *csi2phy, { const struct mipi_csi2phy_device_regs *regs = csi2phy_dev_to_regs(csi2phy); + if (is_x1e80100_cphy(csi2phy, cfg)) { + x1e80100_cphy_write(csi2phy, x1e80100_cphy_shutdown, + ARRAY_SIZE(x1e80100_cphy_shutdown)); + return; + } + writel_relaxed(0, csi2phy->base + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(regs->offset, 5)); @@ -470,6 +580,8 @@ const struct mipi_csi2phy_soc_cfg mipi_csi2_dphy_4nm_x1e = { .offset = 0x1000, .generation = GEN2, }, + .cphy_symbol_rate = X1E80100_CPHY_SYMBOL_RATE, + .cphy_trio_mask = BIT(0), .supply_names = (const char *[]){ "vdda-0p8", "vdda-1p2" diff --git a/drivers/phy/qualcomm/phy-qcom-mipi-csi2-core.c b/drivers/phy/qualcomm/phy-qcom-mipi-csi2-core.c index 1def2d1258d9dd..f7d75618c3cfdb 100644 --- a/drivers/phy/qualcomm/phy-qcom-mipi-csi2-core.c +++ b/drivers/phy/qualcomm/phy-qcom-mipi-csi2-core.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -46,6 +47,9 @@ phy_qcom_mipi_csi2_set_clock_rates(struct mipi_csi2phy_device *csi2phy, u64 min_rate = link_freq / 4; long round_rate; + if (csi2phy->stream_cfg.mode == PHY_MODE_MIPI_CPHY) + min_rate = 0; + phy_qcom_mipi_csi2_add_clock_margin(&min_rate); /* This clock should be enabled only not set */ @@ -91,15 +95,62 @@ phy_qcom_mipi_csi2_set_clock_rates(struct mipi_csi2phy_device *csi2phy, return 0; } +static int phy_qcom_mipi_csi2_set_mode(struct phy *phy, enum phy_mode mode, + int submode) +{ + struct mipi_csi2phy_device *csi2phy = phy_get_drvdata(phy); + + switch (mode) { + case PHY_MODE_MIPI_DPHY: + return submode ? -EINVAL : 0; + case PHY_MODE_MIPI_CPHY: + if (submode < 0 || submode >= BITS_PER_LONG) + return -EINVAL; + if (!csi2phy->soc_cfg->cphy_symbol_rate || + !(csi2phy->soc_cfg->cphy_trio_mask & BIT(submode))) + return -EOPNOTSUPP; + if (csi2phy->irq <= 0) + return csi2phy->irq ?: -EOPNOTSUPP; + + csi2phy->stream_cfg.cphy_trio = submode; + return 0; + default: + return -EINVAL; + } +} + static int phy_qcom_mipi_csi2_configure(struct phy *phy, union phy_configure_opts *opts) { struct mipi_csi2phy_device *csi2phy = phy_get_drvdata(phy); struct phy_configure_opts_mipi_dphy *dphy_cfg_opts = &opts->mipi_dphy; struct mipi_csi2phy_stream_cfg *stream_cfg = &csi2phy->stream_cfg; + enum phy_mode mode = phy_get_mode(phy); int ret; int i; + if (mode == PHY_MODE_MIPI_CPHY) { + /* + * Generic C-PHY options do not exist yet. Carry the symbol rate in + * hs_clk_rate and the number of trios in lanes for this bounded mode. + */ + if (dphy_cfg_opts->lanes != 1 || !dphy_cfg_opts->hs_clk_rate) + return -EINVAL; + if (dphy_cfg_opts->hs_clk_rate != + csi2phy->soc_cfg->cphy_symbol_rate) + return -EOPNOTSUPP; + + stream_cfg->mode = mode; + stream_cfg->combo_mode = 1; + stream_cfg->link_freq = dphy_cfg_opts->hs_clk_rate; + stream_cfg->num_data_lanes = dphy_cfg_opts->lanes; + + return 0; + } + + if (mode != PHY_MODE_MIPI_DPHY) + return -EINVAL; + ret = phy_mipi_dphy_config_validate(dphy_cfg_opts); if (ret) return ret; @@ -107,6 +158,7 @@ static int phy_qcom_mipi_csi2_configure(struct phy *phy, if (dphy_cfg_opts->lanes < 1 || dphy_cfg_opts->lanes > CSI2_MAX_DATA_LANES) return -EINVAL; + stream_cfg->mode = mode; stream_cfg->combo_mode = 0; stream_cfg->link_freq = dphy_cfg_opts->hs_clk_rate; stream_cfg->num_data_lanes = dphy_cfg_opts->lanes; @@ -156,7 +208,17 @@ static int phy_qcom_mipi_csi2_power_on(struct phy *phy) ops->hw_version_read(csi2phy); - return ops->lanes_enable(csi2phy, &csi2phy->stream_cfg); + if (csi2phy->stream_cfg.mode != PHY_MODE_MIPI_CPHY) + return ops->lanes_enable(csi2phy, &csi2phy->stream_cfg); + + enable_irq(csi2phy->irq); + ret = ops->lanes_enable(csi2phy, &csi2phy->stream_cfg); + if (!ret) + return 0; + + disable_irq(csi2phy->irq); + clk_bulk_disable_unprepare(csi2phy->soc_cfg->num_clk, + csi2phy->clks); poweroff_phy: regulator_bulk_disable(csi2phy->soc_cfg->num_supplies, @@ -168,6 +230,12 @@ static int phy_qcom_mipi_csi2_power_on(struct phy *phy) static int phy_qcom_mipi_csi2_power_off(struct phy *phy) { struct mipi_csi2phy_device *csi2phy = phy_get_drvdata(phy); + const struct mipi_csi2phy_hw_ops *ops = csi2phy->soc_cfg->ops; + + if (csi2phy->stream_cfg.mode == PHY_MODE_MIPI_CPHY) { + ops->lanes_disable(csi2phy, &csi2phy->stream_cfg); + disable_irq(csi2phy->irq); + } clk_bulk_disable_unprepare(csi2phy->soc_cfg->num_clk, csi2phy->clks); @@ -181,6 +249,7 @@ static const struct phy_ops phy_qcom_mipi_csi2_ops = { .configure = phy_qcom_mipi_csi2_configure, .power_on = phy_qcom_mipi_csi2_power_on, .power_off = phy_qcom_mipi_csi2_power_off, + .set_mode = phy_qcom_mipi_csi2_set_mode, .owner = THIS_MODULE, }; @@ -241,6 +310,16 @@ static int phy_qcom_mipi_csi2_probe(struct platform_device *pdev) if (IS_ERR(csi2phy->base)) return PTR_ERR(csi2phy->base); + csi2phy->irq = platform_get_irq_optional(pdev, 0); + if (csi2phy->irq > 0) { + ret = devm_request_irq(dev, csi2phy->irq, + csi2phy->soc_cfg->ops->isr, + IRQF_TRIGGER_RISING | IRQF_NO_AUTOEN, + dev_name(dev), csi2phy); + if (ret) + csi2phy->irq = ret; + } + generic_phy = devm_phy_create(dev, NULL, &phy_qcom_mipi_csi2_ops); if (IS_ERR(generic_phy)) { ret = PTR_ERR(generic_phy); diff --git a/drivers/phy/qualcomm/phy-qcom-mipi-csi2-cphy-x1e80100.h b/drivers/phy/qualcomm/phy-qcom-mipi-csi2-cphy-x1e80100.h new file mode 100644 index 00000000000000..370f0abcf5170d --- /dev/null +++ b/drivers/phy/qualcomm/phy-qcom-mipi-csi2-cphy-x1e80100.h @@ -0,0 +1,198 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Qualcomm X1E80100 C-PHY register observations. + * + * These address/value/delay triples were independently transcribed from + * runtime WinDbg MMIO observations on the maintainer's own Surface Pro 11 + * while its front camera was streaming. They are hardware observations, not + * copied vendor source, decompiled output, or an extracted binary payload. + * Delays retain the observed nanosecond argument passed to the common writer. + */ +struct x1e80100_cphy_reg { + u32 reg_addr; + u32 reg_data; + u32 delay_ns; +}; + +static const struct x1e80100_cphy_reg x1e80100_cphy_reset[] = { + { 0x1000, 0x01, 0x00000014 }, + { 0x1000, 0x00, 0x00000000 }, +}; + +static const struct x1e80100_cphy_reg x1e80100_cphy_toggle[] = { + { 0x025c, 0x10, 0x00000064 }, + { 0x025c, 0x00, 0x00000000 }, + { 0x065c, 0x10, 0x00000064 }, + { 0x065c, 0x00, 0x00000000 }, + { 0x0a5c, 0x10, 0x00000064 }, + { 0x0a5c, 0x00, 0x00000000 }, +}; + +static const struct x1e80100_cphy_reg x1e80100_cphy_common[] = { + { 0x1084, 0x00, 0x00000000 }, + { 0x108c, 0x00, 0x000003e8 }, + { 0x02f0, 0x00, 0x00000000 }, + { 0x06f0, 0x00, 0x00000000 }, + { 0x0af0, 0x00, 0x00000000 }, + { 0x1014, 0x02, 0x00000000 }, + { 0x101c, 0x7a, 0x00000000 }, + { 0x1018, 0x01, 0x00000000 }, + { 0x101c, 0x7a, 0x00000000 }, +}; + +static const struct x1e80100_cphy_reg x1e80100_cphy_config[] = { + { 0x0268, 0xf1, 0x00 }, + { 0x0294, 0x01, 0x00 }, + { 0x0278, 0x20, 0x00 }, + { 0x0288, 0x20, 0x00 }, + { 0x026c, 0x3d, 0x00 }, + { 0x028c, 0x30, 0x00 }, + { 0x0270, 0x00, 0x00 }, + { 0x0274, 0x03, 0x00 }, + { 0x0668, 0xf1, 0x00 }, + { 0x0694, 0x01, 0x00 }, + { 0x0678, 0x20, 0x00 }, + { 0x0688, 0x20, 0x00 }, + { 0x066c, 0x3d, 0x00 }, + { 0x068c, 0x30, 0x00 }, + { 0x0670, 0x00, 0x00 }, + { 0x0674, 0x03, 0x00 }, + { 0x0a68, 0xf1, 0x00 }, + { 0x0a94, 0x01, 0x00 }, + { 0x0a78, 0x20, 0x00 }, + { 0x0a88, 0x20, 0x00 }, + { 0x0a6c, 0x3d, 0x00 }, + { 0x0a8c, 0x30, 0x00 }, + { 0x0a70, 0x00, 0x00 }, + { 0x0a74, 0x03, 0x00989680 }, + { 0x0294, 0x09, 0x00000000 }, + { 0x02f4, 0x00, 0x00000000 }, + { 0x02f8, 0x00, 0x00000000 }, + { 0x02fc, 0x00, 0x00000000 }, + { 0x02f0, 0xef, 0x000f4240 }, + { 0x0694, 0x09, 0x00000000 }, + { 0x06f4, 0x00, 0x00000000 }, + { 0x06f8, 0x00, 0x00000000 }, + { 0x06fc, 0x00, 0x00000000 }, + { 0x06f0, 0xef, 0x00000000 }, + { 0x0a94, 0x09, 0x00000000 }, + { 0x0af4, 0x00, 0x00000000 }, + { 0x0af8, 0x00, 0x00000000 }, + { 0x0afc, 0x00, 0x00000000 }, + { 0x0af0, 0xef, 0x00989680 }, + { 0x020c, 0x22, 0x00 }, + { 0x0208, 0x00, 0x00 }, + { 0x0210, 0x00, 0x00 }, + { 0x0214, 0x00, 0x00 }, + { 0x0204, 0x00, 0x00 }, + { 0x02e4, 0x00, 0x00 }, + { 0x02e8, 0x7f, 0x00 }, + { 0x02ec, 0x7f, 0x00 }, + { 0x0218, 0x3e, 0x00 }, + { 0x021c, 0x41, 0x00 }, + { 0x0220, 0x41, 0x00 }, + { 0x0224, 0x7f, 0x00 }, + { 0x0228, 0x00, 0x00 }, + { 0x022c, 0x00, 0x00 }, + { 0x0264, 0x01, 0x00 }, + { 0x0244, 0xb2, 0x00 }, + { 0x0310, 0x35, 0x00 }, + { 0x02bc, 0xd0, 0x00 }, + { 0x0254, 0x00, 0x00 }, + { 0x0240, 0x00, 0x00 }, + { 0x0260, 0xa8, 0x00 }, + { 0x0284, 0x00, 0x00 }, + { 0x0290, 0x02, 0x00 }, + { 0x060c, 0x22, 0x00 }, + { 0x0608, 0x00, 0x00 }, + { 0x0610, 0x00, 0x00 }, + { 0x0614, 0x00, 0x00 }, + { 0x0604, 0x00, 0x00 }, + { 0x06e4, 0x00, 0x00 }, + { 0x06e8, 0x7f, 0x00 }, + { 0x06ec, 0x7f, 0x00 }, + { 0x0618, 0x3e, 0x00 }, + { 0x061c, 0x41, 0x00 }, + { 0x0620, 0x41, 0x00 }, + { 0x0624, 0x7f, 0x00 }, + { 0x0628, 0x00, 0x00 }, + { 0x062c, 0x00, 0x00 }, + { 0x0664, 0x01, 0x00 }, + { 0x0644, 0xb2, 0x00 }, + { 0x0710, 0x35, 0x00 }, + { 0x06bc, 0xd0, 0x00 }, + { 0x0654, 0x00, 0x00 }, + { 0x0640, 0x00, 0x00 }, + { 0x0660, 0xa8, 0x00 }, + { 0x0684, 0x00, 0x00 }, + { 0x0690, 0x02, 0x00 }, + { 0x0a0c, 0x22, 0x00 }, + { 0x0a08, 0x00, 0x00 }, + { 0x0a10, 0x00, 0x00 }, + { 0x0a14, 0x00, 0x00 }, + { 0x0a00, 0x00, 0x00 }, + { 0x0a04, 0x00, 0x00 }, + { 0x0ae4, 0x00, 0x00 }, + { 0x0ae8, 0x7f, 0x00 }, + { 0x0aec, 0x7f, 0x00 }, + { 0x0a18, 0x3e, 0x00 }, + { 0x0a1c, 0x41, 0x00 }, + { 0x0a20, 0x41, 0x00 }, + { 0x0a24, 0x7f, 0x00 }, + { 0x0a28, 0x00, 0x00 }, + { 0x0a2c, 0x00, 0x00 }, + { 0x0a64, 0x01, 0x00 }, + { 0x0a44, 0xb2, 0x00 }, + { 0x0b10, 0x35, 0x00 }, + { 0x0abc, 0xd0, 0x00 }, + { 0x0a54, 0x00, 0x00 }, + { 0x0a40, 0x00, 0x00 }, + { 0x0a60, 0xa8, 0x00 }, + { 0x0a84, 0x00, 0x00 }, + { 0x0a90, 0x02, 0x00 }, + { 0x102c, 0xff, 0x00 }, + { 0x1030, 0xfe, 0x00 }, + { 0x1034, 0xe2, 0x00 }, + { 0x1038, 0xdf, 0x00 }, + { 0x103c, 0x5f, 0x00 }, + { 0x1040, 0xfc, 0x00 }, + { 0x1044, 0xfb, 0x00 }, + { 0x1048, 0x8b, 0x00 }, + { 0x104c, 0x7f, 0x00 }, + { 0x1050, 0xbf, 0x00 }, + { 0x1054, 0xff, 0x00 }, + { 0x1000, 0x0e, 0x00 }, +}; + +static const struct x1e80100_cphy_reg x1e80100_cphy_irq_clear[] = { + { 0x1058, 0xff, 0x00000000 }, + { 0x105c, 0xff, 0x00000000 }, + { 0x1060, 0xff, 0x00000000 }, + { 0x1064, 0xff, 0x00000000 }, + { 0x1068, 0xff, 0x00000000 }, + { 0x106c, 0xff, 0x00000000 }, + { 0x1070, 0xff, 0x00000000 }, + { 0x1074, 0xff, 0x00000000 }, + { 0x1078, 0xff, 0x00000000 }, + { 0x107c, 0xff, 0x00000000 }, + { 0x1080, 0xff, 0x00000064 }, + { 0x1028, 0x01, 0x00000064 }, + { 0x1058, 0x00, 0x00000000 }, + { 0x105c, 0x00, 0x00000000 }, + { 0x1060, 0x00, 0x00000000 }, + { 0x1064, 0x00, 0x00000000 }, + { 0x1068, 0x00, 0x00000000 }, + { 0x106c, 0x00, 0x00000000 }, + { 0x1070, 0x00, 0x00000000 }, + { 0x1074, 0x00, 0x00000000 }, + { 0x1078, 0x00, 0x00000000 }, + { 0x107c, 0x00, 0x00000000 }, + { 0x1080, 0x00, 0x00000064 }, + { 0x1028, 0x00, 0x00000064 }, +}; + +static const struct x1e80100_cphy_reg x1e80100_cphy_shutdown[] = { + { 0x1014, 0x00, 0x00000000 }, + { 0x101c, 0x00, 0x00000000 }, + { 0x1018, 0x00, 0x00000000 }, +}; diff --git a/drivers/phy/qualcomm/phy-qcom-mipi-csi2.h b/drivers/phy/qualcomm/phy-qcom-mipi-csi2.h index adcb371c9bf6fb..3ab856bba880b8 100644 --- a/drivers/phy/qualcomm/phy-qcom-mipi-csi2.h +++ b/drivers/phy/qualcomm/phy-qcom-mipi-csi2.h @@ -23,7 +23,9 @@ struct mipi_csi2phy_lanes_cfg { }; struct mipi_csi2phy_stream_cfg { + enum phy_mode mode; u8 combo_mode; + u8 cphy_trio; s64 link_freq; u8 num_data_lanes; struct mipi_csi2phy_lanes_cfg lane_cfg; @@ -70,6 +72,8 @@ struct mipi_csi2phy_clk_freq { struct mipi_csi2phy_soc_cfg { const struct mipi_csi2phy_hw_ops *ops; const struct mipi_csi2phy_device_regs reg_info; + const unsigned long cphy_symbol_rate; + const unsigned long cphy_trio_mask; const char ** const supply_names; const unsigned int num_supplies; @@ -85,6 +89,7 @@ struct mipi_csi2phy_device { struct phy *phy; void __iomem *base; + int irq; struct clk_bulk_data *clks; struct regulator_bulk_data *supplies; diff --git a/include/linux/phy/phy.h b/include/linux/phy/phy.h index ea47975e288aea..df52c47c5bbe8a 100644 --- a/include/linux/phy/phy.h +++ b/include/linux/phy/phy.h @@ -41,6 +41,7 @@ enum phy_mode { PHY_MODE_PCIE, PHY_MODE_ETHERNET, PHY_MODE_MIPI_DPHY, + PHY_MODE_MIPI_CPHY, PHY_MODE_SATA, PHY_MODE_LVDS, PHY_MODE_DP, From f8ef631fcdd42f88bd4fa10a077db6773f507c73 Mon Sep 17 00:00:00 2001 From: Leon Silcott Date: Sat, 29 Aug 2026 09:29:16 +0100 Subject: [PATCH 4/5] media: qcom: camss: route X1E80100 C-PHY links Accept C-PHY endpoints only for X1E80100 CAMSS and only for the validated one-trio topology at the 1.203-GHz V4L2 link frequency. Propagate the bus type through CSIPHY and CSID, select the X1E receive clocks, and program the CSID C-PHY receiver fields. Leave the existing D-PHY clock, lane, and receiver programming paths unchanged. This re-lift excludes the source integration's VFE write-mode, CSID crop and drop-engine changes, status logging, and sensor-specific format handling. Non-SP11 D-PHY hardware testing remains required. The receiver routing builds on turbineBMW's public CAMSS review series and geocausa's Windows/QTI parity analysis. Link: https://github.com/turbineBMW/surface-pro-11-linux/blob/05e5335bc72476d44390336701cf03efa5fd0165/kernel/sp11-camera-review.patch Link: https://github.com/ooaklee/linux_ms_dev_kit-sp11/commit/d915d679423e002b27f60e6b27116a03e18a96d0 Signed-off-by: Leon Silcott --- .../platform/qcom/camss/camss-csid-680.c | 18 ++++++- .../media/platform/qcom/camss/camss-csid.c | 8 ++- .../media/platform/qcom/camss/camss-csid.h | 1 + .../media/platform/qcom/camss/camss-csiphy.c | 49 +++++++++++++++++++ .../media/platform/qcom/camss/camss-csiphy.h | 1 + drivers/media/platform/qcom/camss/camss.c | 26 +++++++--- 6 files changed, 93 insertions(+), 10 deletions(-) diff --git a/drivers/media/platform/qcom/camss/camss-csid-680.c b/drivers/media/platform/qcom/camss/camss-csid-680.c index 345a67c8fb947d..b510e85b3d7c6d 100644 --- a/drivers/media/platform/qcom/camss/camss-csid-680.c +++ b/drivers/media/platform/qcom/camss/camss-csid-680.c @@ -105,6 +105,7 @@ #define CSI2_RX_CFG0_PHY_TYPE_SEL 24 #define CSI2_RX_CFG0_TPG_MUX_EN BIT(27) #define CSI2_RX_CFG0_TPG_MUX_SEL GENMASK(29, 28) +#define CSI2_RX_CFG0_TPG_NUM_SEL BIT(28) #define CSID_CSI2_RX_CFG1 0x204 #define CSI2_RX_CFG1_PACKET_ECC_CORRECTION_EN BIT(0) @@ -190,8 +191,17 @@ static void __csid_configure_rx(struct csid_device *csid, struct camss *camss; camss = csid->camss; - val = (phy->lane_cnt - 1) << CSI2_RX_CFG0_NUM_ACTIVE_LANES; - val |= phy->lane_assign << CSI2_RX_CFG0_DL0_INPUT_SEL; + if (camss->res->version == CAMSS_X1E80100 && + phy->bus_type == V4L2_MBUS_CSI2_CPHY) { + u8 trio = (phy->lane_assign & 0xf) / 2; + + /* Zero active lanes encodes one C-PHY trio. */ + val = trio << CSI2_RX_CFG0_DL0_INPUT_SEL; + val |= BIT(CSI2_RX_CFG0_PHY_TYPE_SEL); + } else { + val = (phy->lane_cnt - 1) << CSI2_RX_CFG0_NUM_ACTIVE_LANES; + val |= phy->lane_assign << CSI2_RX_CFG0_DL0_INPUT_SEL; + } if (camss->tpg && csid->tpg_linked && camss->tpg[phy->csiphy_id].testgen.mode != TPG_PAYLOAD_MODE_DISABLED) { @@ -200,6 +210,10 @@ static void __csid_configure_rx(struct csid_device *csid, } else { val |= (phy->csiphy_id + CSI2_RX_CFG0_PHY_SEL_BASE_IDX) << CSI2_RX_CFG0_PHY_NUM_SEL; + + if (camss->res->version == CAMSS_X1E80100 && + phy->bus_type == V4L2_MBUS_CSI2_CPHY) + val |= CSI2_RX_CFG0_TPG_NUM_SEL; } writel(val, csid->base + CSID_CSI2_RX_CFG0); diff --git a/drivers/media/platform/qcom/camss/camss-csid.c b/drivers/media/platform/qcom/camss/camss-csid.c index 48459b46a981bc..86d0e24069a9aa 100644 --- a/drivers/media/platform/qcom/camss/camss-csid.c +++ b/drivers/media/platform/qcom/camss/camss-csid.c @@ -557,7 +557,11 @@ static int csid_set_clock_rates(struct csid_device *csid) if (!strcmp(clock->name, "csi0") || !strcmp(clock->name, "csi1") || !strcmp(clock->name, "csi2") || - !strcmp(clock->name, "csi3")) { + !strcmp(clock->name, "csi3") || + (csid->camss->res->version == CAMSS_X1E80100 && + csid->phy.bus_type == V4L2_MBUS_CSI2_CPHY && + (!strcmp(clock->name, "csid") || + !strcmp(clock->name, "csid_csiphy_rx")))) { u64 min_rate = link_freq / 4; long rate; @@ -1275,6 +1279,7 @@ static int csid_link_setup(struct media_entity *entity, if (sd->grp_id == TPG_GRP_ID) { tpg = v4l2_get_subdevdata(sd); + csid->phy.bus_type = V4L2_MBUS_CSI2_DPHY; csid->phy.lane_cnt = tpg->res->lane_cnt; csid->phy.csiphy_id = tpg->id; csid->phy.lane_assign = csid_get_lane_assign(NULL, csid->phy.lane_cnt); @@ -1288,6 +1293,7 @@ static int csid_link_setup(struct media_entity *entity, return -EPERM; csid->phy.csiphy_id = csiphy->id; + csid->phy.bus_type = csiphy->cfg.csi2->bus_type; lane_cfg = &csiphy->cfg.csi2->lane_cfg; csid->phy.lane_cnt = lane_cfg->num_data; diff --git a/drivers/media/platform/qcom/camss/camss-csid.h b/drivers/media/platform/qcom/camss/camss-csid.h index 5296b10f6bac83..b98973d7edd524 100644 --- a/drivers/media/platform/qcom/camss/camss-csid.h +++ b/drivers/media/platform/qcom/camss/camss-csid.h @@ -65,6 +65,7 @@ struct csid_testgen_config { }; struct csid_phy_config { + enum v4l2_mbus_type bus_type; u8 csiphy_id; u8 lane_cnt; u32 lane_assign; diff --git a/drivers/media/platform/qcom/camss/camss-csiphy.c b/drivers/media/platform/qcom/camss/camss-csiphy.c index 8d2e7e865f6c25..b1747aa3f4f163 100644 --- a/drivers/media/platform/qcom/camss/camss-csiphy.c +++ b/drivers/media/platform/qcom/camss/camss-csiphy.c @@ -24,6 +24,7 @@ #include "camss.h" #define MSM_CSIPHY_NAME "msm_csiphy" +#define X1E80100_CPHY_LINK_FREQ 1203000000LL static const struct csiphy_format_info formats_8x16[] = { { MEDIA_BUS_FMT_UYVY8_1X16, 8 }, @@ -312,6 +313,51 @@ static void csiphy_stream_off_legacy(struct csiphy_device *csiphy) csiphy->res->hw_ops->lanes_disable(csiphy, &csiphy->cfg); } +static int csiphy_stream_on_cphy(struct csiphy_device *csiphy) +{ + struct phy_configure_opts_mipi_dphy *cphy_cfg; + union phy_configure_opts cphy_opts = { 0 }; + struct csiphy_lanes_cfg *lane_cfg; + struct device *dev = csiphy->camss->dev; + s64 link_freq; + int ret; + + if (csiphy->camss->res->version != CAMSS_X1E80100) + return -EOPNOTSUPP; + + lane_cfg = &csiphy->cfg.csi2->lane_cfg; + if (lane_cfg->num_data != 1 || lane_cfg->data[0].pos != 0) + return -EOPNOTSUPP; + + link_freq = camss_get_link_freq(&csiphy->subdev.entity, 0, 0); + if (link_freq < 0) { + dev_err(dev, "Cannot get CSI2 transmitter's link frequency\n"); + return link_freq; + } + if (link_freq != X1E80100_CPHY_LINK_FREQ) + return -EOPNOTSUPP; + + /* Carry the symbol rate in the D-PHY options until C-PHY has its own. */ + cphy_cfg = &cphy_opts.mipi_dphy; + cphy_cfg->hs_clk_rate = link_freq * 2; + cphy_cfg->lanes = lane_cfg->num_data; + + ret = phy_set_mode_ext(csiphy->phy, PHY_MODE_MIPI_CPHY, + lane_cfg->data[0].pos); + if (ret) { + dev_err(dev, "failed to select MIPI C-PHY mode: %d\n", ret); + return ret; + } + + ret = phy_configure(csiphy->phy, &cphy_opts); + if (ret) { + dev_err(dev, "failed to configure MIPI C-PHY: %d\n", ret); + return ret; + } + + return phy_power_on(csiphy->phy); +} + /* * csiphy_stream_on - Enable streaming on CSIPHY module * @csiphy: CSIPHY device @@ -332,6 +378,9 @@ static int csiphy_stream_on(struct csiphy_device *csiphy) s64 link_freq; int ret; + if (csiphy->cfg.csi2->bus_type == V4L2_MBUS_CSI2_CPHY) + return csiphy_stream_on_cphy(csiphy); + dphy_cfg = &dphy_opts.mipi_dphy; link_freq = camss_get_link_freq(&csiphy->subdev.entity, bpp, num_lanes); diff --git a/drivers/media/platform/qcom/camss/camss-csiphy.h b/drivers/media/platform/qcom/camss/camss-csiphy.h index de1411c59177c6..7c29a71b6c7100 100644 --- a/drivers/media/platform/qcom/camss/camss-csiphy.h +++ b/drivers/media/platform/qcom/camss/camss-csiphy.h @@ -42,6 +42,7 @@ struct csiphy_lanes_cfg { }; struct csiphy_csi2_cfg { + enum v4l2_mbus_type bus_type; struct csiphy_lanes_cfg lane_cfg; }; diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c index 4c4827de2516ea..fedc47d08f4b73 100644 --- a/drivers/media/platform/qcom/camss/camss.c +++ b/drivers/media/platform/qcom/camss/camss.c @@ -4726,20 +4726,32 @@ static int camss_parse_endpoint_node(struct device *dev, if (ret) return ret; - /* - * Most SoCs support both D-PHY and C-PHY standards, but currently only - * D-PHY is supported in the driver. - */ - if (vep.bus_type != V4L2_MBUS_CSI2_DPHY) { + if (vep.bus_type == V4L2_MBUS_CSI2_CPHY && + !of_device_is_compatible(dev->of_node, "qcom,x1e80100-camss")) { + dev_err(dev, "C-PHY is not supported on this CAMSS\n"); + return -EOPNOTSUPP; + } + + if (vep.bus_type != V4L2_MBUS_CSI2_DPHY && + vep.bus_type != V4L2_MBUS_CSI2_CPHY) { dev_err(dev, "Unsupported bus type %d\n", vep.bus_type); return -EINVAL; } csd->interface.csiphy_id = vep.base.port; + csd->interface.csi2.bus_type = vep.bus_type; mipi_csi2 = &vep.bus.mipi_csi2; - lncfg->clk.pos = mipi_csi2->clock_lane; - lncfg->clk.pol = mipi_csi2->lane_polarities[0]; + if (vep.bus_type == V4L2_MBUS_CSI2_CPHY && + (mipi_csi2->num_data_lanes != 1 || mipi_csi2->data_lanes[0] != 0)) { + dev_err(dev, "Unsupported X1E80100 C-PHY topology\n"); + return -EOPNOTSUPP; + } + + if (vep.bus_type == V4L2_MBUS_CSI2_DPHY) { + lncfg->clk.pos = mipi_csi2->clock_lane; + lncfg->clk.pol = mipi_csi2->lane_polarities[0]; + } lncfg->num_data = mipi_csi2->num_data_lanes; lncfg->data = devm_kcalloc(dev, From 1760fe132029ed9e21ce6f172cc48be830b325e0 Mon Sep 17 00:00:00 2001 From: Leon Silcott Date: Sat, 29 Aug 2026 09:29:16 +0100 Subject: [PATCH 5/5] arm64: dts: qcom: x1e80100-denali-oled: enable front camera Describe the OLED Surface Pro 11 front camera as a Sony IMX681 on CCI1 with its MCLK4, reset, analog and I/O supplies, and privacy indicator. Route the one-trio C-PHY endpoint through CSIPHY2 at the validated 1.203 GHz V4L2 link frequency. Keep the graph and all board wiring in the Denali OLED DTS. This commit depends on the standalone IMX681 driver and the separately reviewed X1E80100 C-PHY receiver support; it does not change shared drivers. Link: https://github.com/turbineBMW/surface-pro-11-linux/blob/05e5335bc72476d44390336701cf03efa5fd0165/kernel/sp11-camera-review.patch Link: https://github.com/ooaklee/linux_ms_dev_kit-sp11/commit/d915d679423e002b27f60e6b27116a03e18a96d0 Signed-off-by: Leon Silcott --- .../qcom/x1e80100-microsoft-denali-oled.dts | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/x1e80100-microsoft-denali-oled.dts b/arch/arm64/boot/dts/qcom/x1e80100-microsoft-denali-oled.dts index e2cdcc5e5db099..b3f94d74261c7e 100644 --- a/arch/arm64/boot/dts/qcom/x1e80100-microsoft-denali-oled.dts +++ b/arch/arm64/boot/dts/qcom/x1e80100-microsoft-denali-oled.dts @@ -5,6 +5,10 @@ /dts-v1/; +#include +#include +#include + #include "hamoa.dtsi" #include "x1-microsoft-denali.dtsi" @@ -13,6 +17,109 @@ compatible = "microsoft,denali-oled", "microsoft,denali", "lenovo,thinkpad-t14s", "qcom,x1e80100"; + + leds { + compatible = "gpio-leds"; + + pinctrl-0 = <&cam_indicator_en>; + pinctrl-names = "default"; + + privacy_led: privacy-led { + function = LED_FUNCTION_INDICATOR; + color = ; + /* GPIO225 polarity is pending hardware validation. */ + gpios = <&tlmm 225 GPIO_ACTIVE_HIGH>; + default-state = "off"; + }; + }; +}; + +&apps_rsc { + regulators-0 { + vreg_l7b_2p8: ldo7 { + regulator-name = "vreg_l7b_2p8"; + regulator-min-microvolt = <2800000>; + regulator-max-microvolt = <2800000>; + regulator-initial-mode = ; + }; + }; + + regulators-8 { + compatible = "qcom,pm8010-rpmh-regulators"; + qcom,pmic-id = "m"; + + vdd-l3-l4-supply = <&vreg_s4c_1p8>; + + vreg_l3m_1p8: ldo3 { + regulator-name = "vreg_l3m_1p8"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-initial-mode = ; + }; + }; +}; + +&camss { + status = "okay"; + + ports { + port@2 { + csiphy2_ep: endpoint@4 { + reg = <4>; + bus-type = ; + data-lanes = <0>; + remote-endpoint = <&imx681_ep>; + }; + }; + }; +}; + +&cci1 { + status = "okay"; +}; + +&cci1_i2c1 { + clock-frequency = <400000>; + + imx681: camera@10 { + compatible = "sony,imx681"; + reg = <0x10>; + + reset-gpios = <&tlmm 237 GPIO_ACTIVE_LOW>; + pinctrl-0 = <&cam_front_default>; + pinctrl-names = "default"; + + clocks = <&camcc CAM_CC_MCLK4_CLK>; + assigned-clocks = <&camcc CAM_CC_MCLK4_CLK>; + assigned-clock-rates = <19200000>; + + avdd-supply = <&vreg_l7b_2p8>; + dovdd-supply = <&vreg_l3m_1p8>; + + leds = <&privacy_led>; + led-names = "privacy"; + + orientation = <0>; + rotation = <0>; + + port { + imx681_ep: endpoint { + bus-type = ; + data-lanes = <0>; + /* Half of the observed 2406-Msymbol/s C-PHY rate. */ + link-frequencies = /bits/ 64 <1203000000>; + remote-endpoint = <&csiphy2_ep>; + }; + }; + }; +}; + +&csiphy2 { + vdda-0p8-supply = <&vreg_l2c_0p8>; + vdda-1p2-supply = <&vreg_l1c_1p2>; + phy-type = ; + + status = "okay"; }; &gpu_zap_shader { @@ -37,3 +144,21 @@ status = "okay"; }; + +&tlmm { + cam_front_default: cam-front-default-state { + mclk4-pins { + pins = "gpio100"; + function = "cam_aon"; + drive-strength = <16>; + bias-disable; + }; + + reset-n-pins { + pins = "gpio237"; + function = "gpio"; + drive-strength = <2>; + bias-disable; + }; + }; +};