mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v5 0/2] media: i2c: Add driver for Mira016
@ 2026-09-30 10:51 Jacopo Mondi
  2026-09-30 10:51 ` [PATCH v5 1/2] dt-bindings: media: i2c: Add Mira016 image sensor Jacopo Mondi
  2026-09-30 10:51 ` [PATCH v5 2/2] media: i2c: mira016: Add driver for Mira016 Jacopo Mondi
  0 siblings, 2 replies; 11+ messages in thread
From: Jacopo Mondi @ 2026-09-30 10:51 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, Conor Dooley

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/1756788

$ ./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 v5:
- Rebase and add support for struct v4l2_subdev_client_info
- Call pm_runtime_set_suspended() conditioanlly
- Remove start streaming from register sequences
- Break-out ROW_LENGTH configuration to a dedicated function
- Remove start streaming from register sequences
- Link to v4: https://patch.msgid.link/20260908-mira016-v4-0-1950504c131c@ideasonboard.com

Changes in v4:
- Address Sashiko comments:
  - Correct XWIN and XRIGHT registers sizes
  - Correct pll esc_period calculation
  - Do not mix gotos and cleanup in endpoint parsing
- Link to v3: https://patch.msgid.link/20260907-mira016-v3-0-b078a6793d65@ideasonboard.com

Changes in v3:
- dts: remove labels from example
- Address Sakari's comments
  - Parse endpoint early
  - Do not overwrite firmware parsing errors
  - use div_u64()
- Link to v2: https://patch.msgid.link/20260904-mira016-v2-0-1dcf7b3a807e@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 |   97 +
 MAINTAINERS                                        |    8 +
 drivers/media/i2c/Kconfig                          |   12 +
 drivers/media/i2c/Makefile                         |    1 +
 drivers/media/i2c/mira016.c                        | 2309 ++++++++++++++++++++
 5 files changed, 2427 insertions(+)
---
base-commit: 58348f64125e9a3e44d3abb275ca7f4e6c9641e5
change-id: 20260902-mira016-77eb6b6253d4

Best regards,
--  
Jacopo Mondi <jacopo.mondi@ideasonboard.com>


^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH v5 1/2] dt-bindings: media: i2c: Add Mira016 image sensor
  2026-09-30 10:51 [PATCH v5 0/2] media: i2c: Add driver for Mira016 Jacopo Mondi
@ 2026-09-30 10:51 ` Jacopo Mondi
  2026-09-30 10:51 ` [PATCH v5 2/2] media: i2c: mira016: Add driver for Mira016 Jacopo Mondi
  1 sibling, 0 replies; 11+ messages in thread
From: Jacopo Mondi @ 2026-09-30 10:51 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, Conor Dooley

Add bindings for the ams OSRAM Mira016 image sensor.

Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
Acked-by: Conor Dooley <conor.dooley@microchip.com>
---
 .../devicetree/bindings/media/i2c/ams,mira016.yaml | 97 ++++++++++++++++++++++
 MAINTAINERS                                        |  7 ++
 2 files changed, 104 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..49a606fca6cb
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/i2c/ams,mira016.yaml
@@ -0,0 +1,97 @@
+# 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>;
+
+        camera@36 {
+            compatible = "ams,mira016";
+            reg = <0x36>;
+            clocks = <&cam1_clk>;
+
+            vdd28-supply = <&cam1_reg>;
+            vdd11-supply = <&cam_dummy_reg>;
+
+            port {
+                endpoint {
+                    data-lanes = <1>;
+                    link-frequencies = /bits/ 64 <750000000>;
+                };
+            };
+        };
+    };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index 4cc4a2dc6d30..1296280f4299 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	[flat|nested] 11+ messages in thread

* [PATCH v5 2/2] media: i2c: mira016: Add driver for Mira016
  2026-09-30 10:51 [PATCH v5 0/2] media: i2c: Add driver for Mira016 Jacopo Mondi
  2026-09-30 10:51 ` [PATCH v5 1/2] dt-bindings: media: i2c: Add Mira016 image sensor Jacopo Mondi
@ 2026-09-30 10:51 ` Jacopo Mondi
  2026-09-30 13:47   ` Tarang Raval
                     ` (2 more replies)
  1 sibling, 3 replies; 11+ messages in thread
From: Jacopo Mondi @ 2026-09-30 10:51 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 | 2309 +++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 2323 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 1296280f4299..38721e673911 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 9b28e91b8a67..a217e2d43f3c 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -321,6 +321,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 fd1cb25718c0..23f8a6b865c4 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -76,6 +76,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..539216b5816f
--- /dev/null
+++ b/drivers/media/i2c/mira016.c
@@ -0,0 +1,2309 @@
+// 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/math64.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_REG16(0xe02c)
+#define MIRA016_XWIN_RIGHT_REG		CCI_REG16(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 lose 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 div_u64((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 },
+};
+
+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,
+				  const struct v4l2_subdev_client_info *ci,
+				  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,
+				 const struct v4l2_subdev_client_info *ci,
+				 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)),
+				500, 10000, 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;
+
+	return 0;
+}
+
+static int mira016_configure_row_length(struct mira016 *mira016)
+{
+	int ret = 0;
+
+	/* 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 = mira016_configure_row_length(mira016);
+	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, NULL, 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__);
+		regulator_bulk_disable(ARRAY_SIZE(mira016_supplies),
+				       mira016->supplies);
+		return ret;
+	}
+
+	gpiod_set_value_cansleep(mira016->reset_gpio, 0);
+
+	return 0;
+}
+
+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;
+
+	ret = v4l2_fwnode_device_parse(&client->dev, &props);
+	if (ret)
+		return 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_ctrl_new_fwnode_properties(ctrl_hdlr, &mira016_ctrl_ops,
+					&props);
+
+	if (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 ctrl_hdlr->error;
+}
+
+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
+	};
+	int ret;
+
+	endpoint = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, 0);
+	if (!endpoint)
+		return -ENODEV;
+
+	ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep_cfg);
+	if (ret)
+		return ret;
+
+	/*
+	 * Link frequencies: the driver supports a single link frequency,
+	 * no need to check bitmap after this call.
+	 */
+	ret = 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);
+	if (ret) {
+		v4l2_fwnode_endpoint_free(&ep_cfg);
+		return ret;
+	}
+
+	/* TODO: Implement D-PHY configuration to support continuous clock. */
+	if (!(ep_cfg.bus.mipi_csi2.flags & V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK)) {
+		dev_err(dev, "Continuous clock is not supported\n");
+		v4l2_fwnode_endpoint_free(&ep_cfg);
+		return -EINVAL;
+	}
+
+	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)
+{
+	u32 xclk_mhz = mira016->xclk_freq / HZ_PER_MHZ;
+
+	/*
+	 * Valid clock frequencies input range:
+	 * 12MHz <= xclk < 20MHz - 24MHz >= xclk <= 64MHz
+	 */
+	if (xclk_mhz < 12 || xclk_mhz > 64 || (xclk_mhz >= 20 && xclk_mhz < 24))
+		return -EINVAL;
+
+	/* Set the PLL parameters that depend on the input clock frequency */
+	mira016->pll.cp_div = xclk_mhz <= 30 ? 1 : 2;
+	mira016->pll.esc_h = xclk_mhz < 20 ? 0 : xclk_mhz < 40 ? 1 : 2;
+	mira016->pll.esc_l = xclk_mhz < 20 ? 0 : xclk_mhz < 60 ? 1 : 2;
+	mira016->pll.esc_period = xclk_mhz < 20 ?
+				  DIV_ROUND_CLOSEST(1000, xclk_mhz)
+				: xclk_mhz < 40 ?
+				  DIV_ROUND_CLOSEST(2000, xclk_mhz)
+				: xclk_mhz < 60 ?
+				  DIV_ROUND_CLOSEST(3000, xclk_mhz)
+				: DIV_ROUND_CLOSEST(4000, xclk_mhz);
+
+	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;
+
+	ret = mira016_parse_endpoint(dev, mira016);
+	if (ret)
+		return ret;
+
+	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");
+
+	/*
+	 * 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(&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	[flat|nested] 11+ messages in thread

* Re: [PATCH v5 2/2] media: i2c: mira016: Add driver for Mira016
  2026-09-30 10:51 ` [PATCH v5 2/2] media: i2c: mira016: Add driver for Mira016 Jacopo Mondi
@ 2026-09-30 13:47   ` Tarang Raval
  2026-09-30 16:34     ` Jacopo Mondi
  2026-10-01  6:46   ` Sakari Ailus
  2026-10-01 10:58   ` Sakari Ailus
  2 siblings, 1 reply; 11+ messages in thread
From: Tarang Raval @ 2026-09-30 13:47 UTC (permalink / raw)
  To: Jacopo Mondi
  Cc: Mauro Carvalho Chehab, Sakari Ailus, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Kieran Bingham, jai.luthra,
	linux-media, devicetree, linux-kernel, Philippe Baetens

Hi Jacopo,

I noticed a few issues. Could you please check the comments below?

> 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 | 2309 +++++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 2323 insertions(+)

...

> +/* 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)

A few of these macros are unused. Can we remove them?

> +
> +/* Chip id */
> +#define MIRA016_CHIP_ID_REG            CCI_REG8(0x011B)
> +#define MIRA016_CHIP_ID                        33

...

> +static const struct cci_reg_sequence mira016_8b_fine_gain_init[] = {
> +     { CCI_REG8(0xe000), 0x0 },
> +     { CCI_REG8(0x01bb), 0xb4 },
...
> +     { 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 },
> +};

I noticed some registers are written multiple times within each
of these arrays. Are all the repeated writes required, or can
the redundant ones be removed?

Like for the above array 0xe000 register.

> +static void mira016_update_pad_format(struct mira016 *mira016,
> +                                     struct v4l2_mbus_framefmt *fmt, u32 code)

third argument is unused. Can we remove it?

> +{
> +       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 int mira016_set_pad_format(struct v4l2_subdev *sd,
> +                                 const struct v4l2_subdev_client_info *ci,
> +                                 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;

State is overwritten before the streaming check, so a failed call
with -EBUSY still changes the active format.

Should we move this check to the top of the function and only check
it for V4L2_SUBDEV_FORMAT_ACTIVE?

> +
> +       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);
> +}
> +
> +                                 u64 streams_mask)
> +{
> +       struct mira016 *mira016 = to_mira016(sd);
> +       struct i2c_client *client = v4l2_get_subdevdata(&mira016->sd);

We can use mira016->dev directly instead &client->dev.

> +       int 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);

same here.

> +

...

> +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, NULL, state, &fmt);
> +
> +       return 0;

You can directly return the result of
mira016_set_pad_format() here.

> +}

...

> +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);

we can use mira016->dev directly instead &client->dev.

> +       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;

Why do you not introduce vflip and hflip controls here?

> +       default:
> +               ret = -EINVAL;
> +               break;
> +       }
> +
> +       pm_runtime_put_autosuspend(&client->dev);
> +
> +       return ret;
> +}

...

> +static int mira016_init_controls(struct mira016 *mira016)
> +{
> +       struct i2c_client *client = v4l2_get_subdevdata(&mira016->sd);

we can use mira016->dev directly.

> +       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;
> +

...

> +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 },

I’m not sure, but I think 233 might be a typo and should be 223,
Since the m range and PLL code range don’t match.

> +               { 64, 127, 128, 191 }, { 1285, 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;
> +}

...

> +               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) {

Since these are unsigned int values, it would be better to use
abs_diff() instead of abs() here.

> +                               n_best = n;
> +                               m_best = m;
> +                               best = abs(pll2 - target_mbps);
> +                       }
> +               }
> +               if (found)
> +                       break;
> +       }

...

> +static int mira016_get_regulators(struct mira016 *mira016)
> +{
> +       struct i2c_client *client = v4l2_get_subdevdata(&mira016->sd);

We can remove this as well.

> +       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_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;
> +
> +       ret = mira016_parse_endpoint(dev, mira016);
> +       if (ret)
> +               return ret;
> +
> +       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",

Please use dev_err_probe.

> +                       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");
> +
> +       /*
> +        * 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;

1262 doesn’t match any of the supported formats. Is this a typo?
Should it be 1062 instead?

> +
> +       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))

Could we use dev here as well, just for consistency?

> +               mira016_power_off(dev);
> +       pm_runtime_set_suspended(dev);
> +       return ret;
> +}
> +

...

> +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

Best Regards,
Tarang

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v5 2/2] media: i2c: mira016: Add driver for Mira016
  2026-09-30 13:47   ` Tarang Raval
@ 2026-09-30 16:34     ` Jacopo Mondi
  2026-10-01  6:51       ` Sakari Ailus
  0 siblings, 1 reply; 11+ messages in thread
From: Jacopo Mondi @ 2026-09-30 16:34 UTC (permalink / raw)
  To: Tarang Raval
  Cc: Jacopo Mondi, Mauro Carvalho Chehab, Sakari Ailus, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Kieran Bingham, jai.luthra,
	linux-media, devicetree, linux-kernel, Philippe Baetens

Hi Tarang,
  thanks for the review

On Wed, Sep 30, 2026 at 01:47:22PM +0000, Tarang Raval wrote:
> Hi Jacopo,
>
> I noticed a few issues. Could you please check the comments below?
>
> > 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 | 2309 +++++++++++++++++++++++++++++++++++++++++++
> >  4 files changed, 2323 insertions(+)
>
> ...
>
> > +/* 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)
>
> A few of these macros are unused. Can we remove them?
>

Will do

> > +
> > +/* Chip id */
> > +#define MIRA016_CHIP_ID_REG            CCI_REG8(0x011B)
> > +#define MIRA016_CHIP_ID                        33
>
> ...
>
> > +static const struct cci_reg_sequence mira016_8b_fine_gain_init[] = {
> > +     { CCI_REG8(0xe000), 0x0 },
> > +     { CCI_REG8(0x01bb), 0xb4 },
> ...
> > +     { 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 },
> > +};
>
> I noticed some registers are written multiple times within each
> of these arrays. Are all the repeated writes required, or can
> the redundant ones be removed?

Good question.

I've broken out almost all documented parts from the register
sequences, which are generated by a vendor provided too.

I'm not sure I would dare to modify them to be honest. Also, if
anything have to be updated, comparing the sequences here with the
generated one will be easier.

>
> Like for the above array 0xe000 register.

0xe000 is repeated multiple times because it's the register that
selects which bank to write to (there are 2 registers banks on this
sensor)

>
> > +static void mira016_update_pad_format(struct mira016 *mira016,
> > +                                     struct v4l2_mbus_framefmt *fmt, u32 code)
>
> third argument is unused. Can we remove it?
>

Ah yes, sure

> > +{
> > +       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 int mira016_set_pad_format(struct v4l2_subdev *sd,
> > +                                 const struct v4l2_subdev_client_info *ci,
> > +                                 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;
>
> State is overwritten before the streaming check, so a failed call
> with -EBUSY still changes the active format.
>
> Should we move this check to the top of the function and only check
> it for V4L2_SUBDEV_FORMAT_ACTIVE?

mmm, you're probably right, if we can't get past this for (ACTIVE &&
streaming) we shouldn't probably update the format in the state at
all.

The expectations are not 100% clear in case of EBUSY here
https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/vidioc-subdev-g-fmt.html

but I think what you suggest makes sense

>
> > +
> > +       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);
> > +}
> > +
> > +                                 u64 streams_mask)
> > +{
> > +       struct mira016 *mira016 = to_mira016(sd);
> > +       struct i2c_client *client = v4l2_get_subdevdata(&mira016->sd);
>
> We can use mira016->dev directly instead &client->dev.
>
> > +       int 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);
>
> same here.
>

ack

> > +
>
> ...
>
> > +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, NULL, state, &fmt);
> > +
> > +       return 0;
>
> You can directly return the result of
> mira016_set_pad_format() here.
>

True

> > +}
>
> ...
>
> > +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);
>
> we can use mira016->dev directly instead &client->dev.
>
> > +       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;
>
> Why do you not introduce vflip and hflip controls here?
>

If you see the control initialization there is a comment about that
	/*
	 * 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);

So we can't change flips at runtime but flips are only programmed at
streaming time as part of the mira016_configure_roi() function


> > +       default:
> > +               ret = -EINVAL;
> > +               break;
> > +       }
> > +
> > +       pm_runtime_put_autosuspend(&client->dev);
> > +
> > +       return ret;
> > +}
>
> ...
>
> > +static int mira016_init_controls(struct mira016 *mira016)
> > +{
> > +       struct i2c_client *client = v4l2_get_subdevdata(&mira016->sd);
>
> we can use mira016->dev directly.
>
> > +       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;
> > +
>
> ...
>
> > +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 },
>
> I’m not sure, but I think 233 might be a typo and should be 223,
> Since the m range and PLL code range don’t match.
>

good good spot, thansk!


> > +               { 64, 127, 128, 191 }, { 1285, 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;
> > +}
>
> ...
>
> > +               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) {
>
> Since these are unsigned int values, it would be better to use
> abs_diff() instead of abs() here.
>

I'll check!

> > +                               n_best = n;
> > +                               m_best = m;
> > +                               best = abs(pll2 - target_mbps);
> > +                       }
> > +               }
> > +               if (found)
> > +                       break;
> > +       }
>
> ...
>
> > +static int mira016_get_regulators(struct mira016 *mira016)
> > +{
> > +       struct i2c_client *client = v4l2_get_subdevdata(&mira016->sd);
>
> We can remove this as well.
>
> > +       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_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;
> > +
> > +       ret = mira016_parse_endpoint(dev, mira016);
> > +       if (ret)
> > +               return ret;
> > +
> > +       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",
>
> Please use dev_err_probe.

What would it give me though ? I know the return value is EINVAL, it
won't save nothing, doesn't it ?

>
> > +                       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");
> > +
> > +       /*
> > +        * 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;
>
> 1262 doesn’t match any of the supported formats. Is this a typo?
> Should it be 1062 instead?

Harmless, but clearly a leftover from my early attempts.
Thanks for spotting!

>
> > +
> > +       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))
>
> Could we use dev here as well, just for consistency?
>

Sure

Thanks for the review!

> > +               mira016_power_off(dev);
> > +       pm_runtime_set_suspended(dev);
> > +       return ret;
> > +}
> > +
>
> ...
>
> > +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
>
> Best Regards,
> Tarang

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v5 2/2] media: i2c: mira016: Add driver for Mira016
  2026-09-30 10:51 ` [PATCH v5 2/2] media: i2c: mira016: Add driver for Mira016 Jacopo Mondi
  2026-09-30 13:47   ` Tarang Raval
@ 2026-10-01  6:46   ` Sakari Ailus
  2026-10-01  7:19     ` Jacopo Mondi
  2026-10-01 10:58   ` Sakari Ailus
  2 siblings, 1 reply; 11+ messages in thread
From: Sakari Ailus @ 2026-10-01  6:46 UTC (permalink / raw)
  To: Jacopo Mondi
  Cc: Philippe Baetens, Mauro Carvalho Chehab, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Kieran Bingham, Jai Luthra,
	linux-media, devicetree, linux-kernel

Hi Jacopo,

On Wed, Sep 30, 2026 at 12:51:20PM +0200, Jacopo Mondi wrote:
> Add driver for the ams OSRAM Mira016 sensor.
> 
> Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> +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
> +	};
> +	int ret;
> +
> +	endpoint = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, 0);
> +	if (!endpoint)
> +		return -ENODEV;
> +
> +	ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep_cfg);
> +	if (ret)
> +		return ret;
> +
> +	/*
> +	 * Link frequencies: the driver supports a single link frequency,
> +	 * no need to check bitmap after this call.
> +	 */
> +	ret = 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);
> +	if (ret) {
> +		v4l2_fwnode_endpoint_free(&ep_cfg);

I recall commenting about this at in least three occasions earlier.

Also applies to the other driver.

> +		return ret;
> +	}
> +
> +	/* TODO: Implement D-PHY configuration to support continuous clock. */
> +	if (!(ep_cfg.bus.mipi_csi2.flags & V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK)) {
> +		dev_err(dev, "Continuous clock is not supported\n");
> +		v4l2_fwnode_endpoint_free(&ep_cfg);
> +		return -EINVAL;
> +	}
> +
> +	mira016->bus_config = ep_cfg.bus.mipi_csi2.flags;
> +
> +	v4l2_fwnode_endpoint_free(&ep_cfg);
> +
> +	return 0;
> +}

-- 
Regards,

Sakari Ailus

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v5 2/2] media: i2c: mira016: Add driver for Mira016
  2026-09-30 16:34     ` Jacopo Mondi
@ 2026-10-01  6:51       ` Sakari Ailus
  0 siblings, 0 replies; 11+ messages in thread
From: Sakari Ailus @ 2026-10-01  6:51 UTC (permalink / raw)
  To: Jacopo Mondi
  Cc: Tarang Raval, Mauro Carvalho Chehab, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Kieran Bingham, jai.luthra,
	linux-media, devicetree, linux-kernel, Philippe Baetens

Hi Jacopo,

On Wed, Sep 30, 2026 at 06:34:12PM +0200, Jacopo Mondi wrote:
> > Like for the above array 0xe000 register.
> 
> 0xe000 is repeated multiple times because it's the register that
> selects which bank to write to (there are 2 registers banks on this
> sensor)

If you have pages, you should use regmap's page support. See e.g.
<URL:https://lore.kernel.org/linux-media/20261001061250.37734-1-sakari.ailus@linux.intel.com/T/#u>.

There are probably other such examples, too.

-- 
Regards,

Sakari Ailus

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v5 2/2] media: i2c: mira016: Add driver for Mira016
  2026-10-01  6:46   ` Sakari Ailus
@ 2026-10-01  7:19     ` Jacopo Mondi
  2026-10-01  7:52       ` Sakari Ailus
  0 siblings, 1 reply; 11+ messages in thread
From: Jacopo Mondi @ 2026-10-01  7:19 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: Jacopo Mondi, Philippe Baetens, Mauro Carvalho Chehab,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Kieran Bingham,
	Jai Luthra, linux-media, devicetree, linux-kernel

Hi Sakari

On Thu, Oct 01, 2026 at 09:46:08AM +0300, Sakari Ailus wrote:
> Hi Jacopo,
>
> On Wed, Sep 30, 2026 at 12:51:20PM +0200, Jacopo Mondi wrote:
> > Add driver for the ams OSRAM Mira016 sensor.
> >
> > Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> > +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
> > +	};
> > +	int ret;
> > +
> > +	endpoint = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, 0);
> > +	if (!endpoint)
> > +		return -ENODEV;
> > +
> > +	ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep_cfg);
> > +	if (ret)
> > +		return ret;
> > +
> > +	/*
> > +	 * Link frequencies: the driver supports a single link frequency,
> > +	 * no need to check bitmap after this call.
> > +	 */
> > +	ret = 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);
> > +	if (ret) {
> > +		v4l2_fwnode_endpoint_free(&ep_cfg);
>
> I recall commenting about this at in least three occasions earlier.

And, again, I have replied twice to your comment without receiving a
response: https://lore.kernel.org/linux-media/arPJ_-yKVMXE-Gav@zed/

I'll repeat here anyway: do not mix cleanups and gotos. In this case
it's harmless, but why contradict the usage notes to avoid typing out
v4l2_fwnode_endpoint_free() 2 times ?

>
> Also applies to the other driver.
>
> > +		return ret;
> > +	}
> > +
> > +	/* TODO: Implement D-PHY configuration to support continuous clock. */
> > +	if (!(ep_cfg.bus.mipi_csi2.flags & V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK)) {
> > +		dev_err(dev, "Continuous clock is not supported\n");
> > +		v4l2_fwnode_endpoint_free(&ep_cfg);
> > +		return -EINVAL;
> > +	}
> > +
> > +	mira016->bus_config = ep_cfg.bus.mipi_csi2.flags;
> > +
> > +	v4l2_fwnode_endpoint_free(&ep_cfg);
> > +
> > +	return 0;
> > +}
>
> --
> Regards,
>
> Sakari Ailus

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v5 2/2] media: i2c: mira016: Add driver for Mira016
  2026-10-01  7:19     ` Jacopo Mondi
@ 2026-10-01  7:52       ` Sakari Ailus
  2026-10-01 10:07         ` Jacopo Mondi
  0 siblings, 1 reply; 11+ messages in thread
From: Sakari Ailus @ 2026-10-01  7:52 UTC (permalink / raw)
  To: Jacopo Mondi
  Cc: Philippe Baetens, Mauro Carvalho Chehab, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Kieran Bingham, Jai Luthra,
	linux-media, devicetree, linux-kernel

Hi Jacopo,

On Thu, Oct 01, 2026 at 09:19:02AM +0200, Jacopo Mondi wrote:
> Hi Sakari
> 
> On Thu, Oct 01, 2026 at 09:46:08AM +0300, Sakari Ailus wrote:
> > Hi Jacopo,
> >
> > On Wed, Sep 30, 2026 at 12:51:20PM +0200, Jacopo Mondi wrote:
> > > Add driver for the ams OSRAM Mira016 sensor.
> > >
> > > Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> > > +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
> > > +	};
> > > +	int ret;
> > > +
> > > +	endpoint = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, 0);
> > > +	if (!endpoint)
> > > +		return -ENODEV;
> > > +
> > > +	ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep_cfg);
> > > +	if (ret)
> > > +		return ret;
> > > +
> > > +	/*
> > > +	 * Link frequencies: the driver supports a single link frequency,
> > > +	 * no need to check bitmap after this call.
> > > +	 */
> > > +	ret = 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);
> > > +	if (ret) {
> > > +		v4l2_fwnode_endpoint_free(&ep_cfg);
> >
> > I recall commenting about this at in least three occasions earlier.
> 
> And, again, I have replied twice to your comment without receiving a
> response: https://lore.kernel.org/linux-media/arPJ_-yKVMXE-Gav@zed/
> 
> I'll repeat here anyway: do not mix cleanups and gotos. In this case
> it's harmless, but why contradict the usage notes to avoid typing out
> v4l2_fwnode_endpoint_free() 2 times ?

It's not about typing but correct error handling. It's much easier to miss
unwinding whatever needs to be unwound in multiple places when you're not
using goto's.

In other words, the pattern you're following is bad, please stop using it.

> 
> >
> > Also applies to the other driver.
> >
> > > +		return ret;
> > > +	}
> > > +
> > > +	/* TODO: Implement D-PHY configuration to support continuous clock. */
> > > +	if (!(ep_cfg.bus.mipi_csi2.flags & V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK)) {
> > > +		dev_err(dev, "Continuous clock is not supported\n");
> > > +		v4l2_fwnode_endpoint_free(&ep_cfg);
> > > +		return -EINVAL;
> > > +	}
> > > +
> > > +	mira016->bus_config = ep_cfg.bus.mipi_csi2.flags;
> > > +
> > > +	v4l2_fwnode_endpoint_free(&ep_cfg);
> > > +
> > > +	return 0;
> > > +}
> >

-- 
Kind regards,

Sakari Ailus

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v5 2/2] media: i2c: mira016: Add driver for Mira016
  2026-10-01  7:52       ` Sakari Ailus
@ 2026-10-01 10:07         ` Jacopo Mondi
  0 siblings, 0 replies; 11+ messages in thread
From: Jacopo Mondi @ 2026-10-01 10:07 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: Jacopo Mondi, Philippe Baetens, Mauro Carvalho Chehab,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Kieran Bingham,
	Jai Luthra, linux-media, devicetree, linux-kernel

On Thu, Oct 01, 2026 at 10:52:35AM +0300, Sakari Ailus wrote:
> Hi Jacopo,
>
> On Thu, Oct 01, 2026 at 09:19:02AM +0200, Jacopo Mondi wrote:
> > Hi Sakari
> >
> > On Thu, Oct 01, 2026 at 09:46:08AM +0300, Sakari Ailus wrote:
> > > Hi Jacopo,
> > >
> > > On Wed, Sep 30, 2026 at 12:51:20PM +0200, Jacopo Mondi wrote:
> > > > Add driver for the ams OSRAM Mira016 sensor.
> > > >
> > > > Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> > > > +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
> > > > +	};
> > > > +	int ret;
> > > > +
> > > > +	endpoint = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, 0);
> > > > +	if (!endpoint)
> > > > +		return -ENODEV;
> > > > +
> > > > +	ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep_cfg);
> > > > +	if (ret)
> > > > +		return ret;
> > > > +
> > > > +	/*
> > > > +	 * Link frequencies: the driver supports a single link frequency,
> > > > +	 * no need to check bitmap after this call.
> > > > +	 */
> > > > +	ret = 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);
> > > > +	if (ret) {
> > > > +		v4l2_fwnode_endpoint_free(&ep_cfg);
> > >
> > > I recall commenting about this at in least three occasions earlier.
> >
> > And, again, I have replied twice to your comment without receiving a
> > response: https://lore.kernel.org/linux-media/arPJ_-yKVMXE-Gav@zed/
> >
> > I'll repeat here anyway: do not mix cleanups and gotos. In this case
> > it's harmless, but why contradict the usage notes to avoid typing out
> > v4l2_fwnode_endpoint_free() 2 times ?
>
> It's not about typing but correct error handling. It's much easier to miss
> unwinding whatever needs to be unwound in multiple places when you're not
> using goto's.
>
> In other words, the pattern you're following is bad, please stop using it.
>

You seem to be missing my main point, and if you think it's not
correct please tell me why instead of keep repeating the same thing
over and over.

This routine uses cleanups because of

	struct fwnode_handle *endpoint __free(fwnode_handle) = NULL;

functions using cleanups shall not use gotos

from cleanup.h

 * Lastly, given that the benefit of cleanup helpers is removal of
 * "goto", and that the "goto" statement can jump between scopes, the
 * expectation is that usage of "goto" and cleanup helpers is never
 * mixed in the same function. I.e. for a given routine, convert all
 * resources that need a "goto" cleanup to scope-based cleanup, or
 * convert none of them.

As said, in this case is harmless, but given that this function might
be extended I wouldn't introduce a goto now to later having to care if
it gets in the way of the cleanup path or not.



> >
> > >
> > > Also applies to the other driver.
> > >
> > > > +		return ret;
> > > > +	}
> > > > +
> > > > +	/* TODO: Implement D-PHY configuration to support continuous clock. */
> > > > +	if (!(ep_cfg.bus.mipi_csi2.flags & V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK)) {
> > > > +		dev_err(dev, "Continuous clock is not supported\n");
> > > > +		v4l2_fwnode_endpoint_free(&ep_cfg);
> > > > +		return -EINVAL;
> > > > +	}
> > > > +
> > > > +	mira016->bus_config = ep_cfg.bus.mipi_csi2.flags;
> > > > +
> > > > +	v4l2_fwnode_endpoint_free(&ep_cfg);
> > > > +
> > > > +	return 0;
> > > > +}
> > >
>
> --
> Kind regards,
>
> Sakari Ailus

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v5 2/2] media: i2c: mira016: Add driver for Mira016
  2026-09-30 10:51 ` [PATCH v5 2/2] media: i2c: mira016: Add driver for Mira016 Jacopo Mondi
  2026-09-30 13:47   ` Tarang Raval
  2026-10-01  6:46   ` Sakari Ailus
@ 2026-10-01 10:58   ` Sakari Ailus
  2 siblings, 0 replies; 11+ messages in thread
From: Sakari Ailus @ 2026-10-01 10:58 UTC (permalink / raw)
  To: Jacopo Mondi
  Cc: Philippe Baetens, Mauro Carvalho Chehab, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Kieran Bingham, Jai Luthra,
	linux-media, devicetree, linux-kernel

Hi Jacopo,

One more small issue...

On Wed, Sep 30, 2026 at 12:51:20PM +0200, Jacopo Mondi wrote:

...

> +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;
> +
> +	ret = mira016_parse_endpoint(dev, mira016);
> +	if (ret)
> +		return ret;
> +
> +	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");
> +
> +	/*
> +	 * 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);

These two come in pairs effectively. The sensor has been powered on earlier
and not powered off, so you can omit the suspended check above.

> +	return ret;
> +}

-- 
Regards,

Sakari Ailus

^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2026-10-01 10:58 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-30 10:51 [PATCH v5 0/2] media: i2c: Add driver for Mira016 Jacopo Mondi
2026-09-30 10:51 ` [PATCH v5 1/2] dt-bindings: media: i2c: Add Mira016 image sensor Jacopo Mondi
2026-09-30 10:51 ` [PATCH v5 2/2] media: i2c: mira016: Add driver for Mira016 Jacopo Mondi
2026-09-30 13:47   ` Tarang Raval
2026-09-30 16:34     ` Jacopo Mondi
2026-10-01  6:51       ` Sakari Ailus
2026-10-01  6:46   ` Sakari Ailus
2026-10-01  7:19     ` Jacopo Mondi
2026-10-01  7:52       ` Sakari Ailus
2026-10-01 10:07         ` Jacopo Mondi
2026-10-01 10:58   ` Sakari Ailus

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®