From f429cbe302c5d2ae3ff5c7386efc84ad784b882d Mon Sep 17 00:00:00 2001 From: Dmitry Ilyin <6576495+widgetii@users.noreply.github.com> Date: Thu, 30 Jul 2026 17:47:22 +0300 Subject: [PATCH 1/2] sensor: fix IMX415 latching into an IMX335 misdetection via 0x316A 0x316A is INCKSEL4, not an identifier. A pristine IMX415 reads 0x00 there, but libsns_imx335.so writes 0x316A=0x7E in both its linear and WDR init tables -- the same value a real IMX335 reads. Streamers ask ipctool which sensor is present and then load that vendor lib, so a single misdetection stamps 0x7E into an IMX415 and every later probe agrees with itself. The loop survives streamer restarts and soft reboots; only removing power clears it. The existing IMX415 test (0x3B00) sat below the 0x316A test and was unreachable once latched. Rule IMX415 out before concluding IMX335, using 3B00h ("set to 2Eh", default after reset 28h, IMX415 datasheet p.46) together with 300Bh, which neither vendor init table writes and which therefore survives the latch (IMX415: 0xA0, IMX335: 0x00). Only reject IMX335 when IMX415 is positively identified, mirroring the IMX347 disambiguation added in #167, so no sensor loses detection if a register drifts. Measured 0x316A: IMX415 pristine 0x00, IMX415 after the IMX335 driver ran 0x7E, real IMX335 0x7E. Measured 0x300B: IMX415 0xA0 both latched and pristine, IMX335 0x00. The dropped HINT claiming 0x30C0 == 0x20 on IMX415 is wrong -- it measures 0x2A. Verified on Hi3516AV300 + IMX415 (imx335_i2c -> imx415_i2c while still latched, then 3840x2160 @ 20fps on libsns_imx415.so after a cold boot) and on Hi3516EV300 + IMX335 (unchanged; its 0x3057 reads 0x06, so it exercises the #167 fall-through). Co-Authored-By: Claude Opus 4.8 --- src/sensors.c | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/sensors.c b/src/sensors.c index 9a4997c..8dd31e0 100644 --- a/src/sensors.c +++ b/src/sensors.c @@ -158,10 +158,9 @@ static int detect_sony_sensor(sensor_ctx_t *ctx, int fd, if (i2c_change_addr(fd, i2c_addr) < 0) return false; - // 0x3057 is Y_OUT_SIZE MSB (host-writable), not a chip ID — Sony - // sensors have no dedicated chip ID register. IMX335 can read 0x06 + // 0x3057 is Y_OUT_SIZE MSB and is host-writable: IMX335 reads 0x06 // here after a WDR-cropping cycle (#157). Disambiguate via OB - // cropping defaults that survive majestic init: + // cropping defaults that survive sensor re-initialisation: // IMX335: 0x3072=0x28, 0x3074=0xB0 // IMX347: 0x3072=0x14, 0x3074=0x3C int chip_id = READ(0x57); @@ -189,8 +188,17 @@ static int detect_sony_sensor(sensor_ctx_t *ctx, int fd, if (r316A == -1) return false; - // HINT: possible check 0x316A == 0x7C && 0x3078 == 0x1 - if (r316A > 0 && ((r316A & 0xFC) == 0x7C)) { + // 0x316A is INCKSEL4: IMX415 reads 0x7E here just like IMX335, and + // libsns_imx335.so *writes* 0x316A=0x7E in both its linear and WDR init + // tables. Once an IMX415 has been brought up with the IMX335 driver the + // misdetection latches until the sensor loses power. Rule IMX415 out + // first: 3B00h is "set to 2Eh, reset default 28h" (IMX415 datasheet p.46) + // and 300Bh is a reset default that neither vendor init table touches + // (IMX415: 0xA0, IMX335: 0x00). + int r3B00 = READ(0xB00); + int is_imx415 = (r3B00 == 0x2E || r3B00 == 0x28) && READ(0xB) == 0xA0; + + if (r316A > 0 && ((r316A & 0xFC) == 0x7C) && !is_imx415) { sprintf(ctx->sensor_id, "IMX335"); return true; } @@ -208,8 +216,8 @@ static int detect_sony_sensor(sensor_ctx_t *ctx, int fd, // from IMX415 datasheet, p.46 // 3B00h, Set to "2Eh", default value after reset is 28h - // HINT: possible check 0x300B == 0xA0 && 0x30C0 == 0x20 - int r3B00 = READ(0xB00); + // Looser than the is_imx415 test above on purpose: catches an IMX415 + // whose 0x300B differs, once 0x316A has ruled IMX335 out. if (r3B00 == 0x2E || r3B00 == 0x28) { sprintf(ctx->sensor_id, "IMX415"); return true; From 46b775cbebc8e0d784272df8dfa22e0b1de59a19 Mon Sep 17 00:00:00 2001 From: Dmitry Ilyin <6576495+widgetii@users.noreply.github.com> Date: Thu, 30 Jul 2026 17:53:42 +0300 Subject: [PATCH 2/2] sensor: don't let a failed discriminator read fall back to IMX335 READ() returns -1 on I2C failure, and the code treated that as a plain "not equal", so a failed 0x300B read made is_imx415 false. A latched IMX415 would then slip into the IMX335 branch and re-arm the very loop this series removes -- and re-arming it costs a physical power cycle. Guard only the IMX335 decision: the checks further down re-test 0x3B00 themselves, so no other sensor's path changes behaviour. Reported by Qodo on #179. Co-Authored-By: Claude Opus 4.8 --- src/sensors.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/sensors.c b/src/sensors.c index 8dd31e0..4e96ddd 100644 --- a/src/sensors.c +++ b/src/sensors.c @@ -196,9 +196,16 @@ static int detect_sony_sensor(sensor_ctx_t *ctx, int fd, // and 300Bh is a reset default that neither vendor init table touches // (IMX415: 0xA0, IMX335: 0x00). int r3B00 = READ(0xB00); - int is_imx415 = (r3B00 == 0x2E || r3B00 == 0x28) && READ(0xB) == 0xA0; + int r300B = READ(0xB); + int is_imx415 = (r3B00 == 0x2E || r3B00 == 0x28) && r300B == 0xA0; if (r316A > 0 && ((r316A & 0xFC) == 0x7C) && !is_imx415) { + // A failed read (-1) must not pass for "not IMX415": a latched + // IMX415 would slip back into IMX335 here and re-arm the loop. + // Only this decision needs the guard — the checks below re-test + // 0x3B00 on their own. + if (r3B00 == -1 || r300B == -1) + return false; sprintf(ctx->sensor_id, "IMX335"); return true; }