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 B9E3A1A0BF1; Mon, 7 Sep 2026 07:45:29 +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=1788767137; cv=none; b=eLmvO/8CLjzVEeZiYo7KhFrJqs3NzbLAYXsdlcpiGRxlBtbsP1aYWw0CINbazR1wKeCyJQlJQIZbK3tOBLyLQBE1/F5rcjdUQtShAXmpd8tZL3IAaGp9/zPuBVbQSy8LOHHGZqqQD74NNyx0/gjJTEC9X+0mhbqRBP4OxopN/bw= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788767137; c=relaxed/simple; bh=XymJW1C8f8hDzdYIKU2v+00ghnhB4tgSvhXqu3L06R8=; h=Date:From:To:Cc:Subject:Message-ID:References:MIME-Version: Content-Type:Content-Disposition:In-Reply-To; b=WOtY0n+f3Amwjb5bbZyw6oeWZoAAyrrhApqaiwy/Y/7qu292ETYD5qJBo5rd6ocIjNZTuKb8hjwJJB+BwhJHLkDJeoMoeVlLQD6sInXllCUN1NthB8kQSCIcTZGHHEaxpkst0WN8uyd45uFxPUqx6iciuPna9Svl48u6nHW/wqU= 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=Eu8SV55s; 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="Eu8SV55s" Received: from ideasonboard.com (mob-109-113-65-131.net.vodafone.it [109.113.65.131]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id DE7FA8FD; Mon, 7 Sep 2026 09:43:45 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1788767027; bh=XymJW1C8f8hDzdYIKU2v+00ghnhB4tgSvhXqu3L06R8=; h=Date:From:To:Cc:Subject:References:In-Reply-To:From; b=Eu8SV55sqlpjfFawTLU/pm6QvjH4EMxj3804G8YtX1sTxi1nBlwHDLept1nyyVsC6 b51avZ6iBTkHRJVmlQimi3O3k0zT9DDb304oHl1QOw+B5zFpUi+6Z/8c4S6/OWMsvD ZjPbci62TEm9I24tqV/7/L62qdT2aUn1RTzSYkhU= Date: Mon, 7 Sep 2026 09:45:16 +0200 From: Jacopo Mondi To: Sakari Ailus Cc: Jacopo Mondi , Philippe Baetens , Mauro Carvalho Chehab , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Kieran Bingham , Jai Luthra , linux-media@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org Subject: Re: [PATCH v2 2/2] media: i2c: mira016: Add driver for Mira016 Message-ID: References: <20260904-mira016-v2-0-1dcf7b3a807e@ideasonboard.com> <20260904-mira016-v2-2-1dcf7b3a807e@ideasonboard.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Disposition: inline Content-Transfer-Encoding: 8bit In-Reply-To: Hi Sakari, thanks for the review On Sun, Sep 06, 2026 at 11:28:02PM +0300, Sakari Ailus wrote: > Hi Jacopo, > > Thanks for the update. Just to be sure there are no mis-understandings: this is a new driver, not an update to the mira220 one (for which, I will send a tiny update soon as well) :) > > I have a few comments below. > > On Fri, Sep 04, 2026 at 12:43:41PM +0200, Jacopo Mondi wrote: > > Add driver for the ams OSRAM Mira016 sensor. > > > > Signed-off-by: Jacopo Mondi > > --- > > MAINTAINERS | 1 + > > drivers/media/i2c/Kconfig | 12 + > > drivers/media/i2c/Makefile | 1 + > > drivers/media/i2c/mira016.c | 2294 +++++++++++++++++++++++++++++++++++++++++++ > > 4 files changed, 2308 insertions(+) > > > > diff --git a/MAINTAINERS b/MAINTAINERS > > index 1c24b05b378b..3bbca15e9430 100644 > > --- a/MAINTAINERS > > +++ b/MAINTAINERS > > @@ -1460,6 +1460,7 @@ M: Philippe Baetens > > L: linux-media@vger.kernel.org > > S: Maintained > > F: Documentation/devicetree/bindings/media/i2c/ams,mira016.yaml > > +F: drivers/media/i2c/mira016.c > > > > AMT (Automatic Multicast Tunneling) > > M: Taehee Yoo > > diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig > > index 5c52007f9cbe..85eb22e9f231 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_MIRA016 > > + tristate "ams OSRAM MIRA016 sensor support" > > + depends on I2C && VIDEO_DEV > > + depends on GPIOLIB > > + select V4L2_CCI_I2C > > + help > > + This is a Video4Linux2 sensor driver for the ams OSRAM > > + MIRA016 camera. > > + > > + To compile this driver as a module, choose M here: the > > + module will be called mira016. > > + > > config VIDEO_MAX9271_LIB > > tristate > > > > diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile > > index d04bd5724552..35d162ffbddc 100644 > > --- a/drivers/media/i2c/Makefile > > +++ b/drivers/media/i2c/Makefile > > @@ -74,6 +74,7 @@ obj-$(CONFIG_VIDEO_MAX9271_LIB) += max9271.o > > obj-$(CONFIG_VIDEO_MAX9286) += max9286.o > > obj-$(CONFIG_VIDEO_MAX96714) += max96714.o > > obj-$(CONFIG_VIDEO_MAX96717) += max96717.o > > +obj-$(CONFIG_VIDEO_MIRA016) += mira016.o > > obj-$(CONFIG_VIDEO_ML86V7667) += ml86v7667.o > > obj-$(CONFIG_VIDEO_MSP3400) += msp3400.o > > obj-$(CONFIG_VIDEO_MT9M001) += mt9m001.o > > diff --git a/drivers/media/i2c/mira016.c b/drivers/media/i2c/mira016.c > > new file mode 100644 > > index 000000000000..8b8fcbd50fe2 > > --- /dev/null > > +++ b/drivers/media/i2c/mira016.c > > @@ -0,0 +1,2294 @@ > > +// SPDX-License-Identifier: GPL-2.0 > > +/* > > + * Copyright (C) 2026, ams OSRAM > > + * Copyright (C) 2026, Ideas On Board Oy > > + * > > + * Based on AMS Mira220 driver > > + * Copyright (C) 2026, ams OSRAM > > + */ > > + > > +#include > > +#include > > +#include > > +#include > > +#include > > +#include > > +#include > > +#include > > +#include > > + > > +#include > > +#include > > +#include > > +#include > > +#include > > +#include > > +#include > > + > > +/* Register select. */ > > +#define MIRA016_BANK_SEL_REG CCI_REG8(0xe000) > > +#define MIRA016_ACTIVE_CONTEXT_REG CCI_REG8(0x4002) > > +#define MIRA016_NEXT_ACTIVE_CONTEXT_REG CCI_REG8(0xe003) > > +#define MIRA016_RW_CONTEXT_REG CCI_REG8(0xe004) > > +#define MIRA016_AUTO_SWITCH_CONTEXT_REG CCI_REG8(0xe005) > > +#define MIRA016_PARAM_HOLD_REG CCI_REG8(0x0006) > > +#define MIRA016_DISABLE_CONTEXTSYNC_REG CCI_REG8(0xe008) > > +#define MIRA016_DISABLE_CONTEXTSYNC BIT(0) > > +#define MIRA016_CMD_REQ_1_REG CCI_REG8(0x000a) > > +#define MIRA016_CMD_HALT_BLOCK_REG CCI_REG8(0x000c) > > + > > +/* Chip id */ > > +#define MIRA016_CHIP_ID_REG CCI_REG8(0x011B) > > +#define MIRA016_CHIP_ID 33 > > + > > +/* PLL config */ > > +#define MHZ(f) ((u32)(f) * HZ_PER_MHZ) > > +#define MIRA016_PLL_N_MIN 1 > > +#define MIRA016_PLL_N_MAX 32 > > +#define MIRA016_PLL_M_MIN 16 > > +#define MIRA016_PLL_M_MAX 255 > > +#define MIRA016_PLL_PLL1_MIN MHZ(12) > > +#define MIRA016_PLL_PLL1_MAX MHZ(22) > > +#define MIRA016_PLL_PLL2_MIN MHZ(640) > > +#define MIRA016_PLL_PLL2_MAX MHZ(1500) > > + > > +#define MIRA016_PLL_DIV_M_REG CCI_REG8(0x2076) > > +#define MIRA016_PLL_DIV_N_REG CCI_REG8(0x2077) > > +#define MIRA016_CLKGEN_CP_DIV_REG CCI_REG8(0x00ce) > > +#define MIRA016_OTP_GRANULARITY_REG CCI_REG8(0x0070) > > +#define MIRA016_GRAN_TG_REG CCI_REG8(0x016d) > > +#define MIRA016_LUT_DEL008_REG CCI_REG8(0x0176) > > +#define MIRA016_CLKGEN_TX_ESC_H_REG CCI_REG24(0x20c6) > > +#define MIRA016_CLKGEN_TX_ESC_L_REG CCI_REG24(0x20c9) > > +#define MIRA016_PLL_PD_REG CCI_REG8(0x2075) > > +#define MIRA016_PLL_LOCK_REG CCI_REG8(0x207c) > > +#define MIRA016_PLL_LOCKED BIT(0) > > + > > +#define MIRA016_EXP_TIME_L_REG CCI_REG32(0x000e) > > +#define MIRA016_EXP_TIME_S_REG CCI_REG32(0x0012) > > +#define MIRA016_NUM_FRAMES_REG CCI_REG32(0x0007) > > +#define MIRA016_TARGET_FRAME_TIME_REG CCI_REG32(0x0008) > > + > > +#define MIRA016_TIME_UNIT_REG CCI_REG16(0x0012) > > +#define MIRA016_GLOB_TIME_REG CCI_REG16(0x015a) > > +#define MIRA016_GLOB_TIME_A_REG CCI_REG16(0x015c) > > +#define MIRA016_GLOB_TIME_B_REG CCI_REG16(0x015e) > > +/* > > + * TODO: LPS timings should be calculated to support new link frequency as > > + * their value derives from the BYTE_PERIOD bus parameter. > > + * > > + * Use the hardcoded values for 1500Mbps. > > + */ > > +#define MIRA016_ENTER_LPS_TIME_REG CCI_REG16(0x0162) > > +#define MIRA016_ENTER_LPS_TIME_1500MBPS 5 > > +#define MIRA016_EXIT_LPS_TIME_REG CCI_REG16(0x0164) > > +#define MIRA016_EXIT_LPS_TIME_1500MBPS 0x479 > > +#define MIRA016_LPS_CYCLE_TIME_REG CCI_REG16(0x0166) > > +#define MIRA016_LPS_CYCLE_TIME_1500MBPS 0x479 > > + > > +#define MIRA016_ROW_TIMINGS_REG CCI_REG16(0x0032) > > + > > +#define MIRA016_PIXEL_ARRAY_TOP 14 > > +#define MIRA016_PIXEL_ARRAY_LEFT 14 > > +#define MIRA016_PIXEL_ARRAY_WIDTH 400 > > +#define MIRA016_PIXEL_ARRAY_HEIGHT 400 > > +#define MIRA016_NATIVE_WIDTH 428 > > +#define MIRA016_NATIVE_HEIGHT 428 > > + > > +/* X ROI */ > > +#define MIRA016_XWIN_LEFT_REG CCI_REG8(0xe02c) > > +#define MIRA016_XWIN_RIGHT_REG CCI_REG8(0xe02e) > > +#define MIRA016_XMIRROR_REG CCI_REG8(0xe030) > > +#define MIRA016_XSUBSAMPLING_REG CCI_REG8(0xe025) > > +#define MIRA016_XBINNING_REG CCI_REG8(0xe02a) > > +#define MIRA016_MIPI_DATA_FIFO_THR_REG CCI_REG8(0x2029) > > +#define MIRA016_HSYNC_LENGTH CCI_REG16(0x0034) > > + > > +/* Y ROI Bank 1 */ > > +#define MIRA016_YWIN_ENA_REG CCI_REG16(0x001e) > > +#define MIRA016_YBINNING_REG CCI_REG8(0x002b) > > +/* Y ROI Bank 0 */ > > +#define MIRA016_YWIN_BLACK_REG CCI_REG16(0X001f) > > +#define MIRA016_YWIN_DIR_REG CCI_REG8(0x0023) > > +#define MIRA016_YWIN_BASE_SIZE 0x0024 > > +#define MIRA016_YWIN_BASE_START 0x0026 > > +#define MIRA016_YWIN_BASE_SUBS 0x0028 > > +#define MIRA016_YWIN_SIZE_REG(x) CCI_REG16(MIRA016_YWIN_BASE_SIZE \ > > + + (x) * 5) > > +#define MIRA016_YWIN_START_REG(x) CCI_REG16(MIRA016_YWIN_BASE_START \ > > + + (x) * 5) > > +#define MIRA016_YWIN_SUBS_REG(x) CCI_REG8(MIRA016_YWIN_BASE_SUBS \ > > + + (x) * 5) > > +#define MIRA016_NUM_Y_WIND 10 > > + > > +/* Data format */ > > +#define MIRA016_DPATH_BITMODE_REG CCI_REG8(0x016a) > > +#define MIRA016_DPATH_BITWIDTH_A_REG CCI_REG16(0x00c0) > > +#define MIRA016_DPATH_BITWIDTH_B_REG CCI_REG16(0x00c2) > > +#define MIRA016_CSI2_DATA_TYPE_REG CCI_REG8(0x0168) > > + > > +/* Fine analogue gain */ > > +#define MIRA016_GDIG_PREAMP CCI_REG8(0x0195) > > +#define MIRA016_BIAS_RG_ADCGAIN CCI_REG8(0x01f0) > > +#define MIRA016_BIAS_RG_MULT CCI_REG8(0x01f3) > > + > > +/* MIPI CSI-2 link frequency for 1.5Mbps per lane */ > > +#define MIRA016_LINK_FREQ_750M 750000000 > > + > > +#define MIRA016_DEFAULT_DURATION_NSEC 8333000ULL > > +#define MIRA016_MAX_VBLANK 500000 > > + > > +/* > > + * Copied from the libcamera IPA. The sensor manual doesn't really specify a > > + * minimum difference between the exposure time and the frame duration. > > + */ > > +#define MIRA016_FRAME_INTEGRATION_DIFF 4 > > + > > +#define MIRA016_MAX_EXPOSURE 500000 > > +#define MIRA016_EXPOSURE_DEF_NSEC 1000000ULL > > + > > +static const s64 mira016_link_freqs[] = { > > + [0] = MIRA016_LINK_FREQ_750M, > > +}; > > + > > +static const char *const mira016_supplies[] = { > > + /* Supplies can be enabled in any order */ > > + "vdd28", /* Analog supply, 2.8 volts */ > > + "vdd11", /* Digital supply, 1.1 volts */ > > +}; > > + > > +struct mira016 { > > + struct device *dev; > > + struct v4l2_subdev sd; > > + struct media_pad pad; > > + > > + struct clk *xclk; > > + u32 xclk_freq; > > + > > + struct regulator_bulk_data supplies[ARRAY_SIZE(mira016_supplies)]; > > + > > + struct gpio_desc *reset_gpio; > > + > > + unsigned long link_freq_bitmap; > > + unsigned int bus_config; > > + > > + struct v4l2_ctrl_handler ctrl_handler; > > + struct v4l2_ctrl *vflip; > > + struct v4l2_ctrl *hflip; > > + struct v4l2_ctrl *vblank; > > + struct v4l2_ctrl *exposure; > > + struct v4l2_ctrl *gain; > > + struct v4l2_ctrl *prate; > > + > > + struct { > > + u8 n; > > + u8 m; > > + u8 cp_div; > > + u8 otp_gran; > > + u8 gran_tg; > > + u8 lut_del; > > + u32 esc_h; > > + u32 esc_l; > > + u32 esc_period; > > + u32 byte_period; > > + u64 data_rate_mbps; > > + } pll; > > + > > + struct { > > + /* > > + * Mira016 timings registers are expressed in useconds > > + * with a time base of `seq_time_base`: > > + * > > + * line_duration = seq_time_base * row_length; > > + * > > + * As a typical value for seq_time_base is 5.333 nanoseconds > > + * for a 1500Mbps@24MHz configuration, calculate the base time > > + * in picoseconds otherwise we would loose precision. > > s/loose/lose/ > > > + */ > > + u32 time_base; > > + u32 seq_time_base; > > + u32 min_exposure_time; > > + u32 glob_time; > > + u32 row_length; > > + } timings; > > + > > + struct regmap *regmap; > > +}; > > + > > +static inline struct mira016 *to_mira016(struct v4l2_subdev *sd) > > +{ > > + return container_of_const(sd, struct mira016, sd); > > +} > > + > > +/* > > + * All Mira016 timing registers are expressed in useconds, while V4L2 > > + * specifies timings in line. > > + * > > + * Convert back and forth using these helpers which use the row time (in > > + * picoseconds, see the comment on the 'timings' anonymous structure). > > + */ > > + > > Extra newline. > Intentional, the comment applies to all below helpers > > +static inline u32 mira016_trow_psec(struct mira016 *mira016) > > +{ > > + return mira016->timings.seq_time_base * mira016->timings.row_length; > > +} > > + > > +static inline u32 mira016_lines_to_usec(struct mira016 *mira016, u32 lines) > > +{ > > + return (u64)lines * mira016_trow_psec(mira016) / HZ_PER_MHZ; > > div_u64()? I wonder why this wasn't caught by any robot testing the above code with a 32 bit build > > > +} > > + > > +static inline u32 mira016_nsec_to_lines(struct mira016 *mira016, u32 nsec) > > +{ > > + return nsec / (mira016_trow_psec(mira016) / HZ_PER_KHZ); > > +} > > + > > +static inline u32 mira016_calc_prate(struct mira016 *mira016, u32 h_tot) > > +{ > > + /* > > + * Pixel rate is calculate as the row duration divided by the total line > > + * length. > > + * > > + * pixel time (psec) = t_row(psec) / h_tot > > + * pixel rate (mbps) = 10^12 / pixel_time > > + * = 10^12 * h_tot / t_row > > + * = h_tot * 10^6 / t_row * 10^6 to avoid overflows > > + */ > > + u32 trow_psec = mira016_trow_psec(mira016); > > + > > + return h_tot * HZ_PER_MHZ / trow_psec * HZ_PER_MHZ; > > Can this overflow? > I don't think so. I chose to multiply by 10^6 two times before and after the division to avoid overflows. Should I re-consider something ? > > +} > > + > > +static inline u32 mira016_calc_min_vblank(struct mira016 *mira016, u32 y_tot) > > +{ > > + /* > > + * See 3.15.2 Frame Rate, equation 4. > > + * > > + * TODO: The minimum frame duration has to be expanded if embedded data > > + * are used. > > + */ > > + u32 trow_nsec = mira016_trow_psec(mira016) / HZ_PER_KHZ; > > + u32 min_duration_nsec = trow_nsec * (y_tot + 35) + 50 * HZ_PER_KHZ; > > What are 35 and 50 here? > No idea, the sensor datasheet doesn't describe those values > > + > > + return mira016_nsec_to_lines(mira016, min_duration_nsec) - y_tot; > > +} > > + > > +static const struct cci_reg_sequence mira016_8b_fine_gain_init[] = { > > + { CCI_REG8(0xe000), 0x0 }, > > + { CCI_REG8(0x01bb), 0xb4 }, > > + { CCI_REG8(0x01bc), 0xac }, > > + { CCI_REG8(0x00d0), 0x0 }, > > + { CCI_REG8(0x016e), 0xfd }, > > + { CCI_REG8(0x0172), 0x0 }, > > + { CCI_REG8(0x0173), 0x0 }, > > + { CCI_REG8(0x016f), 0x7e }, > > + { CCI_REG8(0x0170), 0x0 }, > > + { CCI_REG8(0x0171), 0xfd }, > > + { CCI_REG8(0x0174), 0x0 }, > > + { CCI_REG8(0x0175), 0x0 }, > > + { CCI_REG8(0x0177), 0x78 }, > > + { CCI_REG8(0x018b), 0x4 }, > > + { CCI_REG8(0x018c), 0xe }, > > + { CCI_REG8(0x018d), 0x2 }, > > + { CCI_REG8(0x018e), 0x56 }, > > + { CCI_REG8(0x018f), 0x6 }, > > + { CCI_REG8(0x0190), 0xe }, > > + { CCI_REG8(0x00e8), 0x8 }, > > + { CCI_REG8(0x01ee), 0x1b }, > > + { CCI_REG8(0x01ef), 0x46 }, > > + { CCI_REG8(0x01a2), 0x0 }, > > + { CCI_REG8(0x01a3), 0x1 }, > > + { CCI_REG8(0x031f), 0x0 }, > > + { CCI_REG8(0x0320), 0xa }, > > + { CCI_REG8(0x01a6), 0x0 }, > > + { CCI_REG8(0x01a7), 0x98 }, > > + { CCI_REG8(0x01a4), 0x4 }, > > + { CCI_REG8(0x01a5), 0x27 }, > > + { CCI_REG8(0x0321), 0x4 }, > > + { CCI_REG8(0x0322), 0x30 }, > > + { CCI_REG8(0x01a8), 0x4 }, > > + { CCI_REG8(0x01a9), 0xbe }, > > + { CCI_REG8(0x01a0), 0x1 }, > > + { CCI_REG8(0x01a1), 0x3a }, > > + { CCI_REG8(0x01b2), 0x1 }, > > + { CCI_REG8(0x01b3), 0x54 }, > > + { CCI_REG8(0x01b0), 0x1 }, > > + { CCI_REG8(0x01b1), 0x4d }, > > + { CCI_REG8(0x01ac), 0x1 }, > > + { CCI_REG8(0x01ad), 0x58 }, > > + { CCI_REG8(0x01f0), 0x24 }, > > + { CCI_REG8(0x01f3), 0x2 }, > > + { CCI_REG8(0xe000), 0x1 }, > > + { CCI_REG8(0xe000), 0x1 }, > > + { CCI_REG8(0xe024), 0x3 }, > > + { CCI_REG8(0xe000), 0x0 }, > > + { CCI_REG8(0xe000), 0x0 }, > > + { CCI_REG8(0xe000), 0x0 }, > > Interesting. > Yes the gain handling in this line of sensors is ... intersting :) > > + { CCI_REG8(0x005c), 0x0 }, > > + { CCI_REG8(0x005d), 0x18 }, > > + { CCI_REG8(0xe000), 0x0 }, > > +}; > > + > > +static const struct cci_reg_sequence mira016_10b_fine_gain_init[] = { > > + { CCI_REG8(0xe000), 0x0 }, > > + { CCI_REG8(0x01bb), 0xb4 }, > > + { CCI_REG8(0x01bc), 0xac }, > > + { CCI_REG8(0x00d0), 0x0 }, > > + { CCI_REG8(0x016e), 0xfd }, > > + { CCI_REG8(0x0172), 0x0 }, > > + { CCI_REG8(0x0173), 0x0 }, > > + { CCI_REG8(0x016f), 0x7e }, > > + { CCI_REG8(0x0170), 0x0 }, > > + { CCI_REG8(0x0171), 0xfd }, > > + { CCI_REG8(0x0174), 0x0 }, > > + { CCI_REG8(0x0175), 0x0 }, > > + { CCI_REG8(0x0177), 0x78 }, > > + { CCI_REG8(0x018b), 0x4 }, > > + { CCI_REG8(0x018c), 0x4 }, > > + { CCI_REG8(0x018d), 0x2 }, > > + { CCI_REG8(0x018e), 0x60 }, > > + { CCI_REG8(0x018f), 0x6 }, > > + { CCI_REG8(0x0190), 0x4 }, > > + { CCI_REG8(0x00e8), 0x8 }, > > + { CCI_REG8(0xe000), 0x1 }, > > + { CCI_REG8(0xe000), 0x1 }, > > + { CCI_REG8(0xe024), 0xf }, > > + { CCI_REG8(0xe000), 0x0 }, > > + { CCI_REG8(0xe000), 0x0 }, > > + { CCI_REG8(0x005c), 0x0 }, > > + { CCI_REG8(0x005d), 0x18 }, > > + { CCI_REG8(0x01ee), 0x1b }, > > + { CCI_REG8(0x01ef), 0x46 }, > > + { CCI_REG8(0x01a2), 0x0 }, > > + { CCI_REG8(0x01a3), 0x1 }, > > + { CCI_REG8(0x031f), 0x0 }, > > + { CCI_REG8(0x0320), 0xa }, > > + { CCI_REG8(0x01a6), 0x0 }, > > + { CCI_REG8(0x01a7), 0x98 }, > > + { CCI_REG8(0x01a4), 0x4 }, > > + { CCI_REG8(0x01a5), 0x27 }, > > + { CCI_REG8(0x0321), 0x4 }, > > + { CCI_REG8(0x0322), 0x30 }, > > + { CCI_REG8(0x01a8), 0x4 }, > > + { CCI_REG8(0x01a9), 0xbe }, > > + { CCI_REG8(0x01a0), 0x0 }, > > + { CCI_REG8(0x01a1), 0xfb }, > > + { CCI_REG8(0x01b2), 0x1 }, > > + { CCI_REG8(0x01b3), 0x15 }, > > + { CCI_REG8(0x01b0), 0x1 }, > > + { CCI_REG8(0x01b1), 0xe }, > > + { CCI_REG8(0x01ac), 0x1 }, > > + { CCI_REG8(0x01ad), 0x19 }, > > + { CCI_REG8(0x01f0), 0x24 }, > > + { CCI_REG8(0x01f3), 0x2 }, > > +}; > > + > > +static const struct cci_reg_sequence mira016_12b_fixed_gain_init[] = { > > + { CCI_REG8(0xe000), 0x0 }, > > + { CCI_REG8(0x01bb), 0xb4 }, > > + { CCI_REG8(0x01bc), 0xac }, > > + { CCI_REG8(0x00d0), 0x0 }, > > + { CCI_REG8(0x01f0), 0x7 }, > > + { CCI_REG8(0x01f3), 0x1 }, > > + { CCI_REG8(0x016e), 0xfd }, > > + { CCI_REG8(0x0172), 0x0 }, > > + { CCI_REG8(0x0173), 0x0 }, > > + { CCI_REG8(0x016f), 0xff }, > > + { CCI_REG8(0x0170), 0xff }, > > + { CCI_REG8(0x0171), 0xfd }, > > + { CCI_REG8(0x0174), 0x0 }, > > + { CCI_REG8(0x0175), 0x0 }, > > + { CCI_REG8(0x0177), 0xdc }, > > + { CCI_REG8(0x018b), 0x4 }, > > + { CCI_REG8(0x018c), 0xe }, > > + { CCI_REG8(0x018d), 0x2 }, > > + { CCI_REG8(0x018e), 0x56 }, > > + { CCI_REG8(0x018f), 0xc }, > > + { CCI_REG8(0x0190), 0xe }, > > + { CCI_REG8(0x00e8), 0x3 }, > > + { CCI_REG8(0xe000), 0x1 }, > > + { CCI_REG8(0xe000), 0x1 }, > > + { CCI_REG8(0xe024), 0xf }, > > + { CCI_REG8(0xe000), 0x0 }, > > + { CCI_REG8(0xe000), 0x0 }, > > + { CCI_REG8(0x005c), 0x0 }, > > + { CCI_REG8(0x005d), 0x60 }, > > + { CCI_REG8(0x01ee), 0x1b }, > > + { CCI_REG8(0x01ef), 0x46 }, > > + { CCI_REG8(0x01a2), 0x0 }, > > + { CCI_REG8(0x01a3), 0x1 }, > > + { CCI_REG8(0x031f), 0x0 }, > > + { CCI_REG8(0x0320), 0xa }, > > + { CCI_REG8(0x01a6), 0x0 }, > > + { CCI_REG8(0x01a7), 0x98 }, > > + { CCI_REG8(0x01a4), 0x5 }, > > + { CCI_REG8(0x01a5), 0xa7 }, > > + { CCI_REG8(0x0321), 0x5 }, > > + { CCI_REG8(0x0322), 0xb0 }, > > + { CCI_REG8(0x01a8), 0x6 }, > > + { CCI_REG8(0x01a9), 0x3e }, > > + { CCI_REG8(0x01a0), 0x0 }, > > + { CCI_REG8(0x01a1), 0xf3 }, > > + { CCI_REG8(0x01b2), 0x1 }, > > + { CCI_REG8(0x01b3), 0xd }, > > + { CCI_REG8(0x01b0), 0x1 }, > > + { CCI_REG8(0x01b1), 0x6 }, > > + { CCI_REG8(0x01ac), 0x1 }, > > + { CCI_REG8(0x01ad), 0x11 }, > > +}; > > + > > +static const struct cci_reg_sequence mira016_init_sequence[] = { > > + { CCI_REG8(0x01e4), 0x0 }, > > + { CCI_REG8(0x01e5), 0x13 }, > > + { CCI_REG8(0x01e2), 0x17 }, > > + { CCI_REG8(0x01e3), 0xa8 }, > > + { CCI_REG8(0x01e6), 0x0 }, > > + { CCI_REG8(0x01e7), 0xca }, > > + { CCI_REG8(0x016c), 0x1 }, > > + { CCI_REG8(0x016b), 0x1 }, > > + { CCI_REG8(0x0208), 0x1 }, > > + { CCI_REG8(0x0209), 0xf0 }, > > + { CCI_REG8(0x020a), 0x3 }, > > + { CCI_REG8(0x020b), 0x4d }, > > + { CCI_REG8(0x020c), 0x2 }, > > + { CCI_REG8(0x020d), 0x10 }, > > + { CCI_REG8(0x020e), 0x3 }, > > + { CCI_REG8(0x020f), 0x1 }, > > + { CCI_REG8(0x0210), 0x0 }, > > + { CCI_REG8(0x0211), 0x13 }, > > + { CCI_REG8(0x0212), 0x0 }, > > + { CCI_REG8(0x0213), 0x3 }, > > + { CCI_REG8(0x0214), 0x3 }, > > + { CCI_REG8(0x0215), 0xef }, > > + { CCI_REG8(0x0216), 0x3 }, > > + { CCI_REG8(0x0217), 0xf3 }, > > + { CCI_REG8(0x0218), 0x3 }, > > + { CCI_REG8(0x0219), 0xf4 }, > > + { CCI_REG8(0x021a), 0x0 }, > > + { CCI_REG8(0x021b), 0x1 }, > > + { CCI_REG8(0x021c), 0x3 }, > > + { CCI_REG8(0x021d), 0xf8 }, > > + { CCI_REG8(0x021e), 0x0 }, > > + { CCI_REG8(0x021f), 0x2 }, > > + { CCI_REG8(0x0220), 0x1 }, > > + { CCI_REG8(0x0221), 0xf2 }, > > + { CCI_REG8(0x0222), 0x3 }, > > + { CCI_REG8(0x0223), 0x1b }, > > + { CCI_REG8(0x0224), 0x0 }, > > + { CCI_REG8(0x0225), 0x21 }, > > + { CCI_REG8(0x0226), 0x3 }, > > + { CCI_REG8(0x0227), 0xf0 }, > > + { CCI_REG8(0x0228), 0x3 }, > > + { CCI_REG8(0x0229), 0xf1 }, > > + { CCI_REG8(0x022a), 0x3 }, > > + { CCI_REG8(0x022b), 0xf2 }, > > + { CCI_REG8(0x022c), 0x3 }, > > + { CCI_REG8(0x022d), 0xf5 }, > > + { CCI_REG8(0x022e), 0x3 }, > > + { CCI_REG8(0x022f), 0xf6 }, > > + { CCI_REG8(0x0230), 0x0 }, > > + { CCI_REG8(0x0231), 0xc1 }, > > + { CCI_REG8(0x0232), 0x0 }, > > + { CCI_REG8(0x0233), 0x2 }, > > + { CCI_REG8(0x0234), 0x1 }, > > + { CCI_REG8(0x0235), 0xf2 }, > > + { CCI_REG8(0x0236), 0x3 }, > > + { CCI_REG8(0x0237), 0x6b }, > > + { CCI_REG8(0x0238), 0x3 }, > > + { CCI_REG8(0x0239), 0xff }, > > + { CCI_REG8(0x023a), 0x3 }, > > + { CCI_REG8(0x023b), 0x31 }, > > + { CCI_REG8(0x023c), 0x1 }, > > + { CCI_REG8(0x023d), 0xf0 }, > > + { CCI_REG8(0x023e), 0x3 }, > > + { CCI_REG8(0x023f), 0x87 }, > > + { CCI_REG8(0x0240), 0x0 }, > > + { CCI_REG8(0x0241), 0xa }, > > + { CCI_REG8(0x0242), 0x0 }, > > + { CCI_REG8(0x0243), 0xb }, > > + { CCI_REG8(0x0244), 0x1 }, > > + { CCI_REG8(0x0245), 0xf9 }, > > + { CCI_REG8(0x0246), 0x3 }, > > + { CCI_REG8(0x0247), 0xd }, > > + { CCI_REG8(0x0248), 0x0 }, > > + { CCI_REG8(0x0249), 0x7 }, > > + { CCI_REG8(0x024a), 0x3 }, > > + { CCI_REG8(0x024b), 0xef }, > > + { CCI_REG8(0x024c), 0x3 }, > > + { CCI_REG8(0x024d), 0xf3 }, > > + { CCI_REG8(0x024e), 0x3 }, > > + { CCI_REG8(0x024f), 0xf4 }, > > + { CCI_REG8(0x0250), 0x3 }, > > + { CCI_REG8(0x0251), 0x0 }, > > + { CCI_REG8(0x0252), 0x0 }, > > + { CCI_REG8(0x0253), 0x7 }, > > + { CCI_REG8(0x0254), 0x0 }, > > + { CCI_REG8(0x0255), 0xc }, > > + { CCI_REG8(0x0256), 0x1 }, > > + { CCI_REG8(0x0257), 0xf1 }, > > + { CCI_REG8(0x0258), 0x3 }, > > + { CCI_REG8(0x0259), 0x43 }, > > + { CCI_REG8(0x025a), 0x1 }, > > + { CCI_REG8(0x025b), 0xf8 }, > > + { CCI_REG8(0x025c), 0x3 }, > > + { CCI_REG8(0x025d), 0x10 }, > > + { CCI_REG8(0x025e), 0x0 }, > > + { CCI_REG8(0x025f), 0x7 }, > > + { CCI_REG8(0x0260), 0x3 }, > > + { CCI_REG8(0x0261), 0xf0 }, > > + { CCI_REG8(0x0262), 0x3 }, > > + { CCI_REG8(0x0263), 0xf1 }, > > + { CCI_REG8(0x0264), 0x3 }, > > + { CCI_REG8(0x0265), 0xf2 }, > > + { CCI_REG8(0x0266), 0x3 }, > > + { CCI_REG8(0x0267), 0xf5 }, > > + { CCI_REG8(0x0268), 0x3 }, > > + { CCI_REG8(0x0269), 0xf6 }, > > + { CCI_REG8(0x026a), 0x3 }, > > + { CCI_REG8(0x026b), 0x0 }, > > + { CCI_REG8(0x026c), 0x2 }, > > + { CCI_REG8(0x026d), 0x87 }, > > + { CCI_REG8(0x026e), 0x0 }, > > + { CCI_REG8(0x026f), 0x1 }, > > + { CCI_REG8(0x0270), 0x3 }, > > + { CCI_REG8(0x0271), 0xff }, > > + { CCI_REG8(0x0272), 0x3 }, > > + { CCI_REG8(0x0273), 0x0 }, > > + { CCI_REG8(0x0274), 0x3 }, > > + { CCI_REG8(0x0275), 0xff }, > > + { CCI_REG8(0x0276), 0x2 }, > > + { CCI_REG8(0x0277), 0x87 }, > > + { CCI_REG8(0x0278), 0x3 }, > > + { CCI_REG8(0x0279), 0x2 }, > > + { CCI_REG8(0x027a), 0x3 }, > > + { CCI_REG8(0x027b), 0xf }, > > + { CCI_REG8(0x027c), 0x3 }, > > + { CCI_REG8(0x027d), 0xf7 }, > > + { CCI_REG8(0x027e), 0x0 }, > > + { CCI_REG8(0x027f), 0x16 }, > > + { CCI_REG8(0x0280), 0x0 }, > > + { CCI_REG8(0x0281), 0x33 }, > > + { CCI_REG8(0x0282), 0x0 }, > > + { CCI_REG8(0x0283), 0x4 }, > > + { CCI_REG8(0x0284), 0x0 }, > > + { CCI_REG8(0x0285), 0x11 }, > > + { CCI_REG8(0x0286), 0x3 }, > > + { CCI_REG8(0x0287), 0x9 }, > > + { CCI_REG8(0x0288), 0x0 }, > > + { CCI_REG8(0x0289), 0x2 }, > > + { CCI_REG8(0x028a), 0x0 }, > > + { CCI_REG8(0x028b), 0x20 }, > > + { CCI_REG8(0x028c), 0x0 }, > > + { CCI_REG8(0x028d), 0xb5 }, > > + { CCI_REG8(0x028e), 0x0 }, > > + { CCI_REG8(0x028f), 0xe5 }, > > + { CCI_REG8(0x0290), 0x0 }, > > + { CCI_REG8(0x0291), 0x12 }, > > + { CCI_REG8(0x0292), 0x0 }, > > + { CCI_REG8(0x0293), 0xb5 }, > > + { CCI_REG8(0x0294), 0x0 }, > > + { CCI_REG8(0x0295), 0xe5 }, > > + { CCI_REG8(0x0296), 0x0 }, > > + { CCI_REG8(0x0297), 0x10 }, > > + { CCI_REG8(0x0298), 0x0 }, > > + { CCI_REG8(0x0299), 0x2 }, > > + { CCI_REG8(0x029a), 0x0 }, > > + { CCI_REG8(0x029b), 0x20 }, > > + { CCI_REG8(0x029c), 0x0 }, > > + { CCI_REG8(0x029d), 0xb5 }, > > + { CCI_REG8(0x029e), 0x0 }, > > + { CCI_REG8(0x029f), 0xe5 }, > > + { CCI_REG8(0x02a0), 0x0 }, > > + { CCI_REG8(0x02a1), 0x12 }, > > + { CCI_REG8(0x02a2), 0x0 }, > > + { CCI_REG8(0x02a3), 0xb5 }, > > + { CCI_REG8(0x02a4), 0x0 }, > > + { CCI_REG8(0x02a5), 0xe5 }, > > + { CCI_REG8(0x02a6), 0x0 }, > > + { CCI_REG8(0x02a7), 0x0 }, > > + { CCI_REG8(0x02a8), 0x0 }, > > + { CCI_REG8(0x02a9), 0x12 }, > > + { CCI_REG8(0x02aa), 0x0 }, > > + { CCI_REG8(0x02ab), 0x12 }, > > + { CCI_REG8(0x02ac), 0x0 }, > > + { CCI_REG8(0x02ad), 0x20 }, > > + { CCI_REG8(0x02ae), 0x0 }, > > + { CCI_REG8(0x02af), 0xb5 }, > > + { CCI_REG8(0x02b0), 0x0 }, > > + { CCI_REG8(0x02b1), 0xe5 }, > > + { CCI_REG8(0x02b2), 0x0 }, > > + { CCI_REG8(0x02b3), 0x0 }, > > + { CCI_REG8(0x02b4), 0x0 }, > > + { CCI_REG8(0x02b5), 0x12 }, > > + { CCI_REG8(0x02b6), 0x0 }, > > + { CCI_REG8(0x02b7), 0x12 }, > > + { CCI_REG8(0x02b8), 0x0 }, > > + { CCI_REG8(0x02b9), 0x20 }, > > + { CCI_REG8(0x02ba), 0x0 }, > > + { CCI_REG8(0x02bb), 0x47 }, > > + { CCI_REG8(0x02bc), 0x0 }, > > + { CCI_REG8(0x02bd), 0x27 }, > > + { CCI_REG8(0x02be), 0x0 }, > > + { CCI_REG8(0x02bf), 0xb5 }, > > + { CCI_REG8(0x02c0), 0x0 }, > > + { CCI_REG8(0x02c1), 0xe5 }, > > + { CCI_REG8(0x02c2), 0x0 }, > > + { CCI_REG8(0x02c3), 0x0 }, > > + { CCI_REG8(0x02c4), 0x0 }, > > + { CCI_REG8(0x02c5), 0x4 }, > > + { CCI_REG8(0x02c6), 0x0 }, > > + { CCI_REG8(0x02c7), 0x43 }, > > + { CCI_REG8(0x02c8), 0x0 }, > > + { CCI_REG8(0x02c9), 0x1 }, > > + { CCI_REG8(0x02ca), 0x3 }, > > + { CCI_REG8(0x02cb), 0x2 }, > > + { CCI_REG8(0x02cc), 0x0 }, > > + { CCI_REG8(0x02cd), 0x8 }, > > + { CCI_REG8(0x02ce), 0x3 }, > > + { CCI_REG8(0x02cf), 0xff }, > > + { CCI_REG8(0x02d0), 0x2 }, > > + { CCI_REG8(0x02d1), 0x87 }, > > + { CCI_REG8(0x02d2), 0x3 }, > > + { CCI_REG8(0x02d3), 0xc7 }, > > + { CCI_REG8(0x02d4), 0x3 }, > > + { CCI_REG8(0x02d5), 0xf7 }, > > + { CCI_REG8(0x02d6), 0x0 }, > > + { CCI_REG8(0x02d7), 0x77 }, > > + { CCI_REG8(0x02d8), 0x0 }, > > + { CCI_REG8(0x02d9), 0x17 }, > > + { CCI_REG8(0x02da), 0x0 }, > > + { CCI_REG8(0x02db), 0x8 }, > > + { CCI_REG8(0x02dc), 0x3 }, > > + { CCI_REG8(0x02dd), 0xff }, > > + { CCI_REG8(0x02de), 0x0 }, > > + { CCI_REG8(0x02df), 0x38 }, > > + { CCI_REG8(0x02e0), 0x0 }, > > + { CCI_REG8(0x02e1), 0x17 }, > > + { CCI_REG8(0x02e2), 0x0 }, > > + { CCI_REG8(0x02e3), 0x8 }, > > + { CCI_REG8(0x02e4), 0x3 }, > > + { CCI_REG8(0x02e5), 0xff }, > > + { CCI_REG8(0x02e6), 0x3 }, > > + { CCI_REG8(0x02e7), 0xff }, > > + { CCI_REG8(0x02e8), 0x3 }, > > + { CCI_REG8(0x02e9), 0xff }, > > + { CCI_REG8(0x02ea), 0x3 }, > > + { CCI_REG8(0x02eb), 0xff }, > > + { CCI_REG8(0x02ec), 0x3 }, > > + { CCI_REG8(0x02ed), 0xff }, > > + { CCI_REG8(0x02ee), 0x3 }, > > + { CCI_REG8(0x02ef), 0xff }, > > + { CCI_REG8(0x02f0), 0x3 }, > > + { CCI_REG8(0x02f1), 0xff }, > > + { CCI_REG8(0x02f2), 0x3 }, > > + { CCI_REG8(0x02f3), 0xff }, > > + { CCI_REG8(0x02f4), 0x3 }, > > + { CCI_REG8(0x02f5), 0xff }, > > + { CCI_REG8(0x02f6), 0x3 }, > > + { CCI_REG8(0x02f7), 0xff }, > > + { CCI_REG8(0x02f8), 0x3 }, > > + { CCI_REG8(0x02f9), 0xff }, > > + { CCI_REG8(0x02fa), 0x3 }, > > + { CCI_REG8(0x02fb), 0xff }, > > + { CCI_REG8(0x02fc), 0x3 }, > > + { CCI_REG8(0x02fd), 0xff }, > > + { CCI_REG8(0x02fe), 0x3 }, > > + { CCI_REG8(0x02ff), 0xff }, > > + { CCI_REG8(0x0300), 0x3 }, > > + { CCI_REG8(0x0301), 0xff }, > > + { CCI_REG8(0x0302), 0x3 }, > > + { CCI_REG8(0x0303), 0xff }, > > + { CCI_REG8(0x01e9), 0x0 }, > > + { CCI_REG8(0x01e8), 0x19 }, > > + { CCI_REG8(0x01ea), 0x35 }, > > + { CCI_REG8(0x01eb), 0x37 }, > > + { CCI_REG8(0x01ec), 0x64 }, > > + { CCI_REG8(0x01ed), 0x6b }, > > + { CCI_REG8(0x01f8), 0xf }, > > + { CCI_REG8(0x01d8), 0x1 }, > > + { CCI_REG8(0x01dc), 0x1 }, > > + { CCI_REG8(0x01de), 0x1 }, > > + { CCI_REG8(0x0189), 0x1 }, > > + { CCI_REG8(0x01b7), 0x1 }, > > + { CCI_REG8(0x01c1), 0x7 }, > > + { CCI_REG8(0x01c2), 0xf6 }, > > + { CCI_REG8(0x01c3), 0xff }, > > + { CCI_REG8(0x01c9), 0x7 }, > > + { CCI_REG8(0x0325), 0x0 }, > > + { CCI_REG8(0xe159), 0x0 }, > > + { CCI_REG8(0x033a), 0x0 }, > > + { CCI_REG8(0x01b8), 0x1 }, > > + { CCI_REG8(0x01ba), 0x33 }, > > + { CCI_REG8(0x01be), 0x74 }, > > + { CCI_REG8(0x01bf), 0x36 }, > > + { CCI_REG8(0x01c0), 0x53 }, > > + { CCI_REG8(0x00ef), 0x0 }, > > + { CCI_REG8(0x0326), 0x0 }, > > + { CCI_REG8(0x00f0), 0x0 }, > > + { CCI_REG8(0x00f1), 0x0 }, > > + { CCI_REG8(0x0327), 0x0 }, > > + { CCI_REG8(0x00f2), 0x0 }, > > + { CCI_REG8(0x0071), 0x1 }, > > + { CCI_REG8(0x01b4), 0x1 }, > > + { CCI_REG8(0x01b5), 0x1 }, > > + { CCI_REG8(0x01f1), 0x1 }, > > + { CCI_REG8(0x01f4), 0x1 }, > > + { CCI_REG8(0x01f5), 0x1 }, > > + { CCI_REG8(0x0314), 0x1 }, > > + { CCI_REG8(0x0315), 0x1 }, > > + { CCI_REG8(0x0316), 0x1 }, > > + { CCI_REG8(0x0207), 0x0 }, > > + { CCI_REG8(0x4207), 0x2 }, > > + { CCI_REG8(0x2207), 0x2 }, > > + { CCI_REG8(0xe088), 0x1 }, > > + { CCI_REG8(0xe08d), 0x0 }, > > + { CCI_REG8(0xe08e), 0x30 }, > > + { CCI_REG8(0xe08f), 0xd4 }, > > + { CCI_REG8(0xe089), 0x56 }, > > + { CCI_REG8(0xe08a), 0x10 }, > > + { CCI_REG8(0xe08b), 0x1f }, > > + { CCI_REG8(0xe08c), 0xf }, > > + { CCI_REG8(0xe0a6), 0x0 }, > > + { CCI_REG8(0xe0a9), 0x10 }, > > + { CCI_REG8(0xe0aa), 0x0 }, > > + { CCI_REG8(0xe0ad), 0xa }, > > + { CCI_REG8(0xe0a8), 0x30 }, > > + { CCI_REG8(0xe0a7), 0xf }, > > + { CCI_REG8(0xe0ac), 0x10 }, > > + { CCI_REG8(0xe0ab), 0xf }, > > + { CCI_REG8(0x209d), 0x0 }, > > + { CCI_REG8(0x0328), 0x0 }, > > + { CCI_REG8(0x0063), 0x1 }, > > + { CCI_REG8(0x01f7), 0xf }, > > + { CCI_REG8(0xe0e3), 0x1 }, > > + { CCI_REG8(0xe0e7), 0x3 }, > > + { CCI_REG8(0xe33b), 0x0 }, > > + { CCI_REG8(0xe336), 0x0 }, > > + { CCI_REG8(0xe337), 0x0 }, > > + { CCI_REG8(0xe338), 0x0 }, > > + { CCI_REG8(0xe339), 0x0 }, > > + { CCI_REG8(0x00e9), 0x1 }, > > + { CCI_REG8(0x00ea), 0xfe }, > > + { CCI_REG8(0x0309), 0x3 }, > > + { CCI_REG8(0x030a), 0x2 }, > > + { CCI_REG8(0x030b), 0x2 }, > > + { CCI_REG8(0x030c), 0x5 }, > > + { CCI_REG8(0x030e), 0x15 }, > > + { CCI_REG8(0x030d), 0x14 }, > > + { CCI_REG8(0x030f), 0x1 }, > > + { CCI_REG8(0x0310), 0xd }, > > + { CCI_REG8(0x01d0), 0x1f }, > > + { CCI_REG8(0x01d1), 0x1f }, > > + { CCI_REG8(0x01cd), 0x11 }, > > + { CCI_REG8(0x0016), 0x0 }, > > + { CCI_REG8(0x0017), 0x5 }, > > + { CCI_REG8(0x01f2), 0x0 }, > > + { CCI_REG8(0xe000), 0x0 }, > > + { CCI_REG8(0xe0a2), 0x0 }, > > + { CCI_REG8(0xe07b), 0x0 }, > > + { CCI_REG8(0xe078), 0x0 }, > > + { CCI_REG8(0xe079), 0x1 }, > > + { CCI_REG8(0xe136), 0x0 }, > > + { CCI_REG8(0xe0c6), 0x0 }, > > + { CCI_REG8(0xe0c7), 0x0 }, > > + { CCI_REG8(0xe0c8), 0x1 }, > > + { CCI_REG8(0xe0c9), 0x0 }, > > + { CCI_REG8(0xe0ca), 0x0 }, > > + { CCI_REG8(0xe0cb), 0x1 }, > > + { CCI_REG8(0xe0cc), 0x80 }, > > + { CCI_REG8(0xe0cd), 0x80 }, > > + { CCI_REG8(0xe0be), 0x2 }, > > + { CCI_REG8(0xe0bf), 0x2 }, > > + { CCI_REG8(0xe0c4), 0x8 }, > > + { CCI_REG8(0xe0c5), 0x8 }, > > + { CCI_REG8(0x2075), 0x0 }, > > + { CCI_REG8(0xe000), 0x0 }, > > + { CCI_REG8(0x001e), 0x1 }, > > + { CCI_REG8(0xe000), 0x0 }, > > + { CCI_REG8(0x207e), 0x0 }, > > + { CCI_REG8(0xe084), 0x0 }, > > + { CCI_REG8(0xe085), 0x0 }, > > + { CCI_REG8(0xe086), 0x1 }, > > + { CCI_REG8(0xe087), 0x0 }, > > + { CCI_REG8(0x207f), 0x0 }, > > + { CCI_REG8(0x2080), 0x0 }, > > + { CCI_REG8(0x2081), 0x3 }, > > + { CCI_REG8(0x2082), 0x0 }, > > + { CCI_REG8(0x2083), 0x2 }, > > + { CCI_REG8(0x0090), 0x0 }, > > + { CCI_REG8(0x2097), 0x0 }, > > + { CCI_REG8(0xe000), 0x0 }, > > + { CCI_REG8(0x0011), 0x3 }, > > + { CCI_REG8(0x011d), 0x0 }, > > + { CCI_REG8(0xe000), 0x0 }, > > + { CCI_REG8(0xe000), 0x0 }, > > + { CCI_REG8(0x0193), 0x8 }, > > + { CCI_REG8(0x0194), 0x4 }, > > + { CCI_REG8(0xe32c), 0x1 }, > > + { CCI_REG8(0xe32d), 0x0 }, > > + { CCI_REG8(0xe14e), 0x0 }, > > + { CCI_REG8(0xe312), 0x7f }, > > + { CCI_REG8(0xe329), 0x1 }, > > + { CCI_REG8(0xe32b), 0x0 }, > > + { CCI_REG8(0xe331), 0xf }, > > + { CCI_REG8(0xe332), 0x3 }, > > + { CCI_REG8(0xe32a), 0x0 }, > > + { CCI_REG8(0xe11e), 0x1 }, > > + { CCI_REG8(0xe000), 0x0 }, > > + { CCI_REG8(0xe009), 0x1 }, > > + { CCI_REG8(0x212f), 0x1 }, > > + { CCI_REG8(0x2130), 0x1 }, > > + { CCI_REG8(0x2131), 0x1 }, > > + { CCI_REG8(0x2132), 0x1 }, > > + { CCI_REG8(0x2133), 0x1 }, > > + { CCI_REG8(0x2134), 0x1 }, > > + { CCI_REG8(0x2135), 0x1 }, > > + { CCI_REG8(0xe0e1), 0x1 }, > > + { CCI_REG8(0x018a), 0x1 }, > > + { CCI_REG8(0x00e0), 0x1 }, > > + { CCI_REG8(0xe32e), 0x1 }, > > + { CCI_REG8(0xe340), 0x1 }, > > + { CCI_REG8(0xe004), 0x0 }, > > + { CCI_REG8(0xe000), 0x1 }, > > + { CCI_REG8(0x000e), 0x0 }, > > + { CCI_REG8(0x000f), 0x0 }, > > + { CCI_REG8(0x0010), 0x3 }, > > + { CCI_REG8(0x0011), 0xe8 }, > > + { CCI_REG8(0x0012), 0x0 }, > > + { CCI_REG8(0x0013), 0x0 }, > > + { CCI_REG8(0x0014), 0x0 }, > > + { CCI_REG8(0x0015), 0x0 }, > > + { CCI_REG8(0xe004), 0x1 }, > > + { CCI_REG8(0x000e), 0x0 }, > > + { CCI_REG8(0x000f), 0x0 }, > > + { CCI_REG8(0x0010), 0x3 }, > > + { CCI_REG8(0x0011), 0xe8 }, > > + { CCI_REG8(0x0012), 0x0 }, > > + { CCI_REG8(0x0013), 0x0 }, > > + { CCI_REG8(0x0014), 0x0 }, > > + { CCI_REG8(0x0015), 0x0 }, > > + { CCI_REG8(0xe000), 0x0 }, > > + { CCI_REG8(0x0057), 0x0 }, > > + { CCI_REG8(0x0058), 0x0 }, > > + { CCI_REG8(0x0059), 0x2 }, > > + { CCI_REG8(0x005a), 0x2 }, > > + { CCI_REG8(0x005b), 0x0 }, > > + { CCI_REG8(0xe000), 0x0 }, > > + { CCI_REG8(0xe008), 0x0 }, > > + { CCI_REG8(0x0006), 0x1 }, > > + { CCI_REG8(0xe003), 0x0 }, > > + { CCI_REG8(0x0006), 0x0 }, > > + { CCI_REG8(0xe008), 0x0 }, > > + { CCI_REG8(0xe004), 0x0 }, > > + { CCI_REG8(0xe000), 0x1 }, > > + { CCI_REG8(0x0031), 0x0 }, > > + { CCI_REG8(0xe004), 0x1 }, > > + { CCI_REG8(0x0031), 0x0 }, > > + { CCI_REG8(0xe000), 0x0 }, > > + { CCI_REG8(0x0138), 0x0 }, > > + { CCI_REG8(0xe005), 0x0 }, > > + { CCI_REG8(0x0139), 0x0 }, > > + { CCI_REG8(0x013a), 0x0 }, > > + { CCI_REG8(0x013b), 0x64 }, > > + { CCI_REG8(0x013c), 0x0 }, > > + { CCI_REG8(0x013d), 0x0 }, > > + { CCI_REG8(0x013e), 0x64 }, > > + { CCI_REG8(0x013f), 0x6 }, > > + { CCI_REG8(0x0140), 0x1 }, > > + { CCI_REG8(0x0141), 0x10 }, > > + { CCI_REG8(0x0142), 0x1 }, > > + { CCI_REG8(0x0143), 0x0 }, > > + { CCI_REG8(0x0144), 0x0 }, > > + { CCI_REG8(0x0146), 0x0 }, > > + { CCI_REG8(0x0147), 0x0 }, > > + { CCI_REG8(0x0148), 0x0 }, > > + { CCI_REG8(0xe004), 0x0 }, > > + { CCI_REG8(0xe000), 0x1 }, > > + { CCI_REG8(0x0026), 0x0 }, > > + { CCI_REG8(0xe004), 0x1 }, > > + { CCI_REG8(0x0026), 0x0 }, > > + { CCI_REG8(0xe000), 0x0 }, > > + { CCI_REG8(0x0169), 0x12 }, > > + { CCI_REG8(0xe004), 0x0 }, > > + { CCI_REG8(0xe000), 0x1 }, > > + { CCI_REG8(0x001c), 0x0 }, > > + { CCI_REG8(0x0019), 0x0 }, > > + { CCI_REG8(0x001a), 0x7 }, > > + { CCI_REG8(0x001b), 0x53 }, > > + { CCI_REG8(0x0016), 0x8 }, > > + { CCI_REG8(0x0017), 0x0 }, > > + { CCI_REG8(0x0018), 0x0 }, > > + { CCI_REG8(0xe004), 0x1 }, > > + { CCI_REG8(0x001c), 0x0 }, > > + { CCI_REG8(0x0019), 0x0 }, > > + { CCI_REG8(0x001a), 0x7 }, > > + { CCI_REG8(0x001b), 0x53 }, > > + { CCI_REG8(0x0016), 0x8 }, > > + { CCI_REG8(0x0017), 0x0 }, > > + { CCI_REG8(0x0018), 0x0 }, > > + { CCI_REG8(0xe004), 0x0 }, > > + { CCI_REG8(0xe000), 0x1 }, > > + { CCI_REG8(0x001d), 0x0 }, > > + { CCI_REG8(0xe004), 0x1 }, > > + { CCI_REG8(0x001d), 0x0 }, > > + { CCI_REG8(0xe000), 0x0 }, > > + { CCI_REG8(0x001a), 0x0 }, > > + { CCI_REG8(0x001b), 0x0 }, > > + { CCI_REG8(0x001c), 0x0 }, > > + { CCI_REG8(0xe000), 0x0 }, > > + { CCI_REG8(0xe00a), 0x1 }, > > + { CCI_REG8(0xe00a), 0x0 }, > > +}; > > + > > +struct mira016_gain_lut { > > + u8 gdig_preamp; > > + u8 rg_adcgain; > > + u8 rg_mult; > > +}; > > + > > +/* Table 29: Lookup table for fine analog gain in 10-bit mode */ > > +static const struct mira016_gain_lut mira016_gain_lut_10bit[] = { > > + { 15, 36, 2 }, { 15, 35, 2 }, { 15, 33, 2 }, { 15, 32, 2 }, > > + { 15, 30, 2 }, { 15, 29, 2 }, { 15, 27, 2 }, { 15, 26, 2 }, > > + { 15, 24, 2 }, { 15, 23, 2 }, { 15, 22, 2 }, { 15, 62, 1 }, > > + { 15, 59, 1 }, { 15, 57, 1 }, { 15, 55, 1 }, { 15, 53, 1 }, > > + { 15, 51, 1 }, { 15, 48, 1 }, { 15, 46, 1 }, { 15, 45, 1 }, > > + { 15, 43, 1 }, { 15, 41, 1 }, { 15, 39, 1 }, { 15, 37, 1 }, > > + { 15, 36, 1 }, { 15, 34, 1 }, { 15, 32, 1 }, { 15, 31, 1 }, > > + { 15, 29, 1 }, { 15, 28, 1 }, { 15, 26, 1 }, { 15, 25, 1 }, > > + { 15, 24, 1 }, { 15, 22, 1 }, { 15, 63, 0 }, { 15, 61, 0 }, > > + { 15, 58, 0 }, { 15, 56, 0 }, { 15, 54, 0 }, { 15, 51, 0 }, > > + { 15, 49, 0 }, { 15, 47, 0 }, { 15, 45, 0 }, { 15, 42, 0 }, > > + { 15, 41, 0 }, { 15, 39, 0 }, { 15, 37, 0 }, { 15, 35, 0 }, > > +}; > > + > > +/* Table 26: Lookup table for fine analog gain in 8-bit mode */ > > +static const struct mira016_gain_lut mira016_gain_lut_8bit[] = { > > + { 3, 36, 2 }, { 3, 35, 2 }, { 3, 33, 2 }, { 3, 32, 2 }, > > + { 3, 30, 2 }, { 3, 29, 2 }, { 3, 28, 2 }, { 3, 26, 2 }, > > + { 3, 27, 2 }, { 3, 25, 2 }, { 3, 23, 2 }, { 3, 22, 2 }, > > + { 3, 22, 2 }, { 3, 22, 2 }, { 3, 55, 1 }, { 3, 54, 1 }, > > + { 3, 52, 1 }, { 3, 51, 1 }, { 3, 49, 1 }, { 3, 47, 1 }, > > + { 3, 44, 1 }, { 3, 43, 1 }, { 3, 42, 1 }, { 3, 40, 1 }, > > + { 3, 39, 1 }, { 3, 37, 1 }, { 3, 35, 1 }, { 3, 32, 1 }, > > + { 3, 31, 1 }, { 3, 29, 1 }, { 3, 28, 1 }, { 3, 27, 1 }, > > + { 3, 26, 1 }, { 3, 24, 1 }, { 3, 23, 1 }, { 3, 22, 1 }, > > + { 3, 61, 0 }, { 3, 58, 0 }, { 3, 57, 0 }, { 3, 55, 0 }, > > + { 3, 53, 0 }, { 3, 49, 0 }, { 3, 47, 0 }, { 3, 45, 0 }, > > + { 3, 42, 0 }, { 3, 40, 0 }, { 7, 39, 1 }, { 7, 36, 1 }, > > + { 7, 35, 1 }, { 7, 33, 1 }, { 7, 32, 1 }, { 7, 30, 1 }, > > + { 7, 28, 1 }, { 7, 27, 1 }, { 7, 25, 1 }, { 7, 24, 1 }, > > + { 7, 22, 1 }, { 7, 60, 0 }, { 7, 58, 0 }, { 7, 55, 0 }, > > + { 7, 52, 0 }, { 7, 51, 0 }, { 7, 48, 0 }, { 7, 47, 0 }, > > + { 7, 45, 0 }, { 7, 43, 0 }, { 7, 41, 0 }, { 7, 40, 0 }, > > + { 15, 38, 1 }, { 15, 36, 1 }, { 15, 35, 1 }, { 15, 34, 1 }, > > + { 15, 32, 1 }, { 15, 31, 1 }, { 15, 30, 1 }, { 15, 29, 1 }, > > + { 15, 27, 1 }, { 15, 26, 1 }, { 15, 25, 1 }, { 15, 23, 1 }, > > + { 15, 21, 1 }, { 15, 61, 0 }, { 15, 59, 0 }, { 15, 57, 0 }, > > + { 15, 56, 0 }, { 15, 53, 0 }, { 15, 51, 0 }, { 15, 49, 0 }, > > + { 15, 47, 0 }, { 15, 45, 0 }, { 15, 44, 0 }, { 15, 42, 0 }, > > + { 15, 41, 0 }, { 15, 39, 0 }, { 15, 37, 0 }, > > +}; > > + > > +static const u32 mira016_mbus_formats[] = { > > + MEDIA_BUS_FMT_Y12_1X12, > > + MEDIA_BUS_FMT_Y10_1X10, > > + MEDIA_BUS_FMT_Y8_1X8, > > +}; > > + > > +static void mira016_update_pad_format(struct mira016 *mira016, > > + struct v4l2_mbus_framefmt *fmt, u32 code) > > +{ > > + unsigned int i; > > + > > + for (i = 0; i < ARRAY_SIZE(mira016_mbus_formats); ++i) { > > + if (mira016_mbus_formats[i] == fmt->code) > > + break; > > + } > > + if (i == ARRAY_SIZE(mira016_mbus_formats)) > > + fmt->code = mira016_mbus_formats[0]; > > + > > + fmt->width = MIRA016_PIXEL_ARRAY_WIDTH; > > + fmt->height = MIRA016_PIXEL_ARRAY_HEIGHT; > > + fmt->field = V4L2_FIELD_NONE; > > + fmt->colorspace = V4L2_COLORSPACE_RAW; > > + fmt->ycbcr_enc = V4L2_YCBCR_ENC_601; > > + fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE; > > + fmt->xfer_func = V4L2_XFER_FUNC_NONE; > > +} > > + > > +static u32 mira016_calc_phy_timings(struct mira016 *mira016, > > + struct v4l2_mbus_framefmt *fmt) > > +{ > > + u32 phy_time = 580; > > + > > + /* > > + * PHY timings depend on format, clock mode and integration mode. > > + * Assume 'global timing mode' unconditionally. > > + */ > > + > > + switch (fmt->code) { > > + case MEDIA_BUS_FMT_Y8_1X8: > > + break; > > + case MEDIA_BUS_FMT_Y10_1X10: > > + phy_time += 100; > > + break; > > + case MEDIA_BUS_FMT_Y12_1X12: > > + phy_time += 200; > > + break; > > + } > > + > > + if (mira016->bus_config & V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK) > > + phy_time += 130; > > + > > + return phy_time; > > +} > > + > > +static u32 mira016_calc_adc_timings(struct mira016 *mira016, > > + struct v4l2_mbus_framefmt *fmt) > > +{ > > + u32 adc_time = 1062; > > + > > + /* > > + * ADC timings depend on format, gain mode, clock mode and binning. > > + * > > + * TODO: assume 'fine' gain model for 8 and 10 bit modes and no binning > > + * until proper support is implemented using the Common Raw Sensor > > + * model. > > + */ > > + switch (fmt->code) { > > + case MEDIA_BUS_FMT_Y8_1X8: > > + adc_time = 1062; > > + break; > > + case MEDIA_BUS_FMT_Y10_1X10: > > + adc_time = 1062; > > + break; > > + case MEDIA_BUS_FMT_Y12_1X12: > > + adc_time = 1504; > > + break; > > + } > > + > > + return adc_time; > > +} > > + > > +static int mira016_calc_row_length(struct mira016 *mira016, > > + struct v4l2_subdev_state *state) > > +{ > > + struct v4l2_mbus_framefmt *fmt = v4l2_subdev_state_get_format(state, 0); > > + u32 phy_timings = mira016_calc_phy_timings(mira016, fmt); > > + u32 adc_timings = mira016_calc_adc_timings(mira016, fmt); > > + > > + mira016->timings.row_length = max(phy_timings, adc_timings); > > + > > + return 0; > > +} > > + > > +static int mira016_set_pad_format(struct v4l2_subdev *sd, > > + struct v4l2_subdev_state *state, > > + struct v4l2_subdev_format *fmt) > > +{ > > + struct mira016 *mira016 = to_mira016(sd); > > + struct v4l2_rect *crop; > > + u32 pixel_rate; > > + u32 min_vblank; > > + u32 gain_max; > > + int ret; > > + > > + mira016_update_pad_format(mira016, &fmt->format, fmt->format.code); > > + *v4l2_subdev_state_get_format(state, 0) = fmt->format; > > + > > + crop = v4l2_subdev_state_get_crop(state, 0); > > + crop->width = fmt->format.width; > > + crop->height = fmt->format.height; > > + crop->left = MIRA016_PIXEL_ARRAY_LEFT; > > + crop->top = MIRA016_PIXEL_ARRAY_TOP; > > + > > + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) > > + return 0; > > + > > + /* > > + * Update the row length: changing the image format implies changing the > > + * row_length parameter, which changes the line duration and the pixel > > + * rate consequentially. Also, changing the image format changes the > > + * analogue gain limits. > > + * > > + * Do not allow to change image format while the subdevice is streaming. > > + * > > + * TODO: row length depends on binning, update it also in the > > + * implementation of set_selection. > > + */ > > + if (v4l2_subdev_is_streaming(sd)) > > + return -EBUSY; > > + > > + switch (fmt->format.code) { > > + case MEDIA_BUS_FMT_Y8_1X8: > > + gain_max = ARRAY_SIZE(mira016_gain_lut_8bit); > > + break; > > + case MEDIA_BUS_FMT_Y10_1X10: > > + gain_max = ARRAY_SIZE(mira016_gain_lut_10bit); > > + break; > > + case MEDIA_BUS_FMT_Y12_1X12: > > + default: > > + /* > > + * TODO: Clarify how to handle 12 bit 2x fixed gain which > > + * changes the line timings while streaming. Only allow 1x > > + * for the time being. > > + */ > > + gain_max = 1; > > + break; > > + } > > + > > + ret = __v4l2_ctrl_modify_range(mira016->gain, 1, gain_max, 1, 1); > > + if (ret) > > + return ret; > > + > > + mira016_calc_row_length(mira016, state); > > + > > + min_vblank = mira016_calc_min_vblank(mira016, crop->height); > > + ret = __v4l2_ctrl_modify_range(mira016->vblank, min_vblank, > > + MIRA016_MAX_VBLANK, 1, min_vblank); > > + if (ret) > > + return ret; > > + > > + pixel_rate = mira016_calc_prate(mira016, crop->width); > > + > > + return __v4l2_ctrl_modify_range(mira016->prate, pixel_rate, pixel_rate, > > + 1, pixel_rate); > > +} > > + > > +static int mira016_enum_mbus_code(struct v4l2_subdev *sd, > > + struct v4l2_subdev_state *state, > > + struct v4l2_subdev_mbus_code_enum *code) > > +{ > > + if (code->index >= ARRAY_SIZE(mira016_mbus_formats)) > > + return -EINVAL; > > + > > + code->code = mira016_mbus_formats[code->index]; > > + > > + return 0; > > +} > > + > > +static int mira016_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, 0); > > + return 0; > > + > > + case V4L2_SEL_TGT_NATIVE_SIZE: > > + sel->r.top = 0; > > + sel->r.left = 0; > > + sel->r.width = MIRA016_NATIVE_WIDTH; > > + sel->r.height = MIRA016_NATIVE_HEIGHT; > > + return 0; > > + > > + case V4L2_SEL_TGT_CROP_DEFAULT: > > + case V4L2_SEL_TGT_CROP_BOUNDS: > > + sel->r.top = MIRA016_PIXEL_ARRAY_TOP; > > + sel->r.left = MIRA016_PIXEL_ARRAY_LEFT; > > + sel->r.width = MIRA016_PIXEL_ARRAY_WIDTH; > > + sel->r.height = MIRA016_PIXEL_ARRAY_HEIGHT; > > + return 0; > > + } > > + > > + return -EINVAL; > > +} > > + > > +static int mira016_enum_frame_size(struct v4l2_subdev *sd, > > + struct v4l2_subdev_state *state, > > + struct v4l2_subdev_frame_size_enum *fse) > > +{ > > + unsigned int i; > > + > > + if (fse->index) > > + return -EINVAL; > > + > > + for (i = 0; i < ARRAY_SIZE(mira016_mbus_formats); ++i) { > > + if (mira016_mbus_formats[i] == fse->code) > > + break; > > + } > > + if (i == ARRAY_SIZE(mira016_mbus_formats)) > > + return -EINVAL; > > + > > + fse->min_width = MIRA016_PIXEL_ARRAY_WIDTH; > > + fse->max_width = fse->min_width; > > + fse->min_height = MIRA016_PIXEL_ARRAY_HEIGHT; > > + fse->max_height = fse->min_height; > > + > > + return 0; > > +} > > + > > +static int mira016_start_streaming(struct mira016 *mira016) > > +{ > > + int ret = 0; > > + > > + cci_write(mira016->regmap, MIRA016_BANK_SEL_REG, 0, &ret); > > + cci_write(mira016->regmap, MIRA016_RW_CONTEXT_REG, 0, &ret); > > + cci_write(mira016->regmap, MIRA016_CMD_REQ_1_REG, 1, &ret); > > + fsleep(10); > > + cci_write(mira016->regmap, MIRA016_CMD_REQ_1_REG, 0, &ret); > > + fsleep(10); > > + > > + return ret; > > +} > > + > > +static int mira016_stop_streaming(struct mira016 *mira016) > > +{ > > + int ret = 0; > > + > > + cci_write(mira016->regmap, MIRA016_BANK_SEL_REG, 0, &ret); > > + cci_write(mira016->regmap, MIRA016_CMD_HALT_BLOCK_REG, 1, &ret); > > + fsleep(10); > > + cci_write(mira016->regmap, MIRA016_CMD_HALT_BLOCK_REG, 0, &ret); > > + fsleep(10); > > + > > + return ret; > > +} > > + > > +static int mira016_configure_timings(struct mira016 *mira016) > > +{ > > + int ret = 0; > > + u64 val; > > + > > + /* Configure the PLL clock tree. */ > > + > > + cci_write(mira016->regmap, MIRA016_BANK_SEL_REG, 0, &ret); > > + > > + cci_write(mira016->regmap, MIRA016_PLL_DIV_N_REG, mira016->pll.n, &ret); > > + cci_write(mira016->regmap, MIRA016_PLL_DIV_M_REG, mira016->pll.m, &ret); > > + cci_write(mira016->regmap, MIRA016_CLKGEN_CP_DIV_REG, > > + mira016->pll.cp_div, &ret); > > + cci_write(mira016->regmap, MIRA016_OTP_GRANULARITY_REG, > > + mira016->pll.otp_gran, &ret); > > + cci_write(mira016->regmap, MIRA016_GRAN_TG_REG, mira016->pll.gran_tg, &ret); > > + cci_write(mira016->regmap, MIRA016_LUT_DEL008_REG, > > + mira016->pll.lut_del, &ret); > > + cci_write(mira016->regmap, MIRA016_CLKGEN_TX_ESC_H_REG, > > + mira016->pll.esc_h, &ret); > > + cci_write(mira016->regmap, MIRA016_CLKGEN_TX_ESC_L_REG, > > + mira016->pll.esc_l, &ret); > > + cci_write(mira016->regmap, MIRA016_PLL_PD_REG, 0, &ret); > > + if (ret) > > + return ret; > > + > > + ret = read_poll_timeout(cci_read, ret, > > + ((ret < 0 || (val & MIRA016_PLL_LOCKED))), > > + 1000, 1000, false, mira016->regmap, > > + MIRA016_PLL_LOCK_REG, &val, NULL); > > + if (ret < 0) > > + return ret; > > + > > + /* Configure the sensor timings. */ > > + > > + cci_write(mira016->regmap, MIRA016_TIME_UNIT_REG, > > + mira016->timings.time_base, &ret); > > + cci_write(mira016->regmap, MIRA016_GLOB_TIME_REG, > > + mira016->timings.glob_time, &ret); > > + cci_write(mira016->regmap, MIRA016_GLOB_TIME_A_REG, > > + mira016->timings.glob_time, &ret); > > + cci_write(mira016->regmap, MIRA016_GLOB_TIME_B_REG, > > + mira016->timings.glob_time, &ret); > > + cci_write(mira016->regmap, MIRA016_ENTER_LPS_TIME_REG, > > + MIRA016_ENTER_LPS_TIME_1500MBPS, &ret); > > + cci_write(mira016->regmap, MIRA016_EXIT_LPS_TIME_REG, > > + MIRA016_EXIT_LPS_TIME_1500MBPS, &ret); > > + cci_write(mira016->regmap, MIRA016_LPS_CYCLE_TIME_REG, > > + MIRA016_LPS_CYCLE_TIME_1500MBPS, &ret); > > + > > + return ret; > > +} > > + > > +static int mira016_configure_horizontal_roi(struct mira016 *mira016, > > + struct v4l2_subdev_state *state) > > +{ > > + /* > > + * TODO: Support for binning and subsampling using the Common Raw > > + * Sensor model. This will require to re-calculate the FIFO threshold > > + * and the hsync length with an updated: > > + * > > + * reduction_factor = 2 ^ xbinning * 2 ^ xsubsampling > > + */ > > + u16 reduction_factor = 4; > > + struct v4l2_mbus_framefmt *fmt = v4l2_subdev_state_get_format(state, 0); > > + struct v4l2_rect *crop = v4l2_subdev_state_get_crop(state, 0); > > + u16 nr_of_pixels = crop->width / reduction_factor; > > + u16 xwin_right = crop->left + crop->width - 1; > > + u16 fifo_threshold; > > + u16 hsync_length; > > + int ret = 0; > > + > > + cci_write(mira016->regmap, MIRA016_XWIN_LEFT_REG, crop->left, &ret); > > + cci_write(mira016->regmap, MIRA016_XWIN_RIGHT_REG, xwin_right, &ret); > > + cci_write(mira016->regmap, MIRA016_XMIRROR_REG, > > + mira016->hflip->val, &ret); > > + > > + cci_write(mira016->regmap, MIRA016_XSUBSAMPLING_REG, 0, &ret); > > + cci_write(mira016->regmap, MIRA016_XBINNING_REG, 0, &ret); > > + > > + fifo_threshold = nr_of_pixels - (nr_of_pixels / reduction_factor); > > + fifo_threshold = max(70, fifo_threshold); > > + > > + /* > > + * FIXME: override for 8 bit mode with magic number from register > > + * sequences. Using the value calculated according to the datasheet > > + * introduces artifacts in 8-bit mode. > > + */ > > + if (fmt->code == MEDIA_BUS_FMT_Y8_1X8) > > + fifo_threshold = 0x28; > > + > > + cci_write(mira016->regmap, MIRA016_MIPI_DATA_FIFO_THR_REG, > > + fifo_threshold, &ret); > > + > > + /* Assume Global Timing mode when calculating HSYNC length. */ > > + hsync_length = max(56, crop->width / 2); > > + cci_write(mira016->regmap, MIRA016_HSYNC_LENGTH, hsync_length, &ret); > > + > > + return ret; > > +} > > + > > +static int mira016_configure_vertical_roi(struct mira016 *mira016, > > + struct v4l2_subdev_state *state) > > +{ > > + struct v4l2_rect *crop = v4l2_subdev_state_get_crop(state, 0); > > + int ret = 0; > > + u32 top; > > + > > + /* > > + * Program the first two windows and zero the other 8 to disable them. > > + * > > + * FIXME: Mira016 workaround - Window 1 is not functional. Program > > + * 2 identical windows in Window 1 and Window 2 but only use the second > > + * one (YWIN_ENA = 2). > > + * > > + * TODO: Support for binning and subsampling using the Common Raw > > + * Sensor model. > > + */ > > + > > + cci_write(mira016->regmap, MIRA016_BANK_SEL_REG, 1, &ret); > > + cci_write(mira016->regmap, MIRA016_RW_CONTEXT_REG, 0, &ret); > > + cci_write(mira016->regmap, MIRA016_YWIN_ENA_REG, 0x02, &ret); > > + cci_write(mira016->regmap, MIRA016_YBINNING_REG, 0x00, &ret); > > + cci_write(mira016->regmap, MIRA016_RW_CONTEXT_REG, 1, &ret); > > + cci_write(mira016->regmap, MIRA016_YWIN_ENA_REG, 0x02, &ret); > > + cci_write(mira016->regmap, MIRA016_YBINNING_REG, 0x00, &ret); > > + > > + cci_write(mira016->regmap, MIRA016_BANK_SEL_REG, 0, &ret); > > + cci_write(mira016->regmap, MIRA016_YWIN_BLACK_REG, 0x00, &ret); > > + cci_write(mira016->regmap, MIRA016_YWIN_DIR_REG, > > + mira016->vflip->val, &ret); > > + > > + /* The vertical start position depends on the reading direction. */ > > + top = mira016->vflip->val ? crop->top + crop->height - 1 : crop->top; > > + cci_write(mira016->regmap, MIRA016_YWIN_SIZE_REG(0), crop->height, &ret); > > + cci_write(mira016->regmap, MIRA016_YWIN_START_REG(0), top, &ret); > > + cci_write(mira016->regmap, MIRA016_YWIN_SUBS_REG(0), 0x00, &ret); > > + cci_write(mira016->regmap, MIRA016_YWIN_SIZE_REG(1), crop->height, &ret); > > + cci_write(mira016->regmap, MIRA016_YWIN_START_REG(1), top, &ret); > > + cci_write(mira016->regmap, MIRA016_YWIN_SUBS_REG(1), 0x00, &ret); > > + > > + for (unsigned int i = 2; i < MIRA016_NUM_Y_WIND; ++i) { > > + cci_write(mira016->regmap, MIRA016_YWIN_SIZE_REG(i), 0x00, &ret); > > + cci_write(mira016->regmap, MIRA016_YWIN_START_REG(i), 0x00, &ret); > > + cci_write(mira016->regmap, MIRA016_YWIN_SUBS_REG(i), 0x00, &ret); > > + } > > + > > + return ret; > > +} > > + > > +static int mira016_configure_roi(struct mira016 *mira016, > > + struct v4l2_subdev_state *state) > > +{ > > + int ret = 0; > > + > > + /* > > + * Write horizontal configuration to context 0 first, then repeat for > > + * context 1. > > + */ > > + cci_write(mira016->regmap, MIRA016_RW_CONTEXT_REG, 0, &ret); > > + cci_write(mira016->regmap, MIRA016_BANK_SEL_REG, 1, &ret); > > + if (ret) > > + return ret; > > + > > + ret = mira016_configure_horizontal_roi(mira016, state); > > + if (ret) > > + return ret; > > + > > + cci_write(mira016->regmap, MIRA016_RW_CONTEXT_REG, 1, &ret); > > + if (ret) > > + return ret; > > + > > + ret = mira016_configure_horizontal_roi(mira016, state); > > + if (ret) > > + return ret; > > + > > + ret = mira016_configure_vertical_roi(mira016, state); > > + if (ret) > > + return ret; > > + > > + /* Configure ROW_LENGTH in both contexts. */ > > + cci_write(mira016->regmap, MIRA016_RW_CONTEXT_REG, 0, &ret); > > + cci_write(mira016->regmap, MIRA016_BANK_SEL_REG, 1, &ret); > > + cci_write(mira016->regmap, MIRA016_ROW_TIMINGS_REG, > > + mira016->timings.row_length, &ret); > > + cci_write(mira016->regmap, MIRA016_RW_CONTEXT_REG, 1, &ret); > > + cci_write(mira016->regmap, MIRA016_ROW_TIMINGS_REG, > > + mira016->timings.row_length, &ret); > > + > > + return ret; > > +} > > + > > +static int mira016_configure_format(struct mira016 *mira016, > > + struct v4l2_subdev_state *state) > > +{ > > + struct v4l2_mbus_framefmt *fmt = v4l2_subdev_state_get_format(state, 0); > > + u16 bitwidth; > > + u8 bitmode; > > + u8 csi2_dt; > > + int ret = 0; > > + > > + switch (fmt->code) { > > + case MEDIA_BUS_FMT_Y8_1X8: > > + csi2_dt = MIPI_CSI2_DT_RAW8; > > + bitwidth = 8; > > + bitmode = 0; > > + break; > > + case MEDIA_BUS_FMT_Y10_1X10: > > + csi2_dt = MIPI_CSI2_DT_RAW10; > > + bitwidth = 16; > > + bitmode = 1; > > + break; > > + case MEDIA_BUS_FMT_Y12_1X12: > > + default: > > + csi2_dt = MIPI_CSI2_DT_RAW12; > > + bitwidth = 32; > > + bitmode = 2; > > + break; > > + } > > + > > + cci_write(mira016->regmap, MIRA016_BANK_SEL_REG, 0, &ret); > > + cci_write(mira016->regmap, MIRA016_DPATH_BITMODE_REG, bitmode, &ret); > > + cci_write(mira016->regmap, MIRA016_DPATH_BITWIDTH_A_REG, bitwidth, &ret); > > + cci_write(mira016->regmap, MIRA016_DPATH_BITWIDTH_B_REG, bitwidth, &ret); > > + cci_write(mira016->regmap, MIRA016_CSI2_DATA_TYPE_REG, csi2_dt, &ret); > > + > > + return ret; > > +} > > + > > +static int mira016_init_analogue_gain(struct mira016 *mira016, > > + struct v4l2_subdev_state *state) > > +{ > > + struct v4l2_mbus_framefmt *fmt = v4l2_subdev_state_get_format(state, 0); > > + const struct cci_reg_sequence *gain_init_seq; > > + size_t gain_init_num_regs; > > + > > + switch (fmt->code) { > > + case MEDIA_BUS_FMT_Y8_1X8: > > + gain_init_seq = mira016_8b_fine_gain_init; > > + gain_init_num_regs = ARRAY_SIZE(mira016_8b_fine_gain_init); > > + break; > > + case MEDIA_BUS_FMT_Y10_1X10: > > + gain_init_seq = mira016_10b_fine_gain_init; > > + gain_init_num_regs = ARRAY_SIZE(mira016_10b_fine_gain_init); > > + break; > > + case MEDIA_BUS_FMT_Y12_1X12: > > + default: > > + gain_init_seq = mira016_12b_fixed_gain_init; > > + gain_init_num_regs = ARRAY_SIZE(mira016_12b_fixed_gain_init); > > + break; > > + } > > + > > + return cci_multi_reg_write(mira016->regmap, gain_init_seq, > > + gain_init_num_regs, NULL); > > +} > > + > > +static int mira016_enable_streams(struct v4l2_subdev *sd, > > + struct v4l2_subdev_state *state, u32 pad, > > + u64 streams_mask) > > +{ > > + struct mira016 *mira016 = to_mira016(sd); > > + struct i2c_client *client = v4l2_get_subdevdata(&mira016->sd); > > + int ret; > > + > > + ret = pm_runtime_resume_and_get(&client->dev); > > + if (ret < 0) > > + return ret; > > + > > + /* > > + * vflip and hflip cannot change during streaming as they're programmed > > + * with the ROI windows. > > + */ > > + __v4l2_ctrl_grab(mira016->hflip, true); > > + __v4l2_ctrl_grab(mira016->vflip, true); > > + > > + ret = cci_multi_reg_write(mira016->regmap, mira016_init_sequence, > > + ARRAY_SIZE(mira016_init_sequence), NULL); > > + > > + if (!ret) > > + ret = mira016_configure_format(mira016, state); > > + if (!ret) > > + ret = mira016_configure_timings(mira016); > > + if (!ret) > > + ret = mira016_init_analogue_gain(mira016, state); > > + if (!ret) > > + ret = mira016_configure_roi(mira016, state); > > + if (!ret) > > + ret = __v4l2_ctrl_handler_setup(mira016->sd.ctrl_handler); > > + if (!ret) > > + ret = mira016_start_streaming(mira016); > > + if (ret) > > + goto err_rpm_put; > > + > > + return 0; > > + > > +err_rpm_put: > > + __v4l2_ctrl_grab(mira016->hflip, false); > > + __v4l2_ctrl_grab(mira016->vflip, false); > > + pm_runtime_put_autosuspend(&client->dev); > > + return ret; > > +} > > + > > +static int mira016_disable_streams(struct v4l2_subdev *sd, > > + struct v4l2_subdev_state *state, u32 pad, > > + u64 streams_mask) > > +{ > > + struct mira016 *mira016 = to_mira016(sd); > > + struct i2c_client *client = v4l2_get_subdevdata(&mira016->sd); > > + > > + mira016_stop_streaming(mira016); > > + > > + __v4l2_ctrl_grab(mira016->hflip, false); > > + __v4l2_ctrl_grab(mira016->vflip, false); > > + > > + pm_runtime_put_autosuspend(&client->dev); > > + > > + return 0; > > +} > > + > > +static const struct v4l2_subdev_pad_ops mira016_pad_ops = { > > + .enum_mbus_code = mira016_enum_mbus_code, > > + .get_fmt = v4l2_subdev_get_fmt, > > + .set_fmt = mira016_set_pad_format, > > + .get_selection = mira016_get_selection, > > + .enum_frame_size = mira016_enum_frame_size, > > + .enable_streams = mira016_enable_streams, > > + .disable_streams = mira016_disable_streams, > > +}; > > + > > +static const struct v4l2_subdev_video_ops mira016_video_ops = { > > + .s_stream = v4l2_subdev_s_stream_helper, > > +}; > > + > > +static const struct v4l2_subdev_ops mira016_subdev_ops = { > > + .video = &mira016_video_ops, > > + .pad = &mira016_pad_ops, > > +}; > > + > > +static int mira016_init_state(struct v4l2_subdev *sd, > > + struct v4l2_subdev_state *state) > > +{ > > + struct v4l2_subdev_format fmt = { > > + .which = V4L2_SUBDEV_FORMAT_TRY, > > + .pad = 0, > > + .format = { > > + .code = MEDIA_BUS_FMT_Y8_1X8, > > + .width = MIRA016_PIXEL_ARRAY_WIDTH, > > + .height = MIRA016_PIXEL_ARRAY_HEIGHT > > + }, > > + }; > > + > > + mira016_set_pad_format(sd, state, &fmt); > > + > > + return 0; > > +} > > + > > +static const struct v4l2_subdev_internal_ops mira016_internal_ops = { > > + .init_state = mira016_init_state, > > +}; > > + > > +static int mira016_write_exposure_reg(struct mira016 *mira016, > > + u32 exposure_lines) > > +{ > > + u32 exposure_us; > > + int ret = 0; > > + > > + exposure_us = mira016_lines_to_usec(mira016, exposure_lines); > > + > > + /* Write Bank 1 context 0 and context 1 */ > > + cci_write(mira016->regmap, MIRA016_RW_CONTEXT_REG, 0, &ret); > > + cci_write(mira016->regmap, MIRA016_BANK_SEL_REG, 1, &ret); > > + cci_write(mira016->regmap, MIRA016_EXP_TIME_L_REG, exposure_us, &ret); > > + > > + cci_write(mira016->regmap, MIRA016_RW_CONTEXT_REG, 1, &ret); > > + cci_write(mira016->regmap, MIRA016_EXP_TIME_L_REG, exposure_us, &ret); > > + > > + return ret; > > +} > > + > > +static int mira016_write_frame_duration_reg(struct mira016 *mira016, > > + struct v4l2_subdev_state *state, > > + u32 vblank_lines) > > +{ > > + struct v4l2_rect *crop = v4l2_subdev_state_get_crop(state, 0); > > + u32 frame_duration; > > + int ret = 0; > > + > > + frame_duration = mira016_lines_to_usec(mira016, > > + crop->height + vblank_lines); > > + > > + /* Write Bank 1 context 0 and context 1 */ > > + cci_write(mira016->regmap, MIRA016_RW_CONTEXT_REG, 0, &ret); > > + cci_write(mira016->regmap, MIRA016_BANK_SEL_REG, 1, &ret); > > + cci_write(mira016->regmap, MIRA016_NUM_FRAMES_REG, 1, &ret); > > + cci_write(mira016->regmap, MIRA016_TARGET_FRAME_TIME_REG, > > + frame_duration, &ret); > > + > > + cci_write(mira016->regmap, MIRA016_RW_CONTEXT_REG, 1, &ret); > > + cci_write(mira016->regmap, MIRA016_NUM_FRAMES_REG, 1, &ret); > > + cci_write(mira016->regmap, MIRA016_TARGET_FRAME_TIME_REG, > > + frame_duration, &ret); > > + > > + return ret; > > +} > > + > > +static int mira016_write_analogue_gain(struct mira016 *mira016, > > + struct v4l2_subdev_state *state, > > + u32 gain) > > +{ > > + const struct mira016_gain_lut *lut; > > + struct v4l2_mbus_framefmt *format; > > + int ret = 0; > > + > > + /* > > + * Use 'gain - 1' as the gain control values are indexed from 1 > > + * while the gain luts are 0-indexed. > > + */ > > + format = v4l2_subdev_state_get_format(state, 0); > > + switch (format->code) { > > + case MEDIA_BUS_FMT_Y8_1X8: > > + lut = &mira016_gain_lut_8bit[gain - 1]; > > + break; > > + case MEDIA_BUS_FMT_Y10_1X10: > > + lut = &mira016_gain_lut_10bit[gain - 1]; > > + break; > > + case MEDIA_BUS_FMT_Y12_1X12: > > + default: > > + /* 12 bit gain is fixed, nothing to do here. */ > > + return 0; > > + } > > + > > + cci_write(mira016->regmap, MIRA016_BANK_SEL_REG, 0, &ret); > > + > > + if (format->code == MEDIA_BUS_FMT_Y8_1X8) > > + cci_write(mira016->regmap, MIRA016_GDIG_PREAMP, > > + lut->gdig_preamp, &ret); > > + cci_write(mira016->regmap, MIRA016_BIAS_RG_ADCGAIN, > > + lut->rg_adcgain, &ret); > > + cci_write(mira016->regmap, MIRA016_BIAS_RG_MULT, > > + lut->rg_mult, &ret); > > + > > + return ret; > > +} > > + > > +static int mira016_set_ctrl(struct v4l2_ctrl *ctrl) > > +{ > > + struct mira016 *mira016 = > > + container_of(ctrl->handler, struct mira016, ctrl_handler); > > + struct i2c_client *client = v4l2_get_subdevdata(&mira016->sd); > > + struct v4l2_subdev_state *state; > > + struct v4l2_rect *crop; > > + int ret = 0; > > + > > + state = v4l2_subdev_get_locked_active_state(&mira016->sd); > > + crop = v4l2_subdev_state_get_crop(state, 0); > > + > > + if (ctrl->id == V4L2_CID_VBLANK) { > > + s32 exposure_max = crop->height + ctrl->val > > + - MIRA016_FRAME_INTEGRATION_DIFF; > > + s32 exposure_def = min(exposure_max, > > + mira016->exposure->val); > > + > > + ret = __v4l2_ctrl_modify_range(mira016->exposure, > > + mira016->exposure->minimum, > > + exposure_max, > > + mira016->exposure->step, > > + exposure_def); > > + if (ret) > > + return ret; > > + } > > + > > + if (!pm_runtime_get_if_in_use(&client->dev)) > > + return 0; > > + > > + switch (ctrl->id) { > > + case V4L2_CID_EXPOSURE: > > + ret = mira016_write_exposure_reg(mira016, ctrl->val); > > + break; > > + case V4L2_CID_VBLANK: > > + ret = mira016_write_frame_duration_reg(mira016, state, ctrl->val); > > + break; > > Is hblank part of the register lists? > These sensors (there will hopefully be more supported by this driver) do not have a real horizontal blanking. Their line length is expressed by a time base multipled by a line length which doesn't directly depend on the pixel width but rather on the ADC and PHY timings. You could expand the line duration by increasing the time base, but I wouldn't go there and use VBLANK only to control the frame duration. As you can see the HBLANK control is registered with fixed value of 0. > > + case V4L2_CID_ANALOGUE_GAIN: > > + ret = mira016_write_analogue_gain(mira016, state, ctrl->val); > > + break; > > + default: > > + ret = -EINVAL; > > + break; > > + } > > + > > + pm_runtime_put_autosuspend(&client->dev); > > + > > + return ret; > > +} > > + > > +static const struct v4l2_ctrl_ops mira016_ctrl_ops = { > > + .s_ctrl = mira016_set_ctrl, > > +}; > > + > > +static int mira016_identify_module(struct mira016 *mira016) > > +{ > > + int ret = 0; > > + u64 val; > > + > > + cci_write(mira016->regmap, MIRA016_BANK_SEL_REG, 0, &ret); > > + cci_read(mira016->regmap, MIRA016_CHIP_ID_REG, &val, &ret); > > + if (ret || val != MIRA016_CHIP_ID) > > + return -EINVAL; > > + > > + return 0; > > +} > > + > > +/* Power/clock management functions */ > > +static int mira016_power_on(struct device *dev) > > +{ > > + struct i2c_client *client = to_i2c_client(dev); > > + struct v4l2_subdev *sd = i2c_get_clientdata(client); > > + struct mira016 *mira016 = to_mira016(sd); > > + int ret; > > + > > + ret = regulator_bulk_enable(ARRAY_SIZE(mira016_supplies), > > + mira016->supplies); > > + if (ret) { > > + dev_err(&client->dev, "%s: failed to enable regulators\n", > > + __func__); > > + return ret; > > + } > > + > > + ret = clk_prepare_enable(mira016->xclk); > > + if (ret) { > > + dev_err(&client->dev, "%s: failed to enable clock\n", __func__); > > + goto reg_off; > > As there's a single location where you need to restore the previous state, > I'd just do it here. No need for a goto. > Ack > > + } > > + > > + gpiod_set_value_cansleep(mira016->reset_gpio, 0); > > + > > + return 0; > > + > > +reg_off: > > + regulator_bulk_disable(ARRAY_SIZE(mira016_supplies), mira016->supplies); > > + return ret; > > +} > > + > > +static int mira016_power_off(struct device *dev) > > +{ > > + struct i2c_client *client = to_i2c_client(dev); > > + struct v4l2_subdev *sd = i2c_get_clientdata(client); > > + struct mira016 *mira016 = to_mira016(sd); > > + > > + gpiod_set_value_cansleep(mira016->reset_gpio, 1); > > + clk_disable_unprepare(mira016->xclk); > > + regulator_bulk_disable(ARRAY_SIZE(mira016_supplies), mira016->supplies); > > + > > + return 0; > > +} > > + > > +static const struct dev_pm_ops mira016_pm_ops = { > > + SET_RUNTIME_PM_OPS(mira016_power_off, mira016_power_on, NULL) > > +}; > > + > > +static int mira016_init_controls(struct mira016 *mira016) > > +{ > > + struct i2c_client *client = v4l2_get_subdevdata(&mira016->sd); > > + struct v4l2_fwnode_device_properties props; > > + struct v4l2_ctrl_handler *ctrl_hdlr; > > + struct v4l2_ctrl *link_freq; > > + struct v4l2_ctrl *hblank; > > + u32 min_exposure_lines; > > + u32 def_exposure; > > + u32 min_vblank; > > + u32 def_vblank; > > + u32 pixel_rate; > > + int ret; > > + > > + ctrl_hdlr = &mira016->ctrl_handler; > > + v4l2_ctrl_handler_init(ctrl_hdlr, 12); > > + > > + /* By default, PIXEL_RATE is read only */ > > + pixel_rate = mira016_calc_prate(mira016, MIRA016_PIXEL_ARRAY_WIDTH); > > + mira016->prate = v4l2_ctrl_new_std(ctrl_hdlr, NULL, V4L2_CID_PIXEL_RATE, > > + pixel_rate, pixel_rate, 1, > > + pixel_rate); > > + > > + def_vblank = mira016_nsec_to_lines(mira016, > > + MIRA016_DEFAULT_DURATION_NSEC); > > + def_vblank -= MIRA016_PIXEL_ARRAY_HEIGHT; > > + > > + min_vblank = mira016_calc_min_vblank(mira016, > > + MIRA016_PIXEL_ARRAY_HEIGHT); > > + mira016->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &mira016_ctrl_ops, > > + V4L2_CID_VBLANK, min_vblank, > > + MIRA016_MAX_VBLANK, 1, > > + def_vblank); > > + > > + /* Fixed 0 horizontal blanking. */ > > + hblank = v4l2_ctrl_new_std(ctrl_hdlr, NULL, V4L2_CID_HBLANK, 0, > > + 0, 1, 0); > > + > > + link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, NULL, V4L2_CID_LINK_FREQ, > > + 0, 0, &mira016_link_freqs[0]); > > + > > + min_exposure_lines = mira016_nsec_to_lines(mira016, > > + mira016->timings.min_exposure_time); > > + def_exposure = mira016_nsec_to_lines(mira016, > > + MIRA016_EXPOSURE_DEF_NSEC); > > + mira016->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &mira016_ctrl_ops, > > + V4L2_CID_EXPOSURE, > > + min_exposure_lines, > > + MIRA016_MAX_EXPOSURE, 1, > > + def_exposure); > > + > > + mira016->gain = v4l2_ctrl_new_std(ctrl_hdlr, &mira016_ctrl_ops, > > + V4L2_CID_ANALOGUE_GAIN, > > + 1, 1, 1, 1); > > + > > + /* > > + * Changing VFLIP requires re-programming the top point, hence we > > + * program flips along with the ROI windows at enable_streams time. As > > + * we grab the flip controls there, there's no need to handle the two > > + * controls while streaming. > > + */ > > + mira016->hflip = v4l2_ctrl_new_std(ctrl_hdlr, NULL, > > + V4L2_CID_HFLIP, 0, 1, 1, 0); > > + > > + mira016->vflip = v4l2_ctrl_new_std(ctrl_hdlr, NULL, > > + V4L2_CID_VFLIP, 0, 1, 1, 0); > > + > > + v4l2_fwnode_device_parse(&client->dev, &props); > > v4l2_fwnode_device_parse() can return an error and I'd check for it. I'd > however do that before initialising the control handler as it simplifies > error handling. Interesting idea. I still see most drivers calling v4l2_fwnode_device_parse() and v4l2_ctrl_new_fwnode_properties() in sequence at the end of the controls initialization, but doing the parsing at the beginning of the function is certainly a good idea. > > > + v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &mira016_ctrl_ops, > > + &props); > > + > > + if (ctrl_hdlr->error) { > > + ret = ctrl_hdlr->error; > > ret appears redundant; you can return handler's error field. > Ack! > > + goto error; > > + } > > + > > + link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; > > + hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; > > + > > + mira016->sd.ctrl_handler = ctrl_hdlr; > > + > > + return 0; > > + > > +error: > > + v4l2_ctrl_handler_free(ctrl_hdlr); > > + return ret; > > +} > > + > > +static void mira016_timings_calc(struct mira016 *mira016) > > +{ > > + /* Time base is the number of input clock cycles per microsecond. */ > > + mira016->timings.time_base = mira016->xclk_freq / HZ_PER_MHZ; > > + > > + /* Seq time base is in pico-seconds, to not lose precision. */ > > + mira016->timings.seq_time_base = 8 * HZ_PER_MHZ > > + / mira016->pll.data_rate_mbps; > > + > > + /* Min exposure time is in nanoseconds. */ > > + mira016->timings.min_exposure_time = (151 + mira016->pll.lut_del) > > + * mira016->pll.gran_tg > > + * mira016->timings.seq_time_base > > + / HZ_PER_KHZ; > > + > > + /* Glob time is in microseconds. */ > > + mira016->timings.glob_time = (252 + mira016->pll.lut_del) > > + * mira016->pll.gran_tg > > + * mira016->timings.seq_time_base > > + / HZ_PER_MHZ; > > +} > > + > > +static u8 mira016_n_to_pll_n(u32 pll_n) > > +{ > > + /* Table 11: Lookup table for “N to PLL_DIV_N” mapping */ > > + static const struct pll_n_div { > > + u8 n; > > + u8 pll_n; > > + } pll_n_lut[] = { > > + { 1, 31 }, { 2, 0 }, { 3, 16 }, { 4, 24 }, { 5, 28 }, > > + { 6, 14 }, { 7, 7 }, { 8, 19 }, { 9, 9 }, { 10, 4}, > > + { 11, 2 }, { 12, 17 }, { 13, 8 }, { 14, 20 }, { 15, 10 }, > > + { 16, 21 }, { 17, 26 }, { 18, 29 }, { 19, 30 }, { 20, 15 }, > > + { 21, 23 }, { 22, 27 }, { 23, 13 }, { 24, 22 }, { 25, 11 }, > > + { 26, 5 }, { 27, 18 }, { 28, 25 }, { 29, 12 }, > > + { 30, 6 }, { 31, 3 }, { 32, 1 } > > + }; > > + > > + for (unsigned int i = 0; i < ARRAY_SIZE(pll_n_lut); ++i) { > > + if (pll_n_lut[i].n != pll_n) > > + continue; > > + > > + return pll_n_lut[i].pll_n; > > + } > > + > > + return 0; > > +} > > + > > +static u8 mira016_m_to_pll_m(u32 pll_m) > > +{ > > + /* Table 12: Lookup table for “M to PLL_DIV_M” mapping */ > > + static const struct pll_m_div { > > + u8 m_min; > > + u8 m_max; > > + u8 pll_m_min; > > + u8 pll_m_max; > > + } pll_m_lut[] = { > > + { 16, 31, 224, 239 }, { 32, 63, 192, 233 }, > > + { 64, 127, 128, 191 }, { 128, 255, 0, 127 }, > > + }; > > + > > + for (unsigned int i = 0; i < ARRAY_SIZE(pll_m_lut); ++i) { > > + const struct pll_m_div *p = &pll_m_lut[i]; > > + > > + if (pll_m > p->m_max) > > + continue; > > + > > + return p->pll_m_min + pll_m - p->m_min; > > + } > > + > > + return 0; > > +} > > + > > +static void mira016_pll_calc(struct mira016 *mira016) > > It's nice to have a PLL calculator for this. Thank you! :-) > I also considered using the CCS PLL calculator, but this PLL is so simple it felt like an overkill > > +{ > > + /* Multiply link_freq by 2 to account for D-PHY DDR. */ > > + u64 target_mbps = mira016_link_freqs[__ffs(mira016->link_freq_bitmap)] * 2; > > + u32 clk_in = mira016->xclk_freq; > > + u32 best = UINT_MAX; > > + bool found = false; > > + u32 n_best = 0; > > + u32 m_best = 0; > > + u32 gran_tg; > > + u32 n; > > + u32 m; > > + > > + /* > > + * The PLL clock tree is quite simple: > > + * > > + * clk_in / n * m / 2 = csi_link_freq > > + * > > + * Test all the m/n values in the accepted ranges and if we can't get > > + * the exact output frequency use the best approximation we can find. > > + */ > > + for (n = MIRA016_PLL_N_MIN; n < MIRA016_PLL_N_MAX; ++n) { > > + u32 pll1 = clk_in / n; > > + > > + if (pll1 < MIRA016_PLL_PLL1_MIN || pll1 > MIRA016_PLL_PLL1_MAX) > > + continue; > > + > > + for (m = MIRA016_PLL_M_MIN; m < MIRA016_PLL_M_MAX; ++m) { > > + u32 pll2 = pll1 * m; > > + > > + if (pll2 < MIRA016_PLL_PLL2_MIN || > > + pll2 > MIRA016_PLL_PLL2_MAX) > > + continue; > > + > > + if (pll2 == target_mbps) { > > + found = true; > > + break; > > + } > > + > > + if (abs(pll2 - target_mbps) < best) { > > + n_best = n; > > + m_best = m; > > + best = abs(pll2 - target_mbps); > > + } > > + } > > + if (found) > > + break; > > + } > > + > > + if (!found) { > > + dev_info(mira016->dev, > > + "Unable to achieve the desired link frequency; frame rate might not be accurate\n"); > > + n = n_best; > > + m = m_best; > > + } > > + > > + /* > > + * Re-calculate the actual output bandwidth in Mbps. The effective data > > + * rate is required to calculate the sensor base timings. > > + */ > > + mira016->pll.data_rate_mbps = DIV_ROUND_DOWN_ULL((u64)(clk_in / n) * m, > > + HZ_PER_MHZ); > > + if (mira016->pll.data_rate_mbps < 640 || > > + mira016->pll.data_rate_mbps > 1500) { > > + dev_info(mira016->dev, "Invalid data rate %llu\n", > > + mira016->pll.data_rate_mbps); > > + mira016->pll.data_rate_mbps = 1500; > > + } > > + > > + /* > > + * Translate the m and n values to their register codes using two > > + * dedicated look-up tables and complete the PLL configuration. > > + */ > > + mira016->pll.n = mira016_n_to_pll_n(n); > > + mira016->pll.m = mira016_m_to_pll_m(m); > > + > > + mira016->pll.otp_gran = DIV_ROUND_UP_ULL(mira016->pll.data_rate_mbps, 160) - 1; > > + > > + /* > > + * As the division might introduce rounding errors, compute gran_tg > > + * in the Kbps domain and then divide by 10^3. > > + */ > > + gran_tg = mira016->pll.data_rate_mbps * KHZ_PER_MHZ / 1500; > > + mira016->pll.gran_tg = DIV_ROUND_UP_ULL(gran_tg * 50, KHZ_PER_MHZ); > > + > > + mira016->pll.lut_del = DIV_ROUND_UP_ULL(7000, mira016->pll.gran_tg) - 140; > > + mira016->pll.byte_period = DIV_ROUND_DOWN_ULL(8000, > > + mira016->pll.data_rate_mbps); > > +} > > + > > +static int mira016_parse_endpoint(struct device *dev, struct mira016 *mira016) > > +{ > > + struct fwnode_handle *endpoint __free(fwnode_handle) = NULL; > > + struct v4l2_fwnode_endpoint ep_cfg = { > > + .bus_type = V4L2_MBUS_CSI2_DPHY > > + }; > > + > > + endpoint = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, 0); > > + if (v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep_cfg)) > > + return dev_err_probe(dev, -EINVAL, "Failed to parse endpoint\n"); > > Don't mask error codes! Just return the error code returned by > v4l2_fwnode_endpoint_alloc_parse(). > With PTR_ERR() I presume > > + > > + /* > > + * Link frequencies: the driver supports a single link frequency, > > + * no need to check bitmap after this call. > > + */ > > + if (v4l2_link_freq_to_bitmap(dev, ep_cfg.link_frequencies, > > + ep_cfg.nr_of_link_frequencies, > > + mira016_link_freqs, > > + ARRAY_SIZE(mira016_link_freqs), > > + &mira016->link_freq_bitmap)) { > > Ditto. > > > + v4l2_fwnode_endpoint_free(&ep_cfg); > > + return -EINVAL; > > + } > > + > > + /* TODO: Implement D-PHY configuration to support continuous clock. */ > > + if (!(ep_cfg.bus.mipi_csi2.flags & V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK)) { > > + v4l2_fwnode_endpoint_free(&ep_cfg); > > Instead of callind v4l2_fwnode_endpoint_free() here and above, I'd add a > label for error handling. > ack > > + return dev_err_probe(dev, -EINVAL, > > + "Continuous clock is not supported\n"); > > + } > > + > > + mira016->bus_config = ep_cfg.bus.mipi_csi2.flags; > > + > > + v4l2_fwnode_endpoint_free(&ep_cfg); > > + > > + return 0; > > +} > > + > > +static int mira016_get_regulators(struct mira016 *mira016) > > +{ > > + struct i2c_client *client = v4l2_get_subdevdata(&mira016->sd); > > + > > + for (unsigned int i = 0; i < ARRAY_SIZE(mira016_supplies); i++) > > + mira016->supplies[i].supply = mira016_supplies[i]; > > + > > + return devm_regulator_bulk_get(&client->dev, > > + ARRAY_SIZE(mira016_supplies), > > + mira016->supplies); > > +} > > + > > +static int mira016_validate_xclk_freq(struct mira016 *mira016) > > +{ > > + /* > > + * Valid clock frequencies input range: > > + * 12MHz <= xclk < 20MHz - 24MHz >= xclk <= 64MHz > > + */ > > + if (mira016->xclk_freq < MHZ(12) || mira016->xclk_freq > MHZ(64) || > > + (mira016->xclk_freq >= MHZ(20) && mira016->xclk_freq < MHZ(24))) > > + return -EINVAL; > > + > > + /* Set the PLL parameters that depend on the input clock frequency */ > > + mira016->pll.cp_div = mira016->xclk_freq <= MHZ(30) ? 1 : 2; > > + mira016->pll.esc_h = mira016->xclk_freq < MHZ(20) ? 0 : > > + mira016->xclk_freq < MHZ(40) ? 1 : 2; > > + mira016->pll.esc_l = mira016->xclk_freq < MHZ(20) ? 0 : > > + mira016->xclk_freq < MHZ(60) ? 1 : 2; > > + mira016->pll.esc_period = mira016->xclk_freq < MHZ(20) ? > > + DIV_ROUND_CLOSEST(1000, mira016->xclk_freq) > > + : mira016->xclk_freq < MHZ(40) ? > > + DIV_ROUND_CLOSEST(2000, mira016->xclk_freq) > > + : mira016->xclk_freq < MHZ(60) ? > > + DIV_ROUND_CLOSEST(3000, mira016->xclk_freq) > > + : DIV_ROUND_CLOSEST(4000, mira016->xclk_freq); > > + > > + return 0; > > +} > > + > > +static int mira016_probe(struct i2c_client *client) > > +{ > > + struct device *dev = &client->dev; > > + struct mira016 *mira016; > > + int ret; > > + > > + mira016 = devm_kzalloc(&client->dev, sizeof(*mira016), GFP_KERNEL); > > + if (!mira016) > > + return -ENOMEM; > > + > > + mira016->dev = &client->dev; > > + > > + v4l2_i2c_subdev_init(&mira016->sd, client, &mira016_subdev_ops); > > + > > + mira016->regmap = devm_cci_regmap_init_i2c(client, 16); > > + if (IS_ERR(mira016->regmap)) > > + return dev_err_probe(dev, PTR_ERR(mira016->regmap), > > + "failed to initialize CCI\n"); > > + > > + mira016->xclk = devm_v4l2_sensor_clk_get(dev, NULL); > > + if (IS_ERR(mira016->xclk)) > > + return dev_err_probe(dev, PTR_ERR(mira016->xclk), > > + "failed to get xclk\n"); > > + > > + mira016->xclk_freq = clk_get_rate(mira016->xclk); > > + if (mira016_validate_xclk_freq(mira016)) { > > + dev_err(dev, "xclk frequency not supported: %d Hz\n", > > + mira016->xclk_freq); > > + return -EINVAL; > > + } > > + > > + ret = mira016_get_regulators(mira016); > > + if (ret) > > + return dev_err_probe(dev, ret, "failed to get regulators\n"); > > + > > + mira016->reset_gpio = devm_gpiod_get_optional(dev, "reset", > > + GPIOD_OUT_HIGH); > > + if (IS_ERR(mira016->reset_gpio)) > > + return dev_err_probe(dev, PTR_ERR(mira016->reset_gpio), > > + "failed to get reset gpio\n"); > > + > > + ret = mira016_parse_endpoint(dev, mira016); > > + if (ret) > > + return ret; > > Parsing the endpoint should be done as soon as you can before acquiring > resources related to power management, for instance. -EPROBE_DEFER may well > be returned in some cases. > I can probably do that even before calling v4l2_i2c_subdev_init() > > + > > + /* > > + * Calculate the PLL configuration based on the link frequency > > + * selected by .dts and compute the sensor timing bases. > > + * > > + * Initialize row_length to a value matching the default format for > > + * exposure and frame time limits calculations. > > + */ > > + mira016_pll_calc(mira016); > > + mira016_timings_calc(mira016); > > + mira016->timings.row_length = 1262; > > + > > + ret = mira016_power_on(dev); > > + if (ret) > > + return ret; > > + > > + /* Enable runtime PM and power on the device */ > > + pm_runtime_set_active(dev); > > + pm_runtime_enable(dev); > > + > > + ret = mira016_identify_module(mira016); > > + if (ret) > > + goto error_power_off; > > + > > + ret = mira016_init_controls(mira016); > > + if (ret) > > + goto error_power_off; > > + > > + /* Initialize subdev */ > > + mira016->sd.internal_ops = &mira016_internal_ops; > > + mira016->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; > > + mira016->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; > > + > > + /* Initialize source pads */ > > + mira016->pad.flags = MEDIA_PAD_FL_SOURCE; > > + > > + ret = media_entity_pads_init(&mira016->sd.entity, 1, &mira016->pad); > > + if (ret) { > > + dev_err_probe(dev, ret, "failed to init entity pads\n"); > > + goto error_handler_free; > > + } > > + > > + mira016->sd.state_lock = mira016->ctrl_handler.lock; > > + ret = v4l2_subdev_init_finalize(&mira016->sd); > > + if (ret < 0) { > > + dev_err_probe(dev, ret, "subdev init error\n"); > > + goto error_media_entity; > > + } > > + > > + ret = v4l2_async_register_subdev_sensor(&mira016->sd); > > + if (ret < 0) { > > + dev_err_probe(dev, ret, > > + "failed to register sensor sub-device\n"); > > + goto error_subdev_cleanup; > > + } > > + > > + pm_runtime_set_autosuspend_delay(dev, 1000); > > + pm_runtime_use_autosuspend(dev); > > + pm_runtime_idle(dev); > > + > > + return 0; > > + > > +error_subdev_cleanup: > > + v4l2_subdev_cleanup(&mira016->sd); > > +error_media_entity: > > + media_entity_cleanup(&mira016->sd.entity); > > +error_handler_free: > > + v4l2_ctrl_handler_free(mira016->sd.ctrl_handler); > > +error_power_off: > > + pm_runtime_disable(dev); > > + if (!pm_runtime_status_suspended(&client->dev)) > > + mira016_power_off(dev); > > + pm_runtime_set_suspended(dev); > > + return ret; > > +} > > + > > +static void mira016_remove(struct i2c_client *client) > > +{ > > + struct v4l2_subdev *sd = i2c_get_clientdata(client); > > + struct mira016 *mira016 = to_mira016(sd); > > + > > + v4l2_ctrl_handler_free(mira016->sd.ctrl_handler); > > + > > + v4l2_async_unregister_subdev(sd); > > + v4l2_subdev_cleanup(&mira016->sd); > > + media_entity_cleanup(&sd->entity); > > + > > + pm_runtime_disable(&client->dev); > > + if (!pm_runtime_status_suspended(&client->dev)) > > + mira016_power_off(&client->dev); > > + pm_runtime_set_suspended(&client->dev); > > pm_runtime_dont_use_autosuspend()? > Ack Thanks for the review! > > +} > > + > > +static const struct of_device_id mira016_dt_ids[] = { > > + { .compatible = "ams,mira016" }, > > + { /* sentinel */ } > > +}; > > +MODULE_DEVICE_TABLE(of, mira016_dt_ids); > > + > > +static struct i2c_driver mira016_i2c_driver = { > > + .driver = { > > + .name = "mira016", > > + .of_match_table = mira016_dt_ids, > > + .pm = pm_ptr(&mira016_pm_ops), > > + }, > > + .probe = mira016_probe, > > + .remove = mira016_remove, > > +}; > > + > > +module_i2c_driver(mira016_i2c_driver); > > + > > +MODULE_AUTHOR("Jacopo Mondi "); > > +MODULE_DESCRIPTION("ams OSRAM MIRA016 sensor driver"); > > +MODULE_LICENSE("GPL"); > > > > -- > Kind regards, > > Sakari Ailus