From: Alexander Shiyan <eagle.alexander923@gmail.com>
To: linux-media@vger.kernel.org
Cc: devicetree@vger.kernel.org,
Mauro Carvalho Chehab <mchehab@kernel.org>,
Rob Herring <robh@kernel.org>,
Krzysztof Kozlowski <krzk+dt@kernel.org>,
Conor Dooley <conor+dt@kernel.org>,
Sakari Ailus <sakari.ailus@linux.intel.com>,
Hans Verkuil <hverkuil@kernel.org>,
Hans de Goede <hansg@kernel.org>,
Tetsuya Nomura <tetsuya.nomura@soho-enterprise.com>,
Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>,
Alexander Shiyan <eagle.alexander923@gmail.com>
Subject: [PATCH 2/2] media: i2c: Add driver for Sony IMX662 sensor
Date: Thu, 12 Mar 2026 18:04:37 +0300 [thread overview]
Message-ID: <20260312150437.1091195-3-eagle.alexander923@gmail.com> (raw)
In-Reply-To: <20260312150437.1091195-1-eagle.alexander923@gmail.com>
This patch adds a V4L2 subdevice driver for the Sony IMX662 CMOS image
sensor. The sensor has a native resolution of 1936x1100 (effective
1920x1080) and can achieve up to 90 frames per second depending on
the configuration. The driver supports:
- MIPI CSI-2 with 2 or 4 data lanes.
- RAW10 and RAW12 formats (both colour and monochrome).
- Controls: exposure, analogue gain, horizontal/vertical blanking,
horizontal/vertical flip, brightness.
- A placeholder V4L2_CID_HDR_SENSOR_MODE control for future Clear HDR
support (the actual HDR modes are not yet implemented).
- Runtime PM.
- Cropping via the selection API.
- Multiple link frequencies selectable via device tree.
Tested on ARM64 Rockchip RK3568 platform with a 24 MHz external clock
and various link frequencies.
Signed-off-by: Alexander Shiyan <eagle.alexander923@gmail.com>
---
drivers/media/i2c/Kconfig | 11 +
drivers/media/i2c/Makefile | 1 +
drivers/media/i2c/imx662.c | 1176 ++++++++++++++++++++++++++++++++++++
3 files changed, 1188 insertions(+)
create mode 100644 drivers/media/i2c/imx662.c
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index 609b6a8fc6db..936461b685fe 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -310,6 +310,17 @@ config VIDEO_IMX415
To compile this driver as a module, choose M here: the
module will be called imx415.
+config VIDEO_IMX662
+ tristate "Sony IMX662 sensor support"
+ depends on OF || COMPILE_TEST
+ select V4L2_CCI_I2C
+ help
+ This is a Video4Linux2 sensor driver for the Sony
+ IMX662 camera.
+
+ To compile this driver as a module, choose M here: the
+ module will be called imx662.
+
config VIDEO_IMX678
tristate "Sony IMX678 sensor support"
depends on OF_GPIO
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index 35e6ccbe16d9..e18cb8a68360 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -63,6 +63,7 @@ obj-$(CONFIG_VIDEO_IMX335) += imx335.o
obj-$(CONFIG_VIDEO_IMX355) += imx355.o
obj-$(CONFIG_VIDEO_IMX412) += imx412.o
obj-$(CONFIG_VIDEO_IMX415) += imx415.o
+obj-$(CONFIG_VIDEO_IMX662) += imx662.o
obj-$(CONFIG_VIDEO_IMX678) += imx678.o
obj-$(CONFIG_VIDEO_IR_I2C) += ir-kbd-i2c.o
obj-$(CONFIG_VIDEO_ISL7998X) += isl7998x.o
diff --git a/drivers/media/i2c/imx662.c b/drivers/media/i2c/imx662.c
new file mode 100644
index 000000000000..d7be17b5a47d
--- /dev/null
+++ b/drivers/media/i2c/imx662.c
@@ -0,0 +1,1176 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for the Sony IMX662 CMOS Image Sensor
+ *
+ * Copyright (C) 2026 Alexander Shiyan <eagle.alexander923@gmail.com>
+ *
+ * Some parts of code taken from imx662.c by:
+ * Copyright (C) 2022 Soho Enterprise Ltd.
+ * Author: Tetsuya Nomura <tetsuya.nomura@soho-enterprise.com>
+ *
+ * Some parts of code taken from imx290.c by:
+ * Copyright (C) 2019 FRAMOS GmbH.
+ * Copyright (C) 2019 Linaro Ltd.
+ * Author: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/gpio/consumer.h>
+#include <linux/regulator/consumer.h>
+#include <media/v4l2-cci.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-subdev.h>
+
+#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_DATARATE_SEL CCI_REG8(0x3015)
+
+enum {
+ IMX662_DATARATE_2376 = 0,
+ IMX662_DATARATE_2079 = 1,
+ IMX662_DATARATE_1782 = 2,
+ IMX662_DATARATE_1440 = 3,
+ IMX662_DATARATE_1188 = 4,
+ IMX662_DATARATE_891 = 5,
+ IMX662_DATARATE_720 = 6,
+ IMX662_DATARATE_594 = 7,
+ IMX662_DATARATE_MAX
+};
+
+#define IMX662_WINMODE CCI_REG8(0x3018)
+#define IMX662_WDMODE CCI_REG8(0x301a)
+#define IMX662_VCMODE CCI_REG8(0x301e)
+#define IMX662_HREVERSE CCI_REG8(0x3020)
+#define IMX662_VREVERSE 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 (0x0ffffe)
+#define IMX662_HMAX CCI_REG16_LE(0x302c)
+# define IMX662_HMAX_MAX (0xffff)
+#define IMX662_FDG_SEL0 CCI_REG8(0x3030)
+# define IMX662_FDG_SEL0_LCG (0x00)
+# define IMX662_FDG_SEL0_HCG (0x01)
+#define IMX662_FDG_SEL1 CCI_REG8(0x3031)
+#define IMX662_PIX_HST CCI_REG16_LE(0x303c)
+#define IMX662_LANEMODE CCI_REG8(0x3040)
+#define IMX662_PIX_HWIDTH CCI_REG16_LE(0x303e)
+#define IMX662_PIX_VST CCI_REG16_LE(0x3044)
+#define IMX662_PIX_VWIDTH CCI_REG16_LE(0x3046)
+#define IMX662_SHR0 CCI_REG24_LE(0x3050)
+# define IMX662_SHR0_MIN (4)
+#define IMX662_SHR1 CCI_REG24_LE(0x3054)
+#define IMX662_RHS1 CCI_REG24_LE(0x3060)
+#define IMX662_CHDR_GAIN_EN CCI_REG8(0x3069)
+#define IMX662_GAIN CCI_REG16_LE(0x3070)
+# define IMX662_ANA_GAIN_HCG_MIN (0x22)
+#define IMX662_GAIN1 CCI_REG16_LE(0x3072)
+#define IMX662_EXP_GAIN CCI_REG8(0x3081)
+#define IMX662_CHDR_DGAIN0_HG CCI_REG16_LE(0x308c)
+#define IMX662_CHDR_AGAIN0_LG CCI_REG16_LE(0x3094)
+#define IMX662_CHDR_AGAIN1 CCI_REG16_LE(0x3096)
+#define IMX662_CHDR_AGAIN0_HG CCI_REG16_LE(0x309c)
+#define IMX662_BLKLEVEL CCI_REG16_LE(0x30dc)
+#define IMX662_GAIN_PGC_FIDMD CCI_REG8(0x3400)
+
+#define IMX662_NATIVE_WIDTH (1936U)
+#define IMX662_NATIVE_HEIGHT (1100U)
+#define IMX662_PIXEL_ARRAY_LEFT (8U)
+#define IMX662_PIXEL_ARRAY_TOP (8U)
+#define IMX662_PIXEL_ARRAY_WIDTH (1920U)
+#define IMX662_PIXEL_ARRAY_HEIGHT (1080U)
+#define IMX662_MIN_CROP_WIDTH (80U)
+#define IMX662_MIN_CROP_HEIGHT (180U)
+#define IMX662_CROP_WIDTH_STEP (16U)
+#define IMX662_CROP_HEIGHT_STEP (4U)
+
+enum imx662_colour_variant {
+ IMX662_VARIANT_COLOUR,
+ IMX662_VARIANT_MONO,
+ IMX662_VARIANT_MAX
+};
+
+enum imx662_hdr_mode {
+ IMX662_HDR_OFF,
+ IMX662_HDR_CLRHDR,
+ IMX662_HDR_CLRHDR_DOL2,
+ IMX662_HDR_MAX
+};
+
+static const char * const imx662_supply_names[] = {
+ "avdd",
+ "dvdd",
+ "ovdd",
+};
+
+struct imx662_format {
+ u8 bpp;
+ u8 ad_md_bit;
+ u16 hmax_lane2[IMX662_DATARATE_MAX];
+ u16 hmax_lane4[IMX662_DATARATE_MAX];
+ u32 code[IMX662_VARIANT_MAX];
+};
+
+struct imx662 {
+ struct device *dev;
+ struct clk *clk;
+ struct regmap *regmap;
+
+ struct v4l2_subdev sd;
+ struct media_pad pad;
+
+ struct regulator_bulk_data supplies[ARRAY_SIZE(imx662_supply_names)];
+ struct gpio_desc *reset;
+
+ unsigned int num_data_lanes;
+
+ u8 link_freq_index;
+ u8 inck;
+
+ enum imx662_colour_variant variant;
+
+ enum imx662_hdr_mode hdr;
+
+ const struct imx662_format *format;
+
+ struct v4l2_rect crop;
+
+ bool streaming;
+
+ struct v4l2_ctrl_handler ctrls;
+
+ struct v4l2_ctrl *pixel_rate;
+ struct v4l2_ctrl *link_freq;
+ struct v4l2_ctrl *hblank;
+ struct v4l2_ctrl *vblank;
+ struct v4l2_ctrl *exposure;
+ struct v4l2_ctrl *gain;
+ struct v4l2_ctrl *hflip;
+ struct v4l2_ctrl *vflip;
+};
+
+static inline struct imx662 *to_imx662(struct v4l2_subdev *_sd)
+{
+ return container_of(_sd, struct imx662, sd);
+}
+
+static const struct cci_reg_sequence imx662_regs_common[] = {
+ { IMX662_WINMODE, 0x04 },
+ { IMX662_CHDR_GAIN_EN, 0x00 },
+ { IMX662_CHDR_DGAIN0_HG, 0x0100 },
+ { IMX662_CHDR_AGAIN0_LG, 0x0000 },
+ { IMX662_CHDR_AGAIN1, 0x0000 },
+ { IMX662_CHDR_AGAIN0_HG, 0x0000 },
+};
+
+static const struct cci_reg_sequence imx662_regs_hdr_off[] = {
+ { IMX662_WDMODE, 0x00 },
+ { IMX662_VCMODE, 0x01 },
+ { IMX662_FDG_SEL1, 0x00 },
+ { IMX662_SHR1, 0x000093 },
+ { IMX662_RHS1, 0x000095 },
+ { IMX662_GAIN1, 0x0000 },
+ { IMX662_EXP_GAIN, 0x00 },
+ { IMX662_GAIN_PGC_FIDMD, 0x01 },
+};
+
+static const s64 imx662_link_freqs[] = {
+ [IMX662_DATARATE_2376] = 2376000000LL / 2,
+ [IMX662_DATARATE_2079] = 2079000000LL / 2,
+ [IMX662_DATARATE_1782] = 1782000000LL / 2,
+ [IMX662_DATARATE_1440] = 1440000000LL / 2,
+ [IMX662_DATARATE_1188] = 1188000000LL / 2,
+ [IMX662_DATARATE_891] = 891000000LL / 2,
+ [IMX662_DATARATE_720] = 720000000LL / 2,
+ [IMX662_DATARATE_594] = 594000000LL / 2,
+};
+
+static const char * const imx662_hdr_menu[] = {
+ [IMX662_HDR_OFF] = "No HDR",
+ /* Not implemented modes: */
+ /* [IMX662_HDR_CLRHDR] = "Clear HDR" */
+ /* [IMX662_HDR_CLRHDR_DOL2] = "Clear HDR + DOL 2 Frame" */
+};
+
+static const struct imx662_format imx662_formats[] = {
+ {
+ .bpp = 10,
+ .ad_md_bit = 0,
+ .hmax_lane2 = {
+ [IMX662_DATARATE_594] = 2376,
+ [IMX662_DATARATE_720] = 1980,
+ [IMX662_DATARATE_891] = 990,
+ [IMX662_DATARATE_1440] = 660,
+ },
+ .hmax_lane4 = {
+ [IMX662_DATARATE_594] = 990,
+ [IMX662_DATARATE_720] = 660,
+ },
+ .code = {
+ [IMX662_VARIANT_COLOUR] = MEDIA_BUS_FMT_SRGGB10_1X10,
+ [IMX662_VARIANT_MONO] = MEDIA_BUS_FMT_Y10_1X10,
+ },
+ }, {
+ .bpp = 12,
+ .ad_md_bit = BIT(0),
+ .hmax_lane2 = {
+ [IMX662_DATARATE_720] = 1980,
+ [IMX662_DATARATE_891] = 1188,
+ [IMX662_DATARATE_1188] = 990,
+ },
+ .hmax_lane4 = {
+ [IMX662_DATARATE_594] = 990,
+ },
+ .code = {
+ [IMX662_VARIANT_COLOUR] = MEDIA_BUS_FMT_SRGGB12_1X12,
+ [IMX662_VARIANT_MONO] = MEDIA_BUS_FMT_Y12_1X12,
+ },
+ },
+};
+
+static int imx662_set_gain(struct imx662 *imx662, u32 value)
+{
+ int ret = 0;
+
+ if (imx662->hdr == IMX662_HDR_OFF) {
+ bool useHGC = value >= IMX662_ANA_GAIN_HCG_MIN;
+
+ cci_write(imx662->regmap, IMX662_REGHOLD, 1, &ret);
+ cci_write(imx662->regmap, IMX662_GAIN, value, &ret);
+ cci_write(imx662->regmap, IMX662_FDG_SEL0,
+ useHGC ? IMX662_FDG_SEL0_HCG : IMX662_FDG_SEL0_LCG,
+ &ret);
+ cci_write(imx662->regmap, IMX662_REGHOLD, 0, NULL);
+ } else
+ ret = -EINVAL;
+
+ return ret;
+}
+
+static int imx662_set_hblank(struct imx662 *imx662, u32 value)
+{
+ unsigned int hmax_min;
+
+ if (imx662->num_data_lanes == 2)
+ hmax_min =
+ imx662->format->hmax_lane2[imx662->link_freq_index];
+ else
+ hmax_min =
+ imx662->format->hmax_lane4[imx662->link_freq_index];
+
+ if (!hmax_min)
+ return -EINVAL;
+
+ return cci_write(imx662->regmap, IMX662_HMAX, hmax_min + value, NULL);
+}
+
+static void imx662_exposure_update(struct imx662 *imx662)
+{
+ int exposure_max, exposure_val;
+
+ exposure_max =
+ imx662->vblank->val + imx662->crop.height - IMX662_SHR0_MIN - 1;
+ exposure_val =
+ clamp(imx662->exposure->val, IMX662_SHR0_MIN, exposure_max);
+
+ __v4l2_ctrl_modify_range(imx662->exposure, IMX662_SHR0_MIN,
+ exposure_max, 1, exposure_val);
+ __v4l2_ctrl_s_ctrl(imx662->exposure, exposure_val);
+}
+
+static void imx662_gain_update(struct imx662 *imx662)
+{
+ /* Should be differ (max 80 + EXP_GAIN?) in HDR modes */
+ __v4l2_ctrl_modify_range(imx662->gain, 0, 240, 1, 0);
+}
+
+static void imx662_set_link_limits(struct imx662 *imx662)
+{
+ u64 pixel_rate;
+
+ pixel_rate = imx662_link_freqs[imx662->link_freq_index] * 2;
+ pixel_rate *= imx662->num_data_lanes;
+ do_div(pixel_rate, imx662->format->bpp);
+
+ __v4l2_ctrl_s_ctrl_int64(imx662->pixel_rate, pixel_rate);
+}
+
+static int imx662_set_framing_limits(struct imx662 *imx662)
+{
+ unsigned int vmax_min, hblank_max, vblank_min, vblank_max;
+
+ if (imx662->hdr == IMX662_HDR_OFF)
+ vmax_min = 1250;
+ else
+ vmax_min = 2500;
+
+ hblank_max = IMX662_HMAX_MAX - imx662->crop.width;
+ vblank_min = vmax_min - imx662->crop.height;
+ vblank_max = IMX662_VMAX_MAX - imx662->crop.height;
+
+ __v4l2_ctrl_modify_range(imx662->hblank, 0, hblank_max, 1, 0);
+
+ __v4l2_ctrl_modify_range(imx662->vblank, vblank_min, vblank_max,
+ 1, vblank_min);
+
+ return 0;
+}
+
+static int imx662_set_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct imx662 *imx662 = container_of(ctrl->handler,
+ struct imx662, ctrls);
+ int ret = 0;
+
+ if (ctrl->flags & V4L2_CTRL_FLAG_READ_ONLY)
+ return 0;
+
+ if (!pm_runtime_get_if_in_use(imx662->dev))
+ return 0;
+
+ switch (ctrl->id) {
+ case V4L2_CID_HBLANK:
+ ret = imx662_set_hblank(imx662, ctrl->val);
+ break;
+ case V4L2_CID_VBLANK:
+ cci_write(imx662->regmap, IMX662_VMAX,
+ ctrl->val + imx662->crop.height, &ret);
+ if (ret)
+ break;
+ imx662_exposure_update(imx662);
+ break;
+ case V4L2_CID_EXPOSURE:
+ cci_write(imx662->regmap, IMX662_SHR0,
+ imx662->vblank->val + imx662->crop.height -
+ ctrl->val - 1, &ret);
+ break;
+ case V4L2_CID_ANALOGUE_GAIN:
+ ret = imx662_set_gain(imx662, ctrl->val);
+ break;
+ case V4L2_CID_HFLIP:
+ cci_write(imx662->regmap, IMX662_HREVERSE,
+ ctrl->val ? BIT(0) : 0x00, &ret);
+ break;
+ case V4L2_CID_VFLIP:
+ cci_write(imx662->regmap, IMX662_VREVERSE,
+ ctrl->val ? BIT(0) : 0x00, &ret);
+ break;
+ case V4L2_CID_BRIGHTNESS:
+ cci_write(imx662->regmap, IMX662_BLKLEVEL, ctrl->val, &ret);
+ break;
+ case V4L2_CID_HDR_SENSOR_MODE:
+ if (!imx662->streaming) {
+ imx662->hdr = ctrl->val;
+ imx662_gain_update(imx662);
+ ret = imx662_set_framing_limits(imx662);
+ } else
+ ret = -EBUSY;
+ break;
+ default:
+ dev_err(imx662->dev, "Invalid control %d\n", ctrl->id);
+ ret = -EINVAL;
+ break;
+ }
+
+ pm_runtime_put_autosuspend(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)
+{
+ struct imx662 *imx662 = to_imx662(sd);
+ const struct imx662_format *fmt;
+ u16 hmax_min;
+
+ if (code->index >= ARRAY_SIZE(imx662_formats))
+ return -EINVAL;
+
+ fmt = &imx662_formats[code->index];
+ if (imx662->num_data_lanes == 2)
+ hmax_min = fmt->hmax_lane2[imx662->link_freq_index];
+ else
+ hmax_min = fmt->hmax_lane4[imx662->link_freq_index];
+ if (!hmax_min)
+ return -EINVAL;
+
+ code->code = imx662_formats[code->index].code[imx662->variant];
+
+ 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)
+{
+ if (fse->index)
+ return -EINVAL;
+
+ fse->min_width = IMX662_MIN_CROP_WIDTH;
+ fse->max_width = IMX662_PIXEL_ARRAY_WIDTH;
+ fse->min_height = IMX662_MIN_CROP_HEIGHT;
+ fse->max_height = IMX662_PIXEL_ARRAY_HEIGHT;
+
+ return 0;
+}
+
+static int imx662_set_pad_format(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_format *fmt)
+{
+ struct imx662 *imx662 = to_imx662(sd);
+ const struct imx662_format *format;
+ u32 width, height;
+ int i, ret;
+
+ for (i = 0; i < ARRAY_SIZE(imx662_formats); i++) {
+ if (imx662_formats[i].code[imx662->variant] ==
+ fmt->format.code) {
+ format = &imx662_formats[i];
+ break;
+ }
+ }
+ if (!format)
+ return -EINVAL;
+
+ width = round_down(fmt->format.width, IMX662_CROP_WIDTH_STEP);
+ width = clamp(width, IMX662_MIN_CROP_WIDTH, IMX662_PIXEL_ARRAY_WIDTH);
+ height = round_down(fmt->format.height, IMX662_CROP_HEIGHT_STEP);
+ height = clamp(height,
+ IMX662_MIN_CROP_HEIGHT, IMX662_PIXEL_ARRAY_HEIGHT);
+
+ fmt->format.width = width;
+ fmt->format.height = height;
+ fmt->format.field = V4L2_FIELD_NONE;
+ fmt->format.colorspace = V4L2_COLORSPACE_RAW;
+ fmt->format.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
+ fmt->format.quantization = V4L2_QUANTIZATION_DEFAULT;
+ fmt->format.xfer_func = V4L2_XFER_FUNC_NONE;
+
+ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+ *v4l2_subdev_state_get_format(sd_state, fmt->pad) = fmt->format;
+ return 0;
+ }
+
+ if (imx662->format != format) {
+ imx662->format = format;
+ imx662_set_link_limits(imx662);
+ ret = imx662_set_framing_limits(imx662);
+ if (ret)
+ return ret;
+ imx662_exposure_update(imx662);
+ }
+
+ if (imx662->crop.width != width || imx662->crop.height != height) {
+ imx662->crop.width = width;
+ imx662->crop.height = height;
+ imx662->crop.left =
+ min_t(u32, imx662->crop.left, IMX662_PIXEL_ARRAY_LEFT +
+ IMX662_PIXEL_ARRAY_WIDTH - width);
+ imx662->crop.top =
+ min_t(u32, imx662->crop.top, IMX662_PIXEL_ARRAY_TOP +
+ IMX662_PIXEL_ARRAY_HEIGHT - height);
+ imx662->crop.left =
+ max(imx662->crop.left, IMX662_PIXEL_ARRAY_LEFT);
+ imx662->crop.top =
+ max(imx662->crop.top, IMX662_PIXEL_ARRAY_TOP);
+
+ ret = imx662_set_framing_limits(imx662);
+ if (ret)
+ return ret;
+
+ imx662_exposure_update(imx662);
+ }
+
+ *v4l2_subdev_state_get_format(sd_state, fmt->pad) = fmt->format;
+
+ return 0;
+}
+
+static int imx662_get_selection(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_selection *sel)
+{
+ struct imx662 *imx662 = to_imx662(sd);
+
+ switch (sel->target) {
+ case V4L2_SEL_TGT_CROP:
+ if (sel->which == V4L2_SUBDEV_FORMAT_TRY)
+ sel->r =
+ *v4l2_subdev_state_get_crop(sd_state, sel->pad);
+ else
+ sel->r = imx662->crop;
+
+ 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;
+ 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;
+ }
+
+ return -EINVAL;
+}
+
+static int imx662_set_selection(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_selection *sel)
+{
+ struct imx662 *imx662 = to_imx662(sd);
+ struct v4l2_rect rect = sel->r;
+ struct v4l2_rect *try_crop;
+ struct v4l2_mbus_framefmt *try_fmt;
+ u32 max_left, max_top;
+
+ if (sel->target != V4L2_SEL_TGT_CROP)
+ return -EINVAL;
+
+ max_left = IMX662_PIXEL_ARRAY_LEFT + IMX662_PIXEL_ARRAY_WIDTH -
+ IMX662_MIN_CROP_WIDTH;
+ max_top = IMX662_PIXEL_ARRAY_TOP + IMX662_PIXEL_ARRAY_HEIGHT -
+ IMX662_MIN_CROP_HEIGHT;
+
+ rect.left = clamp_t(u32, rect.left, IMX662_PIXEL_ARRAY_LEFT, max_left);
+ rect.top = clamp_t(u32, rect.top, IMX662_PIXEL_ARRAY_TOP, max_top);
+
+ rect.width =
+ clamp_t(u32,
+ round_down(rect.width, IMX662_CROP_WIDTH_STEP),
+ IMX662_MIN_CROP_WIDTH, IMX662_PIXEL_ARRAY_WIDTH);
+ rect.height =
+ clamp_t(u32,
+ round_down(rect.height, IMX662_CROP_HEIGHT_STEP),
+ IMX662_MIN_CROP_HEIGHT, IMX662_PIXEL_ARRAY_HEIGHT);
+
+ if (rect.left + rect.width - 1 >
+ IMX662_PIXEL_ARRAY_LEFT + IMX662_PIXEL_ARRAY_WIDTH - 1)
+ rect.left =
+ IMX662_PIXEL_ARRAY_LEFT + IMX662_PIXEL_ARRAY_WIDTH -
+ rect.width;
+ if (rect.top + rect.height - 1 >
+ IMX662_PIXEL_ARRAY_TOP + IMX662_PIXEL_ARRAY_HEIGHT - 1)
+ rect.top =
+ IMX662_PIXEL_ARRAY_TOP + IMX662_PIXEL_ARRAY_HEIGHT -
+ rect.height;
+
+ if (sel->flags & V4L2_SEL_FLAG_GE) {
+ if (rect.width < sel->r.width) {
+ u32 new_width = rect.width + IMX662_CROP_WIDTH_STEP;
+
+ if (new_width <= IMX662_PIXEL_ARRAY_WIDTH)
+ rect.width = new_width;
+ }
+ if (rect.height < sel->r.height) {
+ u32 new_height = rect.height + IMX662_CROP_HEIGHT_STEP;
+
+ if (new_height <= IMX662_PIXEL_ARRAY_HEIGHT)
+ rect.height = new_height;
+ }
+ }
+
+ if (sel->flags & V4L2_SEL_FLAG_LE) {
+ if (rect.width > sel->r.width &&
+ rect.width >= IMX662_MIN_CROP_WIDTH +
+ IMX662_CROP_WIDTH_STEP)
+ rect.width -= IMX662_CROP_WIDTH_STEP;
+ if (rect.height > sel->r.height &&
+ rect.height >= IMX662_MIN_CROP_HEIGHT +
+ IMX662_CROP_HEIGHT_STEP)
+ rect.height -= IMX662_CROP_HEIGHT_STEP;
+ }
+
+ if (rect.width < IMX662_MIN_CROP_WIDTH ||
+ rect.height < IMX662_MIN_CROP_HEIGHT)
+ return -EINVAL;
+
+ if (sel->which == V4L2_SUBDEV_FORMAT_TRY) {
+ try_crop = v4l2_subdev_state_get_crop(sd_state, sel->pad);
+ *try_crop = rect;
+
+ try_fmt = v4l2_subdev_state_get_format(sd_state, sel->pad);
+ if (try_fmt) {
+ try_fmt->width = rect.width;
+ try_fmt->height = rect.height;
+ }
+ } else {
+ if (imx662->streaming)
+ return -EBUSY;
+
+ if (imx662->crop.left == rect.left &&
+ imx662->crop.top == rect.top &&
+ imx662->crop.width == rect.width &&
+ imx662->crop.height == rect.height) {
+ sel->r = rect;
+ return 0;
+ }
+
+ imx662->crop = rect;
+
+ try_fmt = v4l2_subdev_state_get_format(sd_state, sel->pad);
+ if (try_fmt) {
+ try_fmt->width = rect.width;
+ try_fmt->height = rect.height;
+ }
+
+ imx662_set_framing_limits(imx662);
+ }
+
+ sel->r = rect;
+
+ return 0;
+}
+
+static int imx662_init_state(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state)
+{
+ struct imx662 *imx662 = to_imx662(sd);
+ struct v4l2_subdev_format fmt = {
+ .which = V4L2_SUBDEV_FORMAT_TRY,
+ .format = {
+ .width = imx662->crop.width,
+ .height = imx662->crop.height,
+ .code = imx662->format->code[imx662->variant],
+ },
+ };
+ int ret;
+
+ ret = imx662_set_pad_format(sd, state, &fmt);
+ if (ret)
+ return ret;
+
+ *v4l2_subdev_state_get_crop(state, 0) = imx662->crop;
+
+ return 0;
+}
+
+static int imx662_enable_streams(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state, u32 pad,
+ u64 streams_mask)
+{
+ struct imx662 *imx662 = to_imx662(sd);
+ int ret;
+
+ ret = pm_runtime_resume_and_get(imx662->dev);
+ if (ret)
+ return ret;
+
+ cci_multi_reg_write(imx662->regmap, imx662_regs_common,
+ ARRAY_SIZE(imx662_regs_common), &ret);
+
+ cci_write(imx662->regmap, IMX662_INCK_SEL, imx662->inck, &ret);
+
+ cci_write(imx662->regmap, IMX662_PIX_HST, imx662->crop.left, &ret);
+ cci_write(imx662->regmap, IMX662_PIX_VST, imx662->crop.top, &ret);
+ cci_write(imx662->regmap, IMX662_PIX_HWIDTH, imx662->crop.width, &ret);
+ cci_write(imx662->regmap, IMX662_PIX_VWIDTH, imx662->crop.height, &ret);
+
+ cci_write(imx662->regmap, IMX662_LANEMODE, imx662->num_data_lanes - 1,
+ &ret);
+
+ cci_write(imx662->regmap, IMX662_DATARATE_SEL,
+ imx662->link_freq_index, &ret);
+
+ cci_write(imx662->regmap, IMX662_ADBIT, imx662->format->ad_md_bit,
+ &ret);
+ cci_write(imx662->regmap, IMX662_MDBIT, imx662->format->ad_md_bit,
+ &ret);
+
+ switch (imx662->hdr) {
+ case IMX662_HDR_OFF:
+ cci_multi_reg_write(imx662->regmap, imx662_regs_hdr_off,
+ ARRAY_SIZE(imx662_regs_hdr_off), &ret);
+ break;
+ default:
+ break;
+ }
+
+ if (ret)
+ goto start_err;
+
+ 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);
+
+ usleep_range(24000, 25000);
+
+ cci_write(imx662->regmap, IMX662_XMSTA, 0x00, &ret);
+ if (!ret) {
+ imx662->streaming = true;
+ return 0;
+ }
+
+start_err:
+ pm_runtime_put_autosuspend(imx662->dev);
+
+ return dev_err_probe(imx662->dev, ret, "Failed to setup sensor\n");
+}
+
+static int imx662_disable_streams(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state, u32 pad,
+ u64 streams_mask)
+{
+ struct imx662 *imx662 = to_imx662(sd);
+ int ret;
+
+ ret = cci_write(imx662->regmap, IMX662_STANDBY, 0x01, NULL);
+
+ cci_write(imx662->regmap, IMX662_XMSTA, 0x01, &ret);
+
+ imx662->streaming = false;
+
+ pm_runtime_put_autosuspend(imx662->dev);
+
+ return ret;
+}
+
+static int imx662_g_mbus_config(struct v4l2_subdev *sd, unsigned int pad_id,
+ struct v4l2_mbus_config *config)
+{
+ struct imx662 *imx662 = to_imx662(sd);
+
+ config->type = V4L2_MBUS_CSI2_DPHY;
+ config->bus.mipi_csi2.flags = V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK;
+ config->bus.mipi_csi2.num_data_lanes = imx662->num_data_lanes;
+
+ return 0;
+}
+
+static const struct v4l2_subdev_video_ops imx662_video_ops = {
+ .s_stream = v4l2_subdev_s_stream_helper,
+};
+
+static const struct v4l2_subdev_pad_ops imx662_pad_ops = {
+ .enum_mbus_code = imx662_enum_mbus_code,
+ .enum_frame_size = imx662_enum_frame_size,
+ .get_fmt = v4l2_subdev_get_fmt,
+ .set_fmt = imx662_set_pad_format,
+ .get_selection = imx662_get_selection,
+ .set_selection = imx662_set_selection,
+ .enable_streams = imx662_enable_streams,
+ .disable_streams = imx662_disable_streams,
+ .get_mbus_config = imx662_g_mbus_config,
+};
+
+static const struct v4l2_subdev_core_ops imx662_core_ops = {
+ .subscribe_event = v4l2_ctrl_subdev_subscribe_event,
+ .unsubscribe_event = v4l2_event_subdev_unsubscribe,
+};
+
+static const struct v4l2_subdev_ops imx662_subdev_ops = {
+ .core = &imx662_core_ops,
+ .video = &imx662_video_ops,
+ .pad = &imx662_pad_ops,
+};
+
+static const struct v4l2_subdev_internal_ops imx662_internal_ops = {
+ .init_state = imx662_init_state,
+};
+
+static const struct media_entity_operations imx662_subdev_entity_ops = {
+ .link_validate = v4l2_subdev_link_validate,
+};
+
+static int imx662_ctrls_init(struct imx662 *imx662)
+{
+ struct v4l2_fwnode_device_properties props;
+ int ret;
+
+ ret = v4l2_fwnode_device_parse(imx662->dev, &props);
+ if (ret)
+ return ret;
+
+ ret = v4l2_ctrl_handler_init(&imx662->ctrls, 10);
+ if (ret)
+ return ret;
+
+ imx662->pixel_rate = v4l2_ctrl_new_std(&imx662->ctrls, &imx662_ctrl_ops,
+ V4L2_CID_PIXEL_RATE, 1,
+ INT_MAX, 1, 1);
+
+ imx662->link_freq =
+ v4l2_ctrl_new_int_menu(&imx662->ctrls, &imx662_ctrl_ops,
+ V4L2_CID_LINK_FREQ,
+ ARRAY_SIZE(imx662_link_freqs) - 1,
+ imx662->link_freq_index,
+ imx662_link_freqs);
+ if (imx662->link_freq)
+ imx662->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+ imx662->hblank = v4l2_ctrl_new_std(&imx662->ctrls, &imx662_ctrl_ops,
+ V4L2_CID_HBLANK, 0, 1, 1, 0);
+
+ imx662->vblank = v4l2_ctrl_new_std(&imx662->ctrls, &imx662_ctrl_ops,
+ V4L2_CID_VBLANK, 0, 1, 1, 0);
+
+ imx662->exposure = v4l2_ctrl_new_std(&imx662->ctrls, &imx662_ctrl_ops,
+ V4L2_CID_EXPOSURE, IMX662_SHR0_MIN,
+ 0xffff, 1, 0xffff);
+
+ imx662->gain = v4l2_ctrl_new_std(&imx662->ctrls, &imx662_ctrl_ops,
+ V4L2_CID_ANALOGUE_GAIN, 0, 1, 1, 0);
+
+ 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);
+
+ v4l2_ctrl_new_std(&imx662->ctrls, &imx662_ctrl_ops,
+ V4L2_CID_BRIGHTNESS, 0, 0x3ff, 1, 50);
+
+ v4l2_ctrl_new_std_menu_items(&imx662->ctrls, &imx662_ctrl_ops,
+ V4L2_CID_HDR_SENSOR_MODE,
+ ARRAY_SIZE(imx662_hdr_menu) - 1, 0,
+ IMX662_HDR_OFF, imx662_hdr_menu);
+
+ ret = v4l2_ctrl_new_fwnode_properties(&imx662->ctrls, &imx662_ctrl_ops,
+ &props);
+ if (ret)
+ return dev_err_probe(imx662->dev, ret,
+ "Failed to add controls\n");
+
+ imx662->sd.ctrl_handler = &imx662->ctrls;
+
+ imx662_set_link_limits(imx662);
+ imx662_gain_update(imx662);
+ imx662_set_framing_limits(imx662);
+ imx662_exposure_update(imx662);
+
+ return 0;
+}
+
+static int imx662_init_clk(struct imx662 *imx662)
+{
+ u32 xclk_freq;
+
+ imx662->clk = devm_v4l2_sensor_clk_get(imx662->dev, NULL);
+ if (IS_ERR(imx662->clk))
+ return dev_err_probe(imx662->dev, PTR_ERR(imx662->clk),
+ "Failed to get clock\n");
+
+ xclk_freq = clk_get_rate(imx662->clk);
+
+ switch (xclk_freq) {
+ case 24000000:
+ imx662->inck = IMX662_INCK_SEL_24;
+ break;
+ case 27000000:
+ imx662->inck = IMX662_INCK_SEL_27;
+ break;
+ case 37125000:
+ imx662->inck = IMX662_INCK_SEL_37_125;
+ break;
+ case 72000000:
+ imx662->inck = IMX662_INCK_SEL_72;
+ break;
+ case 74250000:
+ imx662->inck = IMX662_INCK_SEL_74_25;
+ break;
+ default:
+ dev_err(imx662->dev,
+ "External clock frequency %u is not supported\n",
+ xclk_freq);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int imx662_parse_hw_config(struct imx662 *imx662)
+{
+ static const unsigned long supported_2lane =
+ BIT(IMX662_DATARATE_594) | BIT(IMX662_DATARATE_720) |
+ BIT(IMX662_DATARATE_891) | BIT(IMX662_DATARATE_1188) |
+ BIT(IMX662_DATARATE_1440);
+ static const unsigned long supported_4lane =
+ BIT(IMX662_DATARATE_594) | BIT(IMX662_DATARATE_720);
+ struct v4l2_fwnode_endpoint bus_cfg = {
+ .bus_type = V4L2_MBUS_CSI2_DPHY
+ };
+ struct fwnode_handle *ep;
+ unsigned long link_freq;
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < ARRAY_SIZE(imx662->supplies); i++)
+ imx662->supplies[i].supply = imx662_supply_names[i];
+
+ ret = devm_regulator_bulk_get(imx662->dev,
+ ARRAY_SIZE(imx662->supplies),
+ imx662->supplies);
+ if (ret)
+ return dev_err_probe(imx662->dev, ret,
+ "Failed to get supplies\n");
+
+ imx662->reset = devm_gpiod_get_optional(imx662->dev, "reset",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(imx662->reset))
+ return dev_err_probe(imx662->dev, PTR_ERR(imx662->reset),
+ "Failed to get reset GPIO\n");
+
+ ret = imx662_init_clk(imx662);
+ if (ret)
+ return ret;
+
+ imx662->variant = (uintptr_t)of_device_get_match_data(imx662->dev);
+
+ ep = fwnode_graph_get_next_endpoint(dev_fwnode(imx662->dev), NULL);
+ if (!ep)
+ return -ENXIO;
+
+ ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg);
+ fwnode_handle_put(ep);
+ if (ret)
+ return ret;
+
+ switch (bus_cfg.bus.mipi_csi2.num_data_lanes) {
+ case 2:
+ case 4:
+ imx662->num_data_lanes = bus_cfg.bus.mipi_csi2.num_data_lanes;
+ break;
+ default:
+ ret = dev_err_probe(imx662->dev, -EINVAL,
+ "Invalid number of CSI2 data lanes %d\n",
+ bus_cfg.bus.mipi_csi2.num_data_lanes);
+ goto done_endpoint_free;
+ }
+
+ ret = v4l2_link_freq_to_bitmap(imx662->dev, bus_cfg.link_frequencies,
+ bus_cfg.nr_of_link_frequencies,
+ imx662_link_freqs,
+ ARRAY_SIZE(imx662_link_freqs),
+ &link_freq);
+
+ if (imx662->num_data_lanes == 2)
+ link_freq &= supported_2lane;
+ else
+ link_freq &= supported_4lane;
+
+ if (ret || !link_freq) {
+ dev_err(imx662->dev,
+ "No valid link-frequency property found\n");
+ ret = ret ? : -EINVAL;
+ goto done_endpoint_free;
+ }
+
+ imx662->link_freq_index = __fls(link_freq);
+
+done_endpoint_free:
+ v4l2_fwnode_endpoint_free(&bus_cfg);
+
+ return ret;
+}
+
+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 = regulator_bulk_enable(ARRAY_SIZE(imx662->supplies),
+ imx662->supplies);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to enable regulators\n");
+
+ ret = clk_prepare_enable(imx662->clk);
+ if (ret) {
+ dev_err(dev, "Failed to enable clock\n");
+ regulator_bulk_disable(ARRAY_SIZE(imx662->supplies),
+ imx662->supplies);
+ return ret;
+ }
+
+ usleep_range(1, 2);
+ gpiod_set_value_cansleep(imx662->reset, 1);
+ 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);
+
+ gpiod_set_value_cansleep(imx662->reset, 0);
+ regulator_bulk_disable(ARRAY_SIZE(imx662->supplies), imx662->supplies);
+ clk_disable_unprepare(imx662->clk);
+
+ return 0;
+}
+
+static void imx662_subdev_cleanup(struct imx662 *imx662)
+{
+ media_entity_cleanup(&imx662->sd.entity);
+ v4l2_ctrl_handler_free(&imx662->ctrls);
+}
+
+static int imx662_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct imx662 *imx662;
+ 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))
+ return PTR_ERR(imx662->regmap);
+
+ ret = imx662_parse_hw_config(imx662);
+ if (ret)
+ return ret;
+
+ v4l2_i2c_subdev_init(&imx662->sd, client, &imx662_subdev_ops);
+
+ ret = imx662_power_on(dev);
+ if (ret)
+ goto error_subdev;
+
+ pm_runtime_set_active(dev);
+ pm_runtime_get_noresume(dev);
+ pm_runtime_enable(dev);
+ pm_runtime_set_autosuspend_delay(dev, 1000);
+ pm_runtime_use_autosuspend(dev);
+
+ imx662->crop.left = IMX662_PIXEL_ARRAY_LEFT;
+ imx662->crop.top = IMX662_PIXEL_ARRAY_TOP;
+ imx662->crop.width = IMX662_PIXEL_ARRAY_WIDTH;
+ imx662->crop.height = IMX662_PIXEL_ARRAY_HEIGHT;
+
+ imx662->format = &imx662_formats[0];
+
+ cci_write(imx662->regmap, IMX662_STANDBY, 0x01, &ret);
+ cci_write(imx662->regmap, IMX662_XMSTA, 0x01, &ret);
+ if (ret)
+ goto error_pm;
+
+ ret = imx662_ctrls_init(imx662);
+ if (ret)
+ goto error_pm;
+
+ imx662->sd.internal_ops = &imx662_internal_ops;
+ imx662->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
+ V4L2_SUBDEV_FL_HAS_EVENTS;
+ 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) {
+ dev_err(dev, "Failed to init entity pads: %d\n", ret);
+ goto error_pm;
+ }
+
+ imx662->sd.state_lock = imx662->ctrls.lock;
+
+ ret = v4l2_subdev_init_finalize(&imx662->sd);
+ if (ret) {
+ dev_err(dev, "Subdev init error\n");
+ goto error_media;
+ }
+
+ ret = v4l2_async_register_subdev_sensor(&imx662->sd);
+ if (ret) {
+ dev_err(dev, "Failed to register sensor sub-device: %d\n", ret);
+ goto error_media;
+ }
+
+ pm_runtime_put_autosuspend(dev);
+
+ return 0;
+
+error_media:
+ media_entity_cleanup(&imx662->sd.entity);
+
+error_pm:
+ pm_runtime_disable(dev);
+ pm_runtime_put_noidle(dev);
+ imx662_power_off(dev);
+
+error_subdev:
+ imx662_subdev_cleanup(imx662);
+
+ 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);
+ imx662_subdev_cleanup(imx662);
+
+ pm_runtime_disable(&client->dev);
+ if (!pm_runtime_status_suspended(&client->dev))
+ imx662_power_off(&client->dev);
+ pm_runtime_set_suspended(&client->dev);
+}
+
+static const struct of_device_id imx662_of_match[] __maybe_unused = {
+ {
+ .compatible = "sony,imx662",
+ .data = (void *)IMX662_VARIANT_COLOUR
+ },
+ {
+ .compatible = "sony,imx662-mono",
+ .data = (void *)IMX662_VARIANT_MONO
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, imx662_of_match);
+
+static DEFINE_RUNTIME_DEV_PM_OPS(imx662_pm_ops, imx662_power_off,
+ imx662_power_on, NULL);
+
+static struct i2c_driver imx662_i2c_driver = {
+ .probe = imx662_probe,
+ .remove = imx662_remove,
+ .driver = {
+ .name = "imx662",
+ .pm = pm_ptr(&imx662_pm_ops),
+ .of_match_table = imx662_of_match,
+ },
+};
+module_i2c_driver(imx662_i2c_driver);
+
+MODULE_DESCRIPTION("Sony IMX662 CMOS Image Sensor Driver");
+MODULE_AUTHOR("Alexander Shiyan <eagle.alexander923@gmail.com>");
+MODULE_LICENSE("GPL");
--
2.52.0
next prev parent reply other threads:[~2026-03-12 15:05 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-03-12 15:04 [PATCH 0/2] media: i2c: Add support for Sony IMX662 sensor Alexander Shiyan
2026-03-12 15:04 ` [PATCH 1/2] dt-bindings: media: i2c: Add " Alexander Shiyan
2026-03-12 16:30 ` Rob Herring (Arm)
2026-03-13 13:52 ` Krzysztof Kozlowski
2026-03-12 15:04 ` Alexander Shiyan [this message]
2026-03-12 19:50 ` [PATCH 2/2] media: i2c: Add driver for " Dave Stevenson
2026-03-13 1:15 ` tetsuya.nomura
2026-03-13 7:55 ` Alexander Shiyan
2026-03-17 15:52 ` Dave Stevenson
2026-03-13 12:26 ` Alexander Shiyan
2026-03-13 13:46 ` Krzysztof Kozlowski
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260312150437.1091195-3-eagle.alexander923@gmail.com \
--to=eagle.alexander923@gmail.com \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=hansg@kernel.org \
--cc=hverkuil@kernel.org \
--cc=krzk+dt@kernel.org \
--cc=linux-media@vger.kernel.org \
--cc=manivannan.sadhasivam@linaro.org \
--cc=mchehab@kernel.org \
--cc=robh@kernel.org \
--cc=sakari.ailus@linux.intel.com \
--cc=tetsuya.nomura@soho-enterprise.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox