[PATCH V3] media: i2c: imx334: add new link frequency configuration

shravan kumar <[email protected]> Mon, 3 Aug 2026 11:20:34 +0530
Newsgroups org.kernel.vger.linux-media,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
From: Shravan Chippa <[email protected]>

Add 222 MHz link frequency support and refactor clock registers into
per-frequency register lists. Introduce imx334_clk_params to map each
link frequency to its register set and supported resolution range.
Make the link frequency control writable so userspace can select the
desired operating point.

Signed-off-by: Shravan Chippa <[email protected]>

---
Changes from V2 -> V3

- Refactor: Introduce struct imx334_clk_params
    - Maps each link frequency to its clock register set and supported
      resolution range (width_max, height_max, width_min, height_min).
    - Includes a default_mode pointer for fallback when resolution is
      out of range for the selected link frequency.

- Enhancement: Make link_freq control writable
    - Removed V4L2_CTRL_FLAG_READ_ONLY from link_freq_ctrl so userspace
      can select the desired link frequency operating point.

- Enhancement: Add resolution bounds checking in set_pad_format
    - imx334_set_pad_format() now validates the selected mode against
      the clk_params width/height constraints and falls back to the
      default mode if out of range.

- Cleanup: Remove __v4l2_ctrl_s_ctrl for link_freq in update_controls
    - Link frequency is no longer tied to mode, so the explicit control
      update in imx334_update_controls() was removed.


Changes from V1 -> V2

- Fix: Default mode selection bug
    - Problem: Used __ffs(link_freq_bitmap) as index into the compacted
      active_modes array. This index corresponds to a bit position in the
      bitmap, not an array index in the filtered modes list.
    - Fix: Use index 0, which is always the first valid mode in the
      filtered array.

- Fix: Rename fields for clarity
    - Renamed struct fields:
      * new_supported_modes -> active_modes
      * new_modes_size -> num_active_modes
    - Updated kernel-doc header for struct imx334 to document new fields.

- Fix: INCKSEL2 register write - add error handling and switch-case
    - Location: imx334_enable_streams()
    - Problem: Original code only handled 222 MHz case with no error
      checking (passed NULL to cci_write).
    - Fix: Replaced with switch-case covering all three link frequencies:
      * 891 MHz -> INCKSEL2 = 0x02
      * 445 MHz -> INCKSEL2 = 0x06
      * 222 MHz -> INCKSEL2 = 0x0a
    - Added proper error handling using &ret accumulator pattern with
      dev_err and goto err_rpm_put on failure.

- Fix: Use BIT() macro
    - Location: imx334_update_supported_mode_array()
    - Replaced (1 << i) with BIT(i) for kernel coding style compliance.

- Fix: Use devm_kmalloc_array() for overflow-safe allocation
    - Location: imx334_update_supported_mode_array()
    - Replaced: devm_kmalloc(dev, n * sizeof(struct imx334_mode),
 GFP_KERNEL)
    - With: devm_kmalloc_array(dev, n, sizeof(*temp_ptr), GFP_KERNEL)
    - Provides overflow-safe multiplication and uses sizeof(*ptr) idiom.

- Cleanup: Condensed copy loop
    - Location: imx334_update_supported_mode_array(), second loop
    - Simplified the struct copy with post-increment:
      temp_ptr[size++] = supported_modes[j];

- Fix: Updated function kernel-doc comment
    - Location: imx334_update_supported_mode_array()
    - Reworded: "Search for the supported modes add them in the new list"
    - To: "Build filtered modes array based on DTS link frequencies"

- IMX334_LINK_FREQ_222M changed from 222500000 to 222750000

---

 drivers/media/i2c/imx334.c | 132 +++++++++++++++++++++++++++++++------
 1 file changed, 112 insertions(+), 20 deletions(-)

diff --git a/drivers/media/i2c/imx334.c b/drivers/media/i2c/imx334.c
index 553a16b84f4d..fb036873859c 100644
--- a/drivers/media/i2c/imx334.c
+++ b/drivers/media/i2c/imx334.c
@@ -109,6 +109,7 @@
 /* CSI2 HW configuration */
 #define IMX334_LINK_FREQ_891M		891000000
 #define IMX334_LINK_FREQ_445M		445500000
+#define IMX334_LINK_FREQ_222M		222750000
 #define IMX334_NUM_DATA_LANES		4
 
 #define IMX334_REG_MIN			0x00
@@ -154,7 +155,6 @@ struct imx334_reg_list {
  * @vblank_min: Minimal vertical blanking in lines
  * @vblank_max: Maximum vertical blanking in lines
  * @pclk: Sensor pixel clock
- * @link_freq_idx: Link frequency index
  * @reg_list: Register list for sensor mode
  */
 struct imx334_mode {
@@ -165,7 +165,28 @@ struct imx334_mode {
 	u32 vblank_min;
 	u32 vblank_max;
 	u64 pclk;
-	u32 link_freq_idx;
+	struct imx334_reg_list reg_list;
+};
+
+/**
+ * struct imx334_clk_params - imx334 sensor clock parameters
+ * @data_rate_per_lane: Data rate per lane in bits per second
+ * @link_freq: Link frequency in Hz
+ * @width_max: Maximum image width in pixels
+ * @height_max: Maximum image height in pixels
+ * @width_min: Minimum image width in pixels
+ * @height_min: Minimum image height in pixels
+ * @default_mode: Pointer to the default sensor mode
+ * @reg_list: Register list for clock configuration
+ */
+struct imx334_clk_params {
+	u32 data_rate_per_lane;
+	u32 link_freq;
+	u32 width_max;
+	u32 height_max;
+	u32 width_min;
+	u32 height_min;
+	const struct imx334_mode *default_mode;
 	struct imx334_reg_list reg_list;
 };
 
@@ -216,6 +237,7 @@ struct imx334 {
 static const s64 link_freq[] = {
 	IMX334_LINK_FREQ_891M,
 	IMX334_LINK_FREQ_445M,
+	IMX334_LINK_FREQ_222M,
 };
 
 /* Sensor common mode registers values */
@@ -233,13 +255,6 @@ static const struct cci_reg_sequence common_mode_regs[] = {
 	{ IMX334_REG_UNREAD_PARAM6,	0x0008 },
 	{ IMX334_REG_XVS_XHS_OUTSEL,	0x20 },
 	{ IMX334_REG_XVS_XHS_DRV,	0x0f },
-	{ IMX334_REG_BCWAIT_TIME,	0x3b },
-	{ IMX334_REG_CPWAIT_TIME,	0x2a },
-	{ IMX334_REG_INCKSEL1,		0x0129 },
-	{ IMX334_REG_INCKSEL2,		0x06 },
-	{ IMX334_REG_INCKSEL3,		0xa0 },
-	{ IMX334_REG_INCKSEL4,		0x7e },
-	{ IMX334_REG_SYS_MODE,		0x02 },
 	{ IMX334_REG_HADD_VADD,		0x00 },
 	{ IMX334_REG_VALID_EXPAND,	0x03 },
 	{ IMX334_REG_TCYCLE,		0x00 },
@@ -397,6 +412,39 @@ static const struct cci_reg_sequence mode_3840x2160_regs[] = {
 	{ IMX334_REG_TPLX,		0x005f },
 };
 
+/* Data rate 1782Mbps per lane and 891Mhz link frequency */
+static const struct cci_reg_sequence link_freq_891m_regs[] = {
+	{ IMX334_REG_BCWAIT_TIME,	0x3b },
+	{ IMX334_REG_CPWAIT_TIME,	0x2a },
+	{ IMX334_REG_INCKSEL1,		0x0129 },
+	{ IMX334_REG_INCKSEL2,		0x02 },
+	{ IMX334_REG_INCKSEL3,		0xa0 },
+	{ IMX334_REG_INCKSEL4,		0x7e },
+	{ IMX334_REG_SYS_MODE,		0x00 },
+};
+
+/* Data rate 891Mbps per lane and 445Mhz link frequency */
+static const struct cci_reg_sequence link_freq_445m_regs[] = {
+	{ IMX334_REG_BCWAIT_TIME,	0x3b },
+	{ IMX334_REG_CPWAIT_TIME,	0x2a },
+	{ IMX334_REG_INCKSEL1,		0x0129 },
+	{ IMX334_REG_INCKSEL2,		0x06 },
+	{ IMX334_REG_INCKSEL3,		0xa0 },
+	{ IMX334_REG_INCKSEL4,		0x7e },
+	{ IMX334_REG_SYS_MODE,		0x02 },
+};
+
+/* Data rate 445Mbps per lane and 222Mhz link frequency */
+static const struct cci_reg_sequence link_freq_222m_regs[] = {
+	{ IMX334_REG_BCWAIT_TIME,	0x3b },
+	{ IMX334_REG_CPWAIT_TIME,	0x2a },
+	{ IMX334_REG_INCKSEL1,		0x0129 },
+	{ IMX334_REG_INCKSEL2,		0x0a },
+	{ IMX334_REG_INCKSEL3,		0xa0 },
+	{ IMX334_REG_INCKSEL4,		0x7e },
+	{ IMX334_REG_SYS_MODE,		0x02 },
+};
+
 static const char * const imx334_test_pattern_menu[] = {
 	"Disabled",
 	"Vertical Color Bars",
@@ -442,7 +490,6 @@ static const struct imx334_mode supported_modes[] = {
 		.vblank_min = 90,
 		.vblank_max = 132840,
 		.pclk = 594000000,
-		.link_freq_idx = 0,
 		.reg_list = {
 			.num_of_regs = ARRAY_SIZE(mode_3840x2160_regs),
 			.regs = mode_3840x2160_regs,
@@ -455,7 +502,6 @@ static const struct imx334_mode supported_modes[] = {
 		.vblank_min = 45,
 		.vblank_max = 132840,
 		.pclk = 297000000,
-		.link_freq_idx = 1,
 		.reg_list = {
 			.num_of_regs = ARRAY_SIZE(mode_1920x1080_regs),
 			.regs = mode_1920x1080_regs,
@@ -468,7 +514,6 @@ static const struct imx334_mode supported_modes[] = {
 		.vblank_min = 45,
 		.vblank_max = 132840,
 		.pclk = 297000000,
-		.link_freq_idx = 1,
 		.reg_list = {
 			.num_of_regs = ARRAY_SIZE(mode_1280x720_regs),
 			.regs = mode_1280x720_regs,
@@ -481,7 +526,6 @@ static const struct imx334_mode supported_modes[] = {
 		.vblank_min = 45,
 		.vblank_max = 132840,
 		.pclk = 297000000,
-		.link_freq_idx = 1,
 		.reg_list = {
 			.num_of_regs = ARRAY_SIZE(mode_640x480_regs),
 			.regs = mode_640x480_regs,
@@ -489,6 +533,46 @@ static const struct imx334_mode supported_modes[] = {
 	},
 };
 
+static const struct imx334_clk_params imx334_clk_params[] = {
+	{
+		.data_rate_per_lane = 1782000000,
+		.link_freq = IMX334_LINK_FREQ_891M,
+		.width_max = 3840,
+		.height_max = 2160,
+		.width_min = 3840,
+		.height_min = 2160,
+		.default_mode = &supported_modes[0], /* 3840x2160 */
+		.reg_list = {
+			.num_of_regs = ARRAY_SIZE(link_freq_891m_regs),
+			.regs = link_freq_891m_regs,
+		},
+	}, {
+		.data_rate_per_lane = 891000000,
+		.link_freq = IMX334_LINK_FREQ_445M,
+		.width_max = 1920,
+		.height_max = 1080,
+		.width_min = 640,
+		.height_min = 480,
+		.default_mode = &supported_modes[1], /* 1920x1080 */
+		.reg_list = {
+			.num_of_regs = ARRAY_SIZE(link_freq_445m_regs),
+			.regs = link_freq_445m_regs,
+		},
+	}, {
+		.data_rate_per_lane = 445500000,
+		.link_freq = IMX334_LINK_FREQ_222M,
+		.width_max = 1920,
+		.height_max = 1080,
+		.width_min = 640,
+		.height_min = 480,
+		.default_mode = &supported_modes[1], /* 1920x1080 */
+		.reg_list = {
+			.num_of_regs = ARRAY_SIZE(link_freq_222m_regs),
+			.regs = link_freq_222m_regs,
+		},
+	}
+};
+
 /**
  * to_imx334() - imv334 V4L2 sub-device to imx334 device.
  * @subdev: pointer to imx334 V4L2 sub-device
@@ -512,10 +596,6 @@ static int imx334_update_controls(struct imx334 *imx334,
 {
 	int ret;
 
-	ret = __v4l2_ctrl_s_ctrl(imx334->link_freq_ctrl, mode->link_freq_idx);
-	if (ret)
-		return ret;
-
 	ret = __v4l2_ctrl_modify_range(imx334->pclk_ctrl, mode->pclk,
 				       mode->pclk, 1, mode->pclk);
 	if (ret)
@@ -746,6 +826,7 @@ static int imx334_set_pad_format(struct v4l2_subdev *sd,
 {
 	struct imx334 *imx334 = to_imx334(sd);
 	const struct imx334_mode *mode;
+	const struct imx334_clk_params *clk_params;
 	int ret = 0;
 
 	mode = v4l2_find_nearest_size(supported_modes,
@@ -753,6 +834,11 @@ static int imx334_set_pad_format(struct v4l2_subdev *sd,
 				      width, height,
 				      fmt->format.width, fmt->format.height);
 
+	clk_params = &imx334_clk_params[imx334->link_freq_ctrl->val];
+	if (mode->width > clk_params->width_max || mode->height > clk_params->height_max ||
+	    mode->width < clk_params->width_min || mode->height < clk_params->height_min)
+		mode = clk_params->default_mode;
+
 	imx334_fill_pad_format(imx334, mode, fmt);
 	fmt->format.code = imx334_get_format_code(imx334, fmt->format.code);
 
@@ -824,6 +910,15 @@ static int imx334_enable_streams(struct v4l2_subdev *sd,
 		goto err_rpm_put;
 	}
 
+	/* Write sensor link freq registers */
+	reg_list = &imx334_clk_params[imx334->link_freq_ctrl->val].reg_list;
+	ret = cci_multi_reg_write(imx334->cci, reg_list->regs,
+				  reg_list->num_of_regs, NULL);
+	if (ret) {
+		dev_err(imx334->dev, "fail to write initial registers\n");
+		goto err_rpm_put;
+	}
+
 	/* Write sensor mode registers */
 	reg_list = &imx334->cur_mode->reg_list;
 	ret = cci_multi_reg_write(imx334->cci, reg_list->regs,
@@ -1096,9 +1191,6 @@ static int imx334_init_controls(struct imx334 *imx334)
 							__ffs(imx334->link_freq_bitmap),
 							link_freq);
 
-	if (imx334->link_freq_ctrl)
-		imx334->link_freq_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
-
 	imx334->hblank_ctrl = v4l2_ctrl_new_std(ctrl_hdlr,
 						&imx334_ctrl_ops,
 						V4L2_CID_HBLANK,
-- 
2.34.1