* FYI: i.MX8MP ISP (RKISP1) MI registers corruption
@ 2025-07-17 9:00 Krzysztof Hałasa
2025-07-17 13:03 ` Krzysztof Hałasa
0 siblings, 1 reply; 23+ messages in thread
From: Krzysztof Hałasa @ 2025-07-17 9:00 UTC (permalink / raw)
To: Dafna Hirschfeld, Laurent Pinchart, Heiko Stuebner
Cc: Paul Elder, Jacopo Mondi, Ondrej Jirman, linux-media,
linux-rockchip, linux-arm-kernel, linux-kernel
Hi,
there is a well-known issue in i.MX8MP ISP (Image Signal Processor)
implementation, resulting in a corruption on accesses to ISP MI (memory
interface) registers.
The "reference" (NXP) VVCam driver simply does the operations twice
(i.e., reads twice with the first result discarded, and writes twice).
This fixes the problem on most accesses, but the problems still persist.
It appears that it occurs only after certain system reboots. E.g. after
a boot the problems are present consistently, I can stop and restart the
camera application and they are still there, until the next boot. On the
next boot they can be gone for good. The problems show themselves maybe
in 5% or 10% of boots.
########### READ operations ###########
I have performed some tests and it appears that, at least in read case,
the corruption is caused by flipping bits in register address.
For example: I'm reading (in userspace, 32-bit transfers) 0x32E21478
(mi_mp_y_base_ad_shd) a million times. I get the following results (the
first 5 are valid):
value 0x3C300000 count 184163
value 0x3C3C0000 count 220357
value 0x3C0C0000 count 203534
value 0x3C180000 count 202677
value 0x3C240000 count 189240
value 0 count 23
value 0x1E2200E0 count 1 <<<< looks like register 32E21578
value 0x1E0A00E0 count 1 <<<< looks like register 32E21578
value 0x1E1000E0 count 1 <<<< looks like register 32E21578
value 0x1E1600E0 count 2 <<<< looks like register 32E21578
value 0x12B95456 count 1 <<<< looks like register 32E2153C
Apparently, the data can come from any MI register.
A possible partial workaround involves doing a "ldp wzr, %w0, [%x1]"
operation (this is good for xxx4 and xxxC registers). The first 32 bits
(from xxx0 or xxx8) are corrupted and ignored (in wzr register), but the
real data (xxx4 or xxxC) going to %w0 is ok.
Another one is "ldp xzr, %x0, [%x1]" and is needed for xxx8 registers.
The first transfer (xxx0 and xxx4) if corrupted, then xxx8 and xxxC is
valid (in %x0).
Unfortunately, the above doesn't work for xxx0, they can be corrupted
anyway. Tried 128-bit %v0 transfers, too. ATM I'm doing multiple reads
and pick the most frequent value.
Current values of the ISP MI registers for reference (all-zero rows not
shown):
xx0 xx4 xx8 xxC
-----------------------------------
32E21400: 7A2001 20 3C180000 1FA400
32E21410: 0 0 0 3C200000
32E21420: FD200 0 0 0
32E21430: 0 0 0 B105488E
32E21470: 0 10001 3C0C0000 1FA400
32E21480: 0 0 3C140000 FD200
32E214F0: 0 0 1 3C
32E21500: 0 0 0 2 <<< corrupted write?
32E21530: 0 0 0 12B95456 <<< corrupted write
32E21540: 0 0 0 2000000
32E21550: 780 0 0 332
32E21560: 0 780 438 1FA400
32E21570: 0 1E1E00EF 1E2200E0 0
########### WRITE operations ###########
My current strategy is doing verify after write, then rinse and repeat
until success. This appears to have a side effect, though: the data can
go to a register different that requested. This manifests itself visibly
in registers which are usually 0 (like the 0x32E2153C above). Not very
frequent, though (but it's dangerous: certain registers contain RAM
addresses for video buffers and I guess ISP can corrupt large areas of
memory this way).
I wonder why it only occurs only after certain reboots.
It also appears (will have to verify) than on another identical(?)
device these problems don't show up at all. I'm still investigating.
The camera is a custom board using a Compulab UCM IMX8M PLUS module.
I guess I could try replicating this issue on a Hummingboard i.MX8MP
board.
I wonder if Rockchip ISP blocks (using a very similar ISP, which in both
cases comes from VeriSilicon) are free of these issues.
Any ideas maybe?
--
Krzysztof "Chris" Hałasa
Sieć Badawcza Łukasiewicz
Przemysłowy Instytut Automatyki i Pomiarów PIAP
Al. Jerozolimskie 202, 02-486 Warszawa
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: FYI: i.MX8MP ISP (RKISP1) MI registers corruption
2025-07-17 9:00 FYI: i.MX8MP ISP (RKISP1) MI registers corruption Krzysztof Hałasa
@ 2025-07-17 13:03 ` Krzysztof Hałasa
2025-07-21 8:46 ` Stefan Klug
0 siblings, 1 reply; 23+ messages in thread
From: Krzysztof Hałasa @ 2025-07-17 13:03 UTC (permalink / raw)
To: Dafna Hirschfeld
Cc: Laurent Pinchart, Heiko Stuebner, Paul Elder, Jacopo Mondi,
Ondrej Jirman, linux-media, linux-rockchip, linux-arm-kernel,
linux-kernel
> The "reference" (NXP) VVCam driver simply does the operations twice
> (i.e., reads twice with the first result discarded, and writes twice).
> This fixes the problem on most accesses, but the problems still persist.
It appears the corruptions are quite frequent, though.
Using "ldp qXX, qYY, [x0]" (2 * 128-bit load pair) I get results like
this (each data row is a result of a single ldp):
addr: 32E21400 32E21404 32E21408 32E2140C 32E21410 32E21414 32E21418 32E2141C
--------------------------------------------------------------------------------
values 3D000007 20 3C300000 1FA400 0 0 0 3C380000 count 99993097
values 0 20 3C300000 1FA400 0 0 0 3C380000 count 5930
values 7E900 20 3C300000 1FA400 0 0 0 3C380000 count 338
values FD200 20 3C300000 1FA400 0 0 0 3C380000 count 223
values 3C380000 20 3C300000 1FA400 0 0 0 3C380000 count 220
values 40 20 3C300000 1FA400 0 0 0 3C380000 count 192
The valid value (in 0x32E21400 register) is 3D000007 only, the rest are
corruptions: ca. 6 errors per 100k reads. With other registers, 15 errors
per 100k reads etc.
I also got this:
addr: 32E213F0 32E213F4 32E213F8 32E213FC 32E21400 32E21404 32E21408 32E2140C
--------------------------------------------------------------------------------
values 0 0 0 0 3D000007 20 3C300000 1FA400 count 98638773
values 0 0 0 0 0 20 3C300000 1FA400 count 1330227
values 0 0 0 0 40 20 3C300000 1FA400 count 3721
values 0 0 0 0 3C380000 20 3C300000 1FA400 count 314
values 0 0 0 0 7E900 20 3C300000 1FA400 count 572
values 0 0 0 0 FD200 20 3C300000 1FA400 count 428
values 0 0 0 0 4C010 20 3C300000 1FA400 count 25965
which is ca. 14 errors per 1k reads, though maybe it's special -
non-MI/MI boundary (at 0x32E21400), reserved addresses (0x32E213Fx) etc.
> The problems show themselves maybe in 5% or 10% of boots.
Well, now it appears more like 20%: e.g. in 41 system runs (soft reboots
only, no power-downs), I got problems 8 times.
Obviously I can post the tester source if anyone is interested.
Generally ISP MI register read accesses which can be corrupted are:
- first 32 bits read in a given transfer, and additionally
- every 32 bits on a 32-byte boundary (addresses 0x....00, ...20 etc.).
This means, in practice, on i.MX8MP only, RKISP1_CIF_MI_CTRL and
RKISP1_CIF_MI_MP_CB_SIZE_INIT (with the workaround).
What is this 32-byte boundary?
Writing is a bigger problem, though.
--
Krzysztof "Chris" Hałasa
Sieć Badawcza Łukasiewicz
Przemysłowy Instytut Automatyki i Pomiarów PIAP
Al. Jerozolimskie 202, 02-486 Warszawa
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: FYI: i.MX8MP ISP (RKISP1) MI registers corruption
2025-07-17 13:03 ` Krzysztof Hałasa
@ 2025-07-21 8:46 ` Stefan Klug
2025-07-21 13:16 ` Krzysztof Hałasa
0 siblings, 1 reply; 23+ messages in thread
From: Stefan Klug @ 2025-07-21 8:46 UTC (permalink / raw)
To: Dafna Hirschfeld, Krzysztof Hałasa
Cc: Laurent Pinchart, Heiko Stuebner, Paul Elder, Jacopo Mondi,
Ondrej Jirman, linux-media, linux-rockchip, linux-arm-kernel,
linux-kernel
Hi Krzysztof,
Thanks for your investigations. This is all quite worrisome to say the
least.
Quoting Krzysztof Hałasa (2025-07-17 15:03:54)
> > The "reference" (NXP) VVCam driver simply does the operations twice
> > (i.e., reads twice with the first result discarded, and writes twice).
> > This fixes the problem on most accesses, but the problems still persist.
>
> It appears the corruptions are quite frequent, though.
> Using "ldp qXX, qYY, [x0]" (2 * 128-bit load pair) I get results like
> this (each data row is a result of a single ldp):
>
> addr: 32E21400 32E21404 32E21408 32E2140C 32E21410 32E21414 32E21418 32E2141C
> --------------------------------------------------------------------------------
> values 3D000007 20 3C300000 1FA400 0 0 0 3C380000 count 99993097
> values 0 20 3C300000 1FA400 0 0 0 3C380000 count 5930
> values 7E900 20 3C300000 1FA400 0 0 0 3C380000 count 338
> values FD200 20 3C300000 1FA400 0 0 0 3C380000 count 223
> values 3C380000 20 3C300000 1FA400 0 0 0 3C380000 count 220
> values 40 20 3C300000 1FA400 0 0 0 3C380000 count 192
>
> The valid value (in 0x32E21400 register) is 3D000007 only, the rest are
> corruptions: ca. 6 errors per 100k reads. With other registers, 15 errors
> per 100k reads etc.
>
> I also got this:
> addr: 32E213F0 32E213F4 32E213F8 32E213FC 32E21400 32E21404 32E21408 32E2140C
> --------------------------------------------------------------------------------
> values 0 0 0 0 3D000007 20 3C300000 1FA400 count 98638773
> values 0 0 0 0 0 20 3C300000 1FA400 count 1330227
> values 0 0 0 0 40 20 3C300000 1FA400 count 3721
> values 0 0 0 0 3C380000 20 3C300000 1FA400 count 314
> values 0 0 0 0 7E900 20 3C300000 1FA400 count 572
> values 0 0 0 0 FD200 20 3C300000 1FA400 count 428
> values 0 0 0 0 4C010 20 3C300000 1FA400 count 25965
>
> which is ca. 14 errors per 1k reads, though maybe it's special -
> non-MI/MI boundary (at 0x32E21400), reserved addresses (0x32E213Fx) etc.
>
> > The problems show themselves maybe in 5% or 10% of boots.
How do you detect if the current boot was a "faulty" one?
>
> Well, now it appears more like 20%: e.g. in 41 system runs (soft reboots
> only, no power-downs), I got problems 8 times.
>
> Obviously I can post the tester source if anyone is interested.
I'd like to have a look there. I'm doing a lot of work on the imx8mp at
the moment. I didn't have too many issues other than the ones caused by
myself. There were however a few start/stop issues that are still on my
list for further investigations. But I didn't observe bigger memory
corruptions. So I think this ties into my earlier question of how you
observe that the device is in a bad state.
I'm mostly using the debix boards right now, so I could test if I can
replicate the behavior there.
Can you also share the kernel you are using?
Best regards,
Stefan
>
>
> Generally ISP MI register read accesses which can be corrupted are:
> - first 32 bits read in a given transfer, and additionally
> - every 32 bits on a 32-byte boundary (addresses 0x....00, ...20 etc.).
>
> This means, in practice, on i.MX8MP only, RKISP1_CIF_MI_CTRL and
> RKISP1_CIF_MI_MP_CB_SIZE_INIT (with the workaround).
>
> What is this 32-byte boundary?
>
> Writing is a bigger problem, though.
> --
> Krzysztof "Chris" Hałasa
>
> Sieć Badawcza Łukasiewicz
> Przemysłowy Instytut Automatyki i Pomiarów PIAP
> Al. Jerozolimskie 202, 02-486 Warszawa
>
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: FYI: i.MX8MP ISP (RKISP1) MI registers corruption
2025-07-21 8:46 ` Stefan Klug
@ 2025-07-21 13:16 ` Krzysztof Hałasa
2025-07-23 10:19 ` Stefan Klug
0 siblings, 1 reply; 23+ messages in thread
From: Krzysztof Hałasa @ 2025-07-21 13:16 UTC (permalink / raw)
To: Stefan Klug
Cc: Dafna Hirschfeld, Laurent Pinchart, Heiko Stuebner, Paul Elder,
Jacopo Mondi, Ondrej Jirman, linux-media, linux-rockchip,
linux-arm-kernel, linux-kernel
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 ®_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, ®_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;
}
--
Krzysztof "Chris" Hałasa
Sieć Badawcza Łukasiewicz
Przemysłowy Instytut Automatyki i Pomiarów PIAP
Al. Jerozolimskie 202, 02-486 Warszawa
^ permalink raw reply related [flat|nested] 23+ messages in thread
* Re: FYI: i.MX8MP ISP (RKISP1) MI registers corruption
2025-07-21 13:16 ` Krzysztof Hałasa
@ 2025-07-23 10:19 ` Stefan Klug
2025-07-23 12:06 ` Krzysztof Hałasa
2025-07-24 8:21 ` Krzysztof Hałasa
0 siblings, 2 replies; 23+ messages in thread
From: Stefan Klug @ 2025-07-23 10:19 UTC (permalink / raw)
To: Krzysztof Hałasa
Cc: Dafna Hirschfeld, Laurent Pinchart, Heiko Stuebner, Paul Elder,
Jacopo Mondi, Ondrej Jirman, linux-media, linux-rockchip,
linux-arm-kernel, linux-kernel
Hi Krzysztof,
Quoting Krzysztof Hałasa (2025-07-21 15:16:28)
> 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.
Just a quick heads up. I ran the tester and so far no unexpected
results. I'll run it from time to time after a reboot to see if I ever
hit that condition.
How does your device tree look like? Any chance the ISP is clocked for
overdrive but the device is not or something similar? Although I have a
hard time imagining how that would lead to such effects.
Best regards,
Stefan
>
> 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 ®_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, ®_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;
> }
>
> --
> Krzysztof "Chris" Hałasa
>
> Sieć Badawcza Łukasiewicz
> Przemysłowy Instytut Automatyki i Pomiarów PIAP
> Al. Jerozolimskie 202, 02-486 Warszawa
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: FYI: i.MX8MP ISP (RKISP1) MI registers corruption
2025-07-23 10:19 ` Stefan Klug
@ 2025-07-23 12:06 ` Krzysztof Hałasa
2025-07-23 12:09 ` Laurent Pinchart
2025-07-24 8:21 ` Krzysztof Hałasa
1 sibling, 1 reply; 23+ messages in thread
From: Krzysztof Hałasa @ 2025-07-23 12:06 UTC (permalink / raw)
To: Stefan Klug
Cc: Dafna Hirschfeld, Laurent Pinchart, Heiko Stuebner, Paul Elder,
Jacopo Mondi, Ondrej Jirman, linux-media, linux-rockchip,
linux-arm-kernel, linux-kernel
Hi Stefan,
Stefan Klug <stefan.klug@ideasonboard.com> writes:
> Just a quick heads up. I ran the tester and so far no unexpected
> results. I'll run it from time to time after a reboot to see if I ever
> hit that condition.
>
> How does your device tree look like? Any chance the ISP is clocked for
> overdrive but the device is not or something similar? Although I have a
> hard time imagining how that would lead to such effects.
Interesting.
I tested it on two different devices (a Compulab UCM-based camera and
a Solidrun Hummingboard Mate) and it's the same on both. I think the
first one uses 1600 MHz industrial CPU:
(U-boot) CPU: i.MX8MP[8] rev1.1 at 1200 MHz
Not sure about the Hummingboard.
Both cameras apparently are connected to the second MIPI.
Well... maybe if it's only the second ISP, and there is only one camera,
then we could reroute the data to the first ISP? The MIPI receiver has
a crossbar I think.
And the other way around: for a test, one could reroute MIPI0 to ISP1.
Will have a look.
The DT has the usual stuff (for the second MIPI/ISP):
&i2c6 {
clock-frequency = <400000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c6>;
single-master;
status = "okay";
imx462_camera@1a {
compatible = "sony,imx462";
reg = <0x1a>;
clocks = <&clk IMX8MP_CLK_IPP_DO_CLKO2>;
clock-names = "xclk";
clock-frequency = <37125000>;
reset-gpios = <&gpio2 1 GPIO_ACTIVE_HIGH>;
status = "okay";
port {
imx462_mipi_ep: endpoint {
data-lanes = <1 2 3 4>;
clock-lanes = <0>;
link-frequencies = /bits/ 64 <222750000 148500000>;
remote-endpoint = <&mipi_csi_1_in>;
};
};
};
};
&mipi_csi_1 {
status = "okay";
ports {
port@0 {
reg = <0>;
mipi_csi_1_in: endpoint {
remote-endpoint = <&imx462_mipi_ep>;
data-lanes = <1 2 3 4>;
};
};
port@1 {
reg = <1>;
mipi_csi_1_out: endpoint {
remote-endpoint = <&isp_1_in>;
};
};
};
};
&isp_1 {
status = "okay";
ports {
port@1 {
isp_1_in: endpoint {
bus-type = <MEDIA_BUS_TYPE_PARALLEL>;
remote-endpoint = <&mipi_csi_1_out>;
};
};
};
};
The CCM registers show basically (p. 229 in i.MX8MP ref manual):
8 MEDIA_ISP mux 7 post 0 SYSTEM_PLL2_DIV2 = 500 MHz
20 MEDIA_AXI mux 1 pre 1 post 0 SYSTEM_PLL2_CLK / 2 = 500 MHz
21 MEDIA_APB mux 2 pre 3 post 0 SYSTEM_PLL1_CLK / 4 = 200 MHz
123 MEDIA_MIPI_PHY1_REF mux 0 pre 0 post 0 24M_REF_CLK = 24 MHz
125 MEDIA_CAM2_PIX mux 2 pre 0 post 0 SYSTEM_PLL2_DIV4 = 250 MHz
The first 3 are at the max values, MEDIA_MIPI_PHY1_REF max is 125 MHz,
MEDIA_CAM2_PIX max is 266 MHz. Maybe I should try changing these clocks,
but not sure how do I do that (any change causes rkisp1 driver loading
to fail). Will look at it.
BTW the double read and double write in NXP driver (isp-vvcam) were
introduced by (in their repo):
Author: hexing <Xing.He@verisilicon.com> 2022-08-05 10:19:49
Committer: Robby Cai <robby.cai@nxp.com> 2022-08-08 04:50:48
M865SW-1031: the second isp port jump frames
Reason:mi read or write reg occasionally it does not take effect
WorkAround:read or write twice of mi reg
---------------------------- vvcam/isp/isp_ioctl.c ----------------------------
index 60741bd..e0d3048 100644
@@ -118,5 +118,8 @@ void isp_write_reg(struct isp_ic_dev *dev, u32 offset, u32 val)
if (offset >= ISP_REG_SIZE)
return;
- __raw_writel(val, dev->base + offset);
+ writel(val, dev->base + offset);
+ if ((offset >= REG_ADDR(mi_mp_y_base_ad_init))
+ && (offset <= REG_ADDR(mi_mp_y_pic_size)))
+ writel(val, dev->base + offset);
// isp_info("%s addr 0x%08x val 0x%08x\n", __func__, offset, val);
}
@@ -128,5 +131,8 @@ u32 isp_read_reg(struct isp_ic_dev *dev, u32 offset)
if (offset >= ISP_REG_SIZE)
return 0;
- val = __raw_readl(dev->base + offset);
+ val = readl(dev->base + offset);
+ if ((offset >= REG_ADDR(mi_mp_y_base_ad_init))
+ && (offset <= REG_ADDR(mi_mp_y_pic_size)))
+ val = readl(dev->base + offset);
// isp_info("%s addr 0x%08x val 0x%08x\n", __func__, offset, val);
return val;
--
Krzysztof "Chris" Hałasa
Sieć Badawcza Łukasiewicz
Przemysłowy Instytut Automatyki i Pomiarów PIAP
Al. Jerozolimskie 202, 02-486 Warszawa
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: FYI: i.MX8MP ISP (RKISP1) MI registers corruption
2025-07-23 12:06 ` Krzysztof Hałasa
@ 2025-07-23 12:09 ` Laurent Pinchart
2025-07-24 4:20 ` Krzysztof Hałasa
0 siblings, 1 reply; 23+ messages in thread
From: Laurent Pinchart @ 2025-07-23 12:09 UTC (permalink / raw)
To: Krzysztof Hałasa
Cc: Stefan Klug, Dafna Hirschfeld, Heiko Stuebner, Paul Elder,
Jacopo Mondi, Ondrej Jirman, linux-media, linux-rockchip,
linux-arm-kernel, linux-kernel
On Wed, Jul 23, 2025 at 02:06:43PM +0200, Krzysztof Hałasa wrote:
> Stefan Klug writes:
>
> > Just a quick heads up. I ran the tester and so far no unexpected
> > results. I'll run it from time to time after a reboot to see if I ever
> > hit that condition.
> >
> > How does your device tree look like? Any chance the ISP is clocked for
> > overdrive but the device is not or something similar? Although I have a
> > hard time imagining how that would lead to such effects.
>
> Interesting.
> I tested it on two different devices (a Compulab UCM-based camera and
> a Solidrun Hummingboard Mate) and it's the same on both. I think the
> first one uses 1600 MHz industrial CPU:
>
> (U-boot) CPU: i.MX8MP[8] rev1.1 at 1200 MHz
>
> Not sure about the Hummingboard.
>
> Both cameras apparently are connected to the second MIPI.
>
> Well... maybe if it's only the second ISP, and there is only one camera,
> then we could reroute the data to the first ISP? The MIPI receiver has
> a crossbar I think.
There's a crossbar switch in the ISI, but the connections from CSI-2
receivers to ISPs are fixed.
Have you tried reporting the issue to NXP ?
> And the other way around: for a test, one could reroute MIPI0 to ISP1.
> Will have a look.
>
> The DT has the usual stuff (for the second MIPI/ISP):
>
> &i2c6 {
> clock-frequency = <400000>;
> pinctrl-names = "default";
> pinctrl-0 = <&pinctrl_i2c6>;
> single-master;
> status = "okay";
>
> imx462_camera@1a {
> compatible = "sony,imx462";
> reg = <0x1a>;
> clocks = <&clk IMX8MP_CLK_IPP_DO_CLKO2>;
> clock-names = "xclk";
> clock-frequency = <37125000>;
> reset-gpios = <&gpio2 1 GPIO_ACTIVE_HIGH>;
> status = "okay";
>
> port {
> imx462_mipi_ep: endpoint {
> data-lanes = <1 2 3 4>;
> clock-lanes = <0>;
> link-frequencies = /bits/ 64 <222750000 148500000>;
> remote-endpoint = <&mipi_csi_1_in>;
> };
> };
> };
>
> };
>
> &mipi_csi_1 {
> status = "okay";
>
> ports {
> port@0 {
> reg = <0>;
> mipi_csi_1_in: endpoint {
> remote-endpoint = <&imx462_mipi_ep>;
> data-lanes = <1 2 3 4>;
> };
> };
>
> port@1 {
> reg = <1>;
> mipi_csi_1_out: endpoint {
> remote-endpoint = <&isp_1_in>;
> };
> };
> };
> };
>
> &isp_1 {
> status = "okay";
>
> ports {
> port@1 {
> isp_1_in: endpoint {
> bus-type = <MEDIA_BUS_TYPE_PARALLEL>;
> remote-endpoint = <&mipi_csi_1_out>;
> };
> };
> };
> };
>
> The CCM registers show basically (p. 229 in i.MX8MP ref manual):
> 8 MEDIA_ISP mux 7 post 0 SYSTEM_PLL2_DIV2 = 500 MHz
> 20 MEDIA_AXI mux 1 pre 1 post 0 SYSTEM_PLL2_CLK / 2 = 500 MHz
> 21 MEDIA_APB mux 2 pre 3 post 0 SYSTEM_PLL1_CLK / 4 = 200 MHz
> 123 MEDIA_MIPI_PHY1_REF mux 0 pre 0 post 0 24M_REF_CLK = 24 MHz
> 125 MEDIA_CAM2_PIX mux 2 pre 0 post 0 SYSTEM_PLL2_DIV4 = 250 MHz
>
> The first 3 are at the max values, MEDIA_MIPI_PHY1_REF max is 125 MHz,
> MEDIA_CAM2_PIX max is 266 MHz. Maybe I should try changing these clocks,
> but not sure how do I do that (any change causes rkisp1 driver loading
> to fail). Will look at it.
>
> BTW the double read and double write in NXP driver (isp-vvcam) were
> introduced by (in their repo):
>
> Author: hexing <Xing.He@verisilicon.com> 2022-08-05 10:19:49
> Committer: Robby Cai <robby.cai@nxp.com> 2022-08-08 04:50:48
>
> M865SW-1031: the second isp port jump frames
>
> Reason:mi read or write reg occasionally it does not take effect
>
> WorkAround:read or write twice of mi reg
>
> ---------------------------- vvcam/isp/isp_ioctl.c ----------------------------
> index 60741bd..e0d3048 100644
> @@ -118,5 +118,8 @@ void isp_write_reg(struct isp_ic_dev *dev, u32 offset, u32 val)
> if (offset >= ISP_REG_SIZE)
> return;
> - __raw_writel(val, dev->base + offset);
> + writel(val, dev->base + offset);
> + if ((offset >= REG_ADDR(mi_mp_y_base_ad_init))
> + && (offset <= REG_ADDR(mi_mp_y_pic_size)))
> + writel(val, dev->base + offset);
> // isp_info("%s addr 0x%08x val 0x%08x\n", __func__, offset, val);
> }
> @@ -128,5 +131,8 @@ u32 isp_read_reg(struct isp_ic_dev *dev, u32 offset)
> if (offset >= ISP_REG_SIZE)
> return 0;
> - val = __raw_readl(dev->base + offset);
> + val = readl(dev->base + offset);
> + if ((offset >= REG_ADDR(mi_mp_y_base_ad_init))
> + && (offset <= REG_ADDR(mi_mp_y_pic_size)))
> + val = readl(dev->base + offset);
> // isp_info("%s addr 0x%08x val 0x%08x\n", __func__, offset, val);
> return val;
>
--
Regards,
Laurent Pinchart
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: FYI: i.MX8MP ISP (RKISP1) MI registers corruption
2025-07-23 12:09 ` Laurent Pinchart
@ 2025-07-24 4:20 ` Krzysztof Hałasa
0 siblings, 0 replies; 23+ messages in thread
From: Krzysztof Hałasa @ 2025-07-24 4:20 UTC (permalink / raw)
To: Laurent Pinchart
Cc: Stefan Klug, Dafna Hirschfeld, Heiko Stuebner, Paul Elder,
Jacopo Mondi, Ondrej Jirman, linux-media, linux-rockchip,
linux-arm-kernel, linux-kernel
Laurent Pinchart <laurent.pinchart@ideasonboard.com> writes:
> There's a crossbar switch in the ISI, but the connections from CSI-2
> receivers to ISPs are fixed.
>
> Have you tried reporting the issue to NXP ?
No, they know about it, there is that workaround code (working most of
the time) in their driver. For now, I want to better know the problem.
--
Krzysztof "Chris" Hałasa
Sieć Badawcza Łukasiewicz
Przemysłowy Instytut Automatyki i Pomiarów PIAP
Al. Jerozolimskie 202, 02-486 Warszawa
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: FYI: i.MX8MP ISP (RKISP1) MI registers corruption
2025-07-23 10:19 ` Stefan Klug
2025-07-23 12:06 ` Krzysztof Hałasa
@ 2025-07-24 8:21 ` Krzysztof Hałasa
2025-07-25 11:09 ` Stefan Klug
1 sibling, 1 reply; 23+ messages in thread
From: Krzysztof Hałasa @ 2025-07-24 8:21 UTC (permalink / raw)
To: Stefan Klug
Cc: Dafna Hirschfeld, Laurent Pinchart, Heiko Stuebner, Paul Elder,
Jacopo Mondi, Ondrej Jirman, linux-media, linux-rockchip,
linux-arm-kernel, linux-kernel
Hi Stefan,
> Just a quick heads up. I ran the tester and so far no unexpected
> results. I'll run it from time to time after a reboot to see if I ever
> hit that condition.
Is your camera(s) connected to the first CSI?
I cannot reproduce the problem on csi0, it seems only csi1 is
affected. It would be consistent with NXP's workaround commit text.
--
Krzysztof "Chris" Hałasa
Sieć Badawcza Łukasiewicz
Przemysłowy Instytut Automatyki i Pomiarów PIAP
Al. Jerozolimskie 202, 02-486 Warszawa
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: FYI: i.MX8MP ISP (RKISP1) MI registers corruption
2025-07-24 8:21 ` Krzysztof Hałasa
@ 2025-07-25 11:09 ` Stefan Klug
2025-07-30 10:30 ` Krzysztof Hałasa
0 siblings, 1 reply; 23+ messages in thread
From: Stefan Klug @ 2025-07-25 11:09 UTC (permalink / raw)
To: Krzysztof Hałasa
Cc: Dafna Hirschfeld, Laurent Pinchart, Heiko Stuebner, Paul Elder,
Jacopo Mondi, Ondrej Jirman, linux-media, linux-rockchip,
linux-arm-kernel, linux-kernel
Hi Krzysztof,
Quoting Krzysztof Hałasa (2025-07-24 10:21:45)
> Hi Stefan,
>
> > Just a quick heads up. I ran the tester and so far no unexpected
> > results. I'll run it from time to time after a reboot to see if I ever
> > hit that condition.
>
> Is your camera(s) connected to the first CSI?
> I cannot reproduce the problem on csi0, it seems only csi1 is
> affected. It would be consistent with NXP's workaround commit text.
Yes in that case it is connected to csi0. I sometimes have cases where
one is connected to csi1. I'll rerun the test when I use tha device
again.
Best regards,
Stefan
> --
> Krzysztof "Chris" Hałasa
>
> Sieć Badawcza Łukasiewicz
> Przemysłowy Instytut Automatyki i Pomiarów PIAP
> Al. Jerozolimskie 202, 02-486 Warszawa
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: FYI: i.MX8MP ISP (RKISP1) MI registers corruption
2025-07-25 11:09 ` Stefan Klug
@ 2025-07-30 10:30 ` Krzysztof Hałasa
2025-08-01 12:19 ` Krzysztof Hałasa
0 siblings, 1 reply; 23+ messages in thread
From: Krzysztof Hałasa @ 2025-07-30 10:30 UTC (permalink / raw)
To: Stefan Klug
Cc: Dafna Hirschfeld, Laurent Pinchart, Heiko Stuebner, Paul Elder,
Jacopo Mondi, Ondrej Jirman, linux-media, linux-rockchip,
linux-arm-kernel, linux-kernel
Hi,
still investigating the problem, but I can't see a simple way to work
around it.
It happens with the second ISP only, so the single camera setups (using
isp0) are immune.
The ISP MI (memory interface) register read operations are not
a problem, the workaround uses LDP instructions (for registers located
at addresses not on 32-byte boundary) or read multiple times and return
the most frequent result (fortunately the MI reads have no side
effects).
The writes are a problem, though. Especially writing to
RKISP1_CIF_MI_ICR register (address 0x32E21504) can (and does) corrupt
other MI registers, like RKISP1_CIF_MI_CTRL (0x32E21400):
Camera apps just started:
32E21400: 7A2001 20 3C180000 1FA400
32E21410: 0 0 0 3C200000
32E21420: FD200 0 0 0
32E21470: 0 10001 3C0C0000 1FA400
32E21480: 0 0 3C140000 FD200
32E214F0: 0 0 1 3C
32E21540: 0 0 0 2000000
32E21550: 780 0 0 1
32E21560: 0 780 438 1FA400
32E21570: 0 1E1E00EF 1E2200E0 0
Video stream halted:
32E21400: 1000007 20 3C240000 1FA400
32E21410: 0 0 0 3C2C0000
32E21420: FD200 0 0 0
32E21470: 0 0 3C0C0000 1FA400
32E21480: 0 0 3C140000 FD200
32E21490: 7E900 0 0 0
32E214F0: 0 0 1 7C
32E21500: 0 0 0 7
32E21540: 0 0 0 2000000
32E21550: 780 0 0 1
32E21560: 0 780 438 1FA400
32E21570: 0 1E060000 1E0A0000 77
Note that e.g. the 0x32E21400 register, MI_CTRL, has changed value from
the valid one (0x7A2001) to 0x1000007, which probably originated in
MI_MIS (and was to be written to MI_ICR).
It appears the problems are not constant, they manifest themselves in
maybe 10% of system boots. Once a system boots, the problems are either
present (and don't go away) or not.
It appears the ISP uses 3 clocks (+2 used by CSI receiver) - dump of
their CCM registers (Clock Control Module, IMX8MPRM page 227 and so on):
MEDIA_ISP EN mux 7 post 0 SYSTEM_PLL2_DIV2 = 500 MHz
MEDIA_AXI EN mux 1 pre 1 post 0 SYSTEM_PLL2_CLK / 2 = 500 MHz
MEDIA_APB EN mux 2 pre 3 post 0 SYSTEM_PLL1_CLK / 4 = 200 MHz
MEDIA_MIPI_PHY1_REF EN mux 0 pre 0 post 0 24M_REF_CLK = 24 MHz
MEDIA_CAM2_PIX EN mux 2 pre 0 post 0 SYSTEM_PLL2_DIV4 = 250 MHz
Now, it appears that in certain cases changing the MEDIA_APB
(MEDIA_APB_CLOCK_ROOT) "fixes" the problem (for current boot only), or
make it less (or more) visible. I'm changing the POST divider in
CCM_POST_ROOT21_SET register directly (the MEDIA_APB frequency is then
100 MHz):
0x30388A80 MEDIA_APB EN mux 2 pre 3 post 0 SYSTEM_PLL1_CLK / 4 = 200 MHz
# devmem write32 0x30388AA4 1
0x30388A80 MEDIA_APB EN mux 2 pre 3 post 1 SYSTEM_PLL1_CLK / 8 = 100 MHz
Examples - bad read counts from register 0x32E21400 (mi_ctrl)
per 10 millions of read operations:
POST=0 (default) with POST=1
945 1
1448 2361
1088 2419
1842 2451
1333 0
67 5
2033 0
1 8
13 16
2 4
7 0
1 0
539 0
2605 0
1 0
1723 1
I'm starting to run out of ideas.
--
Krzysztof "Chris" Hałasa
Sieć Badawcza Łukasiewicz
Przemysłowy Instytut Automatyki i Pomiarów PIAP
Al. Jerozolimskie 202, 02-486 Warszawa
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: FYI: i.MX8MP ISP (RKISP1) MI registers corruption
2025-07-30 10:30 ` Krzysztof Hałasa
@ 2025-08-01 12:19 ` Krzysztof Hałasa
2025-08-05 10:57 ` Krzysztof Hałasa
0 siblings, 1 reply; 23+ messages in thread
From: Krzysztof Hałasa @ 2025-08-01 12:19 UTC (permalink / raw)
To: Stefan Klug
Cc: Dafna Hirschfeld, Laurent Pinchart, Heiko Stuebner, Paul Elder,
Jacopo Mondi, Ondrej Jirman, linux-media, linux-rockchip,
linux-arm-kernel, linux-kernel
Hi,
reporting from the field :-)
At this point I've something like the following:
- run camera apps in background (so the clocks etc. are up)
- while (analyze_mi -m1 -s10000000 -r -a 0x32E21400 shows errors) {
change_clock 0x30388A80 3 3 # 50 MHz
change_clock 0x30388A80 0 3 # 200 MHz - the original settings
}
It all takes a couple of seconds.
I guess I made some minor changes to analyze_mi.c, can post it
if needed.
where change_clock writes directly do the CCM registers
(MEDIA_APB_ROOT_CLK) and sets "PRE" and "POST" dividers (3 and 3 =
800 MHz / (3 + 1) / (3 + 1) = 50 Mhz and the same for 200 Mhz.
It appears to work. I'm getting (Connection closed = reboot):
MEDIA_APB EN mux 2 pre 3 SYSTEM_PLL1_CLK / 4 = 200 MHz
Register 0x32E21400 mi_ctrl value 0x007A2001 count 10,000,000
Found possibly stable condition
Connection to 10.0.9.123 closed by remote host.
MEDIA_APB EN mux 2 pre 3 SYSTEM_PLL1_CLK / 4 = 200 MHz
Register 0x32E21400 mi_ctrl value 0x007A2001 count 9,999,999
Register 0x32E21400 mi_ctrl value 0 count 1
MEDIA_APB EN mux 2 pre 3 SYSTEM_PLL1_CLK / 16 = 50 MHz
MEDIA_APB EN mux 2 pre 0 SYSTEM_PLL1_CLK / 4 = 200 MHz
Register 0x32E21400 mi_ctrl value 0x007A2001 count 9,999,998
Register 0x32E21400 mi_ctrl value 0 count 1
Register 0x32E21400 mi_ctrl value 0x00010001 count 1
MEDIA_APB EN mux 2 pre 3 SYSTEM_PLL1_CLK / 16 = 50 MHz
MEDIA_APB EN mux 2 pre 0 SYSTEM_PLL1_CLK / 4 = 200 MHz
Register 0x32E21400 mi_ctrl value 0x007A2001 count 10,000,000
Found possibly stable condition
Connection to 10.0.9.123 closed by remote host.
MEDIA_APB EN mux 2 pre 3 SYSTEM_PLL1_CLK / 4 = 200 MHz
Register 0x32E21400 mi_ctrl value 0x007A2001 count 9,999,988
Register 0x32E21400 mi_ctrl value 0 count 12
MEDIA_APB EN mux 2 pre 3 SYSTEM_PLL1_CLK / 16 = 50 MHz
MEDIA_APB EN mux 2 pre 0 SYSTEM_PLL1_CLK / 4 = 200 MHz
Register 0x32E21400 mi_ctrl value 0x007A2001 count 10,000,000
Found possibly stable condition
Connection to 10.0.9.123 closed by remote host.
MEDIA_APB EN mux 2 pre 3 SYSTEM_PLL1_CLK / 4 = 200 MHz
Register 0x32E21400 mi_ctrl value 0x007A2001 count 10,000,000
Found possibly stable condition
Connection to 10.0.9.123 closed by remote host.
MEDIA_APB EN mux 2 pre 3 SYSTEM_PLL1_CLK / 4 = 200 MHz
Register 0x32E21400 mi_ctrl value 0x007A2001 count 10,000,000
Found possibly stable condition
Connection to 10.0.9.123 closed by remote host.
MEDIA_APB EN mux 2 pre 3 SYSTEM_PLL1_CLK / 4 = 200 MHz
Register 0x32E21400 mi_ctrl value 0x007A2001 count 10,000,000
Found possibly stable condition
Connection to 10.0.9.123 closed by remote host.
MEDIA_APB EN mux 2 pre 3 SYSTEM_PLL1_CLK / 4 = 200 MHz
Register 0x32E21400 mi_ctrl value 0x007A2001 count 10,000,000
Found possibly stable condition
Connection to 10.0.9.123 closed by remote host.
MEDIA_APB EN mux 2 pre 3 SYSTEM_PLL1_CLK / 4 = 200 MHz
Register 0x32E21400 mi_ctrl value 0x007A2001 count 10,000,000
Found possibly stable condition
Connection to 10.0.9.123 closed by remote host.
MEDIA_APB EN mux 2 pre 3 SYSTEM_PLL1_CLK / 4 = 200 MHz
Register 0x32E21400 mi_ctrl value 0x007A2001 count 9,999,781
Register 0x32E21400 mi_ctrl value 0 count 206
Register 0x32E21400 mi_ctrl value 0x3C380000 count 3
Register 0x32E21400 mi_ctrl value 0x000FD200 count 5
Register 0x32E21400 mi_ctrl value 0x3C440000 count 3
Register 0x32E21400 mi_ctrl value 0x3C140000 count 2
MEDIA_APB EN mux 2 pre 3 SYSTEM_PLL1_CLK / 16 = 50 MHz
MEDIA_APB EN mux 2 pre 0 SYSTEM_PLL1_CLK / 4 = 200 MHz
Register 0x32E21400 mi_ctrl value 0x007A2001 count 10,000,000
Found possibly stable condition
Connection to 10.0.9.123 closed by remote host.
MEDIA_APB EN mux 2 pre 3 SYSTEM_PLL1_CLK / 4 = 200 MHz
Register 0x32E21400 mi_ctrl value 0x007A2001 count 10,000,000
Found possibly stable condition
Connection to 10.0.9.123 closed by remote host.
MEDIA_APB EN mux 2 pre 3 SYSTEM_PLL1_CLK / 4 = 200 MHz
Register 0x32E21400 mi_ctrl value 0x007A2001 count 10,000,000
Found possibly stable condition
Connection to 10.0.9.123 closed by remote host.
MEDIA_APB EN mux 2 pre 3 SYSTEM_PLL1_CLK / 4 = 200 MHz
Register 0x32E21400 mi_ctrl value 0x007A2001 count 10,000,000
Found possibly stable condition
Connection to 10.0.9.123 closed by remote host.
MEDIA_APB EN mux 2 pre 3 SYSTEM_PLL1_CLK / 4 = 200 MHz
Register 0x32E21400 mi_ctrl value 0x007A2001 count 10,000,000
Found possibly stable condition
Connection to 10.0.9.123 closed by remote host.
MEDIA_APB EN mux 2 pre 3 SYSTEM_PLL1_CLK / 4 = 200 MHz
Register 0x32E21400 mi_ctrl value 0x007A2001 count 10,000,000
Found possibly stable condition
Connection to 10.0.9.123 closed by remote host.
MEDIA_APB EN mux 2 pre 3 SYSTEM_PLL1_CLK / 4 = 200 MHz
Register 0x32E21400 mi_ctrl value 0x007A2001 count 9,999,999
Register 0x32E21400 mi_ctrl value 0 count 1
MEDIA_APB EN mux 2 pre 3 SYSTEM_PLL1_CLK / 16 = 50 MHz
MEDIA_APB EN mux 2 pre 0 SYSTEM_PLL1_CLK / 4 = 200 MHz
Register 0x32E21400 mi_ctrl value 0x007A2001 count 10,000,000
Found possibly stable condition
Connection to 10.0.9.123 closed by remote host.
MEDIA_APB EN mux 2 pre 3 SYSTEM_PLL1_CLK / 4 = 200 MHz
Register 0x32E21400 mi_ctrl value 0x007A2001 count 10,000,000
Found possibly stable condition
Connection to 10.0.9.123 closed by remote host.
MEDIA_APB EN mux 2 pre 3 SYSTEM_PLL1_CLK / 4 = 200 MHz
Register 0x32E21400 mi_ctrl value 0x007A2001 count 10,000,000
Found possibly stable condition
Connection to 10.0.9.123 closed by remote host.
MEDIA_APB EN mux 2 pre 3 SYSTEM_PLL1_CLK / 4 = 200 MHz
Register 0x32E21400 mi_ctrl value 0x007A2001 count 10,000,000
Found possibly stable condition
Connection to 10.0.9.123 closed by remote host.
MEDIA_APB EN mux 2 pre 3 SYSTEM_PLL1_CLK / 4 = 200 MHz
Register 0x32E21400 mi_ctrl value 0x007A2001 count 10,000,000
Found possibly stable condition
Connection to 10.0.9.123 closed by remote host.
MEDIA_APB EN mux 2 pre 3 SYSTEM_PLL1_CLK / 4 = 200 MHz
Register 0x32E21400 mi_ctrl value 0x007A2001 count 10,000,000
Found possibly stable condition
Connection to 10.0.9.123 closed by remote host.
MEDIA_APB EN mux 2 pre 3 SYSTEM_PLL1_CLK / 4 = 200 MHz
Register 0x32E21400 mi_ctrl value 0x007A2001 count 10,000,000
Found possibly stable condition
Connection to 10.0.9.123 closed by remote host.
MEDIA_APB EN mux 2 pre 3 SYSTEM_PLL1_CLK / 4 = 200 MHz
Register 0x32E21400 mi_ctrl value 0x007A2001 count 9,999,752
Register 0x32E21400 mi_ctrl value 0 count 237
Register 0x32E21400 mi_ctrl value 0x3C140000 count 2
Register 0x32E21400 mi_ctrl value 0x000FD200 count 4
Register 0x32E21400 mi_ctrl value 0x3C380000 count 1
Register 0x32E21400 mi_ctrl value 0x3C440000 count 2
Register 0x32E21400 mi_ctrl value 0x3C2C0000 count 2
MEDIA_APB EN mux 2 pre 3 SYSTEM_PLL1_CLK / 16 = 50 MHz
MEDIA_APB EN mux 2 pre 0 SYSTEM_PLL1_CLK / 4 = 200 MHz
Register 0x32E21400 mi_ctrl value 0x007A2001 count 10,000,000
Found possibly stable condition
Connection to 10.0.9.123 closed by remote host.
MEDIA_APB EN mux 2 pre 3 SYSTEM_PLL1_CLK / 4 = 200 MHz
Register 0x32E21400 mi_ctrl value 0x007A2001 count 10,000,000
Found possibly stable condition
--
Krzysztof "Chris" Hałasa
Sieć Badawcza Łukasiewicz
Przemysłowy Instytut Automatyki i Pomiarów PIAP
Al. Jerozolimskie 202, 02-486 Warszawa
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: FYI: i.MX8MP ISP (RKISP1) MI registers corruption
2025-08-01 12:19 ` Krzysztof Hałasa
@ 2025-08-05 10:57 ` Krzysztof Hałasa
2025-08-05 11:13 ` Krzysztof Hałasa
0 siblings, 1 reply; 23+ messages in thread
From: Krzysztof Hałasa @ 2025-08-05 10:57 UTC (permalink / raw)
To: Stefan Klug
Cc: Dafna Hirschfeld, Laurent Pinchart, Heiko Stuebner, Paul Elder,
Jacopo Mondi, Ondrej Jirman, linux-media, linux-rockchip,
linux-arm-kernel, linux-kernel
Hi,
just in case someone finds it helpful:
SError Interrupt on CPU3, code 0x00000000bf000002 -- SError
CPU: 3 UID: 0 PID: 40083 Comm: devmem Not tainted 6.15+ PREEMPT
pstate: 60000000 (nZCv daif -PAN -UAO -TCO -DIT -SSBS BTYPE=--)
Kernel panic - not syncing: Asynchronous SError Interrupt
What did i do? Wrote 32-bit value to an ISP register (0x32E22E2E4C =
a histogram-64 register) while repeatedly reading other ISP
registers (in userspace). The video stream was active.
This ISP apparently has its own problems.
--
Krzysztof "Chris" Hałasa
Sieć Badawcza Łukasiewicz
Przemysłowy Instytut Automatyki i Pomiarów PIAP
Al. Jerozolimskie 202, 02-486 Warszawa
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: FYI: i.MX8MP ISP (RKISP1) MI registers corruption
2025-08-05 10:57 ` Krzysztof Hałasa
@ 2025-08-05 11:13 ` Krzysztof Hałasa
2025-08-12 5:54 ` FYI: i.MX8MP ISP (RKISP1) MI registers corruption: resolved Krzysztof Hałasa
0 siblings, 1 reply; 23+ messages in thread
From: Krzysztof Hałasa @ 2025-08-05 11:13 UTC (permalink / raw)
To: Stefan Klug
Cc: Dafna Hirschfeld, Laurent Pinchart, Heiko Stuebner, Paul Elder,
Jacopo Mondi, Ondrej Jirman, linux-media, linux-rockchip,
linux-arm-kernel, linux-kernel
Krzysztof Hałasa <khalasa@piap.pl> writes:
> SError Interrupt on CPU3, code 0x00000000bf000002 -- SError
Could have been PEBKAC, though.
--
Krzysztof "Chris" Hałasa
Sieć Badawcza Łukasiewicz
Przemysłowy Instytut Automatyki i Pomiarów PIAP
Al. Jerozolimskie 202, 02-486 Warszawa
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: FYI: i.MX8MP ISP (RKISP1) MI registers corruption: resolved
2025-08-05 11:13 ` Krzysztof Hałasa
@ 2025-08-12 5:54 ` Krzysztof Hałasa
2025-08-12 10:32 ` Laurent Pinchart
0 siblings, 1 reply; 23+ messages in thread
From: Krzysztof Hałasa @ 2025-08-12 5:54 UTC (permalink / raw)
To: Stefan Klug
Cc: Dafna Hirschfeld, Laurent Pinchart, Heiko Stuebner, Paul Elder,
Jacopo Mondi, Ondrej Jirman, linux-media, linux-rockchip,
linux-arm-kernel, linux-kernel
Hi Stefan et al,
BTW I've added Lucas Stach and Shawn Guo to "Cc" list.
The problem is the CPU core power supply voltage :-)
- while the reference manual specifies the max ISP and MEDIA clocks at
500 MHz, the datasheets show this requires the "overdrive" mode =
increased CPU power supply voltage. In "normal" mode the ISPs are
limited to 400 MHz (there are other limits, too).
- I've tried lowering the clock rate after booting the systems (with
a CCM register write), but it didn't fix the problem. I guess some
reset logic is affected here, and the (lower) clock rate must be set
right from the start, in the DT.
- anyway, lowering the frequencies of ISP and MEDIA root clocks fixes
the ISP2 MI corruption. I'm currently investigating PMIC settings
(both my Compulab and SolidRun modules use PCA9450C PMICs), so perhaps
I'll be able to use the higher 500 MHz clocks. It doesn't matter much,
though.
- the question is if we should lower the clocks in the main imx8mp.dtsi
DT file, or the overdrive mode should stay there, and the changes
should be made to the individual board files, or maybe the U-Boot
configs (PMIC output voltages) should be changed etc.
--
Krzysztof "Chris" HaBasa
Sieć Badawcza Aukasiewicz
Przemysłowy Instytut Automatyki i Pomiarów PIAP
Al. Jerozolimskie 202, 02-486 Warszawa
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: FYI: i.MX8MP ISP (RKISP1) MI registers corruption: resolved
2025-08-12 5:54 ` FYI: i.MX8MP ISP (RKISP1) MI registers corruption: resolved Krzysztof Hałasa
@ 2025-08-12 10:32 ` Laurent Pinchart
2025-08-12 12:28 ` Krzysztof Hałasa
2025-08-12 15:02 ` Stefan Klug
0 siblings, 2 replies; 23+ messages in thread
From: Laurent Pinchart @ 2025-08-12 10:32 UTC (permalink / raw)
To: Krzysztof Hałasa
Cc: Stefan Klug, Dafna Hirschfeld, Heiko Stuebner, Paul Elder,
Jacopo Mondi, Ondrej Jirman, linux-media, linux-rockchip,
linux-arm-kernel, linux-kernel
Hi Krzysztof,
On Tue, Aug 12, 2025 at 07:54:46AM +0200, Krzysztof Hałasa wrote:
> Hi Stefan et al,
>
> BTW I've added Lucas Stach and Shawn Guo to "Cc" list.
>
> The problem is the CPU core power supply voltage :-)
Ah, the dreadful overdrive mode.
> - while the reference manual specifies the max ISP and MEDIA clocks at
> 500 MHz, the datasheets show this requires the "overdrive" mode =
> increased CPU power supply voltage. In "normal" mode the ISPs are
> limited to 400 MHz (there are other limits, too).
>
> - I've tried lowering the clock rate after booting the systems (with
> a CCM register write), but it didn't fix the problem. I guess some
> reset logic is affected here, and the (lower) clock rate must be set
> right from the start, in the DT.
That's interesting. I wouldn't have expected that.
> - anyway, lowering the frequencies of ISP and MEDIA root clocks fixes
> the ISP2 MI corruption. I'm currently investigating PMIC settings
> (both my Compulab and SolidRun modules use PCA9450C PMICs), so perhaps
> I'll be able to use the higher 500 MHz clocks. It doesn't matter much,
> though.
>
> - the question is if we should lower the clocks in the main imx8mp.dtsi
> DT file, or the overdrive mode should stay there, and the changes
> should be made to the individual board files, or maybe the U-Boot
> configs (PMIC output voltages) should be changed etc.
I think it would make sense to lower the default clock frequencies, and
provide an overlay to enable overdrive mode.
It's also interesting that the issue only affected the second ISP, as
the first one should also be limited to 400 MHz in normal mode.
--
Regards,
Laurent Pinchart
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: FYI: i.MX8MP ISP (RKISP1) MI registers corruption: resolved
2025-08-12 10:32 ` Laurent Pinchart
@ 2025-08-12 12:28 ` Krzysztof Hałasa
2025-08-12 15:02 ` Stefan Klug
1 sibling, 0 replies; 23+ messages in thread
From: Krzysztof Hałasa @ 2025-08-12 12:28 UTC (permalink / raw)
To: Laurent Pinchart
Cc: Stefan Klug, Dafna Hirschfeld, Heiko Stuebner, Paul Elder,
Jacopo Mondi, Ondrej Jirman, linux-media, linux-rockchip,
linux-arm-kernel, linux-kernel
Hi Laurent,
>> - anyway, lowering the frequencies of ISP and MEDIA root clocks fixes
>> the ISP2 MI corruption. I'm currently investigating PMIC settings
>> (both my Compulab and SolidRun modules use PCA9450C PMICs), so perhaps
>> I'll be able to use the higher 500 MHz clocks. It doesn't matter much,
>> though.
Well, apparently my Compulab UCM module provides overdrive power. At
least the PMIC driver says so (in /sys pseudofiles). I guess SolidRun
won't be different.
1. At least two different designs are affected (Compulab and SolidRun),
2. the PCA9450C is THE i.MX8MP PMIC, most probably connected to the CPU
exactly as in the datasheet (at least WRT VDD_SOC power lines),
3. NXP people experienced the problem on their (which ones?) boards, too
Well... They specify (IMX8MPIEC = the datasheet):
"Two instances of 4-lane MIPI CSI interface and HDR ISP
• For single Camera, MIPI CSI 1 can support up to 400/500 MHz pixel
clock in the Nominal/Overdrive mode.
• For single Camera, MIPI CSI 2 can support up to 277 MHz pixel clock.
• For dual Camera, both MIPI CSI can support up to 266 MHz pixel clock.
• 2x ISP supporting 375 Mpixel/s aggregate performance and up to
3-exposure HDR processing.
•When one camera is used, support up to 12MP@30fps or 4kp45
•When two cameras are used, each supports up to 1080p80"
So, while ISP clock is not exactly pixel clock, perhaps they mean that
the ISP clock is to be limited as well.
ISP2 at 400 MHz (single camera) works for me (with a 1080p60 12-bit
IMX462 sensor), but I haven't tested in extreme conditions etc.
> I think it would make sense to lower the default clock frequencies, and
> provide an overlay to enable overdrive mode.
>
> It's also interesting that the issue only affected the second ISP, as
> the first one should also be limited to 400 MHz in normal mode.
Right. Maybe it's not the overdrive mode after all. I will do more tests
soon.
--
Krzysztof "Chris" Hałasa
Sieć Badawcza Łukasiewicz
Przemysłowy Instytut Automatyki i Pomiarów PIAP
Al. Jerozolimskie 202, 02-486 Warszawa
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: FYI: i.MX8MP ISP (RKISP1) MI registers corruption: resolved
2025-08-12 10:32 ` Laurent Pinchart
2025-08-12 12:28 ` Krzysztof Hałasa
@ 2025-08-12 15:02 ` Stefan Klug
2025-08-12 18:22 ` Adam Ford
1 sibling, 1 reply; 23+ messages in thread
From: Stefan Klug @ 2025-08-12 15:02 UTC (permalink / raw)
To: Krzysztof Hałasa, Laurent Pinchart
Cc: Dafna Hirschfeld, Heiko Stuebner, Paul Elder, Jacopo Mondi,
Ondrej Jirman, linux-media, linux-rockchip, linux-arm-kernel,
linux-kernel
Hi Krzysztof, hi Laurent,
Quoting Laurent Pinchart (2025-08-12 12:32:43)
> Hi Krzysztof,
>
> On Tue, Aug 12, 2025 at 07:54:46AM +0200, Krzysztof Hałasa wrote:
> > Hi Stefan et al,
> >
> > BTW I've added Lucas Stach and Shawn Guo to "Cc" list.
> >
> > The problem is the CPU core power supply voltage :-)
>
> Ah, the dreadful overdrive mode.
>
> > - while the reference manual specifies the max ISP and MEDIA clocks at
> > 500 MHz, the datasheets show this requires the "overdrive" mode =
> > increased CPU power supply voltage. In "normal" mode the ISPs are
> > limited to 400 MHz (there are other limits, too).
> >
> > - I've tried lowering the clock rate after booting the systems (with
> > a CCM register write), but it didn't fix the problem. I guess some
> > reset logic is affected here, and the (lower) clock rate must be set
> > right from the start, in the DT.
>
> That's interesting. I wouldn't have expected that.
>
> > - anyway, lowering the frequencies of ISP and MEDIA root clocks fixes
> > the ISP2 MI corruption. I'm currently investigating PMIC settings
> > (both my Compulab and SolidRun modules use PCA9450C PMICs), so perhaps
> > I'll be able to use the higher 500 MHz clocks. It doesn't matter much,
> > though.
> >
> > - the question is if we should lower the clocks in the main imx8mp.dtsi
> > DT file, or the overdrive mode should stay there, and the changes
> > should be made to the individual board files, or maybe the U-Boot
> > configs (PMIC output voltages) should be changed etc.
>
> I think it would make sense to lower the default clock frequencies, and
> provide an overlay to enable overdrive mode.
>
> It's also interesting that the issue only affected the second ISP, as
> the first one should also be limited to 400 MHz in normal mode.
I support that. As a side note, there is already imx8mp-nominal.dtsi
which is only used by one board. That dtsi also uses the
fsl,operating-mode property which enables additional clock checks. So I
ask myself if the default imx8mp.dtsi should specify overdrive mode, or
if we should add a imx8mp-overdrive.dtsi (then we should possibly rename
them to imx8mp-mode-xxx.dtsi so that they sit side by side) to make it
easier to create overlays for both cases.
Best regards,
Stefan
>
> --
> Regards,
>
> Laurent Pinchart
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: FYI: i.MX8MP ISP (RKISP1) MI registers corruption: resolved
2025-08-12 15:02 ` Stefan Klug
@ 2025-08-12 18:22 ` Adam Ford
2025-08-13 5:10 ` Krzysztof Hałasa
2025-09-02 9:54 ` Krzysztof Hałasa
0 siblings, 2 replies; 23+ messages in thread
From: Adam Ford @ 2025-08-12 18:22 UTC (permalink / raw)
To: Stefan Klug
Cc: Krzysztof Hałasa, Laurent Pinchart, Dafna Hirschfeld,
Heiko Stuebner, Paul Elder, Jacopo Mondi, Ondrej Jirman,
linux-media, linux-rockchip, linux-arm-kernel, linux-kernel
On Tue, Aug 12, 2025 at 10:07 AM Stefan Klug
<stefan.klug@ideasonboard.com> wrote:
>
> Hi Krzysztof, hi Laurent,
>
> Quoting Laurent Pinchart (2025-08-12 12:32:43)
> > Hi Krzysztof,
> >
> > On Tue, Aug 12, 2025 at 07:54:46AM +0200, Krzysztof Hałasa wrote:
> > > Hi Stefan et al,
> > >
> > > BTW I've added Lucas Stach and Shawn Guo to "Cc" list.
> > >
> > > The problem is the CPU core power supply voltage :-)
> >
> > Ah, the dreadful overdrive mode.
> >
> > > - while the reference manual specifies the max ISP and MEDIA clocks at
> > > 500 MHz, the datasheets show this requires the "overdrive" mode =
> > > increased CPU power supply voltage. In "normal" mode the ISPs are
> > > limited to 400 MHz (there are other limits, too).
> > >
> > > - I've tried lowering the clock rate after booting the systems (with
> > > a CCM register write), but it didn't fix the problem. I guess some
> > > reset logic is affected here, and the (lower) clock rate must be set
> > > right from the start, in the DT.
> >
> > That's interesting. I wouldn't have expected that.
> >
> > > - anyway, lowering the frequencies of ISP and MEDIA root clocks fixes
> > > the ISP2 MI corruption. I'm currently investigating PMIC settings
> > > (both my Compulab and SolidRun modules use PCA9450C PMICs), so perhaps
> > > I'll be able to use the higher 500 MHz clocks. It doesn't matter much,
> > > though.
I was reading through the data sheet (not the reference manual), and
it lists a few limitations for the clocks:
For single Camera, MIPI CSI 1 can support up to 400/500 MHz pixel
clock in the Nominal/Overdrive mode.
For single Camera, MIPI CSI 2 can support up to 277 MHz pixel clock.
For dual Camera, both MIPI CSI can support up to 266 MHz pixel clock.
If you're running dual cameras, it sounds like you're capped at 266
MHz regardless of whether or not you're in overdrive or nominal.
> > >
> > > - the question is if we should lower the clocks in the main imx8mp.dtsi
> > > DT file, or the overdrive mode should stay there, and the changes
> > > should be made to the individual board files, or maybe the U-Boot
> > > configs (PMIC output voltages) should be changed etc.
> >
> > I think it would make sense to lower the default clock frequencies, and
> > provide an overlay to enable overdrive mode.
> >
> > It's also interesting that the issue only affected the second ISP, as
> > the first one should also be limited to 400 MHz in normal mode.
>
> I support that. As a side note, there is already imx8mp-nominal.dtsi
> which is only used by one board. That dtsi also uses the
> fsl,operating-mode property which enables additional clock checks. So I
> ask myself if the default imx8mp.dtsi should specify overdrive mode, or
> if we should add a imx8mp-overdrive.dtsi (then we should possibly rename
> them to imx8mp-mode-xxx.dtsi so that they sit side by side) to make it
> easier to create overlays for both cases.
My understanding is that the imx8mp.dtsi is pre-configured for
overdrive mode, so if you need to run the ISP in nominal, the clock
updates should go into imx8mp-nominial.dtsi.
adam
>
> Best regards,
> Stefan
>
> >
> > --
> > Regards,
> >
> > Laurent Pinchart
>
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: FYI: i.MX8MP ISP (RKISP1) MI registers corruption: resolved
2025-08-12 18:22 ` Adam Ford
@ 2025-08-13 5:10 ` Krzysztof Hałasa
2025-09-02 9:54 ` Krzysztof Hałasa
1 sibling, 0 replies; 23+ messages in thread
From: Krzysztof Hałasa @ 2025-08-13 5:10 UTC (permalink / raw)
To: Adam Ford
Cc: Stefan Klug, Laurent Pinchart, Dafna Hirschfeld, Heiko Stuebner,
Paul Elder, Jacopo Mondi, Ondrej Jirman, linux-media,
linux-rockchip, linux-arm-kernel, linux-kernel
Hi Adam,
Adam Ford <aford173@gmail.com> writes:
> I was reading through the data sheet (not the reference manual), and
> it lists a few limitations for the clocks:
>
> For single Camera, MIPI CSI 1 can support up to 400/500 MHz pixel
> clock in the Nominal/Overdrive mode.
> For single Camera, MIPI CSI 2 can support up to 277 MHz pixel clock.
> For dual Camera, both MIPI CSI can support up to 266 MHz pixel clock.
>
> If you're running dual cameras, it sounds like you're capped at 266
> MHz regardless of whether or not you're in overdrive or nominal.
Right. But these are pixel clocks, not ISP clock. At least theoretically
entirely different stuff.
For example, I'm using (at most) two IMX290/462 1920x1080p60 sensors in
12-bit (Bayer) mode, resulting in:
- 148.5 MHz pixel clocks
- 1782 Mb/s total bandwidth (for each sensor - 12 bits)
- 445.5 Mb/s per lane (each sensor uses 4 MIPI lanes)
- 222.75 MHz MIPI clocks (MIPI uses DDR, the double data rate)
So I'm well within specs. Though I don't know if the people writing the
datasheets did really mean what they wrote, or maybe they meant ISP core
clocks as well. Why? Because ISP blocks (and all such logic blocks)
require a certain number of their core clocks for a signal rate (in this
case, a pixel rate). And maybe what they specified in the datasheets was
a calculation of this (unpublished?) required pixel/ISP clock ratio and
the max ISP clock rate (specified incorrectly at 400/500 MHz).
Anyway, this is only a possibility. Another one is that ISP2 simply
cannot reliably run at 500 MHz, for some "analog" reason, design bug
etc.
> My understanding is that the imx8mp.dtsi is pre-configured for
> overdrive mode, so if you need to run the ISP in nominal, the clock
> updates should go into imx8mp-nominial.dtsi.
It seems my modules are setup for overdrive mode.
--
Krzysztof "Chris" Hałasa
Sieć Badawcza Łukasiewicz
Przemysłowy Instytut Automatyki i Pomiarów PIAP
Al. Jerozolimskie 202, 02-486 Warszawa
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: FYI: i.MX8MP ISP (RKISP1) MI registers corruption: resolved
2025-08-12 18:22 ` Adam Ford
2025-08-13 5:10 ` Krzysztof Hałasa
@ 2025-09-02 9:54 ` Krzysztof Hałasa
2025-09-02 11:23 ` Alexander Stein
2025-09-03 0:56 ` Paul Elder
1 sibling, 2 replies; 23+ messages in thread
From: Krzysztof Hałasa @ 2025-09-02 9:54 UTC (permalink / raw)
To: Adam Ford
Cc: Stefan Klug, Laurent Pinchart, Dafna Hirschfeld, Heiko Stuebner,
Paul Elder, Jacopo Mondi, Ondrej Jirman, linux-media,
linux-rockchip, linux-arm-kernel, linux-kernel
Hi,
summary:
I've done a few additional tests and it seems the MEDIA_AXI clock is the
problem. Reducing it to 400 MHz while still running MEDIA_ISP at 500 MHz
produces no errors.
MEDIA_ISP at 400 MHz and MEDIA_AXI at 500 MHz produces errors, though
(register address errors while reading and writing from/to ISP MI
(memory interface) registers, only on the secondary ISP (isp1), and
generally only while streaming data from the ISP).
What is driven by MEDIA_AXI clock root? MEDIAMIX: ISI, LCDIF, ISP, DWE.
According to both datasheets (industrial and commercial), MEDIA_AXI
is limited to 400 MHz in normal mode and 500 MHz in overdrive mode.
All my hardware is setup for overdrive mode, though (two manufacturers,
both using the same PMIC setup).
Since no hardware in the official Linux kernel tree (DT) uses the second
ISP... Should we just add a warning to the imx8mp.dtsi and be done with
it?
Out of tree hardware using isp1 (csi1) obviously exists.
--
Krzysztof "Chris" Hałasa
Sieć Badawcza Łukasiewicz
Przemysłowy Instytut Automatyki i Pomiarów PIAP
Al. Jerozolimskie 202, 02-486 Warszawa
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: FYI: i.MX8MP ISP (RKISP1) MI registers corruption: resolved
2025-09-02 9:54 ` Krzysztof Hałasa
@ 2025-09-02 11:23 ` Alexander Stein
2025-09-03 0:56 ` Paul Elder
1 sibling, 0 replies; 23+ messages in thread
From: Alexander Stein @ 2025-09-02 11:23 UTC (permalink / raw)
To: Adam Ford, Krzysztof Hałasa
Cc: Stefan Klug, Laurent Pinchart, Dafna Hirschfeld, Heiko Stuebner,
Paul Elder, Jacopo Mondi, Ondrej Jirman, linux-media,
linux-rockchip, linux-arm-kernel, linux-kernel
Am Dienstag, 2. September 2025, 11:54:27 CEST schrieb Krzysztof Hałasa:
> Hi,
>
> summary:
>
> I've done a few additional tests and it seems the MEDIA_AXI clock is the
> problem. Reducing it to 400 MHz while still running MEDIA_ISP at 500 MHz
> produces no errors.
> MEDIA_ISP at 400 MHz and MEDIA_AXI at 500 MHz produces errors, though
> (register address errors while reading and writing from/to ISP MI
> (memory interface) registers, only on the secondary ISP (isp1), and
> generally only while streaming data from the ISP).
>
> What is driven by MEDIA_AXI clock root? MEDIAMIX: ISI, LCDIF, ISP, DWE.
>
> According to both datasheets (industrial and commercial), MEDIA_AXI
> is limited to 400 MHz in normal mode and 500 MHz in overdrive mode.
> All my hardware is setup for overdrive mode, though (two manufacturers,
> both using the same PMIC setup).
>
> Since no hardware in the official Linux kernel tree (DT) uses the second
> ISP... Should we just add a warning to the imx8mp.dtsi and be done with
> it?
> Out of tree hardware using isp1 (csi1) obviously exists.
There is also [1] which addresses normal mode and clock limits. I don't know
if there is any progress.
Best regards
Alexander
[1] https://lore.kernel.org/all/20250218-imx8m-clk-v4-2-b7697dc2dcd0@pengutronix.de/
--
TQ-Systems GmbH | Mühlstraße 2, Gut Delling | 82229 Seefeld, Germany
Amtsgericht München, HRB 105018
Geschäftsführer: Detlef Schneider, Rüdiger Stahl, Stefan Schneider
http://www.tq-group.com/
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: FYI: i.MX8MP ISP (RKISP1) MI registers corruption: resolved
2025-09-02 9:54 ` Krzysztof Hałasa
2025-09-02 11:23 ` Alexander Stein
@ 2025-09-03 0:56 ` Paul Elder
1 sibling, 0 replies; 23+ messages in thread
From: Paul Elder @ 2025-09-03 0:56 UTC (permalink / raw)
To: Adam Ford, Krzysztof Hałasa
Cc: Stefan Klug, Laurent Pinchart, Dafna Hirschfeld, Heiko Stuebner,
Jacopo Mondi, Ondrej Jirman, linux-media, linux-rockchip,
linux-arm-kernel, linux-kernel
Hi Krzysztof,
Quoting Krzysztof Hałasa (2025-09-02 18:54:27)
> Hi,
>
> summary:
>
> I've done a few additional tests and it seems the MEDIA_AXI clock is the
> problem. Reducing it to 400 MHz while still running MEDIA_ISP at 500 MHz
> produces no errors.
> MEDIA_ISP at 400 MHz and MEDIA_AXI at 500 MHz produces errors, though
> (register address errors while reading and writing from/to ISP MI
> (memory interface) registers, only on the secondary ISP (isp1), and
> generally only while streaming data from the ISP).
>
> What is driven by MEDIA_AXI clock root? MEDIAMIX: ISI, LCDIF, ISP, DWE.
>
> According to both datasheets (industrial and commercial), MEDIA_AXI
> is limited to 400 MHz in normal mode and 500 MHz in overdrive mode.
> All my hardware is setup for overdrive mode, though (two manufacturers,
> both using the same PMIC setup).
Thanks for the investigation!
>
> Since no hardware in the official Linux kernel tree (DT) uses the second
> ISP... Should we just add a warning to the imx8mp.dtsi and be done with
> it?
afaiu we can't yet use the ISI and ISP simultaneously, so the ISI is
enabled by default and you need overlays to enable the ISP, so
technically nothing upstream uses either ISP. To my knowledge, there is
hardware where you can use both though, such as the Debix SOM A
(imx8mp-debix-som-a-bmb-08.dts) [0].
Paul
[0] https://debix.io/hardware/debix-som-a-io-board.html
> Out of tree hardware using isp1 (csi1) obviously exists.
> --
> Krzysztof "Chris" Hałasa
>
> Sieć Badawcza Łukasiewicz
> Przemysłowy Instytut Automatyki i Pomiarów PIAP
> Al. Jerozolimskie 202, 02-486 Warszawa
^ permalink raw reply [flat|nested] 23+ messages in thread
end of thread, other threads:[~2025-09-03 0:59 UTC | newest]
Thread overview: 23+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-07-17 9:00 FYI: i.MX8MP ISP (RKISP1) MI registers corruption Krzysztof Hałasa
2025-07-17 13:03 ` Krzysztof Hałasa
2025-07-21 8:46 ` Stefan Klug
2025-07-21 13:16 ` Krzysztof Hałasa
2025-07-23 10:19 ` Stefan Klug
2025-07-23 12:06 ` Krzysztof Hałasa
2025-07-23 12:09 ` Laurent Pinchart
2025-07-24 4:20 ` Krzysztof Hałasa
2025-07-24 8:21 ` Krzysztof Hałasa
2025-07-25 11:09 ` Stefan Klug
2025-07-30 10:30 ` Krzysztof Hałasa
2025-08-01 12:19 ` Krzysztof Hałasa
2025-08-05 10:57 ` Krzysztof Hałasa
2025-08-05 11:13 ` Krzysztof Hałasa
2025-08-12 5:54 ` FYI: i.MX8MP ISP (RKISP1) MI registers corruption: resolved Krzysztof Hałasa
2025-08-12 10:32 ` Laurent Pinchart
2025-08-12 12:28 ` Krzysztof Hałasa
2025-08-12 15:02 ` Stefan Klug
2025-08-12 18:22 ` Adam Ford
2025-08-13 5:10 ` Krzysztof Hałasa
2025-09-02 9:54 ` Krzysztof Hałasa
2025-09-02 11:23 ` Alexander Stein
2025-09-03 0:56 ` Paul Elder
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox