From d74d4e2359ec7985831192f9b5ee22ed5e55b81c Mon Sep 17 00:00:00 2001 From: Michael Tretter Date: Wed, 2 Dec 2020 14:30:37 +0100 Subject: media: allegro: move driver out of staging The stateful encoder API was finalized. Nothing is blocking the driver from being moved out of staging. Signed-off-by: Michael Tretter Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 546aa66428c9..a40345e0477c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -699,7 +699,7 @@ M: Michael Tretter R: Pengutronix Kernel Team L: linux-media@vger.kernel.org S: Maintained -F: drivers/staging/media/allegro-dvt/ +F: drivers/media/platform/allegro-dvt/ ALLWINNER A10 CSI DRIVER M: Maxime Ripard -- cgit v1.2.3 From 0f3cc7cac0e8b58a9ec1d1d76abaa9d489112c03 Mon Sep 17 00:00:00 2001 From: Michael Tretter Date: Wed, 2 Dec 2020 14:30:38 +0100 Subject: media: dt-bindings: media: allegro,al5e: Convert to YAML Convert the Allegro DVT video IP codec text binding to Yaml. Add the converted binding to the MAINTAINERS file. Signed-off-by: Michael Tretter Reviewed-by: Rob Herring Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- .../devicetree/bindings/media/allegro,al5e.yaml | 105 +++++++++++++++++++++ .../devicetree/bindings/media/allegro.txt | 43 --------- MAINTAINERS | 1 + 3 files changed, 106 insertions(+), 43 deletions(-) create mode 100644 Documentation/devicetree/bindings/media/allegro,al5e.yaml delete mode 100644 Documentation/devicetree/bindings/media/allegro.txt (limited to 'MAINTAINERS') diff --git a/Documentation/devicetree/bindings/media/allegro,al5e.yaml b/Documentation/devicetree/bindings/media/allegro,al5e.yaml new file mode 100644 index 000000000000..135bea94b587 --- /dev/null +++ b/Documentation/devicetree/bindings/media/allegro,al5e.yaml @@ -0,0 +1,105 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/allegro,al5e.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Allegro DVT Video IP Codecs Device Tree Bindings + +maintainers: + - Michael Tretter + +description: |- + Allegro DVT video IP codecs present in the Xilinx ZynqMP SoC. The IP core may + either be a H.264/H.265 encoder or H.264/H.265 decoder ip core. + + Each actual codec engine is controlled by a microcontroller (MCU). Host + software uses a provided mailbox interface to communicate with the MCU. The + MCUs share an interrupt. + +properties: + compatible: + oneOf: + - items: + - const: allegro,al5e-1.1 + - const: allegro,al5e + - items: + - const: allegro,al5d-1.1 + - const: allegro,al5d + + reg: + items: + - description: The registers + - description: The SRAM + + reg-names: + items: + - const: regs + - const: sram + + interrupts: + maxItems: 1 + + clocks: + items: + - description: Core clock + - description: MCU clock + - description: Core AXI master port clock + - description: MCU AXI master port clock + - description: AXI4-Lite slave port clock + + clock-names: + items: + - const: core_clk + - const: mcu_clk + - const: m_axi_core_aclk + - const: m_axi_mcu_aclk + - const: s_axi_lite_aclk + +required: + - compatible + - reg + - reg-names + - interrupts + - clocks + - clock-names + +additionalProperties: False + +examples: + - | + fpga { + #address-cells = <2>; + #size-cells = <2>; + + al5e: video-codec@a0009000 { + compatible = "allegro,al5e-1.1", "allegro,al5e"; + reg = <0 0xa0009000 0 0x1000>, + <0 0xa0000000 0 0x8000>; + reg-names = "regs", "sram"; + interrupts = <0 96 4>; + clocks = <&xlnx_vcu 0>, <&xlnx_vcu 1>, + <&clkc 71>, <&clkc 71>, <&clkc 71>; + clock-names = "core_clk", "mcu_clk", "m_axi_core_aclk", + "m_axi_mcu_aclk", "s_axi_lite_aclk"; + }; + }; + - | + fpga { + #address-cells = <2>; + #size-cells = <2>; + + al5d: video-codec@a0029000 { + compatible = "allegro,al5d-1.1", "allegro,al5d"; + reg = <0 0xa0029000 0 0x1000>, + <0 0xa0020000 0 0x8000>; + reg-names = "regs", "sram"; + interrupts = <0 96 4>; + clocks = <&xlnx_vcu 2>, <&xlnx_vcu 3>, + <&clkc 71>, <&clkc 71>, <&clkc 71>; + clock-names = "core_clk", "mcu_clk", "m_axi_core_aclk", + "m_axi_mcu_aclk", "s_axi_lite_aclk"; + }; + }; + +... diff --git a/Documentation/devicetree/bindings/media/allegro.txt b/Documentation/devicetree/bindings/media/allegro.txt deleted file mode 100644 index a92e2fbf26c9..000000000000 --- a/Documentation/devicetree/bindings/media/allegro.txt +++ /dev/null @@ -1,43 +0,0 @@ -Device-tree bindings for the Allegro DVT video IP codecs present in the Xilinx -ZynqMP SoC. The IP core may either be a H.264/H.265 encoder or H.264/H.265 -decoder ip core. - -Each actual codec engines is controlled by a microcontroller (MCU). Host -software uses a provided mailbox interface to communicate with the MCU. The -MCU share an interrupt. - -Required properties: - - compatible: value should be one of the following - "allegro,al5e-1.1", "allegro,al5e": encoder IP core - "allegro,al5d-1.1", "allegro,al5d": decoder IP core - - reg: base and length of the memory mapped register region and base and - length of the memory mapped sram - - reg-names: must include "regs" and "sram" - - interrupts: shared interrupt from the MCUs to the processing system - - clocks: must contain an entry for each entry in clock-names - - clock-names: must include "core_clk", "mcu_clk", "m_axi_core_aclk", - "m_axi_mcu_aclk", "s_axi_lite_aclk" - -Example: - al5e: video-codec@a0009000 { - compatible = "allegro,al5e-1.1", "allegro,al5e"; - reg = <0 0xa0009000 0 0x1000>, - <0 0xa0000000 0 0x8000>; - reg-names = "regs", "sram"; - interrupts = <0 96 4>; - clocks = <&xlnx_vcu 0>, <&xlnx_vcu 1>, - <&clkc 71>, <&clkc 71>, <&clkc 71>; - clock-names = "core_clk", "mcu_clk", "m_axi_core_aclk", - "m_axi_mcu_aclk", "s_axi_lite_aclk" - }; - al5d: video-codec@a0029000 { - compatible = "allegro,al5d-1.1", "allegro,al5d"; - reg = <0 0xa0029000 0 0x1000>, - <0 0xa0020000 0 0x8000>; - reg-names = "regs", "sram"; - interrupts = <0 96 4>; - clocks = <&xlnx_vcu 2>, <&xlnx_vcu 3>, - <&clkc 71>, <&clkc 71>, <&clkc 71>; - clock-names = "core_clk", "mcu_clk", "m_axi_core_aclk", - "m_axi_mcu_aclk", "s_axi_lite_aclk" - }; diff --git a/MAINTAINERS b/MAINTAINERS index a40345e0477c..471561d9d55f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -699,6 +699,7 @@ M: Michael Tretter R: Pengutronix Kernel Team L: linux-media@vger.kernel.org S: Maintained +F: Documentation/devicetree/bindings/media/allegro,al5e.yaml F: drivers/media/platform/allegro-dvt/ ALLWINNER A10 CSI DRIVER -- cgit v1.2.3 From 1b5071af824039cb71592d5b2a60773ee1ee94be Mon Sep 17 00:00:00 2001 From: Jacopo Mondi Date: Thu, 19 Nov 2020 17:19:27 +0100 Subject: media: dt-bindings: media: i2c: Rename ov5647.yaml Rename 'ov5647.yaml' as 'ovti,ov5647.yaml' and update the MAINTAINERS file entry accordingly. Signed-off-by: Jacopo Mondi Signed-off-by: Sakari Ailus Signed-off-by: Mauro Carvalho Chehab --- .../devicetree/bindings/media/i2c/ov5647.yaml | 88 ---------------------- .../devicetree/bindings/media/i2c/ovti,ov5647.yaml | 88 ++++++++++++++++++++++ MAINTAINERS | 2 +- 3 files changed, 89 insertions(+), 89 deletions(-) delete mode 100644 Documentation/devicetree/bindings/media/i2c/ov5647.yaml create mode 100644 Documentation/devicetree/bindings/media/i2c/ovti,ov5647.yaml (limited to 'MAINTAINERS') diff --git a/Documentation/devicetree/bindings/media/i2c/ov5647.yaml b/Documentation/devicetree/bindings/media/i2c/ov5647.yaml deleted file mode 100644 index 280c62afae13..000000000000 --- a/Documentation/devicetree/bindings/media/i2c/ov5647.yaml +++ /dev/null @@ -1,88 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/media/i2c/ov5647.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: Omnivision OV5647 raw image sensor - -maintainers: - - Dave Stevenson - - Jacopo Mondi - -description: |- - The OV5647 is a raw image sensor with MIPI CSI-2 and CCP2 image data - interfaces and CCI (I2C compatible) control bus. - -properties: - compatible: - const: ovti,ov5647 - - reg: - description: I2C device address. - maxItems: 1 - - clocks: - description: Reference to the xclk clock. - maxItems: 1 - - pwdn-gpios: - description: Reference to the GPIO connected to the pwdn pin. Active high. - maxItems: 1 - - port: - type: object - description: |- - Should contain one endpoint sub-node used to model connection to the - video receiver according to the specification defined in - Documentation/devicetree/bindings/media/video-interfaces.txt. - - properties: - endpoint: - type: object - - properties: - remote-endpoint: - description: |- - phandle to the video receiver input port. - - clock-noncontinuous: - type: boolean - description: |- - Set to true to allow MIPI CSI-2 non-continuous clock operations. - - additionalProperties: false - - additionalProperties: false - -required: - - compatible - - reg - - clocks - - port - -additionalProperties: false - -examples: - - | - #include - - i2c { - #address-cells = <1>; - #size-cells = <0>; - - ov5647: camera@36 { - compatible = "ovti,ov5647"; - reg = <0x36>; - clocks = <&camera_clk>; - pwdn-gpios = <&pioE 29 GPIO_ACTIVE_HIGH>; - - port { - camera_out: endpoint { - remote-endpoint = <&csi1_ep1>; - }; - }; - }; - }; - -... diff --git a/Documentation/devicetree/bindings/media/i2c/ovti,ov5647.yaml b/Documentation/devicetree/bindings/media/i2c/ovti,ov5647.yaml new file mode 100644 index 000000000000..280c62afae13 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/ovti,ov5647.yaml @@ -0,0 +1,88 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/ov5647.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Omnivision OV5647 raw image sensor + +maintainers: + - Dave Stevenson + - Jacopo Mondi + +description: |- + The OV5647 is a raw image sensor with MIPI CSI-2 and CCP2 image data + interfaces and CCI (I2C compatible) control bus. + +properties: + compatible: + const: ovti,ov5647 + + reg: + description: I2C device address. + maxItems: 1 + + clocks: + description: Reference to the xclk clock. + maxItems: 1 + + pwdn-gpios: + description: Reference to the GPIO connected to the pwdn pin. Active high. + maxItems: 1 + + port: + type: object + description: |- + Should contain one endpoint sub-node used to model connection to the + video receiver according to the specification defined in + Documentation/devicetree/bindings/media/video-interfaces.txt. + + properties: + endpoint: + type: object + + properties: + remote-endpoint: + description: |- + phandle to the video receiver input port. + + clock-noncontinuous: + type: boolean + description: |- + Set to true to allow MIPI CSI-2 non-continuous clock operations. + + additionalProperties: false + + additionalProperties: false + +required: + - compatible + - reg + - clocks + - port + +additionalProperties: false + +examples: + - | + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + ov5647: camera@36 { + compatible = "ovti,ov5647"; + reg = <0x36>; + clocks = <&camera_clk>; + pwdn-gpios = <&pioE 29 GPIO_ACTIVE_HIGH>; + + port { + camera_out: endpoint { + remote-endpoint = <&csi1_ep1>; + }; + }; + }; + }; + +... diff --git a/MAINTAINERS b/MAINTAINERS index 471561d9d55f..be80b8142ac8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13115,7 +13115,7 @@ M: Jacopo Mondi L: linux-media@vger.kernel.org S: Maintained T: git git://linuxtv.org/media_tree.git -F: Documentation/devicetree/bindings/media/i2c/ov5647.yaml +F: Documentation/devicetree/bindings/media/i2c/ovti,ov5647.yaml F: drivers/media/i2c/ov5647.c OMNIVISION OV5670 SENSOR DRIVER -- cgit v1.2.3 From db08f69ef8205dc4bcc2af2a24bcfc8f9da47899 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Fri, 13 Nov 2020 17:12:28 +0100 Subject: media: Documentation: ccs: Add user documentation for the CCS driver Add user documentation for the CCS driver. This includes e.g. sub-devices implemented by the driver. Signed-off-by: Sakari Ailus Signed-off-by: Mauro Carvalho Chehab --- Documentation/userspace-api/media/drivers/ccs.rst | 59 ++++++++++++++++++++++ .../userspace-api/media/drivers/index.rst | 1 + MAINTAINERS | 1 + 3 files changed, 61 insertions(+) create mode 100644 Documentation/userspace-api/media/drivers/ccs.rst (limited to 'MAINTAINERS') diff --git a/Documentation/userspace-api/media/drivers/ccs.rst b/Documentation/userspace-api/media/drivers/ccs.rst new file mode 100644 index 000000000000..00bb3a6288f5 --- /dev/null +++ b/Documentation/userspace-api/media/drivers/ccs.rst @@ -0,0 +1,59 @@ +.. SPDX-License-Identifier: GPL-2.0-only + +.. include:: + +MIPI CCS camera sensor driver +============================= + +The MIPI CCS camera sensor driver is a generic driver for `MIPI CCS +`_ compliant +camera sensors. It exposes three sub-devices representing the pixel array, +the binner and the scaler. + +As the capabilities of individual devices vary, the driver exposes +interfaces based on the capabilities that exist in hardware. + +Pixel Array sub-device +---------------------- + +The pixel array sub-device represents the camera sensor's pixel matrix, as well +as analogue crop functionality present in many compliant devices. The analogue +crop is configured using the ``V4L2_SEL_TGT_CROP`` on the source pad (0) of the +entity. The size of the pixel matrix can be obtained by getting the +``V4L2_SEL_TGT_NATIVE_SIZE`` target. + +Binner +------ + +The binner sub-device represents the binning functionality on the sensor. For +that purpose, selection target ``V4L2_SEL_TGT_COMPOSE`` is supported on the +sink pad (0). + +Additionally, if a device has no scaler or digital crop functionality, the +source pad (1) expses another digital crop selection rectangle that can only +crop at the end of the lines and frames. + +Scaler +------ + +The scaler sub-device represents the digital crop and scaling functionality of +the sensor. The V4L2 selection target ``V4L2_SEL_TGT_CROP`` is used to +configure the digital crop on the sink pad (0) when digital crop is supported. +Scaling is configured using selection target ``V4L2_SEL_TGT_COMPOSE`` on the +sink pad (0) as well. + +Additionally, if the scaler sub-device exists, its source pad (1) exposes +another digital crop selection rectangle that can only crop at the end of the +lines and frames. + +Digital and analogue crop +------------------------- + +Digital crop functionality is referred to as cropping that effectively works by +dropping some data on the floor. Analogue crop, on the other hand, means that +the cropped information is never retrieved. In case of camera sensors, the +analogue data is never read from the pixel matrix that are outside the +configured selection rectangle that designates crop. The difference has an +effect in device timing and likely also in power consumption. + +**Copyright** |copy| 2020 Intel Corporation diff --git a/Documentation/userspace-api/media/drivers/index.rst b/Documentation/userspace-api/media/drivers/index.rst index 05a82f8c0c99..1a9038f5f9fa 100644 --- a/Documentation/userspace-api/media/drivers/index.rst +++ b/Documentation/userspace-api/media/drivers/index.rst @@ -31,6 +31,7 @@ For more details see the file COPYING in the source distribution of Linux. :maxdepth: 5 :numbered: + ccs cx2341x-uapi imx-uapi max2175 diff --git a/MAINTAINERS b/MAINTAINERS index be80b8142ac8..906ef1373285 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11836,6 +11836,7 @@ L: linux-media@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/media/i2c/mipi-ccs.yaml F: Documentation/driver-api/media/drivers/ccs/ +F: Documentation/userspace-api/media/drivers/ccs.rst F: drivers/media/i2c/ccs-pll.c F: drivers/media/i2c/ccs-pll.h F: drivers/media/i2c/ccs/ -- cgit v1.2.3 From a8a2d75b0897fc359ada5fb9f8b62f985351882b Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Thu, 24 Sep 2020 00:21:32 +0200 Subject: media: v4l: uapi: ccs: Add controls for analogue gain constants Add V4L2 controls for analogue gain constants required to control analogue gain. The values are device specific and thus need to be obtained from the driver. Signed-off-by: Sakari Ailus Signed-off-by: Mauro Carvalho Chehab --- Documentation/userspace-api/media/drivers/ccs.rst | 25 +++++++++++++++++++++++ MAINTAINERS | 1 + include/uapi/linux/ccs.h | 14 +++++++++++++ 3 files changed, 40 insertions(+) create mode 100644 include/uapi/linux/ccs.h (limited to 'MAINTAINERS') diff --git a/Documentation/userspace-api/media/drivers/ccs.rst b/Documentation/userspace-api/media/drivers/ccs.rst index 00bb3a6288f5..a95d7941533d 100644 --- a/Documentation/userspace-api/media/drivers/ccs.rst +++ b/Documentation/userspace-api/media/drivers/ccs.rst @@ -56,4 +56,29 @@ analogue data is never read from the pixel matrix that are outside the configured selection rectangle that designates crop. The difference has an effect in device timing and likely also in power consumption. +Private controls +---------------- + +The MIPI CCS driver implements a number of private controls under +``V4L2_CID_USER_BASE_CCS`` to control the MIPI CCS compliant camera sensors. + +Analogue gain model +~~~~~~~~~~~~~~~~~~~ + +The CCS defines an analogue gain model where the gain can be calculated using +the following formula: + + gain = m0 * x + c0 / (m1 * x + c1) + +Either m0 or c0 will be zero. The constants that are device specific, can be +obtained from the following controls: + + V4L2_CID_CCS_ANALOGUE_GAIN_M0 + V4L2_CID_CCS_ANALOGUE_GAIN_M1 + V4L2_CID_CCS_ANALOGUE_GAIN_C0 + V4L2_CID_CCS_ANALOGUE_GAIN_C1 + +The analogue gain (``x`` in the formula) is controlled through +``V4L2_CID_ANALOGUE_GAIN`` in this case. + **Copyright** |copy| 2020 Intel Corporation diff --git a/MAINTAINERS b/MAINTAINERS index 906ef1373285..7faa7ae50716 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11840,6 +11840,7 @@ F: Documentation/userspace-api/media/drivers/ccs.rst F: drivers/media/i2c/ccs-pll.c F: drivers/media/i2c/ccs-pll.h F: drivers/media/i2c/ccs/ +F: include/uapi/linux/ccs.h F: include/uapi/linux/smiapp.h MIPS diff --git a/include/uapi/linux/ccs.h b/include/uapi/linux/ccs.h new file mode 100644 index 000000000000..57515ed7aaab --- /dev/null +++ b/include/uapi/linux/ccs.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ +/* Copyright (C) 2020 Intel Corporation */ + +#ifndef __UAPI_CCS_H__ +#define __UAPI_CCS_H__ + +#include + +#define V4L2_CID_CCS_ANALOGUE_GAIN_M0 (V4L2_CID_USER_CCS_BASE + 1) +#define V4L2_CID_CCS_ANALOGUE_GAIN_C0 (V4L2_CID_USER_CCS_BASE + 2) +#define V4L2_CID_CCS_ANALOGUE_GAIN_M1 (V4L2_CID_USER_CCS_BASE + 3) +#define V4L2_CID_CCS_ANALOGUE_GAIN_C1 (V4L2_CID_USER_CCS_BASE + 4) + +#endif -- cgit v1.2.3 From d170a5f09394ec44c2f43386d1b966e327948384 Mon Sep 17 00:00:00 2001 From: Lukas Bulwahn Date: Wed, 9 Dec 2020 08:46:58 +0100 Subject: media: MAINTAINERS: correct entry in Amlogic GE2D driver section Commit aa821b2b9269 ("media: MAINTAINERS: Add myself as maintainer of the Amlogic GE2D driver") introduced a new MAINTAINERS section, but the file entry points to the wrong location. Hence, ./scripts/get_maintainer.pl --self-test=patterns warns: warning: no file matches F: drivers/media/meson/ge2d/ Adjust the entry to the actual location of the driver. Signed-off-by: Lukas Bulwahn Acked-by: Neil Armstrong Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 7faa7ae50716..47fad204d6aa 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11553,7 +11553,7 @@ L: linux-amlogic@lists.infradead.org S: Supported T: git git://linuxtv.org/media_tree.git F: Documentation/devicetree/bindings/media/amlogic,axg-ge2d.yaml -F: drivers/media/meson/ge2d/ +F: drivers/media/platform/meson/ge2d/ MESON NAND CONTROLLER DRIVER FOR AMLOGIC SOCS M: Liang Yang -- cgit v1.2.3 From 900104c8483736f8e022ff3b084c0928e895d903 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Thu, 7 Jan 2021 14:28:33 +0100 Subject: media: ipu3-cio2: Add T: entry to MAINTAINERS Development for the ipu3-cio2 driver is taking place in media_tree, but there's no T: entry in MAINTAINERS to denote that - rectify that oversight Reviewed-by: Laurent Pinchart Reviewed-by: Andy Shevchenko Signed-off-by: Daniel Scally Signed-off-by: Sakari Ailus Signed-off-by: Mauro Carvalho Chehab --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 47fad204d6aa..e6d8674dc90a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9017,6 +9017,7 @@ M: Bingbu Cao R: Tianshu Qiu L: linux-media@vger.kernel.org S: Maintained +T: git git://linuxtv.org/media_tree.git F: Documentation/userspace-api/media/v4l/pixfmt-srggb10-ipu3.rst F: drivers/media/pci/intel/ipu3/ -- cgit v1.2.3 From 803abec64ef9d31ba068088e90fc20556ab5f605 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Thu, 7 Jan 2021 14:28:38 +0100 Subject: media: ipu3-cio2: Add cio2-bridge to ipu3-cio2 driver Currently on platforms designed for Windows, connections between CIO2 and sensors are not properly defined in DSDT. This patch extends the ipu3-cio2 driver to compensate by building software_node connections, parsing the connection properties from the sensor's SSDB buffer. [Sakari Ailus: Make cio2_bridge_init static inline to a fix compiler warning, wrapped a bunch of long lines.] Suggested-by: Jordan Hand Reviewed-by: Laurent Pinchart Reviewed-by: Andy Shevchenko Reviewed-by: Kieran Bingham Signed-off-by: Daniel Scally Signed-off-by: Sakari Ailus Signed-off-by: Mauro Carvalho Chehab --- MAINTAINERS | 1 + drivers/media/pci/intel/ipu3/Kconfig | 18 ++ drivers/media/pci/intel/ipu3/Makefile | 1 + drivers/media/pci/intel/ipu3/cio2-bridge.c | 314 ++++++++++++++++++++++++++ drivers/media/pci/intel/ipu3/cio2-bridge.h | 125 ++++++++++ drivers/media/pci/intel/ipu3/ipu3-cio2-main.c | 34 +++ drivers/media/pci/intel/ipu3/ipu3-cio2.h | 6 + 7 files changed, 499 insertions(+) create mode 100644 drivers/media/pci/intel/ipu3/cio2-bridge.c create mode 100644 drivers/media/pci/intel/ipu3/cio2-bridge.h (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index e6d8674dc90a..138aaafbb50d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9014,6 +9014,7 @@ INTEL IPU3 CSI-2 CIO2 DRIVER M: Yong Zhi M: Sakari Ailus M: Bingbu Cao +M: Dan Scally R: Tianshu Qiu L: linux-media@vger.kernel.org S: Maintained diff --git a/drivers/media/pci/intel/ipu3/Kconfig b/drivers/media/pci/intel/ipu3/Kconfig index 7a805201034b..24f4e79fe0cb 100644 --- a/drivers/media/pci/intel/ipu3/Kconfig +++ b/drivers/media/pci/intel/ipu3/Kconfig @@ -17,3 +17,21 @@ config VIDEO_IPU3_CIO2 Say Y or M here if you have a Skylake/Kaby Lake SoC with MIPI CSI-2 connected camera. The module will be called ipu3-cio2. + +config CIO2_BRIDGE + bool "IPU3 CIO2 Sensors Bridge" + depends on VIDEO_IPU3_CIO2 + help + This extension provides an API for the ipu3-cio2 driver to create + connections to cameras that are hidden in the SSDB buffer in ACPI. + It can be used to enable support for cameras in detachable / hybrid + devices that ship with Windows. + + Say Y here if your device is a detachable / hybrid laptop that comes + with Windows installed by the OEM, for example: + + - Microsoft Surface models (except Surface Pro 3) + - The Lenovo Miix line (for example the 510, 520, 710 and 720) + - Dell 7285 + + If in doubt, say N here. diff --git a/drivers/media/pci/intel/ipu3/Makefile b/drivers/media/pci/intel/ipu3/Makefile index 429d516452e4..933777e6ea8a 100644 --- a/drivers/media/pci/intel/ipu3/Makefile +++ b/drivers/media/pci/intel/ipu3/Makefile @@ -2,3 +2,4 @@ obj-$(CONFIG_VIDEO_IPU3_CIO2) += ipu3-cio2.o ipu3-cio2-y += ipu3-cio2-main.o +ipu3-cio2-$(CONFIG_CIO2_BRIDGE) += cio2-bridge.o diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.c b/drivers/media/pci/intel/ipu3/cio2-bridge.c new file mode 100644 index 000000000000..c2199042d3db --- /dev/null +++ b/drivers/media/pci/intel/ipu3/cio2-bridge.c @@ -0,0 +1,314 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Author: Dan Scally */ + +#include +#include +#include +#include +#include + +#include "cio2-bridge.h" + +/* + * Extend this array with ACPI Hardware IDs of devices known to be working + * plus the number of link-frequencies expected by their drivers, along with + * the frequency values in hertz. This is somewhat opportunistic way of adding + * support for this for now in the hopes of a better source for the information + * (possibly some encoded value in the SSDB buffer that we're unaware of) + * becoming apparent in the future. + * + * Do not add an entry for a sensor that is not actually supported. + */ +static const struct cio2_sensor_config cio2_supported_sensors[] = { + /* Omnivision OV5693 */ + CIO2_SENSOR_CONFIG("INT33BE", 0), + /* Omnivision OV2680 */ + CIO2_SENSOR_CONFIG("OVTI2680", 0), +}; + +static const struct cio2_property_names prop_names = { + .clock_frequency = "clock-frequency", + .rotation = "rotation", + .bus_type = "bus-type", + .data_lanes = "data-lanes", + .remote_endpoint = "remote-endpoint", + .link_frequencies = "link-frequencies", +}; + +static int cio2_bridge_read_acpi_buffer(struct acpi_device *adev, char *id, + void *data, u32 size) +{ + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *obj; + acpi_status status; + int ret = 0; + + status = acpi_evaluate_object(adev->handle, id, NULL, &buffer); + if (ACPI_FAILURE(status)) + return -ENODEV; + + obj = buffer.pointer; + if (!obj) { + dev_err(&adev->dev, "Couldn't locate ACPI buffer\n"); + return -ENODEV; + } + + if (obj->type != ACPI_TYPE_BUFFER) { + dev_err(&adev->dev, "Not an ACPI buffer\n"); + ret = -ENODEV; + goto out_free_buff; + } + + if (obj->buffer.length > size) { + dev_err(&adev->dev, "Given buffer is too small\n"); + ret = -EINVAL; + goto out_free_buff; + } + + memcpy(data, obj->buffer.pointer, obj->buffer.length); + +out_free_buff: + kfree(buffer.pointer); + return ret; +} + +static void cio2_bridge_create_fwnode_properties( + struct cio2_sensor *sensor, + struct cio2_bridge *bridge, + const struct cio2_sensor_config *cfg) +{ + sensor->prop_names = prop_names; + + sensor->local_ref[0].node = &sensor->swnodes[SWNODE_CIO2_ENDPOINT]; + sensor->remote_ref[0].node = &sensor->swnodes[SWNODE_SENSOR_ENDPOINT]; + + sensor->dev_properties[0] = PROPERTY_ENTRY_U32( + sensor->prop_names.clock_frequency, + sensor->ssdb.mclkspeed); + sensor->dev_properties[1] = PROPERTY_ENTRY_U8( + sensor->prop_names.rotation, + sensor->ssdb.degree); + + sensor->ep_properties[0] = PROPERTY_ENTRY_U32( + sensor->prop_names.bus_type, + V4L2_FWNODE_BUS_TYPE_CSI2_DPHY); + sensor->ep_properties[1] = PROPERTY_ENTRY_U32_ARRAY_LEN( + sensor->prop_names.data_lanes, + bridge->data_lanes, + sensor->ssdb.lanes); + sensor->ep_properties[2] = PROPERTY_ENTRY_REF_ARRAY( + sensor->prop_names.remote_endpoint, + sensor->local_ref); + + if (cfg->nr_link_freqs > 0) + sensor->ep_properties[3] = PROPERTY_ENTRY_U64_ARRAY_LEN( + sensor->prop_names.link_frequencies, + cfg->link_freqs, + cfg->nr_link_freqs); + + sensor->cio2_properties[0] = PROPERTY_ENTRY_U32_ARRAY_LEN( + sensor->prop_names.data_lanes, + bridge->data_lanes, + sensor->ssdb.lanes); + sensor->cio2_properties[1] = PROPERTY_ENTRY_REF_ARRAY( + sensor->prop_names.remote_endpoint, + sensor->remote_ref); +} + +static void cio2_bridge_init_swnode_names(struct cio2_sensor *sensor) +{ + snprintf(sensor->node_names.remote_port, + sizeof(sensor->node_names.remote_port), + SWNODE_GRAPH_PORT_NAME_FMT, sensor->ssdb.link); + snprintf(sensor->node_names.port, + sizeof(sensor->node_names.port), + SWNODE_GRAPH_PORT_NAME_FMT, 0); /* Always port 0 */ + snprintf(sensor->node_names.endpoint, + sizeof(sensor->node_names.endpoint), + SWNODE_GRAPH_ENDPOINT_NAME_FMT, 0); /* And endpoint 0 */ +} + +static void cio2_bridge_create_connection_swnodes(struct cio2_bridge *bridge, + struct cio2_sensor *sensor) +{ + struct software_node *nodes = sensor->swnodes; + + cio2_bridge_init_swnode_names(sensor); + + nodes[SWNODE_SENSOR_HID] = NODE_SENSOR(sensor->name, + sensor->dev_properties); + nodes[SWNODE_SENSOR_PORT] = NODE_PORT(sensor->node_names.port, + &nodes[SWNODE_SENSOR_HID]); + nodes[SWNODE_SENSOR_ENDPOINT] = NODE_ENDPOINT( + sensor->node_names.endpoint, + &nodes[SWNODE_SENSOR_PORT], + sensor->ep_properties); + nodes[SWNODE_CIO2_PORT] = NODE_PORT(sensor->node_names.remote_port, + &bridge->cio2_hid_node); + nodes[SWNODE_CIO2_ENDPOINT] = NODE_ENDPOINT( + sensor->node_names.endpoint, + &nodes[SWNODE_CIO2_PORT], + sensor->cio2_properties); +} + +static void cio2_bridge_unregister_sensors(struct cio2_bridge *bridge) +{ + struct cio2_sensor *sensor; + unsigned int i; + + for (i = 0; i < bridge->n_sensors; i++) { + sensor = &bridge->sensors[i]; + software_node_unregister_nodes(sensor->swnodes); + acpi_dev_put(sensor->adev); + } +} + +static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg, + struct cio2_bridge *bridge, + struct pci_dev *cio2) +{ + struct fwnode_handle *fwnode; + struct cio2_sensor *sensor; + struct acpi_device *adev; + int ret; + + for_each_acpi_dev_match(adev, cfg->hid, NULL, -1) { + if (!adev->status.enabled) + continue; + + if (bridge->n_sensors >= CIO2_NUM_PORTS) { + dev_err(&cio2->dev, "Exceeded available CIO2 ports\n"); + cio2_bridge_unregister_sensors(bridge); + ret = -EINVAL; + goto err_out; + } + + sensor = &bridge->sensors[bridge->n_sensors]; + sensor->adev = adev; + strscpy(sensor->name, cfg->hid, sizeof(sensor->name)); + + ret = cio2_bridge_read_acpi_buffer(adev, "SSDB", + &sensor->ssdb, + sizeof(sensor->ssdb)); + if (ret) + goto err_put_adev; + + if (sensor->ssdb.lanes > CIO2_MAX_LANES) { + dev_err(&adev->dev, + "Number of lanes in SSDB is invalid\n"); + ret = -EINVAL; + goto err_put_adev; + } + + cio2_bridge_create_fwnode_properties(sensor, bridge, cfg); + cio2_bridge_create_connection_swnodes(bridge, sensor); + + ret = software_node_register_nodes(sensor->swnodes); + if (ret) + goto err_put_adev; + + fwnode = software_node_fwnode(&sensor->swnodes[ + SWNODE_SENSOR_HID]); + if (!fwnode) { + ret = -ENODEV; + goto err_free_swnodes; + } + + adev->fwnode.secondary = fwnode; + + dev_info(&cio2->dev, "Found supported sensor %s\n", + acpi_dev_name(adev)); + + bridge->n_sensors++; + } + + return 0; + +err_free_swnodes: + software_node_unregister_nodes(sensor->swnodes); +err_put_adev: + acpi_dev_put(sensor->adev); +err_out: + return ret; +} + +static int cio2_bridge_connect_sensors(struct cio2_bridge *bridge, + struct pci_dev *cio2) +{ + unsigned int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(cio2_supported_sensors); i++) { + const struct cio2_sensor_config *cfg = + &cio2_supported_sensors[i]; + + ret = cio2_bridge_connect_sensor(cfg, bridge, cio2); + if (ret) + goto err_unregister_sensors; + } + + return 0; + +err_unregister_sensors: + cio2_bridge_unregister_sensors(bridge); + return ret; +} + +int cio2_bridge_init(struct pci_dev *cio2) +{ + struct device *dev = &cio2->dev; + struct fwnode_handle *fwnode; + struct cio2_bridge *bridge; + unsigned int i; + int ret; + + bridge = kzalloc(sizeof(*bridge), GFP_KERNEL); + if (!bridge) + return -ENOMEM; + + strscpy(bridge->cio2_node_name, CIO2_HID, + sizeof(bridge->cio2_node_name)); + bridge->cio2_hid_node.name = bridge->cio2_node_name; + + ret = software_node_register(&bridge->cio2_hid_node); + if (ret < 0) { + dev_err(dev, "Failed to register the CIO2 HID node\n"); + goto err_free_bridge; + } + + /* + * Map the lane arrangement, which is fixed for the IPU3 (meaning we + * only need one, rather than one per sensor). We include it as a + * member of the struct cio2_bridge rather than a global variable so + * that it survives if the module is unloaded along with the rest of + * the struct. + */ + for (i = 0; i < CIO2_MAX_LANES; i++) + bridge->data_lanes[i] = i + 1; + + ret = cio2_bridge_connect_sensors(bridge, cio2); + if (ret || bridge->n_sensors == 0) + goto err_unregister_cio2; + + dev_info(dev, "Connected %d cameras\n", bridge->n_sensors); + + fwnode = software_node_fwnode(&bridge->cio2_hid_node); + if (!fwnode) { + dev_err(dev, "Error getting fwnode from cio2 software_node\n"); + ret = -ENODEV; + goto err_unregister_sensors; + } + + set_secondary_fwnode(dev, fwnode); + + return 0; + +err_unregister_sensors: + cio2_bridge_unregister_sensors(bridge); +err_unregister_cio2: + software_node_unregister(&bridge->cio2_hid_node); +err_free_bridge: + kfree(bridge); + + return ret; +} diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.h b/drivers/media/pci/intel/ipu3/cio2-bridge.h new file mode 100644 index 000000000000..dd0ffcafa489 --- /dev/null +++ b/drivers/media/pci/intel/ipu3/cio2-bridge.h @@ -0,0 +1,125 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Author: Dan Scally */ +#ifndef __CIO2_BRIDGE_H +#define __CIO2_BRIDGE_H + +#include +#include + +#include "ipu3-cio2.h" + +#define CIO2_HID "INT343E" +#define CIO2_MAX_LANES 4 +#define MAX_NUM_LINK_FREQS 3 + +#define CIO2_SENSOR_CONFIG(_HID, _NR, ...) \ + (const struct cio2_sensor_config) { \ + .hid = _HID, \ + .nr_link_freqs = _NR, \ + .link_freqs = { __VA_ARGS__ } \ + } + +#define NODE_SENSOR(_HID, _PROPS) \ + (const struct software_node) { \ + .name = _HID, \ + .properties = _PROPS, \ + } + +#define NODE_PORT(_PORT, _SENSOR_NODE) \ + (const struct software_node) { \ + .name = _PORT, \ + .parent = _SENSOR_NODE, \ + } + +#define NODE_ENDPOINT(_EP, _PORT, _PROPS) \ + (const struct software_node) { \ + .name = _EP, \ + .parent = _PORT, \ + .properties = _PROPS, \ + } + +enum cio2_sensor_swnodes { + SWNODE_SENSOR_HID, + SWNODE_SENSOR_PORT, + SWNODE_SENSOR_ENDPOINT, + SWNODE_CIO2_PORT, + SWNODE_CIO2_ENDPOINT, + SWNODE_COUNT +}; + +/* Data representation as it is in ACPI SSDB buffer */ +struct cio2_sensor_ssdb { + u8 version; + u8 sku; + u8 guid_csi2[16]; + u8 devfunction; + u8 bus; + u32 dphylinkenfuses; + u32 clockdiv; + u8 link; + u8 lanes; + u32 csiparams[10]; + u32 maxlanespeed; + u8 sensorcalibfileidx; + u8 sensorcalibfileidxInMBZ[3]; + u8 romtype; + u8 vcmtype; + u8 platforminfo; + u8 platformsubinfo; + u8 flash; + u8 privacyled; + u8 degree; + u8 mipilinkdefined; + u32 mclkspeed; + u8 controllogicid; + u8 reserved1[3]; + u8 mclkport; + u8 reserved2[13]; +} __packed; + +struct cio2_property_names { + char clock_frequency[16]; + char rotation[9]; + char bus_type[9]; + char data_lanes[11]; + char remote_endpoint[16]; + char link_frequencies[17]; +}; + +struct cio2_node_names { + char port[7]; + char endpoint[11]; + char remote_port[7]; +}; + +struct cio2_sensor_config { + const char *hid; + const u8 nr_link_freqs; + const u64 link_freqs[MAX_NUM_LINK_FREQS]; +}; + +struct cio2_sensor { + char name[ACPI_ID_LEN]; + struct acpi_device *adev; + + struct software_node swnodes[6]; + struct cio2_node_names node_names; + + struct cio2_sensor_ssdb ssdb; + struct cio2_property_names prop_names; + struct property_entry ep_properties[5]; + struct property_entry dev_properties[3]; + struct property_entry cio2_properties[3]; + struct software_node_ref_args local_ref[1]; + struct software_node_ref_args remote_ref[1]; +}; + +struct cio2_bridge { + char cio2_node_name[ACPI_ID_LEN]; + struct software_node cio2_hid_node; + u32 data_lanes[4]; + unsigned int n_sensors; + struct cio2_sensor sensors[CIO2_NUM_PORTS]; +}; + +#endif diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c index e8ea69d30bfd..8ab682b38f17 100644 --- a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c +++ b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c @@ -1702,11 +1702,28 @@ static void cio2_queues_exit(struct cio2_device *cio2) cio2_queue_exit(cio2, &cio2->queue[i]); } +static int cio2_check_fwnode_graph(struct fwnode_handle *fwnode) +{ + struct fwnode_handle *endpoint; + + if (IS_ERR_OR_NULL(fwnode)) + return -EINVAL; + + endpoint = fwnode_graph_get_next_endpoint(fwnode, NULL); + if (endpoint) { + fwnode_handle_put(endpoint); + return 0; + } + + return cio2_check_fwnode_graph(fwnode->secondary); +} + /**************** PCI interface ****************/ static int cio2_pci_probe(struct pci_dev *pci_dev, const struct pci_device_id *id) { + struct fwnode_handle *fwnode = dev_fwnode(&pci_dev->dev); struct cio2_device *cio2; int r; @@ -1715,6 +1732,23 @@ static int cio2_pci_probe(struct pci_dev *pci_dev, return -ENOMEM; cio2->pci_dev = pci_dev; + /* + * On some platforms no connections to sensors are defined in firmware, + * if the device has no endpoints then we can try to build those as + * software_nodes parsed from SSDB. + */ + r = cio2_check_fwnode_graph(fwnode); + if (r) { + if (fwnode && !IS_ERR_OR_NULL(fwnode->secondary)) { + dev_err(&pci_dev->dev, "fwnode graph has no endpoints connected\n"); + return -EINVAL; + } + + r = cio2_bridge_init(pci_dev); + if (r) + return r; + } + r = pcim_enable_device(pci_dev); if (r) { dev_err(&pci_dev->dev, "failed to enable device (%d)\n", r); diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2.h b/drivers/media/pci/intel/ipu3/ipu3-cio2.h index 62187ab5ae43..3806d7f04d69 100644 --- a/drivers/media/pci/intel/ipu3/ipu3-cio2.h +++ b/drivers/media/pci/intel/ipu3/ipu3-cio2.h @@ -455,4 +455,10 @@ static inline struct cio2_queue *vb2q_to_cio2_queue(struct vb2_queue *vq) return container_of(vq, struct cio2_queue, vbq); } +#if IS_ENABLED(CONFIG_CIO2_BRIDGE) +int cio2_bridge_init(struct pci_dev *cio2); +#else +static inline int cio2_bridge_init(struct pci_dev *cio2) { return 0; } +#endif + #endif -- cgit v1.2.3 From a59f853b3b4bce1471ad164357c3f51bdd0e6ba9 Mon Sep 17 00:00:00 2001 From: Jacopo Mondi Date: Thu, 14 Jan 2021 18:04:25 +0100 Subject: media: i2c: Add driver for RDACM21 camera module The RDACM21 is a GMSL camera supporting 1280x1080 resolution images developed by IMI based on an Omnivision OV10640 sensor, an Omnivision OV490 ISP and a Maxim MAX9271 GMSL serializer. The driver uses the max9271 library module, to maximize code reuse with other camera module drivers using the same serializer, such as rdacm20. Reviewed-by: Laurent Pinchart Signed-off-by: Jacopo Mondi Signed-off-by: Sakari Ailus Signed-off-by: Mauro Carvalho Chehab --- MAINTAINERS | 12 + drivers/media/i2c/Kconfig | 13 + drivers/media/i2c/Makefile | 2 + drivers/media/i2c/rdacm21.c | 623 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 650 insertions(+) create mode 100644 drivers/media/i2c/rdacm21.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 45c70f101e61..af8f06213f52 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14971,6 +14971,18 @@ F: drivers/media/i2c/max9271.c F: drivers/media/i2c/max9271.h F: drivers/media/i2c/rdacm20.c +RDACM21 Camera Sensor +M: Jacopo Mondi +M: Kieran Bingham +M: Laurent Pinchart +M: Niklas Söderlund +L: linux-media@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/media/i2c/rdacm2x-gmsl.yaml +F: drivers/media/i2c/max9271.c +F: drivers/media/i2c/max9271.h +F: drivers/media/i2c/rdacm21.c + RDC R-321X SoC M: Florian Fainelli S: Maintained diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index bb1b5a340431..ad92f4789b83 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -1239,6 +1239,19 @@ config VIDEO_RDACM20 This camera should be used in conjunction with a GMSL deserialiser such as the MAX9286. +config VIDEO_RDACM21 + tristate "IMI RDACM21 camera support" + depends on I2C + select V4L2_FWNODE + select VIDEO_V4L2_SUBDEV_API + select MEDIA_CONTROLLER + help + This driver supports the IMI RDACM21 GMSL camera, used in + ADAS systems. + + This camera should be used in conjunction with a GMSL + deserialiser such as the MAX9286. + config VIDEO_RJ54N1 tristate "Sharp RJ54N1CB0C sensor support" depends on I2C && VIDEO_V4L2 diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 5b1dcfa3ce76..f5cd92bc285d 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -126,6 +126,8 @@ obj-$(CONFIG_VIDEO_IMX355) += imx355.o obj-$(CONFIG_VIDEO_MAX9286) += max9286.o rdacm20-camera_module-objs := rdacm20.o max9271.o obj-$(CONFIG_VIDEO_RDACM20) += rdacm20-camera_module.o +rdacm21-camera_module-objs := rdacm21.o max9271.o +obj-$(CONFIG_VIDEO_RDACM21) += rdacm21-camera_module.o obj-$(CONFIG_VIDEO_ST_MIPID02) += st-mipid02.o obj-$(CONFIG_SDR_MAX2175) += max2175.o diff --git a/drivers/media/i2c/rdacm21.c b/drivers/media/i2c/rdacm21.c new file mode 100644 index 000000000000..dcc21515e5a4 --- /dev/null +++ b/drivers/media/i2c/rdacm21.c @@ -0,0 +1,623 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * IMI RDACM21 GMSL Camera Driver + * + * Copyright (C) 2017-2020 Jacopo Mondi + * Copyright (C) 2017-2019 Kieran Bingham + * Copyright (C) 2017-2019 Laurent Pinchart + * Copyright (C) 2017-2019 Niklas Söderlund + * Copyright (C) 2016 Renesas Electronics Corporation + * Copyright (C) 2015 Cogent Embedded, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "max9271.h" + +#define MAX9271_RESET_CYCLES 10 + +#define OV490_I2C_ADDRESS 0x24 + +#define OV490_PAGE_HIGH_REG 0xfffd +#define OV490_PAGE_LOW_REG 0xfffe + +/* + * The SCCB slave handling is undocumented; the registers naming scheme is + * totally arbitrary. + */ +#define OV490_SCCB_SLAVE_WRITE 0x00 +#define OV490_SCCB_SLAVE_READ 0x01 +#define OV490_SCCB_SLAVE0_DIR 0x80195000 +#define OV490_SCCB_SLAVE0_ADDR_HIGH 0x80195001 +#define OV490_SCCB_SLAVE0_ADDR_LOW 0x80195002 + +#define OV490_DVP_CTRL3 0x80286009 + +#define OV490_ODS_CTRL_FRAME_OUTPUT_EN 0x0c +#define OV490_ODS_CTRL 0x8029d000 + +#define OV490_HOST_CMD 0x808000c0 +#define OV490_HOST_CMD_TRIGGER 0xc1 + +#define OV490_ID_VAL 0x0490 +#define OV490_ID(_p, _v) ((((_p) & 0xff) << 8) | ((_v) & 0xff)) +#define OV490_PID 0x8080300a +#define OV490_VER 0x8080300b +#define OV490_PID_TIMEOUT 20 +#define OV490_OUTPUT_EN_TIMEOUT 300 + +#define OV490_GPIO0 BIT(0) +#define OV490_SPWDN0 BIT(0) +#define OV490_GPIO_SEL0 0x80800050 +#define OV490_GPIO_SEL1 0x80800051 +#define OV490_GPIO_DIRECTION0 0x80800054 +#define OV490_GPIO_DIRECTION1 0x80800055 +#define OV490_GPIO_OUTPUT_VALUE0 0x80800058 +#define OV490_GPIO_OUTPUT_VALUE1 0x80800059 + +#define OV490_ISP_HSIZE_LOW 0x80820060 +#define OV490_ISP_HSIZE_HIGH 0x80820061 +#define OV490_ISP_VSIZE_LOW 0x80820062 +#define OV490_ISP_VSIZE_HIGH 0x80820063 + +#define OV10640_ID_HIGH 0xa6 +#define OV10640_CHIP_ID 0x300a +#define OV10640_PIXEL_RATE 55000000 + +struct rdacm21_device { + struct device *dev; + struct max9271_device serializer; + struct i2c_client *isp; + struct v4l2_subdev sd; + struct media_pad pad; + struct v4l2_mbus_framefmt fmt; + struct v4l2_ctrl_handler ctrls; + u32 addrs[2]; + u16 last_page; +}; + +static inline struct rdacm21_device *sd_to_rdacm21(struct v4l2_subdev *sd) +{ + return container_of(sd, struct rdacm21_device, sd); +} + +static const struct ov490_reg { + u16 reg; + u8 val; +} ov490_regs_wizard[] = { + {0xfffd, 0x80}, + {0xfffe, 0x82}, + {0x0071, 0x11}, + {0x0075, 0x11}, + {0xfffe, 0x29}, + {0x6010, 0x01}, + /* + * OV490 EMB line disable in YUV and RAW data, + * NOTE: EMB line is still used in ISP and sensor + */ + {0xe000, 0x14}, + {0xfffe, 0x28}, + {0x6000, 0x04}, + {0x6004, 0x00}, + /* + * PCLK polarity - useless due to silicon bug. + * Use 0x808000bb register instead. + */ + {0x6008, 0x00}, + {0xfffe, 0x80}, + {0x0091, 0x00}, + /* bit[3]=0 - PCLK polarity workaround. */ + {0x00bb, 0x1d}, + /* Ov490 FSIN: app_fsin_from_fsync */ + {0xfffe, 0x85}, + {0x0008, 0x00}, + {0x0009, 0x01}, + /* FSIN0 source. */ + {0x000A, 0x05}, + {0x000B, 0x00}, + /* FSIN0 delay. */ + {0x0030, 0x02}, + {0x0031, 0x00}, + {0x0032, 0x00}, + {0x0033, 0x00}, + /* FSIN1 delay. */ + {0x0038, 0x02}, + {0x0039, 0x00}, + {0x003A, 0x00}, + {0x003B, 0x00}, + /* FSIN0 length. */ + {0x0070, 0x2C}, + {0x0071, 0x01}, + {0x0072, 0x00}, + {0x0073, 0x00}, + /* FSIN1 length. */ + {0x0074, 0x64}, + {0x0075, 0x00}, + {0x0076, 0x00}, + {0x0077, 0x00}, + {0x0000, 0x14}, + {0x0001, 0x00}, + {0x0002, 0x00}, + {0x0003, 0x00}, + /* + * Load fsin0,load fsin1,load other, + * It will be cleared automatically. + */ + {0x0004, 0x32}, + {0x0005, 0x00}, + {0x0006, 0x00}, + {0x0007, 0x00}, + {0xfffe, 0x80}, + /* Sensor FSIN. */ + {0x0081, 0x00}, + /* ov10640 FSIN enable */ + {0xfffe, 0x19}, + {0x5000, 0x00}, + {0x5001, 0x30}, + {0x5002, 0x8c}, + {0x5003, 0xb2}, + {0xfffe, 0x80}, + {0x00c0, 0xc1}, + /* ov10640 HFLIP=1 by default */ + {0xfffe, 0x19}, + {0x5000, 0x01}, + {0x5001, 0x00}, + {0xfffe, 0x80}, + {0x00c0, 0xdc}, +}; + +static int ov490_read(struct rdacm21_device *dev, u16 reg, u8 *val) +{ + u8 buf[2] = { reg >> 8, reg }; + int ret; + + ret = i2c_master_send(dev->isp, buf, 2); + if (ret == 2) + ret = i2c_master_recv(dev->isp, val, 1); + + if (ret < 0) { + dev_dbg(dev->dev, "%s: register 0x%04x read failed (%d)\n", + __func__, reg, ret); + return ret; + } + + return 0; +} + +static int ov490_write(struct rdacm21_device *dev, u16 reg, u8 val) +{ + u8 buf[3] = { reg >> 8, reg, val }; + int ret; + + ret = i2c_master_send(dev->isp, buf, 3); + if (ret < 0) { + dev_err(dev->dev, "%s: register 0x%04x write failed (%d)\n", + __func__, reg, ret); + return ret; + } + + return 0; +} + +static int ov490_set_page(struct rdacm21_device *dev, u16 page) +{ + u8 page_high = page >> 8; + u8 page_low = page; + int ret; + + if (page == dev->last_page) + return 0; + + if (page_high != (dev->last_page >> 8)) { + ret = ov490_write(dev, OV490_PAGE_HIGH_REG, page_high); + if (ret) + return ret; + } + + if (page_low != (u8)dev->last_page) { + ret = ov490_write(dev, OV490_PAGE_LOW_REG, page_low); + if (ret) + return ret; + } + + dev->last_page = page; + usleep_range(100, 150); + + return 0; +} + +static int ov490_read_reg(struct rdacm21_device *dev, u32 reg, u8 *val) +{ + int ret; + + ret = ov490_set_page(dev, reg >> 16); + if (ret) + return ret; + + ret = ov490_read(dev, (u16)reg, val); + if (ret) + return ret; + + dev_dbg(dev->dev, "%s: 0x%08x = 0x%02x\n", __func__, reg, *val); + + return 0; +} + +static int ov490_write_reg(struct rdacm21_device *dev, u32 reg, u8 val) +{ + int ret; + + ret = ov490_set_page(dev, reg >> 16); + if (ret) + return ret; + + ret = ov490_write(dev, (u16)reg, val); + if (ret) + return ret; + + dev_dbg(dev->dev, "%s: 0x%08x = 0x%02x\n", __func__, reg, val); + + return 0; +} + +static int rdacm21_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct rdacm21_device *dev = sd_to_rdacm21(sd); + + /* + * Enable serial link now that the ISP provides a valid pixel clock + * to start serializing video data on the GMSL link. + */ + return max9271_set_serial_link(&dev->serializer, enable); +} + +static int rdacm21_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad || code->index > 0) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_YUYV8_1X16; + + return 0; +} + +static int rdacm21_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct v4l2_mbus_framefmt *mf = &format->format; + struct rdacm21_device *dev = sd_to_rdacm21(sd); + + if (format->pad) + return -EINVAL; + + mf->width = dev->fmt.width; + mf->height = dev->fmt.height; + mf->code = MEDIA_BUS_FMT_YUYV8_1X16; + mf->colorspace = V4L2_COLORSPACE_SRGB; + mf->field = V4L2_FIELD_NONE; + mf->ycbcr_enc = V4L2_YCBCR_ENC_601; + mf->quantization = V4L2_QUANTIZATION_FULL_RANGE; + mf->xfer_func = V4L2_XFER_FUNC_NONE; + + return 0; +} + +static const struct v4l2_subdev_video_ops rdacm21_video_ops = { + .s_stream = rdacm21_s_stream, +}; + +static const struct v4l2_subdev_pad_ops rdacm21_subdev_pad_ops = { + .enum_mbus_code = rdacm21_enum_mbus_code, + .get_fmt = rdacm21_get_fmt, + .set_fmt = rdacm21_get_fmt, +}; + +static const struct v4l2_subdev_ops rdacm21_subdev_ops = { + .video = &rdacm21_video_ops, + .pad = &rdacm21_subdev_pad_ops, +}; + +static int ov10640_initialize(struct rdacm21_device *dev) +{ + u8 val; + + /* Power-up OV10640 by setting RESETB and PWDNB pins high. */ + ov490_write_reg(dev, OV490_GPIO_SEL0, OV490_GPIO0); + ov490_write_reg(dev, OV490_GPIO_SEL1, OV490_SPWDN0); + ov490_write_reg(dev, OV490_GPIO_DIRECTION0, OV490_GPIO0); + ov490_write_reg(dev, OV490_GPIO_DIRECTION1, OV490_SPWDN0); + ov490_write_reg(dev, OV490_GPIO_OUTPUT_VALUE0, OV490_GPIO0); + ov490_write_reg(dev, OV490_GPIO_OUTPUT_VALUE0, OV490_SPWDN0); + usleep_range(3000, 5000); + + /* Read OV10640 ID to test communications. */ + ov490_write_reg(dev, OV490_SCCB_SLAVE0_DIR, OV490_SCCB_SLAVE_READ); + ov490_write_reg(dev, OV490_SCCB_SLAVE0_ADDR_HIGH, OV10640_CHIP_ID >> 8); + ov490_write_reg(dev, OV490_SCCB_SLAVE0_ADDR_LOW, (u8)OV10640_CHIP_ID); + + /* Trigger SCCB slave transaction and give it some time to complete. */ + ov490_write_reg(dev, OV490_HOST_CMD, OV490_HOST_CMD_TRIGGER); + usleep_range(1000, 1500); + + ov490_read_reg(dev, OV490_SCCB_SLAVE0_DIR, &val); + if (val != OV10640_ID_HIGH) { + dev_err(dev->dev, "OV10640 ID mismatch: (0x%02x)\n", val); + return -ENODEV; + } + + dev_dbg(dev->dev, "OV10640 ID = 0x%2x\n", val); + + return 0; +} + +static int ov490_initialize(struct rdacm21_device *dev) +{ + u8 pid, ver, val; + unsigned int i; + int ret; + + /* + * Read OV490 Id to test communications. Give it up to 40msec to + * exit from reset. + */ + for (i = 0; i < OV490_PID_TIMEOUT; ++i) { + ret = ov490_read_reg(dev, OV490_PID, &pid); + if (ret == 0) + break; + usleep_range(1000, 2000); + } + if (i == OV490_PID_TIMEOUT) { + dev_err(dev->dev, "OV490 PID read failed (%d)\n", ret); + return ret; + } + + ret = ov490_read_reg(dev, OV490_VER, &ver); + if (ret < 0) + return ret; + + if (OV490_ID(pid, ver) != OV490_ID_VAL) { + dev_err(dev->dev, "OV490 ID mismatch (0x%04x)\n", + OV490_ID(pid, ver)); + return -ENODEV; + } + + /* Wait for firmware boot by reading streamon status. */ + for (i = 0; i < OV490_OUTPUT_EN_TIMEOUT; ++i) { + ov490_read_reg(dev, OV490_ODS_CTRL, &val); + if (val == OV490_ODS_CTRL_FRAME_OUTPUT_EN) + break; + usleep_range(1000, 2000); + } + if (i == OV490_OUTPUT_EN_TIMEOUT) { + dev_err(dev->dev, "Timeout waiting for firmware boot\n"); + return -ENODEV; + } + + ret = ov10640_initialize(dev); + if (ret) + return ret; + + /* Program OV490 with register-value table. */ + for (i = 0; i < ARRAY_SIZE(ov490_regs_wizard); ++i) { + ret = ov490_write(dev, ov490_regs_wizard[i].reg, + ov490_regs_wizard[i].val); + if (ret < 0) { + dev_err(dev->dev, + "%s: register %u (0x%04x) write failed (%d)\n", + __func__, i, ov490_regs_wizard[i].reg, ret); + + return -EIO; + } + + usleep_range(100, 150); + } + + /* + * The ISP is programmed with the content of a serial flash memory. + * Read the firmware configuration to reflect it through the V4L2 APIs. + */ + ov490_read_reg(dev, OV490_ISP_HSIZE_HIGH, &val); + dev->fmt.width = (val & 0xf) << 8; + ov490_read_reg(dev, OV490_ISP_HSIZE_LOW, &val); + dev->fmt.width |= (val & 0xff); + + ov490_read_reg(dev, OV490_ISP_VSIZE_HIGH, &val); + dev->fmt.height = (val & 0xf) << 8; + ov490_read_reg(dev, OV490_ISP_VSIZE_LOW, &val); + dev->fmt.height |= val & 0xff; + + /* Set bus width to 12 bits with [0:11] ordering. */ + ov490_write_reg(dev, OV490_DVP_CTRL3, 0x10); + + dev_info(dev->dev, "Identified RDACM21 camera module\n"); + + return 0; +} + +static int rdacm21_initialize(struct rdacm21_device *dev) +{ + int ret; + + /* Verify communication with the MAX9271: ping to wakeup. */ + dev->serializer.client->addr = MAX9271_DEFAULT_ADDR; + i2c_smbus_read_byte(dev->serializer.client); + usleep_range(3000, 5000); + + /* Enable reverse channel and disable the serial link. */ + ret = max9271_set_serial_link(&dev->serializer, false); + if (ret) + return ret; + + /* Configure I2C bus at 105Kbps speed and configure GMSL. */ + ret = max9271_configure_i2c(&dev->serializer, + MAX9271_I2CSLVSH_469NS_234NS | + MAX9271_I2CSLVTO_1024US | + MAX9271_I2CMSTBT_105KBPS); + if (ret) + return ret; + + ret = max9271_verify_id(&dev->serializer); + if (ret) + return ret; + + /* Enable GPIO1 and hold OV490 in reset during max9271 configuration. */ + ret = max9271_enable_gpios(&dev->serializer, MAX9271_GPIO1OUT); + if (ret) + return ret; + + ret = max9271_clear_gpios(&dev->serializer, MAX9271_GPIO1OUT); + if (ret) + return ret; + + ret = max9271_configure_gmsl_link(&dev->serializer); + if (ret) + return ret; + + ret = max9271_set_address(&dev->serializer, dev->addrs[0]); + if (ret) + return ret; + dev->serializer.client->addr = dev->addrs[0]; + + ret = max9271_set_translation(&dev->serializer, dev->addrs[1], + OV490_I2C_ADDRESS); + if (ret) + return ret; + dev->isp->addr = dev->addrs[1]; + + /* Release OV490 from reset and initialize it. */ + ret = max9271_set_gpios(&dev->serializer, MAX9271_GPIO1OUT); + if (ret) + return ret; + usleep_range(3000, 5000); + + ret = ov490_initialize(dev); + if (ret) + return ret; + + /* + * Set reverse channel high threshold to increase noise immunity. + * + * This should be compensated by increasing the reverse channel + * amplitude on the remote deserializer side. + */ + return max9271_set_high_threshold(&dev->serializer, true); +} + +static int rdacm21_probe(struct i2c_client *client) +{ + struct rdacm21_device *dev; + struct fwnode_handle *ep; + int ret; + + dev = devm_kzalloc(&client->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + dev->dev = &client->dev; + dev->serializer.client = client; + + ret = of_property_read_u32_array(client->dev.of_node, "reg", + dev->addrs, 2); + if (ret < 0) { + dev_err(dev->dev, "Invalid DT reg property: %d\n", ret); + return -EINVAL; + } + + /* Create the dummy I2C client for the sensor. */ + dev->isp = i2c_new_dummy_device(client->adapter, OV490_I2C_ADDRESS); + if (IS_ERR(dev->isp)) + return PTR_ERR(dev->isp); + + ret = rdacm21_initialize(dev); + if (ret < 0) + goto error; + + /* Initialize and register the subdevice. */ + v4l2_i2c_subdev_init(&dev->sd, client, &rdacm21_subdev_ops); + dev->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + v4l2_ctrl_handler_init(&dev->ctrls, 1); + v4l2_ctrl_new_std(&dev->ctrls, NULL, V4L2_CID_PIXEL_RATE, + OV10640_PIXEL_RATE, OV10640_PIXEL_RATE, 1, + OV10640_PIXEL_RATE); + dev->sd.ctrl_handler = &dev->ctrls; + + ret = dev->ctrls.error; + if (ret) + goto error_free_ctrls; + + dev->pad.flags = MEDIA_PAD_FL_SOURCE; + dev->sd.entity.flags |= MEDIA_ENT_F_CAM_SENSOR; + ret = media_entity_pads_init(&dev->sd.entity, 1, &dev->pad); + if (ret < 0) + goto error_free_ctrls; + + ep = fwnode_graph_get_next_endpoint(dev_fwnode(&client->dev), NULL); + if (!ep) { + dev_err(&client->dev, + "Unable to get endpoint in node %pOF\n", + client->dev.of_node); + ret = -ENOENT; + goto error_free_ctrls; + } + dev->sd.fwnode = ep; + + ret = v4l2_async_register_subdev(&dev->sd); + if (ret) + goto error_put_node; + + return 0; + +error_put_node: + fwnode_handle_put(dev->sd.fwnode); +error_free_ctrls: + v4l2_ctrl_handler_free(&dev->ctrls); +error: + i2c_unregister_device(dev->isp); + + return ret; +} + +static int rdacm21_remove(struct i2c_client *client) +{ + struct rdacm21_device *dev = sd_to_rdacm21(i2c_get_clientdata(client)); + + v4l2_async_unregister_subdev(&dev->sd); + v4l2_ctrl_handler_free(&dev->ctrls); + i2c_unregister_device(dev->isp); + fwnode_handle_put(dev->sd.fwnode); + + return 0; +} + +static const struct of_device_id rdacm21_of_ids[] = { + { .compatible = "imi,rdacm21" }, + { } +}; +MODULE_DEVICE_TABLE(of, rdacm21_of_ids); + +static struct i2c_driver rdacm21_i2c_driver = { + .driver = { + .name = "rdacm21", + .of_match_table = rdacm21_of_ids, + }, + .probe_new = rdacm21_probe, + .remove = rdacm21_remove, +}; + +module_i2c_driver(rdacm21_i2c_driver); + +MODULE_DESCRIPTION("GMSL Camera driver for RDACM21"); +MODULE_AUTHOR("Jacopo Mondi"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From d899e5f1db7aa67cad01f4835286cb985890a3da Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Wed, 27 Jan 2021 18:46:32 +0100 Subject: media: dt-bindings: media: imx258: add bindings for IMX258 sensor Add bindings for the IMX258 camera sensor. The bindings, just like the driver, are quite limited, e.g. do not support regulator supplies. Signed-off-by: Krzysztof Kozlowski Reviewed-by: Rob Herring Signed-off-by: Sakari Ailus Signed-off-by: Mauro Carvalho Chehab --- .../devicetree/bindings/media/i2c/imx258.yaml | 134 +++++++++++++++++++++ MAINTAINERS | 1 + 2 files changed, 135 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/i2c/imx258.yaml (limited to 'MAINTAINERS') diff --git a/Documentation/devicetree/bindings/media/i2c/imx258.yaml b/Documentation/devicetree/bindings/media/i2c/imx258.yaml new file mode 100644 index 000000000000..eaf77866ed9b --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/imx258.yaml @@ -0,0 +1,134 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/imx258.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Sony IMX258 13 Mpixel CMOS Digital Image Sensor + +maintainers: + - Krzysztof Kozlowski + +description: |- + IMX258 is a diagonal 5.867mm (Type 1/3.06) 13 Mega-pixel CMOS active pixel + type stacked image sensor with a square pixel array of size 4208 x 3120. It + is programmable through I2C interface. Image data is sent through MIPI + CSI-2. + +properties: + compatible: + const: sony,imx258 + + assigned-clocks: true + assigned-clock-parents: true + assigned-clock-rates: true + + clocks: + description: + Clock frequency from 6 to 27 MHz. + maxItems: 1 + + reg: + maxItems: 1 + + reset-gpios: + description: |- + Reference to the GPIO connected to the XCLR pin, if any. + + vana-supply: + description: + Analog voltage (VANA) supply, 2.7 V + + vdig-supply: + description: + Digital I/O voltage (VDIG) supply, 1.2 V + + vif-supply: + description: + Interface voltage (VIF) supply, 1.8 V + + # See ../video-interfaces.txt for more details + port: + type: object + properties: + endpoint: + type: object + properties: + data-lanes: + oneOf: + - items: + - const: 1 + - const: 2 + - const: 3 + - const: 4 + - items: + - const: 1 + - const: 2 + + link-frequencies: + allOf: + - $ref: /schemas/types.yaml#/definitions/uint64-array + description: + Allowed data bus frequencies. + + required: + - data-lanes + - link-frequencies + +required: + - compatible + - reg + - port + +additionalProperties: false + +examples: + - | + i2c0 { + #address-cells = <1>; + #size-cells = <0>; + + sensor@6c { + compatible = "sony,imx258"; + reg = <0x6c>; + clocks = <&imx258_clk>; + + port { + endpoint { + remote-endpoint = <&csi1_ep>; + data-lanes = <1 2 3 4>; + link-frequencies = /bits/ 64 <320000000>; + }; + }; + }; + }; + + /* Oscillator on the camera board */ + imx258_clk: clk { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <19200000>; + }; + + - | + i2c0 { + #address-cells = <1>; + #size-cells = <0>; + + sensor@6c { + compatible = "sony,imx258"; + reg = <0x6c>; + clocks = <&imx258_clk>; + + assigned-clocks = <&imx258_clk>; + assigned-clock-rates = <19200000>; + + port { + endpoint { + remote-endpoint = <&csi1_ep>; + data-lanes = <1 2 3 4>; + link-frequencies = /bits/ 64 <633600000>; + }; + }; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index af8f06213f52..e6ebb2ff5d1c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -16559,6 +16559,7 @@ M: Sakari Ailus L: linux-media@vger.kernel.org S: Maintained T: git git://linuxtv.org/media_tree.git +F: Documentation/devicetree/bindings/media/i2c/imx258.yaml F: drivers/media/i2c/imx258.c SONY IMX274 SENSOR DRIVER -- cgit v1.2.3 From 41b3e23376e9c316e6bf509ab9983fc8b0c0fc25 Mon Sep 17 00:00:00 2001 From: Martina Krasteva Date: Wed, 3 Feb 2021 14:54:40 +0100 Subject: media: dt-bindings: media: Add bindings for imx334 - Add dt-bindings documentation for Sony imx334 sensor driver. - Add MAINTAINERS entry for Sony imx334 binding documentation. Signed-off-by: Martina Krasteva Reviewed-by: Gjorgji Rosikopulos Acked-by: Daniele Alessandrelli Acked-by: Paul J. Murphy Reviewed-by: Rob Herring Signed-off-by: Sakari Ailus Signed-off-by: Mauro Carvalho Chehab --- .../devicetree/bindings/media/i2c/sony,imx334.yaml | 91 ++++++++++++++++++++++ MAINTAINERS | 8 ++ 2 files changed, 99 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/i2c/sony,imx334.yaml (limited to 'MAINTAINERS') diff --git a/Documentation/devicetree/bindings/media/i2c/sony,imx334.yaml b/Documentation/devicetree/bindings/media/i2c/sony,imx334.yaml new file mode 100644 index 000000000000..24e689314bde --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/sony,imx334.yaml @@ -0,0 +1,91 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright (C) 2021 Intel Corporation +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/sony,imx334.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Sony IMX334 Sensor + +maintainers: + - Paul J. Murphy + - Daniele Alessandrelli + +description: + IMX334 sensor is a Sony CMOS active pixel digital image sensor with an active + array size of 3864H x 2202V. It is programmable through I2C interface. The + I2C client address is fixed to 0x1a as per sensor data sheet. Image data is + sent through MIPI CSI-2. + +properties: + compatible: + const: sony,imx334 + reg: + description: I2C address + maxItems: 1 + + assigned-clocks: true + assigned-clock-parents: true + assigned-clock-rates: true + + clocks: + description: Clock frequency from 6 to 27 MHz, 37.125MHz, 74.25MHz + maxItems: 1 + + reset-gpios: + description: Reference to the GPIO connected to the XCLR pin, if any. + + port: + type: object + additionalProperties: false + $ref: /schemas/graph.yaml#/properties/port + + properties: + endpoint: + type: object + properties: + data-lanes: + $ref: ../video-interfaces.yaml#/properties/data-lanes + link-frequencies: + $ref: ../video-interfaces.yaml#/properties/link-frequencies + + required: + - data-lanes + - link-frequencies + + required: + - endpoint + +required: + - compatible + - reg + - clocks + - port + +additionalProperties: false + +examples: + - | + i2c0 { + #address-cells = <1>; + #size-cells = <0>; + + camera@1a { + compatible = "sony,imx334"; + reg = <0x1a>; + clocks = <&imx334_clk>; + + assigned-clocks = <&imx334_clk>; + assigned-clock-parents = <&imx334_clk_parent>; + assigned-clock-rates = <24000000>; + + port { + imx334: endpoint { + remote-endpoint = <&cam>; + data-lanes = <1 2 3 4>; + link-frequencies = /bits/ 64 <891000000>; + }; + }; + }; + }; +... diff --git a/MAINTAINERS b/MAINTAINERS index e6ebb2ff5d1c..36be45824a43 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -16585,6 +16585,14 @@ S: Maintained T: git git://linuxtv.org/media_tree.git F: drivers/media/i2c/imx319.c +SONY IMX334 SENSOR DRIVER +M: Paul J. Murphy +M: Daniele Alessandrelli +L: linux-media@vger.kernel.org +S: Maintained +T: git git://linuxtv.org/media_tree.git +F: Documentation/devicetree/bindings/media/i2c/sony,imx334.yaml + SONY IMX355 SENSOR DRIVER M: Tianshu Qiu L: linux-media@vger.kernel.org -- cgit v1.2.3 From 9746b11715c3949241e2d88cb9057da4adab7e02 Mon Sep 17 00:00:00 2001 From: Martina Krasteva Date: Wed, 3 Feb 2021 14:54:41 +0100 Subject: media: i2c: Add imx334 camera sensor driver Add a v4l2 sub-device driver for the Sony imx334 image sensor. This is a camera sensor using the i2c bus for control and the csi-2 bus for data. The following features are supported: - manual exposure and analog gain control support - vblank/hblank/pixel rate/link freq control support - supported resolution: - 3840x2160 @ 60fps - supported bayer order output: - SRGGB12 Signed-off-by: Martina Krasteva Reviewed-by: Gjorgji Rosikopulos Acked-by: Daniele Alessandrelli Acked-by: Paul J. Murphy Reviewed-by: Jacopo Mondi Signed-off-by: Sakari Ailus Signed-off-by: Mauro Carvalho Chehab --- MAINTAINERS | 1 + drivers/media/i2c/Kconfig | 14 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/imx334.c | 1131 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1147 insertions(+) create mode 100644 drivers/media/i2c/imx334.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 36be45824a43..0da3255abb91 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -16592,6 +16592,7 @@ L: linux-media@vger.kernel.org S: Maintained T: git git://linuxtv.org/media_tree.git F: Documentation/devicetree/bindings/media/i2c/sony,imx334.yaml +F: drivers/media/i2c/imx334.c SONY IMX355 SENSOR DRIVER M: Tianshu Qiu diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index ad92f4789b83..2d3dc0d82f9e 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -813,6 +813,20 @@ config VIDEO_IMX319 To compile this driver as a module, choose M here: the module will be called imx319. +config VIDEO_IMX334 + tristate "Sony IMX334 sensor support" + depends on OF_GPIO + depends on I2C && VIDEO_V4L2 + select VIDEO_V4L2_SUBDEV_API + select MEDIA_CONTROLLER + select V4L2_FWNODE + help + This is a Video4Linux2 sensor driver for the Sony + IMX334 camera. + + To compile this driver as a module, choose M here: the + module will be called imx334. + config VIDEO_IMX355 tristate "Sony IMX355 sensor support" depends on I2C && VIDEO_V4L2 diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index f5cd92bc285d..6bd22d63e1a7 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -122,6 +122,7 @@ obj-$(CONFIG_VIDEO_IMX258) += imx258.o obj-$(CONFIG_VIDEO_IMX274) += imx274.o obj-$(CONFIG_VIDEO_IMX290) += imx290.o obj-$(CONFIG_VIDEO_IMX319) += imx319.o +obj-$(CONFIG_VIDEO_IMX334) += imx334.o obj-$(CONFIG_VIDEO_IMX355) += imx355.o obj-$(CONFIG_VIDEO_MAX9286) += max9286.o rdacm20-camera_module-objs := rdacm20.o max9271.o diff --git a/drivers/media/i2c/imx334.c b/drivers/media/i2c/imx334.c new file mode 100644 index 000000000000..07e31bc2ef18 --- /dev/null +++ b/drivers/media/i2c/imx334.c @@ -0,0 +1,1131 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Sony imx334 sensor driver + * + * Copyright (C) 2021 Intel Corporation + */ +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +/* Streaming Mode */ +#define IMX334_REG_MODE_SELECT 0x3000 +#define IMX334_MODE_STANDBY 0x01 +#define IMX334_MODE_STREAMING 0x00 + +/* Lines per frame */ +#define IMX334_REG_LPFR 0x3030 + +/* Chip ID */ +#define IMX334_REG_ID 0x3044 +#define IMX334_ID 0x1e + +/* Exposure control */ +#define IMX334_REG_SHUTTER 0x3058 +#define IMX334_EXPOSURE_MIN 1 +#define IMX334_EXPOSURE_OFFSET 5 +#define IMX334_EXPOSURE_STEP 1 +#define IMX334_EXPOSURE_DEFAULT 0x0648 + +/* Analog gain control */ +#define IMX334_REG_AGAIN 0x30e8 +#define IMX334_AGAIN_MIN 0 +#define IMX334_AGAIN_MAX 240 +#define IMX334_AGAIN_STEP 1 +#define IMX334_AGAIN_DEFAULT 0 + +/* Group hold register */ +#define IMX334_REG_HOLD 0x3001 + +/* Input clock rate */ +#define IMX334_INCLK_RATE 24000000 + +/* CSI2 HW configuration */ +#define IMX334_LINK_FREQ 891000000 +#define IMX334_NUM_DATA_LANES 4 + +#define IMX334_REG_MIN 0x00 +#define IMX334_REG_MAX 0xfffff + +/** + * struct imx334_reg - imx334 sensor register + * @address: Register address + * @val: Register value + */ +struct imx334_reg { + u16 address; + u8 val; +}; + +/** + * struct imx334_reg_list - imx334 sensor register list + * @num_of_regs: Number of registers in the list + * @regs: Pointer to register list + */ +struct imx334_reg_list { + u32 num_of_regs; + const struct imx334_reg *regs; +}; + +/** + * struct imx334_mode - imx334 sensor mode structure + * @width: Frame width + * @height: Frame height + * @code: Format code + * @hblank: Horizontal blanking in lines + * @vblank: Vertical blanking in lines + * @vblank_min: Minimal vertical blanking in lines + * @vblank_max: Maximum vertical blanking in lines + * @pclk: Sensor pixel clock + * @link_freq_idx: Link frequency index + * @reg_list: Register list for sensor mode + */ +struct imx334_mode { + u32 width; + u32 height; + u32 code; + u32 hblank; + u32 vblank; + u32 vblank_min; + u32 vblank_max; + u64 pclk; + u32 link_freq_idx; + struct imx334_reg_list reg_list; +}; + +/** + * struct imx334 - imx334 sensor device structure + * @dev: Pointer to generic device + * @client: Pointer to i2c client + * @sd: V4L2 sub-device + * @pad: Media pad. Only one pad supported + * @reset_gpio: Sensor reset gpio + * @inclk: Sensor input clock + * @ctrl_handler: V4L2 control handler + * @link_freq_ctrl: Pointer to link frequency control + * @pclk_ctrl: Pointer to pixel clock control + * @hblank_ctrl: Pointer to horizontal blanking control + * @vblank_ctrl: Pointer to vertical blanking control + * @exp_ctrl: Pointer to exposure control + * @again_ctrl: Pointer to analog gain control + * @vblank: Vertical blanking in lines + * @cur_mode: Pointer to current selected sensor mode + * @mutex: Mutex for serializing sensor controls + * @streaming: Flag indicating streaming state + */ +struct imx334 { + struct device *dev; + struct i2c_client *client; + struct v4l2_subdev sd; + struct media_pad pad; + struct gpio_desc *reset_gpio; + struct clk *inclk; + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_ctrl *link_freq_ctrl; + struct v4l2_ctrl *pclk_ctrl; + struct v4l2_ctrl *hblank_ctrl; + struct v4l2_ctrl *vblank_ctrl; + struct { + struct v4l2_ctrl *exp_ctrl; + struct v4l2_ctrl *again_ctrl; + }; + u32 vblank; + const struct imx334_mode *cur_mode; + struct mutex mutex; + bool streaming; +}; + +static const s64 link_freq[] = { + IMX334_LINK_FREQ, +}; + +/* Sensor mode registers */ +static const struct imx334_reg mode_3840x2160_regs[] = { + {0x3000, 0x01}, + {0x3002, 0x00}, + {0x3018, 0x04}, + {0x37b0, 0x36}, + {0x304c, 0x00}, + {0x300c, 0x3b}, + {0x300d, 0x2a}, + {0x3034, 0x26}, + {0x3035, 0x02}, + {0x314c, 0x29}, + {0x314d, 0x01}, + {0x315a, 0x02}, + {0x3168, 0xa0}, + {0x316a, 0x7e}, + {0x3288, 0x21}, + {0x328a, 0x02}, + {0x302c, 0x3c}, + {0x302e, 0x00}, + {0x302f, 0x0f}, + {0x3076, 0x70}, + {0x3077, 0x08}, + {0x3090, 0x70}, + {0x3091, 0x08}, + {0x30d8, 0x20}, + {0x30d9, 0x12}, + {0x3308, 0x70}, + {0x3309, 0x08}, + {0x3414, 0x05}, + {0x3416, 0x18}, + {0x35ac, 0x0e}, + {0x3648, 0x01}, + {0x364a, 0x04}, + {0x364c, 0x04}, + {0x3678, 0x01}, + {0x367c, 0x31}, + {0x367e, 0x31}, + {0x3708, 0x02}, + {0x3714, 0x01}, + {0x3715, 0x02}, + {0x3716, 0x02}, + {0x3717, 0x02}, + {0x371c, 0x3d}, + {0x371d, 0x3f}, + {0x372c, 0x00}, + {0x372d, 0x00}, + {0x372e, 0x46}, + {0x372f, 0x00}, + {0x3730, 0x89}, + {0x3731, 0x00}, + {0x3732, 0x08}, + {0x3733, 0x01}, + {0x3734, 0xfe}, + {0x3735, 0x05}, + {0x375d, 0x00}, + {0x375e, 0x00}, + {0x375f, 0x61}, + {0x3760, 0x06}, + {0x3768, 0x1b}, + {0x3769, 0x1b}, + {0x376a, 0x1a}, + {0x376b, 0x19}, + {0x376c, 0x18}, + {0x376d, 0x14}, + {0x376e, 0x0f}, + {0x3776, 0x00}, + {0x3777, 0x00}, + {0x3778, 0x46}, + {0x3779, 0x00}, + {0x377a, 0x08}, + {0x377b, 0x01}, + {0x377c, 0x45}, + {0x377d, 0x01}, + {0x377e, 0x23}, + {0x377f, 0x02}, + {0x3780, 0xd9}, + {0x3781, 0x03}, + {0x3782, 0xf5}, + {0x3783, 0x06}, + {0x3784, 0xa5}, + {0x3788, 0x0f}, + {0x378a, 0xd9}, + {0x378b, 0x03}, + {0x378c, 0xeb}, + {0x378d, 0x05}, + {0x378e, 0x87}, + {0x378f, 0x06}, + {0x3790, 0xf5}, + {0x3792, 0x43}, + {0x3794, 0x7a}, + {0x3796, 0xa1}, + {0x3e04, 0x0e}, + {0x3a00, 0x01}, +}; + +/* Supported sensor mode configurations */ +static const struct imx334_mode supported_mode = { + .width = 3840, + .height = 2160, + .hblank = 560, + .vblank = 2340, + .vblank_min = 90, + .vblank_max = 132840, + .pclk = 594000000, + .link_freq_idx = 0, + .code = MEDIA_BUS_FMT_SRGGB12_1X12, + .reg_list = { + .num_of_regs = ARRAY_SIZE(mode_3840x2160_regs), + .regs = mode_3840x2160_regs, + }, +}; + +/** + * to_imx334() - imv334 V4L2 sub-device to imx334 device. + * @subdev: pointer to imx334 V4L2 sub-device + * + * Return: pointer to imx334 device + */ +static inline struct imx334 *to_imx334(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct imx334, sd); +} + +/** + * imx334_read_reg() - Read registers. + * @imx334: pointer to imx334 device + * @reg: register address + * @len: length of bytes to read. Max supported bytes is 4 + * @val: pointer to register value to be filled. + * + * Big endian register addresses with little endian values. + * + * Return: 0 if successful, error code otherwise. + */ +static int imx334_read_reg(struct imx334 *imx334, u16 reg, u32 len, u32 *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx334->sd); + struct i2c_msg msgs[2] = {0}; + u8 addr_buf[2] = {0}; + u8 data_buf[4] = {0}; + int ret; + + if (WARN_ON(len > 4)) + return -EINVAL; + + put_unaligned_be16(reg, addr_buf); + + /* Write register address */ + msgs[0].addr = client->addr; + msgs[0].flags = 0; + msgs[0].len = ARRAY_SIZE(addr_buf); + msgs[0].buf = addr_buf; + + /* Read data from register */ + msgs[1].addr = client->addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = data_buf; + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *val = get_unaligned_le32(data_buf); + + return 0; +} + +/** + * imx334_write_reg() - Write register + * @imx334: pointer to imx334 device + * @reg: register address + * @len: length of bytes. Max supported bytes is 4 + * @val: register value + * + * Big endian register addresses with little endian values. + * + * Return: 0 if successful, error code otherwise. + */ +static int imx334_write_reg(struct imx334 *imx334, u16 reg, u32 len, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx334->sd); + u8 buf[6] = {0}; + + if (WARN_ON(len > 4)) + return -EINVAL; + + put_unaligned_be16(reg, buf); + put_unaligned_le32(val, buf + 2); + if (i2c_master_send(client, buf, len + 2) != len + 2) + return -EIO; + + return 0; +} + +/** + * imx334_write_regs() - Write a list of registers + * @imx334: pointer to imx334 device + * @regs: list of registers to be written + * @len: length of registers array + * + * Return: 0 if successful, error code otherwise. + */ +static int imx334_write_regs(struct imx334 *imx334, + const struct imx334_reg *regs, u32 len) +{ + unsigned int i; + int ret; + + for (i = 0; i < len; i++) { + ret = imx334_write_reg(imx334, regs[i].address, 1, regs[i].val); + if (ret) + return ret; + } + + return 0; +} + +/** + * imx334_update_controls() - Update control ranges based on streaming mode + * @imx334: pointer to imx334 device + * @mode: pointer to imx334_mode sensor mode + * + * Return: 0 if successful, error code otherwise. + */ +static int imx334_update_controls(struct imx334 *imx334, + const struct imx334_mode *mode) +{ + int ret; + + ret = __v4l2_ctrl_s_ctrl(imx334->link_freq_ctrl, mode->link_freq_idx); + if (ret) + return ret; + + ret = __v4l2_ctrl_s_ctrl(imx334->hblank_ctrl, mode->hblank); + if (ret) + return ret; + + return __v4l2_ctrl_modify_range(imx334->vblank_ctrl, mode->vblank_min, + mode->vblank_max, 1, mode->vblank); +} + +/** + * imx334_update_exp_gain() - Set updated exposure and gain + * @imx334: pointer to imx334 device + * @exposure: updated exposure value + * @gain: updated analog gain value + * + * Return: 0 if successful, error code otherwise. + */ +static int imx334_update_exp_gain(struct imx334 *imx334, u32 exposure, u32 gain) +{ + u32 lpfr, shutter; + int ret; + + lpfr = imx334->vblank + imx334->cur_mode->height; + shutter = lpfr - exposure; + + dev_dbg(imx334->dev, "Set long exp %u analog gain %u sh0 %u lpfr %u", + exposure, gain, shutter, lpfr); + + ret = imx334_write_reg(imx334, IMX334_REG_HOLD, 1, 1); + if (ret) + return ret; + + ret = imx334_write_reg(imx334, IMX334_REG_LPFR, 3, lpfr); + if (ret) + goto error_release_group_hold; + + ret = imx334_write_reg(imx334, IMX334_REG_SHUTTER, 3, shutter); + if (ret) + goto error_release_group_hold; + + ret = imx334_write_reg(imx334, IMX334_REG_AGAIN, 1, gain); + +error_release_group_hold: + imx334_write_reg(imx334, IMX334_REG_HOLD, 1, 0); + + return ret; +} + +/** + * imx334_set_ctrl() - Set subdevice control + * @ctrl: pointer to v4l2_ctrl structure + * + * Supported controls: + * - V4L2_CID_VBLANK + * - cluster controls: + * - V4L2_CID_ANALOGUE_GAIN + * - V4L2_CID_EXPOSURE + * + * Return: 0 if successful, error code otherwise. + */ +static int imx334_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct imx334 *imx334 = + container_of(ctrl->handler, struct imx334, ctrl_handler); + u32 analog_gain; + u32 exposure; + int ret; + + switch (ctrl->id) { + case V4L2_CID_VBLANK: + imx334->vblank = imx334->vblank_ctrl->val; + + dev_dbg(imx334->dev, "Received vblank %u, new lpfr %u", + imx334->vblank, + imx334->vblank + imx334->cur_mode->height); + + ret = __v4l2_ctrl_modify_range(imx334->exp_ctrl, + IMX334_EXPOSURE_MIN, + imx334->vblank + + imx334->cur_mode->height - + IMX334_EXPOSURE_OFFSET, + 1, IMX334_EXPOSURE_DEFAULT); + break; + case V4L2_CID_EXPOSURE: + + /* Set controls only if sensor is in power on state */ + if (!pm_runtime_get_if_in_use(imx334->dev)) + return 0; + + exposure = ctrl->val; + analog_gain = imx334->again_ctrl->val; + + dev_dbg(imx334->dev, "Received exp %u analog gain %u", + exposure, analog_gain); + + ret = imx334_update_exp_gain(imx334, exposure, analog_gain); + + pm_runtime_put(imx334->dev); + + break; + default: + dev_err(imx334->dev, "Invalid control %d", ctrl->id); + ret = -EINVAL; + } + + return ret; +} + +/* V4l2 subdevice control ops*/ +static const struct v4l2_ctrl_ops imx334_ctrl_ops = { + .s_ctrl = imx334_set_ctrl, +}; + +/** + * imx334_enum_mbus_code() - Enumerate V4L2 sub-device mbus codes + * @sd: pointer to imx334 V4L2 sub-device structure + * @cfg: V4L2 sub-device pad configuration + * @code: V4L2 sub-device code enumeration need to be filled + * + * Return: 0 if successful, error code otherwise. + */ +static int imx334_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index > 0) + return -EINVAL; + + code->code = supported_mode.code; + + return 0; +} + +/** + * imx334_enum_frame_size() - Enumerate V4L2 sub-device frame sizes + * @sd: pointer to imx334 V4L2 sub-device structure + * @cfg: V4L2 sub-device pad configuration + * @fsize: V4L2 sub-device size enumeration need to be filled + * + * Return: 0 if successful, error code otherwise. + */ +static int imx334_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fsize) +{ + if (fsize->index > 0) + return -EINVAL; + + if (fsize->code != supported_mode.code) + return -EINVAL; + + fsize->min_width = supported_mode.width; + fsize->max_width = fsize->min_width; + fsize->min_height = supported_mode.height; + fsize->max_height = fsize->min_height; + + return 0; +} + +/** + * imx334_fill_pad_format() - Fill subdevice pad format + * from selected sensor mode + * @imx334: pointer to imx334 device + * @mode: pointer to imx334_mode sensor mode + * @fmt: V4L2 sub-device format need to be filled + */ +static void imx334_fill_pad_format(struct imx334 *imx334, + const struct imx334_mode *mode, + struct v4l2_subdev_format *fmt) +{ + fmt->format.width = mode->width; + fmt->format.height = mode->height; + fmt->format.code = mode->code; + fmt->format.field = V4L2_FIELD_NONE; + fmt->format.colorspace = V4L2_COLORSPACE_RAW; + fmt->format.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + fmt->format.quantization = V4L2_QUANTIZATION_DEFAULT; + fmt->format.xfer_func = V4L2_XFER_FUNC_NONE; +} + +/** + * imx334_get_pad_format() - Get subdevice pad format + * @sd: pointer to imx334 V4L2 sub-device structure + * @cfg: V4L2 sub-device pad configuration + * @fmt: V4L2 sub-device format need to be set + * + * Return: 0 if successful, error code otherwise. + */ +static int imx334_get_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct imx334 *imx334 = to_imx334(sd); + + mutex_lock(&imx334->mutex); + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_mbus_framefmt *framefmt; + + framefmt = v4l2_subdev_get_try_format(sd, cfg, fmt->pad); + fmt->format = *framefmt; + } else { + imx334_fill_pad_format(imx334, imx334->cur_mode, fmt); + } + + mutex_unlock(&imx334->mutex); + + return 0; +} + +/** + * imx334_set_pad_format() - Set subdevice pad format + * @sd: pointer to imx334 V4L2 sub-device structure + * @cfg: V4L2 sub-device pad configuration + * @fmt: V4L2 sub-device format need to be set + * + * Return: 0 if successful, error code otherwise. + */ +static int imx334_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct imx334 *imx334 = to_imx334(sd); + const struct imx334_mode *mode; + int ret = 0; + + mutex_lock(&imx334->mutex); + + mode = &supported_mode; + imx334_fill_pad_format(imx334, mode, fmt); + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_mbus_framefmt *framefmt; + + framefmt = v4l2_subdev_get_try_format(sd, cfg, fmt->pad); + *framefmt = fmt->format; + } else { + ret = imx334_update_controls(imx334, mode); + if (!ret) + imx334->cur_mode = mode; + } + + mutex_unlock(&imx334->mutex); + + return ret; +} + +/** + * imx334_init_pad_cfg() - Initialize sub-device pad configuration + * @sd: pointer to imx334 V4L2 sub-device structure + * @cfg: V4L2 sub-device pad configuration + * + * Return: 0 if successful, error code otherwise. + */ +static int imx334_init_pad_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg) +{ + struct imx334 *imx334 = to_imx334(sd); + struct v4l2_subdev_format fmt = { 0 }; + + fmt.which = cfg ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; + imx334_fill_pad_format(imx334, &supported_mode, &fmt); + + return imx334_set_pad_format(sd, cfg, &fmt); +} + +/** + * imx334_start_streaming() - Start sensor stream + * @imx334: pointer to imx334 device + * + * Return: 0 if successful, error code otherwise. + */ +static int imx334_start_streaming(struct imx334 *imx334) +{ + const struct imx334_reg_list *reg_list; + int ret; + + /* Write sensor mode registers */ + reg_list = &imx334->cur_mode->reg_list; + ret = imx334_write_regs(imx334, reg_list->regs, + reg_list->num_of_regs); + if (ret) { + dev_err(imx334->dev, "fail to write initial registers"); + return ret; + } + + /* Setup handler will write actual exposure and gain */ + ret = __v4l2_ctrl_handler_setup(imx334->sd.ctrl_handler); + if (ret) { + dev_err(imx334->dev, "fail to setup handler"); + return ret; + } + + /* Start streaming */ + ret = imx334_write_reg(imx334, IMX334_REG_MODE_SELECT, + 1, IMX334_MODE_STREAMING); + if (ret) { + dev_err(imx334->dev, "fail to start streaming"); + return ret; + } + + return 0; +} + +/** + * imx334_stop_streaming() - Stop sensor stream + * @imx334: pointer to imx334 device + * + * Return: 0 if successful, error code otherwise. + */ +static int imx334_stop_streaming(struct imx334 *imx334) +{ + return imx334_write_reg(imx334, IMX334_REG_MODE_SELECT, + 1, IMX334_MODE_STANDBY); +} + +/** + * imx334_set_stream() - Enable sensor streaming + * @sd: pointer to imx334 subdevice + * @enable: set to enable sensor streaming + * + * Return: 0 if successful, error code otherwise. + */ +static int imx334_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct imx334 *imx334 = to_imx334(sd); + int ret; + + mutex_lock(&imx334->mutex); + + if (imx334->streaming == enable) { + mutex_unlock(&imx334->mutex); + return 0; + } + + if (enable) { + ret = pm_runtime_get_sync(imx334->dev); + if (ret) + goto error_power_off; + + ret = imx334_start_streaming(imx334); + if (ret) + goto error_power_off; + } else { + imx334_stop_streaming(imx334); + pm_runtime_put(imx334->dev); + } + + imx334->streaming = enable; + + mutex_unlock(&imx334->mutex); + + return 0; + +error_power_off: + pm_runtime_put(imx334->dev); + mutex_unlock(&imx334->mutex); + + return ret; +} + +/** + * imx334_detect() - Detect imx334 sensor + * @imx334: pointer to imx334 device + * + * Return: 0 if successful, -EIO if sensor id does not match + */ +static int imx334_detect(struct imx334 *imx334) +{ + int ret; + u32 val; + + ret = imx334_read_reg(imx334, IMX334_REG_ID, 2, &val); + if (ret) + return ret; + + if (val != IMX334_ID) { + dev_err(imx334->dev, "chip id mismatch: %x!=%x", + IMX334_ID, val); + return -ENXIO; + } + + return 0; +} + +/** + * imx334_parse_hw_config() - Parse HW configuration and check if supported + * @imx334: pointer to imx334 device + * + * Return: 0 if successful, error code otherwise. + */ +static int imx334_parse_hw_config(struct imx334 *imx334) +{ + struct fwnode_handle *fwnode = dev_fwnode(imx334->dev); + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + struct fwnode_handle *ep; + unsigned long rate; + int ret; + int i; + + if (!fwnode) + return -ENXIO; + + /* Request optional reset pin */ + imx334->reset_gpio = devm_gpiod_get_optional(imx334->dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(imx334->reset_gpio)) { + dev_err(imx334->dev, "failed to get reset gpio %d", ret); + return PTR_ERR(imx334->reset_gpio); + } + + /* Get sensor input clock */ + imx334->inclk = devm_clk_get(imx334->dev, NULL); + if (IS_ERR(imx334->inclk)) { + dev_err(imx334->dev, "could not get inclk"); + return PTR_ERR(imx334->inclk); + } + + rate = clk_get_rate(imx334->inclk); + if (rate != IMX334_INCLK_RATE) { + dev_err(imx334->dev, "inclk frequency mismatch"); + return -EINVAL; + } + + ep = fwnode_graph_get_next_endpoint(fwnode, NULL); + if (!ep) + return -ENXIO; + + ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg); + fwnode_handle_put(ep); + if (ret) + return ret; + + if (bus_cfg.bus.mipi_csi2.num_data_lanes != IMX334_NUM_DATA_LANES) { + dev_err(imx334->dev, + "number of CSI2 data lanes %d is not supported", + bus_cfg.bus.mipi_csi2.num_data_lanes); + ret = -EINVAL; + goto done_endpoint_free; + } + + if (!bus_cfg.nr_of_link_frequencies) { + dev_err(imx334->dev, "no link frequencies defined"); + ret = -EINVAL; + goto done_endpoint_free; + } + + for (i = 0; i < bus_cfg.nr_of_link_frequencies; i++) + if (bus_cfg.link_frequencies[i] == IMX334_LINK_FREQ) + goto done_endpoint_free; + + ret = -EINVAL; + +done_endpoint_free: + v4l2_fwnode_endpoint_free(&bus_cfg); + + return ret; +} + +/* V4l2 subdevice ops */ +static const struct v4l2_subdev_video_ops imx334_video_ops = { + .s_stream = imx334_set_stream, +}; + +static const struct v4l2_subdev_pad_ops imx334_pad_ops = { + .init_cfg = imx334_init_pad_cfg, + .enum_mbus_code = imx334_enum_mbus_code, + .enum_frame_size = imx334_enum_frame_size, + .get_fmt = imx334_get_pad_format, + .set_fmt = imx334_set_pad_format, +}; + +static const struct v4l2_subdev_ops imx334_subdev_ops = { + .video = &imx334_video_ops, + .pad = &imx334_pad_ops, +}; + +/** + * imx334_power_on() - Sensor power on sequence + * @dev: pointer to i2c device + * + * Return: 0 if successful, error code otherwise. + */ +static int imx334_power_on(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct imx334 *imx334 = to_imx334(sd); + int ret; + + gpiod_set_value_cansleep(imx334->reset_gpio, 1); + + ret = clk_prepare_enable(imx334->inclk); + if (ret) { + dev_err(imx334->dev, "fail to enable inclk"); + goto error_reset; + } + + usleep_range(18000, 20000); + + return 0; + +error_reset: + gpiod_set_value_cansleep(imx334->reset_gpio, 0); + + return ret; +} + +/** + * imx334_power_off() - Sensor power off sequence + * @dev: pointer to i2c device + * + * Return: 0 if successful, error code otherwise. + */ +static int imx334_power_off(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct imx334 *imx334 = to_imx334(sd); + + gpiod_set_value_cansleep(imx334->reset_gpio, 0); + + clk_disable_unprepare(imx334->inclk); + + return 0; +} + +/** + * imx334_init_controls() - Initialize sensor subdevice controls + * @imx334: pointer to imx334 device + * + * Return: 0 if successful, error code otherwise. + */ +static int imx334_init_controls(struct imx334 *imx334) +{ + struct v4l2_ctrl_handler *ctrl_hdlr = &imx334->ctrl_handler; + const struct imx334_mode *mode = imx334->cur_mode; + u32 lpfr; + int ret; + + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 6); + if (ret) + return ret; + + /* Serialize controls with sensor device */ + ctrl_hdlr->lock = &imx334->mutex; + + /* Initialize exposure and gain */ + lpfr = mode->vblank + mode->height; + imx334->exp_ctrl = v4l2_ctrl_new_std(ctrl_hdlr, + &imx334_ctrl_ops, + V4L2_CID_EXPOSURE, + IMX334_EXPOSURE_MIN, + lpfr - IMX334_EXPOSURE_OFFSET, + IMX334_EXPOSURE_STEP, + IMX334_EXPOSURE_DEFAULT); + + imx334->again_ctrl = v4l2_ctrl_new_std(ctrl_hdlr, + &imx334_ctrl_ops, + V4L2_CID_ANALOGUE_GAIN, + IMX334_AGAIN_MIN, + IMX334_AGAIN_MAX, + IMX334_AGAIN_STEP, + IMX334_AGAIN_DEFAULT); + + v4l2_ctrl_cluster(2, &imx334->exp_ctrl); + + imx334->vblank_ctrl = v4l2_ctrl_new_std(ctrl_hdlr, + &imx334_ctrl_ops, + V4L2_CID_VBLANK, + mode->vblank_min, + mode->vblank_max, + 1, mode->vblank); + + /* Read only controls */ + imx334->pclk_ctrl = v4l2_ctrl_new_std(ctrl_hdlr, + &imx334_ctrl_ops, + V4L2_CID_PIXEL_RATE, + mode->pclk, mode->pclk, + 1, mode->pclk); + + imx334->link_freq_ctrl = v4l2_ctrl_new_int_menu(ctrl_hdlr, + &imx334_ctrl_ops, + V4L2_CID_LINK_FREQ, + ARRAY_SIZE(link_freq) - + 1, + mode->link_freq_idx, + link_freq); + if (imx334->link_freq_ctrl) + imx334->link_freq_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + imx334->hblank_ctrl = v4l2_ctrl_new_std(ctrl_hdlr, + &imx334_ctrl_ops, + V4L2_CID_HBLANK, + IMX334_REG_MIN, + IMX334_REG_MAX, + 1, mode->hblank); + if (imx334->hblank_ctrl) + imx334->hblank_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + if (ctrl_hdlr->error) { + dev_err(imx334->dev, "control init failed: %d", + ctrl_hdlr->error); + v4l2_ctrl_handler_free(ctrl_hdlr); + return ctrl_hdlr->error; + } + + imx334->sd.ctrl_handler = ctrl_hdlr; + + return 0; +} + +/** + * imx334_probe() - I2C client device binding + * @client: pointer to i2c client device + * + * Return: 0 if successful, error code otherwise. + */ +static int imx334_probe(struct i2c_client *client) +{ + struct imx334 *imx334; + int ret; + + imx334 = devm_kzalloc(&client->dev, sizeof(*imx334), GFP_KERNEL); + if (!imx334) + return -ENOMEM; + + imx334->dev = &client->dev; + + /* Initialize subdev */ + v4l2_i2c_subdev_init(&imx334->sd, client, &imx334_subdev_ops); + + ret = imx334_parse_hw_config(imx334); + if (ret) { + dev_err(imx334->dev, "HW configuration is not supported"); + return ret; + } + + mutex_init(&imx334->mutex); + + ret = imx334_power_on(imx334->dev); + if (ret) { + dev_err(imx334->dev, "failed to power-on the sensor"); + goto error_mutex_destroy; + } + + /* Check module identity */ + ret = imx334_detect(imx334); + if (ret) { + dev_err(imx334->dev, "failed to find sensor: %d", ret); + goto error_power_off; + } + + /* Set default mode to max resolution */ + imx334->cur_mode = &supported_mode; + imx334->vblank = imx334->cur_mode->vblank; + + ret = imx334_init_controls(imx334); + if (ret) { + dev_err(imx334->dev, "failed to init controls: %d", ret); + goto error_power_off; + } + + /* Initialize subdev */ + imx334->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + imx334->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + /* Initialize source pad */ + imx334->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&imx334->sd.entity, 1, &imx334->pad); + if (ret) { + dev_err(imx334->dev, "failed to init entity pads: %d", ret); + goto error_handler_free; + } + + ret = v4l2_async_register_subdev_sensor_common(&imx334->sd); + if (ret < 0) { + dev_err(imx334->dev, + "failed to register async subdev: %d", ret); + goto error_media_entity; + } + + pm_runtime_set_active(imx334->dev); + pm_runtime_enable(imx334->dev); + pm_runtime_idle(imx334->dev); + + return 0; + +error_media_entity: + media_entity_cleanup(&imx334->sd.entity); +error_handler_free: + v4l2_ctrl_handler_free(imx334->sd.ctrl_handler); +error_power_off: + imx334_power_off(imx334->dev); +error_mutex_destroy: + mutex_destroy(&imx334->mutex); + + return ret; +} + +/** + * imx334_remove() - I2C client device unbinding + * @client: pointer to I2C client device + * + * Return: 0 if successful, error code otherwise. + */ +static int imx334_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx334 *imx334 = to_imx334(sd); + + v4l2_async_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(sd->ctrl_handler); + + pm_runtime_disable(&client->dev); + pm_runtime_suspended(&client->dev); + + mutex_destroy(&imx334->mutex); + + return 0; +} + +static const struct dev_pm_ops imx334_pm_ops = { + SET_RUNTIME_PM_OPS(imx334_power_off, imx334_power_on, NULL) +}; + +static const struct of_device_id imx334_of_match[] = { + { .compatible = "sony,imx334" }, + { } +}; + +MODULE_DEVICE_TABLE(of, imx334_of_match); + +static struct i2c_driver imx334_driver = { + .probe_new = imx334_probe, + .remove = imx334_remove, + .driver = { + .name = "imx334", + .pm = &imx334_pm_ops, + .of_match_table = imx334_of_match, + }, +}; + +module_i2c_driver(imx334_driver); + +MODULE_DESCRIPTION("Sony imx334 sensor driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3