From b4b65b45e313332068ca082d6825cbcd497ab9ba Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Thu, 16 Jul 2026 19:06:48 +0100 Subject: [PATCH 1/3] media: i2c: Remove downstream imx708 driver Signed-off-by: Dave Stevenson --- .../bindings/media/i2c/sony,imx708.yaml | 128 - drivers/media/i2c/imx708.c | 2151 ----------------- 2 files changed, 2279 deletions(-) delete mode 100644 Documentation/devicetree/bindings/media/i2c/sony,imx708.yaml delete mode 100644 drivers/media/i2c/imx708.c diff --git a/Documentation/devicetree/bindings/media/i2c/sony,imx708.yaml b/Documentation/devicetree/bindings/media/i2c/sony,imx708.yaml deleted file mode 100644 index 286aad2e8c697c..00000000000000 --- a/Documentation/devicetree/bindings/media/i2c/sony,imx708.yaml +++ /dev/null @@ -1,128 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/media/i2c/sony,imx708.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: Sony 1/2.3-Inch 12Mpixel CMOS Digital Image Sensor - -maintainers: - - Raspberry Pi Kernel Maintenance - -description: |- - The Sony IMX708 is a 1/2.3-inch CMOS active pixel digital image sensor - with an active array size of 4608H x 2592V. It is programmable through - I2C interface. The I2C address is fixed to 0x1A as per sensor data sheet. - Image data is sent through MIPI CSI-2, which is configured as either 2 or - 4 data lanes. - -properties: - compatible: - const: sony,imx708 - - reg: - maxItems: 1 - - clocks: - maxItems: 1 - - clock-names: - description: Input clock (6 to 27 MHz) - items: - - const: inck - - vdig-supply: - description: - Digital I/O voltage supply, 1.1 volts - - vana1-supply: - description: - Analog1 voltage supply, 2.8 volts - - vana2-supply: - description: - Analog2 voltage supply, 1.8 volts - - vddl-supply: - description: - Digital core voltage supply, 1.8 volts - - reset-gpios: - description: Sensor reset (XCLR) GPIO - maxItems: 1 - - port: - $ref: /schemas/graph.yaml#/$defs/port-base - description: | - Video output port - - properties: - endpoint: - $ref: /schemas/media/video-interfaces.yaml# - unevaluatedProperties: false - - properties: - data-lanes: - anyOf: - - items: - - const: 1 - - const: 2 - - items: - - const: 1 - - const: 2 - - const: 3 - - const: 4 - - link-frequencies: true - - required: - - data-lanes - - link-frequencies - - additionalProperties: false - -required: - - compatible - - reg - - clocks - - clock-names - - vdig-supply - - vana1-supply - - vana2-supply - - vddl-supply - - port - -additionalProperties: false - -examples: - - | - #include - - i2c { - #address-cells = <1>; - #size-cells = <0>; - - imx708: camera-sensor@1a { - compatible = "sony,imx708"; - reg = <0x1a>; - - clocks = <&clk 90>; - clock-names = "inck"; - - vdig-supply = <&camera_vdig>; - vana1-supply = <&camera_vana1>; - vana2-supply = <&camera_vana2>; - vddl-supply = <&camera_vddl>; - - reset-gpios = <&gpio 35 GPIO_ACTIVE_LOW>; - - port { - imx708_ep: endpoint { - data-lanes = <1 2>; - link-frequencies = /bits/ 64 <450000000>; - remote-endpoint = <&csi_ep>; - }; - }; - }; - }; -... diff --git a/drivers/media/i2c/imx708.c b/drivers/media/i2c/imx708.c deleted file mode 100644 index 23af2f3f08bfaf..00000000000000 --- a/drivers/media/i2c/imx708.c +++ /dev/null @@ -1,2151 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * A V4L2 driver for Sony IMX708 cameras. - * Copyright (C) 2022, Raspberry Pi Ltd - * - * Based on Sony imx477 camera driver - * Copyright (C) 2020 Raspberry Pi Ltd - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* - * Parameter to adjust Quad Bayer re-mosaic broken line correction - * strength, used in full-resolution mode only. Set zero to disable. - */ -static int qbc_adjust = 2; -module_param(qbc_adjust, int, 0644); -MODULE_PARM_DESC(qbc_adjust, "Quad Bayer broken line correction strength [0,2-5]"); - -#define IMX708_REG_VALUE_08BIT 1 -#define IMX708_REG_VALUE_16BIT 2 - -/* Chip ID */ -#define IMX708_REG_CHIP_ID 0x0016 -#define IMX708_CHIP_ID 0x0708 - -#define IMX708_REG_MODE_SELECT 0x0100 -#define IMX708_MODE_STANDBY 0x00 -#define IMX708_MODE_STREAMING 0x01 - -#define IMX708_REG_ORIENTATION 0x101 - -#define IMX708_INCLK_FREQ 24000000 - -/* Default initial pixel rate, will get updated for each mode. */ -#define IMX708_INITIAL_PIXEL_RATE 590000000 - -/* V_TIMING internal */ -#define IMX708_REG_FRAME_LENGTH 0x0340 -#define IMX708_FRAME_LENGTH_MAX 0xffff - -/* Long exposure multiplier */ -#define IMX708_LONG_EXP_SHIFT_MAX 7 -#define IMX708_LONG_EXP_SHIFT_REG 0x3100 - -/* Exposure control */ -#define IMX708_REG_EXPOSURE 0x0202 -#define IMX708_EXPOSURE_OFFSET 48 -#define IMX708_EXPOSURE_DEFAULT 0x640 -#define IMX708_EXPOSURE_STEP 1 -#define IMX708_EXPOSURE_MIN 1 -#define IMX708_EXPOSURE_MAX (IMX708_FRAME_LENGTH_MAX - \ - IMX708_EXPOSURE_OFFSET) - -/* Analog gain control */ -#define IMX708_REG_ANALOG_GAIN 0x0204 -#define IMX708_ANA_GAIN_MIN 112 -#define IMX708_ANA_GAIN_MAX 960 -#define IMX708_ANA_GAIN_STEP 1 -#define IMX708_ANA_GAIN_DEFAULT IMX708_ANA_GAIN_MIN - -/* Digital gain control */ -#define IMX708_REG_DIGITAL_GAIN 0x020e -#define IMX708_DGTL_GAIN_MIN 0x0100 -#define IMX708_DGTL_GAIN_MAX 0xffff -#define IMX708_DGTL_GAIN_DEFAULT 0x0100 -#define IMX708_DGTL_GAIN_STEP 1 - -/* Colour balance controls */ -#define IMX708_REG_COLOUR_BALANCE_RED 0x0b90 -#define IMX708_REG_COLOUR_BALANCE_BLUE 0x0b92 -#define IMX708_COLOUR_BALANCE_MIN 0x01 -#define IMX708_COLOUR_BALANCE_MAX 0xffff -#define IMX708_COLOUR_BALANCE_STEP 0x01 -#define IMX708_COLOUR_BALANCE_DEFAULT 0x100 - -/* Test Pattern Control */ -#define IMX708_REG_TEST_PATTERN 0x0600 -#define IMX708_TEST_PATTERN_DISABLE 0 -#define IMX708_TEST_PATTERN_SOLID_COLOR 1 -#define IMX708_TEST_PATTERN_COLOR_BARS 2 -#define IMX708_TEST_PATTERN_GREY_COLOR 3 -#define IMX708_TEST_PATTERN_PN9 4 - -/* Test pattern colour components */ -#define IMX708_REG_TEST_PATTERN_R 0x0602 -#define IMX708_REG_TEST_PATTERN_GR 0x0604 -#define IMX708_REG_TEST_PATTERN_B 0x0606 -#define IMX708_REG_TEST_PATTERN_GB 0x0608 -#define IMX708_TEST_PATTERN_COLOUR_MIN 0 -#define IMX708_TEST_PATTERN_COLOUR_MAX 0x0fff -#define IMX708_TEST_PATTERN_COLOUR_STEP 1 - -#define IMX708_REG_BASE_SPC_GAINS_L 0x7b10 -#define IMX708_REG_BASE_SPC_GAINS_R 0x7c00 - -/* HDR exposure ratio (long:med == med:short) */ -#define IMX708_HDR_EXPOSURE_RATIO 4 -#define IMX708_REG_MID_EXPOSURE 0x3116 -#define IMX708_REG_SHT_EXPOSURE 0x0224 -#define IMX708_REG_MID_ANALOG_GAIN 0x3118 -#define IMX708_REG_SHT_ANALOG_GAIN 0x0216 - -#define IMX708_REG_CLKLANE_BLANK 0x3220 -#define IMX708_CLKLANE_BLANK_NONCONT BIT(0) - -/* QBC Re-mosaic broken line correction registers */ -#define IMX708_LPF_INTENSITY_EN 0xC428 -#define IMX708_LPF_INTENSITY_ENABLED 0x00 -#define IMX708_LPF_INTENSITY_DISABLED 0x01 -#define IMX708_LPF_INTENSITY 0xC429 - -/* - * Metadata buffer holds a variety of data, all sent with the same VC/DT (0x12). - * It comprises two scanlines (of up to 5760 bytes each, for 4608 pixels) - * of embedded data, one line of PDAF data, and two lines of AE-HIST data - * (AE histograms are valid for HDR mode and empty in non-HDR modes). - */ -#define IMX708_EMBEDDED_LINE_WIDTH (5 * 5760) -#define IMX708_NUM_EMBEDDED_LINES 1 - -enum pad_types { - IMAGE_PAD, - METADATA_PAD, - NUM_PADS -}; - -/* IMX708 native and active pixel array size. */ -#define IMX708_NATIVE_WIDTH 4640U -#define IMX708_NATIVE_HEIGHT 2658U -#define IMX708_PIXEL_ARRAY_LEFT 16U -#define IMX708_PIXEL_ARRAY_TOP 24U -#define IMX708_PIXEL_ARRAY_WIDTH 4608U -#define IMX708_PIXEL_ARRAY_HEIGHT 2592U - -struct imx708_reg { - u16 address; - u8 val; -}; - -struct imx708_reg_list { - unsigned int num_of_regs; - const struct imx708_reg *regs; -}; - -/* Mode : resolution and related config&values */ -struct imx708_mode { - /* Frame width */ - unsigned int width; - - /* Frame height */ - unsigned int height; - - /* H-timing in pixels */ - unsigned int line_length_pix; - - /* Analog crop rectangle. */ - struct v4l2_rect crop; - - /* Highest possible framerate. */ - unsigned int vblank_min; - - /* Default framerate. */ - unsigned int vblank_default; - - /* Default register values */ - struct imx708_reg_list reg_list; - - /* Not all modes have the same pixel rate. */ - u64 pixel_rate; - - /* Not all modes have the same minimum exposure. */ - u32 exposure_lines_min; - - /* Not all modes have the same exposure lines step. */ - u32 exposure_lines_step; - - /* HDR flag, used for checking if the current mode is HDR */ - bool hdr; - - /* Quad Bayer Re-mosaic flag */ - bool remosaic; -}; - -/* Default PDAF pixel correction gains */ -static const u8 pdaf_gains[2][9] = { - { 0x4c, 0x4c, 0x4c, 0x46, 0x3e, 0x39, 0x36, 0x36, 0x36 }, - { 0x36, 0x36, 0x36, 0x39, 0x3e, 0x46, 0x4c, 0x4c, 0x4c } -}; - -/* Link frequency setup */ -enum { - IMX708_LINK_FREQ_450MHZ, - IMX708_LINK_FREQ_447MHZ, - IMX708_LINK_FREQ_453MHZ, -}; - -static const s64 link_freqs[] = { - [IMX708_LINK_FREQ_450MHZ] = 450000000, - [IMX708_LINK_FREQ_447MHZ] = 447000000, - [IMX708_LINK_FREQ_453MHZ] = 453000000, -}; - -/* 450MHz is the nominal "default" link frequency */ -static const struct imx708_reg link_450Mhz_regs[] = { - {0x030E, 0x01}, - {0x030F, 0x2c}, -}; - -static const struct imx708_reg link_447Mhz_regs[] = { - {0x030E, 0x01}, - {0x030F, 0x2a}, -}; - -static const struct imx708_reg link_453Mhz_regs[] = { - {0x030E, 0x01}, - {0x030F, 0x2e}, -}; - -static const struct imx708_reg_list link_freq_regs[] = { - [IMX708_LINK_FREQ_450MHZ] = { - .regs = link_450Mhz_regs, - .num_of_regs = ARRAY_SIZE(link_450Mhz_regs) - }, - [IMX708_LINK_FREQ_447MHZ] = { - .regs = link_447Mhz_regs, - .num_of_regs = ARRAY_SIZE(link_447Mhz_regs) - }, - [IMX708_LINK_FREQ_453MHZ] = { - .regs = link_453Mhz_regs, - .num_of_regs = ARRAY_SIZE(link_453Mhz_regs) - }, -}; - -static const struct imx708_reg mode_common_regs[] = { - {0x0100, 0x00}, - {0x0136, 0x18}, - {0x0137, 0x00}, - {0x33F0, 0x02}, - {0x33F1, 0x05}, - {0x3062, 0x00}, - {0x3063, 0x12}, - {0x3068, 0x00}, - {0x3069, 0x12}, - {0x306A, 0x00}, - {0x306B, 0x30}, - {0x3076, 0x00}, - {0x3077, 0x30}, - {0x3078, 0x00}, - {0x3079, 0x30}, - {0x5E54, 0x0C}, - {0x6E44, 0x00}, - {0xB0B6, 0x01}, - {0xE829, 0x00}, - {0xF001, 0x08}, - {0xF003, 0x08}, - {0xF00D, 0x10}, - {0xF00F, 0x10}, - {0xF031, 0x08}, - {0xF033, 0x08}, - {0xF03D, 0x10}, - {0xF03F, 0x10}, - {0x0112, 0x0A}, - {0x0113, 0x0A}, - {0x0114, 0x01}, - {0x0B8E, 0x01}, - {0x0B8F, 0x00}, - {0x0B94, 0x01}, - {0x0B95, 0x00}, - {0x3400, 0x01}, - {0x3478, 0x01}, - {0x3479, 0x1c}, - {0x3091, 0x01}, - {0x3092, 0x00}, - {0x3419, 0x00}, - {0xBCF1, 0x02}, - {0x3094, 0x01}, - {0x3095, 0x01}, - {0x3362, 0x00}, - {0x3363, 0x00}, - {0x3364, 0x00}, - {0x3365, 0x00}, - {0x0138, 0x01}, -}; - -/* 10-bit. */ -static const struct imx708_reg mode_4608x2592_regs[] = { - {0x0342, 0x3D}, - {0x0343, 0x20}, - {0x0340, 0x0A}, - {0x0341, 0x59}, - {0x0344, 0x00}, - {0x0345, 0x00}, - {0x0346, 0x00}, - {0x0347, 0x00}, - {0x0348, 0x11}, - {0x0349, 0xFF}, - {0x034A, 0X0A}, - {0x034B, 0x1F}, - {0x0220, 0x62}, - {0x0222, 0x01}, - {0x0900, 0x00}, - {0x0901, 0x11}, - {0x0902, 0x0A}, - {0x3200, 0x01}, - {0x3201, 0x01}, - {0x32D5, 0x01}, - {0x32D6, 0x00}, - {0x32DB, 0x01}, - {0x32DF, 0x00}, - {0x350C, 0x00}, - {0x350D, 0x00}, - {0x0408, 0x00}, - {0x0409, 0x00}, - {0x040A, 0x00}, - {0x040B, 0x00}, - {0x040C, 0x12}, - {0x040D, 0x00}, - {0x040E, 0x0A}, - {0x040F, 0x20}, - {0x034C, 0x12}, - {0x034D, 0x00}, - {0x034E, 0x0A}, - {0x034F, 0x20}, - {0x0301, 0x05}, - {0x0303, 0x02}, - {0x0305, 0x02}, - {0x0306, 0x00}, - {0x0307, 0x7C}, - {0x030B, 0x02}, - {0x030D, 0x04}, - {0x0310, 0x01}, - {0x3CA0, 0x00}, - {0x3CA1, 0x64}, - {0x3CA4, 0x00}, - {0x3CA5, 0x00}, - {0x3CA6, 0x00}, - {0x3CA7, 0x00}, - {0x3CAA, 0x00}, - {0x3CAB, 0x00}, - {0x3CB8, 0x00}, - {0x3CB9, 0x08}, - {0x3CBA, 0x00}, - {0x3CBB, 0x00}, - {0x3CBC, 0x00}, - {0x3CBD, 0x3C}, - {0x3CBE, 0x00}, - {0x3CBF, 0x00}, - {0x0202, 0x0A}, - {0x0203, 0x29}, - {0x0224, 0x01}, - {0x0225, 0xF4}, - {0x3116, 0x01}, - {0x3117, 0xF4}, - {0x0204, 0x00}, - {0x0205, 0x00}, - {0x0216, 0x00}, - {0x0217, 0x00}, - {0x0218, 0x01}, - {0x0219, 0x00}, - {0x020E, 0x01}, - {0x020F, 0x00}, - {0x3118, 0x00}, - {0x3119, 0x00}, - {0x311A, 0x01}, - {0x311B, 0x00}, - {0x341a, 0x00}, - {0x341b, 0x00}, - {0x341c, 0x00}, - {0x341d, 0x00}, - {0x341e, 0x01}, - {0x341f, 0x20}, - {0x3420, 0x00}, - {0x3421, 0xd8}, - {0x3366, 0x00}, - {0x3367, 0x00}, - {0x3368, 0x00}, - {0x3369, 0x00}, -}; - -static const struct imx708_reg mode_2x2binned_regs[] = { - {0x0342, 0x1E}, - {0x0343, 0x90}, - {0x0340, 0x05}, - {0x0341, 0x38}, - {0x0344, 0x00}, - {0x0345, 0x00}, - {0x0346, 0x00}, - {0x0347, 0x00}, - {0x0348, 0x11}, - {0x0349, 0xFF}, - {0x034A, 0X0A}, - {0x034B, 0x1F}, - {0x0220, 0x62}, - {0x0222, 0x01}, - {0x0900, 0x01}, - {0x0901, 0x22}, - {0x0902, 0x08}, - {0x3200, 0x41}, - {0x3201, 0x41}, - {0x32D5, 0x00}, - {0x32D6, 0x00}, - {0x32DB, 0x01}, - {0x32DF, 0x00}, - {0x350C, 0x00}, - {0x350D, 0x00}, - {0x0408, 0x00}, - {0x0409, 0x00}, - {0x040A, 0x00}, - {0x040B, 0x00}, - {0x040C, 0x09}, - {0x040D, 0x00}, - {0x040E, 0x05}, - {0x040F, 0x10}, - {0x034C, 0x09}, - {0x034D, 0x00}, - {0x034E, 0x05}, - {0x034F, 0x10}, - {0x0301, 0x05}, - {0x0303, 0x02}, - {0x0305, 0x02}, - {0x0306, 0x00}, - {0x0307, 0x7A}, - {0x030B, 0x02}, - {0x030D, 0x04}, - {0x0310, 0x01}, - {0x3CA0, 0x00}, - {0x3CA1, 0x3C}, - {0x3CA4, 0x00}, - {0x3CA5, 0x3C}, - {0x3CA6, 0x00}, - {0x3CA7, 0x00}, - {0x3CAA, 0x00}, - {0x3CAB, 0x00}, - {0x3CB8, 0x00}, - {0x3CB9, 0x1C}, - {0x3CBA, 0x00}, - {0x3CBB, 0x08}, - {0x3CBC, 0x00}, - {0x3CBD, 0x1E}, - {0x3CBE, 0x00}, - {0x3CBF, 0x0A}, - {0x0202, 0x05}, - {0x0203, 0x08}, - {0x0224, 0x01}, - {0x0225, 0xF4}, - {0x3116, 0x01}, - {0x3117, 0xF4}, - {0x0204, 0x00}, - {0x0205, 0x70}, - {0x0216, 0x00}, - {0x0217, 0x70}, - {0x0218, 0x01}, - {0x0219, 0x00}, - {0x020E, 0x01}, - {0x020F, 0x00}, - {0x3118, 0x00}, - {0x3119, 0x70}, - {0x311A, 0x01}, - {0x311B, 0x00}, - {0x341a, 0x00}, - {0x341b, 0x00}, - {0x341c, 0x00}, - {0x341d, 0x00}, - {0x341e, 0x00}, - {0x341f, 0x90}, - {0x3420, 0x00}, - {0x3421, 0x6c}, - {0x3366, 0x00}, - {0x3367, 0x00}, - {0x3368, 0x00}, - {0x3369, 0x00}, -}; - -static const struct imx708_reg mode_2x2binned_720p_regs[] = { - {0x0342, 0x14}, - {0x0343, 0x60}, - {0x0340, 0x04}, - {0x0341, 0xB6}, - {0x0344, 0x03}, - {0x0345, 0x00}, - {0x0346, 0x01}, - {0x0347, 0xB0}, - {0x0348, 0x0E}, - {0x0349, 0xFF}, - {0x034A, 0x08}, - {0x034B, 0x6F}, - {0x0220, 0x62}, - {0x0222, 0x01}, - {0x0900, 0x01}, - {0x0901, 0x22}, - {0x0902, 0x08}, - {0x3200, 0x41}, - {0x3201, 0x41}, - {0x32D5, 0x00}, - {0x32D6, 0x00}, - {0x32DB, 0x01}, - {0x32DF, 0x01}, - {0x350C, 0x00}, - {0x350D, 0x00}, - {0x0408, 0x00}, - {0x0409, 0x00}, - {0x040A, 0x00}, - {0x040B, 0x00}, - {0x040C, 0x06}, - {0x040D, 0x00}, - {0x040E, 0x03}, - {0x040F, 0x60}, - {0x034C, 0x06}, - {0x034D, 0x00}, - {0x034E, 0x03}, - {0x034F, 0x60}, - {0x0301, 0x05}, - {0x0303, 0x02}, - {0x0305, 0x02}, - {0x0306, 0x00}, - {0x0307, 0x76}, - {0x030B, 0x02}, - {0x030D, 0x04}, - {0x0310, 0x01}, - {0x3CA0, 0x00}, - {0x3CA1, 0x3C}, - {0x3CA4, 0x01}, - {0x3CA5, 0x5E}, - {0x3CA6, 0x00}, - {0x3CA7, 0x00}, - {0x3CAA, 0x00}, - {0x3CAB, 0x00}, - {0x3CB8, 0x00}, - {0x3CB9, 0x0C}, - {0x3CBA, 0x00}, - {0x3CBB, 0x04}, - {0x3CBC, 0x00}, - {0x3CBD, 0x1E}, - {0x3CBE, 0x00}, - {0x3CBF, 0x05}, - {0x0202, 0x04}, - {0x0203, 0x86}, - {0x0224, 0x01}, - {0x0225, 0xF4}, - {0x3116, 0x01}, - {0x3117, 0xF4}, - {0x0204, 0x00}, - {0x0205, 0x70}, - {0x0216, 0x00}, - {0x0217, 0x70}, - {0x0218, 0x01}, - {0x0219, 0x00}, - {0x020E, 0x01}, - {0x020F, 0x00}, - {0x3118, 0x00}, - {0x3119, 0x70}, - {0x311A, 0x01}, - {0x311B, 0x00}, - {0x341a, 0x00}, - {0x341b, 0x00}, - {0x341c, 0x00}, - {0x341d, 0x00}, - {0x341e, 0x00}, - {0x341f, 0x60}, - {0x3420, 0x00}, - {0x3421, 0x48}, - {0x3366, 0x00}, - {0x3367, 0x00}, - {0x3368, 0x00}, - {0x3369, 0x00}, -}; - -static const struct imx708_reg mode_hdr_regs[] = { - {0x0342, 0x14}, - {0x0343, 0x60}, - {0x0340, 0x0A}, - {0x0341, 0x5B}, - {0x0344, 0x00}, - {0x0345, 0x00}, - {0x0346, 0x00}, - {0x0347, 0x00}, - {0x0348, 0x11}, - {0x0349, 0xFF}, - {0x034A, 0X0A}, - {0x034B, 0x1F}, - {0x0220, 0x01}, - {0x0222, IMX708_HDR_EXPOSURE_RATIO}, - {0x0900, 0x00}, - {0x0901, 0x11}, - {0x0902, 0x0A}, - {0x3200, 0x01}, - {0x3201, 0x01}, - {0x32D5, 0x00}, - {0x32D6, 0x00}, - {0x32DB, 0x01}, - {0x32DF, 0x00}, - {0x350C, 0x00}, - {0x350D, 0x00}, - {0x0408, 0x00}, - {0x0409, 0x00}, - {0x040A, 0x00}, - {0x040B, 0x00}, - {0x040C, 0x09}, - {0x040D, 0x00}, - {0x040E, 0x05}, - {0x040F, 0x10}, - {0x034C, 0x09}, - {0x034D, 0x00}, - {0x034E, 0x05}, - {0x034F, 0x10}, - {0x0301, 0x05}, - {0x0303, 0x02}, - {0x0305, 0x02}, - {0x0306, 0x00}, - {0x0307, 0xA2}, - {0x030B, 0x02}, - {0x030D, 0x04}, - {0x0310, 0x01}, - {0x3CA0, 0x00}, - {0x3CA1, 0x00}, - {0x3CA4, 0x00}, - {0x3CA5, 0x00}, - {0x3CA6, 0x00}, - {0x3CA7, 0x28}, - {0x3CAA, 0x00}, - {0x3CAB, 0x00}, - {0x3CB8, 0x00}, - {0x3CB9, 0x30}, - {0x3CBA, 0x00}, - {0x3CBB, 0x00}, - {0x3CBC, 0x00}, - {0x3CBD, 0x32}, - {0x3CBE, 0x00}, - {0x3CBF, 0x00}, - {0x0202, 0x0A}, - {0x0203, 0x2B}, - {0x0224, 0x0A}, - {0x0225, 0x2B}, - {0x3116, 0x0A}, - {0x3117, 0x2B}, - {0x0204, 0x00}, - {0x0205, 0x00}, - {0x0216, 0x00}, - {0x0217, 0x00}, - {0x0218, 0x01}, - {0x0219, 0x00}, - {0x020E, 0x01}, - {0x020F, 0x00}, - {0x3118, 0x00}, - {0x3119, 0x00}, - {0x311A, 0x01}, - {0x311B, 0x00}, - {0x341a, 0x00}, - {0x341b, 0x00}, - {0x341c, 0x00}, - {0x341d, 0x00}, - {0x341e, 0x00}, - {0x341f, 0x90}, - {0x3420, 0x00}, - {0x3421, 0x6c}, - {0x3360, 0x01}, - {0x3361, 0x01}, - {0x3366, 0x09}, - {0x3367, 0x00}, - {0x3368, 0x05}, - {0x3369, 0x10}, -}; - -/* Mode configs. Keep separate lists for when HDR is enabled or not. */ -static const struct imx708_mode supported_modes_10bit_no_hdr[] = { - { - /* Full resolution. */ - .width = 4608, - .height = 2592, - .line_length_pix = 0x3d20, - .crop = { - .left = IMX708_PIXEL_ARRAY_LEFT, - .top = IMX708_PIXEL_ARRAY_TOP, - .width = 4608, - .height = 2592, - }, - .vblank_min = 58, - .vblank_default = 58, - .reg_list = { - .num_of_regs = ARRAY_SIZE(mode_4608x2592_regs), - .regs = mode_4608x2592_regs, - }, - .pixel_rate = 595200000, - .exposure_lines_min = 8, - .exposure_lines_step = 1, - .hdr = false, - .remosaic = true - }, - { - /* regular 2x2 binned. */ - .width = 2304, - .height = 1296, - .line_length_pix = 0x1e90, - .crop = { - .left = IMX708_PIXEL_ARRAY_LEFT, - .top = IMX708_PIXEL_ARRAY_TOP, - .width = 4608, - .height = 2592, - }, - .vblank_min = 40, - .vblank_default = 1198, - .reg_list = { - .num_of_regs = ARRAY_SIZE(mode_2x2binned_regs), - .regs = mode_2x2binned_regs, - }, - .pixel_rate = 585600000, - .exposure_lines_min = 4, - .exposure_lines_step = 2, - .hdr = false, - .remosaic = false - }, - { - /* 2x2 binned and cropped for 720p. */ - .width = 1536, - .height = 864, - .line_length_pix = 0x1460, - .crop = { - .left = IMX708_PIXEL_ARRAY_LEFT + 768, - .top = IMX708_PIXEL_ARRAY_TOP + 432, - .width = 3072, - .height = 1728, - }, - .vblank_min = 40, - .vblank_default = 2755, - .reg_list = { - .num_of_regs = ARRAY_SIZE(mode_2x2binned_720p_regs), - .regs = mode_2x2binned_720p_regs, - }, - .pixel_rate = 566400000, - .exposure_lines_min = 4, - .exposure_lines_step = 2, - .hdr = false, - .remosaic = false - }, -}; - -static const struct imx708_mode supported_modes_10bit_hdr[] = { - { - /* There's only one HDR mode, which is 2x2 downscaled */ - .width = 2304, - .height = 1296, - .line_length_pix = 0x1460, - .crop = { - .left = IMX708_PIXEL_ARRAY_LEFT, - .top = IMX708_PIXEL_ARRAY_TOP, - .width = 4608, - .height = 2592, - }, - .vblank_min = 3673, - .vblank_default = 3673, - .reg_list = { - .num_of_regs = ARRAY_SIZE(mode_hdr_regs), - .regs = mode_hdr_regs, - }, - .pixel_rate = 777600000, - .exposure_lines_min = 8 * IMX708_HDR_EXPOSURE_RATIO * IMX708_HDR_EXPOSURE_RATIO, - .exposure_lines_step = 2 * IMX708_HDR_EXPOSURE_RATIO * IMX708_HDR_EXPOSURE_RATIO, - .hdr = true, - .remosaic = false - } -}; - -/* - * The supported formats. - * This table MUST contain 4 entries per format, to cover the various flip - * combinations in the order - * - no flip - * - h flip - * - v flip - * - h&v flips - */ -static const u32 codes[] = { - /* 10-bit modes. */ - MEDIA_BUS_FMT_SRGGB10_1X10, - MEDIA_BUS_FMT_SGRBG10_1X10, - MEDIA_BUS_FMT_SGBRG10_1X10, - MEDIA_BUS_FMT_SBGGR10_1X10, -}; - -static const char * const imx708_test_pattern_menu[] = { - "Disabled", - "Color Bars", - "Solid Color", - "Grey Color Bars", - "PN9" -}; - -static const int imx708_test_pattern_val[] = { - IMX708_TEST_PATTERN_DISABLE, - IMX708_TEST_PATTERN_COLOR_BARS, - IMX708_TEST_PATTERN_SOLID_COLOR, - IMX708_TEST_PATTERN_GREY_COLOR, - IMX708_TEST_PATTERN_PN9, -}; - -/* regulator supplies */ -static const char * const imx708_supply_name[] = { - /* Supplies can be enabled in any order */ - "vana1", /* Analog1 (2.8V) supply */ - "vana2", /* Analog2 (1.8V) supply */ - "vdig", /* Digital Core (1.1V) supply */ - "vddl", /* IF (1.8V) supply */ -}; - -/* - * Initialisation delay between XCLR low->high and the moment when the sensor - * can start capture (i.e. can leave software standby), given by T7 in the - * datasheet is 8ms. This does include I2C setup time as well. - * - * Note, that delay between XCLR low->high and reading the CCI ID register (T6 - * in the datasheet) is much smaller - 600us. - */ -#define IMX708_XCLR_MIN_DELAY_US 8000 -#define IMX708_XCLR_DELAY_RANGE_US 1000 - -struct imx708 { - struct v4l2_subdev sd; - struct media_pad pad[NUM_PADS]; - - struct v4l2_mbus_framefmt fmt; - - struct clk *inclk; - u32 inclk_freq; - - struct gpio_desc *reset_gpio; - struct regulator_bulk_data supplies[ARRAY_SIZE(imx708_supply_name)]; - - struct v4l2_ctrl_handler ctrl_handler; - /* V4L2 Controls */ - struct v4l2_ctrl *pixel_rate; - struct v4l2_ctrl *exposure; - struct v4l2_ctrl *vblank; - struct v4l2_ctrl *hblank; - struct v4l2_ctrl *hdr_mode; - struct v4l2_ctrl *link_freq; - struct { - struct v4l2_ctrl *hflip; - struct v4l2_ctrl *vflip; - }; - - /* Current mode */ - const struct imx708_mode *mode; - - /* - * Mutex for serialized access: - * Protect sensor module set pad format and start/stop streaming safely. - */ - struct mutex mutex; - - /* Streaming on/off */ - bool streaming; - - /* Rewrite common registers on stream on? */ - bool common_regs_written; - - /* Current long exposure factor in use. Set through V4L2_CID_VBLANK */ - unsigned int long_exp_shift; - - unsigned int link_freq_idx; - - unsigned int csi_flags; -}; - -static inline struct imx708 *to_imx708(struct v4l2_subdev *_sd) -{ - return container_of(_sd, struct imx708, sd); -} - -static inline void get_mode_table(unsigned int code, - const struct imx708_mode **mode_list, - unsigned int *num_modes, - bool hdr_enable) -{ - switch (code) { - /* 10-bit */ - case MEDIA_BUS_FMT_SRGGB10_1X10: - case MEDIA_BUS_FMT_SGRBG10_1X10: - case MEDIA_BUS_FMT_SGBRG10_1X10: - case MEDIA_BUS_FMT_SBGGR10_1X10: - if (hdr_enable) { - *mode_list = supported_modes_10bit_hdr; - *num_modes = ARRAY_SIZE(supported_modes_10bit_hdr); - } else { - *mode_list = supported_modes_10bit_no_hdr; - *num_modes = ARRAY_SIZE(supported_modes_10bit_no_hdr); - } - break; - default: - *mode_list = NULL; - *num_modes = 0; - } -} - -/* Read registers up to 2 at a time */ -static int imx708_read_reg(struct imx708 *imx708, u16 reg, u32 len, u32 *val) -{ - struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); - struct i2c_msg msgs[2]; - u8 addr_buf[2] = { reg >> 8, reg & 0xff }; - u8 data_buf[4] = { 0, }; - int ret; - - if (len > 4) - return -EINVAL; - - /* 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[4 - len]; - - ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); - if (ret != ARRAY_SIZE(msgs)) - return -EIO; - - *val = get_unaligned_be32(data_buf); - - return 0; -} - -/* Write registers up to 2 at a time */ -static int imx708_write_reg(struct imx708 *imx708, u16 reg, u32 len, u32 val) -{ - struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); - u8 buf[6]; - - if (len > 4) - return -EINVAL; - - put_unaligned_be16(reg, buf); - put_unaligned_be32(val << (8 * (4 - len)), buf + 2); - if (i2c_master_send(client, buf, len + 2) != len + 2) - return -EIO; - - return 0; -} - -/* Write a list of registers */ -static int imx708_write_regs(struct imx708 *imx708, - const struct imx708_reg *regs, u32 len) -{ - struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); - unsigned int i; - - for (i = 0; i < len; i++) { - int ret; - - ret = imx708_write_reg(imx708, regs[i].address, 1, regs[i].val); - if (ret) { - dev_err_ratelimited(&client->dev, - "Failed to write reg 0x%4.4x. error = %d\n", - regs[i].address, ret); - - return ret; - } - } - - return 0; -} - -/* Get bayer order based on flip setting. */ -static u32 imx708_get_format_code(struct imx708 *imx708) -{ - unsigned int i; - - lockdep_assert_held(&imx708->mutex); - - i = (imx708->vflip->val ? 2 : 0) | - (imx708->hflip->val ? 1 : 0); - - return codes[i]; -} - -static void imx708_set_default_format(struct imx708 *imx708) -{ - struct v4l2_mbus_framefmt *fmt = &imx708->fmt; - - /* Set default mode to max resolution */ - imx708->mode = &supported_modes_10bit_no_hdr[0]; - - /* fmt->code not set as it will always be computed based on flips */ - fmt->colorspace = V4L2_COLORSPACE_RAW; - fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace); - fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true, - fmt->colorspace, - fmt->ycbcr_enc); - fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace); - fmt->width = imx708->mode->width; - fmt->height = imx708->mode->height; - fmt->field = V4L2_FIELD_NONE; -} - -static int imx708_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) -{ - struct imx708 *imx708 = to_imx708(sd); - struct v4l2_mbus_framefmt *try_fmt_img = - v4l2_subdev_state_get_format(fh->state, IMAGE_PAD); - struct v4l2_mbus_framefmt *try_fmt_meta = - v4l2_subdev_state_get_format(fh->state, METADATA_PAD); - struct v4l2_rect *try_crop; - - mutex_lock(&imx708->mutex); - - /* Initialize try_fmt for the image pad */ - if (imx708->hdr_mode->val) { - try_fmt_img->width = supported_modes_10bit_hdr[0].width; - try_fmt_img->height = supported_modes_10bit_hdr[0].height; - } else { - try_fmt_img->width = supported_modes_10bit_no_hdr[0].width; - try_fmt_img->height = supported_modes_10bit_no_hdr[0].height; - } - try_fmt_img->code = imx708_get_format_code(imx708); - try_fmt_img->field = V4L2_FIELD_NONE; - - /* Initialize try_fmt for the embedded metadata pad */ - try_fmt_meta->width = IMX708_EMBEDDED_LINE_WIDTH; - try_fmt_meta->height = IMX708_NUM_EMBEDDED_LINES; - try_fmt_meta->code = MEDIA_BUS_FMT_SENSOR_DATA; - try_fmt_meta->field = V4L2_FIELD_NONE; - - /* Initialize try_crop */ - try_crop = v4l2_subdev_state_get_crop(fh->state, IMAGE_PAD); - try_crop->left = IMX708_PIXEL_ARRAY_LEFT; - try_crop->top = IMX708_PIXEL_ARRAY_TOP; - try_crop->width = IMX708_PIXEL_ARRAY_WIDTH; - try_crop->height = IMX708_PIXEL_ARRAY_HEIGHT; - - mutex_unlock(&imx708->mutex); - - return 0; -} - -static int imx708_set_exposure(struct imx708 *imx708, unsigned int val) -{ - val = max(val, imx708->mode->exposure_lines_min); - val -= val % imx708->mode->exposure_lines_step; - - /* - * In HDR mode this will set the longest exposure. The sensor - * will automatically divide the medium and short ones by 4,16. - */ - return imx708_write_reg(imx708, IMX708_REG_EXPOSURE, - IMX708_REG_VALUE_16BIT, - val >> imx708->long_exp_shift); -} - -static void imx708_adjust_exposure_range(struct imx708 *imx708, - struct v4l2_ctrl *ctrl) -{ - int exposure_max, exposure_def; - - /* Honour the VBLANK limits when setting exposure. */ - exposure_max = imx708->mode->height + imx708->vblank->val - - IMX708_EXPOSURE_OFFSET; - exposure_def = min(exposure_max, imx708->exposure->val); - __v4l2_ctrl_modify_range(imx708->exposure, imx708->exposure->minimum, - exposure_max, imx708->exposure->step, - exposure_def); -} - -static int imx708_set_analogue_gain(struct imx708 *imx708, unsigned int val) -{ - int ret; - - /* - * In HDR mode this will set the gain for the longest exposure, - * and by default the sensor uses the same gain for all of them. - */ - ret = imx708_write_reg(imx708, IMX708_REG_ANALOG_GAIN, - IMX708_REG_VALUE_16BIT, val); - - return ret; -} - -static int imx708_set_frame_length(struct imx708 *imx708, unsigned int val) -{ - int ret; - - imx708->long_exp_shift = 0; - - while (val > IMX708_FRAME_LENGTH_MAX) { - imx708->long_exp_shift++; - val >>= 1; - } - - ret = imx708_write_reg(imx708, IMX708_REG_FRAME_LENGTH, - IMX708_REG_VALUE_16BIT, val); - if (ret) - return ret; - - return imx708_write_reg(imx708, IMX708_LONG_EXP_SHIFT_REG, - IMX708_REG_VALUE_08BIT, imx708->long_exp_shift); -} - -static void imx708_set_framing_limits(struct imx708 *imx708) -{ - const struct imx708_mode *mode = imx708->mode; - unsigned int hblank; - - __v4l2_ctrl_modify_range(imx708->pixel_rate, - mode->pixel_rate, mode->pixel_rate, - 1, mode->pixel_rate); - - /* Update limits and set FPS to default */ - __v4l2_ctrl_modify_range(imx708->vblank, mode->vblank_min, - ((1 << IMX708_LONG_EXP_SHIFT_MAX) * - IMX708_FRAME_LENGTH_MAX) - mode->height, - 1, mode->vblank_default); - - /* - * Currently PPL is fixed to the mode specified value, so hblank - * depends on mode->width only, and is not changeable in any - * way other than changing the mode. - */ - hblank = mode->line_length_pix - mode->width; - __v4l2_ctrl_modify_range(imx708->hblank, hblank, hblank, 1, hblank); -} - -static int imx708_set_ctrl(struct v4l2_ctrl *ctrl) -{ - struct imx708 *imx708 = - container_of(ctrl->handler, struct imx708, ctrl_handler); - struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); - const struct imx708_mode *mode_list; - unsigned int code, num_modes; - int ret = 0; - - switch (ctrl->id) { - case V4L2_CID_VBLANK: - /* - * The VBLANK control may change the limits of usable exposure, - * so check and adjust if necessary. - */ - imx708_adjust_exposure_range(imx708, ctrl); - break; - - case V4L2_CID_WIDE_DYNAMIC_RANGE: - /* - * The WIDE_DYNAMIC_RANGE control can also be applied immediately - * as it doesn't set any registers. Don't do anything if the mode - * already matches. - */ - if (imx708->mode && imx708->mode->hdr != ctrl->val) { - code = imx708_get_format_code(imx708); - get_mode_table(code, &mode_list, &num_modes, ctrl->val); - imx708->mode = v4l2_find_nearest_size(mode_list, - num_modes, - width, height, - imx708->mode->width, - imx708->mode->height); - imx708_set_framing_limits(imx708); - } - break; - } - - /* - * Applying V4L2 control value only happens - * when power is up for streaming - */ - if (pm_runtime_get_if_in_use(&client->dev) == 0) - return 0; - - switch (ctrl->id) { - case V4L2_CID_ANALOGUE_GAIN: - imx708_set_analogue_gain(imx708, ctrl->val); - break; - case V4L2_CID_EXPOSURE: - ret = imx708_set_exposure(imx708, ctrl->val); - break; - case V4L2_CID_DIGITAL_GAIN: - ret = imx708_write_reg(imx708, IMX708_REG_DIGITAL_GAIN, - IMX708_REG_VALUE_16BIT, ctrl->val); - break; - case V4L2_CID_TEST_PATTERN: - ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN, - IMX708_REG_VALUE_16BIT, - imx708_test_pattern_val[ctrl->val]); - break; - case V4L2_CID_TEST_PATTERN_RED: - ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN_R, - IMX708_REG_VALUE_16BIT, ctrl->val); - break; - case V4L2_CID_TEST_PATTERN_GREENR: - ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN_GR, - IMX708_REG_VALUE_16BIT, ctrl->val); - break; - case V4L2_CID_TEST_PATTERN_BLUE: - ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN_B, - IMX708_REG_VALUE_16BIT, ctrl->val); - break; - case V4L2_CID_TEST_PATTERN_GREENB: - ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN_GB, - IMX708_REG_VALUE_16BIT, ctrl->val); - break; - case V4L2_CID_HFLIP: - case V4L2_CID_VFLIP: - ret = imx708_write_reg(imx708, IMX708_REG_ORIENTATION, 1, - imx708->hflip->val | - imx708->vflip->val << 1); - break; - case V4L2_CID_VBLANK: - ret = imx708_set_frame_length(imx708, - imx708->mode->height + ctrl->val); - break; - case V4L2_CID_NOTIFY_GAINS: - ret = imx708_write_reg(imx708, IMX708_REG_COLOUR_BALANCE_BLUE, - IMX708_REG_VALUE_16BIT, - ctrl->p_new.p_u32[0]); - if (ret) - break; - ret = imx708_write_reg(imx708, IMX708_REG_COLOUR_BALANCE_RED, - IMX708_REG_VALUE_16BIT, - ctrl->p_new.p_u32[3]); - break; - case V4L2_CID_WIDE_DYNAMIC_RANGE: - /* Already handled above. */ - break; - default: - dev_info(&client->dev, - "ctrl(id:0x%x,val:0x%x) is not handled\n", - ctrl->id, ctrl->val); - ret = -EINVAL; - break; - } - - pm_runtime_mark_last_busy(&client->dev); - pm_runtime_put_autosuspend(&client->dev); - - return ret; -} - -static const struct v4l2_ctrl_ops imx708_ctrl_ops = { - .s_ctrl = imx708_set_ctrl, -}; - -static int imx708_enum_mbus_code(struct v4l2_subdev *sd, - struct v4l2_subdev_state *sd_state, - struct v4l2_subdev_mbus_code_enum *code) -{ - struct imx708 *imx708 = to_imx708(sd); - - if (code->pad >= NUM_PADS) - return -EINVAL; - - if (code->pad == IMAGE_PAD) { - if (code->index >= (ARRAY_SIZE(codes) / 4)) - return -EINVAL; - - mutex_lock(&imx708->mutex); - code->code = imx708_get_format_code(imx708); - mutex_unlock(&imx708->mutex); - } else { - if (code->index > 0) - return -EINVAL; - - code->code = MEDIA_BUS_FMT_SENSOR_DATA; - } - - return 0; -} - -static int imx708_enum_frame_size(struct v4l2_subdev *sd, - struct v4l2_subdev_state *sd_state, - struct v4l2_subdev_frame_size_enum *fse) -{ - struct imx708 *imx708 = to_imx708(sd); - u32 code; - - if (fse->pad >= NUM_PADS) - return -EINVAL; - - if (fse->pad == IMAGE_PAD) { - const struct imx708_mode *mode_list; - unsigned int num_modes; - - get_mode_table(fse->code, &mode_list, &num_modes, - imx708->hdr_mode->val); - - if (fse->index >= num_modes) - return -EINVAL; - - mutex_lock(&imx708->mutex); - code = imx708_get_format_code(imx708); - mutex_unlock(&imx708->mutex); - - if (fse->code != code) - return -EINVAL; - - fse->min_width = mode_list[fse->index].width; - fse->max_width = fse->min_width; - fse->min_height = mode_list[fse->index].height; - fse->max_height = fse->min_height; - } else { - if (fse->code != MEDIA_BUS_FMT_SENSOR_DATA || fse->index > 0) - return -EINVAL; - - fse->min_width = IMX708_EMBEDDED_LINE_WIDTH; - fse->max_width = fse->min_width; - fse->min_height = IMX708_NUM_EMBEDDED_LINES; - fse->max_height = fse->min_height; - } - - return 0; -} - -static void imx708_reset_colorspace(struct v4l2_mbus_framefmt *fmt) -{ - fmt->colorspace = V4L2_COLORSPACE_RAW; - fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace); - fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true, - fmt->colorspace, - fmt->ycbcr_enc); - fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace); -} - -static void imx708_update_image_pad_format(struct imx708 *imx708, - const struct imx708_mode *mode, - struct v4l2_subdev_format *fmt) -{ - fmt->format.width = mode->width; - fmt->format.height = mode->height; - fmt->format.field = V4L2_FIELD_NONE; - imx708_reset_colorspace(&fmt->format); -} - -static void imx708_update_metadata_pad_format(struct v4l2_subdev_format *fmt) -{ - fmt->format.width = IMX708_EMBEDDED_LINE_WIDTH; - fmt->format.height = IMX708_NUM_EMBEDDED_LINES; - fmt->format.code = MEDIA_BUS_FMT_SENSOR_DATA; - fmt->format.field = V4L2_FIELD_NONE; -} - -static int imx708_get_pad_format(struct v4l2_subdev *sd, - struct v4l2_subdev_state *sd_state, - struct v4l2_subdev_format *fmt) -{ - struct imx708 *imx708 = to_imx708(sd); - - if (fmt->pad >= NUM_PADS) - return -EINVAL; - - mutex_lock(&imx708->mutex); - - if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { - struct v4l2_mbus_framefmt *try_fmt = - v4l2_subdev_state_get_format(sd_state, - fmt->pad); - /* update the code which could change due to vflip or hflip */ - try_fmt->code = fmt->pad == IMAGE_PAD ? - imx708_get_format_code(imx708) : - MEDIA_BUS_FMT_SENSOR_DATA; - fmt->format = *try_fmt; - } else { - if (fmt->pad == IMAGE_PAD) { - imx708_update_image_pad_format(imx708, imx708->mode, - fmt); - fmt->format.code = imx708_get_format_code(imx708); - } else { - imx708_update_metadata_pad_format(fmt); - } - } - - mutex_unlock(&imx708->mutex); - return 0; -} - -static int imx708_set_pad_format(struct v4l2_subdev *sd, - struct v4l2_subdev_state *sd_state, - struct v4l2_subdev_format *fmt) -{ - struct v4l2_mbus_framefmt *framefmt; - const struct imx708_mode *mode; - struct imx708 *imx708 = to_imx708(sd); - - if (fmt->pad >= NUM_PADS) - return -EINVAL; - - mutex_lock(&imx708->mutex); - - if (fmt->pad == IMAGE_PAD) { - const struct imx708_mode *mode_list; - unsigned int num_modes; - - /* Bayer order varies with flips */ - fmt->format.code = imx708_get_format_code(imx708); - - get_mode_table(fmt->format.code, &mode_list, &num_modes, - imx708->hdr_mode->val); - - mode = v4l2_find_nearest_size(mode_list, - num_modes, - width, height, - fmt->format.width, - fmt->format.height); - imx708_update_image_pad_format(imx708, mode, fmt); - if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { - framefmt = v4l2_subdev_state_get_format(sd_state, - fmt->pad); - *framefmt = fmt->format; - } else { - imx708->mode = mode; - imx708_set_framing_limits(imx708); - } - } else { - if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { - framefmt = v4l2_subdev_state_get_format(sd_state, - fmt->pad); - *framefmt = fmt->format; - } else { - /* Only one embedded data mode is supported */ - imx708_update_metadata_pad_format(fmt); - } - } - - mutex_unlock(&imx708->mutex); - - return 0; -} - -static const struct v4l2_rect * -__imx708_get_pad_crop(struct imx708 *imx708, struct v4l2_subdev_state *sd_state, - unsigned int pad, enum v4l2_subdev_format_whence which) -{ - switch (which) { - case V4L2_SUBDEV_FORMAT_TRY: - return v4l2_subdev_state_get_crop(sd_state, pad); - case V4L2_SUBDEV_FORMAT_ACTIVE: - return &imx708->mode->crop; - } - - return NULL; -} - -static int imx708_get_selection(struct v4l2_subdev *sd, - struct v4l2_subdev_state *sd_state, - struct v4l2_subdev_selection *sel) -{ - switch (sel->target) { - case V4L2_SEL_TGT_CROP: { - struct imx708 *imx708 = to_imx708(sd); - - mutex_lock(&imx708->mutex); - sel->r = *__imx708_get_pad_crop(imx708, sd_state, sel->pad, - sel->which); - mutex_unlock(&imx708->mutex); - - return 0; - } - - case V4L2_SEL_TGT_NATIVE_SIZE: - sel->r.left = 0; - sel->r.top = 0; - sel->r.width = IMX708_NATIVE_WIDTH; - sel->r.height = IMX708_NATIVE_HEIGHT; - - return 0; - - case V4L2_SEL_TGT_CROP_DEFAULT: - case V4L2_SEL_TGT_CROP_BOUNDS: - sel->r.left = IMX708_PIXEL_ARRAY_LEFT; - sel->r.top = IMX708_PIXEL_ARRAY_TOP; - sel->r.width = IMX708_PIXEL_ARRAY_WIDTH; - sel->r.height = IMX708_PIXEL_ARRAY_HEIGHT; - - return 0; - } - - return -EINVAL; -} - -/* Start streaming */ -static int imx708_start_streaming(struct imx708 *imx708) -{ - struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); - const struct imx708_reg_list *reg_list, *freq_regs; - int i, ret; - u32 val; - - if (!imx708->common_regs_written) { - ret = imx708_write_regs(imx708, mode_common_regs, - ARRAY_SIZE(mode_common_regs)); - if (ret) { - dev_err(&client->dev, "%s failed to set common settings\n", - __func__); - return ret; - } - - ret = imx708_write_reg(imx708, IMX708_REG_CLKLANE_BLANK, - IMX708_REG_VALUE_08BIT, - imx708->csi_flags & V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK ? - IMX708_CLKLANE_BLANK_NONCONT : 0); - if (ret) { - dev_err(&client->dev, "%s failed to set clock lane mode\n", - __func__); - return ret; - } - - ret = imx708_read_reg(imx708, IMX708_REG_BASE_SPC_GAINS_L, - IMX708_REG_VALUE_08BIT, &val); - if (ret == 0 && val == 0x40) { - for (i = 0; i < 54 && ret == 0; i++) { - ret = imx708_write_reg(imx708, - IMX708_REG_BASE_SPC_GAINS_L + i, - IMX708_REG_VALUE_08BIT, - pdaf_gains[0][i % 9]); - } - for (i = 0; i < 54 && ret == 0; i++) { - ret = imx708_write_reg(imx708, - IMX708_REG_BASE_SPC_GAINS_R + i, - IMX708_REG_VALUE_08BIT, - pdaf_gains[1][i % 9]); - } - } - if (ret) { - dev_err(&client->dev, "%s failed to set PDAF gains\n", - __func__); - return ret; - } - - imx708->common_regs_written = true; - } - - /* Apply default values of current mode */ - reg_list = &imx708->mode->reg_list; - ret = imx708_write_regs(imx708, reg_list->regs, reg_list->num_of_regs); - if (ret) { - dev_err(&client->dev, "%s failed to set mode\n", __func__); - return ret; - } - - /* Update the link frequency registers */ - freq_regs = &link_freq_regs[imx708->link_freq_idx]; - ret = imx708_write_regs(imx708, freq_regs->regs, - freq_regs->num_of_regs); - if (ret) { - dev_err(&client->dev, "%s failed to set link frequency registers\n", - __func__); - return ret; - } - - /* Quad Bayer re-mosaic adjustments (for full-resolution mode only) */ - if (imx708->mode->remosaic && qbc_adjust > 0) { - imx708_write_reg(imx708, IMX708_LPF_INTENSITY, - IMX708_REG_VALUE_08BIT, qbc_adjust); - imx708_write_reg(imx708, - IMX708_LPF_INTENSITY_EN, - IMX708_REG_VALUE_08BIT, - IMX708_LPF_INTENSITY_ENABLED); - } else { - imx708_write_reg(imx708, - IMX708_LPF_INTENSITY_EN, - IMX708_REG_VALUE_08BIT, - IMX708_LPF_INTENSITY_DISABLED); - } - - /* Apply customized values from user */ - ret = __v4l2_ctrl_handler_setup(imx708->sd.ctrl_handler); - if (ret) - return ret; - - /* set stream on register */ - return imx708_write_reg(imx708, IMX708_REG_MODE_SELECT, - IMX708_REG_VALUE_08BIT, IMX708_MODE_STREAMING); -} - -/* Stop streaming */ -static void imx708_stop_streaming(struct imx708 *imx708) -{ - struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); - int ret; - - /* set stream off register */ - ret = imx708_write_reg(imx708, IMX708_REG_MODE_SELECT, - IMX708_REG_VALUE_08BIT, IMX708_MODE_STANDBY); - if (ret) - dev_err(&client->dev, "%s failed to set stream\n", __func__); -} - -static int imx708_set_stream(struct v4l2_subdev *sd, int enable) -{ - struct imx708 *imx708 = to_imx708(sd); - struct i2c_client *client = v4l2_get_subdevdata(sd); - int ret = 0; - - mutex_lock(&imx708->mutex); - if (imx708->streaming == enable) { - mutex_unlock(&imx708->mutex); - return 0; - } - - if (enable) { - ret = pm_runtime_resume_and_get(&client->dev); - if (ret < 0) - goto err_unlock; - - /* - * Apply default & customized values - * and then start streaming. - */ - ret = imx708_start_streaming(imx708); - if (ret) { - pm_runtime_put_sync(&client->dev); - goto err_unlock; - } - } else { - imx708_stop_streaming(imx708); - pm_runtime_mark_last_busy(&client->dev); - pm_runtime_put_autosuspend(&client->dev); - } - - imx708->streaming = enable; - - /* vflip/hflip and hdr mode cannot change during streaming */ - __v4l2_ctrl_grab(imx708->vflip, enable); - __v4l2_ctrl_grab(imx708->hflip, enable); - __v4l2_ctrl_grab(imx708->hdr_mode, enable); - - mutex_unlock(&imx708->mutex); - - return ret; - -err_unlock: - mutex_unlock(&imx708->mutex); - - return ret; -} - -/* Power/clock management functions */ -static int imx708_power_on(struct device *dev) -{ - struct i2c_client *client = to_i2c_client(dev); - struct v4l2_subdev *sd = i2c_get_clientdata(client); - struct imx708 *imx708 = to_imx708(sd); - int ret; - - ret = regulator_bulk_enable(ARRAY_SIZE(imx708_supply_name), - imx708->supplies); - if (ret) { - dev_err(&client->dev, "%s: failed to enable regulators\n", - __func__); - return ret; - } - - ret = clk_prepare_enable(imx708->inclk); - if (ret) { - dev_err(&client->dev, "%s: failed to enable clock\n", - __func__); - goto reg_off; - } - - gpiod_set_value_cansleep(imx708->reset_gpio, 1); - usleep_range(IMX708_XCLR_MIN_DELAY_US, - IMX708_XCLR_MIN_DELAY_US + IMX708_XCLR_DELAY_RANGE_US); - - return 0; - -reg_off: - regulator_bulk_disable(ARRAY_SIZE(imx708_supply_name), - imx708->supplies); - return ret; -} - -static int imx708_power_off(struct device *dev) -{ - struct i2c_client *client = to_i2c_client(dev); - struct v4l2_subdev *sd = i2c_get_clientdata(client); - struct imx708 *imx708 = to_imx708(sd); - - gpiod_set_value_cansleep(imx708->reset_gpio, 0); - regulator_bulk_disable(ARRAY_SIZE(imx708_supply_name), - imx708->supplies); - clk_disable_unprepare(imx708->inclk); - - /* Force reprogramming of the common registers when powered up again. */ - imx708->common_regs_written = false; - - return 0; -} - -static int __maybe_unused imx708_suspend(struct device *dev) -{ - struct i2c_client *client = to_i2c_client(dev); - struct v4l2_subdev *sd = i2c_get_clientdata(client); - struct imx708 *imx708 = to_imx708(sd); - - if (imx708->streaming) - imx708_stop_streaming(imx708); - - return 0; -} - -static int __maybe_unused imx708_resume(struct device *dev) -{ - struct i2c_client *client = to_i2c_client(dev); - struct v4l2_subdev *sd = i2c_get_clientdata(client); - struct imx708 *imx708 = to_imx708(sd); - int ret; - - if (imx708->streaming) { - ret = imx708_start_streaming(imx708); - if (ret) - goto error; - } - - return 0; - -error: - imx708_stop_streaming(imx708); - imx708->streaming = 0; - return ret; -} - -static int imx708_get_regulators(struct imx708 *imx708) -{ - struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(imx708_supply_name); i++) - imx708->supplies[i].supply = imx708_supply_name[i]; - - return devm_regulator_bulk_get(&client->dev, - ARRAY_SIZE(imx708_supply_name), - imx708->supplies); -} - -/* Verify chip ID */ -static int imx708_identify_module(struct imx708 *imx708) -{ - struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); - int ret; - u32 val; - - ret = imx708_read_reg(imx708, IMX708_REG_CHIP_ID, - IMX708_REG_VALUE_16BIT, &val); - if (ret) { - dev_err(&client->dev, "failed to read chip id %x, with error %d\n", - IMX708_CHIP_ID, ret); - return ret; - } - - if (val != IMX708_CHIP_ID) { - dev_err(&client->dev, "chip id mismatch: %x!=%x\n", - IMX708_CHIP_ID, val); - return -EIO; - } - - ret = imx708_read_reg(imx708, 0x0000, IMX708_REG_VALUE_16BIT, &val); - if (!ret) { - dev_info(&client->dev, "camera module ID 0x%04x\n", val); - snprintf(imx708->sd.name, sizeof(imx708->sd.name), "imx708%s%s", - val & 0x02 ? "_wide" : "", - val & 0x80 ? "_noir" : ""); - } - - return 0; -} - -static const struct v4l2_subdev_core_ops imx708_core_ops = { - .subscribe_event = v4l2_ctrl_subdev_subscribe_event, - .unsubscribe_event = v4l2_event_subdev_unsubscribe, -}; - -static const struct v4l2_subdev_video_ops imx708_video_ops = { - .s_stream = imx708_set_stream, -}; - -static const struct v4l2_subdev_pad_ops imx708_pad_ops = { - .enum_mbus_code = imx708_enum_mbus_code, - .get_fmt = imx708_get_pad_format, - .set_fmt = imx708_set_pad_format, - .get_selection = imx708_get_selection, - .enum_frame_size = imx708_enum_frame_size, -}; - -static const struct v4l2_subdev_ops imx708_subdev_ops = { - .core = &imx708_core_ops, - .video = &imx708_video_ops, - .pad = &imx708_pad_ops, -}; - -static const struct v4l2_subdev_internal_ops imx708_internal_ops = { - .open = imx708_open, -}; - -static const struct v4l2_ctrl_config imx708_notify_gains_ctrl = { - .ops = &imx708_ctrl_ops, - .id = V4L2_CID_NOTIFY_GAINS, - .type = V4L2_CTRL_TYPE_U32, - .min = IMX708_COLOUR_BALANCE_MIN, - .max = IMX708_COLOUR_BALANCE_MAX, - .step = IMX708_COLOUR_BALANCE_STEP, - .def = IMX708_COLOUR_BALANCE_DEFAULT, - .dims = { 4 }, - .elem_size = sizeof(u32), -}; - -/* Initialize control handlers */ -static int imx708_init_controls(struct imx708 *imx708) -{ - struct v4l2_ctrl_handler *ctrl_hdlr; - struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); - struct v4l2_fwnode_device_properties props; - struct v4l2_ctrl *ctrl; - unsigned int i; - int ret; - - ctrl_hdlr = &imx708->ctrl_handler; - ret = v4l2_ctrl_handler_init(ctrl_hdlr, 16); - if (ret) - return ret; - - mutex_init(&imx708->mutex); - ctrl_hdlr->lock = &imx708->mutex; - - /* By default, PIXEL_RATE is read only */ - imx708->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, - V4L2_CID_PIXEL_RATE, - IMX708_INITIAL_PIXEL_RATE, - IMX708_INITIAL_PIXEL_RATE, 1, - IMX708_INITIAL_PIXEL_RATE); - - ctrl = v4l2_ctrl_new_int_menu(ctrl_hdlr, &imx708_ctrl_ops, - V4L2_CID_LINK_FREQ, 0, 0, - &link_freqs[imx708->link_freq_idx]); - if (ctrl) - ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; - - /* - * Create the controls here, but mode specific limits are setup - * in the imx708_set_framing_limits() call below. - */ - imx708->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, - V4L2_CID_VBLANK, 0, 0xffff, 1, 0); - imx708->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, - V4L2_CID_HBLANK, 0, 0xffff, 1, 0); - - imx708->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, - V4L2_CID_EXPOSURE, - IMX708_EXPOSURE_MIN, - IMX708_EXPOSURE_MAX, - IMX708_EXPOSURE_STEP, - IMX708_EXPOSURE_DEFAULT); - - v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, - IMX708_ANA_GAIN_MIN, IMX708_ANA_GAIN_MAX, - IMX708_ANA_GAIN_STEP, IMX708_ANA_GAIN_DEFAULT); - - v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, V4L2_CID_DIGITAL_GAIN, - IMX708_DGTL_GAIN_MIN, IMX708_DGTL_GAIN_MAX, - IMX708_DGTL_GAIN_STEP, IMX708_DGTL_GAIN_DEFAULT); - - imx708->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, - V4L2_CID_HFLIP, 0, 1, 1, 0); - - imx708->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, - V4L2_CID_VFLIP, 0, 1, 1, 0); - v4l2_ctrl_cluster(2, &imx708->hflip); - - v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &imx708_ctrl_ops, - V4L2_CID_TEST_PATTERN, - ARRAY_SIZE(imx708_test_pattern_menu) - 1, - 0, 0, imx708_test_pattern_menu); - for (i = 0; i < 4; i++) { - /* - * The assumption is that - * V4L2_CID_TEST_PATTERN_GREENR == V4L2_CID_TEST_PATTERN_RED + 1 - * V4L2_CID_TEST_PATTERN_BLUE == V4L2_CID_TEST_PATTERN_RED + 2 - * V4L2_CID_TEST_PATTERN_GREENB == V4L2_CID_TEST_PATTERN_RED + 3 - */ - v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, - V4L2_CID_TEST_PATTERN_RED + i, - IMX708_TEST_PATTERN_COLOUR_MIN, - IMX708_TEST_PATTERN_COLOUR_MAX, - IMX708_TEST_PATTERN_COLOUR_STEP, - IMX708_TEST_PATTERN_COLOUR_MAX); - /* The "Solid color" pattern is white by default */ - } - - v4l2_ctrl_new_custom(ctrl_hdlr, &imx708_notify_gains_ctrl, NULL); - - imx708->hdr_mode = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, - V4L2_CID_WIDE_DYNAMIC_RANGE, - 0, 1, 1, 0); - - ret = v4l2_fwnode_device_parse(&client->dev, &props); - if (ret) - goto error; - - v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &imx708_ctrl_ops, &props); - - if (ctrl_hdlr->error) { - ret = ctrl_hdlr->error; - dev_err(&client->dev, "%s control init failed (%d)\n", - __func__, ret); - goto error; - } - - imx708->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; - imx708->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; - imx708->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; - imx708->hdr_mode->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; - - imx708->sd.ctrl_handler = ctrl_hdlr; - - /* Setup exposure and frame/line length limits. */ - mutex_lock(&imx708->mutex); - imx708_set_framing_limits(imx708); - mutex_unlock(&imx708->mutex); - - return 0; - -error: - v4l2_ctrl_handler_free(ctrl_hdlr); - mutex_destroy(&imx708->mutex); - - return ret; -} - -static void imx708_free_controls(struct imx708 *imx708) -{ - v4l2_ctrl_handler_free(imx708->sd.ctrl_handler); - mutex_destroy(&imx708->mutex); -} - -static int imx708_check_hwcfg(struct device *dev, struct imx708 *imx708) -{ - struct fwnode_handle *endpoint; - struct v4l2_fwnode_endpoint ep_cfg = { - .bus_type = V4L2_MBUS_CSI2_DPHY - }; - int ret = -EINVAL; - int i; - - endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); - if (!endpoint) { - dev_err(dev, "endpoint node not found\n"); - return -EINVAL; - } - - if (v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep_cfg)) { - dev_err(dev, "could not parse endpoint\n"); - goto error_out; - } - - /* Check the number of MIPI CSI2 data lanes */ - if (ep_cfg.bus.mipi_csi2.num_data_lanes != 2) { - dev_err(dev, "only 2 data lanes are currently supported\n"); - goto error_out; - } - - /* Check the link frequency set in device tree */ - if (!ep_cfg.nr_of_link_frequencies) { - dev_err(dev, "link-frequency property not found in DT\n"); - goto error_out; - } - - for (i = 0; i < ARRAY_SIZE(link_freqs); i++) { - if (link_freqs[i] == ep_cfg.link_frequencies[0]) { - imx708->link_freq_idx = i; - break; - } - } - - if (i == ARRAY_SIZE(link_freqs)) { - dev_err(dev, "Link frequency not supported: %lld\n", - ep_cfg.link_frequencies[0]); - ret = -EINVAL; - goto error_out; - } - - imx708->csi_flags = ep_cfg.bus.mipi_csi2.flags; - - ret = 0; - -error_out: - v4l2_fwnode_endpoint_free(&ep_cfg); - fwnode_handle_put(endpoint); - - return ret; -} - -static int imx708_probe(struct i2c_client *client) -{ - struct device *dev = &client->dev; - struct imx708 *imx708; - int ret; - - imx708 = devm_kzalloc(&client->dev, sizeof(*imx708), GFP_KERNEL); - if (!imx708) - return -ENOMEM; - - v4l2_i2c_subdev_init(&imx708->sd, client, &imx708_subdev_ops); - - /* Check the hardware configuration in device tree */ - if (imx708_check_hwcfg(dev, imx708)) - return -EINVAL; - - /* Get system clock (inclk) */ - imx708->inclk = devm_clk_get(dev, "inclk"); - if (IS_ERR(imx708->inclk)) - return dev_err_probe(dev, PTR_ERR(imx708->inclk), - "failed to get inclk\n"); - - imx708->inclk_freq = clk_get_rate(imx708->inclk); - if (imx708->inclk_freq != IMX708_INCLK_FREQ) - return dev_err_probe(dev, -EINVAL, - "inclk frequency not supported: %d Hz\n", - imx708->inclk_freq); - - ret = imx708_get_regulators(imx708); - if (ret) - return dev_err_probe(dev, ret, "failed to get regulators\n"); - - /* Request optional enable pin */ - imx708->reset_gpio = devm_gpiod_get_optional(dev, "reset", - GPIOD_OUT_HIGH); - - /* - * The sensor must be powered for imx708_identify_module() - * to be able to read the CHIP_ID register - */ - ret = imx708_power_on(dev); - if (ret) - return ret; - - ret = imx708_identify_module(imx708); - if (ret) - goto error_power_off; - - /* Initialize default format */ - imx708_set_default_format(imx708); - - /* - * Enable runtime PM with autosuspend. As the device has been powered - * manually, mark it as active, and increase the usage count without - * resuming the device. - */ - pm_runtime_set_active(dev); - pm_runtime_get_noresume(dev); - pm_runtime_enable(dev); - pm_runtime_set_autosuspend_delay(dev, 5000); - pm_runtime_use_autosuspend(dev); - - /* This needs the pm runtime to be registered. */ - ret = imx708_init_controls(imx708); - if (ret) - goto error_pm_runtime; - - /* Initialize subdev */ - imx708->sd.internal_ops = &imx708_internal_ops; - imx708->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | - V4L2_SUBDEV_FL_HAS_EVENTS; - imx708->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; - - /* Initialize source pads */ - imx708->pad[IMAGE_PAD].flags = MEDIA_PAD_FL_SOURCE; - imx708->pad[METADATA_PAD].flags = MEDIA_PAD_FL_SOURCE; - - ret = media_entity_pads_init(&imx708->sd.entity, NUM_PADS, imx708->pad); - if (ret) { - dev_err(dev, "failed to init entity pads: %d\n", ret); - goto error_handler_free; - } - - ret = v4l2_async_register_subdev_sensor(&imx708->sd); - if (ret < 0) { - dev_err(dev, "failed to register sensor sub-device: %d\n", ret); - goto error_media_entity; - } - - pm_runtime_mark_last_busy(dev); - pm_runtime_put_autosuspend(dev); - - return 0; - -error_media_entity: - media_entity_cleanup(&imx708->sd.entity); - -error_handler_free: - imx708_free_controls(imx708); - -error_pm_runtime: - pm_runtime_disable(dev); - pm_runtime_put_noidle(dev); - -error_power_off: - imx708_power_off(&client->dev); - - return ret; -} - -static void imx708_remove(struct i2c_client *client) -{ - struct v4l2_subdev *sd = i2c_get_clientdata(client); - struct imx708 *imx708 = to_imx708(sd); - - v4l2_async_unregister_subdev(sd); - media_entity_cleanup(&sd->entity); - imx708_free_controls(imx708); - - pm_runtime_disable(&client->dev); - if (!pm_runtime_status_suspended(&client->dev)) - imx708_power_off(&client->dev); - pm_runtime_set_suspended(&client->dev); -} - -static const struct of_device_id imx708_dt_ids[] = { - { .compatible = "sony,imx708" }, - { /* sentinel */ } -}; -MODULE_DEVICE_TABLE(of, imx708_dt_ids); - -static const struct dev_pm_ops imx708_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(imx708_suspend, imx708_resume) - SET_RUNTIME_PM_OPS(imx708_power_off, imx708_power_on, NULL) -}; - -static struct i2c_driver imx708_i2c_driver = { - .driver = { - .name = "imx708", - .of_match_table = imx708_dt_ids, - .pm = &imx708_pm_ops, - }, - .probe = imx708_probe, - .remove = imx708_remove, -}; - -module_i2c_driver(imx708_i2c_driver); - -MODULE_AUTHOR("David Plowman "); -MODULE_DESCRIPTION("Sony IMX708 sensor driver"); -MODULE_LICENSE("GPL v2"); From 80adc2dfef4ad9536c8f5502cbb0d129cc10e639 Mon Sep 17 00:00:00 2001 From: Jai Luthra Date: Wed, 15 Jul 2026 23:01:28 +0530 Subject: [PATCH 2/3] dt-bindings: media: i2c: Add imx708 image sensor Sony IMX708 is an 11.9 Megapixel CMOS sensor, that can output pixels over MIPI CSI-2 bus. Add bindings for it. Signed-off-by: Jai Luthra Reviewed-by: Krzysztof Kozlowski --- .../bindings/media/i2c/sony,imx708.yaml | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/i2c/sony,imx708.yaml diff --git a/Documentation/devicetree/bindings/media/i2c/sony,imx708.yaml b/Documentation/devicetree/bindings/media/i2c/sony,imx708.yaml new file mode 100644 index 00000000000000..bc346730e1f316 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/sony,imx708.yaml @@ -0,0 +1,120 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/sony,imx708.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Sony IMX708 Sensor + +maintainers: + - Raspberry Pi Kernel Maintenance + - Jai Luthra + +description: |- + The Sony IMX708 is a 1/2.3-inch CMOS active pixel digital image sensor + with an active array size of 4608H x 2592V. It is programmable through + I2C interface. The I2C address is fixed to 0x1A as per sensor data sheet. + Image data is sent through MIPI CSI-2, which is configured as either 2 or + 4 data lanes. + +properties: + compatible: + const: sony,imx708 + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + vdig-supply: + description: + Digital I/O voltage supply, 1.1 volts + + vana1-supply: + description: + Analog1 voltage supply, 2.8 volts + + vana2-supply: + description: + Analog2 voltage supply, 1.8 volts + + vddl-supply: + description: + Digital core voltage supply, 1.8 volts + + reset-gpios: + description: Sensor RESETN (XCLR) GPIO + maxItems: 1 + + port: + $ref: /schemas/graph.yaml#/$defs/port-base + additionalProperties: false + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + data-lanes: + oneOf: + - items: + - const: 1 + - const: 2 + - items: + - const: 1 + - const: 2 + - const: 3 + - const: 4 + + required: + - data-lanes + - link-frequencies + + required: + - endpoint + +required: + - compatible + - reg + - clocks + - vdig-supply + - vana1-supply + - vana2-supply + - vddl-supply + - port + +additionalProperties: false + +examples: + - | + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + camera@1a { + compatible = "sony,imx708"; + reg = <0x1a>; + + clocks = <&clk 90>; + + vdig-supply = <&camera_vdig>; + vana1-supply = <&camera_vana1>; + vana2-supply = <&camera_vana2>; + vddl-supply = <&camera_vddl>; + + reset-gpios = <&gpio 35 GPIO_ACTIVE_LOW>; + + port { + imx708_ep: endpoint { + data-lanes = <1 2>; + link-frequencies = /bits/ 64 <450000000>; + remote-endpoint = <&csi_ep>; + }; + }; + }; + }; +... From e1085fef691ed8fcd5901795241549b00dc09505 Mon Sep 17 00:00:00 2001 From: Jai Luthra Date: Wed, 15 Jul 2026 23:01:29 +0530 Subject: [PATCH 3/3] media: i2c: Add a driver for Sony IMX708 Sony IMX708 is an 11.9 Megapixel CMOS sensor with a 16:9 aspect ratio. It is a quad-bayer sensor with support for remosaicing the output to a normal 2x2 bayer pattern. This basic driver adds supports for capturing the full active-area of 4608x2592, up to 14.35fps. The following features are supported by the driver: - MIPI RAW10 output - Fixed quad-bayer correction - Multiple link frequencies (different IOP_PLL_MPY) - VBLANK control for flexible frame-rate - Analogue gain and exposure control - Extremely long exposure time with LONG_EXP_SHIFT - Test pattern support The following features are currently not supported and may be added later. Some of them depend on the new common raw sensor model: - Embedded data stream - Freely configurable crop (analog and digital) - 2x2 binning - Quad-bayer multi-exposure HDR support - HBLANK control - 4-lane support - Configurable clock-tree - Flexible quad-bayer correction Co-developed-by: Nick Hollinghurst Signed-off-by: Nick Hollinghurst Signed-off-by: Jai Luthra --- MAINTAINERS | 8 + drivers/media/i2c/Kconfig | 11 + drivers/media/i2c/imx708.c | 1255 ++++++++++++++++++++++++++++++++++++ 3 files changed, 1274 insertions(+) create mode 100644 drivers/media/i2c/imx708.c diff --git a/MAINTAINERS b/MAINTAINERS index abb7f5e4ebbdd6..b36a204e2cf89f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -25340,6 +25340,14 @@ T: git git://linuxtv.org/media_tree.git F: Documentation/devicetree/bindings/media/i2c/sony,imx708.yaml F: drivers/media/i2c/imx708.c +SONY IMX708 SENSOR DRIVER +M: Raspberry Pi Kernel Maintenance +M: Jai Luthra +L: linux-media@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/media/i2c/sony,imx708.yaml +F: drivers/media/i2c/imx708.c + SONY MEMORYSTICK SUBSYSTEM M: Maxim Levitsky M: Alex Dubov diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 32f0c5793e2ab3..989b6a7d304202 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -360,6 +360,17 @@ config VIDEO_IMX708 To compile this driver as a module, choose M here: the module will be called imx708. +config VIDEO_IMX708 + tristate "Sony IMX708 sensor support" + depends on GPIOLIB + select V4L2_CCI_I2C + help + This is a Video4Linux2 sensor driver for the Sony + IMX708 camera. + + To compile this driver as a module, choose M here: the + module will be called imx708. + config VIDEO_MAX9271_LIB tristate diff --git a/drivers/media/i2c/imx708.c b/drivers/media/i2c/imx708.c new file mode 100644 index 00000000000000..052474befc5e48 --- /dev/null +++ b/drivers/media/i2c/imx708.c @@ -0,0 +1,1255 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A V4L2 driver for Sony IMX708 + * + * Copyright (C) 2026 Ideas on Board Oy + * Copyright (C) 2022 Raspberry Pi Ltd + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Chip ID */ +#define IMX708_REG_CHIP_ID CCI_REG16(0x0016) +#define IMX708_CHIP_ID 0x0708 + +#define IMX708_REG_MODE_SELECT CCI_REG8(0x0100) +#define IMX708_MODE_STANDBY 0x00 +#define IMX708_MODE_STREAMING 0x01 + +#define IMX708_REG_ORIENTATION CCI_REG8(0x0101) + +#define IMX708_INCLK_FREQ 24000000 + +/* Pixel rate */ +#define IMX708_PIXEL_RATE 595200000 + +/* PLL */ +#define IMX708_REG_IVT_PXCK_DIV CCI_REG8(0x0301) +#define IMX708_REG_IVT_SYCK_DIV CCI_REG8(0x0303) +#define IMX708_REG_IVT_PREPLLCK_DIV CCI_REG8(0x0305) +#define IMX708_REG_IVT_PLL_MPY CCI_REG16(0x0306) +#define IMX708_REG_IOP_SYCK_DIV CCI_REG8(0x030b) +#define IMX708_REG_IOP_PREPLLCK_DIV CCI_REG8(0x030d) +#define IMX708_REG_IOP_PLL_MPY CCI_REG16(0x030e) +#define IMX708_REG_PLL_MULT_DRIV CCI_REG8(0x0310) + +/* V_TIMING internal */ +#define IMX708_REG_FRAME_LENGTH CCI_REG16(0x0340) +#define IMX708_FRAME_LENGTH_MAX 0xffff +#define IMX708_REG_LINE_LENGTH CCI_REG16(0x0342) +#define IMX708_LINE_LENGTH 15648 +#define IMX708_VBLANK_MIN 58 + +/* Imaging area */ +#define IMX708_REG_X_ADD_STA CCI_REG16(0x0344) +#define IMX708_REG_X_ADD_END CCI_REG16(0x0348) +#define IMX708_REG_Y_ADD_STA CCI_REG16(0x0346) +#define IMX708_REG_Y_ADD_END CCI_REG16(0x034a) +#define IMX708_REG_DIG_CROP_X_OFFSET CCI_REG16(0x0408) +#define IMX708_REG_DIG_CROP_Y_OFFSET CCI_REG16(0x040a) +#define IMX708_REG_DIG_CROP_WIDTH CCI_REG16(0x040c) +#define IMX708_REG_DIG_CROP_HEIGHT CCI_REG16(0x040e) +#define IMX708_REG_X_OUTPUT_SIZE CCI_REG16(0x034c) +#define IMX708_REG_Y_OUTPUT_SIZE CCI_REG16(0x034e) + +#define IMX708_REG_ACROPLP_EN CCI_REG8(0x32df) +#define IMX708_REG_BINNING_MODE CCI_REG8(0x0900) +#define IMX708_REG_BINNING_TYPE CCI_REG8(0x0901) +#define IMX708_REG_BINNING_WEIGHT CCI_REG8(0x0902) +#define IMX708_REG_BINNING_PRIORITY_H CCI_REG8(0x3200) +#define IMX708_REG_BINNING_PRIORITY_V CCI_REG8(0x3201) + +/* Long exposure multiplier */ +#define IMX708_LONG_EXP_SHIFT_MAX 7 +#define IMX708_LONG_EXP_SHIFT_REG CCI_REG8(0x3100) + +/* Exposure control */ +#define IMX708_REG_EXPOSURE CCI_REG16(0x0202) +#define IMX708_EXPOSURE_OFFSET 48 +#define IMX708_EXPOSURE_DEFAULT 0x640 +#define IMX708_EXPOSURE_STEP 1 +#define IMX708_EXPOSURE_MIN 8 + +/* Analog gain control */ +#define IMX708_REG_ANALOG_GAIN CCI_REG16(0x0204) +#define IMX708_ANA_GAIN_MIN 0 +#define IMX708_ANA_GAIN_MAX 960 +#define IMX708_ANA_GAIN_STEP 1 +#define IMX708_ANA_GAIN_DEFAULT IMX708_ANA_GAIN_MIN + +/* Digital gain control */ +#define IMX708_REG_DIGITAL_GAIN CCI_REG16(0x020e) +#define IMX708_DGTL_GAIN_MIN 0x0100 +#define IMX708_DGTL_GAIN_MAX 0xffff +#define IMX708_DGTL_GAIN_DEFAULT 0x0100 +#define IMX708_DGTL_GAIN_STEP 1 + +/* Colour balance controls */ +#define IMX708_REG_COLOUR_BALANCE_RED CCI_REG16(0x0b90) +#define IMX708_REG_COLOUR_BALANCE_BLUE CCI_REG16(0x0b92) +#define IMX708_COLOUR_BALANCE_MIN 0x01 +#define IMX708_COLOUR_BALANCE_MAX 0xffff +#define IMX708_COLOUR_BALANCE_STEP 0x01 +#define IMX708_COLOUR_BALANCE_DEFAULT 0x100 + +/* Test Pattern Control */ +#define IMX708_REG_TEST_PATTERN CCI_REG16(0x0600) +#define IMX708_TEST_PATTERN_DISABLE 0 +#define IMX708_TEST_PATTERN_SOLID_COLOR 1 +#define IMX708_TEST_PATTERN_COLOR_BARS 2 +#define IMX708_TEST_PATTERN_GREY_COLOR 3 +#define IMX708_TEST_PATTERN_PN9 4 + +/* Test pattern colour components */ +#define IMX708_REG_TEST_PATTERN_R CCI_REG16(0x0602) +#define IMX708_REG_TEST_PATTERN_GR CCI_REG16(0x0604) +#define IMX708_REG_TEST_PATTERN_B CCI_REG16(0x0606) +#define IMX708_REG_TEST_PATTERN_GB CCI_REG16(0x0608) +#define IMX708_TEST_PATTERN_COLOUR_MIN 0 +#define IMX708_TEST_PATTERN_COLOUR_MAX 0x0fff +#define IMX708_TEST_PATTERN_COLOUR_STEP 1 + +#define IMX708_REG_BASE_SPC_GAINS_L CCI_REG8(0x7b10) +#define IMX708_REG_BASE_SPC_GAINS_R CCI_REG8(0x7c00) + +/* Middle and short exposure */ +#define IMX708_REG_MID_EXPOSURE CCI_REG16(0x3116) +#define IMX708_REG_SHT_EXPOSURE CCI_REG16(0x0224) +#define IMX708_REG_MID_ANALOG_GAIN CCI_REG16(0x3118) +#define IMX708_REG_MID_DIGITAL_GAIN CCI_REG16(0x311a) +#define IMX708_REG_SHT_ANALOG_GAIN CCI_REG16(0x0216) +#define IMX708_REG_SHT_DIGITAL_GAIN CCI_REG16(0x0218) + +#define IMX708_REG_CLKLANE_BLANK CCI_REG8(0x3220) +#define IMX708_CLKLANE_BLANK_NONCONT BIT(0) + +/* QBC Re-mosaic broken line correction registers */ +#define IMX708_REG_QBC_RMSC_EN CCI_REG8(0x32d5) +#define IMX708_REG_LPF_INTENSITY_EN CCI_REG8(0xc428) +#define IMX708_LPF_INTENSITY_ENABLED 0x00 +#define IMX708_LPF_INTENSITY_DISABLED 0x01 +#define IMX708_REG_LPF_INTENSITY CCI_REG8(0xc429) +#define IMX708_LPF_INTENSITY_DEFAULT 2 + +/* AE HIST */ +#define IMX708_REG_AEHIST_AUTO_THRESH CCI_REG16(0x3360) +#define IMX708_REG_AEHIST1_AREA_WIDTH CCI_REG16(0x3366) +#define IMX708_REG_AEHIST1_AREA_HEIGHT CCI_REG16(0x3368) + +enum pad_types { + IMX708_SOURCE_PAD, + IMX708_NUM_PADS +}; + +/* IMX708 native and active pixel array size. */ +static const struct v4l2_rect imx708_native_area = { + .top = 0, + .left = 0, + .width = 4640, + .height = 2658, +}; + +static const struct v4l2_rect imx708_active_area = { + .top = 24, + .left = 16, + .width = 4608, + .height = 2592, +}; + +struct imx708_reg_list { + unsigned int num_of_regs; + const struct cci_reg_sequence *regs; +}; + +/* Default PDAF pixel correction gains */ +static const u8 pdaf_gains[2][9] = { + { 0x4c, 0x4c, 0x4c, 0x46, 0x3e, 0x39, 0x36, 0x36, 0x36 }, + { 0x36, 0x36, 0x36, 0x39, 0x3e, 0x46, 0x4c, 0x4c, 0x4c } +}; + +/* Link frequency setup */ +enum { + IMX708_LINK_FREQ_450MHZ, + IMX708_LINK_FREQ_447MHZ, + IMX708_LINK_FREQ_453MHZ, +}; + +static const s64 link_freqs[] = { + [IMX708_LINK_FREQ_450MHZ] = 450000000, + [IMX708_LINK_FREQ_447MHZ] = 447000000, + [IMX708_LINK_FREQ_453MHZ] = 453000000, +}; + +/* 450MHz is the nominal "default" link frequency */ +static const struct cci_reg_sequence link_450Mhz_regs[] = { + { IMX708_REG_IOP_PLL_MPY, 0x012c }, +}; + +static const struct cci_reg_sequence link_447Mhz_regs[] = { + { IMX708_REG_IOP_PLL_MPY, 0x012a }, +}; + +static const struct cci_reg_sequence link_453Mhz_regs[] = { + { IMX708_REG_IOP_PLL_MPY, 0x012e }, +}; + +static const struct imx708_reg_list link_freq_regs[] = { + [IMX708_LINK_FREQ_450MHZ] = { + .regs = link_450Mhz_regs, + .num_of_regs = ARRAY_SIZE(link_450Mhz_regs) + }, + [IMX708_LINK_FREQ_447MHZ] = { + .regs = link_447Mhz_regs, + .num_of_regs = ARRAY_SIZE(link_447Mhz_regs) + }, + [IMX708_LINK_FREQ_453MHZ] = { + .regs = link_453Mhz_regs, + .num_of_regs = ARRAY_SIZE(link_453Mhz_regs) + }, +}; + +static const struct cci_reg_sequence imx708_common_regs[] = { + /* Common */ + { CCI_REG8(0x0100), 0x00 }, + { CCI_REG8(0x0136), 0x18 }, + { CCI_REG8(0x0137), 0x00 }, + { CCI_REG8(0x33f0), 0x02 }, + { CCI_REG8(0x33f1), 0x05 }, + { CCI_REG8(0x3062), 0x00 }, + { CCI_REG8(0x3063), 0x12 }, + { CCI_REG8(0x3068), 0x00 }, + { CCI_REG8(0x3069), 0x12 }, + { CCI_REG8(0x306a), 0x00 }, + { CCI_REG8(0x306b), 0x30 }, + { CCI_REG8(0x3076), 0x00 }, + { CCI_REG8(0x3077), 0x30 }, + { CCI_REG8(0x3078), 0x00 }, + { CCI_REG8(0x3079), 0x30 }, + { CCI_REG8(0x5e54), 0x0c }, + { CCI_REG8(0x6e44), 0x00 }, + { CCI_REG8(0xb0b6), 0x01 }, + { CCI_REG8(0xe829), 0x00 }, + { CCI_REG8(0xf001), 0x08 }, + { CCI_REG8(0xf003), 0x08 }, + { CCI_REG8(0xf00d), 0x10 }, + { CCI_REG8(0xf00f), 0x10 }, + { CCI_REG8(0xf031), 0x08 }, + { CCI_REG8(0xf033), 0x08 }, + { CCI_REG8(0xf03d), 0x10 }, + { CCI_REG8(0xf03f), 0x10 }, + { CCI_REG8(0x0112), 0x0a }, + { CCI_REG8(0x0113), 0x0a }, + { CCI_REG8(0x0114), 0x01 }, + { CCI_REG8(0x0b8e), 0x01 }, + { CCI_REG8(0x0b8f), 0x00 }, + { CCI_REG8(0x0b94), 0x01 }, + { CCI_REG8(0x0b95), 0x00 }, + { CCI_REG8(0x3400), 0x01 }, + { CCI_REG8(0x3478), 0x01 }, + { CCI_REG8(0x3479), 0x1c }, + { CCI_REG8(0x3091), 0x01 }, + { CCI_REG8(0x3092), 0x00 }, + { CCI_REG8(0x3419), 0x00 }, + { CCI_REG8(0xbcf1), 0x02 }, + { CCI_REG8(0x3094), 0x01 }, + { CCI_REG8(0x3095), 0x01 }, + { CCI_REG8(0x3362), 0x00 }, + { CCI_REG8(0x3363), 0x00 }, + { CCI_REG8(0x3364), 0x00 }, + { CCI_REG8(0x3365), 0x00 }, + { CCI_REG8(0x0138), 0x01 }, + /* PLL config */ + { IMX708_REG_IVT_PXCK_DIV, 0x05 }, + { IMX708_REG_IVT_SYCK_DIV, 0x02 }, + { IMX708_REG_IVT_PREPLLCK_DIV, 0x02 }, + { IMX708_REG_IVT_PLL_MPY, 0x007c }, + { IMX708_REG_IOP_SYCK_DIV, 0x02 }, + { IMX708_REG_IOP_PREPLLCK_DIV, 0x04 }, + { IMX708_REG_PLL_MULT_DRIV, 0x01 }, + /* non-HDR defaults */ + { CCI_REG8(0x0220), 0x62 }, + { CCI_REG8(0x0222), 0x01 }, + { CCI_REG8(0x350c), 0x00 }, + { CCI_REG8(0x350d), 0x00 }, + { IMX708_REG_SHT_EXPOSURE, 0x01f4 }, + { IMX708_REG_MID_EXPOSURE, 0x01f4 }, + { IMX708_REG_SHT_ANALOG_GAIN, 0x0000 }, + { IMX708_REG_SHT_DIGITAL_GAIN, 0x0100 }, + { IMX708_REG_MID_ANALOG_GAIN, 0x0000 }, + { IMX708_REG_MID_DIGITAL_GAIN, 0x0100 }, + { IMX708_REG_AEHIST1_AREA_WIDTH, 0x0000 }, + { IMX708_REG_AEHIST1_AREA_HEIGHT, 0x0000 }, + /* Quad-Bayer Compensation */ + { IMX708_REG_QBC_RMSC_EN, 0x01 }, + { IMX708_REG_LPF_INTENSITY, IMX708_LPF_INTENSITY_DEFAULT }, + { IMX708_REG_LPF_INTENSITY_EN, IMX708_LPF_INTENSITY_ENABLED }, + { CCI_REG8(0x32d6), 0x00 }, + { CCI_REG8(0x32db), 0x01 }, + /* Analogue crop disabled */ + { IMX708_REG_ACROPLP_EN, 0x00 }, + /* Unknown registers */ + { CCI_REG8(0x3ca0), 0x00 }, + { CCI_REG8(0x3ca1), 0x64 }, + { CCI_REG8(0x3ca4), 0x00 }, + { CCI_REG8(0x3ca5), 0x00 }, + { CCI_REG8(0x3ca6), 0x00 }, + { CCI_REG8(0x3ca7), 0x00 }, + { CCI_REG8(0x3caa), 0x00 }, + { CCI_REG8(0x3cab), 0x00 }, + { CCI_REG8(0x3cb8), 0x00 }, + { CCI_REG8(0x3cb9), 0x08 }, + { CCI_REG8(0x3cba), 0x00 }, + { CCI_REG8(0x3cbb), 0x00 }, + { CCI_REG8(0x3cbc), 0x00 }, + { CCI_REG8(0x3cbd), 0x3c }, + { CCI_REG8(0x3cbe), 0x00 }, + { CCI_REG8(0x3cbf), 0x00 }, + { CCI_REG8(0x341a), 0x00 }, + { CCI_REG8(0x341b), 0x00 }, + { CCI_REG8(0x341c), 0x00 }, + { CCI_REG8(0x341d), 0x00 }, + { CCI_REG8(0x341e), 0x01 }, + { CCI_REG8(0x341f), 0x20 }, + { CCI_REG8(0x3420), 0x00 }, + { CCI_REG8(0x3421), 0xd8 }, +}; + +/* + * The supported formats. + * This table MUST contain 4 entries per format, to cover the various flip + * combinations in the order + * - no flip + * - h flip + * - v flip + * - h&v flips + */ +static const u32 codes[] = { + /* 10-bit modes. */ + MEDIA_BUS_FMT_SRGGB10_1X10, + MEDIA_BUS_FMT_SGRBG10_1X10, + MEDIA_BUS_FMT_SGBRG10_1X10, + MEDIA_BUS_FMT_SBGGR10_1X10, +}; + +static const char * const imx708_test_pattern_menu[] = { + "Disabled", + "Color Bars", + "Solid Color", + "Grey Color Bars", + "PN9" +}; + +static const int imx708_test_pattern_val[] = { + IMX708_TEST_PATTERN_DISABLE, + IMX708_TEST_PATTERN_COLOR_BARS, + IMX708_TEST_PATTERN_SOLID_COLOR, + IMX708_TEST_PATTERN_GREY_COLOR, + IMX708_TEST_PATTERN_PN9, +}; + +/* regulator supplies */ +static const char * const imx708_supply_name[] = { + /* Supplies can be enabled in any order */ + "vana1", /* Analog1 (2.8V) supply */ + "vana2", /* Analog2 (1.8V) supply */ + "vdig", /* Digital Core (1.1V) supply */ + "vddl", /* IF (1.8V) supply */ +}; + +/* + * Initialisation delay between XCLR low->high and the moment when the sensor + * can start capture (i.e. can leave software standby), given by T7 in the + * datasheet is 8ms. This does include I2C setup time as well. + * + * Note, that delay between XCLR low->high and reading the CCI ID register (T6 + * in the datasheet) is much smaller - 600us. + */ +#define IMX708_XCLR_MIN_DELAY_US 8000 +#define IMX708_XCLR_DELAY_RANGE_US 1000 + +struct imx708 { + struct v4l2_subdev sd; + struct media_pad pad[IMX708_NUM_PADS]; + struct regmap *cci; + + struct clk *inclk; + u32 inclk_freq; + + struct gpio_desc *reset_gpio; + struct regulator_bulk_data supplies[ARRAY_SIZE(imx708_supply_name)]; + + struct v4l2_ctrl_handler ctrl_handler; + /* V4L2 Controls */ + struct v4l2_ctrl *exposure; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct { + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vflip; + }; + + /* Current long exposure factor in use. Set through V4L2_CID_VBLANK */ + unsigned int long_exp_shift; + + unsigned long link_freq_bitmap; + + unsigned int csi_flags; +}; + +static inline struct imx708 *to_imx708(struct v4l2_subdev *_sd) +{ + return container_of_const(_sd, struct imx708, sd); +} + +/* Get bayer order based on flip setting. */ +static u32 imx708_get_format_code(struct imx708 *imx708) +{ + unsigned int i; + + i = (imx708->vflip->val ? 2 : 0) | + (imx708->hflip->val ? 1 : 0); + + return codes[i]; +} + +static void imx708_adjust_exposure_range(struct imx708 *imx708) +{ + int exposure_max, exposure_def; + + /* Honour the VBLANK limits when setting exposure. */ + exposure_max = imx708_active_area.height + imx708->vblank->val - + IMX708_EXPOSURE_OFFSET; + exposure_def = min(exposure_max, imx708->exposure->val); + __v4l2_ctrl_modify_range(imx708->exposure, imx708->exposure->minimum, + exposure_max, imx708->exposure->step, + exposure_def); +} + +static int imx708_set_frame_length(struct imx708 *imx708, unsigned int val) +{ + int ret = 0; + + imx708->long_exp_shift = 0; + + while (val > IMX708_FRAME_LENGTH_MAX) { + imx708->long_exp_shift++; + val >>= 1; + } + + cci_write(imx708->cci, IMX708_REG_FRAME_LENGTH, val, &ret); + cci_write(imx708->cci, IMX708_LONG_EXP_SHIFT_REG, + imx708->long_exp_shift, &ret); + + return ret; +} + +static int imx708_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct imx708 *imx708 = + container_of_const(ctrl->handler, struct imx708, ctrl_handler); + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + int ret = 0; + + switch (ctrl->id) { + case V4L2_CID_VBLANK: + /* + * The VBLANK control may change the limits of usable exposure, + * so check and adjust if necessary. + */ + imx708_adjust_exposure_range(imx708); + break; + + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: { + struct v4l2_subdev_state *state = + v4l2_subdev_get_locked_active_state(&imx708->sd); + struct v4l2_mbus_framefmt *format; + + format = v4l2_subdev_state_get_format(state, IMX708_SOURCE_PAD); + format->code = imx708_get_format_code(imx708); + break; + } + } + + /* + * Only apply control values when device is powered on (RPM ACTIVE) + * and streaming (usage count != 0) + */ + if (!pm_runtime_get_if_in_use(&client->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_ANALOGUE_GAIN: + cci_write(imx708->cci, IMX708_REG_ANALOG_GAIN, ctrl->val, &ret); + break; + case V4L2_CID_VBLANK: + ret = imx708_set_frame_length(imx708, + imx708_active_area.height + + ctrl->val); + fallthrough; /* update exposure with new long_exp_shift */ + case V4L2_CID_EXPOSURE: + cci_write(imx708->cci, IMX708_REG_EXPOSURE, + imx708->exposure->val >> imx708->long_exp_shift, + &ret); + break; + case V4L2_CID_DIGITAL_GAIN: + cci_write(imx708->cci, IMX708_REG_DIGITAL_GAIN, ctrl->val, &ret); + break; + case V4L2_CID_TEST_PATTERN: + cci_write(imx708->cci, IMX708_REG_TEST_PATTERN, + imx708_test_pattern_val[ctrl->val], &ret); + break; + case V4L2_CID_TEST_PATTERN_RED: + cci_write(imx708->cci, IMX708_REG_TEST_PATTERN_R, + ctrl->val, &ret); + break; + case V4L2_CID_TEST_PATTERN_GREENR: + cci_write(imx708->cci, IMX708_REG_TEST_PATTERN_GR, + ctrl->val, &ret); + break; + case V4L2_CID_TEST_PATTERN_BLUE: + cci_write(imx708->cci, IMX708_REG_TEST_PATTERN_B, + ctrl->val, &ret); + break; + case V4L2_CID_TEST_PATTERN_GREENB: + cci_write(imx708->cci, IMX708_REG_TEST_PATTERN_GB, + ctrl->val, &ret); + break; + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + cci_write(imx708->cci, IMX708_REG_ORIENTATION, + imx708->hflip->val | imx708->vflip->val << 1, &ret); + break; + case V4L2_CID_NOTIFY_GAINS: + cci_write(imx708->cci, IMX708_REG_COLOUR_BALANCE_BLUE, + ctrl->p_new.p_u32[0], &ret); + if (ret) + break; + cci_write(imx708->cci, IMX708_REG_COLOUR_BALANCE_RED, + ctrl->p_new.p_u32[3], &ret); + break; + default: + dev_warn(&client->dev, + "ctrl(id:0x%x,val:0x%x) is not handled\n", + ctrl->id, ctrl->val); + ret = -EINVAL; + break; + } + + pm_runtime_put_autosuspend(&client->dev); + + return ret; +} + +static const struct v4l2_ctrl_ops imx708_ctrl_ops = { + .s_ctrl = imx708_set_ctrl, +}; + +static int imx708_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct imx708 *imx708 = to_imx708(sd); + + if (code->pad >= IMX708_NUM_PADS) + return -EINVAL; + + if (code->index >= (ARRAY_SIZE(codes) / 4)) + return -EINVAL; + + code->code = imx708_get_format_code(imx708); + + return 0; +} + +static int imx708_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct imx708 *imx708 = to_imx708(sd); + u32 code; + + if (fse->pad >= IMX708_NUM_PADS) + return -EINVAL; + + code = imx708_get_format_code(imx708); + + if (fse->code != code) + return -EINVAL; + + if (fse->index > 0) + return -EINVAL; + + fse->min_width = imx708_active_area.width; + fse->max_width = fse->min_width; + fse->min_height = imx708_active_area.height; + fse->max_height = fse->min_height; + + return 0; +} + +static int imx708_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct imx708 *imx708 = to_imx708(sd); + struct v4l2_mbus_framefmt *format; + struct v4l2_rect *crop; + + /* Initialize the image pad format. */ + format = v4l2_subdev_state_get_format(state, IMX708_SOURCE_PAD); + format->code = imx708_get_format_code(imx708); + format->width = imx708_active_area.width; + format->height = imx708_active_area.height; + format->field = V4L2_FIELD_NONE; + format->colorspace = V4L2_COLORSPACE_RAW; + format->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + format->quantization = V4L2_QUANTIZATION_FULL_RANGE; + format->xfer_func = V4L2_XFER_FUNC_NONE; + + /* Initialize the image pad crop. */ + crop = v4l2_subdev_state_get_crop(state, IMX708_SOURCE_PAD); + *crop = imx708_active_area; + + return 0; +} + +static int imx708_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + switch (sel->target) { + case V4L2_SEL_TGT_CROP: { + struct v4l2_rect *crop; + + crop = v4l2_subdev_state_get_crop(sd_state, sel->pad); + sel->r = *crop; + + return 0; + } + + case V4L2_SEL_TGT_NATIVE_SIZE: + sel->r = imx708_native_area; + + return 0; + + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r = imx708_active_area; + + return 0; + } + + return -EINVAL; +} + +static int imx708_program_window(struct imx708 *imx708, + struct v4l2_subdev_state *state) +{ + const struct v4l2_rect *crop; + const struct v4l2_mbus_framefmt *format; + int ret = 0; + s32 x_start, y_start; + + crop = v4l2_subdev_state_get_crop(state, IMX708_SOURCE_PAD); + format = v4l2_subdev_state_get_format(state, IMX708_SOURCE_PAD); + + /* Line length */ + cci_write(imx708->cci, IMX708_REG_LINE_LENGTH, IMX708_LINE_LENGTH, + &ret); + + /* Imaging area */ + x_start = crop->left - imx708_active_area.left; + y_start = crop->top - imx708_active_area.top; + cci_write(imx708->cci, IMX708_REG_X_ADD_STA, x_start, &ret); + cci_write(imx708->cci, IMX708_REG_Y_ADD_STA, y_start, &ret); + cci_write(imx708->cci, IMX708_REG_X_ADD_END, x_start + crop->width - 1, + &ret); + cci_write(imx708->cci, IMX708_REG_Y_ADD_END, y_start + crop->height - 1, + &ret); + + /* Binning (fixed: no binning) */ + cci_write(imx708->cci, IMX708_REG_BINNING_MODE, 0x00, &ret); + cci_write(imx708->cci, IMX708_REG_BINNING_TYPE, 0x11, &ret); + cci_write(imx708->cci, IMX708_REG_BINNING_WEIGHT, 0x0a, &ret); + cci_write(imx708->cci, IMX708_REG_BINNING_PRIORITY_H, 0x01, &ret); + cci_write(imx708->cci, IMX708_REG_BINNING_PRIORITY_V, 0x01, &ret); + + /* Digital crop (fixed: no crop) */ + cci_write(imx708->cci, IMX708_REG_DIG_CROP_X_OFFSET, 0, &ret); + cci_write(imx708->cci, IMX708_REG_DIG_CROP_Y_OFFSET, 0, &ret); + cci_write(imx708->cci, IMX708_REG_DIG_CROP_WIDTH, format->width, &ret); + cci_write(imx708->cci, IMX708_REG_DIG_CROP_HEIGHT, format->height, &ret); + + /* Output size */ + cci_write(imx708->cci, IMX708_REG_X_OUTPUT_SIZE, format->width, &ret); + cci_write(imx708->cci, IMX708_REG_Y_OUTPUT_SIZE, format->height, &ret); + + return ret; +} + +static int imx708_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 mask) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct imx708 *imx708 = to_imx708(sd); + const struct imx708_reg_list *freq_regs; + int ret = 0; + + ret = pm_runtime_resume_and_get(&client->dev); + if (ret < 0) + return ret; + + /* Program windowing registers from V4L2 state */ + ret = imx708_program_window(imx708, state); + if (ret) { + dev_err(&client->dev, "%s failed to set mode\n", __func__); + goto err_rpm_put; + } + + /* Update the link frequency registers */ + freq_regs = &link_freq_regs[__ffs(imx708->link_freq_bitmap)]; + cci_multi_reg_write(imx708->cci, freq_regs->regs, + freq_regs->num_of_regs, &ret); + if (ret) { + dev_err(&client->dev, "%s failed to set link frequency registers\n", + __func__); + goto err_rpm_put; + } + + /* Apply customized values from user */ + ret = __v4l2_ctrl_handler_setup(imx708->sd.ctrl_handler); + if (ret) + goto err_rpm_put; + + /* set stream on register */ + cci_write(imx708->cci, IMX708_REG_MODE_SELECT, + IMX708_MODE_STREAMING, &ret); + if (ret) { + dev_err(&client->dev, "%s failed to start streaming\n", + __func__); + goto err_rpm_put; + } + + /* vflip/hflip cannot change during streaming */ + __v4l2_ctrl_grab(imx708->vflip, true); + __v4l2_ctrl_grab(imx708->hflip, true); + + return 0; + +err_rpm_put: + pm_runtime_put_sync(&client->dev); + + return ret; +} + +static int imx708_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 mask) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct imx708 *imx708 = to_imx708(sd); + int ret = 0; + + /* set stream off register */ + cci_write(imx708->cci, IMX708_REG_MODE_SELECT, + IMX708_MODE_STANDBY, &ret); + if (ret) + dev_err(&client->dev, "%s failed to set stream\n", __func__); + + __v4l2_ctrl_grab(imx708->vflip, false); + __v4l2_ctrl_grab(imx708->hflip, false); + + pm_runtime_put_autosuspend(&client->dev); + + return ret; +} + +static int imx708_write_common(struct imx708 *imx708) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + int i, ret = 0; + u64 val; + + cci_multi_reg_write(imx708->cci, imx708_common_regs, + ARRAY_SIZE(imx708_common_regs), &ret); + if (ret) { + dev_err(&client->dev, "%s failed to set common settings\n", + __func__); + return ret; + } + + cci_write(imx708->cci, IMX708_REG_CLKLANE_BLANK, + imx708->csi_flags & V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK ? + IMX708_CLKLANE_BLANK_NONCONT : 0, &ret); + if (ret) { + dev_err(&client->dev, "%s failed to set clock lane mode\n", + __func__); + return ret; + } + + cci_read(imx708->cci, IMX708_REG_BASE_SPC_GAINS_L, &val, &ret); + if (ret == 0 && val == 0x40) { + for (i = 0; i < 54 && ret == 0; i++) { + cci_write(imx708->cci, + CCI_REG8(CCI_REG_ADDR(IMX708_REG_BASE_SPC_GAINS_L) + i), + pdaf_gains[0][i % 9], &ret); + } + for (i = 0; i < 54 && ret == 0; i++) { + cci_write(imx708->cci, + CCI_REG8(CCI_REG_ADDR(IMX708_REG_BASE_SPC_GAINS_R) + i), + pdaf_gains[1][i % 9], &ret); + } + } + if (ret) { + dev_err(&client->dev, "%s failed to set PDAF gains\n", + __func__); + return ret; + } + + return 0; +} + +/* Power/clock management functions */ +static int imx708_power_on(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx708 *imx708 = to_imx708(sd); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(imx708_supply_name), + imx708->supplies); + if (ret) { + dev_err(&client->dev, "%s: failed to enable regulators\n", + __func__); + return ret; + } + + ret = clk_prepare_enable(imx708->inclk); + if (ret) { + dev_err(&client->dev, "%s: failed to enable clock\n", + __func__); + goto reg_off; + } + + gpiod_set_value_cansleep(imx708->reset_gpio, 0); + usleep_range(IMX708_XCLR_MIN_DELAY_US, + IMX708_XCLR_MIN_DELAY_US + IMX708_XCLR_DELAY_RANGE_US); + + ret = imx708_write_common(imx708); + if (ret) { + dev_err(&client->dev, "%s failed to write registers\n", + __func__); + goto clk_off; + } + + return 0; + +clk_off: + gpiod_set_value_cansleep(imx708->reset_gpio, 1); + clk_disable_unprepare(imx708->inclk); + +reg_off: + regulator_bulk_disable(ARRAY_SIZE(imx708_supply_name), + imx708->supplies); + return ret; +} + +static int imx708_power_off(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx708 *imx708 = to_imx708(sd); + + gpiod_set_value_cansleep(imx708->reset_gpio, 1); + regulator_bulk_disable(ARRAY_SIZE(imx708_supply_name), + imx708->supplies); + clk_disable_unprepare(imx708->inclk); + + return 0; +} + +/* Verify chip ID */ +static int imx708_identify_module(struct imx708 *imx708) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + int ret = 0; + u64 val; + + cci_read(imx708->cci, IMX708_REG_CHIP_ID, &val, &ret); + if (ret) { + dev_err(&client->dev, "failed to read chip id %x, with error %d\n", + IMX708_CHIP_ID, ret); + return ret; + } + + if (val != IMX708_CHIP_ID) { + dev_err(&client->dev, "chip id mismatch: %x!=%llx\n", + IMX708_CHIP_ID, val); + return -EIO; + } + + return 0; +} + +static const struct v4l2_subdev_core_ops imx708_core_ops = { + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_video_ops imx708_video_ops = { + .s_stream = v4l2_subdev_s_stream_helper, +}; + +static const struct v4l2_subdev_pad_ops imx708_pad_ops = { + .enum_mbus_code = imx708_enum_mbus_code, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = v4l2_subdev_get_fmt, + .get_selection = imx708_get_selection, + .enum_frame_size = imx708_enum_frame_size, + .enable_streams = imx708_enable_streams, + .disable_streams = imx708_disable_streams, +}; + +static const struct v4l2_subdev_ops imx708_subdev_ops = { + .core = &imx708_core_ops, + .video = &imx708_video_ops, + .pad = &imx708_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops imx708_internal_ops = { + .init_state = imx708_init_state, +}; + +static const struct v4l2_ctrl_config imx708_notify_gains_ctrl = { + .ops = &imx708_ctrl_ops, + .id = V4L2_CID_NOTIFY_GAINS, + .type = V4L2_CTRL_TYPE_U32, + .min = IMX708_COLOUR_BALANCE_MIN, + .max = IMX708_COLOUR_BALANCE_MAX, + .step = IMX708_COLOUR_BALANCE_STEP, + .def = IMX708_COLOUR_BALANCE_DEFAULT, + .dims = { 4 }, + .elem_size = sizeof(u32), +}; + +/* Initialize control handlers */ +static int imx708_init_controls(struct imx708 *imx708) +{ + struct v4l2_ctrl_handler *ctrl_hdlr; + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + struct v4l2_fwnode_device_properties props; + struct v4l2_ctrl *link_freq; + unsigned int i; + s32 hblank, vblank_max, exposure_max; + int ret; + + ret = v4l2_fwnode_device_parse(&client->dev, &props); + if (ret < 0) + return ret; + + ctrl_hdlr = &imx708->ctrl_handler; + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 17); + if (ret) + return ret; + + /* By default, PIXEL_RATE is read only */ + v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, V4L2_CID_PIXEL_RATE, + IMX708_PIXEL_RATE, IMX708_PIXEL_RATE, 1, + IMX708_PIXEL_RATE); + + link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_LINK_FREQ, + ARRAY_SIZE(link_freqs) - 1, + __ffs(imx708->link_freq_bitmap), + link_freqs); + + /* Frame Time = 2^LONG_EXP_SHIFT * REG_FRAME_LENGTH */ + vblank_max = ((1 << IMX708_LONG_EXP_SHIFT_MAX) * + IMX708_FRAME_LENGTH_MAX) - imx708_active_area.height; + imx708->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_VBLANK, IMX708_VBLANK_MIN, + vblank_max, 1, IMX708_VBLANK_MIN); + + hblank = IMX708_LINE_LENGTH - imx708_active_area.width; + imx708->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_HBLANK, hblank, hblank, 1, + hblank); + + /* Max Exposure = FRAME_LENGTH - OFFSET */ + exposure_max = (imx708_active_area.height + IMX708_VBLANK_MIN) - + IMX708_EXPOSURE_OFFSET; + imx708->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_EXPOSURE, + IMX708_EXPOSURE_MIN, + exposure_max, + IMX708_EXPOSURE_STEP, + IMX708_EXPOSURE_DEFAULT); + + v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + IMX708_ANA_GAIN_MIN, IMX708_ANA_GAIN_MAX, + IMX708_ANA_GAIN_STEP, IMX708_ANA_GAIN_DEFAULT); + + v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, V4L2_CID_DIGITAL_GAIN, + IMX708_DGTL_GAIN_MIN, IMX708_DGTL_GAIN_MAX, + IMX708_DGTL_GAIN_STEP, IMX708_DGTL_GAIN_DEFAULT); + + imx708->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + + imx708->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + v4l2_ctrl_cluster(2, &imx708->hflip); + + v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(imx708_test_pattern_menu) - 1, + 0, 0, imx708_test_pattern_menu); + for (i = 0; i < 4; i++) { + /* + * The assumption is that + * V4L2_CID_TEST_PATTERN_GREENR == V4L2_CID_TEST_PATTERN_RED + 1 + * V4L2_CID_TEST_PATTERN_BLUE == V4L2_CID_TEST_PATTERN_RED + 2 + * V4L2_CID_TEST_PATTERN_GREENB == V4L2_CID_TEST_PATTERN_RED + 3 + */ + v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, + V4L2_CID_TEST_PATTERN_RED + i, + IMX708_TEST_PATTERN_COLOUR_MIN, + IMX708_TEST_PATTERN_COLOUR_MAX, + IMX708_TEST_PATTERN_COLOUR_STEP, + IMX708_TEST_PATTERN_COLOUR_MAX); + /* The "Solid color" pattern is white by default */ + } + + v4l2_ctrl_new_custom(ctrl_hdlr, &imx708_notify_gains_ctrl, NULL); + + v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &imx708_ctrl_ops, &props); + + if (ctrl_hdlr->error) { + ret = ctrl_hdlr->error; + dev_err(&client->dev, "%s control init failed (%d)\n", + __func__, ret); + v4l2_ctrl_handler_free(ctrl_hdlr); + + return ret; + } + + link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + imx708->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + imx708->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + imx708->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT; + + imx708->sd.ctrl_handler = ctrl_hdlr; + + return 0; +} + +static int imx708_check_hwcfg(struct device *dev, struct imx708 *imx708) +{ + struct fwnode_handle *endpoint; + struct v4l2_fwnode_endpoint ep_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + int ret = -EINVAL; + + endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); + if (!endpoint) { + dev_err(dev, "endpoint node not found\n"); + return -EINVAL; + } + + if (v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep_cfg)) { + dev_err(dev, "could not parse endpoint\n"); + goto error_out; + } + + /* Check the number of MIPI CSI2 data lanes */ + if (ep_cfg.bus.mipi_csi2.num_data_lanes != 2) { + dev_err(dev, "only 2 data lanes are currently supported\n"); + goto error_out; + } + + imx708->csi_flags = ep_cfg.bus.mipi_csi2.flags; + + /* Check the link frequency set in device tree */ + ret = v4l2_link_freq_to_bitmap(dev, ep_cfg.link_frequencies, + ep_cfg.nr_of_link_frequencies, + link_freqs, ARRAY_SIZE(link_freqs), + &imx708->link_freq_bitmap); + +error_out: + v4l2_fwnode_endpoint_free(&ep_cfg); + fwnode_handle_put(endpoint); + + return ret; +} + +static int imx708_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct imx708 *imx708; + int ret; + + imx708 = devm_kzalloc(&client->dev, sizeof(*imx708), GFP_KERNEL); + if (!imx708) + return -ENOMEM; + + v4l2_i2c_subdev_init(&imx708->sd, client, &imx708_subdev_ops); + + imx708->cci = devm_cci_regmap_init_i2c(client, 16); + if (IS_ERR(imx708->cci)) + return dev_err_probe(dev, PTR_ERR(imx708->cci), + "failed to init CCI\n"); + + if (imx708_check_hwcfg(dev, imx708)) + return -EINVAL; + + imx708->inclk = devm_v4l2_sensor_clk_get(dev, NULL); + if (IS_ERR(imx708->inclk)) + return dev_err_probe(dev, PTR_ERR(imx708->inclk), + "failed to get inclk\n"); + + imx708->inclk_freq = clk_get_rate(imx708->inclk); + if (imx708->inclk_freq != IMX708_INCLK_FREQ) + return dev_err_probe(dev, -EINVAL, + "inclk frequency not supported: %d Hz\n", + imx708->inclk_freq); + + for (int i = 0; i < ARRAY_SIZE(imx708_supply_name); i++) + imx708->supplies[i].supply = imx708_supply_name[i]; + + ret = devm_regulator_bulk_get(&client->dev, + ARRAY_SIZE(imx708_supply_name), + imx708->supplies); + if (ret) + return dev_err_probe(dev, ret, "failed to get regulators\n"); + + imx708->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(imx708->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(imx708->reset_gpio), + "failed to get reset GPIO\n"); + + ret = imx708_power_on(dev); + if (ret) + return ret; + + ret = imx708_identify_module(imx708); + if (ret) + goto error_power_off; + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + ret = imx708_init_controls(imx708); + if (ret) + goto error_pm_runtime; + + imx708->sd.internal_ops = &imx708_internal_ops; + imx708->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + imx708->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + + imx708->pad[IMX708_SOURCE_PAD].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&imx708->sd.entity, IMX708_NUM_PADS, + imx708->pad); + if (ret) { + dev_err_probe(dev, ret, "failed to init entity pads\n"); + goto error_handler_free; + } + + imx708->sd.state_lock = imx708->ctrl_handler.lock; + ret = v4l2_subdev_init_finalize(&imx708->sd); + if (ret) { + dev_err_probe(dev, ret, "failed to finalize subdev\n"); + goto error_media_entity; + } + + ret = v4l2_async_register_subdev_sensor(&imx708->sd); + if (ret < 0) { + dev_err_probe(dev, ret, "failed to register sensor sub-device\n"); + goto error_subdev_cleanup; + } + + pm_runtime_idle(dev); + pm_runtime_set_autosuspend_delay(dev, 5000); + pm_runtime_use_autosuspend(dev); + + return 0; + +error_subdev_cleanup: + v4l2_subdev_cleanup(&imx708->sd); + +error_media_entity: + media_entity_cleanup(&imx708->sd.entity); + +error_handler_free: + v4l2_ctrl_handler_free(imx708->sd.ctrl_handler); + +error_pm_runtime: + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + +error_power_off: + imx708_power_off(&client->dev); + + return ret; +} + +static void imx708_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct imx708 *imx708 = to_imx708(sd); + + v4l2_async_unregister_subdev(sd); + v4l2_subdev_cleanup(sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(imx708->sd.ctrl_handler); + + pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) + imx708_power_off(&client->dev); + pm_runtime_set_suspended(&client->dev); +} + +static const struct of_device_id imx708_dt_ids[] = { + { .compatible = "sony,imx708" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx708_dt_ids); + +static const struct dev_pm_ops imx708_pm_ops = { + SET_RUNTIME_PM_OPS(imx708_power_off, imx708_power_on, NULL) +}; + +static struct i2c_driver imx708_i2c_driver = { + .driver = { + .name = "imx708", + .of_match_table = imx708_dt_ids, + .pm = pm_ptr(&imx708_pm_ops), + }, + .probe = imx708_probe, + .remove = imx708_remove, +}; + +module_i2c_driver(imx708_i2c_driver); + +MODULE_AUTHOR("David Plowman "); +MODULE_AUTHOR("Jai Luthra "); +MODULE_DESCRIPTION("Sony IMX708 sensor driver"); +MODULE_LICENSE("GPL");