Devicetree
 help / color / mirror / Atom feed
* [PATCH v8 2/2] media: i2c: Add driver for AMS-OSRAM Mira220
From: Jacopo Mondi @ 2026-07-24 15:37 UTC (permalink / raw)
  To: Philippe Baetens, Mauro Carvalho Chehab, Rob Herring,
	Sakari Ailus, Krzysztof Kozlowski, Conor Dooley
  Cc: Kieran Bingham, Jai Luthra, linux-media, devicetree, linux-kernel,
	Jacopo Mondi
In-Reply-To: <20260724-mira220-v8-0-d1899ab64709@ideasonboard.com>

From: Philippe Baetens <philippebaetens@gmail.com>

Add a V4L2 subdev driver for driver for the AMS-OSRAM Mira220 image
sensor.

Mira220 is a global shutter image sensor with a resolution of 1600x1400
pixels.

The driver implements support for mono and RGB 12, 10 and 8 bits
formats. The output data-rate per lane is 1500Mbit/s, with a maximum
frame rate up to 90 fps.

Signed-off-by: Philippe Baetens <philippebaetens@gmail.com>
Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>

---
v7->v8:
- Adjust KConfig entry to only depend on GPIOLIB and select CCI
- Register V4L2_CID_LINK_FREQ and validate the dts link-frequencies property
- Refuse the clock-continuous dts property
- Drop MIRA220_NUM_SUPPLIES and use ARRAY_SIZE()
- Rework flip index computation through a dedicated function
- Re-sort mira220_mbus_color_formats[] to avoid inverting hflip value
  when adjusting format to flips
- Address Jai's review comments
  - Re-indent macros with tabs
  - Use container_of_const
  - Use lowercase for hex values
  - Remove left-over mira220_mode *
  - Use HSTART_MASK when programming register
  - Use cci_write(..., &ret) to simplify register write sequences
  - Drop outdate comment
  - Do not * 1 sizes to initialize crop rectangle
  - Remove support for events
  - Use devm_v4l2_sensor_clk_get() to support ACPI systems

v6->v7:
- Address Sashiko review comments:
  - Return error code from mira220_power_on()
  - Use if (!pm_runtime_get_if_in_use(&client->dev)) in set_ctrl to
    support !PM
  - Power OTP off in case of errors
  - Use pm_ptr() when initializing the pm ops

v5->v6:
- Address Sashiko review comments:
  - Clamp max exposure not to overlow the 16 bits register
  - Update pad format on flip
  - Check otp_read() return value before accessing 'id'
  - Add v4l2_subdev_cleanup() to remove()
  - Do not access vblank in init_controls
  - Enable pm_runtime autosuspend last in probe
  - Scale hblank according to lane number to match row_length

v4->v5:
- Address checkpatch warnings and other minor style fixes (c++ comments
  80-cols etc)
- Restore MEDIA_BUS_FMT_Y12_1X12 format
- Run-time identify mono vs rgb variants
- Remove debug messages
- Remove delays not documented in datasheet
- Adjust min/max blankings to datasheet values
- Do not update limits of read-only controls with a single values on
  set_fmt
- Use pm_runtime autosuspend delay
- Remove fixed modes and dynamically program:
  - CSI-2 bus config (lanes number and clock mode)
  - frame windowing
  - image format and data-type
- Remove programming of secondary page for fast switiching (not used yet)
- Add support for reset_gpio in driver
- Use control's mutex for state locking
- Use format from subdev state and remove format from driver structure
- Rework OTP programming routines to shorten error handling
- Use enable/disable_streams() in place of set_stream()
- Add fwnode parsing for data lanes configuration
---
 MAINTAINERS                 |    1 +
 drivers/media/i2c/Kconfig   |   12 +
 drivers/media/i2c/Makefile  |    1 +
 drivers/media/i2c/mira220.c | 2080 +++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 2094 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 55ac4ec433a6..7c4f743ae026 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1436,6 +1436,7 @@ M:	Jacopo Mondi <jacopo.mondi@ideasonboard.com>
 L:	linux-media@vger.kernel.org
 S:	Maintained
 F:	Documentation/devicetree/bindings/media/i2c/ams,mira220.yaml
+F:	drivers/media/i2c/mira220.c
 
 AMT (Automatic Multicast Tunneling)
 M:	Taehee Yoo <ap420073@gmail.com>
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index 5c52007f9cbe..650d5c86baa4 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -313,6 +313,18 @@ config VIDEO_IMX678
 config VIDEO_MAX9271_LIB
 	tristate
 
+config VIDEO_MIRA220
+	tristate "ams MIRA220 sensor support"
+	depends on I2C && VIDEO_DEV
+	depends on GPIOLIB
+	select V4L2_CCI_I2C
+	help
+	  This is a Video4Linux2 sensor driver for the ams
+	  MIRA220 camera.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called mira220.
+
 config VIDEO_MT9M001
 	tristate "mt9m001 support"
 	help
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index d04bd5724552..6ecfe6c65af4 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -74,6 +74,7 @@ obj-$(CONFIG_VIDEO_MAX9271_LIB) += max9271.o
 obj-$(CONFIG_VIDEO_MAX9286) += max9286.o
 obj-$(CONFIG_VIDEO_MAX96714) += max96714.o
 obj-$(CONFIG_VIDEO_MAX96717) += max96717.o
+obj-$(CONFIG_VIDEO_MIRA220)	+= mira220.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/mira220.c b/drivers/media/i2c/mira220.c
new file mode 100644
index 000000000000..8b9148efc581
--- /dev/null
+++ b/drivers/media/i2c/mira220.c
@@ -0,0 +1,2080 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * A V4L2 driver for ams MIRA220 cameras.
+ *
+ * Mira220 comes in monochrome, RGB and IR-RGB variants. The driver supports
+ * the mono and RGB variants.
+ *
+ * Copyright (C) 2026, ams-OSRAM
+ *
+ * Based on Sony IMX219 camera driver
+ * Copyright (C) 2019, Raspberry Pi (Trading) Ltd
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+
+#include <media/v4l2-cci.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-mediabus.h>
+
+/*
+ * Active pixel array is 1600 (H) x 1400 (V) pixels.
+ * Physical resolution including buffer pixels: 1642 (H) x 1464 (V) pixels.
+ */
+#define MIRA220_NATIVE_WIDTH		1642U
+#define MIRA220_NATIVE_HEIGHT		1464U
+#define MIRA220_PIXEL_ARRAY_LEFT	21U
+#define MIRA220_PIXEL_ARRAY_TOP		32U
+#define MIRA220_PIXEL_ARRAY_WIDTH	1600U
+#define MIRA220_PIXEL_ARRAY_HEIGHT	1400U
+
+/* Mira220 does not support analog gain. */
+#define MIRA220_ANALOG_GAIN_MIN		1
+#define MIRA220_ANALOG_GAIN_MAX		1
+#define MIRA220_ANALOG_GAIN_STEP	1
+#define MIRA220_ANALOG_GAIN_DEFAULT	MIRA220_ANALOG_GAIN_MIN
+
+/* Bit order */
+#define MIRA220_BIT_ORDER_REG		CCI_REG8(0x2063)
+#define MIRA220_BIT_ORDER_NORMAL	0x00
+#define MIRA220_BIT_ORDER_REVERSED	0x01
+
+/* Bit depth */
+#define MIRA220_BIT_DEPTH_REG		CCI_REG8(0x209e)
+#define MIRA220_BIT_DEPTH_12_BIT	0x02
+#define MIRA220_BIT_DEPTH_10_BIT	0x04
+#define MIRA220_BIT_DEPTH_8_BIT		0x06
+#define MIRA220_CSI_DATA_TYPE_REG	CCI_REG8(0x208d)
+#define MIRA220_CSI_DATA_TYPE_12_BIT	0x04
+#define MIRA220_CSI_DATA_TYPE_10_BIT	0x02
+#define MIRA220_CSI_DATA_TYPE_8_BIT	0x01
+
+/* Imager state master/slave registers */
+#define MIRA220_IMAGER_STATE_REG		CCI_REG8(0x1003)
+#define MIRA220_IMAGER_STATE_STOP_AT_ROW	0x02
+#define MIRA220_IMAGER_STATE_STOP_AT_FRAME	0x04
+#define MIRA220_IMAGER_STATE_MASTER_CONTROL	0x10
+#define MIRA220_IMAGER_STATE_SLAVE_CONTROL	0x08
+
+/* Start image acquisition */
+#define MIRA220_IMAGER_RUN_REG		CCI_REG8(0x10f0)
+#define MIRA220_IMAGER_RUN_START	0x01
+#define MIRA220_IMAGER_RUN_STOP		0x00
+
+/* Continuous running, not limited to nr of frames. */
+#define MIRA220_IMAGER_RUN_CONT_REG	CCI_REG8(0x1002)
+#define MIRA220_IMAGER_RUN_CONT_ENABLE	0x04
+#define MIRA220_IMAGER_RUN_CONT_DISABLE	0x00
+
+/* Exposure time is indicated in number of rows */
+#define MIRA220_EXP_TIME_REG		CCI_REG16_LE(0x100c)
+
+/* Vertical Blank */
+#define MIRA220_VBLANK_REG		CCI_REG16_LE(0x1012)
+#define MIRA220_MIN_VBLANK_MARGIN	11
+#define MIRA220_MAX_VBLANK		0xffff
+
+/* Horizontal flip */
+#define MIRA220_HFLIP_REG		CCI_REG8(0x209c)
+#define MIRA220_HFLIP_ENABLE_MIRROR	1
+#define MIRA220_HFLIP_DISABLE_MIRROR	0
+
+/* Vertical flip */
+#define MIRA220_VFLIP_REG		CCI_REG8(0x1095)
+#define MIRA220_VFLIP_ENABLE_FLIP	1
+#define MIRA220_VFLIP_DISABLE_FLIP	0
+
+#define MIRA220_VSIZE_REG		CCI_REG16_LE(0x1087)
+#define MIRA220_VSTART_REG		CCI_REG16_LE(0x107d)
+#define MIRA220_HSIZE_REG		CCI_REG16_LE(0x2008)
+#define MIRA220_HSIZE_MASK		GENMASK(9, 0)
+#define MIRA220_HSTART_REG		CCI_REG16_LE(0x200a)
+#define MIRA220_HSTART_MASK		GENMASK(9, 0)
+#define MIRA220_MIPI_HSIZE_REG		CCI_REG16_LE(0x207d)
+
+/* MIPI bus control */
+#define MIRA220_MIPI_LANES_REG		CCI_REG8(0x6012)
+
+#define MIRA220_MIPI_CLK_MODE_REG	CCI_REG8(0x6013)
+#define MIRA220_MIPI_CLK_CONTINUOUS	0x00
+#define MIRA220_MIPI_CLK_NON_CONTINUOUS	0x01
+
+/* OTP control */
+#define MIRA220_OTP_CMD_REG		CCI_REG8(0x0080)
+#define MIRA220_OTP_CMD_UP		0x4
+#define MIRA220_OTP_CMD_DOWN		0x8
+
+/* Global sampling time */
+#define MIRA220_GLOB_NUM_CLK_CYCLES	1928
+
+/* External clock frequency is 38.4 M */
+#define MIRA220_SUPPORTED_XCLK_FREQ	38400000
+
+/* Default exposure is adjusted to mode with smallest height */
+#define MIRA220_DEFAULT_EXPOSURE	1000
+#define MIRA220_MAX_EXPOSURE		0xffff
+#define MIRA220_EXPOSURE_MIN		1
+
+/*
+ * Pixel rate is an artificial value. This value is used for timing calculations
+ * in combination with vblank/hblank.
+ */
+#define MIRA220_PIXEL_RATE		384000000
+
+/* Total line length for 2 lanes calculated for ROW_LENGTH = 304. */
+#define MIRA220_LLP_1600x1400_304	3040
+
+/*
+ * ROW_LENGTH: duration of row length in clk_in cycles; set to minimum supported
+ * value for higher frame rate.
+ */
+#define MIRA220_REG_ROW_LENGTH		CCI_REG16_LE(0x102b)
+#define MIRA220_ROW_LENGTH_MIN		304
+
+/* MIPI CSI-2 link frequency for 1.5Mbps per lane */
+#define MIRA220_LINK_FREQ_750M		750000000
+
+/* Test Pattern */
+#define MIRA220_REG_TEST_PATTERN	CCI_REG8(0x2091)
+#define MIRA220_TEST_PATTERN_DISABLE	0x00
+#define MIRA220_TEST_PATTERN_VERTICAL_GRADIENT 0x01
+
+/* Module variant identifiers */
+#define MIRA220_MONO_PLAIN_PF		0x1e62a032
+#define MIRA220_MONO_PLAIN_NO_PF	0x1e62a045
+#define MIRA220_MONO_AR_PF		0x1e62a036
+#define MIRA220_RGB_PLAIN_PF		0x1e62a03b
+#define MIRA220_RGB_PLAIN_NO_PF		0x1e62a046
+#define MIRA220_RGB_AR_PF		0x1e62a03d
+
+enum mira220_variants {
+	MIRA220_VARIANT_MONO,
+	MIRA220_VARIANT_RGB,
+};
+
+static const s64 mira220_link_freqs[] = {
+	[0] = MIRA220_LINK_FREQ_750M,
+};
+
+static const struct cci_reg_sequence mira220_init_reg_list[] = {
+	/* Base configuration*/
+	{ CCI_REG8(0x6006), 0x0 },
+	{ CCI_REG8(0x6012), 0x1 },
+	{ CCI_REG8(0x6013), 0x0 },
+	{ CCI_REG8(0x6006), 0x1 },
+	{ CCI_REG8(0x205d), 0x0 },
+	{ CCI_REG8(0x2063), 0x0 },
+	{ CCI_REG8(0x24dc), 0x13 },
+	{ CCI_REG8(0x24dd), 0x3 },
+	{ CCI_REG8(0x24de), 0x3 },
+	{ CCI_REG8(0x24df), 0x0 },
+	{ CCI_REG8(0x4006), 0x8 },
+	{ CCI_REG8(0x401c), 0x6f },
+	{ CCI_REG8(0x204b), 0x3 },
+	{ CCI_REG8(0x205b), 0x64 },
+	{ CCI_REG8(0x205c), 0x0 },
+	{ CCI_REG8(0x4018), 0x3f },
+	{ CCI_REG8(0x403b), 0xb },
+	{ CCI_REG8(0x403e), 0xe },
+	{ CCI_REG8(0x402b), 0x6 },
+	{ CCI_REG8(0x401e), 0x2 },
+	{ CCI_REG8(0x4038), 0x3b },
+	{ CCI_REG8(0x1077), 0x0 },
+	{ CCI_REG8(0x1078), 0x0 },
+	{ CCI_REG8(0x1009), 0x8 },
+	{ CCI_REG8(0x100a), 0x0 },
+	{ CCI_REG8(0x110f), 0x8 },
+	{ CCI_REG8(0x1110), 0x0 },
+	{ CCI_REG8(0x1006), 0x2 },
+	{ CCI_REG8(0x402c), 0x64 },
+	{ CCI_REG8(0x3064), 0x0 },
+	{ CCI_REG8(0x3065), 0xf0 },
+	{ CCI_REG8(0x4013), 0x13 },
+	{ CCI_REG8(0x401f), 0x9 },
+	{ CCI_REG8(0x4020), 0x13 },
+	{ CCI_REG8(0x4044), 0x75 },
+	{ CCI_REG8(0x4027), 0x0 },
+	{ CCI_REG8(0x3215), 0x69 },
+	{ CCI_REG8(0x3216), 0xf },
+	{ CCI_REG8(0x322b), 0x69 },
+	{ CCI_REG8(0x322c), 0xf },
+	{ CCI_REG8(0x4051), 0x80 },
+	{ CCI_REG8(0x4052), 0x10 },
+	{ CCI_REG8(0x4057), 0x80 },
+	{ CCI_REG8(0x4058), 0x10 },
+	{ CCI_REG8(0x3212), 0x59 },
+	{ CCI_REG8(0x4047), 0x8f },
+	{ CCI_REG8(0x4026), 0x10 },
+	{ CCI_REG8(0x4032), 0x53 },
+	{ CCI_REG8(0x4036), 0x17 },
+	{ CCI_REG8(0x50b8), 0xf4 },
+	{ CCI_REG8(0x3016), 0x0 },
+	{ CCI_REG8(0x3017), 0x2c },
+	{ CCI_REG8(0x3018), 0x8c },
+	{ CCI_REG8(0x3019), 0x45 },
+	{ CCI_REG8(0x301a), 0x5 },
+	{ CCI_REG8(0x3013), 0xa },
+	{ CCI_REG8(0x301b), 0x0 },
+	{ CCI_REG8(0x301c), 0x4 },
+	{ CCI_REG8(0x301d), 0x88 },
+	{ CCI_REG8(0x301e), 0x45 },
+	{ CCI_REG8(0x301f), 0x5 },
+	{ CCI_REG8(0x3020), 0x0 },
+	{ CCI_REG8(0x3021), 0x4 },
+	{ CCI_REG8(0x3022), 0x88 },
+	{ CCI_REG8(0x3023), 0x45 },
+	{ CCI_REG8(0x3024), 0x5 },
+	{ CCI_REG8(0x3025), 0x0 },
+	{ CCI_REG8(0x3026), 0x4 },
+	{ CCI_REG8(0x3027), 0x88 },
+	{ CCI_REG8(0x3028), 0x45 },
+	{ CCI_REG8(0x3029), 0x5 },
+	{ CCI_REG8(0x302f), 0x0 },
+	{ CCI_REG8(0x3056), 0x0 },
+	{ CCI_REG8(0x3057), 0x0 },
+	{ CCI_REG8(0x3300), 0x1 },
+	{ CCI_REG8(0x3301), 0x0 },
+	{ CCI_REG8(0x3302), 0xb0 },
+	{ CCI_REG8(0x3303), 0xb0 },
+	{ CCI_REG8(0x3304), 0x16 },
+	{ CCI_REG8(0x3305), 0x15 },
+	{ CCI_REG8(0x3306), 0x1 },
+	{ CCI_REG8(0x3307), 0x0 },
+	{ CCI_REG8(0x3308), 0x30 },
+	{ CCI_REG8(0x3309), 0xa0 },
+	{ CCI_REG8(0x330a), 0x16 },
+	{ CCI_REG8(0x330b), 0x15 },
+	{ CCI_REG8(0x330c), 0x1 },
+	{ CCI_REG8(0x330d), 0x0 },
+	{ CCI_REG8(0x330e), 0x30 },
+	{ CCI_REG8(0x330f), 0xa0 },
+	{ CCI_REG8(0x3310), 0x16 },
+	{ CCI_REG8(0x3311), 0x15 },
+	{ CCI_REG8(0x3312), 0x1 },
+	{ CCI_REG8(0x3313), 0x0 },
+	{ CCI_REG8(0x3314), 0x30 },
+	{ CCI_REG8(0x3315), 0xa0 },
+	{ CCI_REG8(0x3316), 0x16 },
+	{ CCI_REG8(0x3317), 0x15 },
+	{ CCI_REG8(0x3318), 0x1 },
+	{ CCI_REG8(0x3319), 0x0 },
+	{ CCI_REG8(0x331a), 0x30 },
+	{ CCI_REG8(0x331b), 0xa0 },
+	{ CCI_REG8(0x331c), 0x16 },
+	{ CCI_REG8(0x331d), 0x15 },
+	{ CCI_REG8(0x331e), 0x1 },
+	{ CCI_REG8(0x331f), 0x0 },
+	{ CCI_REG8(0x3320), 0x30 },
+	{ CCI_REG8(0x3321), 0xa0 },
+	{ CCI_REG8(0x3322), 0x16 },
+	{ CCI_REG8(0x3323), 0x15 },
+	{ CCI_REG8(0x3324), 0x1 },
+	{ CCI_REG8(0x3325), 0x0 },
+	{ CCI_REG8(0x3326), 0x30 },
+	{ CCI_REG8(0x3327), 0xa0 },
+	{ CCI_REG8(0x3328), 0x16 },
+	{ CCI_REG8(0x3329), 0x15 },
+	{ CCI_REG8(0x332a), 0x2b },
+	{ CCI_REG8(0x332b), 0x0 },
+	{ CCI_REG8(0x332c), 0x30 },
+	{ CCI_REG8(0x332d), 0xa0 },
+	{ CCI_REG8(0x332e), 0x16 },
+	{ CCI_REG8(0x332f), 0x15 },
+	{ CCI_REG8(0x3330), 0x1 },
+	{ CCI_REG8(0x3331), 0x0 },
+	{ CCI_REG8(0x3332), 0x10 },
+	{ CCI_REG8(0x3333), 0xa0 },
+	{ CCI_REG8(0x3334), 0x16 },
+	{ CCI_REG8(0x3335), 0x15 },
+	{ CCI_REG8(0x3058), 0x8 },
+	{ CCI_REG8(0x3059), 0x0 },
+	{ CCI_REG8(0x305a), 0x9 },
+	{ CCI_REG8(0x305b), 0x0 },
+	{ CCI_REG8(0x3336), 0x1 },
+	{ CCI_REG8(0x3337), 0x0 },
+	{ CCI_REG8(0x3338), 0x90 },
+	{ CCI_REG8(0x3339), 0xb0 },
+	{ CCI_REG8(0x333a), 0x16 },
+	{ CCI_REG8(0x333b), 0x15 },
+	{ CCI_REG8(0x333c), 0x1f },
+	{ CCI_REG8(0x333d), 0x0 },
+	{ CCI_REG8(0x333e), 0x10 },
+	{ CCI_REG8(0x333f), 0xa0 },
+	{ CCI_REG8(0x3340), 0x16 },
+	{ CCI_REG8(0x3341), 0x15 },
+	{ CCI_REG8(0x3342), 0x52 },
+	{ CCI_REG8(0x3343), 0x0 },
+	{ CCI_REG8(0x3344), 0x10 },
+	{ CCI_REG8(0x3345), 0x80 },
+	{ CCI_REG8(0x3346), 0x16 },
+	{ CCI_REG8(0x3347), 0x15 },
+	{ CCI_REG8(0x3348), 0x1 },
+	{ CCI_REG8(0x3349), 0x0 },
+	{ CCI_REG8(0x334a), 0x10 },
+	{ CCI_REG8(0x334b), 0x80 },
+	{ CCI_REG8(0x334c), 0x16 },
+	{ CCI_REG8(0x334d), 0x1d },
+	{ CCI_REG8(0x334e), 0x1 },
+	{ CCI_REG8(0x334f), 0x0 },
+	{ CCI_REG8(0x3350), 0x50 },
+	{ CCI_REG8(0x3351), 0x84 },
+	{ CCI_REG8(0x3352), 0x16 },
+	{ CCI_REG8(0x3353), 0x1d },
+	{ CCI_REG8(0x3354), 0x18 },
+	{ CCI_REG8(0x3355), 0x0 },
+	{ CCI_REG8(0x3356), 0x10 },
+	{ CCI_REG8(0x3357), 0x84 },
+	{ CCI_REG8(0x3358), 0x16 },
+	{ CCI_REG8(0x3359), 0x1d },
+	{ CCI_REG8(0x335a), 0x80 },
+	{ CCI_REG8(0x335b), 0x2 },
+	{ CCI_REG8(0x335c), 0x10 },
+	{ CCI_REG8(0x335d), 0xc4 },
+	{ CCI_REG8(0x335e), 0x14 },
+	{ CCI_REG8(0x335f), 0x1d },
+	{ CCI_REG8(0x3360), 0xa5 },
+	{ CCI_REG8(0x3361), 0x0 },
+	{ CCI_REG8(0x3362), 0x10 },
+	{ CCI_REG8(0x3363), 0x84 },
+	{ CCI_REG8(0x3364), 0x16 },
+	{ CCI_REG8(0x3365), 0x1d },
+	{ CCI_REG8(0x3366), 0x1 },
+	{ CCI_REG8(0x3367), 0x0 },
+	{ CCI_REG8(0x3368), 0x90 },
+	{ CCI_REG8(0x3369), 0x84 },
+	{ CCI_REG8(0x336a), 0x16 },
+	{ CCI_REG8(0x336b), 0x1d },
+	{ CCI_REG8(0x336c), 0x12 },
+	{ CCI_REG8(0x336d), 0x0 },
+	{ CCI_REG8(0x336e), 0x10 },
+	{ CCI_REG8(0x336f), 0x84 },
+	{ CCI_REG8(0x3370), 0x16 },
+	{ CCI_REG8(0x3371), 0x15 },
+	{ CCI_REG8(0x3372), 0x32 },
+	{ CCI_REG8(0x3373), 0x0 },
+	{ CCI_REG8(0x3374), 0x30 },
+	{ CCI_REG8(0x3375), 0x84 },
+	{ CCI_REG8(0x3376), 0x16 },
+	{ CCI_REG8(0x3377), 0x15 },
+	{ CCI_REG8(0x3378), 0x26 },
+	{ CCI_REG8(0x3379), 0x0 },
+	{ CCI_REG8(0x337a), 0x10 },
+	{ CCI_REG8(0x337b), 0x84 },
+	{ CCI_REG8(0x337c), 0x16 },
+	{ CCI_REG8(0x337d), 0x15 },
+	{ CCI_REG8(0x337e), 0x80 },
+	{ CCI_REG8(0x337f), 0x2 },
+	{ CCI_REG8(0x3380), 0x10 },
+	{ CCI_REG8(0x3381), 0xc4 },
+	{ CCI_REG8(0x3382), 0x14 },
+	{ CCI_REG8(0x3383), 0x15 },
+	{ CCI_REG8(0x3384), 0xa9 },
+	{ CCI_REG8(0x3385), 0x0 },
+	{ CCI_REG8(0x3386), 0x10 },
+	{ CCI_REG8(0x3387), 0x84 },
+	{ CCI_REG8(0x3388), 0x16 },
+	{ CCI_REG8(0x3389), 0x15 },
+	{ CCI_REG8(0x338a), 0x41 },
+	{ CCI_REG8(0x338b), 0x0 },
+	{ CCI_REG8(0x338c), 0x10 },
+	{ CCI_REG8(0x338d), 0x80 },
+	{ CCI_REG8(0x338e), 0x16 },
+	{ CCI_REG8(0x338f), 0x15 },
+	{ CCI_REG8(0x3390), 0x2 },
+	{ CCI_REG8(0x3391), 0x0 },
+	{ CCI_REG8(0x3392), 0x10 },
+	{ CCI_REG8(0x3393), 0xa0 },
+	{ CCI_REG8(0x3394), 0x16 },
+	{ CCI_REG8(0x3395), 0x15 },
+	{ CCI_REG8(0x305c), 0x18 },
+	{ CCI_REG8(0x305d), 0x0 },
+	{ CCI_REG8(0x305e), 0x19 },
+	{ CCI_REG8(0x305f), 0x0 },
+	{ CCI_REG8(0x3396), 0x1 },
+	{ CCI_REG8(0x3397), 0x0 },
+	{ CCI_REG8(0x3398), 0x90 },
+	{ CCI_REG8(0x3399), 0x30 },
+	{ CCI_REG8(0x339a), 0x56 },
+	{ CCI_REG8(0x339b), 0x57 },
+	{ CCI_REG8(0x339c), 0x1 },
+	{ CCI_REG8(0x339d), 0x0 },
+	{ CCI_REG8(0x339e), 0x10 },
+	{ CCI_REG8(0x339f), 0x20 },
+	{ CCI_REG8(0x33a0), 0xd6 },
+	{ CCI_REG8(0x33a1), 0x17 },
+	{ CCI_REG8(0x33a2), 0x1 },
+	{ CCI_REG8(0x33a3), 0x0 },
+	{ CCI_REG8(0x33a4), 0x10 },
+	{ CCI_REG8(0x33a5), 0x28 },
+	{ CCI_REG8(0x33a6), 0xd6 },
+	{ CCI_REG8(0x33a7), 0x17 },
+	{ CCI_REG8(0x33a8), 0x3 },
+	{ CCI_REG8(0x33a9), 0x0 },
+	{ CCI_REG8(0x33aa), 0x10 },
+	{ CCI_REG8(0x33ab), 0x20 },
+	{ CCI_REG8(0x33ac), 0xd6 },
+	{ CCI_REG8(0x33ad), 0x17 },
+	{ CCI_REG8(0x33ae), 0x61 },
+	{ CCI_REG8(0x33af), 0x0 },
+	{ CCI_REG8(0x33b0), 0x10 },
+	{ CCI_REG8(0x33b1), 0x20 },
+	{ CCI_REG8(0x33b2), 0xd6 },
+	{ CCI_REG8(0x33b3), 0x15 },
+	{ CCI_REG8(0x33b4), 0x1 },
+	{ CCI_REG8(0x33b5), 0x0 },
+	{ CCI_REG8(0x33b6), 0x10 },
+	{ CCI_REG8(0x33b7), 0x20 },
+	{ CCI_REG8(0x33b8), 0xd6 },
+	{ CCI_REG8(0x33b9), 0x1d },
+	{ CCI_REG8(0x33ba), 0x1 },
+	{ CCI_REG8(0x33bb), 0x0 },
+	{ CCI_REG8(0x33bc), 0x50 },
+	{ CCI_REG8(0x33bd), 0x20 },
+	{ CCI_REG8(0x33be), 0xd6 },
+	{ CCI_REG8(0x33bf), 0x1d },
+	{ CCI_REG8(0x33c0), 0x2c },
+	{ CCI_REG8(0x33c1), 0x0 },
+	{ CCI_REG8(0x33c2), 0x10 },
+	{ CCI_REG8(0x33c3), 0x20 },
+	{ CCI_REG8(0x33c4), 0xd6 },
+	{ CCI_REG8(0x33c5), 0x1d },
+	{ CCI_REG8(0x33c6), 0x1 },
+	{ CCI_REG8(0x33c7), 0x0 },
+	{ CCI_REG8(0x33c8), 0x90 },
+	{ CCI_REG8(0x33c9), 0x20 },
+	{ CCI_REG8(0x33ca), 0xd6 },
+	{ CCI_REG8(0x33cb), 0x1d },
+	{ CCI_REG8(0x33cc), 0x83 },
+	{ CCI_REG8(0x33cd), 0x0 },
+	{ CCI_REG8(0x33ce), 0x10 },
+	{ CCI_REG8(0x33cf), 0x20 },
+	{ CCI_REG8(0x33d0), 0xd6 },
+	{ CCI_REG8(0x33d1), 0x15 },
+	{ CCI_REG8(0x33d2), 0x1 },
+	{ CCI_REG8(0x33d3), 0x0 },
+	{ CCI_REG8(0x33d4), 0x10 },
+	{ CCI_REG8(0x33d5), 0x30 },
+	{ CCI_REG8(0x33d6), 0xd6 },
+	{ CCI_REG8(0x33d7), 0x15 },
+	{ CCI_REG8(0x33d8), 0x1 },
+	{ CCI_REG8(0x33d9), 0x0 },
+	{ CCI_REG8(0x33da), 0x10 },
+	{ CCI_REG8(0x33db), 0x20 },
+	{ CCI_REG8(0x33dc), 0xd6 },
+	{ CCI_REG8(0x33dd), 0x15 },
+	{ CCI_REG8(0x33de), 0x1 },
+	{ CCI_REG8(0x33df), 0x0 },
+	{ CCI_REG8(0x33e0), 0x10 },
+	{ CCI_REG8(0x33e1), 0x20 },
+	{ CCI_REG8(0x33e2), 0x56 },
+	{ CCI_REG8(0x33e3), 0x15 },
+	{ CCI_REG8(0x33e4), 0x7 },
+	{ CCI_REG8(0x33e5), 0x0 },
+	{ CCI_REG8(0x33e6), 0x10 },
+	{ CCI_REG8(0x33e7), 0x20 },
+	{ CCI_REG8(0x33e8), 0x16 },
+	{ CCI_REG8(0x33e9), 0x15 },
+	{ CCI_REG8(0x3060), 0x26 },
+	{ CCI_REG8(0x3061), 0x0 },
+	{ CCI_REG8(0x302a), 0xff },
+	{ CCI_REG8(0x302b), 0xff },
+	{ CCI_REG8(0x302c), 0xff },
+	{ CCI_REG8(0x302d), 0xff },
+	{ CCI_REG8(0x302e), 0x3f },
+	{ CCI_REG8(0x3013), 0xb },
+	{ CCI_REG8(0x102b), 0x2c },
+	{ CCI_REG8(0x102c), 0x1 },
+	{ CCI_REG8(0x1035), 0x54 },
+	{ CCI_REG8(0x1036), 0x0 },
+	{ CCI_REG8(0x3090), 0x2a },
+	{ CCI_REG8(0x3091), 0x1 },
+	{ CCI_REG8(0x30c6), 0x5 },
+	{ CCI_REG8(0x30c7), 0x0 },
+	{ CCI_REG8(0x30c8), 0x0 },
+	{ CCI_REG8(0x30c9), 0x0 },
+	{ CCI_REG8(0x30ca), 0x0 },
+	{ CCI_REG8(0x30cb), 0x0 },
+	{ CCI_REG8(0x30cc), 0x0 },
+	{ CCI_REG8(0x30cd), 0x0 },
+	{ CCI_REG8(0x30ce), 0x0 },
+	{ CCI_REG8(0x30cf), 0x5 },
+	{ CCI_REG8(0x30d0), 0x0 },
+	{ CCI_REG8(0x30d1), 0x0 },
+	{ CCI_REG8(0x30d2), 0x0 },
+	{ CCI_REG8(0x30d3), 0x0 },
+	{ CCI_REG8(0x30d4), 0x0 },
+	{ CCI_REG8(0x30d5), 0x0 },
+	{ CCI_REG8(0x30d6), 0x0 },
+	{ CCI_REG8(0x30d7), 0x0 },
+	{ CCI_REG8(0x30f3), 0x5 },
+	{ CCI_REG8(0x30f4), 0x0 },
+	{ CCI_REG8(0x30f5), 0x0 },
+	{ CCI_REG8(0x30f6), 0x0 },
+	{ CCI_REG8(0x30f7), 0x0 },
+	{ CCI_REG8(0x30f8), 0x0 },
+	{ CCI_REG8(0x30f9), 0x0 },
+	{ CCI_REG8(0x30fa), 0x0 },
+	{ CCI_REG8(0x30fb), 0x0 },
+	{ CCI_REG8(0x30d8), 0x5 },
+	{ CCI_REG8(0x30d9), 0x0 },
+	{ CCI_REG8(0x30da), 0x0 },
+	{ CCI_REG8(0x30db), 0x0 },
+	{ CCI_REG8(0x30dc), 0x0 },
+	{ CCI_REG8(0x30dd), 0x0 },
+	{ CCI_REG8(0x30de), 0x0 },
+	{ CCI_REG8(0x30df), 0x0 },
+	{ CCI_REG8(0x30e0), 0x0 },
+	{ CCI_REG8(0x30e1), 0x5 },
+	{ CCI_REG8(0x30e2), 0x0 },
+	{ CCI_REG8(0x30e3), 0x0 },
+	{ CCI_REG8(0x30e4), 0x0 },
+	{ CCI_REG8(0x30e5), 0x0 },
+	{ CCI_REG8(0x30e6), 0x0 },
+	{ CCI_REG8(0x30e7), 0x0 },
+	{ CCI_REG8(0x30e8), 0x0 },
+	{ CCI_REG8(0x30e9), 0x0 },
+	{ CCI_REG8(0x30f3), 0x5 },
+	{ CCI_REG8(0x30f4), 0x2 },
+	{ CCI_REG8(0x30f5), 0x0 },
+	{ CCI_REG8(0x30f6), 0x17 },
+	{ CCI_REG8(0x30f7), 0x1 },
+	{ CCI_REG8(0x30f8), 0x0 },
+	{ CCI_REG8(0x30f9), 0x0 },
+	{ CCI_REG8(0x30fa), 0x0 },
+	{ CCI_REG8(0x30fb), 0x0 },
+	{ CCI_REG8(0x30d8), 0x3 },
+	{ CCI_REG8(0x30d9), 0x1 },
+	{ CCI_REG8(0x30da), 0x0 },
+	{ CCI_REG8(0x30db), 0x19 },
+	{ CCI_REG8(0x30dc), 0x1 },
+	{ CCI_REG8(0x30dd), 0x0 },
+	{ CCI_REG8(0x30de), 0x0 },
+	{ CCI_REG8(0x30df), 0x0 },
+	{ CCI_REG8(0x30e0), 0x0 },
+	{ CCI_REG8(0x30a2), 0x5 },
+	{ CCI_REG8(0x30a3), 0x2 },
+	{ CCI_REG8(0x30a4), 0x0 },
+	{ CCI_REG8(0x30a5), 0x22 },
+	{ CCI_REG8(0x30a6), 0x0 },
+	{ CCI_REG8(0x30a7), 0x0 },
+	{ CCI_REG8(0x30a8), 0x0 },
+	{ CCI_REG8(0x30a9), 0x0 },
+	{ CCI_REG8(0x30aa), 0x0 },
+	{ CCI_REG8(0x30ab), 0x5 },
+	{ CCI_REG8(0x30ac), 0x2 },
+	{ CCI_REG8(0x30ad), 0x0 },
+	{ CCI_REG8(0x30ae), 0x22 },
+	{ CCI_REG8(0x30af), 0x0 },
+	{ CCI_REG8(0x30b0), 0x0 },
+	{ CCI_REG8(0x30b1), 0x0 },
+	{ CCI_REG8(0x30b2), 0x0 },
+	{ CCI_REG8(0x30b3), 0x0 },
+	{ CCI_REG8(0x30bd), 0x5 },
+	{ CCI_REG8(0x30be), 0x9f },
+	{ CCI_REG8(0x30bf), 0x0 },
+	{ CCI_REG8(0x30c0), 0x7d },
+	{ CCI_REG8(0x30c1), 0x0 },
+	{ CCI_REG8(0x30c2), 0x0 },
+	{ CCI_REG8(0x30c3), 0x0 },
+	{ CCI_REG8(0x30c4), 0x0 },
+	{ CCI_REG8(0x30c5), 0x0 },
+	{ CCI_REG8(0x30b4), 0x4 },
+	{ CCI_REG8(0x30b5), 0x9c },
+	{ CCI_REG8(0x30b6), 0x0 },
+	{ CCI_REG8(0x30b7), 0x7d },
+	{ CCI_REG8(0x30b8), 0x0 },
+	{ CCI_REG8(0x30b9), 0x0 },
+	{ CCI_REG8(0x30ba), 0x0 },
+	{ CCI_REG8(0x30bb), 0x0 },
+	{ CCI_REG8(0x30bc), 0x0 },
+	{ CCI_REG8(0x30fc), 0x5 },
+	{ CCI_REG8(0x30fd), 0x0 },
+	{ CCI_REG8(0x30fe), 0x0 },
+	{ CCI_REG8(0x30ff), 0x0 },
+	{ CCI_REG8(0x3100), 0x0 },
+	{ CCI_REG8(0x3101), 0x0 },
+	{ CCI_REG8(0x3102), 0x0 },
+	{ CCI_REG8(0x3103), 0x0 },
+	{ CCI_REG8(0x3104), 0x0 },
+	{ CCI_REG8(0x3105), 0x5 },
+	{ CCI_REG8(0x3106), 0x0 },
+	{ CCI_REG8(0x3107), 0x0 },
+	{ CCI_REG8(0x3108), 0x0 },
+	{ CCI_REG8(0x3109), 0x0 },
+	{ CCI_REG8(0x310a), 0x0 },
+	{ CCI_REG8(0x310b), 0x0 },
+	{ CCI_REG8(0x310c), 0x0 },
+	{ CCI_REG8(0x310d), 0x0 },
+	{ CCI_REG8(0x3099), 0x5 },
+	{ CCI_REG8(0x309a), 0x96 },
+	{ CCI_REG8(0x309b), 0x0 },
+	{ CCI_REG8(0x309c), 0x6 },
+	{ CCI_REG8(0x309d), 0x0 },
+	{ CCI_REG8(0x309e), 0x0 },
+	{ CCI_REG8(0x309f), 0x0 },
+	{ CCI_REG8(0x30a0), 0x0 },
+	{ CCI_REG8(0x30a1), 0x0 },
+	{ CCI_REG8(0x310e), 0x5 },
+	{ CCI_REG8(0x310f), 0x2 },
+	{ CCI_REG8(0x3110), 0x0 },
+	{ CCI_REG8(0x3111), 0x2b },
+	{ CCI_REG8(0x3112), 0x0 },
+	{ CCI_REG8(0x3113), 0x0 },
+	{ CCI_REG8(0x3114), 0x0 },
+	{ CCI_REG8(0x3115), 0x0 },
+	{ CCI_REG8(0x3116), 0x0 },
+	{ CCI_REG8(0x3117), 0x5 },
+	{ CCI_REG8(0x3118), 0x2 },
+	{ CCI_REG8(0x3119), 0x0 },
+	{ CCI_REG8(0x311a), 0x2c },
+	{ CCI_REG8(0x311b), 0x0 },
+	{ CCI_REG8(0x311c), 0x0 },
+	{ CCI_REG8(0x311d), 0x0 },
+	{ CCI_REG8(0x311e), 0x0 },
+	{ CCI_REG8(0x311f), 0x0 },
+	{ CCI_REG8(0x30ea), 0x0 },
+	{ CCI_REG8(0x30eb), 0x0 },
+	{ CCI_REG8(0x30ec), 0x0 },
+	{ CCI_REG8(0x30ed), 0x0 },
+	{ CCI_REG8(0x30ee), 0x0 },
+	{ CCI_REG8(0x30ef), 0x0 },
+	{ CCI_REG8(0x30f0), 0x0 },
+	{ CCI_REG8(0x30f1), 0x0 },
+	{ CCI_REG8(0x30f2), 0x0 },
+	{ CCI_REG8(0x313b), 0x3 },
+	{ CCI_REG8(0x313c), 0x31 },
+	{ CCI_REG8(0x313d), 0x0 },
+	{ CCI_REG8(0x313e), 0x7 },
+	{ CCI_REG8(0x313f), 0x0 },
+	{ CCI_REG8(0x3140), 0x68 },
+	{ CCI_REG8(0x3141), 0x0 },
+	{ CCI_REG8(0x3142), 0x34 },
+	{ CCI_REG8(0x3143), 0x0 },
+	{ CCI_REG8(0x31a0), 0x3 },
+	{ CCI_REG8(0x31a1), 0x16 },
+	{ CCI_REG8(0x31a2), 0x0 },
+	{ CCI_REG8(0x31a3), 0x8 },
+	{ CCI_REG8(0x31a4), 0x0 },
+	{ CCI_REG8(0x31a5), 0x7e },
+	{ CCI_REG8(0x31a6), 0x0 },
+	{ CCI_REG8(0x31a7), 0x8 },
+	{ CCI_REG8(0x31a8), 0x0 },
+	{ CCI_REG8(0x31a9), 0x3 },
+	{ CCI_REG8(0x31aa), 0x16 },
+	{ CCI_REG8(0x31ab), 0x0 },
+	{ CCI_REG8(0x31ac), 0x8 },
+	{ CCI_REG8(0x31ad), 0x0 },
+	{ CCI_REG8(0x31ae), 0x7e },
+	{ CCI_REG8(0x31af), 0x0 },
+	{ CCI_REG8(0x31b0), 0x8 },
+	{ CCI_REG8(0x31b1), 0x0 },
+	{ CCI_REG8(0x31b2), 0x3 },
+	{ CCI_REG8(0x31b3), 0x16 },
+	{ CCI_REG8(0x31b4), 0x0 },
+	{ CCI_REG8(0x31b5), 0x8 },
+	{ CCI_REG8(0x31b6), 0x0 },
+	{ CCI_REG8(0x31b7), 0x7e },
+	{ CCI_REG8(0x31b8), 0x0 },
+	{ CCI_REG8(0x31b9), 0x8 },
+	{ CCI_REG8(0x31ba), 0x0 },
+	{ CCI_REG8(0x3120), 0x5 },
+	{ CCI_REG8(0x3121), 0x45 },
+	{ CCI_REG8(0x3122), 0x0 },
+	{ CCI_REG8(0x3123), 0x1d },
+	{ CCI_REG8(0x3124), 0x0 },
+	{ CCI_REG8(0x3125), 0xa9 },
+	{ CCI_REG8(0x3126), 0x0 },
+	{ CCI_REG8(0x3127), 0x6d },
+	{ CCI_REG8(0x3128), 0x0 },
+	{ CCI_REG8(0x3129), 0x5 },
+	{ CCI_REG8(0x312a), 0x15 },
+	{ CCI_REG8(0x312b), 0x0 },
+	{ CCI_REG8(0x312c), 0xa },
+	{ CCI_REG8(0x312d), 0x0 },
+	{ CCI_REG8(0x312e), 0x45 },
+	{ CCI_REG8(0x312f), 0x0 },
+	{ CCI_REG8(0x3130), 0x1d },
+	{ CCI_REG8(0x3131), 0x0 },
+	{ CCI_REG8(0x3132), 0x5 },
+	{ CCI_REG8(0x3133), 0x7d },
+	{ CCI_REG8(0x3134), 0x0 },
+	{ CCI_REG8(0x3135), 0xa },
+	{ CCI_REG8(0x3136), 0x0 },
+	{ CCI_REG8(0x3137), 0xa9 },
+	{ CCI_REG8(0x3138), 0x0 },
+	{ CCI_REG8(0x3139), 0x6d },
+	{ CCI_REG8(0x313a), 0x0 },
+	{ CCI_REG8(0x3144), 0x5 },
+	{ CCI_REG8(0x3145), 0x0 },
+	{ CCI_REG8(0x3146), 0x0 },
+	{ CCI_REG8(0x3147), 0x30 },
+	{ CCI_REG8(0x3148), 0x0 },
+	{ CCI_REG8(0x3149), 0x0 },
+	{ CCI_REG8(0x314a), 0x0 },
+	{ CCI_REG8(0x314b), 0x0 },
+	{ CCI_REG8(0x314c), 0x0 },
+	{ CCI_REG8(0x314d), 0x3 },
+	{ CCI_REG8(0x314e), 0x0 },
+	{ CCI_REG8(0x314f), 0x0 },
+	{ CCI_REG8(0x3150), 0x31 },
+	{ CCI_REG8(0x3151), 0x0 },
+	{ CCI_REG8(0x3152), 0x0 },
+	{ CCI_REG8(0x3153), 0x0 },
+	{ CCI_REG8(0x3154), 0x0 },
+	{ CCI_REG8(0x3155), 0x0 },
+	{ CCI_REG8(0x31d8), 0x5 },
+	{ CCI_REG8(0x31d9), 0x3a },
+	{ CCI_REG8(0x31da), 0x0 },
+	{ CCI_REG8(0x31db), 0x2e },
+	{ CCI_REG8(0x31dc), 0x0 },
+	{ CCI_REG8(0x31dd), 0x9e },
+	{ CCI_REG8(0x31de), 0x0 },
+	{ CCI_REG8(0x31df), 0x7e },
+	{ CCI_REG8(0x31e0), 0x0 },
+	{ CCI_REG8(0x31e1), 0x5 },
+	{ CCI_REG8(0x31e2), 0x4 },
+	{ CCI_REG8(0x31e3), 0x0 },
+	{ CCI_REG8(0x31e4), 0x4 },
+	{ CCI_REG8(0x31e5), 0x0 },
+	{ CCI_REG8(0x31e6), 0x73 },
+	{ CCI_REG8(0x31e7), 0x0 },
+	{ CCI_REG8(0x31e8), 0x4 },
+	{ CCI_REG8(0x31e9), 0x0 },
+	{ CCI_REG8(0x31ea), 0x5 },
+	{ CCI_REG8(0x31eb), 0x0 },
+	{ CCI_REG8(0x31ec), 0x0 },
+	{ CCI_REG8(0x31ed), 0x0 },
+	{ CCI_REG8(0x31ee), 0x0 },
+	{ CCI_REG8(0x31ef), 0x0 },
+	{ CCI_REG8(0x31f0), 0x0 },
+	{ CCI_REG8(0x31f1), 0x0 },
+	{ CCI_REG8(0x31f2), 0x0 },
+	{ CCI_REG8(0x31f3), 0x0 },
+	{ CCI_REG8(0x31f4), 0x0 },
+	{ CCI_REG8(0x31f5), 0x0 },
+	{ CCI_REG8(0x31f6), 0x0 },
+	{ CCI_REG8(0x31f7), 0x0 },
+	{ CCI_REG8(0x31f8), 0x0 },
+	{ CCI_REG8(0x31f9), 0x0 },
+	{ CCI_REG8(0x31fa), 0x0 },
+	{ CCI_REG8(0x31fb), 0x5 },
+	{ CCI_REG8(0x31fc), 0x0 },
+	{ CCI_REG8(0x31fd), 0x0 },
+	{ CCI_REG8(0x31fe), 0x0 },
+	{ CCI_REG8(0x31ff), 0x0 },
+	{ CCI_REG8(0x3200), 0x0 },
+	{ CCI_REG8(0x3201), 0x0 },
+	{ CCI_REG8(0x3202), 0x0 },
+	{ CCI_REG8(0x3203), 0x0 },
+	{ CCI_REG8(0x3204), 0x0 },
+	{ CCI_REG8(0x3205), 0x0 },
+	{ CCI_REG8(0x3206), 0x0 },
+	{ CCI_REG8(0x3207), 0x0 },
+	{ CCI_REG8(0x3208), 0x0 },
+	{ CCI_REG8(0x3209), 0x0 },
+	{ CCI_REG8(0x320a), 0x0 },
+	{ CCI_REG8(0x320b), 0x0 },
+	{ CCI_REG8(0x3164), 0x5 },
+	{ CCI_REG8(0x3165), 0x14 },
+	{ CCI_REG8(0x3166), 0x0 },
+	{ CCI_REG8(0x3167), 0xc },
+	{ CCI_REG8(0x3168), 0x0 },
+	{ CCI_REG8(0x3169), 0x44 },
+	{ CCI_REG8(0x316a), 0x0 },
+	{ CCI_REG8(0x316b), 0x1f },
+	{ CCI_REG8(0x316c), 0x0 },
+	{ CCI_REG8(0x316d), 0x5 },
+	{ CCI_REG8(0x316e), 0x7c },
+	{ CCI_REG8(0x316f), 0x0 },
+	{ CCI_REG8(0x3170), 0xc },
+	{ CCI_REG8(0x3171), 0x0 },
+	{ CCI_REG8(0x3172), 0xa8 },
+	{ CCI_REG8(0x3173), 0x0 },
+	{ CCI_REG8(0x3174), 0x6f },
+	{ CCI_REG8(0x3175), 0x0 },
+	{ CCI_REG8(0x31c4), 0x5 },
+	{ CCI_REG8(0x31c5), 0x24 },
+	{ CCI_REG8(0x31c6), 0x1 },
+	{ CCI_REG8(0x31c7), 0x4 },
+	{ CCI_REG8(0x31c8), 0x0 },
+	{ CCI_REG8(0x31c9), 0x5 },
+	{ CCI_REG8(0x31ca), 0x24 },
+	{ CCI_REG8(0x31cb), 0x1 },
+	{ CCI_REG8(0x31cc), 0x4 },
+	{ CCI_REG8(0x31cd), 0x0 },
+	{ CCI_REG8(0x31ce), 0x5 },
+	{ CCI_REG8(0x31cf), 0x24 },
+	{ CCI_REG8(0x31d0), 0x1 },
+	{ CCI_REG8(0x31d1), 0x4 },
+	{ CCI_REG8(0x31d2), 0x0 },
+	{ CCI_REG8(0x31d3), 0x5 },
+	{ CCI_REG8(0x31d4), 0x73 },
+	{ CCI_REG8(0x31d5), 0x0 },
+	{ CCI_REG8(0x31d6), 0xb1 },
+	{ CCI_REG8(0x31d7), 0x0 },
+	{ CCI_REG8(0x3176), 0x5 },
+	{ CCI_REG8(0x3177), 0x10 },
+	{ CCI_REG8(0x3178), 0x0 },
+	{ CCI_REG8(0x3179), 0x56 },
+	{ CCI_REG8(0x317a), 0x0 },
+	{ CCI_REG8(0x317b), 0x0 },
+	{ CCI_REG8(0x317c), 0x0 },
+	{ CCI_REG8(0x317d), 0x0 },
+	{ CCI_REG8(0x317e), 0x0 },
+	{ CCI_REG8(0x317f), 0x5 },
+	{ CCI_REG8(0x3180), 0x6a },
+	{ CCI_REG8(0x3181), 0x0 },
+	{ CCI_REG8(0x3182), 0xad },
+	{ CCI_REG8(0x3183), 0x0 },
+	{ CCI_REG8(0x3184), 0x0 },
+	{ CCI_REG8(0x3185), 0x0 },
+	{ CCI_REG8(0x3186), 0x0 },
+	{ CCI_REG8(0x3187), 0x0 },
+	{ CCI_REG8(0x100d), 0x0 },
+	{ CCI_REG8(0x1013), 0x2b },
+	/* Sensor control mode */
+	{ CCI_REG8(0x0043), 0x0 }, //  Sensor Control Mode.SLEEP_POWER_MODE(0)
+	{ CCI_REG8(0x0043), 0x0 }, //  Sensor Control Mode.IDLE_POWER_MODE(0)
+	{ CCI_REG8(0x0043), 0x4 }, //  Sensor Control Mode.SYSTEM_CLOCK_ENABLE(0)
+	{ CCI_REG8(0x0043), 0xC }, //  Sensor Control Mode.SRAM_CLOCK_ENABLE(0)
+	{ CCI_REG8(0x1001), 0x41 }, //  Sensor Control Mode.EXT_EVENT_SEL(0)
+	{ CCI_REG8(0x10f2), 0x1 }, //  Sensor Control Mode.NB_OF_FRAMES_A(0)
+	{ CCI_REG8(0x10f3), 0x0 }, //  Sensor Control Mode.NB_OF_FRAMES_A(1)
+	{ CCI_REG8(0x0012), 0x0 }, //  IO Drive Strength.DIG_DRIVE_STRENGTH(0)
+	{ CCI_REG8(0x0012), 0x0 }, //  IO Drive Strength.CCI_DRIVE_STRENGTH(0)
+	{ CCI_REG8(0x1001), 0x41 }, //  Readout && Exposure.EXT_EXP_PW_SEL(0)
+	{ CCI_REG8(0x10d0), 0x0 }, //  Readout && Exposure.EXT_EXP_PW_DELAY(0)
+	{ CCI_REG8(0x10d1), 0x0 }, //  Readout && Exposure.EXT_EXP_PW_DELAY(1)
+	/* MIPI */
+	{ CCI_REG8(0x6006), 0x0 }, //  MIPI.TX_CTRL_EN(0)
+	{ CCI_REG8(0x5004), 0x1 }, //  MIPI.datarate
+	{ CCI_REG8(0x5086), 0x2 }, //  MIPI.datarate
+	{ CCI_REG8(0x5087), 0x4e }, //  MIPI.datarate
+	{ CCI_REG8(0x5088), 0x0 }, //  MIPI.datarate
+	{ CCI_REG8(0x5090), 0x0 }, //  MIPI.datarate
+	{ CCI_REG8(0x5091), 0x8 }, //  MIPI.datarate
+	{ CCI_REG8(0x5092), 0x14 }, //  MIPI.datarate
+	{ CCI_REG8(0x5093), 0xf }, //  MIPI.datarate
+	{ CCI_REG8(0x5094), 0x6 }, //  MIPI.datarate
+	{ CCI_REG8(0x5095), 0x32 }, //  MIPI.datarate
+	{ CCI_REG8(0x5096), 0xe }, //  MIPI.datarate
+	{ CCI_REG8(0x5097), 0x0 }, //  MIPI.datarate
+	{ CCI_REG8(0x5098), 0x11 }, //  MIPI.datarate
+	{ CCI_REG8(0x5004), 0x0 }, //  MIPI.datarate
+	{ CCI_REG8(0x2066), 0x6c }, //  MIPI.datarate
+	{ CCI_REG8(0x2067), 0x7 }, //  MIPI.datarate
+	{ CCI_REG8(0x206e), 0x7e }, //  MIPI.datarate
+	{ CCI_REG8(0x206f), 0x6 }, //  MIPI.datarate
+	{ CCI_REG8(0x20ac), 0x7e }, //  MIPI.datarate
+	{ CCI_REG8(0x20ad), 0x6 }, //  MIPI.datarate
+	{ CCI_REG8(0x2076), 0xc8 }, //  MIPI.datarate
+	{ CCI_REG8(0x2077), 0x0 }, //  MIPI.datarate
+	{ CCI_REG8(0x20b4), 0xc8 }, //  MIPI.datarate
+	{ CCI_REG8(0x20b5), 0x0 }, //  MIPI.datarate
+	{ CCI_REG8(0x2078), 0x1e }, //  MIPI.datarate
+	{ CCI_REG8(0x2079), 0x4 }, //  MIPI.datarate
+	{ CCI_REG8(0x20b6), 0x1e }, //  MIPI.datarate
+	{ CCI_REG8(0x20b7), 0x4 }, //  MIPI.datarate
+	{ CCI_REG8(0x207a), 0xd4 }, //  MIPI.datarate
+	{ CCI_REG8(0x207b), 0x4 }, //  MIPI.datarate
+	{ CCI_REG8(0x20b8), 0xd4 }, //  MIPI.datarate
+	{ CCI_REG8(0x20b9), 0x4 }, //  MIPI.datarate
+	{ CCI_REG8(0x207c), 0x0 }, //  MIPI.VC_ID(0)
+	{ CCI_REG8(0x6001), 0x7 }, //  MIPI.TINIT(0)
+	{ CCI_REG8(0x6002), 0xd8 }, //  MIPI.TINIT(1)
+	{ CCI_REG8(0x6010), 0x0 }, //  MIPI.FRAME_MODE(0)
+	{ CCI_REG8(0x6010), 0x0 }, //  MIPI.EMBEDDED_FRAME_MODE(0)
+	{ CCI_REG8(0x6011), 0x0 }, //  MIPI.DATA_ENABLE_POLARITY(0)
+	{ CCI_REG8(0x6011), 0x0 }, //  MIPI.HSYNC_POLARITY(0)
+	{ CCI_REG8(0x6011), 0x0 }, //  MIPI.VSYNC_POLARITY(0)
+	{ CCI_REG8(0x6016), 0x0 }, //  MIPI.FRAME_COUNTER(0)
+	{ CCI_REG8(0x6017), 0x0 }, //  MIPI.FRAME_COUNTER(1)
+	{ CCI_REG8(0x6037), 0x1 }, //  MIPI.LINE_COUNT_RAW8(0)
+	{ CCI_REG8(0x6037), 0x3 }, //  MIPI.LINE_COUNT_RAW10(0)
+	{ CCI_REG8(0x6037), 0x7 }, //  MIPI.LINE_COUNT_RAW12(0)
+	{ CCI_REG8(0x6039), 0x1 }, //  MIPI.LINE_COUNT_EMB(0)
+	{ CCI_REG8(0x6018), 0x0 }, //  MIPI.CCI_READ_INTERRUPT_EN(0)
+	{ CCI_REG8(0x6018), 0x0 }, //  MIPI.CCI_WRITE_INTERRUPT_EN(0)
+	{ CCI_REG8(0x6065), 0x0 }, //  MIPI.TWAKE_TIMER(0)
+	{ CCI_REG8(0x6066), 0x0 }, //  MIPI.TWAKE_TIMER(1)
+	{ CCI_REG8(0x601c), 0x0 }, //  MIPI.SKEW_CAL_EN(0)
+	{ CCI_REG8(0x601d), 0x0 }, //  MIPI.SKEW_COUNT(0)
+	{ CCI_REG8(0x601e), 0x22 }, //  MIPI.SKEW_COUNT(1)
+	{ CCI_REG8(0x601f), 0x0 }, //  MIPI.SCRAMBLING_EN(0)
+	{ CCI_REG8(0x6003), 0x1 }, //  MIPI.INIT_SKEW_EN(0)
+	{ CCI_REG8(0x6004), 0x7a }, //  MIPI.INIT_SKEW(0)
+	{ CCI_REG8(0x6005), 0x12 }, //  MIPI.INIT_SKEW(1)
+	{ CCI_REG8(0x6006), 0x1 }, //  MIPI.TX_CTRL_EN(0)
+	/* Processing */
+	{ CCI_REG8(0x4006), 0x8 }, //  Processing.BSP(0)
+	{ CCI_REG8(0x2045), 0x1 }, //  Processing.CDS_RNC(0)
+	{ CCI_REG8(0x2048), 0x1 }, //  Processing.CDS_IMG(0)
+	{ CCI_REG8(0x204b), 0x3 }, //  Processing.RNC_EN(0)
+	{ CCI_REG8(0x205b), 0x64 }, //  Processing.RNC_DARK_TARGET(0)
+	{ CCI_REG8(0x205c), 0x0 }, //  Processing.RNC_DARK_TARGET(1)
+	{ CCI_REG8(0x24dc), 0x12 }, //  Defect Pixel Correction.DC_ENABLE(0)
+	{ CCI_REG8(0x24dc), 0x10 }, //  Defect Pixel Correction.DC_MODE(0)
+	{ CCI_REG8(0x24dc), 0x0 }, //  Defect Pixel Correction.DC_REPLACEMENT_VALUE(0)
+	{ CCI_REG8(0x24dd), 0x0 }, //  Defect Pixel Correction.DC_LIMIT_LOW(0)
+	{ CCI_REG8(0x24de), 0x0 }, //  Defect Pixel Correction.DC_LIMIT_HIGH(0)
+	{ CCI_REG8(0x24df), 0x0 }, //  Defect Pixel Correction.DC_LIMIT_HIGH_MODE(0)
+	/* Illumination */
+	{ CCI_REG8(0x10d7), 0x1 }, //  Illumination Trigger.ILLUM_EN(0)
+	{ CCI_REG8(0x10d8), 0x2 }, //  Illumination Trigger.ILLUM_POL(0)
+	/* Histogram */
+	{ CCI_REG8(0x205d), 0x0 }, //  Histogram.HIST_EN(0)
+	{ CCI_REG8(0x205e), 0x0 }, //  Histogram.HIST_USAGE_RATIO(0)
+	{ CCI_REG8(0x2063), 0x0 }, //  Histogram.PIXEL_DATA_SUPP(0)
+	{ CCI_REG8(0x2063), 0x0 }, //  Histogram.PIXEL_TRANSMISSION(0)
+	/* TP */
+	{ CCI_REG8(0x2091), 0x0 }, //  Test Pattern Generator.TPG_EN(0)
+	{ CCI_REG8(0x2091), 0x0 }, //  Test Pattern Generator.TPG_CONFIG(0)
+	/* Reduce Slew Rate - fix for defect line */
+	{ CCI_REG8(0x402d), 0x7b },
+};
+
+static const char *const mira220_test_pattern_menu[] = {
+	"Disabled",
+	"Vertical Gradient",
+};
+
+static const int mira220_test_pattern_val[] = {
+	MIRA220_TEST_PATTERN_DISABLE,
+	MIRA220_TEST_PATTERN_VERTICAL_GRADIENT,
+};
+
+/* regulator supplies */
+static const char *const mira220_supplies[] = {
+	/* Supplies can be enabled in any order */
+	"vana", /* Analog (2.8V) supply */
+	"vdig", /* Digital Core (1.8V) supply */
+	"vddl", /* IF (1.2V) supply */
+};
+
+struct mira220 {
+	struct v4l2_subdev sd;
+	struct media_pad pad;
+
+	enum mira220_variants variant;
+
+	struct v4l2_mbus_framefmt fmt;
+
+	struct clk *xclk;
+	u32 xclk_freq;
+
+	struct regulator_bulk_data supplies[ARRAY_SIZE(mira220_supplies)];
+
+	struct gpio_desc *reset_gpio;
+
+	unsigned int lanes;
+	unsigned int row_length;
+
+	struct v4l2_ctrl_handler ctrl_handler;
+	struct v4l2_ctrl *vflip;
+	struct v4l2_ctrl *hflip;
+	struct v4l2_ctrl *vblank;
+	struct v4l2_ctrl *exposure;
+
+	struct regmap *regmap;
+};
+
+static inline struct mira220 *to_mira220(struct v4l2_subdev *sd)
+{
+	return container_of_const(sd, struct mira220, sd);
+}
+
+static bool mira220_is_mono(struct mira220 *mira220)
+{
+	return mira220->variant == MIRA220_VARIANT_MONO;
+}
+
+/*
+ * The supported formats. This table contains 4 entries per format, to cover the
+ * various flip combinations in the order:
+ * - no flip
+ * - h flip
+ * - v flip
+ * - h&v flips
+ */
+static const u32 mira220_mbus_color_formats[] = {
+	MEDIA_BUS_FMT_SGRBG12_1X12,
+	MEDIA_BUS_FMT_SRGGB12_1X12,
+	MEDIA_BUS_FMT_SBGGR12_1X12,
+	MEDIA_BUS_FMT_SGBRG12_1X12,
+
+	MEDIA_BUS_FMT_SGRBG10_1X10,
+	MEDIA_BUS_FMT_SRGGB10_1X10,
+	MEDIA_BUS_FMT_SBGGR10_1X10,
+	MEDIA_BUS_FMT_SGBRG10_1X10,
+
+	MEDIA_BUS_FMT_SGRBG8_1X8,
+	MEDIA_BUS_FMT_SRGGB8_1X8,
+	MEDIA_BUS_FMT_SBGGR8_1X8,
+	MEDIA_BUS_FMT_SGBRG8_1X8,
+};
+
+static const u32 mira220_mbus_mono_formats[] = {
+	MEDIA_BUS_FMT_Y12_1X12,
+	MEDIA_BUS_FMT_Y10_1X10,
+	MEDIA_BUS_FMT_Y8_1X8,
+};
+
+#define MIRA220_FLIP_FORMAT_MASK	0x3
+static inline unsigned int mira220_flip_format_index(struct mira220 *mira220,
+						     unsigned int index)
+{
+	return (index & ~MIRA220_FLIP_FORMAT_MASK)	|
+	       (mira220->vflip->val ? BIT(1) : 0)	|
+	       (mira220->hflip->val ? BIT(0) : 0);
+}
+
+
+/* Power/clock management functions */
+static int mira220_power_on(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct mira220 *mira220 = to_mira220(sd);
+	int ret;
+
+	ret = regulator_bulk_enable(ARRAY_SIZE(mira220_supplies),
+				    mira220->supplies);
+	if (ret) {
+		dev_err(&client->dev, "%s: failed to enable regulators\n",
+			__func__);
+		return ret;
+	}
+
+	ret = clk_prepare_enable(mira220->xclk);
+	if (ret) {
+		dev_err(&client->dev, "%s: failed to enable clock\n", __func__);
+		goto reg_off;
+	}
+
+	gpiod_set_value_cansleep(mira220->reset_gpio, 0);
+
+	return 0;
+
+reg_off:
+	regulator_bulk_disable(ARRAY_SIZE(mira220_supplies), mira220->supplies);
+	return ret;
+}
+
+static int mira220_power_off(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct mira220 *mira220 = to_mira220(sd);
+
+	gpiod_set_value_cansleep(mira220->reset_gpio, 1);
+	clk_disable_unprepare(mira220->xclk);
+	regulator_bulk_disable(ARRAY_SIZE(mira220_supplies), mira220->supplies);
+
+	return 0;
+}
+
+static int mira220_write_start_streaming_regs(struct mira220 *mira220)
+{
+	int ret = 0;
+
+	/* Setting master control */
+	cci_write(mira220->regmap, MIRA220_IMAGER_STATE_REG,
+		  MIRA220_IMAGER_STATE_MASTER_CONTROL, &ret);
+
+	/* Enable continuous streaming */
+	cci_write(mira220->regmap, MIRA220_IMAGER_RUN_CONT_REG,
+		  MIRA220_IMAGER_RUN_CONT_ENABLE, &ret);
+	cci_write(mira220->regmap, MIRA220_IMAGER_RUN_REG,
+		  MIRA220_IMAGER_RUN_START, &ret);
+
+	return ret;
+}
+
+static int mira220_write_stop_streaming_regs(struct mira220 *mira220)
+{
+	int ret = 0;
+
+	cci_write(mira220->regmap, MIRA220_IMAGER_STATE_REG,
+		  MIRA220_IMAGER_STATE_STOP_AT_ROW, &ret);
+	cci_write(mira220->regmap, MIRA220_IMAGER_RUN_REG,
+		  MIRA220_IMAGER_RUN_STOP, &ret);
+
+	return ret;
+}
+
+/*
+ * Returns the exposure in lines. Calculation is baded on Mira220 datasheet
+ * Section 9.2.
+ */
+static u32 mira220_calc_exposure(struct mira220 *mira220, u32 height, u32 vblank)
+{
+	u32 max_exposure = (height + vblank) -
+			   (MIRA220_GLOB_NUM_CLK_CYCLES / mira220->row_length);
+
+	return min(max_exposure, MIRA220_MAX_EXPOSURE);
+}
+
+static u32 mira220_calc_min_vblank(struct mira220 *mira220)
+{
+	return (MIRA220_GLOB_NUM_CLK_CYCLES / mira220->row_length)
+	       + MIRA220_MIN_VBLANK_MARGIN;
+}
+
+static int mira220_write_exposure_reg(struct mira220 *mira220, u32 exposure)
+{
+	struct i2c_client *const client = v4l2_get_subdevdata(&mira220->sd);
+	int ret;
+
+	ret = cci_write(mira220->regmap, MIRA220_EXP_TIME_REG, exposure, NULL);
+	if (ret) {
+		dev_err_ratelimited(&client->dev,
+				    "Error setting exposure time to %d",
+				    exposure);
+		return ret;
+	}
+
+	return 0;
+}
+
+/* Get bayer order based on flip setting. */
+static u32 mira220_get_format_code(struct mira220 *mira220, u32 code)
+{
+	unsigned int i;
+
+	if (mira220_is_mono(mira220)) {
+		for (i = 0; i < ARRAY_SIZE(mira220_mbus_mono_formats); i++) {
+			if (mira220_mbus_mono_formats[i] == code)
+				return mira220_mbus_mono_formats[i];
+		}
+
+		return mira220_mbus_mono_formats[0];
+	}
+
+	for (i = 0; i < ARRAY_SIZE(mira220_mbus_color_formats); i++) {
+		if (mira220_mbus_color_formats[i] == code)
+			break;
+	}
+	if (i >= ARRAY_SIZE(mira220_mbus_color_formats))
+		i = 0;
+
+	i = mira220_flip_format_index(mira220, i);
+
+	return mira220_mbus_color_formats[i];
+}
+
+static int mira220_set_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct mira220 *mira220 =
+		container_of(ctrl->handler, struct mira220, ctrl_handler);
+	struct i2c_client *client = v4l2_get_subdevdata(&mira220->sd);
+	struct v4l2_mbus_framefmt *format;
+	struct v4l2_subdev_state *state;
+	int ret = 0;
+
+	state = v4l2_subdev_get_locked_active_state(&mira220->sd);
+	format = v4l2_subdev_state_get_format(state, 0);
+
+	if (ctrl->id == V4L2_CID_VBLANK) {
+		int exposure_max, exposure_def;
+
+		/* Update max exposure while meeting expected vblanking */
+		exposure_max = mira220_calc_exposure(mira220, format->height,
+						     ctrl->val);
+		exposure_def = min(exposure_max, MIRA220_DEFAULT_EXPOSURE);
+		ret = __v4l2_ctrl_modify_range(mira220->exposure,
+					       mira220->exposure->minimum,
+					       exposure_max,
+					       mira220->exposure->step,
+					       exposure_def);
+		if (ret)
+			return ret;
+	}
+
+	/* Update the format code to adjust it to the new flip state. */
+	if (ctrl->id == V4L2_CID_VFLIP || ctrl->id == V4L2_CID_HFLIP)
+		format->code = mira220_get_format_code(mira220, format->code);
+
+	if (!pm_runtime_get_if_in_use(&client->dev))
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_EXPOSURE:
+		ret = mira220_write_exposure_reg(mira220, ctrl->val);
+		break;
+	case V4L2_CID_TEST_PATTERN:
+		ret = cci_write(mira220->regmap, MIRA220_REG_TEST_PATTERN,
+				mira220_test_pattern_val[ctrl->val], NULL);
+		break;
+	case V4L2_CID_HFLIP:
+		ret = cci_write(mira220->regmap, MIRA220_HFLIP_REG,
+				mira220->hflip->val, NULL);
+		break;
+	case V4L2_CID_VFLIP:
+		ret = cci_write(mira220->regmap, MIRA220_VFLIP_REG,
+				mira220->vflip->val, NULL);
+		break;
+	case V4L2_CID_VBLANK:
+		ret = cci_write(mira220->regmap, MIRA220_VBLANK_REG, ctrl->val,
+				NULL);
+		break;
+	default:
+		dev_info(&client->dev,
+			 "ctrl(id:0x%x,val:0x%x) is not handled\n", ctrl->id,
+			 ctrl->val);
+		ret = -EINVAL;
+		break;
+	}
+
+	pm_runtime_put_autosuspend(&client->dev);
+
+	return ret;
+}
+
+static const struct v4l2_ctrl_ops mira220_ctrl_ops = {
+	.s_ctrl = mira220_set_ctrl,
+};
+
+static void mira220_update_pad_format(struct mira220 *mira220,
+				      struct v4l2_mbus_framefmt *fmt, u32 code)
+{
+	/* Bayer order varies with flips */
+	fmt->code = mira220_get_format_code(mira220, code);
+	/*
+	 * The mira220 sensor doesn't support binning/skipping but only
+	 * cropping to reduce the frame size so the correct API to configure
+	 * windowing is the .set_selection one, while format is fixed to
+	 * the full resolution 1600x1400 one.
+	 */
+	fmt->width = MIRA220_PIXEL_ARRAY_WIDTH;
+	fmt->height = MIRA220_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 mira220_set_pad_format(struct v4l2_subdev *sd,
+				  struct v4l2_subdev_state *state,
+				  struct v4l2_subdev_format *fmt)
+{
+	struct mira220 *mira220 = to_mira220(sd);
+	struct v4l2_rect *crop;
+	u32 min_vblank;
+	int ret;
+
+	mira220_update_pad_format(mira220, &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 = MIRA220_PIXEL_ARRAY_LEFT;
+	crop->top = MIRA220_PIXEL_ARRAY_TOP;
+
+	if (fmt->which == V4L2_SUBDEV_FORMAT_TRY)
+		return 0;
+
+	/* Update vblank based on new mode. */
+	min_vblank = mira220_calc_min_vblank(mira220);
+	ret = __v4l2_ctrl_modify_range(mira220->vblank, min_vblank,
+				       MIRA220_MAX_VBLANK, 1, min_vblank);
+	if (ret)
+		return ret;
+
+	return __v4l2_ctrl_s_ctrl(mira220->vblank, min_vblank);
+}
+
+static int mira220_enum_mbus_code(struct v4l2_subdev *sd,
+				  struct v4l2_subdev_state *state,
+				  struct v4l2_subdev_mbus_code_enum *code)
+{
+	struct mira220 *mira220 = to_mira220(sd);
+	bool mono = mira220_is_mono(mira220);
+	unsigned int num_formats;
+	unsigned int index;
+	const u32 *codes;
+
+	num_formats = mono ? ARRAY_SIZE(mira220_mbus_mono_formats) :
+			     (ARRAY_SIZE(mira220_mbus_color_formats) / 4);
+
+	if (code->index >= num_formats)
+		return -EINVAL;
+
+	index = mono ? code->index : (code->index * 4);
+	codes = mono ? mira220_mbus_mono_formats : mira220_mbus_color_formats;
+	code->code = mira220_get_format_code(mira220, codes[index]);
+
+	return 0;
+}
+
+static int mira220_enum_frame_size(struct v4l2_subdev *sd,
+				   struct v4l2_subdev_state *state,
+				   struct v4l2_subdev_frame_size_enum *fse)
+{
+	struct mira220 *mira220 = to_mira220(sd);
+	u32 code;
+
+	if (fse->index)
+		return -EINVAL;
+
+	code = mira220_get_format_code(mira220, fse->code);
+	if (fse->code != code)
+		return -EINVAL;
+
+	fse->min_width = MIRA220_PIXEL_ARRAY_WIDTH;
+	fse->max_width = fse->min_width;
+	fse->min_height = MIRA220_PIXEL_ARRAY_HEIGHT;
+	fse->max_height = fse->min_height;
+
+	return 0;
+}
+
+static int mira220_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_SGRBG12_1X12,
+			.width = MIRA220_PIXEL_ARRAY_WIDTH,
+			.height = MIRA220_PIXEL_ARRAY_HEIGHT,
+		},
+	};
+
+	mira220_set_pad_format(sd, state, &fmt);
+
+	return 0;
+}
+
+static int mira220_set_bus_config(struct mira220 *mira220)
+{
+	int ret = 0;
+
+	cci_write(mira220->regmap, MIRA220_MIPI_LANES_REG,
+		  mira220->lanes - 1, &ret);
+
+	cci_write(mira220->regmap, MIRA220_MIPI_CLK_MODE_REG,
+		  MIRA220_MIPI_CLK_CONTINUOUS, &ret);
+
+	return ret;
+}
+
+static int mira220_set_framefmt(struct mira220 *mira220,
+				struct v4l2_subdev_state *state)
+{
+	const struct v4l2_mbus_framefmt *format;
+	u32 top, left;
+	int ret = 0;
+
+	format = v4l2_subdev_state_get_format(state, 0);
+
+	/* Program the image format bit order, bit depth and CSI-2 data type */
+
+	ret = cci_write(mira220->regmap, MIRA220_BIT_ORDER_REG,
+			MIRA220_BIT_ORDER_NORMAL, NULL);
+	if (ret)
+		return ret;
+
+	switch (format->code) {
+	case MEDIA_BUS_FMT_Y8_1X8:
+	case MEDIA_BUS_FMT_SRGGB8_1X8:
+	case MEDIA_BUS_FMT_SGRBG8_1X8:
+	case MEDIA_BUS_FMT_SGBRG8_1X8:
+	case MEDIA_BUS_FMT_SBGGR8_1X8:
+		cci_write(mira220->regmap, MIRA220_BIT_DEPTH_REG,
+			  MIRA220_BIT_DEPTH_8_BIT, &ret);
+		cci_write(mira220->regmap, MIRA220_CSI_DATA_TYPE_REG,
+			  MIRA220_CSI_DATA_TYPE_8_BIT, &ret);
+		break;
+	case MEDIA_BUS_FMT_Y10_1X10:
+	case MEDIA_BUS_FMT_SRGGB10_1X10:
+	case MEDIA_BUS_FMT_SGRBG10_1X10:
+	case MEDIA_BUS_FMT_SGBRG10_1X10:
+	case MEDIA_BUS_FMT_SBGGR10_1X10:
+		cci_write(mira220->regmap, MIRA220_BIT_DEPTH_REG,
+			  MIRA220_BIT_DEPTH_10_BIT, &ret);
+		cci_write(mira220->regmap, MIRA220_CSI_DATA_TYPE_REG,
+			  MIRA220_CSI_DATA_TYPE_10_BIT, &ret);
+
+		break;
+	case MEDIA_BUS_FMT_Y12_1X12:
+	case MEDIA_BUS_FMT_SGRBG12_1X12:
+	case MEDIA_BUS_FMT_SGBRG12_1X12:
+	case MEDIA_BUS_FMT_SBGGR12_1X12:
+	case MEDIA_BUS_FMT_SRGGB12_1X12:
+		cci_write(mira220->regmap, MIRA220_BIT_DEPTH_REG,
+			  MIRA220_BIT_DEPTH_12_BIT, &ret);
+		cci_write(mira220->regmap, MIRA220_CSI_DATA_TYPE_REG,
+			  MIRA220_CSI_DATA_TYPE_12_BIT, &ret);
+
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+	if (ret)
+		return ret;
+
+	/* Program the sensor's row length. */
+	ret = cci_write(mira220->regmap, MIRA220_REG_ROW_LENGTH,
+			mira220->row_length, NULL);
+	if (ret)
+		return ret;
+
+	/*
+	 * Program the ROI area, centered in the sensor's pixel array.
+	 *
+	 * TODO: Use the crop rectangle sizes once the driver is ported to the
+	 * new RAW camera sensor model.
+	 */
+	top = (MIRA220_PIXEL_ARRAY_HEIGHT - format->height) / 2;
+	left = (MIRA220_PIXEL_ARRAY_WIDTH - format->width) / 2;
+
+	cci_write(mira220->regmap, MIRA220_VSTART_REG, top, &ret);
+	cci_write(mira220->regmap, MIRA220_HSTART_REG,
+		  left & MIRA220_HSTART_MASK, &ret);
+	cci_write(mira220->regmap, MIRA220_VSIZE_REG, format->height, &ret);
+	cci_write(mira220->regmap, MIRA220_HSIZE_REG,
+		  (format->width / 2) & MIRA220_HSIZE_MASK, &ret);
+	cci_write(mira220->regmap, MIRA220_MIPI_HSIZE_REG, format->width, &ret);
+
+	return ret;
+}
+
+static int mira220_get_selection(struct v4l2_subdev *sd,
+				 struct v4l2_subdev_state *state,
+				 struct v4l2_subdev_selection *sel)
+{
+	switch (sel->target) {
+	case V4L2_SEL_TGT_CROP:
+		sel->r = *v4l2_subdev_state_get_crop(state, 0);
+		return 0;
+
+	case V4L2_SEL_TGT_NATIVE_SIZE:
+		sel->r.top = 0;
+		sel->r.left = 0;
+		sel->r.width = MIRA220_NATIVE_WIDTH;
+		sel->r.height = MIRA220_NATIVE_HEIGHT;
+		return 0;
+
+	case V4L2_SEL_TGT_CROP_DEFAULT:
+	case V4L2_SEL_TGT_CROP_BOUNDS:
+		sel->r.top = MIRA220_PIXEL_ARRAY_TOP;
+		sel->r.left = MIRA220_PIXEL_ARRAY_LEFT;
+		sel->r.width = MIRA220_PIXEL_ARRAY_WIDTH;
+		sel->r.height = MIRA220_PIXEL_ARRAY_HEIGHT;
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+/* OTP power on */
+static int mira220_otp_power_on(struct mira220 *mira220)
+{
+	int ret = cci_write(mira220->regmap, MIRA220_OTP_CMD_REG,
+			    MIRA220_OTP_CMD_UP, NULL);
+	fsleep(100);
+
+	return ret;
+}
+
+/* OTP power off */
+static int mira220_otp_power_off(struct mira220 *mira220)
+{
+	return cci_write(mira220->regmap, MIRA220_OTP_CMD_REG,
+			 MIRA220_OTP_CMD_DOWN, NULL);
+}
+
+/* OTP power on */
+static int mira220_otp_read(struct mira220 *mira220, u8 addr, u8 offset,
+			    u8 *val, int *err)
+{
+	u64 readback;
+	int ret = 0;
+
+	if (err && *err)
+		return *err;
+
+	cci_write(mira220->regmap, CCI_REG8(0x0086), addr, &ret);
+	cci_write(mira220->regmap, CCI_REG8(0x0080), 0x02, &ret);
+	cci_read(mira220->regmap, CCI_REG8(0x0082 + offset), &readback, &ret);
+	if (ret)
+		goto out;
+
+	*val = readback & 0xFF;
+
+	return 0;
+
+out:
+	if (err)
+		*err = ret;
+	return ret;
+}
+
+/* Verify chip ID, module version, unique ID and variant */
+static int mira220_identify_module(struct mira220 *mira220)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&mira220->sd);
+	u8 b0, b1, b2, b3, b4, b5, b6, b7;
+	u32 variant;
+	int ret;
+	u8 val;
+	u8 id;
+
+	ret = mira220_otp_power_on(mira220);
+	if (ret)
+		return ret;
+
+	/* Log module version checks */
+	mira220_otp_read(mira220, 0x0d, 0, &val, &ret);
+	mira220_otp_read(mira220, 0x1e, 0, &val, &ret);
+	if (ret)
+		goto err_power_off;
+
+	/* Check unique module hardware ID version */
+	ret = mira220_otp_read(mira220, 0x3a, 0, &id, NULL);
+	if (ret)
+		goto err_power_off;
+
+	if (id < 1 || id > 2) {
+		dev_err(&client->dev,
+			"Read OTP 0x3a, id must be 1 or 2, but got: 0x%02x\n", id);
+		ret = -EINVAL;
+		goto err_power_off;
+	}
+
+	/* Read Unique ID bytes sequentially using the correct 4-argument signature */
+	mira220_otp_read(mira220, 0x25, 0, &b0, &ret);
+	mira220_otp_read(mira220, 0x1e, 0, &b1, &ret);
+	mira220_otp_read(mira220, 0x1e, 1, &b2, &ret);
+	mira220_otp_read(mira220, 0x1e, 2, &b3, &ret);
+	mira220_otp_read(mira220, 0x1d, 0, &b4, &ret);
+	mira220_otp_read(mira220, 0x1d, 1, &b5, &ret);
+	mira220_otp_read(mira220, 0x1d, 2, &b6, &ret);
+	mira220_otp_read(mira220, 0x1d, 3, &b7, &ret);
+	if (ret)
+		goto err_power_off;
+
+	dev_dbg(&client->dev, "Unique ID: %02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X\n",
+		b7, b6, b5, b4, b3, b2, b1, b0);
+
+	mira220_otp_read(mira220, 0x3e, 0, &b0, &ret);
+	mira220_otp_read(mira220, 0x3e, 1, &b1, &ret);
+	mira220_otp_read(mira220, 0x3e, 2, &b2, &ret);
+	mira220_otp_read(mira220, 0x3e, 3, &b3, &ret);
+	if (ret)
+		goto err_power_off;
+
+	variant = b0 | b1 << 8 | b2 << 16 | b3 << 24;
+
+	switch (variant) {
+	case MIRA220_MONO_PLAIN_PF:
+	case MIRA220_MONO_PLAIN_NO_PF:
+	case MIRA220_MONO_AR_PF:
+		dev_dbg(&client->dev, "Mira220 MONO variant: %x\n", variant);
+		mira220->variant = MIRA220_VARIANT_MONO;
+		break;
+	case MIRA220_RGB_PLAIN_PF:
+	case MIRA220_RGB_PLAIN_NO_PF:
+	case MIRA220_RGB_AR_PF:
+		dev_dbg(&client->dev, "Mira220 RGB variant: %x\n", variant);
+		mira220->variant = MIRA220_VARIANT_RGB;
+		break;
+	default:
+		dev_info(&client->dev,
+			 "Unsupported mira220 variant %x; default to RGB\n",
+			 variant);
+		mira220->variant = MIRA220_VARIANT_RGB;
+		break;
+	}
+
+err_power_off:
+	mira220_otp_power_off(mira220);
+	return ret;
+}
+
+static int mira220_otp_restore(struct mira220 *mira220)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&mira220->sd);
+	static const u16 reg_list[] = {
+		0x4015, 0x4016, 0x4017, 0x4018, 0x403B, 0x4040, 0x4041, 0x4042,
+		0x402a, 0x4029, 0x4009
+	};
+	u8 val;
+	int ret;
+
+	ret = mira220_otp_power_on(mira220);
+	if (ret)
+		return ret;
+
+	/* Write OTP to image sensor when programmed */
+	ret = mira220_otp_read(mira220, 0x1d, 0, &val, NULL);
+	if (ret < 0) {
+		dev_err(&client->dev, "Failed to read OTP programming status\n");
+		goto out;
+	}
+
+	if (val == 0xff) {
+		dev_info(&client->dev, "OTP not programmed, skipping calibration\n");
+		return mira220_otp_power_off(mira220);
+	}
+
+	for (unsigned int i = 0; i < ARRAY_SIZE(reg_list); i++) {
+		ret = mira220_otp_read(mira220, i, 0, &val, NULL);
+		if (ret < 0) {
+			dev_err(&client->dev, "Failed to read OTP address %d\n", i);
+			goto out;
+		}
+		ret = cci_write(mira220->regmap, CCI_REG8(reg_list[i]), val, NULL);
+		if (ret < 0) {
+			dev_err(&client->dev,
+				"Failed to write register 0x%04x\n", reg_list[i]);
+			goto out;
+		}
+		dev_dbg(&client->dev, "OTP CALIBRATION: 0x%04x, val=0x%02x\n",
+			reg_list[i], val);
+	}
+
+	ret = mira220_otp_read(mira220, 0x0d, 0, &val, NULL);
+	if (ret < 0) {
+		dev_err(&client->dev, "Failed to read OTP address 0x0d\n");
+		goto out;
+	}
+
+	ret = cci_write(mira220->regmap, CCI_REG8(0x403e), val, NULL);
+	if (ret < 0)
+		dev_err(&client->dev, "Failed to write register 0x403e\n");
+
+out:
+	mira220_otp_power_off(mira220);
+	return ret;
+}
+
+static int mira220_enable_streams(struct v4l2_subdev *sd,
+				  struct v4l2_subdev_state *state, u32 pad,
+				  u64 streams_mask)
+{
+	struct mira220 *mira220 = to_mira220(sd);
+	struct i2c_client *client = v4l2_get_subdevdata(&mira220->sd);
+	int ret;
+
+	ret = pm_runtime_resume_and_get(&client->dev);
+	if (ret < 0)
+		return ret;
+
+	/*
+	 * Apply default values of current mode. Stop streaming before
+	 * uploading register sequence.
+	 */
+	ret = mira220_write_stop_streaming_regs(mira220);
+	if (ret)
+		goto err_rpm_put;
+
+	ret = cci_multi_reg_write(mira220->regmap, mira220_init_reg_list,
+				  ARRAY_SIZE(mira220_init_reg_list), NULL);
+	if (ret)
+		goto err_rpm_put;
+
+	ret = mira220_otp_restore(mira220);
+	if (ret)
+		goto err_rpm_put;
+
+	ret = mira220_set_bus_config(mira220);
+	if (ret)
+		goto err_rpm_put;
+
+	ret = mira220_set_framefmt(mira220, state);
+	if (ret)
+		goto err_rpm_put;
+
+	/* Apply customized values from user */
+	ret = __v4l2_ctrl_handler_setup(mira220->sd.ctrl_handler);
+	if (ret)
+		goto err_rpm_put;
+
+	ret = mira220_write_start_streaming_regs(mira220);
+	if (ret)
+		goto err_rpm_put;
+
+	/* vflip and hflip cannot change during streaming */
+	__v4l2_ctrl_grab(mira220->hflip, true);
+	__v4l2_ctrl_grab(mira220->vflip, true);
+
+	return 0;
+
+err_rpm_put:
+	pm_runtime_put_autosuspend(&client->dev);
+	return ret;
+}
+
+static int mira220_disable_streams(struct v4l2_subdev *sd,
+				   struct v4l2_subdev_state *state, u32 pad,
+				   u64 streams_mask)
+{
+	struct mira220 *mira220 = to_mira220(sd);
+	struct i2c_client *client = v4l2_get_subdevdata(&mira220->sd);
+
+	mira220_write_stop_streaming_regs(mira220);
+
+	__v4l2_ctrl_grab(mira220->hflip, false);
+	__v4l2_ctrl_grab(mira220->vflip, false);
+
+	pm_runtime_put_autosuspend(&client->dev);
+
+	return 0;
+}
+
+static int mira220_get_regulators(struct mira220 *mira220)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&mira220->sd);
+
+	for (unsigned int i = 0; i < ARRAY_SIZE(mira220_supplies); i++)
+		mira220->supplies[i].supply = mira220_supplies[i];
+
+	return devm_regulator_bulk_get(&client->dev,
+				       ARRAY_SIZE(mira220_supplies),
+				       mira220->supplies);
+}
+
+static const struct v4l2_subdev_video_ops mira220_video_ops = {
+	.s_stream = v4l2_subdev_s_stream_helper,
+};
+
+static const struct v4l2_subdev_pad_ops mira220_pad_ops = {
+	.enum_mbus_code = mira220_enum_mbus_code,
+	.get_fmt = v4l2_subdev_get_fmt,
+	.set_fmt = mira220_set_pad_format,
+	.get_selection = mira220_get_selection,
+	.enum_frame_size = mira220_enum_frame_size,
+	.enable_streams = mira220_enable_streams,
+	.disable_streams = mira220_disable_streams,
+};
+
+static const struct v4l2_subdev_ops mira220_subdev_ops = {
+	.video = &mira220_video_ops,
+	.pad = &mira220_pad_ops,
+};
+
+static const struct v4l2_subdev_internal_ops mira220_internal_ops = {
+	.init_state = mira220_init_state,
+};
+
+/* Initialize control handlers */
+static int mira220_init_controls(struct mira220 *mira220)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&mira220->sd);
+	struct v4l2_ctrl_handler *ctrl_hdlr;
+	struct v4l2_fwnode_device_properties props;
+	struct v4l2_ctrl *ctrl;
+	u32 max_exposure = 0;
+	u32 min_vblank;
+	u32 hblank_val;
+	int ret;
+
+	ctrl_hdlr = &mira220->ctrl_handler;
+	ret = v4l2_ctrl_handler_init(ctrl_hdlr, 10);
+	if (ret)
+		return ret;
+
+	/* By default, PIXEL_RATE is read only */
+	v4l2_ctrl_new_std(ctrl_hdlr, &mira220_ctrl_ops, V4L2_CID_PIXEL_RATE,
+			  MIRA220_PIXEL_RATE, MIRA220_PIXEL_RATE, 1,
+			  MIRA220_PIXEL_RATE);
+
+	min_vblank = mira220_calc_min_vblank(mira220);
+	mira220->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &mira220_ctrl_ops,
+					    V4L2_CID_VBLANK,
+					    min_vblank, MIRA220_MAX_VBLANK, 1,
+					    min_vblank);
+
+	ctrl = v4l2_ctrl_new_int_menu(ctrl_hdlr, NULL, V4L2_CID_LINK_FREQ,
+				      0, 0, &mira220_link_freqs[0]);
+	if (ctrl)
+		ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+	/*
+	 * Scale hblank according to the number of enabled data lanes to match
+	 * row_length.
+	 */
+	hblank_val = MIRA220_LLP_1600x1400_304 * (2 / mira220->lanes)
+		   - MIRA220_PIXEL_ARRAY_WIDTH;
+	ctrl = v4l2_ctrl_new_std(ctrl_hdlr, NULL, V4L2_CID_HBLANK, hblank_val,
+				 hblank_val, 1, hblank_val);
+	if (ctrl)
+		ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+	/* Max exposure is determined by vblank + vsize and Tglob. */
+	max_exposure = mira220_calc_exposure(mira220,
+					     MIRA220_PIXEL_ARRAY_HEIGHT,
+					     min_vblank);
+
+	mira220->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &mira220_ctrl_ops,
+					      V4L2_CID_EXPOSURE,
+					      MIRA220_EXPOSURE_MIN,
+					      max_exposure, 1,
+					      MIRA220_DEFAULT_EXPOSURE);
+
+	v4l2_ctrl_new_std(ctrl_hdlr, NULL, V4L2_CID_ANALOGUE_GAIN,
+			  MIRA220_ANALOG_GAIN_MIN, MIRA220_ANALOG_GAIN_MAX,
+			  MIRA220_ANALOG_GAIN_STEP,
+			  MIRA220_ANALOG_GAIN_DEFAULT);
+
+	mira220->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &mira220_ctrl_ops,
+					   V4L2_CID_HFLIP, 0, 1, 1, 0);
+	if (mira220->hflip)
+		mira220->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT;
+
+	mira220->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &mira220_ctrl_ops,
+					   V4L2_CID_VFLIP, 0, 1, 1, 0);
+	if (mira220->vflip)
+		mira220->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT;
+
+	v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &mira220_ctrl_ops,
+				     V4L2_CID_TEST_PATTERN,
+				     ARRAY_SIZE(mira220_test_pattern_menu) - 1,
+				     0, 0, mira220_test_pattern_menu);
+
+	if (ctrl_hdlr->error) {
+		ret = ctrl_hdlr->error;
+		goto error;
+	}
+
+	ret = v4l2_fwnode_device_parse(&client->dev, &props);
+	if (ret)
+		goto error;
+
+	ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &mira220_ctrl_ops,
+					      &props);
+	if (ret)
+		goto error;
+
+	mira220->sd.ctrl_handler = ctrl_hdlr;
+
+	return 0;
+
+error:
+	v4l2_ctrl_handler_free(ctrl_hdlr);
+
+	return ret;
+}
+
+static int mira220_parse_endpoint(struct device *dev, struct mira220 *mira220)
+{
+	struct fwnode_handle *endpoint;
+	struct v4l2_fwnode_endpoint ep_cfg = {
+		.bus_type = V4L2_MBUS_CSI2_DPHY
+	};
+	int ret = 0;
+
+	endpoint = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, 0);
+	if (!endpoint) {
+		dev_err(dev, "Endpoint node not found\n");
+		return -EINVAL;
+	}
+
+	if (v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep_cfg)) {
+		ret = -EINVAL;
+		dev_err(dev, "Failed to parse endpoint\n");
+		goto error_out;
+	}
+
+	/* Non-continuous mode not implemented. */
+	if (ep_cfg.bus.mipi_csi2.flags & V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK) {
+		dev_warn(dev, "clock non-continuous mode not supported\n");
+		ret = -EINVAL;
+		goto error_out;
+	}
+
+	if (ep_cfg.nr_of_link_frequencies > ARRAY_SIZE(mira220_link_freqs)) {
+		ret = -EINVAL;
+		dev_err(dev, "Unsupported number of link_frequencies: %u\n",
+			ep_cfg.nr_of_link_frequencies);
+		goto error_out;
+	}
+
+	if (ep_cfg.link_frequencies[0] != MIRA220_LINK_FREQ_750M) {
+		ret = -EINVAL;
+		dev_err(dev, "Unsupported link_frequency: %llu\n",
+			ep_cfg.link_frequencies[0]);
+		goto error_out;
+	}
+
+	/* Check the number of MIPI CSI2 data lanes */
+	if (ep_cfg.bus.mipi_csi2.num_data_lanes != 1 &&
+	    ep_cfg.bus.mipi_csi2.num_data_lanes != 2) {
+		ret = -EINVAL;
+		dev_err(dev, "%u data lanes are not supported\n",
+			ep_cfg.bus.mipi_csi2.num_data_lanes);
+		goto error_out;
+	}
+
+	mira220->lanes = ep_cfg.bus.mipi_csi2.num_data_lanes;
+	mira220->row_length = MIRA220_ROW_LENGTH_MIN * (2 / mira220->lanes);
+
+error_out:
+	v4l2_fwnode_endpoint_free(&ep_cfg);
+	fwnode_handle_put(endpoint);
+
+	return ret;
+}
+
+static int mira220_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct mira220 *mira220;
+	int ret;
+
+	mira220 = devm_kzalloc(&client->dev, sizeof(*mira220), GFP_KERNEL);
+	if (!mira220)
+		return -ENOMEM;
+
+	v4l2_i2c_subdev_init(&mira220->sd, client, &mira220_subdev_ops);
+	mira220->sd.internal_ops = &mira220_internal_ops;
+
+	ret = mira220_parse_endpoint(dev, mira220);
+	if (ret)
+		return ret;
+
+	mira220->regmap = devm_cci_regmap_init_i2c(client, 16);
+	if (IS_ERR(mira220->regmap))
+		return dev_err_probe(dev, PTR_ERR(mira220->regmap),
+				     "failed to initialize CCI\n");
+
+	/* Get system clock (xclk) */
+	mira220->xclk = devm_v4l2_sensor_clk_get(dev, NULL);
+	if (IS_ERR(mira220->xclk))
+		return dev_err_probe(dev, PTR_ERR(mira220->xclk),
+				     "failed to get xclk\n");
+
+	mira220->xclk_freq = clk_get_rate(mira220->xclk);
+	if (mira220->xclk_freq != MIRA220_SUPPORTED_XCLK_FREQ) {
+		dev_err(dev, "xclk frequency not supported: %d Hz\n",
+			mira220->xclk_freq);
+		return -EINVAL;
+	}
+
+	ret = mira220_get_regulators(mira220);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to get regulators\n");
+
+	mira220->reset_gpio = devm_gpiod_get_optional(dev, "reset",
+						      GPIOD_OUT_HIGH);
+	if (IS_ERR(mira220->reset_gpio))
+		return dev_err_probe(dev, PTR_ERR(mira220->reset_gpio),
+				     "failed to get reset gpio\n");
+
+	ret = mira220_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 = mira220_identify_module(mira220);
+	if (ret)
+		goto error_power_off;
+
+	ret = mira220_init_controls(mira220);
+	if (ret)
+		goto error_power_off;
+
+	/* Initialize subdev */
+	mira220->sd.internal_ops = &mira220_internal_ops;
+	mira220->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+	mira220->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
+
+	/* Initialize source pads */
+	mira220->pad.flags = MEDIA_PAD_FL_SOURCE;
+
+	ret = media_entity_pads_init(&mira220->sd.entity, 1, &mira220->pad);
+	if (ret) {
+		dev_err_probe(dev, ret, "failed to init entity pads\n");
+		goto error_handler_free;
+	}
+
+	mira220->sd.state_lock = mira220->ctrl_handler.lock;
+	ret = v4l2_subdev_init_finalize(&mira220->sd);
+	if (ret < 0) {
+		dev_err_probe(dev, ret, "subdev init error\n");
+		goto error_media_entity;
+	}
+
+	ret = v4l2_async_register_subdev_sensor(&mira220->sd);
+	if (ret < 0) {
+		dev_err_probe(dev, ret,
+			      "failed to register sensor sub-device\n");
+		goto error_subdev_cleanup;
+	}
+
+	pm_runtime_idle(dev);
+	pm_runtime_set_autosuspend_delay(dev, 1000);
+	pm_runtime_use_autosuspend(dev);
+
+	return 0;
+
+error_subdev_cleanup:
+	v4l2_subdev_cleanup(&mira220->sd);
+error_media_entity:
+	media_entity_cleanup(&mira220->sd.entity);
+error_handler_free:
+	v4l2_ctrl_handler_free(mira220->sd.ctrl_handler);
+error_power_off:
+	pm_runtime_disable(dev);
+	mira220_power_off(dev);
+	pm_runtime_set_suspended(dev);
+	return ret;
+}
+
+static void mira220_remove(struct i2c_client *client)
+{
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct mira220 *mira220 = to_mira220(sd);
+
+	v4l2_async_unregister_subdev(sd);
+	v4l2_subdev_cleanup(&mira220->sd);
+	media_entity_cleanup(&sd->entity);
+
+	v4l2_ctrl_handler_free(mira220->sd.ctrl_handler);
+
+	pm_runtime_disable(&client->dev);
+	if (!pm_runtime_status_suspended(&client->dev))
+		mira220_power_off(&client->dev);
+	pm_runtime_set_suspended(&client->dev);
+}
+
+static const struct dev_pm_ops mira220_pm_ops = {
+	SET_RUNTIME_PM_OPS(mira220_power_off, mira220_power_on, NULL)
+};
+
+static const struct of_device_id mira220_dt_ids[] = {
+	{ .compatible = "ams,mira220" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mira220_dt_ids);
+
+static struct i2c_driver mira220_i2c_driver = {
+	.driver = {
+		.name = "mira220",
+		.of_match_table	= mira220_dt_ids,
+		.pm = pm_ptr(&mira220_pm_ops),
+	},
+	.probe = mira220_probe,
+	.remove = mira220_remove,
+};
+
+module_i2c_driver(mira220_i2c_driver);
+
+MODULE_AUTHOR("Philippe Baetens <philippe.baetens@ams-osram.com>");
+MODULE_DESCRIPTION("ams MIRA220 sensor driver");
+MODULE_LICENSE("GPL");

-- 
2.54.0


^ permalink raw reply related

* [PATCH v8 0/2] media: i2c: Add driver for Mira220
From: Jacopo Mondi @ 2026-07-24 15:36 UTC (permalink / raw)
  To: Philippe Baetens, Mauro Carvalho Chehab, Rob Herring,
	Sakari Ailus, Krzysztof Kozlowski, Conor Dooley
  Cc: Kieran Bingham, Jai Luthra, linux-media, devicetree, linux-kernel,
	Jacopo Mondi, Conor Dooley

Resume the work done by Philippe on Mira 220, available at
https://patchwork.linuxtv.org/project/linux-media/list/?series=18460

The detailed changelog is available on the single patches.

On the current set of supported features: mira220 doesn't support
binning/skipping but only cropping the full sensor array. It also
supports embedded data. It would be an ideal candidate for the new
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.

Thanks
   j

Changes in v8:
- Address Jai's review comments; changelog is per-patch
- Link to v7: https://lore.kernel.org/r/20260722-mira220-v7-0-dd5f1de98cd4@ideasonboard.com

Changes in v7:
- Address Sashiko review comments; changelog is per-patch
- Link to v6: https://lore.kernel.org/r/20260722-mira220-v6-0-203e32c596de@ideasonboard.com

Changes in v6:
- Address Sashiko review comments; changelog is per-patch
- Link to v5: https://lore.kernel.org/r/20260721-mira220-v5-0-533b0ad1065e@ideasonboard.com

Just a note on hblank: prompted by sashiko review I reworked the hblank
calculation procedure to use the full line length (which will make it
easier to use the new Raw Sensor Model) and I've scaled the line lenght
according tot the number of data lanes, to match the row_length
settings, and I tested frame rate for 1 and 2 data lane operations.

I'll report the results here

1 data lane: hblank = 4480

    10fps -> vblank = 4915
    pi@raspberrypi:~$ v4l2-ctl -d /dev/v4l-subdev6 -c vertical_blanking=4915
    2633.788743 (10.00 fps) cam0-stream0 seq: 000210 bytesused: 1920000
    2633.888732 (10.00 fps) cam0-stream0 seq: 000211 bytesused: 1920000
    2633.988712 (10.00 fps) cam0-stream0 seq: 000212 bytesused: 1920000
    2634.088696 (10.00 fps) cam0-stream0 seq: 000213 bytesused: 1920000

    15fps -> vblank = 2810
    pi@raspberrypi:~$ v4l2-ctl -d /dev/v4l-subdev6 -c vertical_blanking=2810
    2690.081596 (15.00 fps) cam0-stream0 seq: 000833 bytesused: 1920000
    2690.148251 (15.00 fps) cam0-stream0 seq: 000834 bytesused: 1920000

    30fps -> blank = 705
    pi@raspberrypi:~$ v4l2-ctl -d /dev/v4l-subdev6 -c vertical_blanking=705
    2732.142696 (29.98 fps) cam0-stream0 seq: 001736 bytesused: 1920000
    2732.176004 (30.02 fps) cam0-stream0 seq: 001737 bytesused: 1920000
    2732.209315 (30.02 fps) cam0-stream0 seq: 001738 bytesused: 1920000

    min_vblank = 14
    2823.028261 (46.07 fps) cam0-stream0 seq: 005180 bytesused: 1920000
    2823.051318 (43.37 fps) cam0-stream0 seq: 005181 bytesused: 1920000
    2823.073032 (46.05 fps) cam0-stream0 seq: 005182 bytesused: 1920000
    2823.096081 (43.39 fps) cam0-stream0 seq: 005183 bytesused: 1920000

2 data lanes: hblank = 1440

    10fps -> vblank = 11231
    pi@raspberrypi:~$ v4l2-ctl -d /dev/v4l-subdev6 -c vertical_blanking=11231
    163.127449 (10.00 fps) cam0-stream0 seq: 000106 bytesused: 1920000
    163.227446 (10.00 fps) cam0-stream0 seq: 000107 bytesused: 1920000
    163.327425 (10.00 fps) cam0-stream0 seq: 000108 bytesused: 1920000

    15fps -> vblank = 7021
    pi@raspberrypi:~$ v4l2-ctl -d /dev/v4l-subdev6 -c vertical_blanking=7021
    206.325595 (15.00 fps) cam0-stream0 seq: 000551 bytesused: 1920000
    206.392256 (15.00 fps) cam0-stream0 seq: 000552 bytesused: 1920000
    206.458943 (15.00 fps) cam0-stream0 seq: 000553 bytesused: 192000

    30fps -> vblank = 2810
    pi@raspberrypi:~$ v4l2-ctl -d /dev/v4l-subdev6 -c vertical_blanking=2810
    244.891759 (30.00 fps) cam0-stream0 seq: 001153 bytesused: 1920000
    244.925094 (30.00 fps) cam0-stream0 seq: 001154 bytesused: 1920000
    244.958439 (29.99 fps) cam0-stream0 seq: 001155 bytesused: 1920000
    244.991746 (30.02 fps) cam0-stream0 seq: 001156 bytesused: 1920000

    60fps -> vblank = 733
    pi@raspberrypi:~$ v4l2-ctl -d /dev/v4l-subdev6 -c vertical_blanking=733
    67.273857 (57.04 fps) cam0-stream0 seq: 007285 bytesused: 1920000
    367.290091 (61.60 fps) cam0-stream0 seq: 007286 bytesused: 1920000
    367.307637 (56.99 fps) cam0-stream0 seq: 007287 bytesused: 1920000
    367.323866 (61.62 fps) cam0-stream0 seq: 007288 bytesused: 1920000

    90 fps -> vblank = 22
    pi@raspberrypi:~$ v4l2-ctl -d /dev/v4l-subdev6 -c vertical_blanking=22
    326.909808 (83.83 fps) cam0-stream0 seq: 003739 bytesused: 1920000
    326.920403 (94.38 fps) cam0-stream0 seq: 003740 bytesused: 1920000
    326.931644 (88.96 fps) cam0-stream0 seq: 003741 bytesused: 1920000

-------------------------------------------------------------------------------
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-subdev6:

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-subdev6 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: 14 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-subdev6: 46, Succeeded: 46, Failed: 0, Warnings: 0
-------------------------------------------------------------------------------

Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
---
Philippe Baetens (2):
      dt-bindings: media: i2c: Add mira220 image sensor
      media: i2c: Add driver for AMS-OSRAM Mira220

 .../devicetree/bindings/media/i2c/ams,mira220.yaml |  101 +
 MAINTAINERS                                        |    8 +
 drivers/media/i2c/Kconfig                          |   12 +
 drivers/media/i2c/Makefile                         |    1 +
 drivers/media/i2c/mira220.c                        | 2080 ++++++++++++++++++++
 5 files changed, 2202 insertions(+)
---
base-commit: a52e6f7923c17a672135b485ffd96fbd72f46267
change-id: 20260721-mira220-03070a173f7c

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


^ permalink raw reply

* [PATCH v8 1/2] dt-bindings: media: i2c: Add mira220 image sensor
From: Jacopo Mondi @ 2026-07-24 15:37 UTC (permalink / raw)
  To: Philippe Baetens, Mauro Carvalho Chehab, Rob Herring,
	Sakari Ailus, Krzysztof Kozlowski, Conor Dooley
  Cc: Kieran Bingham, Jai Luthra, linux-media, devicetree, linux-kernel,
	Jacopo Mondi, Conor Dooley
In-Reply-To: <20260724-mira220-v8-0-d1899ab64709@ideasonboard.com>

From: Philippe Baetens <philippebaetens@gmail.com>

Add bindings for the AMSOSRAM Mira220 image sensor.

Acked-by: Conor Dooley <conor.dooley@microchip.com>
Signed-off-by: Philippe Baetens <philippebaetens@gmail.com>
Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>

---
v7->v8:
- Add Conor's tag

v5->v6:
- sashiko: use 'unevaluatedProperties: false' at the top level to allow
  properties from video-interface-devices.yaml

v4->v5:
- reflow description to 80 cols and drop |-
- remove 'reg' description
- remove 'anyOf' from data-lanes
- remove 'link-frequencies' from required properties
- remove 'clock-continuous: true' as we already allow all properties
  from 'video-interfaces.yaml'
- remove tabs from the example
---
 .../devicetree/bindings/media/i2c/ams,mira220.yaml | 101 +++++++++++++++++++++
 MAINTAINERS                                        |   7 ++
 2 files changed, 108 insertions(+)

diff --git a/Documentation/devicetree/bindings/media/i2c/ams,mira220.yaml b/Documentation/devicetree/bindings/media/i2c/ams,mira220.yaml
new file mode 100644
index 000000000000..d113f7f81ffb
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/i2c/ams,mira220.yaml
@@ -0,0 +1,101 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/i2c/ams,mira220.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: AMS 2.2 MP NIR enhanced global shutter image sensor
+
+maintainers:
+  - Philippe Baetens <philippebaetens@gmail.com>
+
+description:
+  2.2 MP NIR enhanced global shutter image sensor designed for 2D and 3D
+  consumer and industrial machine vision applications. Due to its small size,
+  configurability and high sensitivity both in visual as well as NIR, the
+  Mira220 is well suited for 2D and 3D applications, which include Active Stereo
+  Vision, Structured Light Vision for Robotics and AR/VR.
+
+allOf:
+  - $ref: /schemas/media/video-interface-devices.yaml#
+
+properties:
+  compatible:
+    const: ams,mira220
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    maxItems: 1
+
+  vdig-supply:
+    description:
+      Digital I/O voltage supply, 1.35 volts
+
+  vana-supply:
+    description:
+      Analog1 voltage supply, 2.8 volts
+
+  vddl-supply:
+    description:
+      Digital core voltage supply, 1.8 volts
+
+  reset-gpios:
+    description: Sensor reset (XCLR) 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
+              - const: 2
+
+        required:
+          - data-lanes
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - vdig-supply
+  - vana-supply
+  - vddl-supply
+  - port
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        mira220: camera-sensor@54 {
+            compatible = "ams,mira220";
+            reg = <0x54>;
+            clocks = <&cam1_clk>;
+            vana-supply = <&cam1_reg>;
+            vdig-supply = <&cam_dummy_reg>;
+            vddl-supply = <&cam_dummy_reg>;
+            port {
+                mira220_ep: endpoint {
+                    clock-lanes = <0>;
+                    data-lanes = <1 2>;
+                    link-frequencies = /bits/ 64 <750000000>;
+                };
+            };
+        };
+    };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index febe918d6316..55ac4ec433a6 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1430,6 +1430,13 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/iio/light/ams,as73211.yaml
 F:	drivers/iio/light/as73211.c
 
+AMS MIRA220 DRIVER
+M:	Philippe Baetens <philippebaetens@gmail.com>
+M:	Jacopo Mondi <jacopo.mondi@ideasonboard.com>
+L:	linux-media@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/media/i2c/ams,mira220.yaml
+
 AMT (Automatic Multicast Tunneling)
 M:	Taehee Yoo <ap420073@gmail.com>
 L:	netdev@vger.kernel.org

-- 
2.54.0


^ permalink raw reply related

* Re: [PATCH v8 0/9] Support for block device NVMEM providers
From: Ulf Hansson @ 2026-07-24 15:35 UTC (permalink / raw)
  To: Srinivas Kandagatla
  Cc: Loic Poulain, Ulf Hansson, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Bjorn Andersson, Konrad Dybcio, Jens Axboe,
	Johannes Berg, Jeff Johnson, Bartosz Golaszewski, Marcel Holtmann,
	Luiz Augusto von Dentz, Balakrishna Godavarthi, Rocky Liao,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Andrew Lunn, Heiner Kallweit, Russell King,
	Saravana Kannan, Christian Marangi, linux-mmc, devicetree,
	linux-kernel, linux-arm-msm, linux-block, linux-wireless, ath10k,
	linux-bluetooth, netdev, daniel, Bartosz Golaszewski,
	Krzysztof Kozlowski, Piotr Kwapulinski, Konrad Dybcio
In-Reply-To: <3d954cdd-6dde-4be6-84c9-2f2f7d008f5b@kernel.org>

On Wed, Jul 22, 2026 at 3:56 PM Srinivas Kandagatla <srini@kernel.org> wrote:
>
>
>
> On 7/3/26 2:45 PM, Loic Poulain wrote:
> > On embedded devices, it is common for factory provisioning to store
> > device-specific information, such as Ethernet or WiFi MAC addresses,
> > in a dedicated area of an eMMC partition. This avoids the need for
> > and additional EEPROM/OTP and leverages the persistence of eMMC.
> >
> > One example is the Arduino UNO-Q, where the WiFi MAC address and the
> > Bluetooth Device address are stored in the eMMC Boot1 partition.
> >
> > Until now, accessing this information required a custom bootloader
> > to read the data and inject it into the Device Tree before handing
> > control over to the kernel. This approach is fragile and leads to
> > device-specific workarounds.
> >
> > Rather than adding a new NVMEM provider specifically to the eMMC
> > subsystem, the new support operates at the block layer, allowing any
> > block device to behave like other non-volatile memories such as EEPROM
> > or OTP.
> >
> > This series builds on earlier work by Daniel Golle that enables block
> > devices to act as NVMEM providers:
> > https://lore.kernel.org/all/6061aa4201030b9bb2f8d03ef32a564fdb786ed1.1709667858.git.daniel@makrotopia.org/
> >
> > It also introduces an NVMEM layout description for the Arduino UNO-Q,
> > allowing device-specific data stored in the eMMC Boot1 partition to
> > be accessed in a standard way.
> >
> > WiFi and Ethernet already support retrieving MAC addresses from NVMEM.
> > Bluetooth requires similar support, which is also addressed.
> >
> > Note that this is currently limited to MMC-backed block devices, as
> > only the MMC core associates a firmware node with the block device
> > (add_disk_fwnode). This can be easily extended in the future to
> > support additional block drivers.
> >
> > Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com>
>
> What is the merge strategy here, Am happy to take it via nvmem tree if
> all the non nvmem patches are acked or reviewed-by.

I am planning to pick up patch1 (dt), but waiting until all the
complete series is ready to go.

[...]

Kind regards
Uffe

^ permalink raw reply

* RE: [PATCH v4 0/9] Add RZ/G3L USB2.0 host support
From: Biju Das @ 2026-07-24 15:26 UTC (permalink / raw)
  To: Biju Das, biju.das.au, Philipp Zabel, Vinod Koul, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Geert Uytterhoeven,
	Michael Turquette, Stephen Boyd, Liam Girdwood, Mark Brown,
	magnus.damm, Bartosz Golaszewski
  Cc: Neil Armstrong, Yoshihiro Shimoda, linux-phy@lists.infradead.org,
	devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-clk@vger.kernel.org, linux-renesas-soc@vger.kernel.org,
	Prabhakar Mahadev Lad, biju.das.au
In-Reply-To: <TY3PR01MB1134672A4A05BBD95782F70C486C12@TY3PR01MB11346.jpnprd01.prod.outlook.com>

Hi All,

> -----Original Message-----
> From: Biju Das <biju.das.jz@bp.renesas.com>
> Sent: 22 July 2026 20:15
> Subject: RE: [PATCH v4 0/9] Add RZ/G3L USB2.0 host support
> 
> Hi All,
> 
> > -----Original Message-----
> > From: Biju <biju.das.au@gmail.com>
> > Sent: 09 July 2026 09:31
> > Subject: [PATCH v4 0/9] Add RZ/G3L USB2.0 host support
> >
> > From: Biju Das <biju.das.jz@bp.renesas.com>
> >
> > Hi All,
> >
> > This patch series aims to add USB2.0 host support for the RZ/G3L SMARC
> > EVK. The RZ/G3L USB PHY block is similar to RZ/G3S, but each port has
> > an OTG controller, unlike RZ/G3S, which has an OTG controller only on
> > port 1. This series migrates regulator driver to use id-table so there is hard dependency between reset
> driver and regulator driver.
> >
> > Merge strategy:
> >   An Ack/Rb tag from Regulator Maintainer for patch#4.
> >   Patch #1,#2,#3,#4 - Reset subsystem
> >   Patch #5,#6,#7 - PHY subsystem
> >   Patch #8,#9 - Renesas SoC
> >
> > v3->v4:
> >  * Updated cover letter with merge strategy
> >  * Reorder the phy binding patch for easy merging.
> >  * Updated the commit description for patch #2,#3, #4.
> >  * Moved the handling of regulator_driver_name to patch #3
> >    in preparation for id_table match.
> >  * Migrated to id_table match using driver_name and reduced the length
> >    < 24.
> >  * Dropped rzg3l specific platform driver.
> >  * Dropped the tag for regulator driver,
> >  * Added id_table handling by introducing a common probe().
> > v2->v3:
> >  * Replaced items->enum in reset controller bindings.
> >  * Collected the tag for reset binding patch.
> >  * Dropped clock patch as it is accepted.
> > v1->v2:
> >  * Dropped patch#6(Introduce helper for regulator registration)
> >  * Passing pointer to an array of regulators to make it scalable.
> >  * Dropped regulator1-vbus and added a regulators group node.
> >  * Updated commit description for patch#1,#4,#6,#7,#8,#9 and #10.
> >  * Added enum instead of const in the compatible section.
> >  * Updated schema check.
> >  * Collected tag for PHY binding patch.
> >    in the regulator driver.
> >  * Added regulators group node and its children in SoC dtsi.
> >
> > Biju Das (9):
> >   dt-bindings: reset: renesas,rzg2l-usbphy-ctrl: Document RZ/G3L support
> >   reset: rzg2l-usbphy-ctrl: Introduce info struct for match data
> >   reset: rzg2l-usbphy-ctrl: Add RZ/G3L support
> >   regulator: renesas-usb-vbus-regulator: Add RZ/G3L VBUS regulator
> >     support
> >   dt-bindings: phy: renesas,usb2-phy: Document RZ/G3L PHY bindings
> >   phy: renesas: phy-rcar-gen3-usb2: Add RZ/G3L support
> >   phy: renesas: phy-rcar-gen3-usb2: Fix devm action registration for
> >     disabled VBUS regulator
> >   arm64: dts: renesas: r9a08g046: Add USB2.0 device nodes
> >   arm64: dts: renesas: r9a08g046l48-smarc: Add USB2.0 support
> >
> >  .../bindings/phy/renesas,usb2-phy.yaml        |   2 +
> >  .../reset/renesas,rzg2l-usbphy-ctrl.yaml      |  48 +++++++-
> >  arch/arm64/boot/dts/renesas/r9a08g046.dtsi    | 105 ++++++++++++++++++
> >  .../boot/dts/renesas/r9a08g046l48-smarc.dts   |  49 ++++++++
> >  drivers/phy/renesas/phy-rcar-gen3-usb2.c      |  20 ++--
> >  .../regulator/renesas-usb-vbus-regulator.c    |  74 +++++++++++-
> >  drivers/reset/reset-rzg2l-usbphy-ctrl.c       |  44 +++++---
> >  7 files changed, 316 insertions(+), 26 deletions(-)
> >
> > --
> > 2.43.0
> 
> Gentle ping.

Based on the feedback on [1],

I have modelled pwrrdy signal as power sequence driver and used
with DSI driver and it works fine. So, it should work with USB PHY control
driver as well.

So, there will binding change to dropping renesas,sysc-pwrrdy for RZ/G3L.
and the driver will use Power sequence driver for configuring Power ready signal.

What should I do for RZ/G3S as it uses renesas,sysc-pwrrdy? Should I leave like
as it is to avoid ABI breakage? Or do you want me to add backward compatibility?

Please let me know.

[1] https://lore.kernel.org/all/TY3PR01MB11346892D9619A8D1A6C2DF4A86CF2@TY3PR01MB11346.jpnprd01.prod.outlook.com/


Cheers,
Biju

^ permalink raw reply

* Re: [PATCH v3 0/3] pmdomain: mediatek: Add MT6858 support
From: Ulf Hansson @ 2026-07-24 15:25 UTC (permalink / raw)
  To: nikolai.burov
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
	AngeloGioacchino Del Regno, Ulf Hansson, Matthias Brugger,
	devicetree, linux-kernel, linux-arm-kernel, linux-mediatek,
	linux-pm, Nikolai Burov, Krzysztof Kozlowski
In-Reply-To: <20260720-mt6858-pmdomain-v3-0-8966d8de93c8@jolla.com>

On Mon, Jul 20, 2026 at 10:46 PM Nikolai Burov via B4 Relay
<devnull+nikolai.burov.jolla.com@kernel.org> wrote:
>
> Add power domain driver support for the MediaTek Dimensity 7100 (MT6858)
> SoC. On this SoC, there are some new subsystem-specific bus protection
> blocks, which are listed here in ascending address order:
>
> - IMG_SUB0 at 0x1502f000
> - CAM_SUB1 at 0x1a00c000
> - CAM_SUB0 at 0x1a00d000
> - IPE_SUB0 at 0x1b00e000
>
> The VLPCFG bus configuration registers at 0x1c00c000 are also new.
>
> Additionally, implement the SMC-based power sequence required for
> enabling the modem power domain on this SoC.
>
> Signed-off-by: Nikolai Burov <nikolai.burov@jolla.com>
> ---
> Changes in v3:
> - Fix error path to also skip sram_pdn for MODEM_SECURE_PWRSEQ
>   (discovered in Sashiko report)
> - Link to v2: https://patch.msgid.link/20260715-mt6858-pmdomain-v2-0-6293e87fc093@jolla.com
>
> Changes in v2:
> - Rebase
> - MTK_SCPD_MODEM_SECURE -> MTK_SCPD_MODEM_SECURE_PWRSEQ
> - Convert SMC command enum to defines
> - Based on the new SIMPLE_PWRSEQ changes, skip the sram_pdn stage for
>   MODEM_SECURE_PWRSEQ too since it wasn't intended to have an effect
> - Clarify that the SMC call also handles bus protection
> - Simply if/else code based on the assumption *_PWRSEQ are mutually
>   exclusive
> - Add comment explaining that PWR_STA_2ND is skipped for modem
>   (mentioned in Sashiko report)
> - Link to v1: https://patch.msgid.link/20260714-mt6858-pmdomain-v1-0-4f09bbb822e0@jolla.com
>
> To: Rob Herring <robh@kernel.org>
> To: Krzysztof Kozlowski <krzk+dt@kernel.org>
> To: Conor Dooley <conor+dt@kernel.org>
> To: Matthias Brugger <matthias.bgg@gmail.com>
> To: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
> To: Ulf Hansson <ulfh@kernel.org>
> Cc: Matthias Brugger <mbrugger@suse.com>
> Cc: devicetree@vger.kernel.org
> Cc: linux-kernel@vger.kernel.org
> Cc: linux-arm-kernel@lists.infradead.org
> Cc: linux-mediatek@lists.infradead.org
> Cc: linux-pm@vger.kernel.org
>
> ---
> Nikolai Burov (3):
>       dt-bindings: power: Add MediaTek MT6858 power domain controller
>       pmdomain: mediatek: Add support for secure modem power domain control
>       pmdomain: mediatek: Add support for MT6858 SoC
>
>  .../bindings/power/mediatek,power-controller.yaml  |  21 +-
>  drivers/pmdomain/mediatek/mt6858-pm-domains.h      | 466 +++++++++++++++++++++
>  drivers/pmdomain/mediatek/mtk-pm-domains.c         |  55 ++-
>  drivers/pmdomain/mediatek/mtk-pm-domains.h         |   6 +
>  include/dt-bindings/power/mediatek,mt6858-power.h  |  23 +
>  include/linux/soc/mediatek/mtk_sip_svc.h           |   3 +
>  6 files changed, 568 insertions(+), 6 deletions(-)
> ---
> base-commit: 3fe08b9796f36ef437ab9328e7dd1e5ff2d66603
> change-id: 20260712-mt6858-pmdomain-b1b456c96675
>
> Best regards,
> --
> Nikolai Burov <nikolai.burov@jolla.com>
>
>

The v3 series applied for next, thanks!

Kind regards
Uffe

^ permalink raw reply

* Re: [PATCH] arm64: dts: monaco: Fix CSI I2C controllers default bus frequency
From: Konrad Dybcio @ 2026-07-24 15:22 UTC (permalink / raw)
  To: Hans de Goede, Bjorn Andersson, Konrad Dybcio,
	Krzysztof Kozlowski
  Cc: Conor Dooley, Loic Poulain, Wenmeng Liu, devicetree,
	linux-arm-msm
In-Reply-To: <b690426c-94fa-43b8-a805-7413e45f2849@oss.qualcomm.com>

On 7/24/26 4:22 PM, Hans de Goede wrote:
> Hi,
> 
> On 24-Jul-26 1:42 PM, Konrad Dybcio wrote:
>> On 7/23/26 1:48 PM, Hans de Goede wrote:
>>> 1 MHz is quite fast for an I2C bus and not necessarly reliable, change this
>>> to 400 KHz which is a more reasonable default.
>>>
>>> Signed-off-by: Hans de Goede <johannes.goede@oss.qualcomm.com>
>>
>>
>> I guess this is a question of what's more common among client
>> devices. I assume you have some that aren't happy with 1 MHz?
> 
> Looking at ACPI tables for many x86 devices 400 KHz is the standard,
> with some devices going down to 100 KHz. 1 MHz is pretty much
> unheard off.
> 
> Note the official I2C spec is limited to 100 KHz anything faster then
> that is an extension.
> 
> My specific reason for submitting this patch is imx219 sensors on
> Mona sometimes failing to initialize at 1MHz which is kinda to
> be expected when going for 10x the I2C specification max value.

FWIW I don't mind this patch but wanted to know more background

Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>

Konrad

^ permalink raw reply

* Re: [PATCH v3 7/8] mmc: sdhci-cadence: add Cadence SD6HC support
From: sashiko-bot @ 2026-07-24 15:20 UTC (permalink / raw)
  To: Tanmay Kathpalia; +Cc: robh, conor+dt, devicetree
In-Reply-To: <20260724145009.7456-7-tanmay.kathpalia@altera.com>

Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [High] SD6HC PHY timing calculations use the requested clock frequency instead of the actual physical hardware clock.
- [High] SD6HC PHY remains uninitialized during initial MMC card discovery at 400kHz.
- [High] Unvalidated `cdns,delay-element-ps` device tree property leads to multiple division-by-zero kernel panics.
- [High] The Agilex5 platform initialization logic is unreachable dead code due to a missing match table entry.
- [High] `clk_wr_delay` is incorrectly hardcoded to 255 when the required delay exceeds the DLL's maximum limit.
- [Medium] SD6HC PHY code bypasses the driver's established abstraction for accessing Host Register Set (HRS) registers.
- [Medium] `wmb()` is incorrectly used as a mechanism to flush posted MMIO writes to the hardware.
--

--- Patch [7]: [PATCH v3 7/8] mmc: sdhci-cadence: add Cadence SD6HC support ---
commit 61309bb2c01b2db2af64956b37f9af9f380deea8
Author: Tanmay Kathpalia <tanmay.kathpalia@altera.com>

mmc: sdhci-cadence: add Cadence SD6HC support

This commit introduces support for the Cadence sixth-generation SD/SDIO/eMMC
host controller (SD6HC) with an integrated combo-PHY. It splits the existing
driver into core and PHY-specific files, adding new configuration, timing,
and DLL logic.

> diff --git a/drivers/mmc/host/sdhci-cadence-core.c b/drivers/mmc/host/sdhci-cadence-core.c
> --- a/drivers/mmc/host/sdhci-cadence-core.c
> +++ b/drivers/mmc/host/sdhci-cadence-core.c
[ ... ]
> @@ -481,6 +471,16 @@ static const struct sdhci_ops sdhci_cdns4_ops = {
>  	.set_uhs_signaling = sdhci_cdns_set_uhs_signaling,
>  };
>  
> +static const struct sdhci_ops sdhci_cdns6_ops = {
> +	.set_clock = sdhci_set_clock,

[Severity: High]
Might this leave the SD6HC PHY uninitialized during initial MMC card
discovery at 400kHz? 

During power-up, the clock transitions from 0 to 400kHz while remaining in
MMC_TIMING_LEGACY. The core MMC sdhci_set_ios() optimizes clock enabling by
returning early if the timing mode does not change. Because this only hooks
set_uhs_signaling to program the PHY, it looks like it will get bypassed
when only the frequency changes. Does the driver need a custom set_clock
callback to intercept pure frequency changes and initialize the PHY?

> +	.get_timeout_clock = sdhci_cdns_get_timeout_clock,
> +	.set_bus_width = sdhci_set_bus_width,
> +	.reset = sdhci_reset,
> +	.platform_execute_tuning = sdhci_cdns_execute_tuning,
> +	.set_uhs_signaling = sdhci_cdns_set_uhs_signaling,
> +	.hw_reset = sdhci_cdns6_hw_reset,
> +};
[ ... ]
> @@ -671,6 +709,10 @@ static const struct of_device_id sdhci_cdns_match[] = {
>  		.compatible = "cdns,sd4hc",
>  		.data = &sdhci_cdns4_drv_data,
>  	},
> +	{
> +		.compatible = "cdns,sd6hc",
> +		.data = &sdhci_cdns6_drv_data,
> +	},
>  	{ /* sentinel */ }
>  };

[Severity: High]
Is the altr,agilex5-sd6hc compatible string missing from this match table?

Looking at the driver sources, there is an sdhci_cdns6_agilex5_init()
function that asserts and deasserts specific sdhc, combophy, and ocp resets
for the Altera Agilex5. Without a match entry hooking up this initialization
logic, the driver falls back to the generic cdns,sd6hc match data, making
the Agilex5 reset sequence unreachable dead code.

> diff --git a/drivers/mmc/host/sdhci-cadence-phy-v6.c b/drivers/mmc/host/sdhci-cadence-phy-v6.c
> --- /dev/null
> +++ b/drivers/mmc/host/sdhci-cadence-phy-v6.c
[ ... ]
> +static void sdhci_cdns6_write_phy_reg(struct sdhci_cdns_priv *priv, const u32 address,
> +				      const u32 value)
> +{
> +	writel(address, priv->hrs_addr + SDHCI_CDNS_HRS04);
> +	writel(value, priv->hrs_addr + SDHCI_CDNS_HRS05);
> +}

[Severity: Medium]
Will using direct writel() bypass the established priv->priv_writel()
abstraction?

The core sdhci-cadence architecture uses priv->priv_writel() to support
hardware like the Pensando Elba SoC, which requires custom byte-lane enable
writes. Using writel() directly for Host Register Set (HRS) access here
could cause silent hardware programming failures if SoCs with similar quirks
incorporate the SD6HC IP.

[ ... ]
> +	if (!phy->sdhc_extended_wr_mode) {
> +		u32 out_hold, out_setup, out_hold_margin;
> +		u32 n;
> +
> +		if (!data_ddr)
> +			wr0_dly = 1;
> +
> +		out_setup = output_max;
> +		out_hold = output_min;
> +		out_hold_margin = DIV_ROUND_UP(out_setup - out_hold, 4);
> +		out_hold += out_hold_margin;
> +
> +		if (!phy->cp_dll_bypass_mode)
> +			n = DIV_ROUND_UP(256 * out_hold, phy->t_sdmclk_calc);
> +		else
> +			n = DIV_ROUND_UP(out_hold, phy->delay_element) - 1;
> +
> +		if (n <= phy->dll_max_value)
> +			clk_wr_delay = n;
> +		else
> +			clk_wr_delay = 255;

[Severity: High]
Should this assignment clamp to phy->dll_max_value instead of hardcoding 255?

In sdhci_cdns6_phy_lock_dll(), phy->dll_max_value is explicitly restricted to
127 when delay elements are doubled. If n exceeds 127 in that scenario, writing
255 here violates the maximum allowable tap index, potentially causing
unpredictable wraparound or breakage of hardware timing on slow clocks.

[ ... ]
> +	/*
> +	 * Ensure all preceding PHY register writes complete and reach the controller before
> +	 * releasing the PHY from reset. Without this, SDR104 has been observed to fail
> +	 * intermittently on some boards.
> +	 */
> +	wmb();

[Severity: Medium]
Does a wmb() actually flush posted MMIO writes to the hardware here?

A wmb() only orders CPU instructions; it does not flush posted write buffers
on interconnect buses like PCIe or AXI. If the intent is to guarantee MMIO
writes have settled before the PHY is taken out of reset, a dummy readl()
to the same device is usually required to prevent intermittent device failures.

[ ... ]
> +	ret = of_property_read_u32(dev->of_node, "cdns,delay-element-ps", &phy->delay_element);
> +	if (ret)
> +		phy->delay_element = SDHCI_CDNS6_PHY_DEFAULT_DELAY_ELEMENT;
> +
> +	phy->delay_element_org = phy->delay_element;

[Severity: High]
Could this cause a division-by-zero kernel panic if the device tree explicitly
provides a 0 value for the delay element?

The property is read without validating that it is non-zero. Later, in
sdhci_cdns6_phy_lock_dll(), phy->delay_element is used as a divisor in the
DIV_ROUND_UP(phy->t_sdmclk, delay_element) macro. A malformed device tree
could trivially trigger this crash on probe.

[ ... ]
> +void sdhci_cdns6_set_uhs_signaling(struct sdhci_host *host, unsigned int timing)
> +{
> +	struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
> +	struct sdhci_cdns6_phy *phy = priv->phy;
> +	int ret;
> +
> +	/* Clock may be 0 during initial ios setup; skip PHY update */
> +	if (!host->mmc->ios.clock)
> +		return;
> +
> +	phy->t_sdclk = DIV_ROUND_DOWN_ULL(1000000000000ULL, host->mmc->ios.clock);

[Severity: High]
Can this lead to PHY timing mismatches by using the requested ios.clock instead
of the actual physical hardware clock?

The host->mmc->ios.clock field contains the requested target frequency. However,
the SDHCI controller can only output discrete frequencies based on its internal
integer dividers, which is tracked in host->mmc->actual_clock. If the requested
clock doesn't perfectly match a divider step, the PHY will be programmed for a
frequency the bus isn't actually running at, potentially causing data corruption.

> +	phy->mode = timing;
> +
> +	ret = sdhci_cdns6_phy_update_timings(host);
[ ... ]
> +void sdhci_cdns6_hw_reset(struct sdhci_host *host)
> +{
> +	struct sdhci_cdns_priv *priv = sdhci_cdns_priv(host);
> +	void __iomem *reg;
> +
> +	reg = priv->hrs_addr + SDHCI_CDNS_HRS11;
> +	writel(SDHCI_CDNS_HRS11_EMMC_RST, reg);

[Severity: Medium]
Does this direct writel() skip the priv->priv_writel() abstraction as well?

Just like in sdhci_cdns6_write_phy_reg(), using writel() directly bypasses
the established abstraction and breaks variants that rely on custom byte-lane
writes for hardware reset.

> +	/* eMMC HW reset assertion: spec requires >= 1us, give margin */
> +	usleep_range(10, 20);

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260724145009.7456-1-tanmay.kathpalia@altera.com?part=7

^ permalink raw reply

* Re: [PATCH 05/12] clk: qcom: ipq-cmn-pll: Add PPE clock support
From: sashiko-bot @ 2026-07-24 15:19 UTC (permalink / raw)
  To: Luo Jie; +Cc: conor+dt, devicetree, robh
In-Reply-To: <20260724-qcom_ipq5210_cmnpll-v1-5-a8b8679d215f@oss.qualcomm.com>

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Using DIV_ROUND_CLOSEST_ULL() with a 64-bit divisor causes a divide-by-zero kernel panic due to 32-bit truncation.
--

--- Patch [5]: [PATCH 05/12] clk: qcom: ipq-cmn-pll: Add PPE clock support ---
commit aed5f1938300824fb26726205b9e4ce2ebf415fa
Author: Luo Jie <jie.luo@oss.qualcomm.com>

clk: qcom: ipq-cmn-pll: Add PPE clock support

This commit adds support for the PPE (packet process engine) clock,
which is derived from the CMN PLL output. It uses a configurable 6-bit
divider and shares a frequency control register with the NSS clock.

> diff --git a/drivers/clk/qcom/ipq-cmn-pll.c b/drivers/clk/qcom/ipq-cmn-pll.c
> index 3df326c22fbac..020ad12ca1f2b 100644
> --- a/drivers/clk/qcom/ipq-cmn-pll.c
> +++ b/drivers/clk/qcom/ipq-cmn-pll.c

[ ... ]

> @@ -462,6 +463,83 @@ static struct clk_hw *ipq_cmn_pll_nss_register(struct platform_device *pdev,
>  	return &nss_clk->hw;
>  }
>  
> +/*
> + * PPE (Packet Process Engine) clock operations.
> + * The PPE clock is derived from CMN PLL rate / 2, then divided by
> + * a configurable 6-bit divider (8-63).
> + */

[ ... ]

> +static int clk_ppe_set_rate(struct clk_hw *hw, unsigned long rate,
> +			    unsigned long parent_rate)
> +{
> +	struct clk_cmn_pll *ppe_clk = to_clk_cmn_pll(hw);
> +	unsigned long div;
> +	int ret;
> +
> +	if (rate == 0)
> +		return -EINVAL;
> +
> +	div = DIV_ROUND_CLOSEST_ULL((u64)parent_rate, 2ULL * rate);

[Severity: High]
Does this code overflow the 32-bit divisor limit of DIV_ROUND_CLOSEST_ULL?

DIV_ROUND_CLOSEST_ULL relies on the kernel do_div() macro, which strictly
truncates the divisor to 32 bits on all architectures. If a caller requests
a large rate (e.g. 2,147,483,648 Hz), 2ULL * rate will have its lower 32
bits as 0. Could this regression cause the divisor to truncate to 0 and
trigger a direct divide-by-zero kernel panic?

> +	if (div < CMN_PLL_NSS_PPE_DIV_MIN || div > CMN_PLL_NSS_PPE_DIV_MAX)
> +		return -EINVAL;
> +

[ ... ]

> +static const struct clk_ops clk_ppe_ops = {
> +	.recalc_rate = clk_ppe_recalc_rate,
> +	.determine_rate = clk_nss_ppe_determine_rate,
> +	.set_rate = clk_ppe_set_rate,
> +};

While clk_ppe_set_rate might be protected if the clock framework previously
clamps the rate via determine_rate, this patch also points to the shared
clk_nss_ppe_determine_rate. 

Can unconstrained rates passed into clk_nss_ppe_determine_rate from the
clock framework (such as via debugfs or a malformed device tree) cause a
similar truncation and kernel panic during rate calculation?

> +static struct clk_hw *ipq_cmn_pll_ppe_register(struct platform_device *pdev,
> +					       struct regmap *regmap,
> +					       struct clk_hw *cmn_pll_hw)
> +{

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260724-qcom_ipq5210_cmnpll-v1-0-a8b8679d215f@oss.qualcomm.com?part=5

^ permalink raw reply

* Re: [PATCH 04/12] clk: qcom: ipq-cmn-pll: Add NSS clock support
From: sashiko-bot @ 2026-07-24 15:15 UTC (permalink / raw)
  To: Luo Jie; +Cc: robh, conor+dt, devicetree
In-Reply-To: <20260724-qcom_ipq5210_cmnpll-v1-4-a8b8679d215f@oss.qualcomm.com>

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] clk_nss_ppe_determine_rate violates the clock framework API contract when req->rate is 0.
- [Medium] Modifying the NSS clock divider resets the entire CMN PLL block, potentially disrupting other shared fixed-rate clocks.
--

--- Patch [4]: [PATCH 04/12] clk: qcom: ipq-cmn-pll: Add NSS clock support ---
commit b809b8bd5941993b064f7e68f395a77c3ea61452
Author: Luo Jie <jie.luo@oss.qualcomm.com>

clk: qcom: ipq-cmn-pll: Add NSS clock support

The NSS (network subsystem) clock is derived from the CMN PLL output
divided by 2 and then further divided by a configurable 6-bit divider
with a valid range of 8-63.

> diff --git a/drivers/clk/qcom/ipq-cmn-pll.c b/drivers/clk/qcom/ipq-cmn-pll.c
> index aa319d577871a..3df326c22fbac 100644
> --- a/drivers/clk/qcom/ipq-cmn-pll.c
> +++ b/drivers/clk/qcom/ipq-cmn-pll.c

[ ... ]

> @@ -364,11 +369,105 @@ static struct clk_hw *ipq_cmn_pll_clk_hw_register(struct platform_device *pdev)
>  	return &cmn_pll->hw;
>  }
>  
> +/*
> + * NSS (Network Subsystem) clock operations.
> + * The NSS clock is derived from CMN PLL rate / 2, then divided by
> + * a configurable 6-bit divider (8-63).
> + */

[ ... ]

> +static int clk_nss_ppe_determine_rate(struct clk_hw *hw,
> +				      struct clk_rate_request *req)
> +{
> +	unsigned long div;
> +
> +	if (WARN_ON_ONCE(!req->rate))
> +		return 0;

[Severity: Medium]
When req->rate is 0, this returns 0 (success) without updating req->rate to a
valid boundary or returning an error code like -EINVAL.

Can this regression violate the clock framework API contract by falsely
indicating to consumers that 0 Hz is a supported configuration?

> +
> +	div = DIV_ROUND_CLOSEST_ULL((u64)req->best_parent_rate, 2ULL * req->rate);
> +	div = clamp_t(unsigned long, div, CMN_PLL_NSS_PPE_DIV_MIN,
> +		      CMN_PLL_NSS_PPE_DIV_MAX);
> +
> +	req->rate = DIV_ROUND_CLOSEST_ULL((u64)req->best_parent_rate, 2ULL * div);
> +	return 0;
> +}
> +
> +static int clk_nss_set_rate(struct clk_hw *hw, unsigned long rate,
> +			    unsigned long parent_rate)
> +{

[ ... ]

> +	ret = regmap_update_bits(nss_clk->regmap, CMN_PLL_NSS_PPE_FREQ_CTRL,
> +				 CMN_PLL_NSS_CLK_SEL,
> +				 FIELD_PREP(CMN_PLL_NSS_CLK_SEL, div));
> +	if (ret)
> +		return ret;
> +
> +	return clk_cmn_pll_ana_soft_reset(nss_clk->regmap);

[Severity: Medium]
Does updating the leaf NSS clock divider strictly require a hardware-level
analog reset of the parent PLL block?

Calling clk_cmn_pll_ana_soft_reset() drops the global PLL lock by toggling
CMN_ANA_EN_SW_RSTN:

clk_cmn_pll_ana_soft_reset() {
	...
	ret = regmap_clear_bits(regmap, CMN_PLL_POWER_ON_AND_RESET,
				CMN_ANA_EN_SW_RSTN);
	...
}

Will this regression interrupt other derived clocks (e.g., PCIe, WLAN,
SYS_CLK) and cause link drops or system instability whenever the NSS divider
is dynamically updated?

> +}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260724-qcom_ipq5210_cmnpll-v1-0-a8b8679d215f@oss.qualcomm.com?part=4

^ permalink raw reply

* Re: [PATCH 06/12] clk: qcom: ipq-cmn-pll: Add PON reference clock support
From: sashiko-bot @ 2026-07-24 15:11 UTC (permalink / raw)
  To: Luo Jie; +Cc: robh, conor+dt, devicetree
In-Reply-To: <20260724-qcom_ipq5210_cmnpll-v1-6-a8b8679d215f@oss.qualcomm.com>

Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] 64-bit divisor passed to DIV_ROUND_CLOSEST_ULL causes 32-bit truncation and potential divide-by-zero panics.
- [Medium] Non-atomic register updates when switching to PON mode can cause transient invalid clock states.
- [Medium] `clk_pon_refclk_determine_rate()` returns 0 on invalid rate requests instead of an error code.
- [Medium] Missing PLL analog soft reset when entering UNIPHY fixed mode.
- [Medium] UNIPHY mode is incorrectly excluded from rate rounding for near-match requests.
- [Medium] Missing error check on regmap_read in clk_pon_refclk_recalc_rate leads to use of uninitialized variable.
--

--- Patch [6]: [PATCH 06/12] clk: qcom: ipq-cmn-pll: Add PON reference clock support ---
commit 66c5c5bce6266424eec1b5f6855bb022b40a0322
Author: Luo Jie <jie.luo@oss.qualcomm.com>

clk: qcom: ipq-cmn-pll: Add PON reference clock support

This commit adds support for the PON reference clock to the IPQ CMN PLL driver.
The clock supports UNIPHY and PON modes, with configurable dividers.

> diff --git a/drivers/clk/qcom/ipq-cmn-pll.c b/drivers/clk/qcom/ipq-cmn-pll.c
> index 020ad12ca1f2b..1ae32052b5634 100644
> --- a/drivers/clk/qcom/ipq-cmn-pll.c
> +++ b/drivers/clk/qcom/ipq-cmn-pll.c

[ ... ]

> @@ -540,6 +545,155 @@ static struct clk_hw *ipq_cmn_pll_ppe_register(struct platform_device *pdev,
>  	return &ppe_clk->hw;
>  }
>  
> +/*
> + * PON (Passive Optical Network) reference clock operations.
> + * The PON refclk is derived from CMN PLL rate / 2, then divided by
> + * a configurable 8-bit divider (1-255).
> + */

[ ... ]

> +static unsigned long clk_pon_refclk_recalc_rate(struct clk_hw *hw,
> +						unsigned long parent_rate)
> +{
> +	struct clk_cmn_pll *pon_clk = to_clk_cmn_pll(hw);
> +	u32 val, div;
> +
> +	regmap_read(pon_clk->regmap, CMN_PLL_PON_CONFIG, &val);
> +
> +	/* Check if in UNIPHY mode (bit 9 = 0) - fixed 31.25 MHz */
> +	if (!(val & CMN_PLL_PON_MODE_SEL))

[Severity: Medium]
Could this use uninitialized memory if the underlying MMIO read fails?

The return value of regmap_read() is a standard negative error code on failure.
Since it is not checked, val may be left uninitialized if the read fails,
causing garbage clock rates to be returned to the framework.

> +		return 31250000UL;
> +
> +	/* PON mode: calculate from divider */
> +	div = FIELD_GET(CMN_PLL_PON_DIV_CTRL, val);
> +	if (WARN_ON_ONCE(!div))
> +		return 0;
> +
> +	return DIV_ROUND_CLOSEST_ULL((u64)parent_rate, 2ULL * div);
> +}
> +
> +static int clk_pon_refclk_determine_rate(struct clk_hw *hw,
> +					 struct clk_rate_request *req)
> +{
> +	unsigned long div;
> +
> +	if (WARN_ON_ONCE(!req->rate))
> +		return 0;

[Severity: Medium]
Should this return a negative error code (like -EINVAL) instead of 0?

Returning 0 tells the Common Clock Framework that the rate request was
successfully determined and adapted, which will cause it to proceed to call
set_rate() with rate = 0.

> +
> +	/* UNIPHY fixed mode */
> +	if (req->rate == 31250000UL)
> +		return 0;

[Severity: Medium]
Does this exact match prevent near-match rounding to UNIPHY mode?

If a consumer requests 31249999 Hz, it seems it would bypass this exact match
and fall back to calculating a PON mode divider. This might yield a worse
rate, even though the hardware inherently supports exactly 31.25 MHz.

> +
> +	div = DIV_ROUND_CLOSEST_ULL((u64)req->best_parent_rate, 2ULL * req->rate);

[Severity: High]
Can this cause a divide-by-zero panic on 32-bit platforms?

DIV_ROUND_CLOSEST_ULL() internally uses do_div(), which fundamentally
requires a 32-bit divisor. When the requested rate is large enough,
2ULL * req->rate can exceed 32 bits. On 32-bit platforms, do_div() silently
truncates the 64-bit divisor to 32 bits, which could result in a divisor of 0.

> +
> +	/* Clamp to valid range (1-255) */
> +	div = clamp_t(unsigned long, div, 1, 255);
> +
> +	req->rate = DIV_ROUND_CLOSEST_ULL((u64)req->best_parent_rate, 2ULL * div);
> +	return 0;
> +}
> +
> +static int clk_pon_refclk_set_rate(struct clk_hw *hw, unsigned long rate,
> +				   unsigned long parent_rate)
> +{
> +	struct clk_cmn_pll *pon_clk = to_clk_cmn_pll(hw);
> +	unsigned long div;
> +	int ret;
> +
> +	if (rate == 0)
> +		return -EINVAL;
> +
> +	/* UNIPHY fixed mode (31.25 MHz) */
> +	if (rate == 31250000UL)
> +		return regmap_clear_bits(pon_clk->regmap, CMN_PLL_PON_CONFIG,
> +					 CMN_PLL_PON_MODE_SEL);

[Severity: Medium]
Is it intentional to bypass the analog soft reset here?

The soft reset is performed at the end of the function for PON mode. If
entering UNIPHY mode also requires a soft reset to reliably latch or
stabilize the new clock output, the hardware might be left in an
inconsistent state.

> +
> +	div = DIV_ROUND_CLOSEST_ULL((u64)parent_rate, 2ULL * rate);

[Severity: High]
Can this cause a divide-by-zero panic on 32-bit platforms?

Similar to the determine_rate callback, the 64-bit expression 2ULL * rate
is passed to a macro that expects a 32-bit divisor.

> +	if (div == 0 || div > 255)
> +		return -EINVAL;
> +
> +	/* Switch to PON mode */
> +	ret = regmap_set_bits(pon_clk->regmap, CMN_PLL_PON_CONFIG,
> +			      CMN_PLL_PON_MODE_SEL);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_update_bits(pon_clk->regmap, CMN_PLL_PON_CONFIG,
> +				 CMN_PLL_PON_DIV_CTRL,
> +				 FIELD_PREP(CMN_PLL_PON_DIV_CTRL, div));

[Severity: Medium]
Can this sequential update cause transient invalid clock states?

There is a brief window where the mode bit is set to PON mode, but the
divider has not yet been updated. If the previous divider was 0 or
inappropriate, it might expose downstream hardware to an invalid
frequency or glitch.

> +	if (ret)
> +		return ret;
> +
> +	return clk_cmn_pll_ana_soft_reset(pon_clk->regmap);
> +}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260724-qcom_ipq5210_cmnpll-v1-0-a8b8679d215f@oss.qualcomm.com?part=6

^ permalink raw reply

* Re: [PATCH v17 2/2] platform: Add initial synology microp driver
From: sashiko-bot @ 2026-07-24 15:11 UTC (permalink / raw)
  To: Markus Probst; +Cc: linux-leds, lee, robh, devicetree, ojeda, conor+dt
In-Reply-To: <20260724-synology_microp_initial-v17-2-e2f70b4dc691@posteo.de>

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] Hardware blinking is not disabled when setting a solid brightness.
- [Medium] StatusLedHandler::blink_set does not update delay_on and delay_off when subled_intensity == 0, causing incorrect sysfs blink rate reporting.
--

--- Patch [2]: [PATCH v17 2/2] platform: Add initial synology microp driver ---
commit 4d27f9676f69d36945bb122b6d9df48adadc7625
Author: Markus Probst <markus.probst@posteo.de>

platform: Add initial synology microp driver

This commit adds an initial Synology microp driver written in Rust to
control the power, status, alert, and USB LEDs on Synology NAS devices.
The driver targets a microcontroller found on these devices.

> diff --git a/drivers/platform/synology_microp/led.rs b/drivers/platform/synology_microp/led.rs
> new file mode 100644
> index 0000000000000..ce57adfd307f2
> --- /dev/null
> +++ b/drivers/platform/synology_microp/led.rs

[ ... ]

> +    fn brightness_set<'bound>(
> +        &self,
> +        dev: &'bound Self::Bus,
> +        _classdev: &led::Device<'bound, Self>,
> +        brightness: u32,
> +    ) -> Result<()> {
> +        let mut blink = self.blink.lock();
> +        (self.command)(if brightness == 0 {
> +            *blink = false;
> +            State::Off
> +        } else if *blink {
> +            State::Blink

[Severity: Medium]
Does this prevent transitioning the LED from a blinking state to a solid ON
state?

If userspace sets a hardware blink, the blink variable becomes true. Later, if
userspace writes a non-zero brightness to turn the LED solid ON, this code
path checks the blink variable and issues State::Blink instead of State::On,
leaving the hardware in a blinking state.

> +        } else {
> +            State::On
> +        })
> +        .write(dev)?;
> +
> +        Ok(())
> +    }

[ ... ]

> +    fn brightness_set<'bound>(
> +        &self,
> +        dev: &'bound Self::Bus,
> +        classdev: &led::MultiColorDevice<'bound, Self>,
> +        brightness: u32,
> +    ) -> Result<()> {
> +        let mut blink = self.blink.lock();
> +        if brightness == 0 {
> +            *blink = false;
> +        }
> +
> +        let amber_brightness = classdev.subleds()[1].brightness();
> +
> +        let (color, subled_brightness) = if amber_brightness == 0 {
> +            (StatusLedColor::Green, classdev.subleds()[0].brightness())
> +        } else {
> +            (StatusLedColor::Amber, amber_brightness)
> +        };
> +
> +        Command::StatusLed(
> +            color,
> +            if subled_brightness == 0 {
> +                State::Off
> +            } else if *blink {
> +                State::Blink

[Severity: Medium]
Similarly here, does this prevent the multi-color status LED from being set to
a solid ON state once it is blinking?

> +            } else {
> +                State::On
> +            },
> +        )
> +        .write(dev)
> +    }

[ ... ]

> +    fn blink_set<'bound>(
> +        &self,
> +        dev: &'bound Self::Bus,
> +        classdev: &led::MultiColorDevice<'bound, Self>,
> +        delay_on: &mut usize,
> +        delay_off: &mut usize,
> +    ) -> Result<()> {
> +        let mut blink = self.blink.lock();
> +        *blink = true;
> +
> +        let amber_intensity = classdev.subleds()[1].intensity();
> +
> +        let (color, subled_intensity) = if amber_intensity == 0 {
> +            (StatusLedColor::Green, classdev.subleds()[0].intensity())
> +        } else {
> +            (StatusLedColor::Amber, amber_intensity)
> +        };
> +        Command::StatusLed(
> +            color,
> +            if *delay_on == 0 && *delay_off != 0 {
> +                *blink = false;
> +                State::Off
> +            } else if subled_intensity == 0 {
> +                State::Off

[Severity: Medium]
Are delay_on and delay_off left un-updated when subled_intensity is 0?

If userspace configures a blink rate while the multi-color LED intensity is 0,
this returns State::Off to the hardware but successfully returns to the LED
core without updating the delay values to the fixed hardware blink rate.
This can cause sysfs to report the user-provided values rather than the actual
hardware rate.

> +            } else if *delay_on != 0 && *delay_off == 0 {
> +                *blink = false;
> +                State::On
> +            } else {
> +                *delay_on = BLINK_DELAY.as_millis() as usize;
> +                *delay_off = BLINK_DELAY.as_millis() as usize;
> +
> +                State::Blink
> +            },
> +        )
> +        .write(dev)
> +    }
> +}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260724-synology_microp_initial-v17-0-e2f70b4dc691@posteo.de?part=2

^ permalink raw reply

* Re: [PATCH 2/2] net: wwan: qcom_bam_dmux: Alloc RX buffers as a single coherent block
From: Vishnu Santhosh @ 2026-07-24 15:07 UTC (permalink / raw)
  To: Stephan Gerhold
  Cc: Stephan Gerhold, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Loic Poulain, Sergey Ryazanov, Johannes Berg,
	linux-arm-msm, netdev, devicetree, linux-kernel, chris.lew,
	Deepak Kumar Singh
In-Reply-To: <amMxkrW3mu48HIOr@linaro.org>


On 24-07-2026 03:04 pm, Stephan Gerhold wrote:
> On Fri, Jul 24, 2026 at 10:16:31AM +0530, Vishnu Santhosh wrote:
>> On 14-07-2026 01:05 pm, Stephan Gerhold wrote:
>>> On Tue, Jul 14, 2026 at 11:02:32AM +0530, Vishnu Santhosh wrote:
>>>> On Qualcomm SoCs where the modem (e.g. the mDSP on Shikra, VMID 43 /
>>>> NAV) is the AXI master for BAM-DMUX RX transfers and the XPU enforces
>>>> per-region access control, each individually DMA-mapped RX buffer
>>>> requires its own XPU resource group (RG). With ~16 RGs available, the
>>>> 32 per-buffer dma_map_single() calls exhaust the table and the first
>>>> inbound transfer faults with an XPU violation.
>>>>
>>>> BAM-DMUX is a singleton (exactly one instance per SoC), so the
>>>> destination VMID does not need to be a DT property; it is looked up
>>>> from the compatible string's match data instead. Add struct
>>>> bam_dmux_data with a single vmid field, and a shikra_data instance
>>>> hardcoding QCOM_SCM_VMID_NAV for qcom,shikra-bam-dmux.
>>>>
>>>> When match data is present, allocate all BAM_DMUX_NUM_SKB RX buffers as
>>>> a single contiguous dma_alloc_coherent() block and SCM-assign that
>>>> block to HLOS plus the VMID once at probe. This reduces RG consumption
>>>> from 32 to 1. The block is never reclaimed across a modem power cycle
>>>> (bam_dmux_power_off() does not touch it), so the probe-time assignment
>>>> covers every subsequent restart without re-assigning or reclaiming. It
>>>> is reclaimed to HLOS only once, at remove or on a probe error, and if
>>>> that reclaim fails it is leaked rather than returned to the page
>>>> allocator.
>>>>
>>>> Each rx_skbs[] slot is pre-assigned its virtual and DMA address from
>>>> the block, so no per-buffer mapping is needed at power-on. Because the
>>>> coherent block is not page-backed, received payload is copied into a
>>>> regular netdev skb before handoff to the network stack; this is an
>>>> unavoidable extra copy on the XPU-enforced RX path.
>>>>
>>>> Platforms without match data are unaffected: rx_virt stays NULL, no
>>>> coherent memory is allocated, and the per-buffer dma_map_single() path
>>>> is unchanged.
>>>>
>>>> Co-developed-by: Deepak Kumar Singh <deepak.singh@oss.qualcomm.com>
>>>> Signed-off-by: Deepak Kumar Singh <deepak.singh@oss.qualcomm.com>
>>>> Signed-off-by: Vishnu Santhosh <vishnu.santhosh@oss.qualcomm.com>
>>> So how do you handle TX buffers? Right now, they are just passed on from
>>> the net subsystem. There can be up to 32 TX buffers in progress as well.
>>>
>>> Overall, I have mixed feelings about this patch. It looks reasonably
>>> simple, but fundamentally I don't understand why we need to go back to
>>> the old days of implementing protection using a highly limited MPU (in
>>> your case: the xPU).
>>>
>>> Why does the setup of BAM-DMUX differ e.g. from the setup for the crypto
>>> engine? Crypto is also using bam-dma, but it avoids this inflexibility
>>> by making use of the &apps_smmu. Is BAM-DMUX not covered by the SMMU? Or
>>> did you just decide to bypass the SMMU in this case? (If so: Why?)
>> I checked with secure systems team on this. Crypto BAM is
>> behind apps_smmu, so protection is enforced through the SMMU's Stage-2
>> page tables.
>>
>> A2 BAM (used by BAM-DMUX) is present in secure domain and does not
>> support Stage-2 translation on this SoC, and there is no IOMMU domain
>> that can be attached to it. The only protection mechanism available is
>> the xPU.
>>
> Thanks for investigating this!
>
> So is this a hardware limitation or something you could change with a
> firmware update? Could you move the A2 BAM out of the secure domain and
> protect it via the IOMMU instead of the xPU mechanism? The other modern
> platforms with IPA do not have this limitation, they can use the IOMMU
> for this.
>
> We can try to support the xPU protection mechanism in the BAM-DMUX
> driver, but it's pretty bad from a performance and memory usage point of
> view if you need to copy buffers around multiple times. So if you have
> some way to change this in the firmware (and there is still time to do
> so before production boards ship), I would strongly recommend to
> investigate that.
>
> Thanks,
> Stephan

I agree that relying on xPU protection in BAM-DMUX introduces performance
and memory overheads that would ideally be avoided.

I'll dig a bit deeper with the relevant hardware and secure systems teams
to understand whether there is any possibility to move A2 BAM behind an
IOMMU domain and get back.


Thanks,
Vishnu


^ permalink raw reply

* Re: [PATCH v17 1/2] dt-bindings: embedded-controller: Add synology microp devices
From: sashiko-bot @ 2026-07-24 15:04 UTC (permalink / raw)
  To: Markus Probst; +Cc: linux-leds, lee, robh, conor+dt, devicetree, ojeda
In-Reply-To: <20260724-synology_microp_initial-v17-1-e2f70b4dc691@posteo.de>

> Add the Synology Microp devicetree bindings. Those devices are
> microcontrollers found on Synology NAS devices. They are connected to a
> serial port on the host device.
> 
> Those devices are used to control certain LEDs, fan speeds, a beeper, to
> handle buttons, fan failures and to properly shutdown and reboot the
> device.
> 
> The device has a different feature set depending on the Synology NAS
> model, like having different number of fans, buttons and leds. Depending
> on the architecture of the model, they also need a different system
> shutdown behaviour.
> 
> Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
> Signed-off-by: Markus Probst <markus.probst@posteo.de>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260724-synology_microp_initial-v17-0-e2f70b4dc691@posteo.de?part=1


^ permalink raw reply

* [PATCH v3 0/8] mmc: sdhci-cadence: add SD6HC support and Agilex5 enablement
From: Tanmay Kathpalia @ 2026-07-24 15:00 UTC (permalink / raw)
  To: linux-mmc
  Cc: ulfh, adrian.hunter, krzk+dt, robh, conor+dt, dinguyen, p.zabel,
	devicetree
In-Reply-To: <20260724145009.7456-1-tanmay.kathpalia@altera.com>

This series adds support for the Cadence SD6HC (sixth-generation) SDHCI
controller and enables it on Altera Agilex5 SoCs.

The SD6HC PHY architecture differs substantially from the SD4HC: it
requires dedicated per-speed-mode IO cell timing parameters and a
DLL-based delay line to achieve correct signal margins across all speed
grades from Default Speed through HS400. These are programmed through a
new sdhci-cadence-phy-v6.c file; shared driver infrastructure lives in
sdhci-cadence-core.c.

Note: this series depends on commit ab45ecfab540 ("dt-bindings: reset:
altr: add COMBOPHY_RESET for Agilex5"), which is already in mainline via
the reset tree. It is not yet in mmc/next; apply or merge that commit
when building/testing this series on top of mmc/next. The previous v1/v2
reset binding patch is therefore omitted from this series.

Patch 1: DT binding
  Adds a dedicated cdns,sd6hc.yaml binding with SD6HC compatible strings,
  clock/reset/IOMMU constraints, and PHY timing properties.

Patches 2-4: Device tree
  Patch 2 adds the SD6HC controller node to the Agilex5 SoC DTSI and
  enables SD card operation (4-bit, SDR104, 200 MHz) on the SOCDK board
  with GPIO-switched IO-voltage regulation.
  Patch 3 registers the intel,socfpga-agilex5-socdk-emmc board variant
  in the arm/altera binding.
  Patch 4 adds socfpga_agilex5_socdk_emmc.dts for eMMC-only operation
  (8-bit, HS200/HS400, 1.8 V IO, 200 MHz).

Patches 5-8: Driver
  Patch 5 renames SD4HC-specific functions and structures with a "cdns4"
  prefix to separate them from shared driver paths.
  Patch 6 encapsulates SD4HC PHY probing in sdhci_cdns4_phy_probe() and
  makes every of_device_id entry carry explicit platform data, removing
  the silent fallback.
  Patch 7 introduces the SD6HC PHY driver (sdhci-cadence-phy-v6.c): DLL
  lock/bypass, per-speed-mode IO cell timing, tuning, and HW reset. The
  common driver core selects between v4 and v6 PHY operations based on
  the SDHCI specification version reported by the controller.
  Patch 8 adds the Agilex5 platform overlay under altr,agilex5-sd6hc:
  40-bit DMA mask for the SMMU address space, quirks for
  CAP_CLOCK_BASE_BROKEN, PRESET_VALUE_BROKEN, ACMD23_BROKEN and
  MULTIBLOCK_READ_ACMD12, .get_max_clock set to
  sdhci_pltfm_clk_get_max_clock, and an init hook that asserts and
  deasserts the three named resets together.

Tested on Agilex5 SOCDK:
  - SD card: Default Speed, High Speed, SDR25, SDR50, SDR104
  - eMMC daughter board: HS200, HS400

Signed-off-by: Tanmay Kathpalia <tanmay.kathpalia@altera.com>

---
Changes in v3:

  [bindings]
  - Drop the COMBOPHY_RESET binding patch; it is already in mainline as
    commit ab45ecfab540 via the reset tree.
  - Split SD6HC into a dedicated cdns,sd6hc.yaml binding instead of
    extending cdns,sdhci.yaml (Krzysztof).
  - Rename PHY timing properties with standard unit suffixes:
    cdns,iocell-*-delay-ps and cdns,delay-element-ps (Krzysztof).
  - Define clock-names and reset-names in the SD6HC binding, and keep
    iommus / dma-coherent at the top level (Krzysztof).
  - Rewrite binding/driver commit messages to describe the hardware
    rather than restating the diff (Krzysztof).
  - Fix alphabetical order of the SOCDK eMMC board compatible in
    altera.yaml, and retitle the patch as dt-bindings: arm: altera
    (Krzysztof).

  [drivers]
  - Trim sdhci-cadence.h to include only what it uses (types.h,
    compiler.h, spinlock.h, sdhci-pltfm.h) and keep direct includes in
    the .c files that need them (Adrian) (patch 7).
  - Pulse combophy with sdhc-reset and sdmmc-ocp (exclusive assert /
    deassert together). SoftPHY is shared with NAND at the SoC level,
    but only one of SDMMC or NAND is enabled on a given board
    (replaces shared_deasserted-only handling from v2) (patch 8).
  - Keep #include <linux/iopoll.h> when adding dma-mapping.h (patch 8).
  - Unwrap dev_err_probe() lines that already fit in 100 columns
    (Adrian) (patch 8).

Link V2: https://lore.kernel.org/linux-mmc/20260627201457.12318-1-tanmay.kathpalia@altera.com/
Link V1: https://lore.kernel.org/linux-mmc/20260511202132.5597-1-tanmay.kathpalia@altera.com/

Tanmay Kathpalia (8):
  dt-bindings: mmc: add Cadence SD6HC binding
  arm64: dts: agilex5: add SD/eMMC host controller
  dt-bindings: arm: altera: add Agilex5 SOCDK eMMC board variant
  arm64: dts: agilex5: add SOCDK eMMC daughter board
  mmc: sdhci-cadence: rename SD4HC symbols for SD6HC groundwork
  mmc: sdhci-cadence: refactor driver structure for V6 controller
    support
  mmc: sdhci-cadence: add Cadence SD6HC support
  mmc: sdhci-cadence: add Altera Agilex5 SD6HC support

 .../devicetree/bindings/arm/altera.yaml       |   1 +
 .../devicetree/bindings/mmc/cdns,sd6hc.yaml   | 114 +++
 MAINTAINERS                                   |   7 +
 arch/arm64/boot/dts/intel/Makefile            |   1 +
 .../arm64/boot/dts/intel/socfpga_agilex5.dtsi |  25 +
 .../boot/dts/intel/socfpga_agilex5_socdk.dts  |  38 +-
 .../dts/intel/socfpga_agilex5_socdk_emmc.dts  | 127 +++
 drivers/mmc/host/Makefile                     |   1 +
 .../{sdhci-cadence.c => sdhci-cadence-core.c} | 314 ++++--
 drivers/mmc/host/sdhci-cadence-phy-v6.c       | 929 ++++++++++++++++++
 drivers/mmc/host/sdhci-cadence.h              | 111 +++
 11 files changed, 1596 insertions(+), 72 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/mmc/cdns,sd6hc.yaml
 create mode 100644 arch/arm64/boot/dts/intel/socfpga_agilex5_socdk_emmc.dts
 rename drivers/mmc/host/{sdhci-cadence.c => sdhci-cadence-core.c} (65%)
 create mode 100644 drivers/mmc/host/sdhci-cadence-phy-v6.c
 create mode 100644 drivers/mmc/host/sdhci-cadence.h

-- 
2.43.7

^ permalink raw reply

* [PATCH 12/12] arm64: dts: qcom: Update IPQ5210 xo_board to use fixed factor clock
From: Luo Jie @ 2026-07-24 15:00 UTC (permalink / raw)
  To: Bjorn Andersson, Michael Turquette, Stephen Boyd, Brian Masney,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Konrad Dybcio
  Cc: linux-arm-msm, linux-clk, linux-kernel, devicetree, quic_kkumarcs,
	quic_linchen, quic_leiwei, quic_suruchia, Luo Jie
In-Reply-To: <20260724-qcom_ipq5210_cmnpll-v1-0-a8b8679d215f@oss.qualcomm.com>

xo_board is fixed to 24 MHZ, which is routed from WiFi output clock
48 MHZ (also being the reference clock of CMN PLL) divided 2 by
analog block routing channel.

Signed-off-by: Luo Jie <jie.luo@oss.qualcomm.com>
---
 arch/arm64/boot/dts/qcom/ipq5210-rdp504.dts | 7 ++++++-
 arch/arm64/boot/dts/qcom/ipq5210.dtsi       | 3 ++-
 2 files changed, 8 insertions(+), 2 deletions(-)

diff --git a/arch/arm64/boot/dts/qcom/ipq5210-rdp504.dts b/arch/arm64/boot/dts/qcom/ipq5210-rdp504.dts
index 7410d092814e..fa537633d5f5 100644
--- a/arch/arm64/boot/dts/qcom/ipq5210-rdp504.dts
+++ b/arch/arm64/boot/dts/qcom/ipq5210-rdp504.dts
@@ -85,8 +85,13 @@ &ref_48mhz_clk {
 	clock-mult = <1>;
 };
 
+/*
+ * The frequency of xo_board is fixed to 24 MHZ, which is routed
+ * from WiFi output clock 48 MHZ divided by 2.
+ */
 &xo_board {
-	clock-frequency = <24000000>;
+	clock-div = <2>;
+	clock-mult = <1>;
 };
 
 &xo_clk {
diff --git a/arch/arm64/boot/dts/qcom/ipq5210.dtsi b/arch/arm64/boot/dts/qcom/ipq5210.dtsi
index 9bbd55cc1e75..45026f7835bf 100644
--- a/arch/arm64/boot/dts/qcom/ipq5210.dtsi
+++ b/arch/arm64/boot/dts/qcom/ipq5210.dtsi
@@ -26,7 +26,8 @@ sleep_clk: sleep-clk {
 		};
 
 		xo_board: xo-board-clk {
-			compatible = "fixed-clock";
+			compatible = "fixed-factor-clock";
+			clocks = <&xo_clk>;
 			#clock-cells = <0>;
 		};
 

-- 
2.43.0


^ permalink raw reply related

* [PATCH 11/12] arm64: dts: qcom: ipq5210: Add CMN PLL device node
From: Luo Jie @ 2026-07-24 15:00 UTC (permalink / raw)
  To: Bjorn Andersson, Michael Turquette, Stephen Boyd, Brian Masney,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Konrad Dybcio
  Cc: linux-arm-msm, linux-clk, linux-kernel, devicetree, quic_kkumarcs,
	quic_linchen, quic_leiwei, quic_suruchia, Luo Jie
In-Reply-To: <20260724-qcom_ipq5210_cmnpll-v1-0-a8b8679d215f@oss.qualcomm.com>

Add CMN PLL node for enabling output clocks to the networking
hardware blocks on IPQ5210 devices.

The reference clock of CMN PLL is routed from XO to the CMN PLL
through the WiFi block.
XO (48 MHZ or 96 MHZ or 192 MHZ)-->WiFi (multiplier/divider)-->
48 MHZ to CMN PLL.

Signed-off-by: Luo Jie <jie.luo@oss.qualcomm.com>
---
 arch/arm64/boot/dts/qcom/ipq5210-rdp504.dts | 15 +++++++++++++++
 arch/arm64/boot/dts/qcom/ipq5210.dtsi       | 24 ++++++++++++++++++++++++
 2 files changed, 39 insertions(+)

diff --git a/arch/arm64/boot/dts/qcom/ipq5210-rdp504.dts b/arch/arm64/boot/dts/qcom/ipq5210-rdp504.dts
index 941f866ecfe9..7410d092814e 100644
--- a/arch/arm64/boot/dts/qcom/ipq5210-rdp504.dts
+++ b/arch/arm64/boot/dts/qcom/ipq5210-rdp504.dts
@@ -74,6 +74,21 @@ &uart1 {
 	status = "okay";
 };
 
+/*
+ * The bootstrap pins for the board select the XO clock frequency that
+ * supports 48 MHZ, 96 MHZ or 192 MHZ. This setting automatically
+ * enables the right dividers, to ensure the reference clock output
+ * from WiFi to the CMN PLL is 48 MHZ.
+ */
+&ref_48mhz_clk {
+	clock-div = <1>;
+	clock-mult = <1>;
+};
+
 &xo_board {
 	clock-frequency = <24000000>;
 };
+
+&xo_clk {
+	clock-frequency = <48000000>;
+};
diff --git a/arch/arm64/boot/dts/qcom/ipq5210.dtsi b/arch/arm64/boot/dts/qcom/ipq5210.dtsi
index 2cdc10529c48..9bbd55cc1e75 100644
--- a/arch/arm64/boot/dts/qcom/ipq5210.dtsi
+++ b/arch/arm64/boot/dts/qcom/ipq5210.dtsi
@@ -5,6 +5,7 @@
 
 #include <dt-bindings/interrupt-controller/arm-gic.h>
 #include <dt-bindings/clock/qcom,ipq5210-gcc.h>
+#include <dt-bindings/clock/qcom,ipq5210-cmn-pll.h>
 #include <dt-bindings/reset/qcom,ipq5210-gcc.h>
 
 / {
@@ -13,6 +14,12 @@ / {
 	interrupt-parent = <&intc>;
 
 	clocks {
+		ref_48mhz_clk: ref-48mhz-clk {
+			compatible = "fixed-factor-clock";
+			clocks = <&xo_clk>;
+			#clock-cells = <0>;
+		};
+
 		sleep_clk: sleep-clk {
 			compatible = "fixed-clock";
 			#clock-cells = <0>;
@@ -22,6 +29,11 @@ xo_board: xo-board-clk {
 			compatible = "fixed-clock";
 			#clock-cells = <0>;
 		};
+
+		xo_clk: xo-clk {
+			compatible = "fixed-clock";
+			#clock-cells = <0>;
+		};
 	};
 
 	cpus {
@@ -141,6 +153,18 @@ tlmm: pinctrl@1000000 {
 			#interrupt-cells = <2>;
 		};
 
+		cmn_pll: clock-controller@9b000 {
+			compatible = "qcom,ipq5210-cmn-pll";
+			reg = <0x0 0x0009b000 0x0 0x800>;
+			clocks = <&ref_48mhz_clk>,
+				 <&gcc GCC_CMN_12GPLL_AHB_CLK>,
+				 <&gcc GCC_CMN_12GPLL_SYS_CLK>;
+			clock-names = "ref", "ahb", "sys";
+			#clock-cells = <1>;
+			assigned-clocks = <&cmn_pll IPQ5210_CMN_PLL_CLK>;
+			assigned-clock-rates-u64 = /bits/ 64 <12000000000>;
+		};
+
 		gcc: clock-controller@1800000 {
 			compatible = "qcom,ipq5210-gcc";
 			reg = <0x0 0x01800000 0x0 0x40000>;

-- 
2.43.0


^ permalink raw reply related

* [PATCH 09/12] clk: qcom: ipq-cmn-pll: Add clock gate support for fixed clocks
From: Luo Jie @ 2026-07-24 15:00 UTC (permalink / raw)
  To: Bjorn Andersson, Michael Turquette, Stephen Boyd, Brian Masney,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Konrad Dybcio
  Cc: linux-arm-msm, linux-clk, linux-kernel, devicetree, quic_kkumarcs,
	quic_linchen, quic_leiwei, quic_suruchia, Luo Jie
In-Reply-To: <20260724-qcom_ipq5210_cmnpll-v1-0-a8b8679d215f@oss.qualcomm.com>

Some CMN PLL output clocks require a hardware gate in addition to a
fixed rate. The gate control bits reside in the register
CMN_PLL_OUTPUT_RELATED_1 (0x79c). Each such clock is registered as a
composite clock combining a clk_fixed_rate provider with a clk_gate.

The enable_bit field is added to struct cmn_pll_fixed_output_clk (set to
-1 for non-gated clocks). The CLK_PLL_OUTPUT_RAW() and CLK_PLL_GATE()
helper macros are introduced alongside the existing CLK_PLL_OUTPUT(). The
base MMIO address is added to struct clk_cmn_pll to allow gate clock
registration to locate the output control register.

Since CMN_PLL_OUTPUT_RELATED_1 is shared by all of these gate clocks,
protect it with a single spinlock stored in struct clk_cmn_pll, so that
concurrent enable and disable calls on different gate clocks correctly
serialize their read-modify-write access to the shared register.

Signed-off-by: Luo Jie <jie.luo@oss.qualcomm.com>
---
 drivers/clk/qcom/ipq-cmn-pll.c | 86 +++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 84 insertions(+), 2 deletions(-)

diff --git a/drivers/clk/qcom/ipq-cmn-pll.c b/drivers/clk/qcom/ipq-cmn-pll.c
index dfc28c12468b..00e243282b31 100644
--- a/drivers/clk/qcom/ipq-cmn-pll.c
+++ b/drivers/clk/qcom/ipq-cmn-pll.c
@@ -92,34 +92,62 @@
 #define CMN_PLL_DIVIDER_CTRL			0x794
 #define CMN_PLL_DIVIDER_CTRL_FACTOR		GENMASK(9, 0)
 
+/* Clock gate enable bits. */
+#define CMN_PLL_OUTPUT_RELATED_1		0x79c
+#define CLK25M_EN_BIT				15
+#define CLK50M_EN_BIT3_BIT			14
+#define CLK250M_EN_BIT				13
+#define CLK31P25M_EN_BIT			12
+#define CLK50M_EN_BIT				11
+#define CLK50M_EN_BIT2_BIT			10
+
 /**
  * struct cmn_pll_fixed_output_clk - CMN PLL output clocks information
  * @id:	Clock specifier to be supplied
  * @name: Clock name to be registered
  * @rate: Clock rate
+ * @enable_bit: Enable bit in CMN_PLL_OUTPUT_RELATED_1 for gate clock,
+ *              -1 for non-gated clocks.
  */
 struct cmn_pll_fixed_output_clk {
 	unsigned int id;
 	const char *name;
 	unsigned long rate;
+	int enable_bit;
 };
 
 /**
  * struct clk_cmn_pll - CMN PLL hardware specific data
  * @regmap: hardware regmap.
+ * @base: register base address for gate clock registration
+ * @gate_lock: protects read-modify-write access to CMN_PLL_OUTPUT_RELATED_1,
+ *             which is shared by multiple gate clocks
  * @hw: handle between common and hardware-specific interfaces
+ *
+ * This structure is used for all CMN PLL-derived clocks including
+ * the main PLL, NSS clock, PPE clock, PON reference clock, and
+ * EPHY-RAW clock.
  */
 struct clk_cmn_pll {
 	struct regmap *regmap;
+	void __iomem *base;
+	spinlock_t gate_lock;
 	struct clk_hw hw;
 };
 
-#define CLK_PLL_OUTPUT(_id, _name, _rate) {		\
+#define CLK_PLL_OUTPUT_RAW(_id, _name, _rate, _bit) {	\
 	.id =		_id,				\
 	.name =		_name,				\
 	.rate =		_rate,				\
+	.enable_bit =	_bit,				\
 }
 
+#define CLK_PLL_OUTPUT(_id, _name, _rate)	\
+	CLK_PLL_OUTPUT_RAW(_id, _name, _rate, -1)
+
+#define CLK_PLL_GATE(_id, _name, _rate, _bit)	\
+	CLK_PLL_OUTPUT_RAW(_id, _name, _rate, _bit)
+
 #define to_clk_cmn_pll(_hw) container_of(_hw, struct clk_cmn_pll, hw)
 
 static const struct regmap_config ipq_cmn_pll_regmap_config = {
@@ -368,6 +396,8 @@ static struct clk_hw *ipq_cmn_pll_clk_hw_register(struct platform_device *pdev)
 
 	cmn_pll->hw.init = &init;
 	cmn_pll->regmap = regmap;
+	cmn_pll->base = base;
+	spin_lock_init(&cmn_pll->gate_lock);
 
 	ret = devm_clk_hw_register(dev, &cmn_pll->hw);
 	if (ret)
@@ -776,6 +806,50 @@ static struct clk_hw *ipq_cmn_pll_ephy_raw_register(struct platform_device *pdev
 	return &ephy_raw_clk->hw;
 }
 
+/*
+ * Register a composite clock that combines a fixed-rate clock with a gate.
+ * The gate control bit lives in CMN_PLL_OUTPUT_RELATED_1, which is shared
+ * across multiple gate clocks, so all of them must use the same lock.
+ */
+static struct clk_hw *ipq_cmn_pll_register_fixed_gate(struct device *dev,
+						      const char *name,
+						      struct clk_hw *parent_hw,
+						      unsigned long rate,
+						      void __iomem *base,
+						      spinlock_t *lock,
+						      u8 enable_bit)
+{
+	struct clk_parent_data pdata = { .hw = parent_hw };
+	struct clk_fixed_rate *fixed;
+	struct clk_gate *gate;
+
+	gate = devm_kzalloc(dev, sizeof(*gate), GFP_KERNEL);
+	if (!gate)
+		return ERR_PTR(-ENOMEM);
+
+	fixed = devm_kzalloc(dev, sizeof(*fixed), GFP_KERNEL);
+	if (!fixed)
+		return ERR_PTR(-ENOMEM);
+
+	gate->reg = base + CMN_PLL_OUTPUT_RELATED_1;
+	gate->bit_idx = enable_bit;
+	gate->lock = lock;
+
+	fixed->fixed_rate = rate;
+
+	/*
+	 * These gated clocks may be relied on by external hardware or
+	 * bootloader-enabled paths without an in-kernel client driver.
+	 * Add CLK_IGNORE_UNUSED so the clock framework does not disable
+	 * them when no consumer has claimed them.
+	 */
+	return devm_clk_hw_register_composite_pdata(dev, name, &pdata, 1,
+						    NULL, NULL,
+						    &fixed->hw, &clk_fixed_rate_ops,
+						    &gate->hw, &clk_gate_ops,
+						    CLK_IGNORE_UNUSED);
+}
+
 static int ipq_cmn_pll_register_clks(struct platform_device *pdev)
 {
 	const struct cmn_pll_fixed_output_clk *p, *fixed_clk;
@@ -812,7 +886,15 @@ static int ipq_cmn_pll_register_clks(struct platform_device *pdev)
 
 	/* Register the fixed rate output clocks. */
 	for (i = 0; i < num_clks; i++) {
-		if (fixed_clk[i].rate) {
+		if (fixed_clk[i].enable_bit != -1) {
+			hw = ipq_cmn_pll_register_fixed_gate(dev,
+							     fixed_clk[i].name,
+							     cmn_pll_hw,
+							     fixed_clk[i].rate,
+							     cmn_pll->base,
+							     &cmn_pll->gate_lock,
+							     fixed_clk[i].enable_bit);
+		} else if (fixed_clk[i].rate) {
 			struct clk_parent_data pdata = { .hw = cmn_pll_hw };
 
 			hw = devm_clk_hw_register_fixed_rate_parent_data(dev,

-- 
2.43.0


^ permalink raw reply related

* [PATCH 10/12] clk: qcom: ipq-cmn-pll: Add all output clocks for IPQ5210
From: Luo Jie @ 2026-07-24 15:00 UTC (permalink / raw)
  To: Bjorn Andersson, Michael Turquette, Stephen Boyd, Brian Masney,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Konrad Dybcio
  Cc: linux-arm-msm, linux-clk, linux-kernel, devicetree, quic_kkumarcs,
	quic_linchen, quic_leiwei, quic_suruchia, Luo Jie
In-Reply-To: <20260724-qcom_ipq5210_cmnpll-v1-0-a8b8679d215f@oss.qualcomm.com>

Add the IPQ5210 output clock array and the device match table entry,
wiring together all the infrastructure added in the previous patches.

The IPQ5210 CMN PLL supplies 12 output clocks:

Fixed-rate:           XO 24 MHz, sleep 32 kHz
Gated fixed-rate:     PCS 31.25 MHz, ETH0/1/2 50 MHz, EPHY 50 MHz,
	       	      ETH 25 MHz
Configurable divider: NSS, PPE
PON reference:        PON (enable/disable + 8-bit divider)
EPHY-RAW:             125/312.5 MHz select

Signed-off-by: Luo Jie <jie.luo@oss.qualcomm.com>
---
 drivers/clk/qcom/ipq-cmn-pll.c | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/drivers/clk/qcom/ipq-cmn-pll.c b/drivers/clk/qcom/ipq-cmn-pll.c
index 00e243282b31..902a222e4533 100644
--- a/drivers/clk/qcom/ipq-cmn-pll.c
+++ b/drivers/clk/qcom/ipq-cmn-pll.c
@@ -55,6 +55,7 @@
 
 #include <dt-bindings/clock/qcom,ipq-cmn-pll.h>
 #include <dt-bindings/clock/qcom,ipq5018-cmn-pll.h>
+#include <dt-bindings/clock/qcom,ipq5210-cmn-pll.h>
 #include <dt-bindings/clock/qcom,ipq5332-cmn-pll.h>
 #include <dt-bindings/clock/qcom,ipq5424-cmn-pll.h>
 #include <dt-bindings/clock/qcom,ipq6018-cmn-pll.h>
@@ -164,6 +165,22 @@ static const struct cmn_pll_fixed_output_clk ipq5018_output_clks[] = {
 	{ /* Sentinel */ }
 };
 
+static const struct cmn_pll_fixed_output_clk ipq5210_output_clks[] = {
+	CLK_PLL_OUTPUT(IPQ5210_XO_24MHZ_CLK, "xo-24mhz", 24000000UL),
+	CLK_PLL_OUTPUT(IPQ5210_SLEEP_32KHZ_CLK, "sleep-32khz", 32000UL),
+	CLK_PLL_GATE(IPQ5210_PCS_31P25MHZ_CLK, "pcs-31p25mhz", 31250000UL, CLK31P25M_EN_BIT),
+	CLK_PLL_GATE(IPQ5210_ETH0_50MHZ_CLK, "eth0-50mhz", 50000000UL, CLK50M_EN_BIT),
+	CLK_PLL_GATE(IPQ5210_ETH1_50MHZ_CLK, "eth1-50mhz", 50000000UL, CLK50M_EN_BIT2_BIT),
+	CLK_PLL_GATE(IPQ5210_ETH2_50MHZ_CLK, "eth2-50mhz", 50000000UL, CLK50M_EN_BIT3_BIT),
+	CLK_PLL_GATE(IPQ5210_EPHY_50MHZ_CLK, "ephy-50mhz", 50000000UL, CLK250M_EN_BIT),
+	CLK_PLL_GATE(IPQ5210_ETH_25MHZ_CLK, "eth-25mhz", 25000000UL, CLK25M_EN_BIT),
+	CLK_PLL_OUTPUT(IPQ5210_NSS_CLK, "nss", 0),
+	CLK_PLL_OUTPUT(IPQ5210_PPE_CLK, "ppe", 0),
+	CLK_PLL_OUTPUT(IPQ5210_PON_REFCLK, "pon", 0),
+	CLK_PLL_OUTPUT(IPQ5210_EPHY_RAW_CLK, "ephy-raw", 0),
+	{ /* Sentinel */ }
+};
+
 static const struct cmn_pll_fixed_output_clk ipq6018_output_clks[] = {
 	CLK_PLL_OUTPUT(IPQ6018_BIAS_PLL_CC_CLK, "bias_pll_cc_clk", 300000000UL),
 	CLK_PLL_OUTPUT(IPQ6018_BIAS_PLL_NSS_NOC_CLK, "bias_pll_nss_noc_clk", 416500000UL),
@@ -986,6 +1003,7 @@ static const struct dev_pm_ops ipq_cmn_pll_pm_ops = {
 
 static const struct of_device_id ipq_cmn_pll_clk_ids[] = {
 	{ .compatible = "qcom,ipq5018-cmn-pll", .data = &ipq5018_output_clks },
+	{ .compatible = "qcom,ipq5210-cmn-pll", .data = &ipq5210_output_clks },
 	{ .compatible = "qcom,ipq5332-cmn-pll", .data = &ipq5332_output_clks },
 	{ .compatible = "qcom,ipq5424-cmn-pll", .data = &ipq5424_output_clks },
 	{ .compatible = "qcom,ipq6018-cmn-pll", .data = &ipq6018_output_clks },

-- 
2.43.0


^ permalink raw reply related

* [PATCH 08/12] clk: composite: Export devm_clk_hw_register_composite_pdata
From: Luo Jie @ 2026-07-24 15:00 UTC (permalink / raw)
  To: Bjorn Andersson, Michael Turquette, Stephen Boyd, Brian Masney,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Konrad Dybcio
  Cc: linux-arm-msm, linux-clk, linux-kernel, devicetree, quic_kkumarcs,
	quic_linchen, quic_leiwei, quic_suruchia, Luo Jie
In-Reply-To: <20260724-qcom_ipq5210_cmnpll-v1-0-a8b8679d215f@oss.qualcomm.com>

devm_clk_hw_register_composite_pdata() is defined but not exported,
preventing its use from loadable modules. Add EXPORT_SYMBOL_GPL() so
that out-of-tree and built-as-module clock drivers can call it.

Signed-off-by: Luo Jie <jie.luo@oss.qualcomm.com>
---
 drivers/clk/clk-composite.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/clk/clk-composite.c b/drivers/clk/clk-composite.c
index 835b1e4e5869..11842bce5918 100644
--- a/drivers/clk/clk-composite.c
+++ b/drivers/clk/clk-composite.c
@@ -462,3 +462,4 @@ struct clk_hw *devm_clk_hw_register_composite_pdata(struct device *dev,
 						rate_hw, rate_ops, gate_hw,
 						gate_ops, flags);
 }
+EXPORT_SYMBOL_GPL(devm_clk_hw_register_composite_pdata);

-- 
2.43.0


^ permalink raw reply related

* Re: [PATCH v7 2/3] pwm: rp1: Add RP1 PWM controller driver
From: Andrea della Porta @ 2026-07-24 15:04 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: Andrea della Porta, devicetree, conor+dt, robh, ukleinek
In-Reply-To: <20260720100212.BEF1D1F000E9@smtp.kernel.org>

Hi,

On 10:02 Mon 20 Jul     , sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
> - [High] Missing `.remove` callback leads to Use-After-Free in the PWM and Syscon cores and leaks clock resources on driver unbind.
> - [High] Register context loss on suspend/resume silently breaks PWM output.
> - [High] Unclamped hardware duty ticks lead to integer underflow in the PWM core for normal polarity.
> --
> 
> --- Patch [2]: [PATCH v7 2/3] pwm: rp1: Add RP1 PWM controller driver ---
> commit 0c98703d857577902e57d25b42a3d46dfbde5795
> Author: Naushir Patuck <naush@raspberrypi.com>
> 
> pwm: rp1: Add RP1 PWM controller driver
> 
> The Raspberry Pi RP1 southbridge features an embedded PWM controller with
> 4 output channels, alongside an RPM interface to read the fan speed on the
> Raspberry Pi 5. Add the supporting driver.
> 
> > diff --git a/drivers/pwm/pwm-rp1.c b/drivers/pwm/pwm-rp1.c
> > new file mode 100644
> > --- /dev/null
> > +++ b/drivers/pwm/pwm-rp1.c
> 
> [ ... ]
> 
> > +static int rp1_pwm_round_waveform_fromhw(struct pwm_chip *chip,
> > +					 struct pwm_device *pwm,
> > +					 const void *_wfhw,
> > +					 struct pwm_waveform *wf)
> > +{
> > +	struct rp1_pwm *rp1 = pwmchip_get_drvdata(chip);
> > +	const struct rp1_pwm_waveform *wfhw = _wfhw;
> > +	u64 clk_rate = rp1->clk_rate;
> > +	u64 ticks;
> > +
> > +	*wf = (struct pwm_waveform){ };
> > +
> > +	if (!wfhw->enabled)
> > +		return 0;
> > +
> > +	wf->period_length_ns = DIV_ROUND_UP_ULL(((u64)wfhw->period_ticks + 1) * NSEC_PER_SEC,
> > +						clk_rate);
> > +
> > +	if (!wfhw->inverted_polarity) {
> > +		wf->duty_length_ns = DIV_ROUND_UP_ULL((u64)wfhw->duty_ticks * NSEC_PER_SEC,
> > +						      (u32)clk_rate);
> > +	} else {
> 
> [Severity: High]
> Does the normal polarity case need to clamp the duty ticks similar to the
> inverted polarity case?
> 
> If the bootloader configures a continuous-high state where the raw hardware
> duty ticks exceed the period ticks + 1, this path calculates a duty_length_ns
> that is strictly greater than the period_length_ns.
> 
> When the PWM core subsequently reads this initial state, pwm_wf2state()
> handles duty > period by treating it as an inverted polarity and calculating
> duty_cycle = period - duty_length_ns. This subtraction can silently
> underflow, causing the core to interpret the hardware state as having a badly
> corrupted duty cycle.

The conditional right after the opening bracket of this else branch (see below
for the code) is there just for that: clamping the duty to sane values. So it
already takes care of that.

> 
> > +		if (wfhw->duty_ticks > (u64)wfhw->period_ticks + 1) {
> > +			/* 100% duty cycle case */
> > +			ticks = 0;
> > +		} else {
> 
> [ ... ]
> 
> > +static int rp1_pwm_write_waveform(struct pwm_chip *chip,
> > +				  struct pwm_device *pwm,
> > +				  const void *_wfhw)
> > +{
> > +	struct rp1_pwm *rp1 = pwmchip_get_drvdata(chip);
> > +	const struct rp1_pwm_waveform *wfhw = _wfhw;
> > +	u32 value, ctrl;
> > +
> > +	/* set polarity */
> > +	regmap_read(rp1->regmap, RP1_PWM_CHAN_CTRL(pwm->hwpwm), &value);
> > +	if (!wfhw->inverted_polarity)
> > +		value &= ~RP1_PWM_CHAN_CTRL_POLARITY;
> > +	else
> > +		value |= RP1_PWM_CHAN_CTRL_POLARITY;
> > +	regmap_write(rp1->regmap, RP1_PWM_CHAN_CTRL(pwm->hwpwm), value);
> 
> [Severity: High]
> Could the PWM controller lose its hardware configuration after a system
> suspend and resume?
> 
> During rp1_pwm_request(), the channel control register is initialized with
> essential bits like RP1_PWM_CHAN_CTRL_DEFAULT, which includes the
> MODE_TE_MS and FIFO_POP_MASK configurations.
> 
> If the hardware loses power during suspend, the registers reset. On resume,
> when a consumer re-applies its state, this regmap_read() will read the wiped
> hardware register. The driver will then update only the polarity bit and
> write it back, failing to restore the required operational mode bits that
> were initialized during the request phase.

I think I've already answered this here: 
https://lore.kernel.org/all/aiGAINsTG8VZLn28@apocalypse/
so I guess Sashiko is not sotring the entire history as
context, so I'll reply it here:

AFAIK the RP1 is never powered-off, so just shutting down the clock should be ok
and no register are reset to default.

> 
> > +
> > +	/* early exit if disabled */
> 
> [ ... ]
> 
> > +static struct platform_driver rp1_pwm_driver = {
> > +	.probe = rp1_pwm_probe,
> > +	/*
> > +	 * .remove is not needed because this driver cannot be removed/unbound
> > +	 * cause it relies on syscon which cannot be removed
> > +	 */
> > +	.driver = {
> > +		.name = "rp1-pwm",
> 
> [Severity: High]
> Is it safe to omit the remove callback here?
> 
> Even with suppress_bind_attrs set on the driver, if the parent RP1 PCIe
> device is removed, reset, or unbound via sysfs, it will forcibly unbind all
> child devices.
> 
> Without a remove callback to run cleanup, pwmchip_remove() is never called
> and the clock is left enabled. Because the backing memory is devm-managed,
> the devres subsystem will eventually free the pwm_chip and regmap memory.
> This can leave dangling pointers in the core pwm_chips IDR and the global
> syscon_list, which could result in a use-after-free.

This is true. Uwe, I guess I should reintroduce the .remove callback,
what do you think?

Thanks,
Andrea

> 
> > +		.of_match_table = rp1_pwm_of_match,
> > +		.pm = pm_ptr(&rp1_pwm_pm_ops),
> > +		.suppress_bind_attrs = true,
> > +	},
> > +};
> 
> -- 
> Sashiko AI review · https://sashiko.dev/#/patchset/cover.1784539788.git.andrea.porta@suse.com?part=2

^ permalink raw reply

* [PATCH 06/12] clk: qcom: ipq-cmn-pll: Add PON reference clock support
From: Luo Jie @ 2026-07-24 15:00 UTC (permalink / raw)
  To: Bjorn Andersson, Michael Turquette, Stephen Boyd, Brian Masney,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Konrad Dybcio
  Cc: linux-arm-msm, linux-clk, linux-kernel, devicetree, quic_kkumarcs,
	quic_linchen, quic_leiwei, quic_suruchia, Luo Jie
In-Reply-To: <20260724-qcom_ipq5210_cmnpll-v1-0-a8b8679d215f@oss.qualcomm.com>

The PON (Passive Optical Network) reference clock is derived from the
CMN PLL output divided by 2 and then further divided by a configurable
8-bit divider (1-255). The clock supports two modes:

- UNIPHY mode (CMN_PLL_PON_MODE_SEL = 0): fixed 31.25 MHz output
- PON mode   (CMN_PLL_PON_MODE_SEL = 1): pll_rate / (2 * divider)

The clock output can be independently enabled and disabled via the
CMN_PLL_PON_EN bit.

Signed-off-by: Luo Jie <jie.luo@oss.qualcomm.com>
---
 drivers/clk/qcom/ipq-cmn-pll.c | 158 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 158 insertions(+)

diff --git a/drivers/clk/qcom/ipq-cmn-pll.c b/drivers/clk/qcom/ipq-cmn-pll.c
index 020ad12ca1f2..1ae32052b563 100644
--- a/drivers/clk/qcom/ipq-cmn-pll.c
+++ b/drivers/clk/qcom/ipq-cmn-pll.c
@@ -72,6 +72,11 @@
 #define CMN_PLL_NSS_PPE_DIV_MIN			8
 #define CMN_PLL_NSS_PPE_DIV_MAX			63
 
+#define CMN_PLL_PON_CONFIG			0x42c
+#define CMN_PLL_PON_MODE_SEL			BIT(9)
+#define CMN_PLL_PON_EN				BIT(8)
+#define CMN_PLL_PON_DIV_CTRL			GENMASK(7, 0)
+
 #define CMN_PLL_POWER_ON_AND_RESET		0x780
 #define CMN_ANA_EN_SW_RSTN			BIT(6)
 
@@ -540,6 +545,155 @@ static struct clk_hw *ipq_cmn_pll_ppe_register(struct platform_device *pdev,
 	return &ppe_clk->hw;
 }
 
+/*
+ * PON (Passive Optical Network) reference clock operations.
+ * The PON refclk is derived from CMN PLL rate / 2, then divided by
+ * a configurable 8-bit divider (1-255).
+ */
+static int clk_pon_refclk_enable(struct clk_hw *hw)
+{
+	struct clk_cmn_pll *pon_clk = to_clk_cmn_pll(hw);
+
+	return regmap_set_bits(pon_clk->regmap, CMN_PLL_PON_CONFIG,
+			       CMN_PLL_PON_EN);
+}
+
+static void clk_pon_refclk_disable(struct clk_hw *hw)
+{
+	struct clk_cmn_pll *pon_clk = to_clk_cmn_pll(hw);
+
+	regmap_clear_bits(pon_clk->regmap, CMN_PLL_PON_CONFIG,
+			  CMN_PLL_PON_EN);
+}
+
+static int clk_pon_refclk_is_enabled(struct clk_hw *hw)
+{
+	struct clk_cmn_pll *pon_clk = to_clk_cmn_pll(hw);
+
+	return regmap_test_bits(pon_clk->regmap, CMN_PLL_PON_CONFIG,
+				CMN_PLL_PON_EN);
+}
+
+static unsigned long clk_pon_refclk_recalc_rate(struct clk_hw *hw,
+						unsigned long parent_rate)
+{
+	struct clk_cmn_pll *pon_clk = to_clk_cmn_pll(hw);
+	u32 val, div;
+
+	regmap_read(pon_clk->regmap, CMN_PLL_PON_CONFIG, &val);
+
+	/* Check if in UNIPHY mode (bit 9 = 0) - fixed 31.25 MHz */
+	if (!(val & CMN_PLL_PON_MODE_SEL))
+		return 31250000UL;
+
+	/* PON mode: calculate from divider */
+	div = FIELD_GET(CMN_PLL_PON_DIV_CTRL, val);
+	if (WARN_ON_ONCE(!div))
+		return 0;
+
+	return DIV_ROUND_CLOSEST_ULL((u64)parent_rate, 2ULL * div);
+}
+
+static int clk_pon_refclk_determine_rate(struct clk_hw *hw,
+					 struct clk_rate_request *req)
+{
+	unsigned long div;
+
+	if (WARN_ON_ONCE(!req->rate))
+		return 0;
+
+	/* UNIPHY fixed mode */
+	if (req->rate == 31250000UL)
+		return 0;
+
+	div = DIV_ROUND_CLOSEST_ULL((u64)req->best_parent_rate, 2ULL * req->rate);
+
+	/* Clamp to valid range (1-255) */
+	div = clamp_t(unsigned long, div, 1, 255);
+
+	req->rate = DIV_ROUND_CLOSEST_ULL((u64)req->best_parent_rate, 2ULL * div);
+	return 0;
+}
+
+static int clk_pon_refclk_set_rate(struct clk_hw *hw, unsigned long rate,
+				   unsigned long parent_rate)
+{
+	struct clk_cmn_pll *pon_clk = to_clk_cmn_pll(hw);
+	unsigned long div;
+	int ret;
+
+	if (rate == 0)
+		return -EINVAL;
+
+	/* UNIPHY fixed mode (31.25 MHz) */
+	if (rate == 31250000UL)
+		return regmap_clear_bits(pon_clk->regmap, CMN_PLL_PON_CONFIG,
+					 CMN_PLL_PON_MODE_SEL);
+
+	div = DIV_ROUND_CLOSEST_ULL((u64)parent_rate, 2ULL * rate);
+	if (div == 0 || div > 255)
+		return -EINVAL;
+
+	/* Switch to PON mode */
+	ret = regmap_set_bits(pon_clk->regmap, CMN_PLL_PON_CONFIG,
+			      CMN_PLL_PON_MODE_SEL);
+	if (ret)
+		return ret;
+
+	ret = regmap_update_bits(pon_clk->regmap, CMN_PLL_PON_CONFIG,
+				 CMN_PLL_PON_DIV_CTRL,
+				 FIELD_PREP(CMN_PLL_PON_DIV_CTRL, div));
+	if (ret)
+		return ret;
+
+	return clk_cmn_pll_ana_soft_reset(pon_clk->regmap);
+}
+
+static const struct clk_ops clk_pon_refclk_ops = {
+	.enable = clk_pon_refclk_enable,
+	.disable = clk_pon_refclk_disable,
+	.is_enabled = clk_pon_refclk_is_enabled,
+	.recalc_rate = clk_pon_refclk_recalc_rate,
+	.determine_rate = clk_pon_refclk_determine_rate,
+	.set_rate = clk_pon_refclk_set_rate,
+};
+
+static struct clk_hw *ipq_cmn_pll_pon_refclk_register(struct platform_device *pdev,
+						      struct regmap *regmap,
+						      struct clk_hw *cmn_pll_hw)
+{
+	struct clk_parent_data pdata = { .hw = cmn_pll_hw };
+	struct device *dev = &pdev->dev;
+	struct clk_init_data init = {};
+	struct clk_cmn_pll *pon_clk;
+	int ret;
+
+	pon_clk = devm_kzalloc(dev, sizeof(*pon_clk), GFP_KERNEL);
+	if (!pon_clk)
+		return ERR_PTR(-ENOMEM);
+
+	init.name = "pon-clk";
+	init.parent_data = &pdata;
+	init.num_parents = 1;
+	init.ops = &clk_pon_refclk_ops;
+	/*
+	 * The PON reference clock may already be enabled by bootloader
+	 * or consumed by hardware without an in-kernel client driver.
+	 * Add CLK_IGNORE_UNUSED so the clock framework does not disable
+	 * it when no consumer has claimed it.
+	 */
+	init.flags = CLK_IGNORE_UNUSED;
+
+	pon_clk->hw.init = &init;
+	pon_clk->regmap = regmap;
+
+	ret = devm_clk_hw_register(dev, &pon_clk->hw);
+	if (ret)
+		return ERR_PTR(ret);
+
+	return &pon_clk->hw;
+}
+
 static int ipq_cmn_pll_register_clks(struct platform_device *pdev)
 {
 	const struct cmn_pll_fixed_output_clk *p, *fixed_clk;
@@ -589,6 +743,10 @@ static int ipq_cmn_pll_register_clks(struct platform_device *pdev)
 		} else if (!strcmp(fixed_clk[i].name, "ppe")) {
 			hw = ipq_cmn_pll_ppe_register(pdev, cmn_pll->regmap,
 						      cmn_pll_hw);
+		} else if (!strcmp(fixed_clk[i].name, "pon")) {
+			hw = ipq_cmn_pll_pon_refclk_register(pdev,
+							     cmn_pll->regmap,
+							     cmn_pll_hw);
 		} else {
 			continue;
 		}

-- 
2.43.0


^ permalink raw reply related

* [PATCH 07/12] clk: qcom: ipq-cmn-pll: Add EPHY-RAW clock support
From: Luo Jie @ 2026-07-24 15:00 UTC (permalink / raw)
  To: Bjorn Andersson, Michael Turquette, Stephen Boyd, Brian Masney,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Konrad Dybcio
  Cc: linux-arm-msm, linux-clk, linux-kernel, devicetree, quic_kkumarcs,
	quic_linchen, quic_leiwei, quic_suruchia, Luo Jie
In-Reply-To: <20260724-qcom_ipq5210_cmnpll-v1-0-a8b8679d215f@oss.qualcomm.com>

The EPHY-RAW clock is an IPQ5210-specific output from the CMN PLL that
provides the raw reference clock to the SoC-integrated Ethernet PHY
(EPHY), which supports up to 2.5G link speed. The output frequency is
selected via bit 10 (CMN_PLL_GEPHY_312P5M_125M_SEL) of the
CMN_PLL_PON_CONFIG register:

- 0: 125 MHz   - for 1G/100M/10M link speed
- 1: 312.5 MHz - for 2.5G link speed

Signed-off-by: Luo Jie <jie.luo@oss.qualcomm.com>
---
 drivers/clk/qcom/ipq-cmn-pll.c | 86 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 86 insertions(+)

diff --git a/drivers/clk/qcom/ipq-cmn-pll.c b/drivers/clk/qcom/ipq-cmn-pll.c
index 1ae32052b563..dfc28c12468b 100644
--- a/drivers/clk/qcom/ipq-cmn-pll.c
+++ b/drivers/clk/qcom/ipq-cmn-pll.c
@@ -76,6 +76,7 @@
 #define CMN_PLL_PON_MODE_SEL			BIT(9)
 #define CMN_PLL_PON_EN				BIT(8)
 #define CMN_PLL_PON_DIV_CTRL			GENMASK(7, 0)
+#define CMN_PLL_GEPHY_312P5M_125M_SEL		BIT(10)
 
 #define CMN_PLL_POWER_ON_AND_RESET		0x780
 #define CMN_ANA_EN_SW_RSTN			BIT(6)
@@ -694,6 +695,87 @@ static struct clk_hw *ipq_cmn_pll_pon_refclk_register(struct platform_device *pd
 	return &pon_clk->hw;
 }
 
+/*
+ * EPHY-RAW clock operations for IPQ5210.
+ * The output clock rate is selected via bit 10 of CMN_PLL_PON_CONFIG:
+ *   0: 125 MHz  (for 1G link speed)
+ *   1: 312.5 MHz (for 2.5G link speed)
+ */
+static unsigned long clk_ephy_raw_recalc_rate(struct clk_hw *hw,
+					      unsigned long parent_rate)
+{
+	struct clk_cmn_pll *ephy_raw_clk = to_clk_cmn_pll(hw);
+	u32 val;
+
+	regmap_read(ephy_raw_clk->regmap, CMN_PLL_PON_CONFIG, &val);
+
+	if (val & CMN_PLL_GEPHY_312P5M_125M_SEL)
+		return 312500000UL;
+
+	return 125000000UL;
+}
+
+static int clk_ephy_raw_determine_rate(struct clk_hw *hw,
+				       struct clk_rate_request *req)
+{
+	if (req->rate <= 125000000UL)
+		req->rate = 125000000UL;
+	else
+		req->rate = 312500000UL;
+
+	return 0;
+}
+
+static int clk_ephy_raw_set_rate(struct clk_hw *hw, unsigned long rate,
+				 unsigned long parent_rate)
+{
+	struct clk_cmn_pll *ephy_raw_clk = to_clk_cmn_pll(hw);
+
+	if (rate == 125000000UL)
+		return regmap_clear_bits(ephy_raw_clk->regmap,
+					 CMN_PLL_PON_CONFIG,
+					 CMN_PLL_GEPHY_312P5M_125M_SEL);
+
+	return regmap_set_bits(ephy_raw_clk->regmap,
+			       CMN_PLL_PON_CONFIG,
+			       CMN_PLL_GEPHY_312P5M_125M_SEL);
+}
+
+static const struct clk_ops clk_ephy_raw_ops = {
+	.recalc_rate = clk_ephy_raw_recalc_rate,
+	.determine_rate = clk_ephy_raw_determine_rate,
+	.set_rate = clk_ephy_raw_set_rate,
+};
+
+static struct clk_hw *ipq_cmn_pll_ephy_raw_register(struct platform_device *pdev,
+						    struct regmap *regmap,
+						    struct clk_hw *cmn_pll_hw)
+{
+	struct clk_parent_data pdata = { .hw = cmn_pll_hw };
+	struct clk_cmn_pll *ephy_raw_clk;
+	struct device *dev = &pdev->dev;
+	struct clk_init_data init = {};
+	int ret;
+
+	ephy_raw_clk = devm_kzalloc(dev, sizeof(*ephy_raw_clk), GFP_KERNEL);
+	if (!ephy_raw_clk)
+		return ERR_PTR(-ENOMEM);
+
+	init.name = "ephy-raw";
+	init.parent_data = &pdata;
+	init.num_parents = 1;
+	init.ops = &clk_ephy_raw_ops;
+
+	ephy_raw_clk->hw.init = &init;
+	ephy_raw_clk->regmap = regmap;
+
+	ret = devm_clk_hw_register(dev, &ephy_raw_clk->hw);
+	if (ret)
+		return ERR_PTR(ret);
+
+	return &ephy_raw_clk->hw;
+}
+
 static int ipq_cmn_pll_register_clks(struct platform_device *pdev)
 {
 	const struct cmn_pll_fixed_output_clk *p, *fixed_clk;
@@ -747,6 +829,10 @@ static int ipq_cmn_pll_register_clks(struct platform_device *pdev)
 			hw = ipq_cmn_pll_pon_refclk_register(pdev,
 							     cmn_pll->regmap,
 							     cmn_pll_hw);
+		} else if (!strcmp(fixed_clk[i].name, "ephy-raw")) {
+			hw = ipq_cmn_pll_ephy_raw_register(pdev,
+							   cmn_pll->regmap,
+							   cmn_pll_hw);
 		} else {
 			continue;
 		}

-- 
2.43.0


^ permalink raw reply related

* [PATCH 05/12] clk: qcom: ipq-cmn-pll: Add PPE clock support
From: Luo Jie @ 2026-07-24 15:00 UTC (permalink / raw)
  To: Bjorn Andersson, Michael Turquette, Stephen Boyd, Brian Masney,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Konrad Dybcio
  Cc: linux-arm-msm, linux-clk, linux-kernel, devicetree, quic_kkumarcs,
	quic_linchen, quic_leiwei, quic_suruchia, Luo Jie
In-Reply-To: <20260724-qcom_ipq5210_cmnpll-v1-0-a8b8679d215f@oss.qualcomm.com>

The PPE (packet process engine) clock is derived from the CMN PLL output
divided by 2 and then further divided by a configurable 6-bit divider
with a valid range of 8-63. It shares the CMN_PLL_NSS_PPE_FREQ_CTRL
register with the NSS clock but uses a different field (bits [5:0]).

The determine_rate operation is shared with the NSS clock since the
divider constraints and calculation are identical.

Signed-off-by: Luo Jie <jie.luo@oss.qualcomm.com>
---
 drivers/clk/qcom/ipq-cmn-pll.c | 81 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 81 insertions(+)

diff --git a/drivers/clk/qcom/ipq-cmn-pll.c b/drivers/clk/qcom/ipq-cmn-pll.c
index 3df326c22fba..020ad12ca1f2 100644
--- a/drivers/clk/qcom/ipq-cmn-pll.c
+++ b/drivers/clk/qcom/ipq-cmn-pll.c
@@ -68,6 +68,7 @@
 
 #define CMN_PLL_NSS_PPE_FREQ_CTRL		0x98
 #define CMN_PLL_NSS_CLK_SEL			GENMASK(13, 8)
+#define CMN_PLL_PPE_CLK_SEL			GENMASK(5, 0)
 #define CMN_PLL_NSS_PPE_DIV_MIN			8
 #define CMN_PLL_NSS_PPE_DIV_MAX			63
 
@@ -462,6 +463,83 @@ static struct clk_hw *ipq_cmn_pll_nss_register(struct platform_device *pdev,
 	return &nss_clk->hw;
 }
 
+/*
+ * PPE (Packet Process Engine) clock operations.
+ * The PPE clock is derived from CMN PLL rate / 2, then divided by
+ * a configurable 6-bit divider (8-63).
+ */
+static unsigned long clk_ppe_recalc_rate(struct clk_hw *hw,
+					 unsigned long parent_rate)
+{
+	struct clk_cmn_pll *ppe_clk = to_clk_cmn_pll(hw);
+	u32 val, div;
+
+	regmap_read(ppe_clk->regmap, CMN_PLL_NSS_PPE_FREQ_CTRL, &val);
+	div = FIELD_GET(CMN_PLL_PPE_CLK_SEL, val);
+	if (WARN_ON_ONCE(!div))
+		return 0;
+
+	return DIV_ROUND_CLOSEST_ULL((u64)parent_rate, 2ULL * div);
+}
+
+static int clk_ppe_set_rate(struct clk_hw *hw, unsigned long rate,
+			    unsigned long parent_rate)
+{
+	struct clk_cmn_pll *ppe_clk = to_clk_cmn_pll(hw);
+	unsigned long div;
+	int ret;
+
+	if (rate == 0)
+		return -EINVAL;
+
+	div = DIV_ROUND_CLOSEST_ULL((u64)parent_rate, 2ULL * rate);
+	if (div < CMN_PLL_NSS_PPE_DIV_MIN || div > CMN_PLL_NSS_PPE_DIV_MAX)
+		return -EINVAL;
+
+	ret = regmap_update_bits(ppe_clk->regmap, CMN_PLL_NSS_PPE_FREQ_CTRL,
+				 CMN_PLL_PPE_CLK_SEL,
+				 FIELD_PREP(CMN_PLL_PPE_CLK_SEL, div));
+	if (ret)
+		return ret;
+
+	return clk_cmn_pll_ana_soft_reset(ppe_clk->regmap);
+}
+
+static const struct clk_ops clk_ppe_ops = {
+	.recalc_rate = clk_ppe_recalc_rate,
+	.determine_rate = clk_nss_ppe_determine_rate,
+	.set_rate = clk_ppe_set_rate,
+};
+
+static struct clk_hw *ipq_cmn_pll_ppe_register(struct platform_device *pdev,
+					       struct regmap *regmap,
+					       struct clk_hw *cmn_pll_hw)
+{
+	struct clk_parent_data pdata = { .hw = cmn_pll_hw };
+	struct device *dev = &pdev->dev;
+	struct clk_init_data init = {};
+	struct clk_cmn_pll *ppe_clk;
+	int ret;
+
+	ppe_clk = devm_kzalloc(dev, sizeof(*ppe_clk), GFP_KERNEL);
+	if (!ppe_clk)
+		return ERR_PTR(-ENOMEM);
+
+	init.name = "ppe-clk";
+	init.parent_data = &pdata;
+	init.num_parents = 1;
+	init.ops = &clk_ppe_ops;
+
+	ppe_clk->hw.init = &init;
+	ppe_clk->regmap = regmap;
+
+	ret = devm_clk_hw_register(dev, &ppe_clk->hw);
+	if (ret)
+		return ERR_PTR(ret);
+
+	return &ppe_clk->hw;
+}
+
 static int ipq_cmn_pll_register_clks(struct platform_device *pdev)
 {
 	const struct cmn_pll_fixed_output_clk *p, *fixed_clk;
@@ -508,6 +586,9 @@ static int ipq_cmn_pll_register_clks(struct platform_device *pdev)
 		} else if (!strcmp(fixed_clk[i].name, "nss")) {
 			hw = ipq_cmn_pll_nss_register(pdev, cmn_pll->regmap,
 						      cmn_pll_hw);
+		} else if (!strcmp(fixed_clk[i].name, "ppe")) {
+			hw = ipq_cmn_pll_ppe_register(pdev, cmn_pll->regmap,
+						      cmn_pll_hw);
 		} else {
 			continue;
 		}

-- 
2.43.0


^ permalink raw reply related

* [PATCH 04/12] clk: qcom: ipq-cmn-pll: Add NSS clock support
From: Luo Jie @ 2026-07-24 15:00 UTC (permalink / raw)
  To: Bjorn Andersson, Michael Turquette, Stephen Boyd, Brian Masney,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Konrad Dybcio
  Cc: linux-arm-msm, linux-clk, linux-kernel, devicetree, quic_kkumarcs,
	quic_linchen, quic_leiwei, quic_suruchia, Luo Jie
In-Reply-To: <20260724-qcom_ipq5210_cmnpll-v1-0-a8b8679d215f@oss.qualcomm.com>

The NSS (network subsystem) clock is derived from the CMN PLL output
divided by 2 and then further divided by a configurable 6-bit divider
with a valid range of 8-63.

Signed-off-by: Luo Jie <jie.luo@oss.qualcomm.com>
---
 drivers/clk/qcom/ipq-cmn-pll.c | 119 +++++++++++++++++++++++++++++++++++++++--
 1 file changed, 114 insertions(+), 5 deletions(-)

diff --git a/drivers/clk/qcom/ipq-cmn-pll.c b/drivers/clk/qcom/ipq-cmn-pll.c
index aa319d577871..3df326c22fba 100644
--- a/drivers/clk/qcom/ipq-cmn-pll.c
+++ b/drivers/clk/qcom/ipq-cmn-pll.c
@@ -66,6 +66,11 @@
 #define CMN_PLL_LOCKED				0x64
 #define CMN_PLL_CLKS_LOCKED			BIT(8)
 
+#define CMN_PLL_NSS_PPE_FREQ_CTRL		0x98
+#define CMN_PLL_NSS_CLK_SEL			GENMASK(13, 8)
+#define CMN_PLL_NSS_PPE_DIV_MIN			8
+#define CMN_PLL_NSS_PPE_DIV_MAX			63
+
 #define CMN_PLL_POWER_ON_AND_RESET		0x780
 #define CMN_ANA_EN_SW_RSTN			BIT(6)
 
@@ -364,11 +369,105 @@ static struct clk_hw *ipq_cmn_pll_clk_hw_register(struct platform_device *pdev)
 	return &cmn_pll->hw;
 }
 
+/*
+ * NSS (Network Subsystem) clock operations.
+ * The NSS clock is derived from CMN PLL rate / 2, then divided by
+ * a configurable 6-bit divider (8-63).
+ */
+static unsigned long clk_nss_recalc_rate(struct clk_hw *hw,
+					 unsigned long parent_rate)
+{
+	struct clk_cmn_pll *nss_clk = to_clk_cmn_pll(hw);
+	u32 val, div;
+
+	regmap_read(nss_clk->regmap, CMN_PLL_NSS_PPE_FREQ_CTRL, &val);
+	div = FIELD_GET(CMN_PLL_NSS_CLK_SEL, val);
+	if (WARN_ON_ONCE(!div))
+		return 0;
+
+	return DIV_ROUND_CLOSEST_ULL((u64)parent_rate, 2ULL * div);
+}
+
+static int clk_nss_ppe_determine_rate(struct clk_hw *hw,
+				      struct clk_rate_request *req)
+{
+	unsigned long div;
+
+	if (WARN_ON_ONCE(!req->rate))
+		return 0;
+
+	div = DIV_ROUND_CLOSEST_ULL((u64)req->best_parent_rate, 2ULL * req->rate);
+	div = clamp_t(unsigned long, div, CMN_PLL_NSS_PPE_DIV_MIN,
+		      CMN_PLL_NSS_PPE_DIV_MAX);
+
+	req->rate = DIV_ROUND_CLOSEST_ULL((u64)req->best_parent_rate, 2ULL * div);
+	return 0;
+}
+
+static int clk_nss_set_rate(struct clk_hw *hw, unsigned long rate,
+			    unsigned long parent_rate)
+{
+	struct clk_cmn_pll *nss_clk = to_clk_cmn_pll(hw);
+	unsigned long div;
+	int ret;
+
+	if (rate == 0)
+		return -EINVAL;
+
+	div = DIV_ROUND_CLOSEST_ULL((u64)parent_rate, 2ULL * rate);
+	if (div < CMN_PLL_NSS_PPE_DIV_MIN || div > CMN_PLL_NSS_PPE_DIV_MAX)
+		return -EINVAL;
+
+	ret = regmap_update_bits(nss_clk->regmap, CMN_PLL_NSS_PPE_FREQ_CTRL,
+				 CMN_PLL_NSS_CLK_SEL,
+				 FIELD_PREP(CMN_PLL_NSS_CLK_SEL, div));
+	if (ret)
+		return ret;
+
+	return clk_cmn_pll_ana_soft_reset(nss_clk->regmap);
+}
+
+static const struct clk_ops clk_nss_ops = {
+	.recalc_rate = clk_nss_recalc_rate,
+	.determine_rate = clk_nss_ppe_determine_rate,
+	.set_rate = clk_nss_set_rate,
+};
+
+static struct clk_hw *ipq_cmn_pll_nss_register(struct platform_device *pdev,
+					       struct regmap *regmap,
+					       struct clk_hw *cmn_pll_hw)
+{
+	struct clk_parent_data pdata = { .hw = cmn_pll_hw };
+	struct device *dev = &pdev->dev;
+	struct clk_init_data init = {};
+	struct clk_cmn_pll *nss_clk;
+	int ret;
+
+	nss_clk = devm_kzalloc(dev, sizeof(*nss_clk), GFP_KERNEL);
+	if (!nss_clk)
+		return ERR_PTR(-ENOMEM);
+
+	init.name = "nss-clk";
+	init.parent_data = &pdata;
+	init.num_parents = 1;
+	init.ops = &clk_nss_ops;
+
+	nss_clk->hw.init = &init;
+	nss_clk->regmap = regmap;
+
+	ret = devm_clk_hw_register(dev, &nss_clk->hw);
+	if (ret)
+		return ERR_PTR(ret);
+
+	return &nss_clk->hw;
+}
+
 static int ipq_cmn_pll_register_clks(struct platform_device *pdev)
 {
 	const struct cmn_pll_fixed_output_clk *p, *fixed_clk;
 	struct clk_hw_onecell_data *hw_data;
 	struct device *dev = &pdev->dev;
+	struct clk_cmn_pll *cmn_pll;
 	struct clk_hw *cmn_pll_hw;
 	unsigned int num_clks;
 	struct clk_hw *hw;
@@ -395,14 +494,24 @@ static int ipq_cmn_pll_register_clks(struct platform_device *pdev)
 	if (IS_ERR(cmn_pll_hw))
 		return PTR_ERR(cmn_pll_hw);
 
+	cmn_pll = to_clk_cmn_pll(cmn_pll_hw);
+
 	/* Register the fixed rate output clocks. */
 	for (i = 0; i < num_clks; i++) {
-		struct clk_parent_data pdata = { .hw = cmn_pll_hw };
+		if (fixed_clk[i].rate) {
+			struct clk_parent_data pdata = { .hw = cmn_pll_hw };
+
+			hw = devm_clk_hw_register_fixed_rate_parent_data(dev,
+									 fixed_clk[i].name,
+									 &pdata, 0,
+									 fixed_clk[i].rate);
+		} else if (!strcmp(fixed_clk[i].name, "nss")) {
+			hw = ipq_cmn_pll_nss_register(pdev, cmn_pll->regmap,
+						      cmn_pll_hw);
+		} else {
+			continue;
+		}
 
-		hw = devm_clk_hw_register_fixed_rate_parent_data(dev,
-								 fixed_clk[i].name,
-								 &pdata, 0,
-								 fixed_clk[i].rate);
 		if (IS_ERR(hw))
 			return PTR_ERR(hw);
 

-- 
2.43.0


^ permalink raw reply related


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