media: rp1: csi2: Track CSI-2 errors
authorTomi Valkeinen <tomi.valkeinen@ideasonboard.com>
Thu, 21 Sep 2023 15:18:53 +0000 (18:18 +0300)
committerDom Cobley <popcornmix@gmail.com>
Mon, 19 Feb 2024 11:35:02 +0000 (11:35 +0000)
Track the errors from the CSI-2 receiver: overflows and discards. These
are recorded in a table which can be read by the userspace via debugfs.

As tracking the errors may cause much more interrupt load, the tracking
needs to be enabled with a module parameter.

Note that the recording is not perfect: we only record the last
discarded DT for each discard type, instead of recording all of them.
This means that e.g. if the device is discarding two unmatched DTs, the
debugfs file only shows the last one recorded. Recording all of them
would need a more sophisticated recording system to avoid the need of a
very large table, or dynamic allocation.

Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
drivers/media/platform/raspberrypi/rp1_cfe/csi2.c
drivers/media/platform/raspberrypi/rp1_cfe/csi2.h

index 84480cecd23e6378e3ccb680273e157677a9f5ac..898a35e273d96136c0d74b43a112c6bb3df6a277 100644 (file)
 #include "csi2.h"
 #include "cfe.h"
 
+static bool csi2_track_errors;
+module_param_named(track_csi2_errors, csi2_track_errors, bool, 0);
+MODULE_PARM_DESC(track_csi2_errors, "track csi-2 errors");
+
 #define csi2_dbg_verbose(fmt, arg...)                             \
        do {                                                      \
                if (cfe_debug_verbose)                            \
 #define CSI2_DISCARDS_INACTIVE 0x00c
 #define CSI2_DISCARDS_UNMATCHED        0x010
 #define CSI2_DISCARDS_LEN_LIMIT        0x014
+
+#define CSI2_DISCARDS_AMOUNT_SHIFT     0
+#define CSI2_DISCARDS_AMOUNT_MASK      GENMASK(23, 0)
+#define CSI2_DISCARDS_DT_SHIFT         24
+#define CSI2_DISCARDS_DT_MASK          GENMASK(29, 24)
+#define CSI2_DISCARDS_VC_SHIFT         30
+#define CSI2_DISCARDS_VC_MASK          GENMASK(31, 30)
+
 #define CSI2_LLEV_PANICS       0x018
 #define CSI2_ULEV_PANICS       0x01c
 #define CSI2_IRQ_MASK          0x020
+#define CSI2_IRQ_MASK_IRQ_OVERFLOW             BIT(0)
+#define CSI2_IRQ_MASK_IRQ_DISCARD_OVERFLOW     BIT(1)
+#define CSI2_IRQ_MASK_IRQ_DISCARD_LENGTH_LIMIT BIT(2)
+#define CSI2_IRQ_MASK_IRQ_DISCARD_UNMATCHED    BIT(3)
+#define CSI2_IRQ_MASK_IRQ_DISCARD_INACTIVE     BIT(4)
+#define CSI2_IRQ_MASK_IRQ_ALL                                              \
+       (CSI2_IRQ_MASK_IRQ_OVERFLOW | CSI2_IRQ_MASK_IRQ_DISCARD_OVERFLOW | \
+        CSI2_IRQ_MASK_IRQ_DISCARD_LENGTH_LIMIT |                          \
+        CSI2_IRQ_MASK_IRQ_DISCARD_UNMATCHED |                             \
+        CSI2_IRQ_MASK_IRQ_DISCARD_INACTIVE)
+
 #define CSI2_CTRL              0x024
 #define CSI2_CH_CTRL(x)                ((x) * 0x40 + 0x28)
 #define CSI2_CH_ADDR0(x)       ((x) * 0x40 + 0x2c)
@@ -149,6 +172,92 @@ static int csi2_regs_show(struct seq_file *s, void *data)
 
 DEFINE_SHOW_ATTRIBUTE(csi2_regs);
 
+static int csi2_errors_show(struct seq_file *s, void *data)
+{
+       struct csi2_device *csi2 = s->private;
+       unsigned long flags;
+       u32 discards_table[DISCARDS_TABLE_NUM_VCS][DISCARDS_TABLE_NUM_ENTRIES];
+       u32 discards_dt_table[DISCARDS_TABLE_NUM_ENTRIES];
+       u32 overflows;
+
+       spin_lock_irqsave(&csi2->errors_lock, flags);
+
+       memcpy(discards_table, csi2->discards_table, sizeof(discards_table));
+       memcpy(discards_dt_table, csi2->discards_dt_table,
+              sizeof(discards_dt_table));
+       overflows = csi2->overflows;
+
+       csi2->overflows = 0;
+       memset(csi2->discards_table, 0, sizeof(discards_table));
+       memset(csi2->discards_dt_table, 0, sizeof(discards_dt_table));
+
+       spin_unlock_irqrestore(&csi2->errors_lock, flags);
+
+       seq_printf(s, "Overflows %u\n", overflows);
+       seq_puts(s, "Discards:\n");
+       seq_puts(s, "VC            OVLF        LEN  UNMATCHED   INACTIVE\n");
+
+       for (unsigned int vc = 0; vc < DISCARDS_TABLE_NUM_VCS; ++vc) {
+               seq_printf(s, "%u       %10u %10u %10u %10u\n", vc,
+                          discards_table[vc][DISCARDS_TABLE_OVERFLOW],
+                          discards_table[vc][DISCARDS_TABLE_LENGTH_LIMIT],
+                          discards_table[vc][DISCARDS_TABLE_UNMATCHED],
+                          discards_table[vc][DISCARDS_TABLE_INACTIVE]);
+       }
+
+       seq_printf(s, "Last DT %10u %10u %10u %10u\n",
+                  discards_dt_table[DISCARDS_TABLE_OVERFLOW],
+                  discards_dt_table[DISCARDS_TABLE_LENGTH_LIMIT],
+                  discards_dt_table[DISCARDS_TABLE_UNMATCHED],
+                  discards_dt_table[DISCARDS_TABLE_INACTIVE]);
+
+       return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(csi2_errors);
+
+static void csi2_isr_handle_errors(struct csi2_device *csi2, u32 status)
+{
+       spin_lock(&csi2->errors_lock);
+
+       if (status & IRQ_OVERFLOW)
+               csi2->overflows++;
+
+       for (unsigned int i = 0; i < DISCARDS_TABLE_NUM_ENTRIES; ++i) {
+               static const u32 discard_bits[] = {
+                       IRQ_DISCARD_OVERFLOW,
+                       IRQ_DISCARD_LEN_LIMIT,
+                       IRQ_DISCARD_UNMATCHED,
+                       IRQ_DISCARD_INACTIVE,
+               };
+               static const u8 discard_regs[] = {
+                       CSI2_DISCARDS_OVERFLOW,
+                       CSI2_DISCARDS_LEN_LIMIT,
+                       CSI2_DISCARDS_UNMATCHED,
+                       CSI2_DISCARDS_INACTIVE,
+               };
+               u32 amount;
+               u8 dt, vc;
+               u32 v;
+
+               if (!(status & discard_bits[i]))
+                       continue;
+
+               v = csi2_reg_read(csi2, discard_regs[i]);
+               csi2_reg_write(csi2, discard_regs[i], 0);
+
+               amount = (v & CSI2_DISCARDS_AMOUNT_MASK) >>
+                        CSI2_DISCARDS_AMOUNT_SHIFT;
+               dt = (v & CSI2_DISCARDS_DT_MASK) >> CSI2_DISCARDS_DT_SHIFT;
+               vc = (v & CSI2_DISCARDS_VC_MASK) >> CSI2_DISCARDS_VC_SHIFT;
+
+               csi2->discards_table[vc][i] += amount;
+               csi2->discards_dt_table[i] = dt;
+       }
+
+       spin_unlock(&csi2->errors_lock);
+}
+
 void csi2_isr(struct csi2_device *csi2, bool *sof, bool *eof, bool *lci)
 {
        unsigned int i;
@@ -183,6 +292,9 @@ void csi2_isr(struct csi2_device *csi2, bool *sof, bool *eof, bool *lci)
                eof[i] = !!(status & IRQ_FE_ACK(i));
                lci[i] = !!(status & IRQ_LE_ACK(i));
        }
+
+       if (csi2_track_errors)
+               csi2_isr_handle_errors(csi2, status);
 }
 
 void csi2_set_buffer(struct csi2_device *csi2, unsigned int channel,
@@ -277,6 +389,9 @@ void csi2_stop_channel(struct csi2_device *csi2, unsigned int channel)
 
 void csi2_open_rx(struct csi2_device *csi2)
 {
+       csi2_reg_write(csi2, CSI2_IRQ_MASK,
+                      csi2_track_errors ? CSI2_IRQ_MASK_IRQ_ALL : 0);
+
        dphy_start(&csi2->dphy);
 
        csi2_reg_write(csi2, CSI2_CTRL,
@@ -286,6 +401,8 @@ void csi2_open_rx(struct csi2_device *csi2)
 void csi2_close_rx(struct csi2_device *csi2)
 {
        dphy_stop(&csi2->dphy);
+
+       csi2_reg_write(csi2, CSI2_IRQ_MASK, 0);
 }
 
 static struct csi2_device *to_csi2_device(struct v4l2_subdev *subdev)
@@ -398,11 +515,17 @@ int csi2_init(struct csi2_device *csi2, struct dentry *debugfs)
 {
        unsigned int i, ret;
 
+       spin_lock_init(&csi2->errors_lock);
+
        csi2->dphy.dev = csi2->v4l2_dev->dev;
        dphy_probe(&csi2->dphy);
 
        debugfs_create_file("csi2_regs", 0444, debugfs, csi2, &csi2_regs_fops);
 
+       if (csi2_track_errors)
+               debugfs_create_file("csi2_errors", 0444, debugfs, csi2,
+                                   &csi2_errors_fops);
+
        for (i = 0; i < CSI2_NUM_CHANNELS * 2; i++)
                csi2->pad[i].flags = i < CSI2_NUM_CHANNELS ?
                                     MEDIA_PAD_FL_SINK : MEDIA_PAD_FL_SOURCE;
index 94d43f0d8828e092a67a4084e697c2a6cdb64262..e316d7f8e7ec03b1ddfa9e7a8e26253a105bda62 100644 (file)
@@ -17,6 +17,8 @@
 
 #define CSI2_NUM_CHANNELS 4
 
+#define DISCARDS_TABLE_NUM_VCS 4
+
 enum csi2_mode {
        CSI2_MODE_NORMAL,
        CSI2_MODE_REMAP,
@@ -37,6 +39,14 @@ struct csi2_cfg {
        u32 buffer_size;
 };
 
+enum discards_table_index {
+       DISCARDS_TABLE_OVERFLOW = 0,
+       DISCARDS_TABLE_LENGTH_LIMIT,
+       DISCARDS_TABLE_UNMATCHED,
+       DISCARDS_TABLE_INACTIVE,
+       DISCARDS_TABLE_NUM_ENTRIES,
+};
+
 struct csi2_device {
        /* Parent V4l2 device */
        struct v4l2_device *v4l2_dev;
@@ -53,6 +63,12 @@ struct csi2_device {
 
        struct media_pad pad[CSI2_NUM_CHANNELS * 2];
        struct v4l2_subdev sd;
+
+       /* lock for csi2 errors counters */
+       spinlock_t errors_lock;
+       u32 overflows;
+       u32 discards_table[DISCARDS_TABLE_NUM_VCS][DISCARDS_TABLE_NUM_ENTRIES];
+       u32 discards_dt_table[DISCARDS_TABLE_NUM_ENTRIES];
 };
 
 void csi2_isr(struct csi2_device *csi2, bool *sof, bool *eof, bool *lci);