mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-10-23 05:11:24 +00:00
lr1110 updater final
This commit is contained in:
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);
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in new issue
Block a user