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