From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 53D0E4746AD; Fri, 7 Aug 2026 12:56:37 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=213.167.242.64 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786107413; cv=none; b=OlUM2C37oGlW27kyzr5YFi6mW1GjBfPXIx4HnfIPATpa3QDC/4QR6J7AaPkwNB/2Wexvp5iw1zVyQq55BFoj5uSap9KjB76eUWa25GLnE07cqIDUp4EwlJwV+SgTNnYHoArY8qV7LED/ZhF8++GiVWuNAwqSc5zW9AQA9g2bhfk= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786107413; c=relaxed/simple; bh=7XHYc0GuDHKhDCgOIFWp8nALwwTVMaEA0kSbrs7XJDk=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=o75cJl8rqYMRoA30xD4JqLuZYEF5M6pSO0Uccz+x/uGzvZ7WD3XH4aL39b1ACyrXqN4h0F/VhoHCVEyHJRzgKV+fH66/oADdwTHmJrwHFvsHh4N6U7w2ixrMTvJTm6dWgoh02CnkRKedNVRF+hRcKNQtA3qnDQDvEfjEKNMdreA= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=ideasonboard.com; spf=pass smtp.mailfrom=ideasonboard.com; dkim=pass (1024-bit key) header.d=ideasonboard.com header.i=@ideasonboard.com header.b=mprps9Jf; arc=none smtp.client-ip=213.167.242.64 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=ideasonboard.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=ideasonboard.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="mprps9Jf" Received: from mail.ideasonboard.com (unknown [IPv6:2401:4900:1c67:4f4:b619:ec54:a907:a006]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 57B7EB06; Fri, 7 Aug 2026 14:55:11 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786107312; bh=7XHYc0GuDHKhDCgOIFWp8nALwwTVMaEA0kSbrs7XJDk=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=mprps9JfMTPgUYyMex2Vox081hSqdBdoW+lmgxnNPm3zKf98NaICdPxkKxD+nf4tc wnDQbzDspVJLrYiEVmmpjes9mClKSROlWFmDVp0Odp0TDQ4JOIBwSwVT8U7Su8oObv k9fiIV84JVuBVJrvoBrm2+vBE3x3qiSBdZ+SIqpc= From: Jai Luthra Date: Fri, 07 Aug 2026 18:25:20 +0530 Subject: [PATCH v2 3/3] media: i2c: Add a driver for Sony IMX708 Precedence: bulk X-Mailing-List: devicetree@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Message-Id: <20260807-imx708-v2-3-7d3d7b57779d@ideasonboard.com> References: <20260807-imx708-v2-0-7d3d7b57779d@ideasonboard.com> In-Reply-To: <20260807-imx708-v2-0-7d3d7b57779d@ideasonboard.com> To: Mauro Carvalho Chehab , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Sakari Ailus Cc: Dave Stevenson , Naushir Patuck , David Plowman , Kieran Bingham , Laurent Pinchart , linux-media@vger.kernel.org, devicetree@vger.kernel.org, Raspberry Pi Kernel Maintenance , Jai Luthra , Nick Hollinghurst X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=42726; i=jai.luthra@ideasonboard.com; h=from:subject:message-id; bh=7XHYc0GuDHKhDCgOIFWp8nALwwTVMaEA0kSbrs7XJDk=; b=owEBbQKS/ZANAwAKAUPekfkkmnFFAcsmYgBqddXf8XSycSaOtamNXMYrQ1kz0mxpzwU1LYBto O5KtL8atBmJAjMEAAEKAB0WIQRN4NgY5dV16NRar8VD3pH5JJpxRQUCanXV3wAKCRBD3pH5JJpx RQr1EACJ+inFCLlK4fIaWdadfMkeJjf81ZyvisTw71hP2rzuvFB8UDTwGWfioZzUEf4MSl5w9RJ s6nkt0wd05cqiwiK3KgHGZImS0asPxxfHmLyAyC2/YAcQu1rvVQ0k1LGLNUWNUaGXx/Ema9FpAN +UHUsEDyxcJ2VhgdsNo7SzVnNhbimhhNLVMqtpP+4ahzdO4gDbSgCp9HSLFFpS5dHulOqHafjrB XeFWA4sYNUOX5Y47tE9ngEi4H50h/VLzjzdrSHFy7680YKXfpb4iBQGQYgGWDTP/VKWaCPM/FOb VwUm5PWdjx7AkpZCZe7yPc8SvS2Du88wfew+kR6FdujnN9BdrXy6LDMRycd2zcln7t6Am8K9Y6V vqBHTXYEhZqd7di3UkDlxqEsuru8ro6RCEmf+YqjpwP6EpZvkH/3WxsUiTp/rySkfZydoevdNvG vTtosQmxFZViRXgpJUhjp9HlMQhbBnF0JpweDgoMp+u8k9NxgnosTDYUNWa6iC4UzBh2g7zRmGF MoF+48RoBknYCwcainitAFIdkQdK0UsMaW3sDqJ4rcBfcseT1UXQWi0xuug6Gmmc5IFqqdyVPcQ VRQ63BYFXmZ8PSgsd9kfjsHAERArfhUx2jQ7P+fww3yPkWq3tPwu4Lclo9Eo9MXeuvHxUmD1TP7 RhNxO97htx22rvA== X-Developer-Key: i=jai.luthra@ideasonboard.com; a=openpgp; fpr=4DE0D818E5D575E8D45AAFC543DE91F9249A7145 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 --- Changes in v2: - Use the helpers from ccs-pll.h to calculate the VT and OP clock tree, setting up the limits following the datasheet instead of the CCS limit registers - Keep the same VT Pixel Rate and OP Link Rate as before. The VT tree runs much faster than the 2-Lane CSI link, and we don't have enough details about the FIFO capacity to support freely-configurable rates just yet. - Drop macros of CCS registers, and include ccs-regs.h to use the existing names - Update Copyright and MODULE_AUTHOR entries --- MAINTAINERS | 1 + drivers/media/i2c/Kconfig | 12 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/imx708.c | 1312 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1326 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 9928f68175ed..7317bd852264 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -25288,6 +25288,7 @@ R: Raspberry Pi Kernel Maintenance 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 diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 5c52007f9cbe..38ec23eb6e38 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -310,6 +310,18 @@ config VIDEO_IMX678 To compile this driver as a module, choose M here: the module will be called imx678. +config VIDEO_IMX708 + tristate "Sony IMX708 sensor support" + depends on GPIOLIB + select V4L2_CCI_I2C + select VIDEO_CCS_PLL + 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/Makefile b/drivers/media/i2c/Makefile index d04bd5724552..15dc9755e321 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -62,6 +62,7 @@ obj-$(CONFIG_VIDEO_IMX355) += imx355.o obj-$(CONFIG_VIDEO_IMX412) += imx412.o obj-$(CONFIG_VIDEO_IMX415) += imx415.o obj-$(CONFIG_VIDEO_IMX678) += imx678.o +obj-$(CONFIG_VIDEO_IMX708) += imx708.o obj-$(CONFIG_VIDEO_IMX471) += imx471.o obj-$(CONFIG_VIDEO_IR_I2C) += ir-kbd-i2c.o obj-$(CONFIG_VIDEO_ISL7998X) += isl7998x.o diff --git a/drivers/media/i2c/imx708.c b/drivers/media/i2c/imx708.c new file mode 100644 index 000000000000..7d5b8bdc2b43 --- /dev/null +++ b/drivers/media/i2c/imx708.c @@ -0,0 +1,1312 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A V4L2 driver for Sony IMX708 + * + * Copyright (C) 2022-2026 Raspberry Pi Ltd + * Copyright (C) 2026 Ideas on Board Oy + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ccs/ccs-regs.h" +#include "ccs-pll.h" + +#define IMX708_CHIP_ID 0x0708 + +#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_IOP_PXCK_DIV CCI_REG8(0x0309) +#define IMX708_REG_IOP_SYCK_DIV CCI_REG8(0x030b) +#define IMX708_REG_IOP_PREPLLCK_DIV CCI_REG8(0x030d) + +/* V_TIMING internal */ +#define IMX708_FRAME_LENGTH_MAX 0xffff +#define IMX708_LINE_LENGTH 15648 +#define IMX708_VBLANK_MIN 58 + +/* Imaging area */ +#define IMX708_REG_ACROPLP_EN CCI_REG8(0x32df) +#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_EXPOSURE_OFFSET 48 +#define IMX708_EXPOSURE_DEFAULT 0x640 +#define IMX708_EXPOSURE_STEP 1 +#define IMX708_EXPOSURE_MIN 8 + +/* Analog gain control */ +#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_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_COLOUR_BALANCE_MIN 0x01 +#define IMX708_COLOUR_BALANCE_MAX 0xffff +#define IMX708_COLOUR_BALANCE_STEP 0x01 +#define IMX708_COLOUR_BALANCE_DEFAULT 0x100 + +/* Test pattern colour components */ +#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 exposure */ +#define IMX708_REG_MID_EXPOSURE CCI_REG16(0x3116) +#define IMX708_REG_MID_ANALOG_GAIN CCI_REG16(0x3118) +#define IMX708_REG_MID_DIGITAL_GAIN CCI_REG16(0x311a) + +#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, +}; + +/* 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, +}; + +static const struct cci_reg_sequence imx708_common_regs[] = { + /* Common */ + { CCS_R_MODE_SELECT, 0x00 }, + { CCS_R_EXTCLK_FREQUENCY_MHZ, 0x1800}, + { 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 }, + { CCS_R_CSI_DATA_FORMAT, 0x0a0a}, + { CCS_R_CSI_LANE_MODE, 0x01 }, + { CCS_R_ABSOLUTE_GAIN_GREENR, 0x0100}, + { CCS_R_ABSOLUTE_GAIN_GREENB, 0x0100}, + { 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 }, + { CCS_R_TEMP_SENSOR_CTRL, 0x01 }, + /* non-HDR defaults */ + { CCS_R_HDR_MODE, 0x62 }, + { CCS_R_EXPOSURE_RATIO, 0x01 }, + { CCI_REG8(0x350c), 0x00 }, + { CCI_REG8(0x350d), 0x00 }, + { CCS_R_DIRECT_SHORT_INTEGRATION_TIME, 0x01f4 }, + { IMX708_REG_MID_EXPOSURE, 0x01f4 }, + { CCS_R_SHORT_ANALOG_GAIN_GLOBAL, 0x0000 }, + { CCS_R_SHORT_DIGITAL_GAIN_GLOBAL, 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[] = { + CCS_TEST_PATTERN_MODE_NONE, + CCS_TEST_PATTERN_MODE_COLOR_BARS, + CCS_TEST_PATTERN_MODE_SOLID_COLOR, + CCS_TEST_PATTERN_MODE_FADE_TO_GREY, + CCS_TEST_PATTERN_MODE_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 ccs_pll pll; + + 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; + + u8 lanes; +}; + +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, CCS_R_FRAME_LENGTH_LINES, 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, CCS_R_ANALOG_GAIN_CODE_GLOBAL, + 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, CCS_R_COARSE_INTEGRATION_TIME, + imx708->exposure->val >> imx708->long_exp_shift, + &ret); + break; + case V4L2_CID_DIGITAL_GAIN: + cci_write(imx708->cci, CCS_R_DIGITAL_GAIN_GLOBAL, ctrl->val, + &ret); + break; + case V4L2_CID_TEST_PATTERN: + cci_write(imx708->cci, CCS_R_TEST_PATTERN_MODE, + imx708_test_pattern_val[ctrl->val], &ret); + break; + case V4L2_CID_TEST_PATTERN_RED: + cci_write(imx708->cci, CCS_R_TEST_DATA_RED, + ctrl->val, &ret); + break; + case V4L2_CID_TEST_PATTERN_GREENR: + cci_write(imx708->cci, CCS_R_TEST_DATA_GREENR, + ctrl->val, &ret); + break; + case V4L2_CID_TEST_PATTERN_BLUE: + cci_write(imx708->cci, CCS_R_TEST_DATA_BLUE, + ctrl->val, &ret); + break; + case V4L2_CID_TEST_PATTERN_GREENB: + cci_write(imx708->cci, CCS_R_TEST_DATA_GREENB, + ctrl->val, &ret); + break; + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + cci_write(imx708->cci, CCS_R_IMAGE_ORIENTATION, + imx708->hflip->val | imx708->vflip->val << 1, &ret); + break; + case V4L2_CID_NOTIFY_GAINS: + cci_write(imx708->cci, CCS_R_ABSOLUTE_GAIN_BLUE, + ctrl->p_new.p_u32[0], &ret); + if (ret) + break; + cci_write(imx708->cci, CCS_R_ABSOLUTE_GAIN_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, CCS_R_LINE_LENGTH_PCK, 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, CCS_R_X_ADDR_START, x_start, &ret); + cci_write(imx708->cci, CCS_R_Y_ADDR_START, y_start, &ret); + cci_write(imx708->cci, CCS_R_X_ADDR_END, x_start + crop->width - 1, + &ret); + cci_write(imx708->cci, CCS_R_Y_ADDR_END, y_start + crop->height - 1, + &ret); + + /* Binning (fixed: no binning) */ + cci_write(imx708->cci, CCS_R_BINNING_MODE, 0x00, &ret); + cci_write(imx708->cci, CCS_R_BINNING_TYPE, 0x11, &ret); + cci_write(imx708->cci, CCS_R_BINNING_WEIGHTING, 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, CCS_R_DIGITAL_CROP_X_OFFSET, 0, &ret); + cci_write(imx708->cci, CCS_R_DIGITAL_CROP_Y_OFFSET, 0, &ret); + cci_write(imx708->cci, CCS_R_DIGITAL_CROP_IMAGE_WIDTH, format->width, + &ret); + cci_write(imx708->cci, CCS_R_DIGITAL_CROP_IMAGE_HEIGHT, format->height, + &ret); + + /* Output size */ + cci_write(imx708->cci, CCS_R_X_OUTPUT_SIZE, format->width, &ret); + cci_write(imx708->cci, CCS_R_Y_OUTPUT_SIZE, format->height, &ret); + + return ret; +} + +static int imx708_pll_calculate(struct imx708 *imx708, u64 link_freq) +{ + struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd); + struct ccs_pll *pll = &imx708->pll; + const struct ccs_pll_limits imx708_pll_limits = { + .min_ext_clk_freq_hz = 6000000, + .max_ext_clk_freq_hz = 27000000, + + .vt_fr = { + .min_pre_pll_clk_div = 1, + .max_pre_pll_clk_div = 4, + .min_pll_ip_clk_freq_hz = 6000000, + .max_pll_ip_clk_freq_hz = 12000000, + .min_pll_multiplier = 92, + .max_pll_multiplier = 360, + .min_pll_op_clk_freq_hz = 1100000000, + .max_pll_op_clk_freq_hz = 2160000000, + }, + .vt_bk = { + /* + * Datasheet suggests 2 or 4 (not 3) for VT_SYS_CLK_DIV. + * It's hard to encode that here. We luckily don't hit + * 3 for the currently used pixel rate. + * TODO: Verify VT_SYS_CLK_DIV = 3. + */ + .min_sys_clk_div = 2, + .max_sys_clk_div = 4, + .min_sys_clk_freq_hz = 540000000, + .max_sys_clk_freq_hz = 1080000000, + /* + * The CCS registers suggest MAX_VT_PIX_CLK_DIV = 10 and + * MIN_VT_PIX_CLK_FREQ = 55MHz, but the datasheet + * explicitly requires the divisor to be fixed to 5, so + * stick to it below and update pix_clk_freq range + * accordingly. + */ + .min_pix_clk_div = 5, + .max_pix_clk_div = 5, + .min_pix_clk_freq_hz = 108000000, + .max_pix_clk_freq_hz = 216000000, + }, + .op_fr = { + .min_pre_pll_clk_div = 1, + .max_pre_pll_clk_div = 27, + .min_pll_ip_clk_freq_hz = 1000000, + .max_pll_ip_clk_freq_hz = 12000000, + /* + * The CCS register suggests MIN_OP_PLL_MULTIPLIER = 92, + * but the datasheet explicitly requires 105 for + * DUAL_PLL mode. + */ + .min_pll_multiplier = 105, + .max_pll_multiplier = 2500, + .min_pll_op_clk_freq_hz = 1250000000, + .max_pll_op_clk_freq_hz = 2500000000, + }, + .op_bk = { + /* + * The CCS register suggests MAX_OP_SYS_CLK_DIV = 2, + * but the datasheet table allows 1,2 and 4. + * We stick to 1,2 to be safe. + * TODO: Verify OP_SYS_CLK_DIV = 3 and 4. + */ + .min_sys_clk_div = 1, + .max_sys_clk_div = 2, + .min_sys_clk_freq_hz = 625000000, + .max_sys_clk_freq_hz = 2500000000, + /* RAW8 and RAW10 */ + .min_pix_clk_div = 8, + .max_pix_clk_div = 10, + /* + * The frequency range is derived from above + * constraints, as the CCS regs (0x1170, 0x1174) are 0. + */ + .min_pix_clk_freq_hz = 62500000, + .max_pix_clk_freq_hz = 312500000, + }, + + .min_line_length_pck_bin = IMX708_LINE_LENGTH, + .min_line_length_pck = IMX708_LINE_LENGTH, + }; + + memset(pll, 0, sizeof(*pll)); + + pll->bus_type = CCS_PLL_BUS_TYPE_CSI2_DPHY; + pll->op_lanes = imx708->lanes; + pll->csi2.lanes = imx708->lanes; + + /* CCS_NUM_OF_VT_LANES (0x1139) = 4 */ + pll->vt_lanes = 4; + + pll->binning_horizontal = 1; + pll->binning_vertical = 1; + pll->scale_m = 1; + pll->scale_n = 1; + pll->bits_per_pixel = 10; + pll->flags = CCS_PLL_FLAG_LANE_SPEED_MODEL + | CCS_PLL_FLAG_DUAL_PLL + | CCS_PLL_FLAG_FIFO_DERATING; + pll->link_freq = link_freq; + pll->ext_clk_freq_hz = imx708->inclk_freq; + pll->pixel_rate_pixel_array = IMX708_PIXEL_RATE; + + return ccs_pll_calculate(&client->dev, &imx708_pll_limits, pll); +} + +static int imx708_program_pll(struct imx708 *imx708) +{ + int ret = 0; + + cci_write(imx708->cci, CCS_R_VT_PIX_CLK_DIV, + imx708->pll.vt_bk.pix_clk_div, &ret); + cci_write(imx708->cci, CCS_R_VT_SYS_CLK_DIV, + imx708->pll.vt_bk.sys_clk_div, &ret); + cci_write(imx708->cci, CCS_R_PRE_PLL_CLK_DIV, + imx708->pll.vt_fr.pre_pll_clk_div, &ret); + cci_write(imx708->cci, CCS_R_PLL_MULTIPLIER, + imx708->pll.vt_fr.pll_multiplier, &ret); + cci_write(imx708->cci, CCS_R_OP_PIX_CLK_DIV, + imx708->pll.op_bk.pix_clk_div, &ret); + cci_write(imx708->cci, CCS_R_OP_SYS_CLK_DIV, + imx708->pll.op_bk.sys_clk_div, &ret); + cci_write(imx708->cci, CCS_R_OP_PRE_PLL_CLK_DIV, + imx708->pll.op_fr.pre_pll_clk_div, &ret); + cci_write(imx708->cci, CCS_R_OP_PLL_MULTIPLIER, + imx708->pll.op_fr.pll_multiplier, &ret); + cci_write(imx708->cci, CCS_R_PLL_MODE, CCS_PLL_MODE_DUAL, + &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); + 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; + } + + /* Program the PLL for the selected link frequency */ + ret = imx708_program_pll(imx708); + if (ret) { + dev_err(&client->dev, "%s failed to configure PLL\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, CCS_R_MODE_SELECT, + CCS_MODE_SELECT_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, CCS_R_MODE_SELECT, + CCS_MODE_SELECT_SOFTWARE_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, CCS_R_SENSOR_MODEL_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; + + ret = imx708_pll_calculate(imx708, + link_freqs[__ffs(imx708->link_freq_bitmap)]); + if (ret) { + v4l2_ctrl_handler_free(ctrl_hdlr); + dev_err(imx708->sd.dev, "PLL calculations failed: %d\n", ret); + return ret; + } + + v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, V4L2_CID_PIXEL_RATE, + imx708->pll.pixel_rate_pixel_array, + imx708->pll.pixel_rate_pixel_array, 1, + imx708->pll.pixel_rate_pixel_array); + + 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->lanes = ep_cfg.bus.mipi_csi2.num_data_lanes; + 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("Nick Hollinghurst "); +MODULE_AUTHOR("David Plowman "); +MODULE_AUTHOR("Jai Luthra "); +MODULE_DESCRIPTION("Sony IMX708 sensor driver"); +MODULE_LICENSE("GPL"); -- 2.54.0