lr1110 updater final

This commit is contained in:
liquidraver committed 2026-07-21 14:43:39 +02:00
1 parent 4001b3f130
commit 3e04c11621
8 files changed
+314 -6

No files matched your search

@@ -104,6 +104,26 @@ if(DEFINED UPDATER_ALLOW_BOOTLOADER_UPDATE)
UPDATER_ALLOW_BOOTLOADER_UPDATE=${UPDATER_ALLOW_BOOTLOADER_UPDATE})
endif()
# Idle gap after every flash page program, in ms. Diagnostic for a marginal
# supply — see the note in src/main.c.
# west build ... -- -DUPDATER_CHUNK_DELAY_MS=10
if(DEFINED UPDATER_CHUNK_DELAY_MS)
target_compile_definitions(app PRIVATE
UPDATER_CHUNK_DELAY_MS=${UPDATER_CHUNK_DELAY_MS})
endif()
# Flash even with an SD card in the slot (shared SPI bus — see src/main.c).
# west build ... -- -DUPDATER_IGNORE_SDCARD=1
if(DEFINED UPDATER_IGNORE_SDCARD)
target_compile_definitions(app PRIVATE
UPDATER_IGNORE_SDCARD=${UPDATER_IGNORE_SDCARD})
endif()
# Let a pasted log identify which build produced it.
if(UPDATER_SPI_DMA)
target_compile_definitions(app PRIVATE UPDATER_SPI_DMA=1)
endif()
# Include paths for Semtech driver headers and firmware image
target_include_directories(app PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src
@@ -266,6 +266,111 @@ int lr1110_updater_reset_to_bootloader(void)
return ret;
}
/* ── SD card presence probe ───────────────────────────────────
*
* On boards where the SD slot shares the radio's SPI bus, an inserted card
* breaks LR1110 flashing: every write still reports OK and the programmed
* image fails its integrity check at boot, which is indistinguishable from a
* dozen other faults and cost days to track down. There is no card-detect pin
* wired on the M9, so presence is established over the bus.
*
* Standard SPI-mode detection: >=74 dummy clocks with CS high to bring the
* card up in SPI mode, then CMD0 (GO_IDLE_STATE). A present card answers R1
* with the MSB clear (0x01 = idle). An empty slot leaves MISO pulled high, so
* every byte reads 0xFF and nothing else. False "absent" is possible if a card
* ignores CMD0 — no worse than not probing; false "present" essentially cannot
* happen, since 0xFF is all an empty slot can produce.
*
* Runs at 400 kHz (SD init is specified at 100-400 kHz; the radio path stays
* at its own clock) and is safe on the shared bus: the LR1110's NSS is parked
* inactive by hal_init, and the TFT is held in reset and is write-only.
*/
#if defined(CONFIG_BOARD_THINKNODE_M9)
/* No DT node exists for the slot — the base board DTS only parks its CS with
* a gpio-hog. GPIO48 = gpio1 pin 16. */
#define SDCARD_CS_PORT_NODE DT_NODELABEL(gpio1)
#define SDCARD_CS_PIN 16
#endif
int lr1110_updater_probe_sdcard(void)
{
#ifdef SDCARD_CS_PORT_NODE
const struct device *cs_port = DEVICE_DT_GET(SDCARD_CS_PORT_NODE);
struct spi_config slow_cfg = {
.frequency = 400000,
.operation = SPI_WORD_SET(8) | SPI_TRANSFER_MSB,
};
/* CMD0: GO_IDLE_STATE, arg 0, CRC7 0x95 (valid, and required while the
* card is still in its CRC-checked power-up state). */
static const uint8_t cmd0[6] = { 0x40, 0x00, 0x00, 0x00, 0x00, 0x95 };
uint8_t tx[8], rx[8];
int ret, present = 0;
if (!device_is_ready(cs_port)) {
return -ENODEV;
}
if (gpio_pin_configure(cs_port, SDCARD_CS_PIN, GPIO_OUTPUT_HIGH) < 0) {
return -EIO;
}
/* Wake-up clocks, CS HIGH (deselected) — 10 bytes = 80 cycles. */
memset(tx, 0xFF, sizeof(tx));
const struct spi_buf wake_buf = { .buf = tx, .len = sizeof(tx) };
const struct spi_buf_set wake = { .buffers = &wake_buf, .count = 1 };
ret = spi_write(spi_dev, &slow_cfg, &wake);
if (ret == 0) {
ret = spi_write(spi_dev, &slow_cfg, &wake); /* >=74 clocks total */
}
if (ret < 0) {
goto out;
}
/* Select the card and issue CMD0. */
gpio_pin_set(cs_port, SDCARD_CS_PIN, 0);
const struct spi_buf cmd_buf = { .buf = (uint8_t *)cmd0, .len = sizeof(cmd0) };
const struct spi_buf_set cmd = { .buffers = &cmd_buf, .count = 1 };
ret = spi_write(spi_dev, &slow_cfg, &cmd);
if (ret == 0) {
/* Clock the response out with MOSI held high, as the spec
* requires — spi_read() would drive zeros instead. */
memset(tx, 0xFF, sizeof(tx));
const struct spi_buf rtx_buf = { .buf = tx, .len = sizeof(rx) };
const struct spi_buf_set rtx = { .buffers = &rtx_buf, .count = 1 };
const struct spi_buf rrx_buf = { .buf = rx, .len = sizeof(rx) };
const struct spi_buf_set rrx = { .buffers = &rrx_buf, .count = 1 };
ret = spi_transceive(spi_dev, &slow_cfg, &rtx, &rrx);
if (ret == 0) {
for (size_t i = 0; i < sizeof(rx); i++) {
if ((rx[i] & 0x80) == 0) { /* valid R1 token */
present = 1;
break;
}
}
}
}
/* Deselect, then one more byte so the card releases the bus. */
gpio_pin_set(cs_port, SDCARD_CS_PIN, 1);
memset(tx, 0xFF, sizeof(tx));
const struct spi_buf rel_buf = { .buf = tx, .len = 1 };
const struct spi_buf_set rel = { .buffers = &rel_buf, .count = 1 };
spi_write(spi_dev, &slow_cfg, &rel);
out:
/* Leave CS parked HIGH exactly as the gpio-hog had it. */
gpio_pin_configure(cs_port, SDCARD_CS_PIN, GPIO_OUTPUT_HIGH);
return (ret < 0) ? ret : present;
#else
return -ENOTSUP;
#endif
}
/* ── Semtech HAL interface ────────────────────────────────── */
lr11xx_hal_status_t lr11xx_hal_write(const void *context, const uint8_t *command,
@@ -66,6 +66,18 @@ void *lr1110_updater_get_context(void);
* - busy_seen: false if BUSY never rose within the bounded watch window.
* - spi_ret: return code of the last spi_write().
*/
/**
* @brief Probe the shared SPI bus for an inserted SD card.
*
* Boards where the SD slot shares the radio's bus cannot flash the LR1110
* reliably with a card inserted. No card-detect pin is available, so presence
* is established with an SPI-mode CMD0 exchange.
*
* @return 1 if a card responded, 0 if the slot is empty, -ENOTSUP on boards
* with no shared SD slot, or a negative errno on bus failure.
*/
int lr1110_updater_probe_sdcard(void);
uint32_t lr1110_updater_last_busy_rise_us(void);
uint32_t lr1110_updater_last_busy_hold_us(void);
bool lr1110_updater_last_busy_seen(void);
+88 -6
View File
@@ -56,12 +56,20 @@
#endif
/*
* The 0x6500 -> 0x1001 chip-bootloader update is ONE-WAY and, on every chip
* we have observed take it, leaves the radio unable to boot ANY transceiver
* image (0x0402, 0x0401 and 0x0303 all write without error and never run,
* across two independent flashers). Chips still on bootloader 0x6500 flash
* and run normally. So it must be opted into explicitly, never performed as
* a side effect of asking for firmware 0x0402.
* Perform the 0x6500 -> 0x1001 chip-bootloader update when firmware 0x0402 is
* the target (0x0402 will not run on the old bootloader).
*
* Verified end-to-end on a T1000-E 2026-07-21: Stage A then Stage B, chip
* afterwards reports TYPE=0x01 FW=0x0402 and runs from flash. An earlier
* belief that this update bricks radios came from a sample where every
* observed 0x1001 chip was on one board (ThinkNode M9) — that board has a
* separate, still-open problem, so a board fault was being read as a
* bootloader fault.
*
* It IS one-way: Semtech ships loaders in the forward direction only, and the
* new bootloader lives inside the encrypted loader payload, so there is
* nothing to flash back. Set this to 0 to flash firmware only and leave the
* chip bootloader untouched.
*/
#ifndef UPDATER_ALLOW_BOOTLOADER_UPDATE
#define UPDATER_ALLOW_BOOTLOADER_UPDATE 1
@@ -116,6 +124,36 @@ static void led_off(void) {}
static void led_toggle(void) {}
#endif
/* Idle gap inserted after every flash page program.
*
* Diagnostic for a marginal supply. A page program pulls current for ~3.6 ms
* and we issue 959 of them back to back, so a rail that cannot sustain that
* burst will sag — and a sagging rail corrupts what gets programmed while the
* write still reports OK (the bootloader never reads back). Spacing the pages
* lets the supply recover between them. 10 ms stretches a full image write
* from ~6.6 s to ~16 s, which costs nothing.
* west build ... -- -DUPDATER_CHUNK_DELAY_MS=10
*/
#ifndef UPDATER_CHUNK_DELAY_MS
#define UPDATER_CHUNK_DELAY_MS 0
#endif
/* Refuse to flash with an SD card in the slot.
*
* On the M9 the slot shares SPI2 with the radio, and a card present during
* flashing produces a silently corrupt image: every write reports OK, the
* chip spends real time programming, and the result fails its integrity check
* at boot with no error anywhere to point at. Field-confirmed 2026-07-21 —
* removing the card was what finally made that board flash.
*
* Gated rather than warned because the asymmetry is stark: the cost of the
* gate is ejecting a card, the cost of missing it is days of debugging.
* west build ... -- -DUPDATER_IGNORE_SDCARD=1
*/
#ifndef UPDATER_IGNORE_SDCARD
#define UPDATER_IGNORE_SDCARD 0
#endif
/* ── Helpers ──────────────────────────────────────────────────── */
static void updater_done(void)
@@ -153,6 +191,37 @@ static void fatal_error(const char *msg)
}
}
static void check_sdcard(void)
{
int rc = lr1110_updater_probe_sdcard();
if (rc == -ENOTSUP) {
return; /* board has no SD slot on the radio's bus */
}
if (rc < 0) {
printk(" SD slot: probe failed (%d) — continuing\n", rc);
return;
}
if (rc == 0) {
printk(" SD slot: empty\n");
return;
}
printk(" SD slot: CARD PRESENT\n");
if (UPDATER_IGNORE_SDCARD) {
printk(" WARNING: flashing anyway (UPDATER_IGNORE_SDCARD=1).\n");
return;
}
printk("\n");
printk(" The SD slot shares SPI2 with the radio. Flashing with a\n");
printk(" card inserted corrupts the image while every write still\n");
printk(" reports success — the chip ends up running nothing and\n");
printk(" there is no error to point at.\n");
printk("\n");
printk(" Remove the SD card and try again.\n");
fatal_error("SD card present — refusing to flash");
}
/* Map a millivolt value from devicetree to the chip's TCXO supply code. */
static uint8_t tcxo_code_from_mv(uint16_t mv)
{
@@ -413,6 +482,10 @@ static void erase_and_flash_image(void *ctx, const char *what,
led_toggle();
}
if (UPDATER_CHUNK_DELAY_MS > 0) {
k_msleep(UPDATER_CHUNK_DELAY_MS);
}
offset += this_chunk * sizeof(uint32_t);
remaining -= this_chunk;
chunk_idx++;
@@ -587,6 +660,14 @@ int main(void)
printk(" Image: %u words (%u KB)\n",
LR11XX_FIRMWARE_IMAGE_SIZE,
(LR11XX_FIRMWARE_IMAGE_SIZE * 4) / 1024);
printk(" Build: SPI=%s chunk-delay=%ums bl-update=%s\n",
#ifdef UPDATER_SPI_DMA
"DMA",
#else
"PIO",
#endif
(unsigned)UPDATER_CHUNK_DELAY_MS,
UPDATER_ALLOW_BOOTLOADER_UPDATE ? "yes" : "no");
printk("============================================\n");
printk("\n");
@@ -599,6 +680,7 @@ int main(void)
fatal_error("HAL init failed");
}
ctx = lr1110_updater_get_context();
check_sdcard();
/* ── Step 2: Hardware reset ── */
printk("[2/8] Hardware reset LR1110...\n");