Re: FYI: i.MX8MP ISP (RKISP1) MI registers corruption

Krzysztof Hałasa
2025-07-21T13:16:38Z

Hi Stefan,

Stefan Klug <stefan.klug@ideasonboard.com> writes:

>> > The problems show themselves maybe in 5% or 10% of boots.
>
> How do you detect if the current boot was a "faulty" one?

I just try to run the tester.

NOTE: make sure you don't run it against a sleeping ISP (clock disabled,
reset etc). I just run the camera application in one session and my
tester in another. Also, make sure the -m argument and the address are
both valid for the ISP in use (0x32E1xxxx for the first MIPI/ISP and
0x32E2xxxx for the second).

If it shows something like:

# ./analyze_mi -m1 -s10000000 -a0x32E21400 -8
Using LDP Q*, Q*, [X*] instructions (2 * 128 bits)
addr: 32E21400 32E21404 32E21408 32E2140C 32E21410 32E21414 32E21418 32E2141C
------------------------------------------------------------------------------
values 7A2001 20 3C240000 1FA400 0 0 0 3C2C0000 count 2242922
values 7A2001 20 3C300000 1FA400 0 0 0 3C2C0000 count 126
values 7A2001 20 3C300000 1FA400 0 0 0 3C380000 count 2106801
values 7A2001 20 3C3C0000 1FA400 0 0 0 3C380000 count 110
values 7A2001 20 3C3C0000 1FA400 0 0 0 3C440000 count 1982059
values 7A2001 20 3C0C0000 1FA400 0 0 0 3C440000 count 94
values 7A2001 20 3C0C0000 1FA400 0 0 0 3C140000 count 1871623
values 7A2001 20 3C180000 1FA400 0 0 0 3C140000 count 64
values 7A2001 20 3C180000 1FA400 0 0 0 3C200000 count 1796142
values 7A2001 20 3C240000 1FA400 0 0 0 3C200000 count 59

then all is good.

OTOH if it's:
# ./analyze_mi -m1 -s10000000 -a0x32E21400 -8
Using LDP Q*, Q*, [X*] instructions (2 * 128 bits)
addr: 32E21400 32E21404 32E21408 32E2140C 32E21410 32E21414 32E21418 32E2141C
------------------------------------------------------------------------------
values 0 20 3C300000 1FA400 0 0 0 3C380000 count 139
values 7A2001 20 3C300000 1FA400 0 0 0 3C380000 count 2202205
values 7A2001 20 3C3C0000 1FA400 0 0 0 3C380000 count 108
values 7A2001 20 3C3C0000 1FA400 0 0 0 3C440000 count 2121036
values 0 20 3C3C0000 1FA400 0 0 0 3C440000 count 117
values 7A2001 20 3C0C0000 1FA400 0 0 0 3C440000 count 112
values 7A2001 20 3C0C0000 1FA400 0 0 0 3C140000 count 1997298
values 0 20 3C0C0000 1FA400 0 0 0 3C140000 count 88
values 7A2001 20 3C180000 1FA400 0 0 0 3C140000 count 106
values 7A2001 20 3C180000 1FA400 0 0 0 3C200000 count 1893775
values 0 20 3C180000 1FA400 0 0 0 3C200000 count 80
values 7A2001 20 3C240000 1FA400 0 0 0 3C200000 count 72
values 7A2001 20 3C240000 1FA400 0 0 0 3C2C0000 count 1784697
values 0 20 3C240000 1FA400 0 0 0 3C2C0000 count 65
values 7A2001 20 3C300000 1FA400 0 0 0 3C2C0000 count 67
values FD200 20 3C0C0000 1FA400 0 0 0 3C140000 count 4
values FD200 20 3C3C0000 1FA400 0 0 0 3C440000 count 5
values 10001 20 3C0C0000 1FA400 0 0 0 3C140000 count 3
values FD200 20 3C180000 1FA400 0 0 0 3C200000 count 6
values 10001 20 3C180000 1FA400 0 0 0 3C200000 count 2
values 3C200000 20 3C180000 1FA400 0 0 0 3C200000 count 1
values 3C380000 20 3C300000 1FA400 0 0 0 3C380000 count 3
values 10001 20 3C300000 1FA400 0 0 0 3C380000 count 5
values FD200 20 3C240000 1FA400 0 0 0 3C2C0000 count 1
values FD200 20 3C300000 1FA400 0 0 0 3C380000 count 3
values 3C2C0000 20 3C240000 1FA400 0 0 0 3C2C0000 count 1
values 10001 20 3C3C0000 1FA400 0 0 0 3C440000 count 1

then, well, something's not right with the register at 0x32E21400.

The NXP-supplied docs on the ISP are not very helpful here, but maybe
the Rockchip RK3288 ISP docs are (there is a PDF on rockchip.fr).
It seems that the problem probably sits in the PVCI/AHB Slave -> CTRL ->
"To all (ISP) modules" (I don't know how ISP registers are connected to
the AHB/AXI on .MX8MP and it probably doesn't matter, the problem is
somewhere between the "AHB Slave and the "all modules").
It appears "only" MI registers are affected, though.

> Can you also share the kernel you are using?

I'm currently using v6.15 with several unrelated patches. Apparently
only this test patch could be relevant. BTW it won't have much effect
on the userspace utility (and it's not needed for the utility, it's just
for the driver and the camera application).

However, those problems were present with NXP-supplied kernels, too.

index 60c97bb7b18b..9530e653191a 100644
--- a/drivers/media/platform/rockchip/rkisp1/rkisp1-common.c
+++ b/drivers/media/platform/rockchip/rkisp1/rkisp1-common.c
@@ -192,3 +192,80 @@ void rkisp1_bls_swap_regs(enum rkisp1_fmt_raw_pat_type pattern,
for (unsigned int i = 0; i < 4; ++i)
output[i] = input[swap[pattern][i]];
}
+
+#define MIN_MATCHES 5
+#define READS 9
+u32 rkisp1_read(struct rkisp1_device *rkisp1, unsigned int addr)
+{
+ if (addr >= RKISP1_CIF_MI_BASE && addr <= RKISP1_CIF_MI_MP_Y_PIC_SIZE) {
+ unsigned long flags;
+ spin_lock_irqsave(&rkisp1->reg_lock, flags);
+
+ uint32_t v[READS];
+ unsigned a, b, cnt;
+ // i.MX8MP ISP MI interface corrupts transfers from time to time
+ if (addr % 8) { // 0x...4 and 0x...C
+ uint32_t value;
+ addr -= 4;
+ // first value may be corrupted, we discard it in wzr register
+ asm volatile ("ldp wzr, %w0, [%x1]" "\n"
+ : "=r" (value) // output
+ : "r" (rkisp1->base_addr + addr)); // input
+ spin_unlock_irqrestore(&rkisp1->reg_lock, flags);
+ return value;
+ }
+ if (addr % 16) { // 0x...8
+ uint64_t value;
+ addr -= 8; // 64-bit: will read 0x...0 and 0x...8
+ // first value may be corrupted, we discard it in xzr register
+ asm volatile ("ldp xzr, %x0, [%x1]" "\n"
+ : "=r" (value) // output
+ : "r" (rkisp1->base_addr + addr)); // input
+ spin_unlock_irqrestore(&rkisp1->reg_lock, flags);
+ return value; // little endian: only least significant 32 bits
+ }
+
+ // 0x...0 adreses are problematic: read multiple times
+ for (a = 0; a < ARRAY_SIZE(v); a++)
+ v[a] = readl(rkisp1->base_addr + addr);
+ for (a = 0; a < ARRAY_SIZE(v) - MIN_MATCHES + 1; a++) {
+ cnt = 0;
+ for (b = a; b < ARRAY_SIZE(v); b++)
+ if (v[b] == v[a]) {
+ cnt++;
+ if (cnt == MIN_MATCHES) {
+ spin_unlock_irqrestore(&rkisp1->reg_lock, flags);
+ return v[a];
+ }
+ }
+ }
+ spin_unlock_irqrestore(&rkisp1->reg_lock, flags);
+ pr_warn("Error reading ISP MI register 0x%X, returning the last value 0x%X\n", addr, v[ARRAY_SIZE(v) - 1]);
+ return v[ARRAY_SIZE(v) - 1];
+ }
+
+ return readl(rkisp1->base_addr + addr);
+}
+
+#define MAX_WRITES 5
+void rkisp1_write(struct rkisp1_device *rkisp1, unsigned int addr, u32 val)
+{
+ if (addr >= RKISP1_CIF_MI_BASE &&
+ addr <= RKISP1_CIF_MI_MP_Y_PIC_SIZE &&
+ addr != RKISP1_CIF_MI_ICR /* write only */ &&
+ addr != RKISP1_CIF_MI_INIT) {
+ for (unsigned cnt = 0; cnt < MAX_WRITES; cnt++) {
+ unsigned long flags;
+ spin_lock_irqsave(&rkisp1->reg_lock, flags);
+ writel(val, rkisp1->base_addr + addr);
+ spin_unlock_irqrestore(&rkisp1->reg_lock, flags);
+
+ if (rkisp1_read(rkisp1, addr) == val)
+ return; // succeeded
+ }
+ pr_warn("Error writing 0x%X to ISP MI register 0x%X\n", val, addr);
+ return;
+ }
+
+ writel(val, rkisp1->base_addr + addr);
+}

index ca952fd0829b..21bab4a3e647 100644
--- a/drivers/media/platform/rockchip/rkisp1/rkisp1-common.h
+++ b/drivers/media/platform/rockchip/rkisp1/rkisp1-common.h
@@ -520,6 +520,7 @@ struct rkisp1_device {
struct media_pipeline pipe;
struct mutex stream_lock; /* serialize {start/stop}_streaming cb between capture devices */
struct rkisp1_debug debug;
+ spinlock_t reg_lock; // used to serialize access to MI registers
const struct rkisp1_info *info;
int irqs[RKISP1_NUM_IRQS];
bool irqs_enabled;
@@ -547,16 +548,8 @@ struct rkisp1_mbus_info {
unsigned int direction;
};

-static inline void
-rkisp1_write(struct rkisp1_device *rkisp1, unsigned int addr, u32 val)
-{
- writel(val, rkisp1->base_addr + addr);
-}
-
-static inline u32 rkisp1_read(struct rkisp1_device *rkisp1, unsigned int addr)
-{
- return readl(rkisp1->base_addr + addr);
-}
+u32 rkisp1_read(struct rkisp1_device *rkisp1, unsigned int addr);
+void rkisp1_write(struct rkisp1_device *rkisp1, unsigned int addr, u32 val);

/*
* rkisp1_cap_enum_mbus_codes - A helper function that return the i'th supported mbus code
diff --git a/drivers/media/platform/rockchip/rkisp1/rkisp1-dev.c b/drivers/media/platform/rockchip/rkisp1/rkisp1-dev.c
index dc65a7924f8a..07f87b70151b 100644
--- a/drivers/media/platform/rockchip/rkisp1/rkisp1-dev.c
+++ b/drivers/media/platform/rockchip/rkisp1/rkisp1-dev.c
@@ -611,6 +611,7 @@ static int rkisp1_probe(struct platform_device *pdev)
return ret;

mutex_init(&rkisp1->stream_lock);
+ spin_lock_init(&rkisp1->reg_lock);

rkisp1->base_addr = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(rkisp1->base_addr))

The (current working version of) analyze_mi.c, compile with -O2 -lpopt
and perhaps with -W -Wall -Wno-sign-compare
-Wno-missing-field-initializers -Wno-pointer-sign.
There is also a regular read_test (a single register and the whole ISP
MI, may use "alt read" workaround), and a write_test for a single
register as well.

// -*- mode: c; c-basic-offset: 4; tab-width: 4; -*-
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2025 Sieć Badawcza Łukasiewicz
* - Przemysłowy Instytut Automatyki i Pomiarów PIAP
* Written by Krzysztof Hałasa
*
* An i.MX8MP ISP MI register test utility v0.1
* Make sure the ISP is active at all times while running this program.
*/

#include <err.h>
#include <fcntl.h>
#include <popt.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>

#define ISP1_ADDR 0x32e10000 // must be page-aligned
#define ISP2_ADDR 0x32e20000
#define ISP_SIZE 0x4000 // with holes
#define MI_OFFSET 0x1400
#define MI_SIZE 0x200
#define MI_REGS (MI_SIZE / 4 /* 32-bit */)
#define MAX_VALUES 128 // max different values for a register

#define error(...) err(EXIT_FAILURE, __VA_ARGS__)
#define errorx(...) errx(EXIT_FAILURE, __VA_ARGS__)
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
#define REG(offset, name, dec) [((offset) - MI_OFFSET) / 4] {(name), (dec)}

static const struct reg {
const char *name;
int dec;
} reg_tab[MI_REGS] = {
REG(0x1400, "mi_ctrl", 0), // Global control register
REG(0x1404, "mi_init", 0), // Control register for address init and skip function (w)

// Main picture
REG(0x1408, "mi_mp_y_base_ad_init", 0), // Base address for Y component, JPEG or raw data
REG(0x140C, "mi_mp_y_size_init", 1), // Size of Y component, JPEG or raw data
REG(0x1410, "mi_mp_y_offs_cnt_init", 0), // Offset counter init value for Y, JPEG or raw data
REG(0x1414, "mi_mp_y_offs_cnt_start", 0), // Offset counter start value for Y, JPEG or raw data
REG(0x1418, "mi_mp_y_irq_offs_init", 0), // Fill level interrupt offset value for Y, JPEG or raw data
REG(0x141C, "mi_mp_cb_base_ad_init", 0), // Base address for Cb component ring buffer
REG(0x1420, "mi_mp_cb_size_init", 1), // Size of Cb component ring buffer
REG(0x1424, "mi_mp_cb_offs_cnt_init", 0), // Offset counter init value for Cb component ring buffer
REG(0x1428, "mi_mp_cb_offs_cnt_start", 0), // Offset counter start value for Cb component ring buffer
REG(0x142C, "mi_mp_cr_base_ad_init", 0), // Base address for Cr component ring buffer
REG(0x1430, "mi_mp_cr_size_init", 0), // Size of Cr component ring buffer
REG(0x1434, "mi_mp_cr_offs_cnt_init", 0), // Offset counter init value for Cr component ring buffer
REG(0x1438, "mi_mp_cr_offs_cnt_start", 0), // Offset counter start value for Cr component ring buffer

#if 0
// Self picture
REG(0x143C, "mi_sp_y_base_ad_init", 0), // Base address for Y component ring buffer
REG(0x1440, "mi_sp_y_size_init", 1), // Size of Y component ring buffer
REG(0x1444, "mi_sp_y_offs_cnt_init", 0), // Offset counter init value for Y component ring buffer
REG(0x1448, "mi_sp_y_offs_cnt_start", 0), // Offset counter start value for Y component ring buffer
REG(0x144C, "mi_sp_y_llength", 1), // Line length of Y component
REG(0x1450, "mi_sp_cb_base_ad_init", 0), // Base address for Cb component ring buffer
REG(0x1454, "mi_sp_cb_size_init", 1), // Size of Cb component ring buffer
REG(0x1458, "mi_sp_cb_offs_cnt_init", 0), // Offset counter init value for Cb component ring buffer
REG(0x145C, "mi_sp_cb_offs_cnt_start", 0), // Offset counter start value for Cb component ring buffer
REG(0x1460, "mi_sp_cr_base_ad_init", 0), // Base address for Cr component ring buffer
REG(0x1464, "mi_sp_cr_size_init", 1), // Size of Cr component ring buffer
REG(0x1468, "mi_sp_cr_offs_cnt_init", 0), // Offset counter init value for Cr component ring buffer
REG(0x146C, "mi_sp_cr_offs_cnt_start", 0), // Offset counter start value for Cr component ring buffer
#endif

REG(0x1470, "mi_byte_cnt", 0), // Counter value of JPEG or RAW data bytes
REG(0x1474, "mi_ctrl_shd", 0), // global control internal shadow register

// Main picture
REG(0x1478, "mi_mp_y_base_ad_shd", 0), // Base address shadow register for Y component, JPEG or raw data ring buffer
REG(0x147C, "mi_mp_y_size_shd", 1), // Size shadow register of Y component, JPEG or raw data
REG(0x1480, "mi_mp_y_offs_cnt_shd", 0), // Current offset counter of Y component, JPEG or raw data ring buffer
REG(0x1484, "mi_mp_y_irq_offs_shd", 0), // Shadow register of fill level interrupt offset value for Y component, JPEG or raw data
REG(0x1488, "mi_mp_cb_base_ad_shd", 0), // Base address shadow register for Cb component ring buffer
REG(0x148C, "mi_mp_cb_size_shd", 1), // Size shadow register of Cb component ring buffer
REG(0x1490, "mi_mp_cb_offs_cnt_shd", 0), // Current offset counter of Cb component ring buffer
REG(0x1494, "mi_mp_cr_base_ad_shd", 0), // Base address shadow register for Cr component ring buffer
REG(0x1498, "mi_mp_cr_size_shd", 0), // Size shadow register of Cr component ring buffer
REG(0x149C, "mi_mp_cr_offs_cnt_shd", 0), // Current offset counter of Cr component ring buffer

#if 0
// Self picture
REG(0x14A0, "mi_sp_y_base_ad_shd", 0), // Base address shadow register for Y component ring buffer
REG(0x14A4, "mi_sp_y_size_shd", 1), // Size shadow register of Y component ring buffer
REG(0x14A8, "mi_sp_y_offs_cnt_shd", 0), // Current offset counter of Y component ring buffer

REG(0x14B0, "mi_sp_cb_base_ad_shd", 0), // Base address shadow register for Cb component ring buffer
REG(0x14B4, "mi_sp_cb_size_shd", 1), // Size shadow register of Cb component ring buffer
REG(0x14B8, "mi_sp_cb_offs_cnt_shd", 0), // Current offset counter of Cb component ring buffer
REG(0x14BC, "mi_sp_cr_base_ad_shd", 0), // Base address shadow register for Cr component ring buffer
REG(0x14C0, "mi_sp_cr_size_shd", 1), // Size shadow register of Cr component ring buffer
REG(0x14C4, "mi_sp_cr_offs_cnt_shd", 0), // Current offset counter of Cr component ring buffer
#endif

REG(0x14C8, "mi_dma_y_pic_start_ad", 1), // Y component image start address
REG(0x14CC, "mi_dma_y_pic_width", 1), // Y component image width
REG(0x14D0, "mi_dma_y_llength", 1), // Y component original line length
REG(0x14D4, "mi_dma_y_pic_size", 1), // Y component image size
REG(0x14D8, "mi_dma_cb_pic_start_ad", 0), // Cb component image start address
REG(0x14E8, "mi_dma_cr_pic_start_ad", 0), // Cr component image start address

REG(0x14F8, "mi_imsc", 0), // Interrupt Mask (1: interrupt active, 0: interrupt masked)
REG(0x14FC, "mi_ris", 0), // Raw Interrupt Status
REG(0x1500, "mi_mis", 0), // Masked Interrupt Status
REG(0x1504, "mi_icr", 0), // Interrupt Clear Register (w)
REG(0x1508, "mi_isr", 0), // Interrupt Set Register (w)

REG(0x150C, "mi_status", 0), // MI Status Register
REG(0x1510, "mi_status_clr", 0), // MI Status Clear Register (w)
REG(0x1514, "mi_sp_y_pic_width", 1), // Y component image width
REG(0x1518, "mi_sp_y_pic_height", 1), // Y component image height
REG(0x151C, "mi_sp_y_pic_size", 1), // Y component image size
REG(0x1520, "mi_dma_ctrl", 0), // DMA control register
REG(0x1524, "mi_dma_start", 0), // DMA start register (w)
REG(0x1528, "mi_dma_status", 0), // DMA status register
REG(0x152C, "mi_pixel_cnt", 0), // Counter value for defect pixel list

// Main picture
REG(0x1530, "mi_mp_y_base_ad_init2", 0), // Base address 2 (ping pong) for Y component, JPEG or raw data
REG(0x1534, "mi_mp_cb_base_ad_init2", 0), // Base address 2 (pingpong) for Cb component
REG(0x1538, "mi_mp_cr_base_ad_init2", 0), // Base address 2 (pingpong) for Cr component ring buffer

#if 0
// Self picture
REG(0x153C, "mi_sp_y_base_ad_init2", 0), // Base address 2 (ping pong) for Y component, JPEG or raw data
REG(0x1540, "mi_sp_cb_base_ad_init2", 0), // Base address 2 (pingpong) for Cb component
REG(0x1544, "mi_sp_cr_base_ad_init2", 0), // Base address 2 (pingpong) for Cr component ring buffer
#endif

REG(0x1548, "mi_reserved_1", 0),
#ifdef ISP_MI_HANDSHAKE_NANO // never defined
REG(0x154C, "mi_mp_handshake", 0), // MI mp handshake control for Nano handshake
#else
REG(0x154C, "mi_reserved_1_1", 0),
#endif
REG(0x1550, "mi_mp_y_llength", 1), // MI mp y llength for Nano handshake,
REG(0x1554, "mi_mp_y_slice_offset", 0), // MI mp y slice offset for Nano handshake,
REG(0x1558, "mi_mp_c_slice_offset", 0), // MI mp c slice offset for Nano handshare,
REG(0x155C, "mi_output_align_format", 0), // MI output byte swap and LSB alignment control for Nano
REG(0x1560, "mi_mp_output_fifo_size", 0), // MI mp output fifo control for Nano,
REG(0x1564, "mi_mp_y_pic_width", 1), // MI mp y width pix for Nano handshake,
REG(0x1568, "mi_mp_y_pic_height", 1), // MI mp y height pix for Nano handshake,
REG(0x156C, "mi_mp_y_pic_size", 1), // MI mp y pix size for Nano handshare

#ifdef ISP_MI_BP // not defined
REG(0x1580, "mi_bp_ctrl", 0),
REG(0x1584, "mi_bp_r_base_ad_shd", 0),
REG(0x1588, "mi_bp_gr_base_ad_shd", 0),
REG(0x158C, "mi_bp_gb_base_ad_shd", 0),
REG(0x1590, "mi_bp_b_base_ad_shd", 0),
REG(0x1594, "mi_bp_r_offs_cnt_shd", 0),
REG(0x1598, "mi_bp_gr_offs_cnt_shd", 0),
REG(0x159C, "mi_bp_gb_offs_cnt_shd", 0),
REG(0x15A0, "mi_bp_b_offs_cnt_shd", 0),
REG(0x15A4, "mi_bp_wr_offs_cnt_init", 0),
REG(0x15A8, "mi_bp_wr_irq_offs_shd", 0),
REG(0x15AC, "mi_bp_wr_irq_offs_init", 0),
REG(0x15B0, "mi_bp_wr_size_shd", 0),
REG(0x15B4, "mi_bp_wr_size_init", 0),
REG(0x15B8, "mi_bp_wr_llength", 0),
REG(0x15BC, "mi_bp_pic_width", 1),
REG(0x15C0, "mi_bp_pic_height", 1),
REG(0x15C4, "mi_bp_pic_size", 1),
REG(0x15C8, "mi_bp_r_offs_cnt_start", 0),
REG(0x15CC, "mi_bp_gr_offs_cnt_start", 0),
REG(0x15D0, "mi_bp_gb_offs_cnt_start", 0),
REG(0x15D4, "mi_bp_b_offs_cnt_start", 0),
REG(0x15D8, "mi_bp_r_base_ad_init", 0),
REG(0x15DC, "mi_bp_gr_base_ad_init", 0),
REG(0x15E0, "mi_bp_gb_base_ad_init", 0),
REG(0x15E4, "mi_bp_b_base_ad_init", 0),
#endif
REG(0x15E8, "mi_dma_y_raw_fmt", 0),
REG(0x15EC, "mi_dma_y_raw_lval", 0)
};

struct values {
struct {
uint32_t value;
unsigned count;
} data[MAX_VALUES];
unsigned data_count;
};

static int alt_read, verbose, debug;

static const struct reg *get_reg(uint32_t phys)
{
phys &= ISP_SIZE - 1;
if (phys >= MI_OFFSET && phys < MI_OFFSET + MI_SIZE * 2 /* 2 copies */)
return &reg_tab[(phys - MI_OFFSET) / 4 /* 32-bit*/ % MI_REGS /* 2 copies */];
return NULL;
}

static void add_value(uint32_t phys, uint32_t value, struct values *values)
{
unsigned cnt;
for (cnt = 0; cnt < values->data_count; cnt++) {
if (values->data[cnt].value == value) {
values->data[cnt].count++;
break;
}
}

if (cnt == values->data_count) { // not found yet
if (values->data_count == MAX_VALUES)
errorx("Too many register 0x%08X values", phys);
values->data[cnt].value = value;
values->data[cnt].count = 1;
values->data_count++;
}
}

static void show_values(uint32_t phys, const struct values *values)
{
const struct reg *reg = get_reg(phys);

for (unsigned cnt = 0; cnt < values->data_count; cnt++) {
printf("Register 0x%08X %-23s", phys, reg && reg->name ? reg->name : "");
if ((reg && reg->dec) || values->data[cnt].value < 10)
printf(" value %10u count %u\n", values->data[cnt].value, values->data[cnt].count);
else
printf(" value 0x%08X count %u\n", values->data[cnt].value, values->data[cnt].count);
}
printf("\n");
}

static uint32_t read_reg(const volatile uint32_t *virt)
{
if (alt_read) {
if ((long)virt % 8) {
// 32-bit LDP works with registers at xxx4 and xxxC, but corrupts xxx0 and xxx8 transfers.
virt--;
uint32_t value;
asm volatile ("ldp wzr, %w0, [%x1]" "\n"
: "=r" (value) // output
: "r" (virt)); // input
return value;
} else {
virt -= 2;
uint64_t value;
asm volatile ("ldp xzr, %x0, [%x1]" "\n"
: "=r" (value) // output
: "r" (virt)); // input
return value; // little endian: only least significant 32 bits
}
} else
return *(volatile uint32_t *)(virt);
}

static void __attribute__((noinline)) read_reg2(const volatile uint32_t *virt, void *v)
{
uint32_t r, s;
asm volatile ("ldp %w0, %w1, [%x2]" "\n"
: "=r" (r), "=r" (s) // output
: "r" (virt)); // input
memcpy(v, &r, sizeof(r)); // possibly corrupted
memcpy(v + sizeof(r), &s, sizeof(s)); // possibly corrupted if virt starts on 8-byte boundary (valid for xxx4 and xxxC)
}

static void __attribute__((noinline)) read_reg4(const volatile uint32_t *virt, void *v)
{
uint64_t r, s;
asm volatile ("ldp %x0, %x1, [%x2]" "\n"
: "=r" (r), "=r" (s) // output
: "r" (virt)); // input
memcpy(v, &r, sizeof(r)); // first half possibly corrupted, corruption in second half at xxxC also possible
memcpy(v + sizeof(r), &s, sizeof(s)); // first half possibly corrupted if virt starts on 16-byte boundary (valid for xxx8)
}

static void __attribute__((noinline)) read_reg8(const volatile uint32_t *virt, void *v)
{
_Float128 r, s;
asm volatile ("ldp %q0, %q1, [%x2]" "\n"
: "=w" (r), "=w" (s) // output
: "r" (virt)); // input
memcpy(v, &r, sizeof(r)); // possibly corrupted
memcpy(v + sizeof(r), &s, sizeof(s)); // possibly corrupted if virt starts on 8-byte boundary (valid for xxx4 and xxxC)
}

static void test_all(int samples, uint32_t mi_phys, uint32_t *mi)
{
struct values values[MI_REGS] = {};
for (unsigned cnt = 0; cnt < samples; cnt++) {
if (cnt % (samples / 10) == 0)
printf("Sample %u\n", cnt);
for (unsigned reg = 0; reg < MI_REGS; reg++)
add_value(MI_OFFSET + reg * 4, read_reg(mi + reg), &values[reg]);
}
printf("\n");

for (unsigned reg = 0; reg < MI_REGS; reg++) {
uint32_t reg_addr = mi_phys + reg * 4;

if (values[reg].data_count && (verbose || values[reg].data[0].value || values[reg].data[0].count != samples))
show_values(reg_addr, &values[reg]);
}
}

static void test_reg(int samples, uint32_t phys, uint32_t *virt, uint32_t write_mask)
{
if (!write_mask) {
struct values values = {};
for (unsigned cnt = 0; cnt < samples; cnt++) {
if (cnt % (samples / 10) == 0)
printf("Sample %u\n", cnt);
add_value(phys, read_reg(virt), &values);
}
printf("\n");

show_values(phys, &values);
} else {
const struct reg *reg = get_reg(phys);
if (reg && reg->name)
printf("Register 0x%08X %s: (all values in hex)\n", phys, reg->name);
else
printf("Register 0x%08X: (all values in hex)\n", phys);

uint32_t value = 0, prev_value = 0;
unsigned mismatch_count = 0;
for (unsigned cnt = 0; cnt < samples; cnt++) {
*virt = value;
uint32_t new_values[9];
unsigned iter;

for (iter = 0; iter < ARRAY_SIZE(new_values); iter++)
new_values[iter] = read_reg(virt);

for (iter = 0; iter < ARRAY_SIZE(new_values); iter++)
if (new_values[iter] != value)
break;

if (iter != ARRAY_SIZE(new_values)) {
printf("%8u:", cnt);
printf(prev_value < 10 ? " %8u" : " %08X", prev_value);
printf(value < 10 ? " WR %8u RD" : " WR %08X RD", value);
for (unsigned iter = 0; iter < ARRAY_SIZE(new_values); iter++)
if (new_values[iter] == value)
printf(" valid");
else if (new_values[iter] == prev_value)
printf(" previous");
else if (iter && new_values[iter] == new_values[iter - 1])
printf(" same");
else
printf(new_values[iter] < 10 ? " %8u" : " %08X", new_values[iter]);
putchar('\n');
mismatch_count++;
}
prev_value = value;
value = random();
value ^= ((uint32_t)random()) << 16;
value &= write_mask;
}
if (mismatch_count)
printf("%u mismatches found\n", mismatch_count);
else
printf("No mismatches found\n");
}
}

static void test_reg_ldp(int samples, uint32_t phys, uint32_t *virt, unsigned words /* 32-bit*/)
{
if (phys & (words * 2 - 1))
errorx("Register address 0x%08X for 2 * %u-bit test is invalid", phys, words * 16);

struct {
uint32_t v[8]; // max
unsigned count;
} data[MAX_VALUES] = {};
unsigned data_count = 0;

switch (words) {
case 2: printf("Using LDP W*, W*, [X*] instructions (2 * 32 bits)\n"); break;
case 4: printf("Using LDP X*, X*, [X*] instructions (2 * 64 bits)\n"); break;
default: printf("Using LDP Q*, Q*, [X*] instructions (2 * 128 bits)\n");
}

for (unsigned sample = 0; sample < samples; sample++) {
uint32_t v[8];

switch (words) {
case 2: read_reg2(virt, v); break;
case 4: read_reg4(virt, v); break;
default: read_reg8(virt, v);
}

unsigned cnt;
for (cnt = 0; cnt < data_count; cnt++) {
if (!memcmp(data[cnt].v, v, words * 4)) {
data[cnt].count++;
break;
}
}

if (cnt == data_count) { // not found yet
if (data_count == MAX_VALUES)
errorx("Too many register 0x%08X values", phys);
memcpy(data[cnt].v, v, words * 4);
data[cnt].count = 1;
data_count++;
}
}

printf("addr: ");
for (unsigned idx = 0; idx < words; idx++)
printf(" %08X", phys + 4 * idx);
printf("\n------");
for (unsigned idx = 0; idx < words; idx++)
printf("---------");
putchar('\n');

for (unsigned cnt = 0; cnt < data_count; cnt++) {
printf("values");
for (unsigned idx = 0; idx < words; idx++)
printf(" %8X", data[cnt].v[idx]);
printf(" count %u\n", data[cnt].count);
}
putchar('\n');
}

int main(int argc, char **argv)
{
int samples = 100000, mipi = 0, reg_addr = 0, test_read = 0, test_read2 = 0, test_read4 = 0, test_read8 = 0, test_write = 0;
long write_mask = 0xFFFFFFFF;
struct poptOption options[] = {
{"samples", 's', POPT_ARG_INT, &samples, 0, "sample count"},
{"mipi", 'm', POPT_ARG_INT, &mipi, 0, "MIPI channel"},
{"address", 'a', POPT_ARG_INT, &reg_addr, 0, "ISP register address"},
{"read-test", 'r', POPT_ARG_NONE, &test_read, 0, "Perform a register read test"},
{"read-test2", '2', POPT_ARG_NONE, &test_read2, 0, "Perform a 2 * 32-bit register read test"},
{"read-test4", '4', POPT_ARG_NONE, &test_read4, 0, "Perform a 2 * 64-bit register read test"},
{"read-test8", '8', POPT_ARG_NONE, &test_read8, 0, "Perform a 2 * 128-bit register read test"},
{"write-test", 'w', POPT_ARG_NONE, &test_write, 0, "Perform a register write test"},
{"write-mask", 'M', POPT_ARG_LONG, &write_mask, 0, "Value mask for write test", "MASK"},
{"alt-read-mode", 'A', POPT_ARG_NONE, &alt_read, 0, "Alternate register read mode"},
{"verbose", 'v', POPT_ARG_NONE, &verbose, 0, "Verbose output"},
{"debug", 'd', POPT_ARG_NONE, &debug, 0, "Debug output"},
POPT_AUTOHELP
POPT_TABLEEND
};

poptContext context = poptGetContext(NULL, argc, (const char **)argv, options, 0);
int i = poptGetNextOpt(context);
if (i < -1)
errorx("%s: %s\n", poptBadOption(context, POPT_BADOPTION_NOALIAS), poptStrerror(i));

if (poptPeekArg(context)) {
poptPrintUsage(context, stderr, 0);
exit(1);
}
poptFreeContext(context);

if (samples <= 0)
errorx("Invalid number of samples");
if (mipi != 0 && mipi != 1)
errorx("Invalid MIPI interface index");
if (test_read + test_read2 + test_read4 + test_read8 + test_write > 1)
errorx("Multiple tests requested");
if (write_mask < 1 || write_mask > 0xFFFFFFFF)
errorx("Invalid write mask");

int mem_fd = open("/dev/mem", O_RDWR | O_SYNC);
if (mem_fd < 0)
error("Error opening /dev/mem");

uint32_t phys = mipi ? ISP2_ADDR : ISP1_ADDR;
if (debug)
printf("MIPI_ISP%u registers at 0x%X (size 0x%X)\n", mipi, phys, ISP_SIZE);
void *virt = mmap(0, ISP_SIZE, test_write ? PROT_READ | PROT_WRITE : PROT_READ, MAP_SHARED, mem_fd, phys);
if (virt == MAP_FAILED)
error("Mmap failed");

if (debug)
printf("Mapped ISP registers at %p\n", virt);
if (test_read || test_read2 || test_read4 || test_read8 || test_write) {
if (reg_addr & 3 || reg_addr < phys || reg_addr >= phys + ISP_SIZE)
errorx("Invalid ISP register address 0x%08X", reg_addr);
virt += reg_addr - phys;
if (debug)
printf("Register 0x%X mapped at %p\n", reg_addr, virt);
if (test_read)
test_reg(samples, reg_addr, virt, 0);
else if (test_read2)
test_reg_ldp(samples, reg_addr, virt, 2);
else if (test_read4)
test_reg_ldp(samples, reg_addr, virt, 4);
else if (test_read8)
test_reg_ldp(samples, reg_addr, virt, 8);
else
test_reg(samples, reg_addr, virt, write_mask);
} else
test_all(samples, phys + MI_OFFSET, virt + MI_OFFSET);

return 0;
}