* [PATCH v2 0/2] media: i2c: Add driver for Mira016
@ 2026-09-04 10:43 Jacopo Mondi
2026-09-04 10:43 ` [PATCH v2 1/2] dt-bindings: media: i2c: Add Mira016 image sensor Jacopo Mondi
2026-09-04 10:43 ` [PATCH v2 2/2] media: i2c: mira016: Add driver for Mira016 Jacopo Mondi
0 siblings, 2 replies; 5+ messages in thread
From: Jacopo Mondi @ 2026-09-04 10:43 UTC (permalink / raw)
To: Philippe Baetens, Mauro Carvalho Chehab, Sakari Ailus,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Kieran Bingham,
Jai Luthra
Cc: linux-media, devicetree, linux-kernel, Jacopo Mondi
ams OSRAM Mira016 is a 0.16MP NIR enhanced global shutter image sensor.
It supports 8, 10 and 12 bits luma-only image formats with configurable
gains (only in 8 and 10 bit modes, with the 12 bit gains fixed at 1x).
Add a Video4Linux2 subdevice driver for it.
The Mira016 supports binning, skipping and embedded data. It would be
an ideal candidate for the new common RAW sensor model.
As the model is not available yet in mainline, this first version
doesn't support set_selection() and doesn't support embedded data.
These features will be implemented on top of the new RAW sensor model
once available.
CI pipeline:
https://gitlab.freedesktop.org/linux-media/users/jmondi/-/pipelines/1740218
$ ./v4l2-compliance -d /dev/v4l-subdev2
v4l2-compliance 1.33.0-5491, 64 bits, 64-bit time_t
v4l2-compliance SHA: b32589c51481 2026-07-16 08:51:38
Compliance test for device /dev/v4l-subdev2:
Driver Info:
Driver version : 6.18.37
Capabilities : 0x00000000
Client Capabilities: 0x0000000000000002
interval-uses-which
Required ioctls:
test VIDIOC_SUDBEV_QUERYCAP: OK
test invalid ioctls: OK
Allow for multiple opens:
test second /dev/v4l-subdev2 open: OK
test VIDIOC_SUBDEV_QUERYCAP: OK
test for unlimited opens: OK
Debug ioctls:
test VIDIOC_LOG_STATUS: OK (Not Supported)
Input ioctls:
test VIDIOC_G/S_TUNER/ENUM_FREQ_BANDS: OK (Not Supported)
test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
test VIDIOC_S_HW_FREQ_SEEK: OK (Not Supported)
test VIDIOC_ENUMAUDIO: OK (Not Supported)
test VIDIOC_G/S/ENUMINPUT: OK (Not Supported)
test VIDIOC_G/S_AUDIO: OK (Not Supported)
Inputs: 0 Audio Inputs: 0 Tuners: 0
Output ioctls:
test VIDIOC_G/S_MODULATOR: OK (Not Supported)
test VIDIOC_G/S_FREQUENCY: OK (Not Supported)
test VIDIOC_ENUMAUDOUT: OK (Not Supported)
test VIDIOC_G/S/ENUMOUTPUT: OK (Not Supported)
test VIDIOC_G/S_AUDOUT: OK (Not Supported)
Outputs: 0 Audio Outputs: 0 Modulators: 0
Input/Output configuration ioctls:
test VIDIOC_ENUM/G/S/QUERY_STD: OK (Not Supported)
test VIDIOC_ENUM/G/S/QUERY_DV_TIMINGS: OK (Not Supported)
test VIDIOC_DV_TIMINGS_CAP: OK (Not Supported)
test VIDIOC_G/S_EDID: OK (Not Supported)
Control ioctls:
test VIDIOC_QUERY_EXT_CTRL/QUERYMENU: OK
test VIDIOC_QUERYCTRL: OK
test VIDIOC_G/S_CTRL: OK
test VIDIOC_G/S/TRY_EXT_CTRLS: OK
test VIDIOC_(UN)SUBSCRIBE_EVENT/DQEVENT: OK
test VIDIOC_G/S_JPEGCOMP: OK (Not Supported)
Standard Controls: 11 Private Controls: 0
Format ioctls:
test VIDIOC_ENUM_FMT/FRAMESIZES/FRAMEINTERVALS: OK (Not Supported)
test VIDIOC_G/S_PARM: OK (Not Supported)
test VIDIOC_G_FBUF: OK (Not Supported)
test VIDIOC_G_FMT: OK (Not Supported)
test VIDIOC_TRY_FMT: OK (Not Supported)
test VIDIOC_S_FMT: OK (Not Supported)
test VIDIOC_G_SLICED_VBI_CAP: OK (Not Supported)
test Cropping: OK (Not Supported)
test Composing: OK (Not Supported)
test Scaling: OK (Not Supported)
Codec ioctls:
test VIDIOC_(TRY_)ENCODER_CMD: OK (Not Supported)
test VIDIOC_G_ENC_INDEX: OK (Not Supported)
test VIDIOC_(TRY_)DECODER_CMD: OK (Not Supported)
Buffer ioctls:
test VIDIOC_REQBUFS/CREATE_BUFS/QUERYBUF: OK (Not Supported)
test CREATE_BUFS maximum buffers: OK
test VIDIOC_REMOVE_BUFS: OK
test VIDIOC_EXPBUF: OK (Not Supported)
test Requests: OK (Not Supported)
test blocking wait: OK (Not Supported)
Total for device /dev/v4l-subdev2: 46, Succeeded: 46, Failed: 0, Warnings: 0
Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
---
Changes in v2:
- Address Sashiko comments:
- Do not overwrite cci_write() return error codes
- Don't call mira016_calc_row_length() in init_state()
- This would re-calculate row length at each open() call and modify
the sensor configuration even for TRY states.
- The call is also unecessary as:
- row_length is initialized in probe()
- mira016_calc_row_length() is called by set_pad_format() for ACTIVE
states already
- Offset the gain codes by 1. The gain LUT tables are 0-indexed while
V4L2 controls are indexed by 1. Adjust the gain index before accessing
the LUT to avoid and out-of-bounds error
- Free the control handler before the subdev state during remove, as the
s_ctrl handler uses the subdev active state
- Link to v1: https://patch.msgid.link/20260903-mira016-v1-0-af0013ef070d@ideasonboard.com
---
Jacopo Mondi (2):
dt-bindings: media: i2c: Add Mira016 image sensor
media: i2c: mira016: Add driver for Mira016
.../devicetree/bindings/media/i2c/ams,mira016.yaml | 95 +
MAINTAINERS | 8 +
drivers/media/i2c/Kconfig | 12 +
drivers/media/i2c/Makefile | 1 +
drivers/media/i2c/mira016.c | 2294 ++++++++++++++++++++
5 files changed, 2410 insertions(+)
---
base-commit: f9536a8065d9eed0d9114878c56ffc4e5a18bdfc
change-id: 20260902-mira016-77eb6b6253d4
Best regards,
--
Jacopo Mondi <jacopo.mondi@ideasonboard.com>
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v2 1/2] dt-bindings: media: i2c: Add Mira016 image sensor
2026-09-04 10:43 [PATCH v2 0/2] media: i2c: Add driver for Mira016 Jacopo Mondi
@ 2026-09-04 10:43 ` Jacopo Mondi
2026-09-04 15:13 ` Conor Dooley
2026-09-04 10:43 ` [PATCH v2 2/2] media: i2c: mira016: Add driver for Mira016 Jacopo Mondi
1 sibling, 1 reply; 5+ messages in thread
From: Jacopo Mondi @ 2026-09-04 10:43 UTC (permalink / raw)
To: Philippe Baetens, Mauro Carvalho Chehab, Sakari Ailus,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Kieran Bingham,
Jai Luthra
Cc: linux-media, devicetree, linux-kernel, Jacopo Mondi
Add bindings for the ams OSRAM Mira016 image sensor.
Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
---
.../devicetree/bindings/media/i2c/ams,mira016.yaml | 95 ++++++++++++++++++++++
MAINTAINERS | 7 ++
2 files changed, 102 insertions(+)
diff --git a/Documentation/devicetree/bindings/media/i2c/ams,mira016.yaml b/Documentation/devicetree/bindings/media/i2c/ams,mira016.yaml
new file mode 100644
index 000000000000..dff51c8a2ede
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/i2c/ams,mira016.yaml
@@ -0,0 +1,95 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/i2c/ams,mira016.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: AMS 0.16 MP NIR enhanced global shutter image sensor
+
+maintainers:
+ - Jacopo Mondi <jacopo.mondi@ideasonboard.com>
+ - Philippe Baetens <philippebaetens@gmail.com>
+
+description:
+ Mira016 is a 0.16 MP NIR enhanced global shutter image sensor designed for 2D
+ and 3D consumer and industrial machine vision applications. The sensor is
+ compliant to the MIPI CSI-2 v1.3 protocol interface and the D-PHY v1.2
+ physical layer specifications to transmit the image data to the host
+ processor. It uses one data lane and one clock lane operating up to 1.5 Gbps.
+
+allOf:
+ - $ref: /schemas/media/video-interface-devices.yaml#
+
+properties:
+ compatible:
+ const: ams,mira016
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ maxItems: 1
+
+ vdd28-supply:
+ description:
+ I/O voltage supply, 2.8 volts
+
+ vdd11-supply:
+ description:
+ I/O voltage supply, 1.1 volts
+
+ reset-gpios:
+ description: Sensor reset (RST_N) GPIO
+ maxItems: 1
+
+ port:
+ $ref: /schemas/graph.yaml#/$defs/port-base
+ additionalProperties: false
+ description:
+ Video output port
+
+ properties:
+ endpoint:
+ $ref: /schemas/media/video-interfaces.yaml#
+ unevaluatedProperties: false
+
+ properties:
+ data-lanes:
+ items:
+ - const: 1
+
+ required:
+ - data-lanes
+ - link-frequencies
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - vdd28-supply
+ - vdd11-supply
+ - port
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ mira016: camera-sensor@36 {
+ compatible = "ams,mira016";
+ reg = <0x36>;
+ clocks = <&cam1_clk>;
+ vdd28-supply = <&cam1_reg>;
+ vdd11-supply = <&cam_dummy_reg>;
+ port {
+ mira016_ep: endpoint {
+ data-lanes = <1>;
+ link-frequencies = /bits/ 64 <750000000>;
+ };
+ };
+ };
+ };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index e12dc3ca57ab..1c24b05b378b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1454,6 +1454,13 @@ S: Maintained
F: Documentation/devicetree/bindings/iio/light/ams,as73211.yaml
F: drivers/iio/light/as73211.c
+AMS MIRA016 DRIVER
+M: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
+M: Philippe Baetens <philippebaetens@gmail.com>
+L: linux-media@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/media/i2c/ams,mira016.yaml
+
AMT (Automatic Multicast Tunneling)
M: Taehee Yoo <ap420073@gmail.com>
L: netdev@vger.kernel.org
--
2.55.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v2 2/2] media: i2c: mira016: Add driver for Mira016
2026-09-04 10:43 [PATCH v2 0/2] media: i2c: Add driver for Mira016 Jacopo Mondi
2026-09-04 10:43 ` [PATCH v2 1/2] dt-bindings: media: i2c: Add Mira016 image sensor Jacopo Mondi
@ 2026-09-04 10:43 ` Jacopo Mondi
2026-09-04 11:02 ` sashiko-bot
1 sibling, 1 reply; 5+ messages in thread
From: Jacopo Mondi @ 2026-09-04 10:43 UTC (permalink / raw)
To: Philippe Baetens, Mauro Carvalho Chehab, Sakari Ailus,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Kieran Bingham,
Jai Luthra
Cc: linux-media, devicetree, linux-kernel, Jacopo Mondi
Add driver for the ams OSRAM Mira016 sensor.
Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
---
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 <philippebaetens@gmail.com>
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 <ap420073@gmail.com>
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 <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/iopoll.h>
+#include <linux/math.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+#include <linux/units.h>
+
+#include <media/mipi-csi2.h>
+#include <media/v4l2-cci.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-mediabus.h>
+
+/* 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.
+ */
+ 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).
+ */
+
+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;
+}
+
+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;
+}
+
+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;
+
+ 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 },
+ { 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;
+ 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;
+ }
+
+ 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_ctrl_new_fwnode_properties(ctrl_hdlr, &mira016_ctrl_ops,
+ &props);
+
+ if (ctrl_hdlr->error) {
+ ret = ctrl_hdlr->error;
+ 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)
+{
+ /* 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");
+
+ /*
+ * 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)) {
+ 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);
+ 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;
+
+ /*
+ * 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);
+}
+
+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 <jacopo.mondi@ideasonboard.com>");
+MODULE_DESCRIPTION("ams OSRAM MIRA016 sensor driver");
+MODULE_LICENSE("GPL");
--
2.55.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v2 2/2] media: i2c: mira016: Add driver for Mira016
2026-09-04 10:43 ` [PATCH v2 2/2] media: i2c: mira016: Add driver for Mira016 Jacopo Mondi
@ 2026-09-04 11:02 ` sashiko-bot
0 siblings, 0 replies; 5+ messages in thread
From: sashiko-bot @ 2026-09-04 11:02 UTC (permalink / raw)
To: Jacopo Mondi; +Cc: devicetree, robh, conor+dt, media-ci
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [Critical] The set_fmt pad operation corrupts the active subdevice state if called while streaming, leading to an out-of-bounds array access.
- [High] V4L2 control handler is freed before unregistering the subdevice, allowing a use-after-free race condition.
- [High] mira016_init_state() calls set_pad_format() with V4L2_SUBDEV_FORMAT_TRY, skipping the active state control range and internal timing initialization.
- [High] Missing stabilization delay after de-asserting hardware reset and enabling clocks before accessing I2C registers.
- [High] I2C errors are silently swallowed by read_poll_timeout macro in mira016_configure_timings.
- [Medium] Missing pm_runtime_mark_last_busy() causes immediate suspend when dropping the runtime PM reference.
--
commit e3f67c157423cc82225f8c12e6de36ab28b0fded
Author: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
media: i2c: mira016: Add driver for Mira016
Add driver for the ams OSRAM Mira016 sensor.
> diff --git a/drivers/media/i2c/mira016.c b/drivers/media/i2c/mira016.c
> new file mode 100644
> index 0000000000000..8b8fcbd50fe23
> --- /dev/null
> +++ b/drivers/media/i2c/mira016.c
> @@ -0,0 +1,2294 @@
[ ... ]
> +static int mira016_configure_timings(struct mira016 *mira016)
> +{
[ ... ]
> + 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;
[Severity: High]
Does this read_poll_timeout loop unintentionally swallow I2C errors?
If cci_read() fails (for example, returning -EIO), the error is assigned
to ret, making the condition (ret < 0 || ...) evaluate to true. This
causes the loop to break successfully, and the macro evaluates to 0.
Because the return value of read_poll_timeout is stored in ret, it overwrites
the original I2C error with 0, completely defeating the check
if (ret < 0) below. This would cause the driver to silently ignore failures
during PLL configuration.
[ ... ]
> +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];
[Severity: Critical]
Can this code trigger an out-of-bounds array read if userspace changes the
image format while the device is streaming?
Looking at mira016_set_pad_format(), the requested format is written to the
active subdevice state BEFORE the driver checks if the subdevice is currently
streaming:
mira016_update_pad_format(mira016, &fmt->format, fmt->format.code);
*v4l2_subdev_state_get_format(state, 0) = fmt->format;
...
if (v4l2_subdev_is_streaming(sd))
return -EBUSY;
If the subdevice is streaming, it returns -EBUSY and skips updating the
analog gain control limits. The state has already been corrupted, though.
If userspace changes an 8-bit format to a 10-bit format while streaming,
the active state changes to 10-bit, but the control handler range remains
bound to the wider limits of the 8-bit format (max 83).
A subsequent call to VIDIOC_S_CTRL setting the gain to 83 would pass the
V4L2 core range check. Here in mira016_write_analogue_gain(), it would index
into the 49-element mira016_gain_lut_10bit array using 82, leading to an
out-of-bounds kernel memory read and an invalid hardware state.
[ ... ]
> +static int mira016_set_pad_format(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_format *fmt)
> +{
[ ... ]
> + 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;
[Severity: High]
Does this early return prevent the active state from being fully initialized?
During probe, v4l2_subdev_init_finalize() calls mira016_init_state() to
populate the active state. However, mira016_init_state() constructs a
dummy format using V4L2_SUBDEV_FORMAT_TRY:
struct v4l2_subdev_format fmt = {
.which = V4L2_SUBDEV_FORMAT_TRY,
...
mira016_set_pad_format(sd, state, &fmt);
Because mira016_set_pad_format() immediately returns when it sees
V4L2_SUBDEV_FORMAT_TRY, it skips updating the analog gain control limit
and fails to set mira016->timings.row_length for the active state.
This leaves the control range incorrectly clamped to default probe limits
and breaks pixel rate calculations until userspace explicitly issues a
VIDIOC_SUBDEV_S_FMT to override it.
[ ... ]
> +static int mira016_power_on(struct device *dev)
> +{
[ ... ]
> + ret = clk_prepare_enable(mira016->xclk);
> + if (ret) {
> + dev_err(&client->dev, "%s: failed to enable clock\n", __func__);
> + goto reg_off;
> + }
> +
> + gpiod_set_value_cansleep(mira016->reset_gpio, 0);
> +
> + return 0;
> +}
[Severity: High]
Should there be a delay here to allow the hardware to stabilize after
de-asserting the reset line?
Looking at mira016_probe(), the driver immediately issues I2C reads to the
sensor using mira016_identify_module() right after calling mira016_power_on():
ret = mira016_power_on(dev);
if (ret)
return ret;
...
ret = mira016_identify_module(mira016);
Without a stabilization delay (e.g., usleep_range()) after de-asserting
reset and before sending I2C commands, this may cause sporadic or consistent
probe failures due to I2C timeouts or NACKs.
[ ... ]
> +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);
[Severity: Medium]
Will this bypass the configured autosuspend delay of 1000ms?
The driver drops its PM reference here without first calling
pm_runtime_mark_last_busy(&client->dev).
If the streaming duration exceeds the autosuspend delay configured at probe
time, the runtime PM core will calculate the delay expiration against an old
last_busy timestamp. This will evaluate as already expired, causing the
device to immediately suspend rather than honoring the timeout.
[ ... ]
> +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);
[Severity: High]
Does this sequence open a use-after-free race condition?
The control handler and its active state mutex are destroyed by
v4l2_ctrl_handler_free(), but this happens before the subdevice is
unregistered via v4l2_async_unregister_subdev().
Until the subdevice is unregistered, it remains fully visible to userspace.
If an ioctl (like VIDIOC_S_CTRL) is executed concurrently by userspace
while remove() is running, the V4L2 core will access the already-freed
control handler structures and attempt to acquire a destroyed mutex.
Unregistering the subdevice first would prevent userspace from accessing it
while it's being dismantled.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260904-mira016-v2-0-1dcf7b3a807e@ideasonboard.com?part=2
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v2 1/2] dt-bindings: media: i2c: Add Mira016 image sensor
2026-09-04 10:43 ` [PATCH v2 1/2] dt-bindings: media: i2c: Add Mira016 image sensor Jacopo Mondi
@ 2026-09-04 15:13 ` Conor Dooley
0 siblings, 0 replies; 5+ messages in thread
From: Conor Dooley @ 2026-09-04 15:13 UTC (permalink / raw)
To: Jacopo Mondi
Cc: Philippe Baetens, Mauro Carvalho Chehab, Sakari Ailus,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Kieran Bingham,
Jai Luthra, linux-media, devicetree, linux-kernel
[-- Attachment #1: Type: text/plain, Size: 788 bytes --]
On Fri, Sep 04, 2026 at 12:43:40PM +0200, Jacopo Mondi wrote:
> +examples:
> + - |
> + i2c {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + mira016: camera-sensor@36 {
Drop the two unused labels here if there's a v3,
Acked-by: Conor Dooley <conor.dooley@microchip.com>
pw-bot: not-applicable
Thanks,
Conor.
> + compatible = "ams,mira016";
> + reg = <0x36>;
> + clocks = <&cam1_clk>;
> + vdd28-supply = <&cam1_reg>;
> + vdd11-supply = <&cam_dummy_reg>;
> + port {
> + mira016_ep: endpoint {
> + data-lanes = <1>;
> + link-frequencies = /bits/ 64 <750000000>;
> + };
> + };
> + };
> + };
> +...
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-09-04 15:13 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-04 10:43 [PATCH v2 0/2] media: i2c: Add driver for Mira016 Jacopo Mondi
2026-09-04 10:43 ` [PATCH v2 1/2] dt-bindings: media: i2c: Add Mira016 image sensor Jacopo Mondi
2026-09-04 15:13 ` Conor Dooley
2026-09-04 10:43 ` [PATCH v2 2/2] media: i2c: mira016: Add driver for Mira016 Jacopo Mondi
2026-09-04 11:02 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox