diff --git a/arch/arm/boot/dts/overlays/Makefile b/arch/arm/boot/dts/overlays/Makefile
index f4696c04638c35..3b386ccfa57e8b 100644
--- a/arch/arm/boot/dts/overlays/Makefile
+++ b/arch/arm/boot/dts/overlays/Makefile
@@ -149,6 +149,7 @@ dtbo-$(CONFIG_ARCH_BCM2835) += \
imx500.dtbo \
imx500-pi5.dtbo \
imx519.dtbo \
+ imx662.dtbo \
imx708.dtbo \
interludeaudio-analog.dtbo \
interludeaudio-digital.dtbo \
diff --git a/arch/arm/boot/dts/overlays/README b/arch/arm/boot/dts/overlays/README
index 8f4ae928987846..09bd2014b458fe 100644
--- a/arch/arm/boot/dts/overlays/README
+++ b/arch/arm/boot/dts/overlays/README
@@ -3097,6 +3097,27 @@ Params: rotation Mounting rotation of the camera sensor (0 or
but vcm=off will disable.
+Name: imx290
+Info: Sony IMX662 camera module.
+ Uses Unicam 1, which is the standard camera connector on most Pi
+ variants.
+Load: dtoverlay=imx662,
+Params: 4lane Enable 4 CSI2 lanes. This requires a Compute
+ Module (1, 3, 4, or 5) or Pi 5.
+ clock-frequency Sets the clock frequency to match that used on
+ the board. Default 24MHz
+ mono Denote that the module is a mono sensor.
+ orientation Sensor orientation (0 = front, 1 = rear,
+ 2 = external, default external)
+ rotation Mounting rotation of the camera sensor (0 or
+ 180, default 0)
+ media-controller Configure use of Media Controller API for
+ configuring the sensor (default on)
+ cam0 Adopt the default configuration for CAM0 on a
+ Compute Module (CSI0, i2c_vc, and cam0_reg).
+ vcm Enable a VCM focus drive on the sensor.
+
+
Name: imx708
Info: Sony IMX708 camera module.
Uses Unicam 1, which is the standard camera connector on most Pi
diff --git a/arch/arm/boot/dts/overlays/imx662-overlay.dts b/arch/arm/boot/dts/overlays/imx662-overlay.dts
new file mode 100644
index 00000000000000..17e25e020a1dd6
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/imx662-overlay.dts
@@ -0,0 +1,114 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Definitions for IMX662 camera module on VC I2C bus
+/dts-v1/;
+/plugin/;
+
+#include
+
+/{
+ compatible = "brcm,bcm2835";
+
+ fragment@0 {
+ target = <&i2c0if>;
+ __overlay__ {
+ status = "okay";
+ };
+ };
+
+ clk_frag: fragment@1 {
+ target = <&cam1_clk>;
+ __overlay__ {
+ status = "okay";
+ clock-frequency = <24000000>;
+ };
+ };
+
+ fragment@2 {
+ target = <&i2c0mux>;
+ __overlay__ {
+ status = "okay";
+ };
+ };
+
+ i2c_frag: fragment@100 {
+ target = <&i2c_csi_dsi>;
+ __overlay__ {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "okay";
+
+ #include "imx662.dtsi"
+
+ vcm: ad5398@c {
+ compatible = "adi,ad5398";
+ reg = <0x0c>;
+ status = "disabled";
+ VANA-supply = <&cam1_reg>;
+ };
+ };
+ };
+
+ csi_frag: fragment@101 {
+ target = <&csi1>;
+ csi: __overlay__ {
+ status = "okay";
+
+ port {
+ csi_ep: endpoint {
+ remote-endpoint = <&cam_endpoint>;
+ clock-lanes = <0>;
+ data-lanes = <1 2>;
+ clock-noncontinuous;
+ };
+ };
+ };
+ };
+
+ fragment@102 {
+ target = <&csi1>;
+ __dormant__ {
+ compatible = "brcm,bcm2835-unicam-legacy";
+ };
+ };
+
+ fragment@201 {
+ target = <&csi_ep>;
+ __dormant__ {
+ data-lanes = <1 2 3 4>;
+ };
+ };
+
+ fragment@202 {
+ target = <&cam_endpoint>;
+ __dormant__ {
+ data-lanes = <1 2 3 4>;
+ link-frequencies =
+ /bits/ 64 <297000000>;
+ };
+ };
+
+ __overrides__ {
+ rotation = <&cam_node>,"rotation:0";
+ orientation = <&cam_node>,"orientation:0";
+ media-controller = <0>,"!102";
+ cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>,
+ <&csi_frag>, "target:0=",<&csi0>,
+ <&clk_frag>, "target:0=",<&cam0_clk>,
+ <&cam_node>, "clocks:0=",<&cam0_clk>,
+ <&cam_node>, "VANA-supply:0=",<&cam0_reg>,
+ <&vcm>, "VANA-supply:0=", <&cam0_reg>;
+ vcm = <&vcm>, "status=okay",
+ <&cam_node>,"lens-focus:0=", <&vcm>;
+ 4lane = <0>, "+201+202";
+ clock-frequency = <&clk_frag>,"clock-frequency:0",
+ <&cam_node>,"clock-frequency:0";
+ };
+};
+
+&cam_node {
+ status = "okay";
+};
+
+&cam_endpoint {
+ remote-endpoint = <&csi_ep>;
+};
diff --git a/arch/arm/boot/dts/overlays/imx662.dtsi b/arch/arm/boot/dts/overlays/imx662.dtsi
new file mode 100644
index 00000000000000..bf5815b1f24b6b
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/imx662.dtsi
@@ -0,0 +1,28 @@
+// Fragment that configures an imx662
+
+cam_node: imx662@10 {
+ compatible = "sony,imx662aaqr";
+ reg = <0x1a>;
+ status = "disabled";
+
+ clocks = <&cam1_clk>;
+ clock-names = "xclk";
+ clock-frequency = <24000000>;
+
+ vdda-supply = <&cam1_reg>; /* 2.8v */
+ vddd-supply = <&cam_dummy_reg>; /* 1.8v */
+ vdddo-supply = <&cam_dummy_reg>;/* 1.2v */
+
+ rotation = <180>;
+ orientation = <2>;
+
+ port {
+ cam_endpoint: endpoint {
+ clock-lanes = <0>;
+ data-lanes = <1 2>;
+ clock-noncontinuous;
+ link-frequencies =
+ /bits/ 64 <594000000>;
+ };
+ };
+};
diff --git a/arch/arm/configs/bcm2709_defconfig b/arch/arm/configs/bcm2709_defconfig
index 788352d3712a55..0013d2cbf79c99 100644
--- a/arch/arm/configs/bcm2709_defconfig
+++ b/arch/arm/configs/bcm2709_defconfig
@@ -956,6 +956,7 @@ CONFIG_VIDEO_IMX415=m
CONFIG_VIDEO_IMX477=m
CONFIG_VIDEO_IMX500=m
CONFIG_VIDEO_IMX519=m
+CONFIG_VIDEO_IMX662=m
CONFIG_VIDEO_IMX708=m
CONFIG_VIDEO_MIRA220=m
CONFIG_VIDEO_MT9V011=m
diff --git a/arch/arm/configs/bcm2711_defconfig b/arch/arm/configs/bcm2711_defconfig
index 2b980f60e14960..c2e6b190df0b54 100644
--- a/arch/arm/configs/bcm2711_defconfig
+++ b/arch/arm/configs/bcm2711_defconfig
@@ -995,6 +995,7 @@ CONFIG_VIDEO_IMX415=m
CONFIG_VIDEO_IMX477=m
CONFIG_VIDEO_IMX500=m
CONFIG_VIDEO_IMX519=m
+CONFIG_VIDEO_IMX662=m
CONFIG_VIDEO_IMX708=m
CONFIG_VIDEO_MIRA220=m
CONFIG_VIDEO_MT9V011=m
diff --git a/arch/arm/configs/bcmrpi_defconfig b/arch/arm/configs/bcmrpi_defconfig
index 82e3d25308cea7..8fa6e859ced426 100644
--- a/arch/arm/configs/bcmrpi_defconfig
+++ b/arch/arm/configs/bcmrpi_defconfig
@@ -949,6 +949,7 @@ CONFIG_VIDEO_IMX415=m
CONFIG_VIDEO_IMX477=m
CONFIG_VIDEO_IMX500=m
CONFIG_VIDEO_IMX519=m
+CONFIG_VIDEO_IMX662=m
CONFIG_VIDEO_IMX708=m
CONFIG_VIDEO_MIRA220=m
CONFIG_VIDEO_MT9V011=m
diff --git a/arch/arm64/configs/bcm2711_defconfig b/arch/arm64/configs/bcm2711_defconfig
index af192047b411be..11601a6874a999 100644
--- a/arch/arm64/configs/bcm2711_defconfig
+++ b/arch/arm64/configs/bcm2711_defconfig
@@ -1047,6 +1047,7 @@ CONFIG_VIDEO_IMX415=m
CONFIG_VIDEO_IMX477=m
CONFIG_VIDEO_IMX500=m
CONFIG_VIDEO_IMX519=m
+CONFIG_VIDEO_IMX662=m
CONFIG_VIDEO_IMX708=m
CONFIG_VIDEO_MIRA220=m
CONFIG_VIDEO_MT9V011=m
diff --git a/arch/arm64/configs/bcm2712_defconfig b/arch/arm64/configs/bcm2712_defconfig
index 91af35ccc0dcae..ccb4baeb6e17f2 100644
--- a/arch/arm64/configs/bcm2712_defconfig
+++ b/arch/arm64/configs/bcm2712_defconfig
@@ -1049,6 +1049,7 @@ CONFIG_VIDEO_IMX415=m
CONFIG_VIDEO_IMX477=m
CONFIG_VIDEO_IMX500=m
CONFIG_VIDEO_IMX519=m
+CONFIG_VIDEO_IMX662=m
CONFIG_VIDEO_IMX708=m
CONFIG_VIDEO_MIRA220=m
CONFIG_VIDEO_MT9V011=m
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index 0d8af9d822d2cc..e7f8d4eaccfd16 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -324,6 +324,17 @@ config VIDEO_IMX519
To compile this driver as a module, choose M here: the
module will be called IMX519.
+config VIDEO_IMX662
+ tristate "Sony IMX662 sensor support"
+ select REGMAP_I2C
+ select V4L2_CCI_I2C
+ help
+ This is a Video4Linux2 sensor driver for the Sony
+ IMX662 camera sensor.
+
+ To compile this driver as a module, choose M here: the
+ module will be called imx662.
+
config VIDEO_IMX708
tristate "Sony IMX708 sensor support"
depends on I2C && VIDEO_DEV
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index efc521b1bb2218..c241991f1d1ce1 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -66,6 +66,7 @@ obj-$(CONFIG_VIDEO_IMX415) += imx415.o
obj-$(CONFIG_VIDEO_IMX477) += imx477.o
obj-$(CONFIG_VIDEO_IMX500) += imx500.o
obj-$(CONFIG_VIDEO_IMX519) += imx519.o
+obj-$(CONFIG_VIDEO_IMX662) += imx662.o
obj-$(CONFIG_VIDEO_IMX708) += imx708.o
obj-$(CONFIG_VIDEO_IR_I2C) += ir-kbd-i2c.o
obj-$(CONFIG_VIDEO_IRS1125) += irs1125.o
diff --git a/drivers/media/i2c/imx662.c b/drivers/media/i2c/imx662.c
new file mode 100644
index 00000000000000..f9d24fb26c8b4d
--- /dev/null
+++ b/drivers/media/i2c/imx662.c
@@ -0,0 +1,1196 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Sony IMX662 CMOS Image Sensor Driver
+ *
+ * Copyright (C) 2022 Soho Enterprise Ltd.
+ * Copyright (C) 2026 Raspberry Pi.
+ *
+ *
+ */
+#define DEBUG
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+static bool hcg_mode;
+module_param(hcg_mode, bool, 0664);
+MODULE_PARM_DESC(hcg_mode, "Enable HCG mode");
+
+#define IMX662_STANDBY CCI_REG8(0x3000)
+#define IMX662_REGHOLD CCI_REG8(0x3001)
+#define IMX662_XMSTA CCI_REG8(0x3002)
+#define IMX662_INCK_SEL CCI_REG8(0x3014)
+ #define IMX662_INCK_SEL_74_25 0x00
+ #define IMX662_INCK_SEL_37_125 0x01
+ #define IMX662_INCK_SEL_72 0x02
+ #define IMX662_INCK_SEL_27 0x03
+ #define IMX662_INCK_SEL_24 0x04
+#define IMX662_LANE_RATE CCI_REG8(0x3015)
+ #define IMX662_LANE_RATE_2376 0x00
+ #define IMX662_LANE_RATE_2079 0x01
+ #define IMX662_LANE_RATE_1782 0x02
+ #define IMX662_LANE_RATE_1440 0x03
+ #define IMX662_LANE_RATE_1188 0x04
+ #define IMX662_LANE_RATE_891 0x05
+ #define IMX662_LANE_RATE_720 0x06
+ #define IMX662_LANE_RATE_594 0x07
+#define IMX662_FLIP_WINMODEH CCI_REG8(0x3020)
+#define IMX662_FLIP_WINMODEV CCI_REG8(0x3021)
+#define IMX662_ADBIT CCI_REG8(0x3022)
+#define IMX662_MDBIT CCI_REG8(0x3023)
+#define IMX662_VMAX CCI_REG24_LE(0x3028)
+ #define IMX662_VMAX_MAX 0x03ffff
+#define IMX662_HMAX CCI_REG16_LE(0x302c)
+ #define IMX662_HMAX_MAX 0xffff
+#define IMX662_FR_FDG_SEL0 CCI_REG8(0x3030)
+ #define IMX662_FDG_SEL0_LCG 0x00
+ #define IMX662_FDG_SEL0_HCG 0x01
+#define IMX662_FR_FDG_SEL1 CCI_REG8(0x3031)
+#define IMX662_FR_FDG_SEL2 CCI_REG8(0x3032)
+#define IMX662_CSI_LANE_MODE CCI_REG8(0x3040)
+#define IMX662_EXPOSURE CCI_REG24_LE(0x3050)
+#define IMX662_GAIN CCI_REG8(0x3070)
+
+#define IMX662_PIX_HSTART CCI_REG16_LE(0x303c)
+#define IMX662_PIX_HWIDTH CCI_REG16_LE(0x303e)
+ /* PIX_HWIDTH = n4 * 16, and n4 > 32 */
+ #define IMX662_PIX_HWIDTH_MIN 528
+#define IMX662_PIX_VSTART CCI_REG16_LE(0x3044)
+#define IMX662_PIX_VWIDTH CCI_REG16_LE(0x3046)
+ /* VMAX is required to be >=820, and VMAX >= PIX_VWIDTH + 70 */
+ #define IMX662_PIX_VWIDTH_MIN 750
+
+#define IMX662_EXPOSURE_MIN 1
+#define IMX662_EXPOSURE_STEP 1
+/* Exposure must be this many lines less than VMAX */
+#define IMX662_EXPOSURE_OFFSET 4
+
+#define IMX662_NATIVE_WIDTH 1936
+#define IMX662_NATIVE_HEIGHT 1101
+#define IMX662_PIXEL_ARRAY_LEFT 0
+#define IMX662_PIXEL_ARRAY_TOP 20
+#define IMX662_PIXEL_ARRAY_WIDTH 1937
+#define IMX662_PIXEL_ARRAY_HEIGHT 1100
+
+static const char * const imx662_supply_name[] = {
+ "vdda",
+ "vddd",
+ "vdddo",
+};
+
+#define IMX662_NUM_SUPPLIES ARRAY_SIZE(imx662_supply_name)
+
+struct imx662_mode {
+ u32 width;
+ u32 height;
+ u32 hmax_min;
+ u32 vmax_min;
+ struct v4l2_rect crop;
+
+ const struct cci_reg_sequence *mode_data;
+ u32 mode_data_size;
+};
+
+struct imx662_sensor_cfg {
+ bool mono;
+};
+
+struct imx662 {
+ struct device *dev;
+ struct clk *xclk;
+ u8 inck_sel;
+ struct regmap *regmap;
+ bool hcg_mode;
+ u8 nlanes;
+ u8 bpp;
+
+ const struct imx662_sensor_cfg *sensor_cfg;
+
+ struct v4l2_subdev sd;
+ struct media_pad pad;
+ struct v4l2_mbus_framefmt current_format;
+ const struct imx662_mode *current_mode;
+
+ struct regulator_bulk_data supplies[IMX662_NUM_SUPPLIES];
+ struct gpio_desc *rst_gpio;
+
+ struct v4l2_ctrl_handler ctrls;
+ struct v4l2_ctrl *pixel_rate;
+ struct v4l2_ctrl *hblank;
+ struct v4l2_ctrl *vblank;
+ struct v4l2_ctrl *hflip;
+ struct v4l2_ctrl *vflip;
+ struct v4l2_ctrl *exposure;
+};
+
+static const struct cci_reg_sequence imx662_global_settings[] = {
+ { CCI_REG8(0x3002), 0x00}, //#Master mode operation start
+ { CCI_REG8(0x301A), 0x00}, // HDR mode select (Normal)
+ { CCI_REG8(0x301B), 0x00}, // Normal/binning
+ { CCI_REG8(0x301C), 0x00}, // XVS sub sample
+ { CCI_REG8(0x301E), 0x01}, // virtual channel
+ { CCI_REG8(0x3060), 0x16}, // DOL output timing
+ { CCI_REG8(0x3061), 0x01}, // DOL output timing
+ { CCI_REG8(0x3062), 0x00}, // DOL output timing
+ { CCI_REG8(0x3064), 0xC4}, // DOL output timing
+ { CCI_REG8(0x3065), 0x0C}, // DOL output timing
+ { CCI_REG8(0x3066), 0x00}, // DOL output timing
+ { CCI_REG8(0x3069), 0x00}, // Direct Gain Enable
+ { CCI_REG8(0x3072), 0x00}, // GAIN SEF1
+ { CCI_REG8(0x3073), 0x00}, // GAIN SEF1
+ { CCI_REG8(0x3074), 0x00}, // GAIN SEF2
+ { CCI_REG8(0x3075), 0x00}, // GAIN SEF2
+ { CCI_REG8(0x3081), 0x00}, // EXP_GAIN
+ { CCI_REG8(0x308C), 0x00}, // Clear HDR DGAIN
+ { CCI_REG8(0x308D), 0x01}, // Clear HDR DGAIN
+ { CCI_REG8(0x3094), 0x00}, // CHDR AGAIN LG
+ { CCI_REG8(0x3095), 0x00}, // CHDR AGAIN LG
+ { CCI_REG8(0x3096), 0x00}, // CHDR AGAIN1
+ { CCI_REG8(0x3097), 0x00}, // CHDR AGAIN1
+ { CCI_REG8(0x309C), 0x00}, // CHDR AGAIN HG
+ { CCI_REG8(0x309D), 0x00}, // CHDR AGAIN HG
+ { CCI_REG8(0x30A4), 0xAA}, // XVS/XHS OUT
+ { CCI_REG8(0x30A6), 0x0F}, // XVS/XHS DRIVE HiZ
+ { CCI_REG8(0x30CC), 0x00}, // XVS width
+ { CCI_REG8(0x30CD), 0x00}, // XHS width
+ { CCI_REG8(0x3400), 0x01}, // GAIN Adjust
+ { CCI_REG8(0x3444), 0xAC}, // RESERVED
+ { CCI_REG8(0x3460), 0x21}, // Normal Mode 22H=C HDR mode
+ { CCI_REG8(0x3492), 0x08}, // RESERVED
+ { CCI_REG8(0x3A50), 0xFF}, // Normal 12bit
+ { CCI_REG8(0x3A51), 0x03}, // Normal 12bit
+ { CCI_REG8(0x3A52), 0x00}, // AD 12bit
+ { CCI_REG8(0x3B00), 0x39}, // RESERVED
+ { CCI_REG8(0x3B23), 0x2D}, // RESERVED
+ { CCI_REG8(0x3B45), 0x04}, // RESERVED
+ { CCI_REG8(0x3C0A), 0x1F}, // RESERVED
+ { CCI_REG8(0x3C0B), 0x1E}, // RESERVED
+ { CCI_REG8(0x3C38), 0x21}, // RESERVED
+ { CCI_REG8(0x3C40), 0x06}, // Normal mode. CHDR=05h
+ { CCI_REG8(0x3C44), 0x00}, // RESERVED
+ { CCI_REG8(0x3CB6), 0xD8}, // RESERVED
+ { CCI_REG8(0x3CC4), 0xDA}, // RESERVED
+ { CCI_REG8(0x3E24), 0x79}, // RESERVED
+ { CCI_REG8(0x3E2C), 0x15}, // RESERVED
+ { CCI_REG8(0x3EDC), 0x2D}, // RESERVED
+ { CCI_REG8(0x4498), 0x05}, // RESERVED
+ { CCI_REG8(0x449C), 0x19}, // RESERVED
+ { CCI_REG8(0x449D), 0x00}, // RESERVED
+ { CCI_REG8(0x449E), 0x32}, // RESERVED
+ { CCI_REG8(0x449F), 0x01}, // RESERVED
+ { CCI_REG8(0x44A0), 0x92}, // RESERVED
+ { CCI_REG8(0x44A2), 0x91}, // RESERVED
+ { CCI_REG8(0x44A4), 0x8C}, // RESERVED
+ { CCI_REG8(0x44A6), 0x87}, // RESERVED
+ { CCI_REG8(0x44A8), 0x82}, // RESERVED
+ { CCI_REG8(0x44AA), 0x78}, // RESERVED
+ { CCI_REG8(0x44AC), 0x6E}, // RESERVED
+ { CCI_REG8(0x44AE), 0x69}, // RESERVED
+ { CCI_REG8(0x44B0), 0x92}, // RESERVED
+ { CCI_REG8(0x44B2), 0x91}, // RESERVED
+ { CCI_REG8(0x44B4), 0x8C}, // RESERVED
+ { CCI_REG8(0x44B6), 0x87}, // RESERVED
+ { CCI_REG8(0x44B8), 0x82}, // RESERVED
+ { CCI_REG8(0x44BA), 0x78}, // RESERVED
+ { CCI_REG8(0x44BC), 0x6E}, // RESERVED
+ { CCI_REG8(0x44BE), 0x69}, // RESERVED
+ { CCI_REG8(0x44C1), 0x01}, // RESERVED
+ { CCI_REG8(0x44C2), 0x7F}, // RESERVED
+ { CCI_REG8(0x44C3), 0x01}, // RESERVED
+ { CCI_REG8(0x44C4), 0x7A}, // RESERVED
+ { CCI_REG8(0x44C5), 0x01}, // RESERVED
+ { CCI_REG8(0x44C6), 0x7A}, // RESERVED
+ { CCI_REG8(0x44C7), 0x01}, // RESERVED
+ { CCI_REG8(0x44C8), 0x70}, // RESERVED
+ { CCI_REG8(0x44C9), 0x01}, // RESERVED
+ { CCI_REG8(0x44CA), 0x6B}, // RESERVED
+ { CCI_REG8(0x44CB), 0x01}, // RESERVED
+ { CCI_REG8(0x44CC), 0x6B}, // RESERVED
+ { CCI_REG8(0x44CD), 0x01}, // RESERVED
+ { CCI_REG8(0x44CE), 0x5C}, // RESERVED
+ { CCI_REG8(0x44CF), 0x01}, // RESERVED
+ { CCI_REG8(0x44D0), 0x7F}, // RESERVED
+ { CCI_REG8(0x44D1), 0x01}, // RESERVED
+ { CCI_REG8(0x44D2), 0x7F}, // RESERVED
+ { CCI_REG8(0x44D3), 0x01}, // RESERVED
+ { CCI_REG8(0x44D4), 0x7A}, // RESERVED
+ { CCI_REG8(0x44D5), 0x01}, // RESERVED
+ { CCI_REG8(0x44D6), 0x7A}, // RESERVED
+ { CCI_REG8(0x44D7), 0x01}, // RESERVED
+ { CCI_REG8(0x44D8), 0x70}, // RESERVED
+ { CCI_REG8(0x44D9), 0x01}, // RESERVED
+ { CCI_REG8(0x44DA), 0x6B}, // RESERVED
+ { CCI_REG8(0x44DB), 0x01}, // RESERVED
+ { CCI_REG8(0x44DC), 0x6B}, // RESERVED
+ { CCI_REG8(0x44DD), 0x01}, // RESERVED
+ { CCI_REG8(0x44DE), 0x5C}, // RESERVED
+ { CCI_REG8(0x44DF), 0x01}, // RESERVED
+ { CCI_REG8(0x4534), 0x1C}, // RESERVED
+ { CCI_REG8(0x4535), 0x03}, // RESERVED
+ { CCI_REG8(0x4538), 0x1C}, // RESERVED
+ { CCI_REG8(0x4539), 0x1C}, // RESERVED
+ { CCI_REG8(0x453A), 0x1C}, // RESERVED
+ { CCI_REG8(0x453B), 0x1C}, // RESERVED
+ { CCI_REG8(0x453C), 0x1C}, // RESERVED
+ { CCI_REG8(0x453D), 0x1C}, // RESERVED
+ { CCI_REG8(0x453E), 0x1C}, // RESERVED
+ { CCI_REG8(0x453F), 0x1C}, // RESERVED
+ { CCI_REG8(0x4540), 0x1C}, // RESERVED
+ { CCI_REG8(0x4541), 0x03}, // RESERVED
+ { CCI_REG8(0x4542), 0x03}, // RESERVED
+ { CCI_REG8(0x4543), 0x03}, // RESERVED
+ { CCI_REG8(0x4544), 0x03}, // RESERVED
+ { CCI_REG8(0x4545), 0x03}, // RESERVED
+ { CCI_REG8(0x4546), 0x03}, // RESERVED
+ { CCI_REG8(0x4547), 0x03}, // RESERVED
+ { CCI_REG8(0x4548), 0x03}, // RESERVED
+ { CCI_REG8(0x4549), 0x03}, // RESERVED
+};
+
+static const struct cci_reg_sequence imx662_1080p_common_settings[] = {
+ /* mode settings */
+ { CCI_REG8(0x3018), 0x04}, // WINMODE - window cropping mode
+ { IMX662_FR_FDG_SEL1, 0x00 },
+ { IMX662_FR_FDG_SEL2, 0x00 },
+};
+
+/* supported link frequencies */
+static const s64 imx662_link_freq_2lanes[] = {
+ 594000000,
+};
+
+static const s64 imx662_link_freq_4lanes[] = {
+ 297000000,
+};
+
+/*
+ * In this function and in the similar ones below we rely on imx662_probe()
+ * to ensure that nlanes is either 2 or 4.
+ */
+static inline const s64 *imx662_link_freqs_ptr(const struct imx662 *imx662)
+{
+ if (imx662->nlanes == 2)
+ return imx662_link_freq_2lanes;
+ else
+ return imx662_link_freq_4lanes;
+}
+
+/* Mode configs */
+static const struct imx662_mode imx662_modes[] = {
+ {
+ /*
+ * Note that this mode reads out the areas documented as
+ * "effective margin for color processing" and "effective pixel
+ * ignored area" in the datasheet.
+ */
+ .width = 1936,
+ .height = 1100,
+ .hmax_min = 990 * 3,
+ .vmax_min = 1250,
+ .crop = {
+ .left = IMX662_PIXEL_ARRAY_LEFT,
+ .top = IMX662_PIXEL_ARRAY_TOP,
+ .width = IMX662_NATIVE_WIDTH,
+ .height = IMX662_NATIVE_HEIGHT,
+ },
+ .mode_data = imx662_1080p_common_settings,
+ .mode_data_size = ARRAY_SIZE(imx662_1080p_common_settings),
+ },
+};
+
+#define IMX662_NUM_MODES ARRAY_SIZE(imx662_modes)
+
+static inline struct imx662 *to_imx662(struct v4l2_subdev *_sd)
+{
+ return container_of(_sd, struct imx662, sd);
+}
+
+#define IMX662_NUM_FORMATS 2
+
+static __u32 imx662_get_code(const struct imx662 *imx662, unsigned int index)
+{
+ if (index >= IMX662_NUM_FORMATS)
+ return 0;
+
+ if (imx662->sensor_cfg->mono)
+ return index ? MEDIA_BUS_FMT_Y12_1X12 : MEDIA_BUS_FMT_Y10_1X10;
+
+ return index ? MEDIA_BUS_FMT_SRGGB12_1X12 : MEDIA_BUS_FMT_SRGGB10_1X10;
+}
+
+static int imx662_set_exposure(struct imx662 *imx662,
+ const struct v4l2_mbus_framefmt *format,
+ u32 value)
+{
+ u32 exposure = (format->height + imx662->vblank->val) - value - 1;
+ int ret;
+
+ ret = cci_write(imx662->regmap, IMX662_EXPOSURE, exposure, NULL);
+ if (ret)
+ dev_err(imx662->dev, "Unable to write exposure\n");
+
+ return ret;
+}
+
+static int imx662_set_hmax(struct imx662 *imx662,
+ const struct v4l2_mbus_framefmt *format,
+ u32 value)
+{
+ u32 hmax = (value + format->width) / 3;
+ int ret;
+
+ ret = cci_write(imx662->regmap, IMX662_HMAX, hmax, NULL);
+ if (ret)
+ dev_err(imx662->dev, "Error setting HMAX register\n");
+
+ return ret;
+}
+
+static int imx662_set_vmax(struct imx662 *imx662,
+ const struct v4l2_mbus_framefmt *format,
+ u32 value)
+{
+ u32 vmax = value + format->height;
+
+ int ret;
+
+ ret = cci_write(imx662->regmap, IMX662_VMAX, vmax, NULL);
+ if (ret)
+ dev_err(imx662->dev, "Unable to write vmax\n");
+
+ /*
+ * Changing vblank changes the allowed range for exposure.
+ * We don't supply the current exposure as default here as it
+ * may lie outside the new range. We will reset it just below.
+ */
+ __v4l2_ctrl_modify_range(imx662->exposure,
+ IMX662_EXPOSURE_MIN,
+ vmax - IMX662_EXPOSURE_OFFSET,
+ IMX662_EXPOSURE_STEP,
+ vmax - IMX662_EXPOSURE_OFFSET);
+
+ /*
+ * Becuse of the way exposure works for this sensor, updating
+ * vblank causes the effective exposure to change, so we must
+ * set it back to the "new" correct value.
+ */
+ imx662_set_exposure(imx662, format, imx662->exposure->val);
+
+ return ret;
+}
+
+/* Stop streaming */
+static int imx662_stop_streaming(struct imx662 *imx662)
+{
+ int ret;
+
+ ret = cci_write(imx662->regmap, IMX662_STANDBY, 0x01, NULL);
+
+ msleep(30);
+
+ return cci_write(imx662->regmap, IMX662_XMSTA, 0x01, &ret);
+}
+
+static int imx662_set_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct imx662 *imx662 = container_of(ctrl->handler,
+ struct imx662, ctrls);
+ const struct v4l2_mbus_framefmt *format;
+ struct v4l2_subdev_state *state;
+ int ret = 0;
+
+ if (ctrl->flags & V4L2_CTRL_FLAG_READ_ONLY)
+ return 0;
+
+ /* V4L2 controls values will be applied only when power is already up */
+ if (!pm_runtime_get_if_in_use(imx662->dev))
+ return 0;
+
+ state = v4l2_subdev_get_locked_active_state(&imx662->sd);
+ format = v4l2_subdev_state_get_format(state, 0);
+
+ switch (ctrl->id) {
+ case V4L2_CID_ANALOGUE_GAIN:
+ ret = cci_write(imx662->regmap, IMX662_GAIN, ctrl->val, NULL);
+ break;
+ case V4L2_CID_EXPOSURE:
+ ret = imx662_set_exposure(imx662, format, ctrl->val);
+ break;
+ case V4L2_CID_HBLANK:
+ ret = imx662_set_hmax(imx662, format, ctrl->val);
+ break;
+ case V4L2_CID_VBLANK:
+ ret = imx662_set_vmax(imx662, format, ctrl->val);
+ break;
+ case V4L2_CID_HFLIP:
+ ret = cci_write(imx662->regmap, IMX662_FLIP_WINMODEH, ctrl->val,
+ NULL);
+ break;
+ case V4L2_CID_VFLIP:
+ ret = cci_write(imx662->regmap, IMX662_FLIP_WINMODEV, ctrl->val,
+ NULL);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ pm_runtime_put(imx662->dev);
+
+ return ret;
+}
+
+static const struct v4l2_ctrl_ops imx662_ctrl_ops = {
+ .s_ctrl = imx662_set_ctrl,
+};
+
+static int imx662_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ const struct imx662 *imx662 = to_imx662(sd);
+
+ if (code->index >= IMX662_NUM_FORMATS)
+ return -EINVAL;
+
+ code->code = imx662_get_code(imx662, code->index);
+
+ return 0;
+}
+
+static int imx662_enum_frame_size(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_frame_size_enum *fse)
+{
+ const struct imx662 *imx662 = to_imx662(sd);
+
+ if (fse->code != imx662_get_code(imx662, 0) &&
+ fse->code != imx662_get_code(imx662, 1))
+ return -EINVAL;
+
+ if (fse->index >= IMX662_NUM_MODES)
+ return -EINVAL;
+
+ fse->min_width = imx662_modes[fse->index].width;
+ fse->max_width = imx662_modes[fse->index].width;
+ fse->min_height = imx662_modes[fse->index].height;
+ fse->max_height = imx662_modes[fse->index].height;
+
+ return 0;
+}
+
+static u64 imx662_calc_pixel_rate(struct imx662 *imx662)
+{
+ /*
+ * Horizontal blanking timing is programmed based on units of clock,
+ * not directly of pixel rate.
+ * Analysing the register settings provided by Sony shows that the HMAX
+ * register takes units of 74.25MHz. However that only gives a register
+ * value of 660 for 90fps, which doesn't fit within V4L2's idea of how
+ * line timings would be calculated.
+ *
+ * Multiplying the 74.25MHz by 3 gives us a line length of 1980, which
+ * is long enough, and still provides a simple conversion back to the
+ * required register setting.
+ */
+ return 222750000;
+}
+
+static int imx662_set_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_format *fmt)
+{
+ struct imx662 *imx662 = to_imx662(sd);
+ struct v4l2_mbus_framefmt *format;
+ struct v4l2_rect *crop;
+ unsigned int i;
+
+ crop = v4l2_subdev_state_get_crop(state, fmt->pad);
+ format = v4l2_subdev_state_get_format(state, fmt->pad);
+
+ format->width = crop->width;
+ format->height = crop->height;
+
+ for (i = 0; i < IMX662_NUM_FORMATS; i++)
+ if (imx662_get_code(imx662, i) == fmt->format.code)
+ break;
+
+ if (i >= IMX662_NUM_FORMATS)
+ i = 0;
+
+ format->code = imx662_get_code(imx662, i);
+ 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;
+
+ fmt->format = *format;
+
+ if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
+/*
+ FIXME: Update blanking and exposure ranges on new mode.
+ Slightly irrelevant as there is only one mode at present.
+
+ imx662->current_mode = mode;
+
+ __v4l2_ctrl_modify_range(imx662->hblank,
+ mode->hmax_min - mode->width,
+ IMX662_HMAX_MAX - mode->width,
+ 1, mode->hmax_min - mode->width);
+ __v4l2_ctrl_s_ctrl(imx662->hblank,
+ mode->hmax_min - mode->width);
+
+ __v4l2_ctrl_modify_range(imx662->vblank,
+ mode->vmax_min - mode->height,
+ IMX662_VMAX_MAX - mode->height,
+ 2,
+ mode->vmax_min - mode->height);
+ __v4l2_ctrl_s_ctrl(imx662->vblank,
+ mode->vmax_min - mode->height);
+
+ __v4l2_ctrl_modify_range(imx662->exposure,
+ IMX662_EXPOSURE_MIN,
+ mode->vmax_min - 2,
+ IMX662_EXPOSURE_STEP,
+ mode->vmax_min - 2);
+*/
+ }
+
+ return 0;
+}
+
+static int imx662_set_selection(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_selection *sel)
+{
+ struct v4l2_mbus_framefmt *format;
+ struct v4l2_rect *crop;
+ struct v4l2_rect rect;
+
+ if (sel->target != V4L2_SEL_TGT_CROP)
+ return -EINVAL;
+
+ /*
+ * Clamp the crop rectangle boundaries and align them to a multiple of 4
+ * pixels to satisfy hardware requirements.
+ */
+ rect.left = clamp(ALIGN(sel->r.left, 2), 0,
+ IMX662_PIXEL_ARRAY_WIDTH - IMX662_PIX_HWIDTH_MIN);
+ rect.top = clamp(ALIGN(sel->r.top, 4), 0,
+ IMX662_PIXEL_ARRAY_HEIGHT - IMX662_PIX_HWIDTH_MIN);
+ rect.width = clamp_t(unsigned int, ALIGN(sel->r.width, 16),
+ IMX662_PIX_VWIDTH_MIN, IMX662_PIXEL_ARRAY_WIDTH);
+ rect.height = clamp_t(unsigned int, ALIGN(sel->r.height, 4),
+ IMX662_PIX_VWIDTH_MIN, IMX662_PIXEL_ARRAY_HEIGHT);
+
+ rect.width = min_t(unsigned int, rect.width,
+ IMX662_PIXEL_ARRAY_WIDTH - rect.left);
+ rect.height = min_t(unsigned int, rect.height,
+ IMX662_PIXEL_ARRAY_HEIGHT - rect.top);
+
+ crop = v4l2_subdev_state_get_crop(state, sel->pad);
+
+ if (rect.width != crop->width || rect.height != crop->height) {
+ /*
+ * Reset the output image size if the crop rectangle size has
+ * been modified.
+ */
+ format = v4l2_subdev_state_get_format(state, sel->pad);
+ format->width = rect.width;
+ format->height = rect.height;
+ }
+
+ *crop = rect;
+ sel->r = rect;
+
+ return 0;
+}
+
+static int imx662_entity_init_state(struct v4l2_subdev *subdev,
+ struct v4l2_subdev_state *sd_state)
+{
+ struct v4l2_subdev_selection sel = {
+ .target = V4L2_SEL_TGT_CROP,
+ .r.width = IMX662_PIXEL_ARRAY_WIDTH & ~1,
+ .r.height = IMX662_PIXEL_ARRAY_HEIGHT,
+ };
+ struct v4l2_subdev_format fmt = {
+ .which = V4L2_SUBDEV_FORMAT_TRY,
+ .format = {
+ .width = IMX662_PIXEL_ARRAY_WIDTH & ~1,
+ .height = IMX662_PIXEL_ARRAY_HEIGHT,
+ },
+ };
+
+ imx662_set_selection(subdev, sd_state, &sel);
+ imx662_set_fmt(subdev, sd_state, &fmt);
+
+ return 0;
+}
+
+static int imx662_write_current_format(struct imx662 *imx662,
+ const struct v4l2_mbus_framefmt *format)
+{
+ int ret;
+
+ switch (format->code) {
+ case MEDIA_BUS_FMT_SRGGB10_1X10:
+ case MEDIA_BUS_FMT_Y10_1X10:
+ ret = cci_write(imx662->regmap, IMX662_ADBIT, 0x00, NULL);
+ ret = cci_write(imx662->regmap, IMX662_MDBIT, 0x00, &ret);
+ ret = cci_write(imx662->regmap, CCI_REG8(0x3a50), 0x62, &ret);
+ ret = cci_write(imx662->regmap, CCI_REG8(0x3a51), 0x01, &ret);
+ ret = cci_write(imx662->regmap, CCI_REG8(0x3a52), 0x19, &ret);
+ break;
+ case MEDIA_BUS_FMT_SRGGB12_1X12:
+ case MEDIA_BUS_FMT_Y12_1X12:
+ ret = cci_write(imx662->regmap, IMX662_ADBIT, 0x01, NULL);
+ ret = cci_write(imx662->regmap, IMX662_MDBIT, 0x01, &ret);
+ ret = cci_write(imx662->regmap, CCI_REG8(0x3a50), 0xff, &ret);
+ ret = cci_write(imx662->regmap, CCI_REG8(0x3a51), 0x03, &ret);
+ ret = cci_write(imx662->regmap, CCI_REG8(0x3a52), 0x00, &ret);
+ break;
+ default:
+ dev_err(imx662->dev, "Unknown bpp %u\n", imx662->bpp);
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static int imx662_get_selection(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_selection *sel)
+{
+ switch (sel->target) {
+ case V4L2_SEL_TGT_CROP: {
+ sel->r = *v4l2_subdev_state_get_crop(state, sel->pad);
+
+ return 0;
+ }
+
+ case V4L2_SEL_TGT_NATIVE_SIZE:
+ sel->r.top = 0;
+ sel->r.left = 0;
+ sel->r.width = IMX662_NATIVE_WIDTH;
+ sel->r.height = IMX662_NATIVE_HEIGHT;
+
+ return 0;
+
+ case V4L2_SEL_TGT_CROP_DEFAULT:
+ case V4L2_SEL_TGT_CROP_BOUNDS:
+ sel->r.top = IMX662_PIXEL_ARRAY_TOP;
+ sel->r.left = IMX662_PIXEL_ARRAY_LEFT;
+ sel->r.width = IMX662_PIXEL_ARRAY_WIDTH;
+ sel->r.height = IMX662_PIXEL_ARRAY_HEIGHT;
+
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+/* Start streaming */
+static int imx662_start_streaming(struct imx662 *imx662,
+ struct v4l2_subdev_state *state)
+{
+ const struct v4l2_mbus_framefmt *format;
+ const struct v4l2_rect *crop;
+ int ret;
+
+ format = v4l2_subdev_state_get_format(state, 0);
+ crop = v4l2_subdev_state_get_crop(state, 0);
+
+ /* Set init register settings */
+ ret = cci_multi_reg_write(imx662->regmap, imx662_global_settings,
+ ARRAY_SIZE(imx662_global_settings), NULL);
+
+ cci_write(imx662->regmap, IMX662_INCK_SEL, imx662->inck_sel, &ret);
+
+ /* Apply the register values related to current frame format */
+ ret = imx662_write_current_format(imx662, format);
+
+ /* Apply default values of current mode */
+ cci_multi_reg_write(imx662->regmap, imx662->current_mode->mode_data,
+ imx662->current_mode->mode_data_size, &ret);
+
+ cci_write(imx662->regmap, IMX662_PIX_HSTART, crop->left & ~1, &ret);
+ cci_write(imx662->regmap, IMX662_PIX_VSTART, crop->top & ~1, &ret);
+ cci_write(imx662->regmap, IMX662_PIX_HWIDTH, crop->width & ~1, &ret);
+ cci_write(imx662->regmap, IMX662_PIX_VWIDTH, crop->height & ~1, &ret);
+
+ cci_write(imx662->regmap, IMX662_FR_FDG_SEL0,
+ (imx662->hcg_mode ? IMX662_FDG_SEL0_HCG : IMX662_FDG_SEL0_LCG),
+ &ret);
+
+ /* Apply lane config registers of current mode */
+ cci_write(imx662->regmap, IMX662_CSI_LANE_MODE,
+ imx662->nlanes == 2 ? 0x01 : 0x03, &ret);
+
+ cci_write(imx662->regmap, IMX662_LANE_RATE,
+ imx662->nlanes == 2 ? IMX662_LANE_RATE_1188 :
+ IMX662_LANE_RATE_594,
+ &ret);
+
+ if (ret < 0) {
+ dev_err(imx662->dev, "Failed sending start streaming setup\n");
+ return ret;
+ }
+
+ /* Apply customized values from user */
+ ret = __v4l2_ctrl_handler_setup(imx662->sd.ctrl_handler);
+ if (ret) {
+ dev_err(imx662->dev, "Could not sync v4l2 controls\n");
+ return ret;
+ }
+
+ cci_write(imx662->regmap, IMX662_STANDBY, 0x00, &ret);
+ cci_write(imx662->regmap, IMX662_XMSTA, 0x00, &ret);
+ if (ret < 0)
+ dev_err(imx662->dev, "Failed sending start streaming setup pt2\n");
+
+ return ret;
+}
+
+static int imx662_set_stream(struct v4l2_subdev *sd, int enable)
+{
+ struct imx662 *imx662 = to_imx662(sd);
+ struct v4l2_subdev_state *state;
+ int ret = 0;
+
+ state = v4l2_subdev_lock_and_get_active_state(sd);
+
+ if (enable) {
+ ret = pm_runtime_resume_and_get(imx662->dev);
+ if (ret < 0)
+ goto unlock_and_return;
+
+ ret = imx662_start_streaming(imx662, state);
+ if (ret) {
+ dev_err(imx662->dev, "Start stream failed\n");
+ pm_runtime_put(imx662->dev);
+ goto unlock_and_return;
+ }
+ } else {
+ imx662_stop_streaming(imx662);
+ pm_runtime_put(imx662->dev);
+ }
+
+ /*
+ * vflip should not be changed during streaming as the sensor will
+ * produce an invalid frame.
+ * hflip can be changed whilst streaming without generating an invalid
+ * frame.
+ */
+ __v4l2_ctrl_grab(imx662->vflip, enable);
+
+unlock_and_return:
+ v4l2_subdev_unlock_state(state);
+
+ return ret;
+}
+
+static int imx662_get_regulators(struct device *dev, struct imx662 *imx662)
+{
+ unsigned int i;
+
+ for (i = 0; i < IMX662_NUM_SUPPLIES; i++)
+ imx662->supplies[i].supply = imx662_supply_name[i];
+
+ return devm_regulator_bulk_get(dev, IMX662_NUM_SUPPLIES,
+ imx662->supplies);
+}
+
+static int imx662_power_on(struct device *dev)
+{
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
+ struct imx662 *imx662 = to_imx662(sd);
+ int ret;
+
+ ret = clk_prepare_enable(imx662->xclk);
+ if (ret) {
+ dev_err(dev, "Failed to enable clock\n");
+ return ret;
+ }
+
+ ret = regulator_bulk_enable(IMX662_NUM_SUPPLIES, imx662->supplies);
+ if (ret) {
+ dev_err(dev, "Failed to enable regulators\n");
+ clk_disable_unprepare(imx662->xclk);
+ return ret;
+ }
+
+ usleep_range(1, 2);
+ gpiod_set_value_cansleep(imx662->rst_gpio, 0);
+ usleep_range(30000, 31000);
+
+ return 0;
+}
+
+static int imx662_power_off(struct device *dev)
+{
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
+ struct imx662 *imx662 = to_imx662(sd);
+
+ clk_disable_unprepare(imx662->xclk);
+ gpiod_set_value_cansleep(imx662->rst_gpio, 1);
+ regulator_bulk_disable(IMX662_NUM_SUPPLIES, imx662->supplies);
+
+ return 0;
+}
+
+static const struct dev_pm_ops imx662_pm_ops = {
+ SET_RUNTIME_PM_OPS(imx662_power_off, imx662_power_on, NULL)
+};
+
+static const struct v4l2_subdev_video_ops imx662_video_ops = {
+ .s_stream = imx662_set_stream,
+};
+
+static const struct v4l2_subdev_pad_ops imx662_pad_ops = {
+ //.init_cfg = imx662_entity_init_cfg,
+ .enum_mbus_code = imx662_enum_mbus_code,
+ .enum_frame_size = imx662_enum_frame_size,
+ .get_fmt = v4l2_subdev_get_fmt,
+ .set_fmt = imx662_set_fmt,
+ .get_selection = imx662_get_selection,
+ .set_selection = imx662_set_selection,
+};
+
+static const struct v4l2_subdev_ops imx662_subdev_ops = {
+ .video = &imx662_video_ops,
+ .pad = &imx662_pad_ops,
+};
+
+static const struct v4l2_subdev_internal_ops imx662_internal_ops = {
+ .init_state = imx662_entity_init_state,
+};
+
+static const struct media_entity_operations imx662_subdev_entity_ops = {
+ .link_validate = v4l2_subdev_link_validate,
+};
+
+/*
+ * Returns 0 if all link frequencies used by the driver for the given number
+ * of MIPI data lanes are mentioned in the device tree, or the value of the
+ * first missing frequency otherwise.
+ */
+static int imx662_check_link_freqs(const struct imx662 *imx662,
+ const struct v4l2_fwnode_endpoint *ep)
+{
+ const s64 *freqs = imx662_link_freqs_ptr(imx662);
+
+ if (ep->nr_of_link_frequencies != 1)
+ return -EINVAL;
+ if (freqs[0] != ep->link_frequencies[0])
+ return -EINVAL;
+
+ return 0;
+}
+
+static int imx662_probe(struct i2c_client *client)
+{
+ struct v4l2_fwnode_device_properties props;
+ struct device *dev = &client->dev;
+ struct fwnode_handle *endpoint;
+ /* Only CSI2 is supported for now: */
+ struct v4l2_fwnode_endpoint ep = {
+ .bus_type = V4L2_MBUS_CSI2_DPHY
+ };
+ const struct imx662_mode *mode;
+ struct v4l2_subdev_state *state;
+ struct v4l2_ctrl *ctrl;
+ struct imx662 *imx662;
+ u32 xclk_freq;
+ int ret;
+
+ imx662 = devm_kzalloc(dev, sizeof(*imx662), GFP_KERNEL);
+ if (!imx662)
+ return -ENOMEM;
+
+ imx662->dev = dev;
+ imx662->regmap = devm_cci_regmap_init_i2c(client, 16);
+ if (IS_ERR(imx662->regmap)) {
+ dev_err(dev, "Unable to initialize I2C\n");
+ return PTR_ERR(imx662->regmap);
+ }
+
+ imx662->hcg_mode = hcg_mode;
+
+ imx662->sensor_cfg =
+ (const struct imx662_sensor_cfg *)of_device_get_match_data(imx662->dev);
+
+ endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL);
+ if (!endpoint) {
+ dev_err(dev, "Endpoint node not found\n");
+ return -EINVAL;
+ }
+
+ ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep);
+ fwnode_handle_put(endpoint);
+ if (ret == -ENXIO) {
+ dev_err(dev, "Unsupported bus type, should be CSI2\n");
+ goto free_err;
+ } else if (ret) {
+ dev_err(dev, "Parsing endpoint node failed\n");
+ goto free_err;
+ }
+
+ /* Get number of data lanes */
+ imx662->nlanes = ep.bus.mipi_csi2.num_data_lanes;
+ if (imx662->nlanes != 2 && imx662->nlanes != 4) {
+ dev_err(dev, "Invalid data lanes: %d\n", imx662->nlanes);
+ ret = -EINVAL;
+ goto free_err;
+ }
+
+ dev_dbg(dev, "Using %u data lanes\n", imx662->nlanes);
+
+ /* Check that link frequences for all the modes are in device tree */
+ ret = imx662_check_link_freqs(imx662, &ep);
+ if (ret) {
+ dev_err(dev, "Link frequency configuration not supported\n");
+ goto free_err;
+ }
+
+ /* get system clock (xclk) */
+ imx662->xclk = devm_clk_get(dev, "xclk");
+ if (IS_ERR(imx662->xclk)) {
+ dev_err(dev, "Could not get xclk");
+ ret = PTR_ERR(imx662->xclk);
+ goto free_err;
+ }
+
+ ret = fwnode_property_read_u32(dev_fwnode(dev), "clock-frequency",
+ &xclk_freq);
+ if (ret) {
+ dev_err(dev, "Could not get xclk frequency\n");
+ goto free_err;
+ }
+
+ /* external clock can be one of a range of values - validate it */
+ switch (xclk_freq) {
+ case 74250000:
+ imx662->inck_sel = IMX662_INCK_SEL_74_25;
+ break;
+ case 37125000:
+ imx662->inck_sel = IMX662_INCK_SEL_37_125;
+ break;
+ case 72000000:
+ imx662->inck_sel = IMX662_INCK_SEL_72;
+ break;
+ case 27000000:
+ imx662->inck_sel = IMX662_INCK_SEL_27;
+ break;
+ case 24000000:
+ imx662->inck_sel = IMX662_INCK_SEL_24;
+ break;
+ default:
+ dev_err(dev, "External clock frequency %u is not supported\n",
+ xclk_freq);
+ ret = -EINVAL;
+ goto free_err;
+ }
+
+ ret = clk_set_rate(imx662->xclk, xclk_freq);
+ if (ret) {
+ dev_err(dev, "Could not set xclk frequency\n");
+ goto free_err;
+ }
+
+ ret = imx662_get_regulators(dev, imx662);
+ if (ret < 0) {
+ dev_err(dev, "Cannot get regulators\n");
+ goto free_err;
+ }
+
+ imx662->rst_gpio = devm_gpiod_get_optional(dev, "reset",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(imx662->rst_gpio)) {
+ dev_err(dev, "Cannot get reset gpio\n");
+ ret = PTR_ERR(imx662->rst_gpio);
+ goto free_err;
+ }
+
+ imx662->current_mode = &imx662_modes[0];
+
+ v4l2_ctrl_handler_init(&imx662->ctrls, 11);
+
+ v4l2_ctrl_new_std(&imx662->ctrls, &imx662_ctrl_ops,
+ V4L2_CID_ANALOGUE_GAIN, imx662->hcg_mode ? 34 : 0,
+ 100, 1, imx662->hcg_mode ? 34 : 0);
+
+ mode = imx662->current_mode;
+ imx662->hblank = v4l2_ctrl_new_std(&imx662->ctrls, &imx662_ctrl_ops,
+ V4L2_CID_HBLANK,
+ mode->hmax_min - mode->width,
+ IMX662_HMAX_MAX - mode->width, 1,
+ mode->hmax_min - mode->width);
+
+ imx662->vblank = v4l2_ctrl_new_std(&imx662->ctrls, &imx662_ctrl_ops,
+ V4L2_CID_VBLANK,
+ mode->vmax_min - mode->height,
+ IMX662_VMAX_MAX - mode->height, 2,
+ mode->vmax_min - mode->height);
+
+ imx662->exposure = v4l2_ctrl_new_std(&imx662->ctrls, &imx662_ctrl_ops,
+ V4L2_CID_EXPOSURE,
+ IMX662_EXPOSURE_MIN,
+ mode->vmax_min - 2,
+ IMX662_EXPOSURE_STEP,
+ mode->vmax_min - 2);
+
+ imx662->hflip = v4l2_ctrl_new_std(&imx662->ctrls, &imx662_ctrl_ops,
+ V4L2_CID_HFLIP, 0, 1, 1, 0);
+ imx662->vflip = v4l2_ctrl_new_std(&imx662->ctrls, &imx662_ctrl_ops,
+ V4L2_CID_VFLIP, 0, 1, 1, 0);
+
+ ctrl = v4l2_ctrl_new_int_menu(&imx662->ctrls, NULL,
+ V4L2_CID_LINK_FREQ, 0, 0,
+ imx662_link_freqs_ptr(imx662));
+ if (ctrl)
+ ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+ imx662->pixel_rate = v4l2_ctrl_new_std(&imx662->ctrls, &imx662_ctrl_ops,
+ V4L2_CID_PIXEL_RATE,
+ imx662_calc_pixel_rate(imx662),
+ imx662_calc_pixel_rate(imx662),
+ 1,
+ imx662_calc_pixel_rate(imx662));
+
+ ret = v4l2_fwnode_device_parse(&client->dev, &props);
+ if (ret)
+ goto free_ctrl;
+
+ ret = v4l2_ctrl_new_fwnode_properties(&imx662->ctrls, &imx662_ctrl_ops,
+ &props);
+ if (ret)
+ goto free_ctrl;
+
+ imx662->sd.ctrl_handler = &imx662->ctrls;
+
+ if (imx662->ctrls.error) {
+ dev_err(dev, "Control initialization error %d\n",
+ imx662->ctrls.error);
+ ret = imx662->ctrls.error;
+ goto free_ctrl;
+ }
+
+ v4l2_i2c_subdev_init(&imx662->sd, client, &imx662_subdev_ops);
+ imx662->sd.internal_ops = &imx662_internal_ops;
+ imx662->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+ imx662->sd.dev = &client->dev;
+ imx662->sd.entity.ops = &imx662_subdev_entity_ops;
+ imx662->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
+
+ imx662->pad.flags = MEDIA_PAD_FL_SOURCE;
+ ret = media_entity_pads_init(&imx662->sd.entity, 1, &imx662->pad);
+ if (ret < 0) {
+ dev_err(dev, "Could not register media entity\n");
+ goto free_ctrl;
+ }
+
+ imx662->sd.state_lock = imx662->sd.ctrl_handler->lock;
+
+ ret = v4l2_subdev_init_finalize(&imx662->sd);
+ if (ret < 0) {
+ dev_err(imx662->dev, "subdev initialization error %d\n", ret);
+ goto free_ctrl;
+ }
+
+ state = v4l2_subdev_lock_and_get_active_state(&imx662->sd);
+ //imx662_ctrl_update(imx662, imx662->current_mode);
+ v4l2_subdev_unlock_state(state);
+
+ ret = v4l2_async_register_subdev(&imx662->sd);
+ if (ret < 0) {
+ dev_err(dev, "Could not register v4l2 device\n");
+ goto free_entity;
+ }
+
+ /* Power on the device to match runtime PM state below */
+ ret = imx662_power_on(dev);
+ if (ret < 0) {
+ dev_err(dev, "Could not power on the device\n");
+ goto free_entity;
+ }
+
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+ pm_runtime_idle(dev);
+
+ v4l2_fwnode_endpoint_free(&ep);
+
+ return 0;
+
+free_entity:
+ media_entity_cleanup(&imx662->sd.entity);
+free_ctrl:
+ v4l2_ctrl_handler_free(&imx662->ctrls);
+free_err:
+ v4l2_fwnode_endpoint_free(&ep);
+
+ return ret;
+}
+
+static void imx662_remove(struct i2c_client *client)
+{
+ struct v4l2_subdev *sd = i2c_get_clientdata(client);
+ struct imx662 *imx662 = to_imx662(sd);
+
+ v4l2_async_unregister_subdev(sd);
+ media_entity_cleanup(&sd->entity);
+ v4l2_ctrl_handler_free(sd->ctrl_handler);
+
+ pm_runtime_disable(imx662->dev);
+ if (!pm_runtime_status_suspended(imx662->dev))
+ imx662_power_off(imx662->dev);
+ pm_runtime_set_suspended(imx662->dev);
+}
+
+static const struct imx662_sensor_cfg imx662_colour_cfg = {
+};
+
+static const struct imx662_sensor_cfg imx662_mono_cfg = {
+ .mono = true,
+};
+
+static const struct of_device_id imx662_of_match[] = {
+ { .compatible = "sony,imx662aaqr", .data = &imx662_colour_cfg },
+ { .compatible = "sony,imx662aamr", .data = &imx662_mono_cfg },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx662_of_match);
+
+static struct i2c_driver imx662_i2c_driver = {
+ .probe = imx662_probe,
+ .remove = imx662_remove,
+ .driver = {
+ .name = "imx662",
+ .pm = &imx662_pm_ops,
+ .of_match_table = of_match_ptr(imx662_of_match),
+ },
+};
+
+module_i2c_driver(imx662_i2c_driver);
+
+MODULE_DESCRIPTION("Sony IMX662 CMOS Image Sensor Driver");
+MODULE_AUTHOR("Tetsuya Nomura ");
+MODULE_AUTHOR("Dave Stevenson ");
+MODULE_LICENSE("GPL");