[PATCH 3/6] media: rockchip: rkcif: count the MIPI capture errors

Daniel Golle daniel at makrotopia.org
Tue Nov 3 02:45:35 PST 2026


The VICAP MIPI block flags DMA FIFO overflows, a bandwidth shortage and
an RX FIFO overflow in its interrupt status, but the driver never
enables these sources and clears the status bits unread, so a capture
that loses data does so silently.

Enable the error sources while a stream runs on the interface, count
every class in a debugfs directory named after the interface and warn
with the raw status under a rate limit. The RK3588 lays out the error
bits differently and adds a per-ID size error; both layouts follow the
vendor driver.

Assisted-by: LLM
Signed-off-by: Daniel Golle <daniel at makrotopia.org>
---
 .../rockchip/rkcif/rkcif-capture-mipi.c       | 94 +++++++++++++++++++
 .../platform/rockchip/rkcif/rkcif-common.h    | 12 +++
 .../media/platform/rockchip/rkcif/rkcif-dev.c |  5 +
 .../platform/rockchip/rkcif/rkcif-regs.h      |  2 +
 4 files changed, 113 insertions(+)

diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c b/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c
index 53cfd37bf02bd..af0134e6c4c67 100644
--- a/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c
+++ b/drivers/media/platform/rockchip/rkcif/rkcif-capture-mipi.c
@@ -7,6 +7,7 @@
  * Copyright (C) 2025 Collabora, Ltd.
  */
 
+#include <linux/debugfs.h>
 #include <linux/interrupt.h>
 
 #include <media/mipi-csi2.h>
@@ -44,6 +45,35 @@
 
 #define RKCIF_MIPI_INT_FRAME0_END(id) BIT(8 + (id) * 2 + 0)
 #define RKCIF_MIPI_INT_FRAME1_END(id) BIT(8 + (id) * 2 + 1)
+#define RKCIF_MIPI_INT_ALL_END GENMASK(15, 8)
+
+#define RK3568_MIPI_INT_DMA_Y_FIFO_OVERFLOW BIT(16)
+#define RK3568_MIPI_INT_DMA_UV_FIFO_OVERFLOW BIT(17)
+#define RK3568_MIPI_INT_CONFIG_FIFO_OVERFLOW BIT(18)
+#define RK3568_MIPI_INT_BANDWIDTH_LACK BIT(19)
+#define RK3568_MIPI_INT_RX_FIFO_OVERFLOW BIT(20)
+
+#define RK3588_MIPI_INT_DMA_Y_FIFO_OVERFLOW BIT(16)
+#define RK3588_MIPI_INT_DMA_UV_FIFO_OVERFLOW BIT(17)
+#define RK3588_MIPI_INT_BANDWIDTH_LACK BIT(18)
+#define RK3588_MIPI_INT_RX_FIFO_OVERFLOW BIT(19)
+#define RK3588_MIPI_INT_SIZE_ERROR GENMASK(27, 24)
+
+static const struct rkcif_mipi_error rk3568_mipi_errors[] = {
+	{ RK3568_MIPI_INT_DMA_Y_FIFO_OVERFLOW, "dma_y_fifo_overflow" },
+	{ RK3568_MIPI_INT_DMA_UV_FIFO_OVERFLOW, "dma_uv_fifo_overflow" },
+	{ RK3568_MIPI_INT_CONFIG_FIFO_OVERFLOW, "config_fifo_overflow" },
+	{ RK3568_MIPI_INT_BANDWIDTH_LACK, "bandwidth_lack" },
+	{ RK3568_MIPI_INT_RX_FIFO_OVERFLOW, "rx_fifo_overflow" },
+};
+
+static const struct rkcif_mipi_error rk3588_mipi_errors[] = {
+	{ RK3588_MIPI_INT_DMA_Y_FIFO_OVERFLOW, "dma_y_fifo_overflow" },
+	{ RK3588_MIPI_INT_DMA_UV_FIFO_OVERFLOW, "dma_uv_fifo_overflow" },
+	{ RK3588_MIPI_INT_BANDWIDTH_LACK, "bandwidth_lack" },
+	{ RK3588_MIPI_INT_RX_FIFO_OVERFLOW, "rx_fifo_overflow" },
+	{ RK3588_MIPI_INT_SIZE_ERROR, "size_error" },
+};
 
 static const struct rkcif_output_fmt mipi_out_fmts[] = {
 	/* YUV formats */
@@ -420,10 +450,14 @@ rkcif_rk3568_mipi_ctrl0(struct rkcif_stream *stream,
 const struct rkcif_mipi_match_data rkcif_rk3568_vicap_mipi_match_data = {
 	.mipi_num = 1,
 	.mipi_ctrl0 = rkcif_rk3568_mipi_ctrl0,
+	.errors = rk3568_mipi_errors,
+	.errors_num = ARRAY_SIZE(rk3568_mipi_errors),
 	.regs = {
 		[RKCIF_MIPI_CTRL] = 0x20,
 		[RKCIF_MIPI_INTEN] = 0xa4,
 		[RKCIF_MIPI_INTSTAT] = 0xa8,
+		[RKCIF_MIPI_LINE_CNT_ID0_1] = 0xb4,
+		[RKCIF_MIPI_LINE_CNT_ID2_3] = 0xb8,
 	},
 	.regs_id = {
 		[RKCIF_ID0] = {
@@ -531,10 +565,14 @@ rkcif_rk3588_mipi_ctrl0(struct rkcif_stream *stream,
 const struct rkcif_mipi_match_data rkcif_rk3588_vicap_mipi_match_data = {
 	.mipi_num = 6,
 	.mipi_ctrl0 = rkcif_rk3588_mipi_ctrl0,
+	.errors = rk3588_mipi_errors,
+	.errors_num = ARRAY_SIZE(rk3588_mipi_errors),
 	.regs = {
 		[RKCIF_MIPI_CTRL] = 0x20,
 		[RKCIF_MIPI_INTEN] = 0x74,
 		[RKCIF_MIPI_INTSTAT] = 0x78,
+		[RKCIF_MIPI_LINE_CNT_ID0_1] = 0x84,
+		[RKCIF_MIPI_LINE_CNT_ID2_3] = 0x88,
 	},
 	.regs_id = {
 		[RKCIF_ID0] = {
@@ -699,6 +737,53 @@ rkcif_mipi_stream_read(struct rkcif_stream *stream, unsigned int index)
 	return readl(stream->rkcif->base_addr + addr);
 }
 
+static u32 rkcif_mipi_error_mask(struct rkcif_interface *interface)
+{
+	const struct rkcif_mipi_match_data *match_data =
+		interface->rkcif->match_data->mipi;
+	unsigned int i;
+	u32 mask = 0;
+
+	for (i = 0; i < match_data->errors_num; i++)
+		mask |= match_data->errors[i].mask;
+
+	return mask;
+}
+
+static void rkcif_mipi_handle_errors(struct rkcif_interface *interface,
+				     u32 intstat)
+{
+	const struct rkcif_mipi_match_data *match_data =
+		interface->rkcif->match_data->mipi;
+	unsigned int i;
+
+	intstat &= rkcif_mipi_error_mask(interface);
+	if (!intstat)
+		return;
+
+	for (i = 0; i < match_data->errors_num; i++)
+		if (intstat & match_data->errors[i].mask)
+			interface->error_count[i]++;
+
+	dev_warn_ratelimited(interface->rkcif->dev, "%s: capture errors %#x\n",
+			     interface->sd.name, intstat);
+}
+
+static void rkcif_mipi_debugfs_init(struct rkcif_interface *interface)
+{
+	const struct rkcif_mipi_match_data *match_data =
+		interface->rkcif->match_data->mipi;
+	struct dentry *parent = interface->rkcif->debugfs_dir;
+	unsigned int i;
+
+	interface->debugfs_dir = debugfs_create_dir(interface->sd.name, parent);
+
+	for (i = 0; i < match_data->errors_num; i++)
+		debugfs_create_ulong(match_data->errors[i].name, 0444,
+				     interface->debugfs_dir,
+				     &interface->error_count[i]);
+}
+
 static void rkcif_mipi_queue_buffer(struct rkcif_stream *stream,
 				    unsigned int index)
 {
@@ -742,6 +827,7 @@ static int rkcif_mipi_start_streaming(struct rkcif_stream *stream)
 
 	int_mask |= RKCIF_MIPI_INT_FRAME0_END(stream->id);
 	int_mask |= RKCIF_MIPI_INT_FRAME1_END(stream->id);
+	int_mask |= rkcif_mipi_error_mask(interface);
 
 	spin_lock_irqsave(&interface->irq_lock, flags);
 	int_temp = rkcif_mipi_read(interface, RKCIF_MIPI_INTEN);
@@ -785,6 +871,8 @@ static void rkcif_mipi_stop_streaming(struct rkcif_stream *stream)
 	spin_lock_irqsave(&interface->irq_lock, flags);
 	int_temp = rkcif_mipi_read(interface, RKCIF_MIPI_INTEN);
 	int_temp &= ~int_mask;
+	if (!(int_temp & RKCIF_MIPI_INT_ALL_END))
+		int_temp &= ~rkcif_mipi_error_mask(interface);
 	rkcif_mipi_write(interface, RKCIF_MIPI_INTEN, int_temp);
 
 	int_temp = rkcif_mipi_read(interface, RKCIF_MIPI_INTSTAT);
@@ -836,6 +924,8 @@ irqreturn_t rkcif_mipi_isr(int irq, void *ctx)
 		if (intstat)
 			ret = IRQ_HANDLED;
 
+		rkcif_mipi_handle_errors(interface, intstat);
+
 		for (unsigned int j = 0; j < interface->streams_num; j++) {
 			struct rkcif_stream *stream = &interface->streams[j];
 
@@ -879,6 +969,8 @@ int rkcif_mipi_register(struct rkcif_device *rkcif)
 		if (ret)
 			continue;
 
+		rkcif_mipi_debugfs_init(interface);
+
 		for (unsigned int j = 0; j < RKCIF_ID_MAX; j++) {
 			struct rkcif_stream *stream = &interface->streams[j];
 
@@ -906,6 +998,7 @@ int rkcif_mipi_register(struct rkcif_device *rkcif)
 		for (unsigned int j = 0; j < interface->streams_num; j++)
 			rkcif_stream_unregister(&interface->streams[j]);
 
+		debugfs_remove_recursive(interface->debugfs_dir);
 		rkcif_interface_unregister(interface);
 	}
 	return ret;
@@ -923,6 +1016,7 @@ void rkcif_mipi_unregister(struct rkcif_device *rkcif)
 		for (unsigned int j = 0; j < interface->streams_num; j++)
 			rkcif_stream_unregister(&interface->streams[j]);
 
+		debugfs_remove_recursive(interface->debugfs_dir);
 		rkcif_interface_unregister(interface);
 	}
 }
diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-common.h b/drivers/media/platform/rockchip/rkcif/rkcif-common.h
index 16a5ecb25c2fb..13708435dd432 100644
--- a/drivers/media/platform/rockchip/rkcif/rkcif-common.h
+++ b/drivers/media/platform/rockchip/rkcif/rkcif-common.h
@@ -28,6 +28,7 @@
 
 #define RKCIF_DRIVER_NAME "rockchip-cif"
 #define RKCIF_CLK_MAX	  5
+#define RKCIF_MIPI_ERRORS_MAX 5
 
 enum rkcif_format_type {
 	RKCIF_FMT_TYPE_INVALID,
@@ -198,18 +199,28 @@ struct rkcif_interface {
 		struct rkcif_dvp dvp;
 	};
 
+	struct dentry *debugfs_dir;
+	unsigned long error_count[RKCIF_MIPI_ERRORS_MAX];
+
 	/* interrupt enable and status are shared by the streams of a block */
 	spinlock_t irq_lock;
 
 	void (*set_crop)(struct rkcif_stream *stream, u16 left, u16 top);
 };
 
+struct rkcif_mipi_error {
+	u32 mask;
+	const char *name;
+};
+
 struct rkcif_mipi_match_data {
 	unsigned int mipi_num;
 	unsigned int regs[RKCIF_MIPI_REGISTER_MAX];
 	unsigned int regs_id[RKCIF_ID_MAX][RKCIF_MIPI_ID_REGISTER_MAX];
 	u32 (*mipi_ctrl0)(struct rkcif_stream *stream,
 			  const struct rkcif_output_fmt *active_out_fmt);
+	const struct rkcif_mipi_error *errors;
+	unsigned int errors_num;
 	struct {
 		unsigned int offset;
 	} blocks[RKCIF_MIPI_MAX - RKCIF_MIPI_BASE];
@@ -248,6 +259,7 @@ struct rkcif_device {
 	struct media_device media_dev;
 	struct v4l2_device v4l2_dev;
 	struct v4l2_async_notifier notifier;
+	struct dentry *debugfs_dir;
 };
 
 #endif
diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-dev.c b/drivers/media/platform/rockchip/rkcif/rkcif-dev.c
index fbc8ed78d99ea..630a5ccf59c11 100644
--- a/drivers/media/platform/rockchip/rkcif/rkcif-dev.c
+++ b/drivers/media/platform/rockchip/rkcif/rkcif-dev.c
@@ -10,6 +10,7 @@
  */
 
 #include <linux/clk.h>
+#include <linux/debugfs.h>
 #include <linux/delay.h>
 #include <linux/interrupt.h>
 #include <linux/mfd/syscon.h>
@@ -231,6 +232,8 @@ static int rkcif_probe(struct platform_device *pdev)
 	v4l2_async_nf_init(&rkcif->notifier, &rkcif->v4l2_dev);
 	rkcif->notifier.ops = &rkcif_notifier_ops;
 
+	rkcif->debugfs_dir = debugfs_create_dir(dev_name(dev), NULL);
+
 	ret = rkcif_register(rkcif);
 	if (ret) {
 		dev_err(dev, "failed to register media entities: %d\n", ret);
@@ -246,6 +249,7 @@ static int rkcif_probe(struct platform_device *pdev)
 err_rkcif_unregister:
 	rkcif_unregister(rkcif);
 err_notifier_cleanup:
+	debugfs_remove_recursive(rkcif->debugfs_dir);
 	v4l2_async_nf_cleanup(&rkcif->notifier);
 	media_device_unregister(&rkcif->media_dev);
 err_v4l2_dev_unregister:
@@ -262,6 +266,7 @@ static void rkcif_remove(struct platform_device *pdev)
 
 	v4l2_async_nf_unregister(&rkcif->notifier);
 	rkcif_unregister(rkcif);
+	debugfs_remove_recursive(rkcif->debugfs_dir);
 	v4l2_async_nf_cleanup(&rkcif->notifier);
 	media_device_unregister(&rkcif->media_dev);
 	v4l2_device_unregister(&rkcif->v4l2_dev);
diff --git a/drivers/media/platform/rockchip/rkcif/rkcif-regs.h b/drivers/media/platform/rockchip/rkcif/rkcif-regs.h
index 3cf7ee19de300..92da7497b902a 100644
--- a/drivers/media/platform/rockchip/rkcif/rkcif-regs.h
+++ b/drivers/media/platform/rockchip/rkcif/rkcif-regs.h
@@ -132,6 +132,8 @@ enum rkcif_mipi_register_index {
 	RKCIF_MIPI_CTRL,
 	RKCIF_MIPI_INTEN,
 	RKCIF_MIPI_INTSTAT,
+	RKCIF_MIPI_LINE_CNT_ID0_1,
+	RKCIF_MIPI_LINE_CNT_ID2_3,
 	RKCIF_MIPI_REGISTER_MAX
 };
 
-- 
2.56.0



More information about the linux-arm-kernel mailing list