[PATCH v3 21/31] media: synopsys: csi2: Add irq support to record error count
From: Frank Li <Frank.Li@nxp.com>
Date: 2025-08-21 20:18:22
Also in:
imx, linux-devicetree, linux-media, linux-phy, linux-staging, lkml
Subsystem:
media input infrastructure (v4l/dvb), the rest · Maintainers:
Mauro Carvalho Chehab, Linus Torvalds
Add irq support to record error count. Use debugfs to show such error count. Signed-off-by: Frank Li <Frank.Li@nxp.com> --- drivers/media/platform/synopsys/mipi-csi2.c | 171 ++++++++++++++++++++++++++++ include/media/dw-mipi-csi2.h | 8 ++ 2 files changed, 179 insertions(+)
diff --git a/drivers/media/platform/synopsys/mipi-csi2.c b/drivers/media/platform/synopsys/mipi-csi2.c
index 4d67ab978db56d8075824ba515876193499c0c97..e628cc67046b05bf0178c002a8bdf4bbcd64b30e 100644
--- a/drivers/media/platform/synopsys/mipi-csi2.c
+++ b/drivers/media/platform/synopsys/mipi-csi2.c@@ -4,6 +4,7 @@ * * Copyright (c) 2012-2017 Mentor Graphics Inc. */ +#include <linux/debugfs.h> #include <linux/export.h> #include <linux/clk.h> #include <linux/interrupt.h>
@@ -37,6 +38,19 @@ struct dw_csi2_regs { u32 msk2; u32 phy_tst_ctrl0; u32 phy_tst_ctrl1; + u32 int_st_main; + u32 int_st_dphy_fatal; + u32 int_msk_dphy_fatal; + u32 int_force_dphy_fatal; + u32 int_st_pkt_fatal; + u32 int_msk_pkt_fatal; + u32 int_force_pkt_fatal; + u32 int_st_dphy; + u32 int_msk_dphy; + u32 int_force_dphy; + u32 int_st_ipi_fatal; + u32 int_msk_ipi_fatal; + u32 int_force_ipi_fatal; }; /* Help check wrong access unexisted register at difference IP version */
@@ -65,6 +79,7 @@ static const struct dw_csi2_regs dw_csi2_v150 = { .version = DW_REG(0x0), .n_lanes = DW_REG(0x4), .resetn = DW_REG(0x8), + .int_st_main = DW_REG(0xc), .data_ids_1 = DW_REG(0x10), .data_ids_2 = DW_REG(0x14), .phy_shutdownz = DW_REG(0x40),
@@ -72,8 +87,48 @@ static const struct dw_csi2_regs dw_csi2_v150 = { .phy_state = DW_REG(0x48), .phy_tst_ctrl0 = DW_REG(0x50), .phy_tst_ctrl1 = DW_REG(0x54), + .int_st_dphy_fatal = DW_REG(0xe0), + .int_msk_dphy_fatal = DW_REG(0xe4), + .int_force_dphy_fatal = DW_REG(0xe8), + .int_st_pkt_fatal = DW_REG(0xf0), + .int_msk_pkt_fatal = DW_REG(0xf4), + .int_force_pkt_fatal = DW_REG(0xf8), + .int_st_dphy = DW_REG(0x110), + .int_msk_dphy = DW_REG(0x114), + .int_force_dphy = DW_REG(0x118), + .int_st_ipi_fatal = DW_REG(0x140), + .int_msk_ipi_fatal = DW_REG(0x144), + .int_force_ipi_fatal = DW_REG(0x148), }; +#define INT_ST_MAIN_FATAL_ERR_PHY BIT(0) +#define INT_ST_MAIN_FATAL_ERR_PKT BIT(1) +#define INT_ST_MAIN_FATAL_ERR_BNDRY_FRAMEL BIT(2) +#define INT_ST_MAIN_FATAL_ERR_SEQ_FRAME BIT(3) +#define INT_ST_MAIN_FATAL_ERR_CRC_FRAME BIT(4) +#define INT_ST_MAIN_FATAL_ERR_PLD_CRC BIT(5) +#define INT_ST_MAIN_ERR_DID BIT(6) +#define INT_ST_MAIN_ERR_ECC BIT(7) +#define INT_ST_MAIN_ERR_PHY BIT(16) +#define INT_ST_MAIN_FATAL_ERR_IPI BIT(18) + +#define INT_MSK_DPHY_FATAL_ERR_SOT_LANE0 BIT(0) +#define INT_MSK_DPHY_FATAL_ERR_SOT_LANE1 BIT(1) + +#define INT_ST_PKT_FATAL_ERR_ECC BIT(0) +#define INT_ST_PKT_FATAL_ERR_PAYLOAD BIT(1) + +#define INT_MSK_PKT_FATAL_ERR_ECC BIT(0) +#define INT_MSK_PKT_FATAL_ERR_PAYLOAD BIT(1) + +#define INT_ST_IPI_FATAL_ERR_IFFIFO_UNDERFLOW BIT(0) +#define INT_ST_IPI_FATAL_ERR_IFFIFO_OVERFLOW BIT(1) +#define INT_ST_IPI_FATAL_ERR_FRAME_SYNC BIT(2) +#define INT_ST_IPI_FATAL_ERR_FIFO_NOT_EMPTY BIT(3) +#define INT_ST_IPI_FATAL_ERR_HLINE_TIME BIT(4) +#define INT_ST_IPI_FATAL_ERR_FIFO_OVERFLOW BIT(5) +#define INT_ST_IPI_FATAL_ERR_PD_FIFO_OVERFLOW BIT(6) + static int dw_csi2_reg_err(struct dw_mipi_csi2_dev *csi2, const char *name) { dev_err_once(csi2->dev, "access to unexisted register: %s", name);
@@ -108,6 +163,25 @@ static inline struct dw_mipi_csi2_dev *notifier_to_dev(struct v4l2_async_notifie return container_of(n, struct dw_mipi_csi2_dev, notifier); } +struct dw_csi2_event { + u32 mask; + const char * const name; + u32 counter; +}; + +static const struct dw_csi2_event dw_events[] = { + { INT_ST_MAIN_FATAL_ERR_IPI, "IPI Interface Fatal Error" }, + { INT_ST_MAIN_ERR_PHY, "PHY Error" }, + { INT_ST_MAIN_ERR_ECC, "Header Single Bit Error" }, + { INT_ST_MAIN_ERR_DID, "Data ID Error" }, + { INT_ST_MAIN_FATAL_ERR_PLD_CRC, "Payload CRC Fatal Error" }, + { INT_ST_MAIN_FATAL_ERR_CRC_FRAME, "Frame CRC Fatal Error" }, + { INT_ST_MAIN_FATAL_ERR_SEQ_FRAME, "Frame Sequence Fatal Error" }, + { INT_ST_MAIN_FATAL_ERR_BNDRY_FRAMEL, "Frame Boundaries Fatal Error" }, + { INT_ST_MAIN_FATAL_ERR_PKT, "Packet Construction Fatal Error" }, + { INT_ST_MAIN_FATAL_ERR_PHY, "PHY Fatal Error" }, +}; + /* * The required sequence of MIPI CSI-2 startup as specified in the i.MX6 * reference manual is as follows:
@@ -338,6 +412,40 @@ static int dw_mipi_csi2_phy_prep(struct dw_mipi_csi2_dev *csi2, int bpp) return ret; } +static void dw_csi2_enable_irq(struct dw_mipi_csi2_dev *csi2) +{ + u32 val; + + /* Define errors to be enabled */ + val = INT_MSK_DPHY_FATAL_ERR_SOT_LANE0 | + INT_MSK_DPHY_FATAL_ERR_SOT_LANE0; + dw_writel(csi2, val, int_msk_dphy_fatal); + + val = INT_ST_PKT_FATAL_ERR_ECC | INT_ST_PKT_FATAL_ERR_PAYLOAD; + dw_writel(csi2, val, int_msk_pkt_fatal); + + val = INT_MSK_PKT_FATAL_ERR_ECC | INT_MSK_PKT_FATAL_ERR_PAYLOAD; + dw_writel(csi2, val, int_msk_dphy); + + val = INT_ST_IPI_FATAL_ERR_IFFIFO_UNDERFLOW | + INT_ST_IPI_FATAL_ERR_IFFIFO_OVERFLOW | + INT_ST_IPI_FATAL_ERR_FRAME_SYNC | + INT_ST_IPI_FATAL_ERR_FIFO_NOT_EMPTY | + INT_ST_IPI_FATAL_ERR_HLINE_TIME | + INT_ST_IPI_FATAL_ERR_FIFO_OVERFLOW | + INT_ST_IPI_FATAL_ERR_PD_FIFO_OVERFLOW; + + dw_writel(csi2, val, int_msk_ipi_fatal); +} + +static void dw_csi2_disable_irq(struct dw_mipi_csi2_dev *csi2) +{ + dw_writel(csi2, 0, int_msk_dphy_fatal); + dw_writel(csi2, 0, int_msk_pkt_fatal); + dw_writel(csi2, 0, int_msk_dphy); + dw_writel(csi2, 0, int_msk_ipi_fatal); +} + static int csi2_start(struct dw_mipi_csi2_dev *csi2, int bpp) { unsigned int lanes;
@@ -393,6 +501,8 @@ static int csi2_start(struct dw_mipi_csi2_dev *csi2, int bpp) if (ret) goto err_stop_upstream; + dw_csi2_enable_irq(csi2); + return 0; err_stop_upstream:
@@ -422,6 +532,8 @@ static void csi2_stop(struct dw_mipi_csi2_dev *csi2) csi2_enable(csi2, false); + dw_csi2_disable_irq(csi2); + pm_runtime_put(csi2->dev); }
@@ -729,6 +841,42 @@ static int dw_detect_version(struct dw_mipi_csi2_dev *csi2) return 0; } +static irqreturn_t dw_csi2_irq_handler(int irq, void *priv) +{ + struct dw_mipi_csi2_dev *csi2 = priv; + u32 status; + int i; + + /* Hardware auto clean after read */ + status = dw_readl(csi2, int_st_main); + + for (i = 0; i < csi2->num_event; i++) { + struct dw_csi2_event *event = &csi2->event[i]; + + if (status & event->mask) + event->counter++; + } + + return IRQ_HANDLED; +} + +static void dw_mipi_csi_create_debugfs(struct dw_mipi_csi2_dev *csi2) +{ + int i; + + csi2->debugfs_root = debugfs_create_dir(dev_name(csi2->dev), NULL); + + if (!csi2->debugfs_root) + return; + + for (i = 0; i < csi2->num_event; i++) { + struct dw_csi2_event *event = &csi2->event[i]; + + debugfs_create_u32(event->name, 0600, csi2->debugfs_root, + &event->counter); + } +} + int dw_mipi_csi2_init(struct platform_device *pdev, struct dw_mipi_csi2_dev *csi2, const struct dw_mipi_csi2_config *config) {
@@ -800,6 +948,27 @@ int dw_mipi_csi2_init(struct platform_device *pdev, struct dw_mipi_csi2_dev *csi if (ret) return dev_err_probe(&pdev->dev, ret, "Failed to detect IP version"); + if (config->has_irq) { + int irq; + + csi2->event = devm_kmemdup(&pdev->dev, dw_events, sizeof(dw_events), GFP_KERNEL); + csi2->num_event = ARRAY_SIZE(dw_events); + + if (!csi2->event) + return -ENOMEM; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return dev_err_probe(&pdev->dev, irq, "Failed to get IRQ\n"); + + ret = devm_request_irq(&pdev->dev, irq, dw_csi2_irq_handler, 0, + dev_name(&pdev->dev), csi2); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, "Failed to request IRQ\n"); + } + + dw_mipi_csi_create_debugfs(csi2); + return csi2_async_register(csi2); }
@@ -809,5 +978,7 @@ void dw_mipi_csi2_deinit(struct dw_mipi_csi2_dev *csi2) { v4l2_async_unregister_subdev(&csi2->sd); media_entity_cleanup(&csi2->sd.entity); + + debugfs_remove(csi2->debugfs_root); } EXPORT_SYMBOL_GPL(dw_mipi_csi2_deinit);
diff --git a/include/media/dw-mipi-csi2.h b/include/media/dw-mipi-csi2.h
index 14a80c09fd273c334f91ea70d955dcf92b6646ce..20ce6a21f0674cad54f01edb508dce14cd07a8dd 100644
--- a/include/media/dw-mipi-csi2.h
+++ b/include/media/dw-mipi-csi2.h@@ -27,6 +27,7 @@ struct dw_mipi_csi2_config { u32 num_pads; /* Max 64 pad now */ u32 sink_pad_mask; bool has_phy: 1; + bool has_irq: 1; }; struct dw_mipi_tstif {
@@ -44,6 +45,8 @@ static inline int dw_mipi_tstif_write(struct dw_mipi_tstif *tstif, u32 indice, u return tstif->write(tstif, indice, data); } +struct dw_csi2_event; + struct dw_mipi_csi2_dev { struct device *dev; struct v4l2_subdev sd;
@@ -74,6 +77,11 @@ struct dw_mipi_csi2_dev { bool sink_linked[DW_MAX_PAD_NUM]; const struct dw_mipi_csi2_config *config; + struct dw_csi2_event *event; + int num_event; + + struct dentry *debugfs_root; + struct dw_mipi_tstif tstif; };
--
2.34.1