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 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"); 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, 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,