From 3b551dff95cc2c7bf06b569ceda82350008db4e2 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Wed, 26 Aug 2026 10:19:37 -0500 Subject: [PATCH 1/7] add emmcio module --- extmod/vfs_blockdev.c | 23 + ports/nordic/common-hal/busio/SPI.c | 9 + ports/nordic/common-hal/emmcio/EMMC.c | 1073 +++++++++++++++++++++ ports/nordic/common-hal/emmcio/EMMC.h | 99 ++ ports/nordic/common-hal/emmcio/__init__.c | 19 + ports/nordic/common-hal/emmcio/emmc_hw.h | 219 +++++ ports/nordic/supervisor/port.c | 8 + py/circuitpy_defns.mk | 6 + py/circuitpy_mpconfig.mk | 8 + shared-bindings/emmcio/EMMC.c | 461 +++++++++ shared-bindings/emmcio/EMMC.h | 34 + shared-bindings/emmcio/__init__.c | 92 ++ shared-bindings/emmcio/__init__.h | 11 + shared-module/emmcio/__init__.c | 133 +++ shared-module/emmcio/__init__.h | 41 + supervisor/shared/filesystem.c | 12 + supervisor/shared/usb/usb_msc_flash.c | 46 +- 17 files changed, 2292 insertions(+), 2 deletions(-) create mode 100644 ports/nordic/common-hal/emmcio/EMMC.c create mode 100644 ports/nordic/common-hal/emmcio/EMMC.h create mode 100644 ports/nordic/common-hal/emmcio/__init__.c create mode 100644 ports/nordic/common-hal/emmcio/emmc_hw.h create mode 100644 shared-bindings/emmcio/EMMC.c create mode 100644 shared-bindings/emmcio/EMMC.h create mode 100644 shared-bindings/emmcio/__init__.c create mode 100644 shared-bindings/emmcio/__init__.h create mode 100644 shared-module/emmcio/__init__.c create mode 100644 shared-module/emmcio/__init__.h diff --git a/extmod/vfs_blockdev.c b/extmod/vfs_blockdev.c index 74d1262364e..2b47db07573 100644 --- a/extmod/vfs_blockdev.c +++ b/extmod/vfs_blockdev.c @@ -38,6 +38,10 @@ #include "shared-bindings/sdioio/SDCard.h" #endif +#if CIRCUITPY_EMMCIO +#include "shared-bindings/emmcio/EMMC.h" +#endif + #if MICROPY_VFS @@ -75,6 +79,25 @@ void mp_vfs_blockdev_init(mp_vfs_blockdev_t *self, mp_obj_t bdev) { self->u.ioctl[2] = (mp_obj_t)sdioio_sdcard_ioctl; // native version } #endif + + #if CIRCUITPY_EMMCIO + if (mp_obj_get_type(bdev) == &emmcio_emmc_type) { + self->flags |= MP_BLOCKDEV_FLAG_NATIVE | MP_BLOCKDEV_FLAG_HAVE_IOCTL; + self->readblocks[0] = mp_const_none; + self->readblocks[1] = bdev; + self->readblocks[2] = (mp_obj_t)emmcio_emmc_readblocks_native; + if (emmcio_emmc_is_write_enabled(bdev)) { + self->writeblocks[0] = mp_const_none; + self->writeblocks[1] = bdev; + self->writeblocks[2] = (mp_obj_t)emmcio_emmc_writeblocks_native; + } else { + self->writeblocks[0] = MP_OBJ_NULL; + } + self->u.ioctl[0] = mp_const_none; + self->u.ioctl[1] = bdev; + self->u.ioctl[2] = (mp_obj_t)emmcio_emmc_ioctl_native; + } + #endif if (self->u.ioctl[0] != MP_OBJ_NULL) { // Device supports new block protocol, so indicate it self->flags |= MP_BLOCKDEV_FLAG_HAVE_IOCTL; diff --git a/ports/nordic/common-hal/busio/SPI.c b/ports/nordic/common-hal/busio/SPI.c index de54dd08a27..5e0f2b242dc 100644 --- a/ports/nordic/common-hal/busio/SPI.c +++ b/ports/nordic/common-hal/busio/SPI.c @@ -14,6 +14,10 @@ #include "nrfx_spim.h" #include "nrf_gpio.h" +#if CIRCUITPY_EMMCIO +#include "common-hal/emmcio/EMMC.h" +#endif + #ifndef NRFX_SPIM3_ENABLED #define NRFX_SPIM3_ENABLED (0) #endif @@ -122,6 +126,11 @@ void common_hal_busio_spi_construct(busio_spi_obj_t *self, const mcu_pin_obj_t * // Find a free instance, with most desirable (highest freq and not shared) allocated first. self->spim_peripheral = NULL; for (size_t i = 0; i < MP_ARRAY_SIZE(spim_peripherals); i++) { + #if CIRCUITPY_EMMCIO + if (spim_peripherals[i].spim.p_reg == NRF_SPIM3 && emmcio_spim3_in_use()) { + continue; + } + #endif if ((spim_peripherals[i].spim.p_reg->ENABLE & SPIM_ENABLE_ENABLE_Msk) == 0) { self->spim_peripheral = &spim_peripherals[i]; break; diff --git a/ports/nordic/common-hal/emmcio/EMMC.c b/ports/nordic/common-hal/emmcio/EMMC.c new file mode 100644 index 00000000000..3d5e5f7d1ba --- /dev/null +++ b/ports/nordic/common-hal/emmcio/EMMC.c @@ -0,0 +1,1073 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Tim Cocks for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +// ============================================================================ +// eMMC flash driver (1-bit MMC protocol over the nRF52840) +// Read and write paths; writing is gated at runtime by +// EMMC(write_enabled=True). +// ============================================================================ +// Two layers: +// +// * COMMAND / control phases (init, CMD17/18 headers, busy polling) are +// bit-banged on GPIO. They are short and timing-insensitive. +// +// * The 512-byte DATA payloads ride SPIM3 + EasyDMA at 16 MHz. eMMC DAT0 at +// default speed is SPI-mode-0 compatible: the host launches data while +// CLK is low, the card samples (and launches) on the rising edge, MSB +// first. The start-bit hunt is bit-banged, then the payload + CRC16 is +// exactly byte-aligned for one RX DMA; on ENABLE=0 the pins fall back to +// their GPIO latches, so the surrounding bit-bang continues seamlessly. +// +// INTEGRITY: every block read is verified against the card's CRC16 and the +// caller retries on a mismatch. +// +// ============================================================================ + +#include "common-hal/emmcio/EMMC.h" +#include "common-hal/emmcio/emmc_hw.h" + +#include + +#include "extmod/vfs.h" // MP_BLOCKDEV_IOCTL_* +#include "py/mphal.h" +#include "py/runtime.h" // RUN_BACKGROUND_TASKS +#include "shared-bindings/microcontroller/__init__.h" +#include "common-hal/microcontroller/Pin.h" +#include "peripherals/nrf/nrf52840/pins.h" +#include "shared-module/emmcio/__init__.h" + +#define CMD_SAFE_HALF_US 1u // slow clock for the IDENTIFICATION phase only + +// Command-phase half-period: starts safe (eMMC identification requires a slow +// clock), switched to 0 (full-speed bit-bang, ~1-2 MHz) once init completes. +static uint32_t s_cmd_half_us = CMD_SAFE_HALF_US; + +static volatile uint32_t g_emmc_clk_half_us = CMD_SAFE_HALF_US; + + +static bool s_ready; +static uint32_t s_rca; +static uint32_t s_block_count; // from EXT_CSD SEC_COUNT; 0 = not read yet +static uint8_t s_device_type; // from EXT_CSD[196]; 0 = not read yet + +// One 512-byte block + its CRC16, byte-aligned, for the RX DMA. +static uint8_t s_dma_rx[EMMC_BLOCK_SIZE + 2]; + +// ---- bounded-wait helpers -------------------------------------------------- +// The 32768 Hz counter is 24-bit, so every elapsed calculation masks. +#define US_TO_TICKS(us) ((uint32_t)(((uint64_t)(us) * EMMC_TICKS_HZ + 999999u) / 1000000u)) + +static inline uint32_t ticks_since(uint32_t t0) { + return ticks_since_raw(t0); +} + +static inline void half_delay(uint32_t us) { + if (us) { + common_hal_mcu_delay_us(us); + } +} + +static bool s_deadline_armed; +static uint32_t s_deadline_t0; +static uint32_t s_deadline_lim; + +void common_hal_emmcio_emmc_set_deadline(uint32_t timeout_us) { + s_deadline_t0 = EMMC_TICKS(); + s_deadline_lim = US_TO_TICKS(timeout_us); + s_deadline_armed = true; +} + +void common_hal_emmcio_emmc_clear_deadline(void) { + s_deadline_armed = false; +} + +static bool emmc_deadline_expired(void) { + return s_deadline_armed && ticks_since(s_deadline_t0) >= s_deadline_lim; +} + +// Long-wait service: run background tasks +static inline void emmc_yield(void) { + RUN_BACKGROUND_TASKS; +} + +// Safe clock pulse for command/CRC phases. +static inline void clk_pulse(void) { + CLK_HIGH(); + half_delay(s_cmd_half_us); + CLK_LOW(); + half_delay(s_cmd_half_us); +} + +static void cmd_send_bit(uint8_t bit) { + // caller (send_command) sets CMD_OUT() once. + if (bit) { + CMD_HIGH(); + } else { + CMD_LOW(); + } + clk_pulse(); +} + +// SAMPLE POINT: read the line at the END of the low phase, i.e. before this +// bit's clock pulse, not in the middle of it. That is the one point in the +// cycle where BOTH of the card's timing modes hold valid data, which is what +// makes this path work either side of an HS_TIMING switch: +// +// * backward-compatible timing: the card launches on the FALLING edge and +// holds the bit until the next one, so the whole low phase is valid. +// tOSU(min) = tWL(min) - tODLY, data good from ~8 ns after the edge. +// We read a full low phase later. +// * high-speed timing: the card launches on the RISING edge (tODLY, 13.7 ns +// max, referenced to it) and holds until the next rising edge, so the low +// phase is again inside the window. +// +static uint8_t cmd_recv_bit(void) { + // caller sets CMD_IN() once before the response read + uint8_t b = (uint8_t)READ_CMD(); + clk_pulse(); + return b; +} + +static uint8_t crc7(const uint8_t *data, uint8_t len) { + uint8_t crc = 0; + for (uint8_t i = 0; i < len; i++) { + uint8_t v = data[i]; + for (int b = 7; b >= 0; b--) { + crc <<= 1; + if (((v >> b) & 1) ^ ((crc >> 7) & 1)) { + crc ^= 0x09; + } + crc &= 0x7F; + } + } + return (crc << 1) | 1; +} + +// Table-driven CRC16-CCITT +static uint16_t s_crc16_tab[256]; +static void crc16_tab_init(void) { + for (uint32_t i = 0; i < 256; i++) { + uint16_t crc = (uint16_t)(i << 8); + for (int b = 0; b < 8; b++) { + crc = (crc & 0x8000) ? (uint16_t)((crc << 1) ^ 0x1021) : (uint16_t)(crc << 1); + } + s_crc16_tab[i] = crc; + } +} + +// The port builds at -Os, so this is an opt-up. crc16 is pure computation, +// the level can only change its speed, never its value. +__attribute__((optimize("O2"))) +static uint16_t crc16(const uint8_t *data, uint32_t len) { + uint16_t crc = 0; + for (uint32_t i = 0; i < len; i++) { + crc = (uint16_t)((crc << 8) ^ s_crc16_tab[(crc >> 8) ^ data[i]]); + } + return crc; +} + +static bool send_command(uint8_t cmd_index, uint32_t arg, uint8_t *r1_out) { + uint8_t frame[6]; + frame[0] = 0x40 | (cmd_index & 0x3F); + frame[1] = (uint8_t)(arg >> 24); + frame[2] = (uint8_t)(arg >> 16); + frame[3] = (uint8_t)(arg >> 8); + frame[4] = (uint8_t)(arg); + frame[5] = crc7(frame, 5); + + // PRE-COMMAND GAP on an UNDRIVEN line + CMD_IN(); + for (int i = 0; i < 24; i++) { + clk_pulse(); + } + CMD_OUT(); + cmd_send_bit(0); + cmd_send_bit(1); + for (int b = 5; b >= 0; b--) { + cmd_send_bit((frame[0] >> b) & 1); + } + for (int i = 1; i <= 4; i++) { + for (int b = 7; b >= 0; b--) { + cmd_send_bit((frame[i] >> b) & 1); + } + } + for (int b = 7; b >= 1; b--) { + cmd_send_bit((frame[5] >> b) & 1); + } + cmd_send_bit(1); + + CMD_IN(); + bool responded = false; + for (int t = 0; t < 200; t++) { + clk_pulse(); + if (!READ_CMD()) { + responded = true; + break; + } + } + if (!responded) { + return false; + } + + if (!r1_out) { + return true; + } + + uint8_t resp[6] = {0}; + for (int i = 0; i < 38; i++) { + uint8_t bit = cmd_recv_bit(); + resp[i / 8] |= (bit << (7 - (i % 8))); + } + memcpy(r1_out, resp, 6); + + // Leave CMD as an INPUT (pulled up) + return true; +} + +// Bit-banged MMC commands intermittently miss the response on the first try +// (settling after the previous command); retry until the card answers. +static bool send_command_retry(uint8_t cmd, uint32_t arg, uint8_t *r1_out, int tries) { + for (int t = 0; t < tries; t++) { + if (emmc_deadline_expired()) { + return false; + } + if (send_command(cmd, arg, r1_out)) { + return true; + } + if (t == 0) { + // First miss = the card still settling after the previous burst: a + // handful of idle clocks is all it needs. + for (int c = 0; c < 16; c++) { + clk_pulse(); + } + } else { + mp_hal_delay_ms(2); + } + } + return false; +} + +// DATA read: per-bit CLK toggle uses the configurable (possibly 0) half-period. +// -O2 opts up from the port's -Os default and is load-bearing: at -Os the GPIO +// and delay helpers stop being inlined and become calls inside the per-bit +// loop, where the instruction count is the bit timing. +__attribute__((optimize("O2"))) +static bool read_data_block(uint8_t *buf) { + const uint32_t hd = g_emmc_clk_half_us; + const uint32_t clk_bit = emmc_pinout.clk_bit; + const uint32_t dat_bit = emmc_pinout.dat_bit; + + DAT0_IN(); + // START-BIT HUNT + { + uint32_t t0 = EMMC_TICKS(); + const uint32_t lim = US_TO_TICKS(80000u); // 80 ms bound + const uint32_t yield_at = US_TO_TICKS(500u); + bool got_start = false; + for (;;) { + // This hunt samples in the HIGH phase and stays there in both + // timing modes + for (int burst = 0; burst < 64 && !got_start; burst++) { + RCLK_HIGH(clk_bit); + half_delay(hd); + EDGE_SETTLE(); + if (!RDAT_GET(dat_bit)) { + got_start = true; // leave with RCLK HIGH (as before) + break; + } + RCLK_LOW(clk_bit); + half_delay(hd); + } + uint32_t el = ticks_since(t0); + if (got_start) { + break; + } + if (el >= lim || emmc_deadline_expired()) { + return false; + } + if (el >= yield_at) { + emmc_yield(); + } + } + } + RCLK_LOW(clk_bit); + half_delay(hd); + + // The start bit was just consumed by the bit-bang hunt above, so the + // remaining 512 data bytes + CRC16 are exactly byte-aligned. + emmc_spim_xfer(NULL, 0, s_dma_rx, sizeof(s_dma_rx)); + memcpy(buf, s_dma_rx, EMMC_BLOCK_SIZE); + uint16_t card_crc = (uint16_t)(((uint16_t)s_dma_rx[EMMC_BLOCK_SIZE] << 8) | + s_dma_rx[EMMC_BLOCK_SIZE + 1]); + RCLK_HIGH(clk_bit); + half_delay(hd); + RCLK_LOW(clk_bit); + half_delay(hd); // end bit + bool crc_ok = crc16(buf, EMMC_BLOCK_SIZE) == card_crc; + DAT0_OUT(); + RDAT_HIGH(dat_bit); + return crc_ok; // a mismatch: caller retries +} + +bool common_hal_emmcio_emmc_read_status(uint8_t *r1_out) { + return send_command_retry(13, s_rca, r1_out, 8); +} + +// Clock out an R2 response and reassemble the CID. R2 framing: start(0) + +// transmission(0) + 6 reserved ones + CID[127:1] + end(1) = 136 bits. +static void drain_r2_cid(uint8_t *cid_out) { + uint8_t bits[136]; + for (int i = 0; i < 136; i++) { + bits[i] = cmd_recv_bit(); + } + memset(cid_out, 0, 16); + for (int i = 0; i < 128; i++) { + // bits[0] start, bits[1] transmission, bits[2..7] six reserved ones, + // bits[8..134] CID[127:1], bits[135] end bit + cid_out[i / 8] |= (uint8_t)(bits[8 + i] << (7 - (i % 8))); + } +} + +#define EMMC_POWER_OFF_MS 50u + +static void emmc_power_cycle(void) { + emmc_spim_deinit(); // SPIM3 must not drive DAT0 either + emmc_pins_init(); + + RST_ASSERT(); + CLK_LOW(); + CMD_LOW(); + DAT0_OUT(); + DAT0_LOW(); + VCCQ_OFF(); + mp_hal_delay_ms(EMMC_POWER_OFF_MS); +} + +static bool emmc_init(emmcio_emmc_obj_t *self) { + s_ready = false; + s_block_count = 0; + s_device_type = 0; + g_emmc_clk_half_us = CMD_SAFE_HALF_US; + s_cmd_half_us = CMD_SAFE_HALF_US; + + self->cmd0_sent = false; + self->cmd1_retries = -1; + self->cmd2_resp = false; + self->cmd3_resp = false; + self->cmd7_resp = false; + self->cmd16_resp = false; + memset(self->cid, 0, sizeof(self->cid)); + self->hs_switch_error = false; + self->hs_active = false; + self->hs_stage = 0; + + emmc_power_cycle(); + + emmc_spim_init(); // hardware-clocked data path, at M16 + crc16_tab_init(); + + CLK_LOW(); + CMD_HIGH(); + DAT0_HIGH(); + + VCCQ_ON(); + mp_hal_delay_ms(10); + + RST_ASSERT(); + mp_hal_delay_ms(1); + RST_RELEASE(); + mp_hal_delay_ms(2); + + CMD_HIGH(); + for (int i = 0; i < 80; i++) { // 74+ clocks before the first command + clk_pulse(); + } + + send_command(0, 0x00000000, NULL); // CMD0 GO_IDLE (no response expected) + self->cmd0_sent = true; + mp_hal_delay_ms(1); + + // CMD1 SEND_OP_COND, arg 0x40FF8000: HCS=1 + uint8_t r3[6] = {0}; + for (int retry = 0; retry < 1000; retry++) { + bool ok = send_command(1, 0x40FF8000, r3); + emmc_yield(); + mp_hal_delay_ms(1); + if (ok && (r3[1] & 0x80)) { // response seen AND busy bit set = ready + self->cmd1_retries = retry; + break; + } + if (emmc_deadline_expired()) { + break; + } + } + if (self->cmd1_retries < 0) { // card never responded ready -> stop + return false; + } + + for (int t = 0; t < 8; t++) { + self->cmd2_resp = send_command(2, 0, NULL); + if (self->cmd2_resp) { + drain_r2_cid(self->cid); + break; + } + mp_hal_delay_ms(2); + } + mp_hal_delay_ms(1); + + uint8_t r6[6] = {0}; + s_rca = 0x0001u << 16; + self->cmd3_resp = send_command_retry(3, s_rca, r6, 8); // SET_RELATIVE_ADDR + mp_hal_delay_ms(1); + + uint8_t r1[6] = {0}; + self->cmd7_resp = send_command_retry(7, s_rca, r1, 8); // SELECT_CARD + mp_hal_delay_ms(1); + self->cmd16_resp = send_command_retry(16, EMMC_BLOCK_SIZE, r1, 8); // SET_BLOCKLEN + mp_hal_delay_ms(1); + + // strict: ready only if the card actually selected AND accepted block length + s_ready = self->cmd7_resp && self->cmd16_resp; + if (s_ready) { + s_cmd_half_us = 0u; // identification done: full-speed commands + g_emmc_clk_half_us = 0u; + } + return s_ready; +} + +uint32_t common_hal_emmcio_emmc_get_block_count(emmcio_emmc_obj_t *self) { + (void)self; + return s_block_count; +} + +// The block-device ioctl +bool common_hal_emmcio_emmc_ioctl(uint32_t op, uint32_t arg, uint32_t *out_value) { + (void)arg; + *out_value = 0; + switch (op) { + case MP_BLOCKDEV_IOCTL_INIT: + // The constructor already did the whole CMD0..CMD16 + EXT_CSD + // walk, or raised. 0 means "initialised"; a card that has since + // been deinited answers with the error the callers check for + // (s_ready), so a mount over a dead object fails at INIT rather + // than at the first read. + *out_value = s_ready ? 0u : 1u; + break; + case MP_BLOCKDEV_IOCTL_DEINIT: + case MP_BLOCKDEV_IOCTL_SYNC: + case MP_BLOCKDEV_IOCTL_BLOCK_ERASE: + break; + case MP_BLOCKDEV_IOCTL_BLOCK_COUNT: + *out_value = s_block_count; + break; + case MP_BLOCKDEV_IOCTL_BLOCK_SIZE: + *out_value = EMMC_BLOCK_SIZE; + break; + default: + return false; + } + return true; +} + +// Power-off: release the bus pins and cut the VCCQ I/O rail. +// The card is gone until the next emmc_init(). +static void emmc_power_down(void) { + s_ready = false; + s_block_count = 0; + emmc_spim_deinit(); + RST_ASSERT(); + emmc_pins_release(); + VCCQ_OFF(); // rail off (pin stays an output) +} + +// CMD8 SEND_EXT_CSD: an ADTC (read) command -- the card responds R1, then +// sends a single 512-byte EXT_CSD data block on DAT0 exactly like CMD17. +// Read-only and safe. buf must be >= EMMC_BLOCK_SIZE. +bool common_hal_emmcio_emmc_read_ext_csd(uint8_t *buf) { + if (!s_ready) { + return false; + } + uint8_t r1[6]; + if (!send_command_retry(8, 0, r1, 8)) { + return false; + } + if (!read_data_block(buf)) { + return false; + } + // SEC_COUNT[215:212], little-endian. 0x00760000 on this part = 7,733,248 + // blocks; the value is the software LBA bound for every later read. + s_block_count = (uint32_t)buf[212] | ((uint32_t)buf[213] << 8) | + ((uint32_t)buf[214] << 16) | ((uint32_t)buf[215] << 24); + // DEVICE_TYPE[196] gates the HS_TIMING switch (bit 1 = 52 MHz supported; + // this part reads 0x57). + s_device_type = buf[196]; + return true; +} + +// ---- R1b / program busy on DAT0 -------------------------------------------- +// Shared by the CMD6 switch (below) and the write path (further down): the +// card pulls DAT0 low while it programs and releases it high when done, and it +// only advances on OUR clock, so the host must keep clocking for the card to +// get anywhere. + +#define EMMC_BUSY_LEADIN_CLOCKS 16 + +// run_bg says whether a long stall may run background tasks. +// true -- the wait is between transfers, so it is safe to let the rest of +// the system have a turn. +// false -- the wait is inside a write, with the card mid-program. Nothing +// runs, so no background task can re-enter this driver or change +// the board's state out from under a programming card. The stall +// is bounded (<=500 ms) and background tasks resume between +// blocks and between calls. +static bool dat0_busy_wait(uint32_t timeout_us, bool run_bg) { + DAT0_IN(); // never drive against a busy card + for (int i = 0; i < EMMC_BUSY_LEADIN_CLOCKS; i++) { + clk_pulse(); + } + uint32_t t0 = EMMC_TICKS(); + const uint32_t lim = US_TO_TICKS(timeout_us); + for (;;) { + bool released = false; + for (int i = 0; i < 64 && !released; i++) { + CLK_HIGH(); + half_delay(s_cmd_half_us); + released = READ_DAT0() != 0; + CLK_LOW(); + half_delay(s_cmd_half_us); + } + uint32_t el = ticks_since(t0); + if (released) { + DAT0_OUT(); // back to the read path's resting state + DAT0_HIGH(); + return true; + } + if (el >= lim || emmc_deadline_expired()) { + // DAT0 STAYS AN INPUT on a timeout + return false; + } + if (run_bg) { + emmc_yield(); + } + } +} + +// CMD6 SWITCH argument: access 0b11 (WRITE_BYTE) | index 185 | value 1 | +// cmd_set 0 -> 0x03 B9 01 00. +#define EMMC_SWITCH_HS_TIMING_ARG 0x03B90100u +#define EMMC_EXT_CSD_HS_TIMING 185u +#define EMMC_EXT_CSD_DEVICE_TYPE 196u +#define EMMC_DEVICE_TYPE_HS52 0x02u + +// GENERIC_CMD6_TIME on this part is 0x05 = 50 ms. Ten times that is the bound. +#define EMMC_CMD6_BUSY_US 500000u + +// Poll CMD13 until the card is back in tran and ready for data. This is the +// authoritative "the switch finished" test, and it is also where SWITCH_ERROR +// (status bit 7) shows up if the card rejected the write. +static bool wait_tran_after_switch(emmcio_emmc_obj_t *self, uint32_t timeout_us) { + uint32_t t0 = EMMC_TICKS(); + const uint32_t lim = US_TO_TICKS(timeout_us); + for (;;) { + uint8_t r1[6]; + if (common_hal_emmcio_emmc_read_status(r1)) { + uint32_t status = ((uint32_t)r1[1] << 24) | ((uint32_t)r1[2] << 16) | + ((uint32_t)r1[3] << 8) | (uint32_t)r1[4]; + if (status & (1u << 7)) { // SWITCH_ERROR: the card said no + self->hs_switch_error = true; + return false; + } + if (((status >> 9) & 0xFu) == 4u && ((status >> 8) & 1u)) { + return true; // tran + ready_for_data + } + } + if (ticks_since(t0) >= lim || emmc_deadline_expired()) { + return false; + } + emmc_yield(); + mp_hal_delay_ms(1); + } +} + +static bool emmc_set_high_speed(emmcio_emmc_obj_t *self) { + if (!s_ready) { + return false; + } + // Gate on the card's own capability byte. + if (!(s_device_type & EMMC_DEVICE_TYPE_HS52)) { + return false; + } + self->hs_stage = 1; + + uint8_t r1[6]; + if (!send_command_retry(6, EMMC_SWITCH_HS_TIMING_ARG, r1, 8)) { + return false; + } + self->hs_stage = 2; + // run_bg = true: a CMD6 on a volatile byte has no in-flight card state a + // power-off gesture could damage, so this wait services them as the read + // path does. + if (!dat0_busy_wait(EMMC_CMD6_BUSY_US, true)) { + return false; + } + self->hs_stage = 3; + if (!wait_tran_after_switch(self, EMMC_CMD6_BUSY_US)) { + return false; + } + self->hs_stage = 4; + + // THE DATA PATH'S HALF OF THE SWITCH. HS_TIMING moves the edge the card + // launches DAT0 on, from falling to rising, so SPIM has to move its sample + // edge with it (CPHA=1) or every block after this point comes back shifted + // by a bit and fails its CRC16. The command path needs no such flag, + // cmd_recv_bit() reads at a point that is valid in both timings. But, + // SPIM samples on an edge, and an edge has to pick one. + // + // This happens BEFORE the readback, because the readback is itself a block + // read off a card that has already switched. + NRF_SPIM3->CONFIG = SPIM_CONFIG_MODE1; + + // Read the byte back AT THE OLD CLOCK. A card that ACKed the switch but did + // not take it would otherwise be met with a 32 MHz bus it never agreed to, + // and the only symptom would be CRC noise that looks like a wiring fault. + uint8_t ext_csd[EMMC_BLOCK_SIZE]; + if (!common_hal_emmcio_emmc_read_ext_csd(ext_csd) || + ext_csd[EMMC_EXT_CSD_HS_TIMING] != 1u) { + NRF_SPIM3->CONFIG = SPIM_CONFIG_MODE0; + return false; // still at M16, card still readable + } + self->hs_stage = 5; + + // Only now does the host clock move. The re-read is a smoke test of the + // faster bus with the integrity layer watching: if the first fast transfer + // cannot even fetch a block the card just served correctly, fall straight + // back. + NRF_SPIM3->FREQUENCY = SPIM_FREQ_M32; + if (!common_hal_emmcio_emmc_read_ext_csd(ext_csd) || ext_csd[EMMC_EXT_CSD_HS_TIMING] != 1u) { + // Back to the old CLOCK but NOT to the old phase: the card is in + // high-speed timing and stays there until the rail drops, and + // high-speed timing is specified from 0 Hz up. Mode 1 is how we talk + // to it at M16 now. + NRF_SPIM3->FREQUENCY = SPIM_FREQ_M16; + return false; + } + self->hs_active = true; + self->hs_stage = 6; + return true; +} + +bool common_hal_emmcio_emmc_readblocks(uint32_t block_addr, uint8_t *buf, uint32_t count) { + if (!s_ready || count == 0) { + return false; + } + // Reject an out-of-range LBA before any command reaches the card + if (s_block_count != 0 && + (block_addr >= s_block_count || count > s_block_count - block_addr)) { + return false; + } + uint8_t r1[6]; + if (count == 1) { + if (!send_command_retry(17, block_addr, r1, 8)) { + return false; + } + return read_data_block(buf); + } + // RETRY like CMD17 above: at high bus duty the card intermittently misses + // the first command after the previous burst's CMD12 + if (!send_command_retry(18, block_addr, r1, 4)) { + return false; + } + + uint32_t bt0 = EMMC_TICKS(); + const uint32_t blim = US_TO_TICKS(150000u); + for (uint32_t i = 0; i < count; i++) { + if (i && (ticks_since(bt0) >= blim || emmc_deadline_expired())) { + (void)send_command_retry(12, 0, r1, 3); + return false; + } + if (!read_data_block(buf + i * EMMC_BLOCK_SIZE)) { + (void)send_command_retry(12, 0, r1, 3); + return false; + } + } + (void)send_command_retry(12, 0, r1, 3); + return true; +} + +// The card declares MIN_PERF_W_* = 0x00: no minimum write performance +#define EMMC_WR_BUSY_US 500000u +// Same shape as the read side +#define EMMC_WR_BURST_US 250000u + +// -Os states the intent for this bit-bang, but matches the port default and so +// changes nothing today. +__attribute__((optimize("Os"))) +static bool write_data_block(const uint8_t *buf) { + const uint32_t hd = g_emmc_clk_half_us; + const uint32_t clk_bit = emmc_pinout.clk_bit; + const uint32_t dat_bit = emmc_pinout.dat_bit; + + // Write convention: change DAT0 while CLK is LOW, then a full half-period + // of setup before the rising edge where the card latches it. DAT0 is a + // HIGH-DRIVE (H0H1) output. + // + // The frame opens with DAT0 idle-HIGH for a whole byte (the Nwr gap) so + // the card cannot mistake a stray low for an early start bit and misframe + // the token. + DAT0_OUT(); + RDAT_HIGH(dat_bit); + + uint8_t *tx = EMMC_TX_FRAME; // the reserved low-RAM SPIM3 buffer + uint16_t crc = crc16(buf, EMMC_BLOCK_SIZE); + tx[0] = 0xFF; // Nwr idle gap + tx[1] = 0xFE; // 7 idle bits + START 0 + memcpy(&tx[2], buf, EMMC_BLOCK_SIZE); + tx[2 + EMMC_BLOCK_SIZE] = (uint8_t)(crc >> 8); + tx[2 + EMMC_BLOCK_SIZE + 1] = (uint8_t)crc; + RCLK_LOW(clk_bit); + // Launch edge is mode 0's, always + // + // HS_TIMING moved the card's OUTPUT edge, and only that. Its input timing + // is unchanged: both of the datasheet's tables (p.18 high-speed, p.19 + // backward-compatible) give tISU = tIH = 3 ns for CMD/DAT "referenced to + // CLK", i.e. the card latches the host on the rising edge in either mode. + // So the read path has to follow the card to CPHA=1 and the write path + // must NOT: in mode 1 SPIM shifts MOSI on the leading edge, which is the + // very edge the card samples -- zero setup against a 3 ns requirement, + // and the card takes the previous bit. Mode 0 shifts on the trailing + // edge and hands the card a whole half period of setup: 31 ns at M16, + // 15.6 ns at M32, both an order of magnitude over tISU. + // + // Saving and restoring rather than assuming keeps "the peripheral + // register is the state" true for the read path (emmc_hw.h): this + // function borrows the phase for one DMA and gives it back. Two register + // writes against a ~130 us transfer. + const uint32_t saved_cfg = NRF_SPIM3->CONFIG; + if (saved_cfg != SPIM_CONFIG_MODE0) { + NRF_SPIM3->CONFIG = SPIM_CONFIG_MODE0; + } + // The TX frame ends exactly at the crc's last bit, no trailing idle + // byte. The card emits its CRC-status token a couple of clocks after the + // end bit. + emmc_spim_xfer(tx, 2u + EMMC_BLOCK_SIZE + 2u, NULL, 0); + if (saved_cfg != SPIM_CONFIG_MODE0) { + NRF_SPIM3->CONFIG = saved_cfg; + } + // END bit: DAT0 is back at its GPIO latch (output HIGH) -- clock it. + half_delay(hd); + EDGE_SETTLE(); + RCLK_HIGH(clk_bit); + half_delay(hd); + RCLK_LOW(clk_bit); + + // CRC-status token: the card drives DAT0 low (start bit), then 3 status + // bits -- 010 accepted, 101 CRC error, 110 write error -- then releases. + DAT0_IN(); + int wr_status = -1; + for (int i = 0; i < 16; i++) { + RCLK_HIGH(clk_bit); + half_delay(hd); + EDGE_SETTLE(); + int start = (int)RDAT_GET(dat_bit); + RCLK_LOW(clk_bit); + half_delay(hd); + if (!start) { + int st = 0; + for (int k = 0; k < 3; k++) { + RCLK_HIGH(clk_bit); + half_delay(hd); + EDGE_SETTLE(); + st = (st << 1) | (int)RDAT_GET(dat_bit); + RCLK_LOW(clk_bit); + half_delay(hd); + } + wr_status = st; + break; + } + } + + // Programming busy on DAT0 + if (!dat0_busy_wait(EMMC_WR_BUSY_US, false)) { + return false; // DAT0 left an INPUT -- see the wait + } + + // ENFORCE the token: 0b010 = accepted. Anything else -- including "never + // saw one" -- means the card did not take the block, and returning false + // makes the caller retry instead of believing a glitch was stored. + if (wr_status != 0x2) { + return false; + } + return true; +} + +bool common_hal_emmcio_emmc_writeblocks(uint32_t block_addr, const uint8_t *buf, uint32_t count) { + if (!s_ready || count == 0) { + return false; + } + if (s_block_count != 0 && + (block_addr >= s_block_count || count > s_block_count - block_addr)) { + return false; + } + uint8_t r1[6]; + if (count == 1) { + if (!send_command_retry(24, block_addr, r1, 8)) { + return false; + } + return write_data_block(buf); + } + // Settle-miss retry, exactly as CMD18: at high bus duty the card + // intermittently misses the first command after the previous burst. + if (!send_command_retry(25, block_addr, r1, 4)) { + return false; + } + uint32_t bt0 = EMMC_TICKS(); + const uint32_t blim = US_TO_TICKS(EMMC_WR_BURST_US); + for (uint32_t i = 0; i < count; i++) { + if (i && (ticks_since(bt0) >= blim || emmc_deadline_expired())) { + (void)send_command_retry(12, 0, r1, 3); + return false; + } + if (!write_data_block(buf + i * EMMC_BLOCK_SIZE)) { + (void)send_command_retry(12, 0, r1, 3); + return false; + } + } + (void)send_command_retry(12, 0, r1, 3); + + (void)dat0_busy_wait(EMMC_WR_BUSY_US, false); + return true; +} + +uint32_t common_hal_emmcio_emmc_get_frequency(emmcio_emmc_obj_t *self) { + // SPIM3's M16/M32 codes are special values, NOT points on the linear scale + // the K125..M8 codes sit on (0x0A000000 would decode to 156 MHz there), so + // this is a lookup and not arithmetic. Only two values are ever written. + (void)self; + return NRF_SPIM3->FREQUENCY == SPIM_FREQ_M32 ? 32000000u : 16000000u; +} + +bool common_hal_emmcio_emmc_get_high_speed(emmcio_emmc_obj_t *self) { + return self->hs_active; +} + +const uint8_t *common_hal_emmcio_emmc_get_cid(emmcio_emmc_obj_t *self) { + return self->cid; +} + +static bool s_constructed; + +emmc_pinout_t emmc_pinout; + +static const mcu_pin_obj_t *s_claimed_pins[5]; +static size_t s_claimed_pin_count; + +bool emmcio_spim3_in_use(void) { + return s_constructed; +} + +void emmcio_emmc_release_hardware(void) { + emmc_power_down(); + for (size_t i = 0; i < s_claimed_pin_count; i++) { + reset_pin_number(s_claimed_pins[i]->number); + } + s_claimed_pin_count = 0; + s_constructed = false; +} + +static void emmc_claim_pins(const mcu_pin_obj_t *clock, const mcu_pin_obj_t *command, + const mcu_pin_obj_t *data, const mcu_pin_obj_t *reset, const mcu_pin_obj_t *vccq, + bool never_reset) { + emmc_pinout.clk = clock->number; + emmc_pinout.cmd = command->number; + emmc_pinout.dat0 = data->number; + emmc_pinout.rst = reset != NULL ? reset->number : EMMC_NO_PIN; + emmc_pinout.vccq = vccq != NULL ? vccq->number : EMMC_NO_PIN; + emmc_pinout.clk_bit = 1u << clock->number; + emmc_pinout.dat_bit = 1u << data->number; + emmc_pinout.dat0_cnf = &NRF_P0->PIN_CNF[data->number]; + emmc_pinout.dat0_cnf_in = EMMC_CNF_IN; + emmc_pinout.dat0_cnf_out = EMMC_CNF_OUT_H0H1; + + s_claimed_pin_count = 0; + const mcu_pin_obj_t *pins[] = { clock, command, data, reset, vccq }; + for (size_t i = 0; i < MP_ARRAY_SIZE(pins); i++) { + if (pins[i] == NULL) { + continue; + } + claim_pin(pins[i]); + if (never_reset) { + never_reset_pin_number(pins[i]->number); + } + s_claimed_pins[s_claimed_pin_count++] = pins[i]; + } +} + +static mp_rom_error_text_t emmc_check_pins(const mcu_pin_obj_t *clock, + const mcu_pin_obj_t *command, const mcu_pin_obj_t *data, + const mcu_pin_obj_t *reset, const mcu_pin_obj_t *vccq) { + // The data path drives CLK and DAT0 through NRF_P0 directly, so both have + // to be on port 0. CMD, RESET and VCCQ go through the HAL and may be + // anywhere. + if (clock->number >= P0_PIN_NUM || data->number >= P0_PIN_NUM) { + return MP_ERROR_TEXT("eMMC clock and data must be on port 0"); + } + if ((NRF_SPIM3->ENABLE & SPIM_ENABLE_ENABLE_Msk) != 0) { + return MP_ERROR_TEXT("SPI peripheral in use"); + } + const mcu_pin_obj_t *pins[] = { clock, command, data, reset, vccq }; + for (size_t i = 0; i < MP_ARRAY_SIZE(pins); i++) { + if (pins[i] != NULL && !pin_number_is_free(pins[i]->number)) { + return MP_ERROR_TEXT("Hardware in use, try alternative pins"); + } + } + return NULL; +} + +static const char *init_failure_stage(emmcio_emmc_obj_t *self) { + if (!self->cmd0_sent) { + return "cmd0"; + } + if (self->cmd1_retries < 0) { + return "cmd1 (card never ready)"; + } + if (!self->cmd2_resp) { + return "cmd2 (no CID)"; + } + if (!self->cmd3_resp) { + return "cmd3"; + } + if (!self->cmd7_resp) { + return "cmd7 (select)"; + } + if (!self->cmd16_resp) { + return "cmd16 (blocklen)"; + } + return "ext_csd"; +} + +static const char *hs_failure_stage(emmcio_emmc_obj_t *self) { + switch (self->hs_stage) { + case 0: + return "DEVICE_TYPE (card does not advertise 52 MHz)"; + case 1: + return "cmd6 (no response)"; + case 2: + return "cmd6 busy (card never released DAT0)"; + case 3: + return self->hs_switch_error + ? "cmd13 SWITCH_ERROR (card rejected HS_TIMING)" + : "cmd13 (card never came back to tran)"; + case 4: + return "readback (EXT_CSD[185] did not take)"; + default: + return "32 MHz smoke test (fell back to 16 MHz)"; + } +} + +static const char *emmc_power_up(emmcio_emmc_obj_t *self, bool high_speed, bool *hs_failed) { + *hs_failed = false; + s_constructed = true; + + if (!emmc_init(self)) { + const char *stage = init_failure_stage(self); + emmcio_emmc_release_hardware(); + return stage; + } + + uint8_t ext_csd[EMMC_BLOCK_SIZE]; + if (!common_hal_emmcio_emmc_read_ext_csd(ext_csd)) { + emmcio_emmc_release_hardware(); + return "ext_csd"; + } + if (high_speed && !emmc_set_high_speed(self)) { + const char *stage = hs_failure_stage(self); + emmcio_emmc_release_hardware(); + *hs_failed = true; + return stage; + } + return NULL; +} + +mp_rom_error_text_t common_hal_emmcio_emmc_construct(emmcio_emmc_obj_t *self, + const mcu_pin_obj_t *clock, const mcu_pin_obj_t *command, const mcu_pin_obj_t *data, + const mcu_pin_obj_t *reset, const mcu_pin_obj_t *vccq, + bool high_speed, bool write_enabled, const char **stage_out) { + *stage_out = NULL; + if (emmcio_is_automounted()) { + return MP_ERROR_TEXT("eMMC owned by the USB drive; set CIRCUITPY_EMMC_USB = false in settings.toml"); + } + if (s_constructed) { + return MP_ERROR_TEXT("Peripheral in use"); + } + mp_rom_error_text_t pin_err = emmc_check_pins(clock, command, data, reset, vccq); + if (pin_err != NULL) { + return pin_err; + } + emmc_claim_pins(clock, command, data, reset, vccq, false); + + bool hs_failed = false; + const char *stage = emmc_power_up(self, high_speed, &hs_failed); + if (stage != NULL) { + *stage_out = stage; + return hs_failed ? MP_ERROR_TEXT("eMMC high-speed switch failed at %s") + : MP_ERROR_TEXT("eMMC init failed at %s"); + } + + self->deinited = false; + self->write_enabled = write_enabled; + return NULL; +} + +void common_hal_emmcio_emmc_deinit(emmcio_emmc_obj_t *self) { + if (self->deinited) { + return; + } + emmcio_emmc_release_hardware(); + self->deinited = true; +} + +bool common_hal_emmcio_emmc_deinited(emmcio_emmc_obj_t *self) { + return self->deinited; +} + +#if CIRCUITPY_EMMC_USB + +static emmcio_emmc_obj_t s_automount_obj; +static bool s_automounted; + +mp_obj_t emmcio_automount_construct(const mcu_pin_obj_t *clock, const mcu_pin_obj_t *command, + const mcu_pin_obj_t *data, const mcu_pin_obj_t *reset, const mcu_pin_obj_t *vccq, + bool high_speed, bool write_enabled) { + if (s_constructed) { + return MP_OBJ_NULL; + } + if (emmc_check_pins(clock, command, data, reset, vccq) != NULL) { + return MP_OBJ_NULL; + } + emmc_claim_pins(clock, command, data, reset, vccq, true); + s_automount_obj.base.type = &emmcio_emmc_type; + bool hs_failed = false; + if (emmc_power_up(&s_automount_obj, high_speed, &hs_failed) != NULL) { + return MP_OBJ_NULL; + } + s_automount_obj.deinited = false; + s_automount_obj.write_enabled = write_enabled; + s_automounted = true; + return MP_OBJ_FROM_PTR(&s_automount_obj); +} + +bool emmcio_is_automounted(void) { + return s_automounted; +} + +void emmcio_automount_abandon(void) { + s_automounted = false; + s_automount_obj.deinited = true; + if (s_constructed) { + emmcio_emmc_release_hardware(); + } +} +#endif diff --git a/ports/nordic/common-hal/emmcio/EMMC.h b/ports/nordic/common-hal/emmcio/EMMC.h new file mode 100644 index 00000000000..65055268da4 --- /dev/null +++ b/ports/nordic/common-hal/emmcio/EMMC.h @@ -0,0 +1,99 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Tim Cocks for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +// The nRF52840 eMMC back end: the 1-bit MMC protocol (CLK/CMD/DAT0 plus RST_n +// and a VCCQ rail gate) bit-banged on GPIO, with the 512-byte data payloads +// carried by SPIM3 + EasyDMA. +// +// Only one EMMC object can exist at a time, it owns SPIM3. + +#pragma once + +#include +#include + +#include "py/obj.h" + +#include "common-hal/microcontroller/Pin.h" +#include "shared-bindings/emmcio/EMMC.h" + +typedef struct { + mp_obj_base_t base; + bool deinited; + bool write_enabled; + + + // How far bring-up got, so a failure names the step it stopped at + bool cmd0_sent; + int32_t cmd1_retries; // retries until ready; -1 = never ready + bool cmd2_resp; + bool cmd3_resp; + bool cmd7_resp; + bool cmd16_resp; + uint8_t cid[16]; // CMD2 R2 payload (CID[127:0]) + + // These stay at their zero values unless high_speed was asked for. + bool hs_switch_error; // CMD13 reported SWITCH_ERROR after the CMD6 + bool hs_active; // EXT_CSD[185] verified AND the host clock is at M32 + // How far the switch got, so the failure message names a step + // 0 not attempted, 1 DEVICE_TYPE ok, 2 CMD6 answered, 3 DAT0 released, + // 4 back in tran, 5 EXT_CSD[185] verified, 6 running at M32. + uint8_t hs_stage; +} emmcio_emmc_obj_t; + +mp_rom_error_text_t common_hal_emmcio_emmc_construct(emmcio_emmc_obj_t *self, + const mcu_pin_obj_t *clock, const mcu_pin_obj_t *command, const mcu_pin_obj_t *data, + const mcu_pin_obj_t *reset, const mcu_pin_obj_t *vccq, + bool high_speed, bool write_enabled, const char **stage_out); +void common_hal_emmcio_emmc_deinit(emmcio_emmc_obj_t *self); +bool common_hal_emmcio_emmc_deinited(emmcio_emmc_obj_t *self); + +bool common_hal_emmcio_emmc_readblocks(uint32_t block_addr, uint8_t *buf, uint32_t count); + +// CMD24 (count == 1) / CMD25 + CMD12 (count > 1), each block followed by the +// card's CRC-status token and its programming busy. Direct writes only: the +// card's volatile cache is never enabled, so when this returns true the data +// is in NAND and there is nothing to flush. +bool common_hal_emmcio_emmc_writeblocks(uint32_t block_addr, const uint8_t *buf, uint32_t count); + +// Block-device ioctl, taking extmod/vfs.h's MP_BLOCKDEV_IOCTL_* ops. False +// means the op is not implemented. +bool common_hal_emmcio_emmc_ioctl(uint32_t op, uint32_t arg, uint32_t *out_value); + +// CMD8 -> the 512-byte extended CSD. buf must be >= EMMC_BLOCK_SIZE. +bool common_hal_emmcio_emmc_read_ext_csd(uint8_t *buf); + +// CMD13 SEND_STATUS -- the 6-byte R1 response. +bool common_hal_emmcio_emmc_read_status(uint8_t *r1_out); + +uint32_t common_hal_emmcio_emmc_get_block_count(emmcio_emmc_obj_t *self); // 0 until EXT_CSD has been read +uint32_t common_hal_emmcio_emmc_get_frequency(emmcio_emmc_obj_t *self); // the SPIM data-phase clock, Hz +bool common_hal_emmcio_emmc_get_high_speed(emmcio_emmc_obj_t *self); +const uint8_t *common_hal_emmcio_emmc_get_cid(emmcio_emmc_obj_t *self); // 16 bytes, CID[127:0] + +// A wall-clock budget spanning a whole sequence of driver calls, so a card +// that never answers cannot stall a caller. +void common_hal_emmcio_emmc_set_deadline(uint32_t timeout_us); +void common_hal_emmcio_emmc_clear_deadline(void); + + + +// True while a live EMMC object owns SPIM3 +bool emmcio_spim3_in_use(void); + +// Power the card down and give up its pins +void emmcio_emmc_release_hardware(void); + +#if CIRCUITPY_EMMC_USB +// Bring up the supervisor's statically allocated EMMC object. +// Returns MP_OBJ_NULL if the card cannot be brought up. +mp_obj_t emmcio_automount_construct(const mcu_pin_obj_t *clock, const mcu_pin_obj_t *command, + const mcu_pin_obj_t *data, const mcu_pin_obj_t *reset, const mcu_pin_obj_t *vccq, + bool high_speed, bool write_enabled); + +// The automount's undo, safe to call from any of its failure paths. +void emmcio_automount_abandon(void); +#endif diff --git a/ports/nordic/common-hal/emmcio/__init__.c b/ports/nordic/common-hal/emmcio/__init__.c new file mode 100644 index 00000000000..1f2cc314796 --- /dev/null +++ b/ports/nordic/common-hal/emmcio/__init__.c @@ -0,0 +1,19 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Tim Cocks for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#include "common-hal/emmcio/EMMC.h" +#include "shared-bindings/emmcio/__init__.h" +#include "shared-module/emmcio/__init__.h" + +void emmcio_reset(void) { + // The supervisor's mount outlives the VM, so its card stays up. + if (emmcio_is_automounted()) { + return; + } + if (emmcio_spim3_in_use()) { + emmcio_emmc_release_hardware(); + } +} diff --git a/ports/nordic/common-hal/emmcio/emmc_hw.h b/ports/nordic/common-hal/emmcio/emmc_hw.h new file mode 100644 index 00000000000..2e465b3df75 --- /dev/null +++ b/ports/nordic/common-hal/emmcio/emmc_hw.h @@ -0,0 +1,219 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Tim Cocks for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +// The hardware the eMMC driver drives: the five pins, the RTC2 tick source +// and the SPIM3 data engine. + + +#pragma once + +#include +#include + +#include "nrf.h" +#include "nrf_gpio.h" + +// CLK and DAT0 must be on port 0: the data path drives them through NRF_P0 +// directly. RST and VCCQ are optional +#define EMMC_NO_PIN 0xFFu + +typedef struct { + uint8_t clk; + uint8_t cmd; + uint8_t dat0; + uint8_t rst; // active low; may be EMMC_NO_PIN + uint8_t vccq; // I/O rail gate; may be EMMC_NO_PIN + uint32_t clk_bit; // port-0 masks, for the data path's direct register use + uint32_t dat_bit; + volatile uint32_t *dat0_cnf; + uint32_t dat0_cnf_in; // input, buffer connected, pull-up + uint32_t dat0_cnf_out; // output, input disconnected, no pull, H0H1 +} emmc_pinout_t; + +#define EMMC_CNF_IN ((GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos) | \ + (GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos) | \ + (GPIO_PIN_CNF_PULL_Pullup << GPIO_PIN_CNF_PULL_Pos) | \ + (GPIO_PIN_CNF_DRIVE_S0S1 << GPIO_PIN_CNF_DRIVE_Pos) | \ + (GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos)) + +#define EMMC_CNF_OUT_H0H1 ((GPIO_PIN_CNF_DIR_Output << GPIO_PIN_CNF_DIR_Pos) | \ + (GPIO_PIN_CNF_INPUT_Disconnect << GPIO_PIN_CNF_INPUT_Pos) | \ + (GPIO_PIN_CNF_PULL_Disabled << GPIO_PIN_CNF_PULL_Pos) | \ + (GPIO_PIN_CNF_DRIVE_H0H1 << GPIO_PIN_CNF_DRIVE_Pos) | \ + (GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos)) + +extern emmc_pinout_t emmc_pinout; + +static inline void emmc_opt_pin_write(uint8_t pin, bool high) { + if (pin == EMMC_NO_PIN) { + return; + } + if (high) { + nrf_gpio_pin_set(pin); + } else { + nrf_gpio_pin_clear(pin); + } +} + +static inline void emmc_opt_pin_output(uint8_t pin) { + if (pin != EMMC_NO_PIN) { + nrf_gpio_cfg_output(pin); + } +} + +// SPIM3 clock codes. +#define SPIM_FREQ_M16 0x0A000000u +#define SPIM_FREQ_M32 0x14000000u + +// SPIM3 CONFIG codes +#define SPIM_CONFIG_MODE0 0u // MSB first, CPOL0/CPHA0 +#define SPIM_CONFIG_MODE1 (1u << 1) // MSB first, CPOL0/CPHA1 + +// ---- pin control --------------------------------------------------------- +// The command/init path uses the HAL macros; the data path uses the direct +// port-0 register accesses below (~3 cycles vs ~130 for the HAL, which is the +// difference between a usable bit-bang clock and a useless one). +#define CLK_HIGH() nrf_gpio_pin_set(emmc_pinout.clk) +#define CLK_LOW() nrf_gpio_pin_clear(emmc_pinout.clk) +#define CMD_HIGH() nrf_gpio_pin_set(emmc_pinout.cmd) +#define CMD_LOW() nrf_gpio_pin_clear(emmc_pinout.cmd) +#define DAT0_HIGH() nrf_gpio_pin_set(emmc_pinout.dat0) +#define DAT0_LOW() nrf_gpio_pin_clear(emmc_pinout.dat0) +#define DAT0_IN() (*emmc_pinout.dat0_cnf = emmc_pinout.dat0_cnf_in) +// DAT0 as a HIGH-DRIVE output (H0H1) so edges are fast and clean. +#define DAT0_OUT() (*emmc_pinout.dat0_cnf = emmc_pinout.dat0_cnf_out) +#define CMD_IN() nrf_gpio_cfg_input(emmc_pinout.cmd, NRF_GPIO_PIN_PULLUP) +#define CMD_OUT() nrf_gpio_cfg_output(emmc_pinout.cmd) +#define READ_CMD() nrf_gpio_pin_read(emmc_pinout.cmd) +#define READ_DAT0() nrf_gpio_pin_read(emmc_pinout.dat0) + +#define RCLK_HIGH(bit) (NRF_P0->OUTSET = (bit)) +#define RCLK_LOW(bit) (NRF_P0->OUTCLR = (bit)) +#define RDAT_HIGH(bit) (NRF_P0->OUTSET = (bit)) +#define RDAT_GET(bit) ((NRF_P0->IN & (bit)) != 0u) +// A few NOPs of settle after a clock edge for the delay-free (hd==0) path: +// covers the card's data-output valid time without throttling to a busy-wait. +#define EDGE_SETTLE() __asm__ volatile ("nop\nnop\nnop") + +#define RST_ASSERT() emmc_opt_pin_write(emmc_pinout.rst, false) +#define RST_RELEASE() emmc_opt_pin_write(emmc_pinout.rst, true) +#define VCCQ_ON() emmc_opt_pin_write(emmc_pinout.vccq, true) +#define VCCQ_OFF() emmc_opt_pin_write(emmc_pinout.vccq, false) + +static inline void emmc_pins_init(void) { + nrf_gpio_cfg(emmc_pinout.clk, NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_DISCONNECT, + NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE); // high-drive CLK + nrf_gpio_cfg_output(emmc_pinout.cmd); + DAT0_OUT(); // high-drive DAT0 + emmc_opt_pin_output(emmc_pinout.rst); + // VCCQ: standard drive. Do NOT "improve" this to H0H1 without evidence -- + // the rail gate does not need the extra drive and the card came up on it. + emmc_opt_pin_output(emmc_pinout.vccq); +} + +static inline void emmc_pins_release(void) { + nrf_gpio_cfg_default(emmc_pinout.clk); + nrf_gpio_cfg_default(emmc_pinout.cmd); + nrf_gpio_cfg_default(emmc_pinout.dat0); + if (emmc_pinout.rst != EMMC_NO_PIN) { + nrf_gpio_cfg_default(emmc_pinout.rst); + } + // VCCQ stays an output, driven low: the rail must stay off, not float. +} + +// ---- the write path's DMA buffer (anomaly 198) ---------------------------- +// SPIM3 on the nRF52840 corrupts TX bytes when EasyDMA reads them out of the +// upper RAM regions while the CPU is busy elsewhere (errata 198). The port +// already reserves 8 KiB of low RAM for exactly this (mpconfigport.h:36, +// SPIM3_BUFFER_RAM_START_ADDR), and busio's SPI uses it for the same reason -- +// which is safe to share because an emmcio.EMMC object owns SPIM3 outright +// while it lives: busio's allocator asks emmcio_spim3_in_use() and falls back +// to SPIM0/1/2, so the two can never have a transfer in flight at once. +// +// The write path relies on this buffer rather than on retrying a bad CRC +// status; the status token is still enforced as the backstop. +#define EMMC_TX_FRAME ((uint8_t *)SPIM3_BUFFER_RAM_START_ADDR) + +// ---- time ---------------------------------------------------------------- +// Free-running 32768 Hz counter (RTC2, the supervisor's tick source). 24-bit, +// so differences must be masked; it wraps every 512 s. +#define EMMC_TICKS_HZ 32768u +#define EMMC_TICK_MASK 0x00FFFFFFu +#define EMMC_TICKS() (NRF_RTC2->COUNTER) + +static inline uint32_t ticks_since_raw(uint32_t t0) { + return (EMMC_TICKS() - t0) & EMMC_TICK_MASK; +} + +// 20 ms, ~75x the 260 us a full block takes at M16. +#define EMMC_SPIM_TIMEOUT_TICKS ((EMMC_TICKS_HZ * 20u) / 1000u) + +// ---- SPIM3 data engine --------------------------------------------------- +// SPIM3 is the only instance that runs above 8 MHz. M16 = 16 MHz, the fastest +// in-spec step for this card at power-on timing (TRAN_SPEED 0x32 -> 26 MHz cap +// in backwards-compatible mode). The bus is fixed there; the ONE way it moves +// is emmc_set_high_speed(), which first gets the card's own EXT_CSD to read +// back HS_TIMING = 1 (52 MHz limit) and only then steps to M32. +// There is still no free-floating "speed knob": the two codes at the top of +// this file are the only values ever written. +// +// NRF_SPIM3->FREQUENCY and ->CONFIG are read and written directly: the +// peripheral register is the state, and it survives +// ENABLE=0 between transfers. emmc_spim_init() puts both back to M16 / +// mode 0 on every init, so a fresh object always starts at compat speed even +// if the previous one ran high. + +static inline void emmc_spim_init(void) { + NRF_SPIM3->ENABLE = 0; + NRF_SPIM3->PSEL.SCK = emmc_pinout.clk; + NRF_SPIM3->PSEL.MOSI = 0xFFFFFFFFu; // attached per-transfer (write path only) + NRF_SPIM3->PSEL.MISO = 0xFFFFFFFFu; + NRF_SPIM3->PSEL.CSN = 0xFFFFFFFFu; + NRF_SPIM3->FREQUENCY = SPIM_FREQ_M16; + NRF_SPIM3->CONFIG = SPIM_CONFIG_MODE0; // the card comes up in compat timing + NRF_SPIM3->ORC = 0xFF; // idle-high filler +} + +static inline void emmc_spim_deinit(void) { + NRF_SPIM3->ENABLE = 0; + NRF_SPIM3->PSEL.SCK = 0xFFFFFFFFu; + NRF_SPIM3->PSEL.MOSI = 0xFFFFFFFFu; + NRF_SPIM3->PSEL.MISO = 0xFFFFFFFFu; +} + +// One blocking DMA transfer with the wires temporarily owned by SPIM. While +// ENABLED the peripheral drives SCK (+MOSI for TX) / samples MISO; on disable +// the pins fall back to their GPIO latches (CLK low, DAT0 as configured), so +// the surrounding bit-bang phases continue seamlessly. +// +// A read is rx-only (MOSI unselected), so SPIM3 anomaly 198 (TX corruption) +// cannot bite there at all. Writes make TX real, which is why their frame is +// built in EMMC_TX_FRAME above. +static inline void emmc_spim_xfer(const uint8_t *tx, uint32_t txlen, uint8_t *rx, uint32_t rxlen) { + NRF_SPIM3->PSEL.MOSI = tx ? emmc_pinout.dat0 : 0xFFFFFFFFu; + NRF_SPIM3->PSEL.MISO = rx ? emmc_pinout.dat0 : 0xFFFFFFFFu; + NRF_SPIM3->ENABLE = 7; + NRF_SPIM3->TXD.PTR = (uint32_t)tx; + NRF_SPIM3->TXD.MAXCNT = tx ? txlen : 0; + NRF_SPIM3->RXD.PTR = (uint32_t)rx; + NRF_SPIM3->RXD.MAXCNT = rx ? rxlen : 0; + NRF_SPIM3->EVENTS_END = 0; + NRF_SPIM3->TASKS_START = 1; + { + uint32_t t0 = EMMC_TICKS(); + while (!NRF_SPIM3->EVENTS_END) { + if (ticks_since_raw(t0) >= EMMC_SPIM_TIMEOUT_TICKS) { + NRF_SPIM3->EVENTS_STOPPED = 0; + NRF_SPIM3->TASKS_STOP = 1; + // Let EasyDMA stop writing before the buffer is handed back. + for (uint32_t i = 0; i < 10000u && !NRF_SPIM3->EVENTS_STOPPED; i++) { + } + break; + } + } + } + NRF_SPIM3->ENABLE = 0; +} diff --git a/ports/nordic/supervisor/port.c b/ports/nordic/supervisor/port.c index 1eabfcbe216..81a9ad0f82e 100644 --- a/ports/nordic/supervisor/port.c +++ b/ports/nordic/supervisor/port.c @@ -22,6 +22,10 @@ #include "nrf/power.h" #include "nrf/timers.h" +#if CIRCUITPY_EMMCIO +#include "shared-bindings/emmcio/__init__.h" +#endif + #include "nrf_nvic.h" #include "common-hal/microcontroller/Pin.h" @@ -204,6 +208,10 @@ void reset_port(void) { rtc_reset(); #endif + #if CIRCUITPY_EMMCIO + emmcio_reset(); + #endif + timers_reset(); #if CIRCUITPY_WATCHDOG diff --git a/py/circuitpy_defns.mk b/py/circuitpy_defns.mk index b50ab1ee633..d180f769295 100755 --- a/py/circuitpy_defns.mk +++ b/py/circuitpy_defns.mk @@ -219,6 +219,9 @@ endif ifeq ($(CIRCUITPY__EVE),1) SRC_PATTERNS += _eve/% endif +ifeq ($(CIRCUITPY_EMMCIO),1) +SRC_PATTERNS += emmcio/% +endif ifeq ($(CIRCUITPY_EPAPERDISPLAY),1) SRC_PATTERNS += epaperdisplay/% endif @@ -540,6 +543,8 @@ SRC_COMMON_HAL_ALL = \ dotclockframebuffer/DotClockFramebuffer.c \ dotclockframebuffer/__init__.c \ dualbank/__init__.c \ + emmcio/EMMC.c \ + emmcio/__init__.c \ floppyio/__init__.c \ frequencyio/FrequencyIn.c \ frequencyio/__init__.c \ @@ -761,6 +766,7 @@ SRC_SHARED_MODULE_ALL = \ displayio/area.c \ displayio/__init__.c \ dotclockframebuffer/__init__.c \ + emmcio/__init__.c \ epaperdisplay/__init__.c \ epaperdisplay/EPaperDisplay.c \ i2cioexpander/IOExpander.c \ diff --git a/py/circuitpy_mpconfig.mk b/py/circuitpy_mpconfig.mk index e4e01fcecc8..c03f99b9d8a 100755 --- a/py/circuitpy_mpconfig.mk +++ b/py/circuitpy_mpconfig.mk @@ -309,6 +309,14 @@ CFLAGS += -DCIRCUITPY_VECTORIO=$(CIRCUITPY_VECTORIO) CIRCUITPY_DUALBANK ?= 0 CFLAGS += -DCIRCUITPY_DUALBANK=$(CIRCUITPY_DUALBANK) +# eMMC block device. Only nordic implements it. +CIRCUITPY_EMMCIO ?= 0 +CFLAGS += -DCIRCUITPY_EMMCIO=$(CIRCUITPY_EMMCIO) + +# Mount the eMMC at boot and expose it over USB MSC. +CIRCUITPY_EMMC_USB ?= $(CIRCUITPY_EMMCIO) +CFLAGS += -DCIRCUITPY_EMMC_USB=$(CIRCUITPY_EMMC_USB) + # Enabled micropython.native decorator (experimental) CIRCUITPY_ENABLE_MPY_NATIVE ?= 0 CFLAGS += -DCIRCUITPY_ENABLE_MPY_NATIVE=$(CIRCUITPY_ENABLE_MPY_NATIVE) diff --git a/shared-bindings/emmcio/EMMC.c b/shared-bindings/emmcio/EMMC.c new file mode 100644 index 00000000000..d095e6c7cc4 --- /dev/null +++ b/shared-bindings/emmcio/EMMC.c @@ -0,0 +1,461 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Tim Cocks for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#include "py/obj.h" +#include "py/objproperty.h" +#include "py/runtime.h" +#include "py/mperrno.h" +#include "py/mphal.h" + +#include "extmod/vfs.h" + +#include "shared-bindings/emmcio/EMMC.h" +#include "shared-bindings/microcontroller/Pin.h" +#include "shared-bindings/util.h" +#include "shared/runtime/context_manager_helpers.h" +#include "common-hal/emmcio/EMMC.h" + +static void check_for_deinit(emmcio_emmc_obj_t *self) { + if (common_hal_emmcio_emmc_deinited(self)) { + raise_deinited_error(); + } +} + +// | class EMMC: +// | """The on-board eMMC as a block device""" +// | +// | def __init__( +// | self, +// | *, +// | clock: microcontroller.Pin, +// | command: microcontroller.Pin, +// | data: microcontroller.Pin, +// | reset: Optional[microcontroller.Pin] = None, +// | vccq: Optional[microcontroller.Pin] = None, +// | high_speed: bool = False, +// | write_enabled: bool = False, +// | ) -> None: +// | """Power up the card and make it ready for block access. +// | +// | Only one `EMMC` object may exist at a time. Call `deinit()`, or use +// | the object as a context manager, to release the card and its pins. +// | +// | :param ~microcontroller.Pin clock: the card's CLK pin +// | :param ~microcontroller.Pin command: the card's CMD pin +// | :param ~microcontroller.Pin data: the card's DAT0 pin. The bus is +// | 1-bit, so this is a single pin. +// | :param ~microcontroller.Pin reset: the card's RST_n pin, if the board +// | wires one +// | :param ~microcontroller.Pin vccq: a pin gating the card's I/O rail, +// | if the board has one +// | :param bool high_speed: Run the bus at its faster clock rate. Raises +// | an `OSError` if the card will not make the switch. +// | :param bool write_enabled: Allow `writeblocks()`. When `False`, the +// | object is read-only and every write path refuses. +// | +// | :raises ValueError: if the pins are unusable or already in use, or +// | if the card is owned by the USB drive. +// | :raises OSError: if the card does not come up. +// | +// | Mount the card's filesystem:: +// | +// | import board +// | import emmcio +// | import storage +// | +// | emmc = emmcio.EMMC( +// | clock=board.EMMC_CLK, +// | command=board.EMMC_CMD, +// | data=board.EMMC_DAT0, +// | reset=board.EMMC_RESET, +// | vccq=board.EMMC_VCCQ, +// | high_speed=True, +// | write_enabled=True, +// | ) +// | storage.mount(storage.VfsFat(emmc), "/sd") +// | """ +// | ... +// | +static mp_obj_t emmcio_emmc_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_clock, ARG_command, ARG_data, ARG_reset, ARG_vccq, ARG_high_speed, ARG_write_enabled }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_clock, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_OBJ }, + { MP_QSTR_command, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_OBJ }, + { MP_QSTR_data, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_OBJ }, + { MP_QSTR_reset, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_vccq, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_high_speed, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, + { MP_QSTR_write_enabled, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + const mcu_pin_obj_t *clock = validate_obj_is_pin(args[ARG_clock].u_obj, MP_QSTR_clock); + const mcu_pin_obj_t *command = validate_obj_is_pin(args[ARG_command].u_obj, MP_QSTR_command); + const mcu_pin_obj_t *data = validate_obj_is_pin(args[ARG_data].u_obj, MP_QSTR_data); + const mcu_pin_obj_t *reset = validate_obj_is_pin_or_none(args[ARG_reset].u_obj, MP_QSTR_reset); + const mcu_pin_obj_t *vccq = validate_obj_is_pin_or_none(args[ARG_vccq].u_obj, MP_QSTR_vccq); + + // Every line is a separate net, so two of them being the same pin is a + // wiring mistake, not a configuration. + const mcu_pin_obj_t *pins[] = { clock, command, data, reset, vccq }; + const qstr names[] = { MP_QSTR_clock, MP_QSTR_command, MP_QSTR_data, MP_QSTR_reset, MP_QSTR_vccq }; + for (size_t i = 0; i < MP_ARRAY_SIZE(pins); i++) { + for (size_t j = i + 1; j < MP_ARRAY_SIZE(pins); j++) { + if (pins[i] != NULL && pins[i] == pins[j]) { + mp_raise_ValueError_varg(MP_ERROR_TEXT("%q and %q must be different"), + names[i], names[j]); + } + } + } + + emmcio_emmc_obj_t *self = mp_obj_malloc(emmcio_emmc_obj_t, &emmcio_emmc_type); + // A stage means the card itself did not come up; anything else means the + // wiring or the hardware is unusable. + const char *stage = NULL; + mp_rom_error_text_t err = common_hal_emmcio_emmc_construct(self, + clock, command, data, reset, vccq, + args[ARG_high_speed].u_bool, args[ARG_write_enabled].u_bool, &stage); + if (err != NULL) { + if (stage != NULL) { + mp_raise_msg_varg(&mp_type_OSError, err, stage); + } + mp_raise_ValueError(err); + } + return MP_OBJ_FROM_PTR(self); +} + +// | def deinit(self) -> None: +// | """Release the card and the pins it uses. Any further use of this +// | object raises a `ValueError`.""" +// | ... +// | +static mp_obj_t emmcio_emmc_deinit(mp_obj_t self_in) { + common_hal_emmcio_emmc_deinit(MP_OBJ_TO_PTR(self_in)); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(emmcio_emmc_deinit_obj, emmcio_emmc_deinit); + +// | def __enter__(self) -> EMMC: +// | """No-op used by Context Managers.""" +// | ... +// | +// | def __exit__(self) -> None: +// | """Automatically deinitializes the hardware when exiting a context. See +// | :ref:`lifetime-and-contextmanagers` for more info.""" +// | ... +// | +static mp_obj_t emmcio_emmc_obj___exit__(size_t n_args, const mp_obj_t *args) { + return emmcio_emmc_deinit(args[0]); +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(emmcio_emmc___exit___obj, 4, 4, emmcio_emmc_obj___exit__); + +#define CHUNK_BLOCKS 64u + +static int emmc_read_chunked(emmcio_emmc_obj_t *self, uint8_t *out, mp_uint_t start, mp_uint_t count, bool from_vm) { + mp_uint_t total = common_hal_emmcio_emmc_get_block_count(self); + if (count == 0 || start >= total || count > total - start) { + return -MP_EINVAL; + } + for (mp_uint_t done = 0; done < count;) { + mp_uint_t run = MIN(CHUNK_BLOCKS, count - done); + bool ok = false; + for (int attempt = 0; attempt < 3 && !ok; attempt++) { + ok = common_hal_emmcio_emmc_readblocks(start + done, out + done * EMMC_BLOCK_SIZE, run); + } + if (!ok) { + return -MP_EIO; + } + done += run; + RUN_BACKGROUND_TASKS; + if (from_vm) { + mp_handle_pending(true); + } + } + return 0; +} + +// | def readblocks(self, start_block: int, buf: WriteableBuffer) -> None: +// | """Read into ``buf`` starting at ``start_block``. +// | +// | :param int start_block: the first block to read +// | :param WriteableBuffer buf: a buffer whose length is a non-zero +// | multiple of `block_size` +// | +// | :raises ValueError: if ``buf`` is the wrong length, or the requested +// | blocks run past the end of the card. +// | :raises OSError: if the card fails to deliver the data.""" +// | ... +// | +static mp_obj_t emmcio_emmc_readblocks(mp_obj_t self_in, mp_obj_t start_in, mp_obj_t buf_in) { + emmcio_emmc_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_for_deinit(self); + + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(buf_in, &bufinfo, MP_BUFFER_WRITE); + if (bufinfo.len == 0 || (bufinfo.len % EMMC_BLOCK_SIZE) != 0) { + mp_raise_ValueError_varg(MP_ERROR_TEXT("Buffer must be a multiple of %d bytes"), 512); + } + mp_uint_t start = mp_obj_get_int_truncated(start_in); + mp_uint_t count = bufinfo.len / EMMC_BLOCK_SIZE; + mp_uint_t total = common_hal_emmcio_emmc_get_block_count(self); + if (start >= total || count > total - start) { + mp_raise_ValueError(MP_ERROR_TEXT("address out of range")); + } + + int err = emmc_read_chunked(self, bufinfo.buf, start, count, true); + if (err != 0) { + mp_raise_OSError(-err); + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_3(emmcio_emmc_readblocks_obj, emmcio_emmc_readblocks); + +static int emmc_write_chunked(emmcio_emmc_obj_t *self, const uint8_t *src, mp_uint_t start, mp_uint_t count, bool from_vm) { + mp_uint_t total = common_hal_emmcio_emmc_get_block_count(self); + if (count == 0 || start >= total || count > total - start) { + return -MP_EINVAL; + } + for (mp_uint_t done = 0; done < count;) { + mp_uint_t run = MIN(CHUNK_BLOCKS, count - done); + bool ok = false; + for (int attempt = 0; attempt < 3 && !ok; attempt++) { + ok = common_hal_emmcio_emmc_writeblocks(start + done, src + done * EMMC_BLOCK_SIZE, run); + } + if (!ok) { + return -MP_EIO; + } + done += run; + RUN_BACKGROUND_TASKS; + if (from_vm) { + mp_handle_pending(true); + } + } + return 0; +} + +// | def writeblocks(self, start_block: int, buf: ReadableBuffer) -> None: +// | """Write ``buf`` to the card starting at ``start_block``. +// | +// | :param int start_block: the first block to write +// | :param ReadableBuffer buf: a buffer whose length is a non-zero +// | multiple of `block_size` +// | +// | :raises RuntimeError: if this object was not constructed with +// | ``write_enabled=True``. +// | :raises ValueError: if ``buf`` is the wrong length, or the requested +// | blocks run past the end of the card. +// | :raises OSError: if the write fails.""" +// | ... +// | +static mp_obj_t emmcio_emmc_writeblocks(mp_obj_t self_in, mp_obj_t start_in, mp_obj_t buf_in) { + emmcio_emmc_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_for_deinit(self); + if (!self->write_enabled) { + mp_raise_msg(&mp_type_RuntimeError, + MP_ERROR_TEXT("Read-only")); + } + + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(buf_in, &bufinfo, MP_BUFFER_READ); + if (bufinfo.len == 0 || (bufinfo.len % EMMC_BLOCK_SIZE) != 0) { + mp_raise_ValueError_varg(MP_ERROR_TEXT("Buffer must be a multiple of %d bytes"), 512); + } + mp_uint_t start = mp_obj_get_int_truncated(start_in); + mp_uint_t count = bufinfo.len / EMMC_BLOCK_SIZE; + mp_uint_t total = common_hal_emmcio_emmc_get_block_count(self); + if (start >= total || count > total - start) { + mp_raise_ValueError(MP_ERROR_TEXT("address out of range")); + } + + int err = emmc_write_chunked(self, bufinfo.buf, start, count, true); + if (err != 0) { + mp_raise_OSError(-err); + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_3(emmcio_emmc_writeblocks_obj, emmcio_emmc_writeblocks); + +// | def ioctl(self, op: int, arg: int) -> Optional[int]: +// | """Perform a block-device control operation, as required by the +// | block-device protocol. Returns `None` for operations this device does +// | not implement.""" +// | ... +// | +static mp_obj_t emmcio_emmc_ioctl(mp_obj_t self_in, mp_obj_t op_in, mp_obj_t arg_in) { + emmcio_emmc_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_for_deinit(self); + uint32_t out = 0; + if (!common_hal_emmcio_emmc_ioctl(mp_obj_get_int_truncated(op_in), + mp_obj_get_int_truncated(arg_in), &out)) { + return mp_const_none; + } + return mp_obj_new_int_from_uint(out); +} +static MP_DEFINE_CONST_FUN_OBJ_3(emmcio_emmc_ioctl_obj, emmcio_emmc_ioctl); + +mp_uint_t emmcio_emmc_readblocks_native(mp_obj_t self_in, uint8_t *buf, + uint32_t start_block, uint32_t nblocks) { + emmcio_emmc_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (common_hal_emmcio_emmc_deinited(self)) { + return -MP_ENODEV; + } + return emmc_read_chunked(self, buf, start_block, nblocks, false); +} + +mp_uint_t emmcio_emmc_writeblocks_native(mp_obj_t self_in, const uint8_t *buf, + uint32_t start_block, uint32_t nblocks) { + emmcio_emmc_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (common_hal_emmcio_emmc_deinited(self)) { + return -MP_ENODEV; + } + + if (!self->write_enabled) { + return -MP_EROFS; + } + return emmc_write_chunked(self, buf, start_block, nblocks, false); +} + +bool emmcio_emmc_ioctl_native(mp_obj_t self_in, uint32_t cmd, uint32_t arg, + size_t *out_value) { + + (void)self_in; + uint32_t out = 0; + bool ok = common_hal_emmcio_emmc_ioctl(cmd, arg, &out); + *out_value = out; + return ok; +} + +bool emmcio_emmc_is_write_enabled(mp_obj_t self_in) { + emmcio_emmc_obj_t *self = MP_OBJ_TO_PTR(self_in); + return !common_hal_emmcio_emmc_deinited(self) && self->write_enabled; +} + +// | def read_ext_csd(self) -> bytes: +// | """Read the card's 512-byte extended CSD register.""" +// | ... +// | +static mp_obj_t emmcio_emmc_read_ext_csd(mp_obj_t self_in) { + emmcio_emmc_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_for_deinit(self); + + uint8_t ext_csd[EMMC_BLOCK_SIZE]; + if (!common_hal_emmcio_emmc_read_ext_csd(ext_csd)) { + mp_raise_OSError(MP_EIO); + } + return mp_obj_new_bytes(ext_csd, sizeof(ext_csd)); +} +static MP_DEFINE_CONST_FUN_OBJ_1(emmcio_emmc_read_ext_csd_obj, emmcio_emmc_read_ext_csd); + +// | def status(self) -> int: +// | """Read the card's 32-bit status register.""" +// | ... +// | +static mp_obj_t emmcio_emmc_status(mp_obj_t self_in) { + emmcio_emmc_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_for_deinit(self); + + uint8_t r1[6]; + if (!common_hal_emmcio_emmc_read_status(r1)) { + mp_raise_OSError(MP_EIO); + } + uint32_t status = ((uint32_t)r1[1] << 24) | ((uint32_t)r1[2] << 16) | + ((uint32_t)r1[3] << 8) | (uint32_t)r1[4]; + return mp_obj_new_int_from_uint(status); +} +static MP_DEFINE_CONST_FUN_OBJ_1(emmcio_emmc_status_obj, emmcio_emmc_status); + +// | count: int +// | """The number of blocks on the card.""" +// | +static mp_obj_t emmcio_emmc_get_count(mp_obj_t self_in) { + emmcio_emmc_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_for_deinit(self); + return mp_obj_new_int_from_uint(common_hal_emmcio_emmc_get_block_count(self)); +} +static MP_DEFINE_CONST_FUN_OBJ_1(emmcio_emmc_get_count_obj, emmcio_emmc_get_count); +MP_PROPERTY_GETTER(emmcio_emmc_count_obj, (mp_obj_t)&emmcio_emmc_get_count_obj); + +// | block_size: int +// | """The size of one block, in bytes.""" +// | +static mp_obj_t emmcio_emmc_get_block_size(mp_obj_t self_in) { + return MP_OBJ_NEW_SMALL_INT(EMMC_BLOCK_SIZE); +} +static MP_DEFINE_CONST_FUN_OBJ_1(emmcio_emmc_get_block_size_obj, emmcio_emmc_get_block_size); +MP_PROPERTY_GETTER(emmcio_emmc_block_size_obj, (mp_obj_t)&emmcio_emmc_get_block_size_obj); + +// | cid: bytes +// | """The card's 16-byte identification register.""" +// | +static mp_obj_t emmcio_emmc_get_cid(mp_obj_t self_in) { + emmcio_emmc_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_for_deinit(self); + return mp_obj_new_bytes(common_hal_emmcio_emmc_get_cid(self), 16); +} +static MP_DEFINE_CONST_FUN_OBJ_1(emmcio_emmc_get_cid_obj, emmcio_emmc_get_cid); +MP_PROPERTY_GETTER(emmcio_emmc_cid_obj, (mp_obj_t)&emmcio_emmc_get_cid_obj); + +// | write_enabled: bool +// | """Whether `writeblocks()` is permitted on this object.""" +// | +static mp_obj_t emmcio_emmc_get_write_enabled(mp_obj_t self_in) { + emmcio_emmc_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_for_deinit(self); + return mp_obj_new_bool(self->write_enabled); +} +static MP_DEFINE_CONST_FUN_OBJ_1(emmcio_emmc_get_write_enabled_obj, emmcio_emmc_get_write_enabled); +MP_PROPERTY_GETTER(emmcio_emmc_write_enabled_obj, (mp_obj_t)&emmcio_emmc_get_write_enabled_obj); + +// | high_speed: bool +// | """Whether the card is running at its faster clock rate.""" +// | +static mp_obj_t emmcio_emmc_get_high_speed(mp_obj_t self_in) { + emmcio_emmc_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_for_deinit(self); + return mp_obj_new_bool(common_hal_emmcio_emmc_get_high_speed(self)); +} +static MP_DEFINE_CONST_FUN_OBJ_1(emmcio_emmc_get_high_speed_obj, emmcio_emmc_get_high_speed); +MP_PROPERTY_GETTER(emmcio_emmc_high_speed_obj, (mp_obj_t)&emmcio_emmc_get_high_speed_obj); + +// | frequency: int +// | """The bus clock rate in Hz.""" +// | +static mp_obj_t emmcio_emmc_get_frequency(mp_obj_t self_in) { + emmcio_emmc_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_for_deinit(self); + return mp_obj_new_int_from_uint(common_hal_emmcio_emmc_get_frequency(self)); +} +static MP_DEFINE_CONST_FUN_OBJ_1(emmcio_emmc_get_frequency_obj, emmcio_emmc_get_frequency); +MP_PROPERTY_GETTER(emmcio_emmc_frequency_obj, (mp_obj_t)&emmcio_emmc_get_frequency_obj); + +static const mp_rom_map_elem_t emmcio_emmc_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&emmcio_emmc_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, + { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&emmcio_emmc___exit___obj) }, + + { MP_ROM_QSTR(MP_QSTR_readblocks), MP_ROM_PTR(&emmcio_emmc_readblocks_obj) }, + { MP_ROM_QSTR(MP_QSTR_writeblocks), MP_ROM_PTR(&emmcio_emmc_writeblocks_obj) }, + { MP_ROM_QSTR(MP_QSTR_ioctl), MP_ROM_PTR(&emmcio_emmc_ioctl_obj) }, + { MP_ROM_QSTR(MP_QSTR_read_ext_csd), MP_ROM_PTR(&emmcio_emmc_read_ext_csd_obj) }, + { MP_ROM_QSTR(MP_QSTR_status), MP_ROM_PTR(&emmcio_emmc_status_obj) }, + + { MP_ROM_QSTR(MP_QSTR_count), MP_ROM_PTR(&emmcio_emmc_count_obj) }, + { MP_ROM_QSTR(MP_QSTR_high_speed), MP_ROM_PTR(&emmcio_emmc_high_speed_obj) }, + { MP_ROM_QSTR(MP_QSTR_frequency), MP_ROM_PTR(&emmcio_emmc_frequency_obj) }, + { MP_ROM_QSTR(MP_QSTR_block_size), MP_ROM_PTR(&emmcio_emmc_block_size_obj) }, + { MP_ROM_QSTR(MP_QSTR_cid), MP_ROM_PTR(&emmcio_emmc_cid_obj) }, + { MP_ROM_QSTR(MP_QSTR_write_enabled), MP_ROM_PTR(&emmcio_emmc_write_enabled_obj) }, +}; +static MP_DEFINE_CONST_DICT(emmcio_emmc_locals_dict, emmcio_emmc_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + emmcio_emmc_type, + MP_QSTR_EMMC, + MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS, + locals_dict, &emmcio_emmc_locals_dict, + make_new, emmcio_emmc_make_new + ); diff --git a/shared-bindings/emmcio/EMMC.h b/shared-bindings/emmcio/EMMC.h new file mode 100644 index 00000000000..af04c1d1fc5 --- /dev/null +++ b/shared-bindings/emmcio/EMMC.h @@ -0,0 +1,34 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Tim Cocks for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "py/obj.h" + +#define EMMC_BLOCK_SIZE 512u + +extern const mp_obj_type_t emmcio_emmc_type; + +// ---- native block-device protocol ----------------------------------------- +// +// The same shape sdcardio and sdioio present, so extmod/vfs_blockdev.c can +// bypass the Python method call. + +// 0 on success, negative errno on failure. Never raises. +mp_uint_t emmcio_emmc_readblocks_native(mp_obj_t self_in, uint8_t *buf, + uint32_t start_block, uint32_t nblocks); + +// 0 on success, -MP_EROFS on an object without write_enabled=True, other +// negative errno on failure. Never raises. +mp_uint_t emmcio_emmc_writeblocks_native(mp_obj_t self_in, const uint8_t *buf, + uint32_t start_block, uint32_t nblocks); + +// false = op not implemented, the caller turns that into None. +bool emmcio_emmc_ioctl_native(mp_obj_t self_in, uint32_t cmd, uint32_t arg, + size_t *out_value); + +// Whether this object may write at all +bool emmcio_emmc_is_write_enabled(mp_obj_t self_in); diff --git a/shared-bindings/emmcio/__init__.c b/shared-bindings/emmcio/__init__.c new file mode 100644 index 00000000000..3bc8ff51be7 --- /dev/null +++ b/shared-bindings/emmcio/__init__.c @@ -0,0 +1,92 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Tim Cocks for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#include + +#include "py/obj.h" +#include "py/runtime.h" + +#include "shared-bindings/emmcio/EMMC.h" +#include "shared-module/emmcio/__init__.h" + +// | """Block device access to the on-board eMMC +// | +// | The `emmcio` module exposes the board's soldered-down eMMC chip as a block +// | device. It provides no filesystem of its own: to read files, hand an `EMMC` +// | object to `storage.VfsFat` and mount it. +// | +// | .. note:: This module is only available on boards with an eMMC wired to the +// | dedicated SPI peripheral, and only one `EMMC` object may exist at a time. +// | """ +// | + +// | def automounted() -> bool: +// | """`True` when the eMMC has been mounted as a filesystem for you at +// | startup, `False` when it is free for Python to open. +// | +// | While this is `True`, constructing `EMMC` raises a `ValueError`.""" +// | ... +// | +static mp_obj_t emmcio_automounted(void) { + return mp_obj_new_bool(emmcio_is_automounted()); +} +static MP_DEFINE_CONST_FUN_OBJ_0(emmcio_automounted_obj, emmcio_automounted); + +// | def automount_status() -> str: +// | """Why the eMMC is or is not mounted at ``/sd``, as one of: +// | +// | * ``"ok"`` -- mounted. +// | * ``"disabled"`` -- ``CIRCUITPY_EMMC_USB = 0`` in ``settings.toml``, or +// | the automount is not in this build. +// | * ``"safe mode"`` -- the board booted into safe mode. +// | * ``"no card"`` -- the card did not come up inside the boot budget. +// | * ``"no filesystem"`` -- the card came up but has no FAT volume. +// | * ``"skipped after fault"`` -- the *previous* boot did not come back out +// | of the automount, so this boot left the card alone to be sure USB came +// | up. The next boot tries again.""" +// | ... +// | +static mp_obj_t emmcio_automount_status(void) { + const char *s = "disabled"; + #if CIRCUITPY_EMMC_USB + switch (emmcio_automount_get_status()) { + case EMMCIO_AUTOMOUNT_OK: + s = "ok"; + break; + case EMMCIO_AUTOMOUNT_SAFE_MODE: + s = "safe mode"; + break; + case EMMCIO_AUTOMOUNT_NO_CARD: + s = "no card"; + break; + case EMMCIO_AUTOMOUNT_NO_FILESYSTEM: + s = "no filesystem"; + break; + case EMMCIO_AUTOMOUNT_SKIPPED_AFTER_FAULT: + s = "skipped after fault"; + break; + default: + break; + } + #endif + return mp_obj_new_str(s, strlen(s)); +} +static MP_DEFINE_CONST_FUN_OBJ_0(emmcio_automount_status_obj, emmcio_automount_status); + +static const mp_rom_map_elem_t emmcio_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_emmcio) }, + { MP_ROM_QSTR(MP_QSTR_EMMC), MP_ROM_PTR(&emmcio_emmc_type) }, + { MP_ROM_QSTR(MP_QSTR_automounted), MP_ROM_PTR(&emmcio_automounted_obj) }, + { MP_ROM_QSTR(MP_QSTR_automount_status), MP_ROM_PTR(&emmcio_automount_status_obj) }, +}; +static MP_DEFINE_CONST_DICT(emmcio_module_globals, emmcio_module_globals_table); + +const mp_obj_module_t emmcio_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&emmcio_module_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_emmcio, emmcio_module); diff --git a/shared-bindings/emmcio/__init__.h b/shared-bindings/emmcio/__init__.h new file mode 100644 index 00000000000..efe68c01b70 --- /dev/null +++ b/shared-bindings/emmcio/__init__.h @@ -0,0 +1,11 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Tim Cocks for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#pragma once + +// Clear module state on every VM reset. The card's rail has already been cut +// by then, so the state must not pretend to survive. +void emmcio_reset(void); diff --git a/shared-module/emmcio/__init__.c b/shared-module/emmcio/__init__.c new file mode 100644 index 00000000000..c2757d81697 --- /dev/null +++ b/shared-module/emmcio/__init__.c @@ -0,0 +1,133 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Tim Cocks for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#include "shared-module/emmcio/__init__.h" + +#if CIRCUITPY_EMMC_USB + +#include "py/mpstate.h" + +#include "extmod/vfs.h" +#include "extmod/vfs_fat.h" +#include "lib/oofatfs/ff.h" + +#include "supervisor/filesystem.h" +#include "supervisor/shared/safe_mode.h" +#include "supervisor/shared/settings.h" + +#include "shared-bindings/microcontroller/Pin.h" + +#include "common-hal/emmcio/EMMC.h" + +#if !defined(DEFAULT_EMMC_CLOCK) || !defined(DEFAULT_EMMC_COMMAND) || !defined(DEFAULT_EMMC_DATA) +#error "CIRCUITPY_EMMC_USB needs DEFAULT_EMMC_CLOCK, DEFAULT_EMMC_COMMAND and DEFAULT_EMMC_DATA in mpconfigboard.h" +#endif + +// RST_n and the I/O rail gate are optional: a board that hard-wires either one +// simply does not define it. +#ifndef DEFAULT_EMMC_RESET +#define DEFAULT_EMMC_RESET NULL +#endif +#ifndef DEFAULT_EMMC_VCCQ +#define DEFAULT_EMMC_VCCQ NULL +#endif + + +static mp_vfs_mount_t _emmc_vfs; +static fs_user_mount_t _emmc_usermount; + +static bool _tried; +static emmcio_automount_status_t _status = EMMCIO_AUTOMOUNT_NOT_TRIED; + +#define AUTOMOUNT_BUDGET_US 5000000u + +// One word of RAM that survives a reset but not a power cycle. If it is still +// set when we get here, the previous boot faulted. Skip the card for this +// boot so the board enumerates, and clear the crumb so the next boot tries again. +#define AUTOMOUNT_CRUMB_MAGIC 0x454d4d43u // 'EMMC' + +static struct { + uint32_t magic; + uint32_t in_progress; +} _crumb __attribute__((section(".uninitialized"))); + +static void automount_give_up(emmcio_automount_status_t status) { + common_hal_emmcio_emmc_clear_deadline(); + // Leave the card powered down and the pins released + emmcio_automount_abandon(); + _crumb.in_progress = 0; + _status = status; +} + +void automount_emmc(void) { + if (_tried) { + return; + } + _tried = true; + + if (get_safe_mode() != SAFE_MODE_NONE) { + _status = EMMCIO_AUTOMOUNT_SAFE_MODE; + return; + } + + bool enabled = true; + (void)settings_get_bool("CIRCUITPY_EMMC_USB", &enabled); + if (!enabled) { + _status = EMMCIO_AUTOMOUNT_DISABLED; + return; + } + + if (_crumb.magic == AUTOMOUNT_CRUMB_MAGIC && _crumb.in_progress != 0) { + _crumb.in_progress = 0; + _status = EMMCIO_AUTOMOUNT_SKIPPED_AFTER_FAULT; + return; + } + _crumb.magic = AUTOMOUNT_CRUMB_MAGIC; + _crumb.in_progress = 1; + + common_hal_emmcio_emmc_set_deadline(AUTOMOUNT_BUDGET_US); + + mp_obj_t dev = emmcio_automount_construct(DEFAULT_EMMC_CLOCK, DEFAULT_EMMC_COMMAND, + DEFAULT_EMMC_DATA, DEFAULT_EMMC_RESET, DEFAULT_EMMC_VCCQ, true, true); + if (dev == MP_OBJ_NULL) { + automount_give_up(EMMCIO_AUTOMOUNT_NO_CARD); + return; + } + + fs_user_mount_t *vfs = &_emmc_usermount; + vfs->base.type = &mp_fat_vfs_type; + vfs->fatfs.drv = vfs; + // Initialise underlying block device. + vfs->blockdev.block_size = FF_MIN_SS; + mp_vfs_blockdev_init(&vfs->blockdev, dev); + + if (f_mount(&vfs->fatfs) != FR_OK) { + automount_give_up(EMMCIO_AUTOMOUNT_NO_FILESYSTEM); + return; + } + + // Same as CIRCUITPY: while a host has the drive, the host owns writing. + filesystem_set_concurrent_write_protection(vfs, true); + filesystem_set_writable_by_usb(vfs, true); + + mp_vfs_mount_t *emmc_vfs = &_emmc_vfs; + emmc_vfs->str = CIRCUITPY_EMMC_MOUNT_PATH; + emmc_vfs->len = sizeof(CIRCUITPY_EMMC_MOUNT_PATH) - 1; + emmc_vfs->obj = MP_OBJ_FROM_PTR(&_emmc_usermount); + emmc_vfs->next = MP_STATE_VM(vfs_mount_table); + MP_STATE_VM(vfs_mount_table) = emmc_vfs; + + // The budget covers bring-up and the mount only + common_hal_emmcio_emmc_clear_deadline(); + _crumb.in_progress = 0; + _status = EMMCIO_AUTOMOUNT_OK; +} + +emmcio_automount_status_t emmcio_automount_get_status(void) { + return _status; +} + +#endif // CIRCUITPY_EMMC_USB diff --git a/shared-module/emmcio/__init__.h b/shared-module/emmcio/__init__.h new file mode 100644 index 00000000000..a4e634b8851 --- /dev/null +++ b/shared-module/emmcio/__init__.h @@ -0,0 +1,41 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Tim Cocks for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "py/obj.h" + +#if CIRCUITPY_EMMC_USB + +#ifndef CIRCUITPY_EMMC_MOUNT_PATH +#define CIRCUITPY_EMMC_MOUNT_PATH "/sd" +#endif + +typedef enum { + EMMCIO_AUTOMOUNT_NOT_TRIED = 0, + EMMCIO_AUTOMOUNT_OK, + EMMCIO_AUTOMOUNT_DISABLED, // CIRCUITPY_EMMC_USB = 0 + EMMCIO_AUTOMOUNT_SAFE_MODE, + EMMCIO_AUTOMOUNT_NO_CARD, // bring-up failed or timed out + EMMCIO_AUTOMOUNT_NO_FILESYSTEM, // card came up, f_mount refused it + EMMCIO_AUTOMOUNT_SKIPPED_AFTER_FAULT, // last boot died in here +} emmcio_automount_status_t; + +void automount_emmc(void); + +bool emmcio_is_automounted(void); + +emmcio_automount_status_t emmcio_automount_get_status(void); + +#else + +#include + +static inline bool emmcio_is_automounted(void) { + return false; +} + +#endif diff --git a/supervisor/shared/filesystem.c b/supervisor/shared/filesystem.c index 3998c304b7f..cefac51563f 100644 --- a/supervisor/shared/filesystem.c +++ b/supervisor/shared/filesystem.c @@ -19,6 +19,10 @@ #include "shared-module/sdcardio/__init__.h" #endif +#if CIRCUITPY_EMMC_USB +#include "shared-module/emmcio/__init__.h" +#endif + static mp_vfs_mount_t _circuitpy_vfs; static fs_user_mount_t _circuitpy_usermount; @@ -230,6 +234,14 @@ bool filesystem_init(bool create_allowed, bool force_create) { #endif #endif + // Same reason as the SD card above, mount it before USB enumerates rather than + // lazily from tud_msc_test_unit_ready_cb() -- and the same requirement, + // that settings.toml (just mounted, a few lines up) is readable, because + // this is where CIRCUITPY_EMMC_USB is honoured. + #if CIRCUITPY_EMMC_USB + automount_emmc(); + #endif + return true; } diff --git a/supervisor/shared/usb/usb_msc_flash.c b/supervisor/shared/usb/usb_msc_flash.c index e9e36131c13..45483353567 100644 --- a/supervisor/shared/usb/usb_msc_flash.c +++ b/supervisor/shared/usb/usb_msc_flash.c @@ -38,7 +38,21 @@ #define SDCARD_COUNT 0 #endif -#define LUN_COUNT (1 + SAVES_COUNT + SDCARD_COUNT) +#if CIRCUITPY_EMMC_USB +#include "shared-module/emmcio/__init__.h" + +#define EMMC_COUNT 1 +#define EMMC_LUN (1 + SAVES_COUNT + SDCARD_COUNT) + +// SCSI INQUIRY product id for the eMMC LUN. At most 16 characters. +#ifndef CIRCUITPY_EMMC_MSC_PRODUCT_ID +#define CIRCUITPY_EMMC_MSC_PRODUCT_ID "eMMC" +#endif +#else +#define EMMC_COUNT 0 +#endif + +#define LUN_COUNT (1 + SAVES_COUNT + SDCARD_COUNT + EMMC_COUNT) // The ellipsis range in the designated initializer of `ejected` is not standard C, // but it works in both gcc and clang. @@ -165,6 +179,26 @@ static fs_user_mount_t *get_vfs(int lun) { } } #endif + #ifdef EMMC_LUN + if (lun == EMMC_LUN) { + const char *path_under_mount; + + fs_user_mount_t *emmc = filesystem_for_path(CIRCUITPY_EMMC_MOUNT_PATH, &path_under_mount); + // Unlike the SD card there is no heap-mount case to allow: the eMMC's + // drive exists only when the supervisor mounted it, and + // that mount is static. A user mount made by code.py stays a Python + // filesystem and never becomes a LUN. + if (emmc != root && + ((emmc->blockdev.flags & MP_BLOCKDEV_FLAG_NATIVE) != 0) && + !gc_ptr_on_heap(emmc)) { + return emmc; + } else { + // Clear any ejected state so that a remount causes it to reappear. + ejected[EMMC_LUN] = false; + locked[EMMC_LUN] = false; + } + } + #endif return NULL; } @@ -362,7 +396,15 @@ void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16 (void)lun; memcpy(vendor_id, CFG_TUD_MSC_VENDOR, strlen(CFG_TUD_MSC_VENDOR)); - memcpy(product_id, CFG_TUD_MSC_PRODUCT, strlen(CFG_TUD_MSC_PRODUCT)); + #ifdef EMMC_LUN + if (lun == EMMC_LUN) { + static const char emmc_product_id[] = CIRCUITPY_EMMC_MSC_PRODUCT_ID; + memcpy(product_id, emmc_product_id, strlen(emmc_product_id)); + } else + #endif + { + memcpy(product_id, CFG_TUD_MSC_PRODUCT, strlen(CFG_TUD_MSC_PRODUCT)); + } memcpy(product_rev, CFG_TUD_MSC_PRODUCT_REV, strlen(CFG_TUD_MSC_PRODUCT_REV)); } From ac9b8dd62943d59a5f17d47084fce86a4e9e89ae Mon Sep 17 00:00:00 2001 From: foamyguy Date: Wed, 26 Aug 2026 11:15:07 -0500 Subject: [PATCH 2/7] remove scsi inquiry product id, remove automount_status() --- shared-bindings/emmcio/__init__.c | 44 --------------------------- shared-module/emmcio/__init__.c | 16 ++-------- shared-module/emmcio/__init__.h | 12 -------- supervisor/shared/usb/usb_msc_flash.c | 15 +-------- 4 files changed, 4 insertions(+), 83 deletions(-) diff --git a/shared-bindings/emmcio/__init__.c b/shared-bindings/emmcio/__init__.c index 3bc8ff51be7..b3b3cc37713 100644 --- a/shared-bindings/emmcio/__init__.c +++ b/shared-bindings/emmcio/__init__.c @@ -4,8 +4,6 @@ // // SPDX-License-Identifier: MIT -#include - #include "py/obj.h" #include "py/runtime.h" @@ -35,52 +33,10 @@ static mp_obj_t emmcio_automounted(void) { } static MP_DEFINE_CONST_FUN_OBJ_0(emmcio_automounted_obj, emmcio_automounted); -// | def automount_status() -> str: -// | """Why the eMMC is or is not mounted at ``/sd``, as one of: -// | -// | * ``"ok"`` -- mounted. -// | * ``"disabled"`` -- ``CIRCUITPY_EMMC_USB = 0`` in ``settings.toml``, or -// | the automount is not in this build. -// | * ``"safe mode"`` -- the board booted into safe mode. -// | * ``"no card"`` -- the card did not come up inside the boot budget. -// | * ``"no filesystem"`` -- the card came up but has no FAT volume. -// | * ``"skipped after fault"`` -- the *previous* boot did not come back out -// | of the automount, so this boot left the card alone to be sure USB came -// | up. The next boot tries again.""" -// | ... -// | -static mp_obj_t emmcio_automount_status(void) { - const char *s = "disabled"; - #if CIRCUITPY_EMMC_USB - switch (emmcio_automount_get_status()) { - case EMMCIO_AUTOMOUNT_OK: - s = "ok"; - break; - case EMMCIO_AUTOMOUNT_SAFE_MODE: - s = "safe mode"; - break; - case EMMCIO_AUTOMOUNT_NO_CARD: - s = "no card"; - break; - case EMMCIO_AUTOMOUNT_NO_FILESYSTEM: - s = "no filesystem"; - break; - case EMMCIO_AUTOMOUNT_SKIPPED_AFTER_FAULT: - s = "skipped after fault"; - break; - default: - break; - } - #endif - return mp_obj_new_str(s, strlen(s)); -} -static MP_DEFINE_CONST_FUN_OBJ_0(emmcio_automount_status_obj, emmcio_automount_status); - static const mp_rom_map_elem_t emmcio_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_emmcio) }, { MP_ROM_QSTR(MP_QSTR_EMMC), MP_ROM_PTR(&emmcio_emmc_type) }, { MP_ROM_QSTR(MP_QSTR_automounted), MP_ROM_PTR(&emmcio_automounted_obj) }, - { MP_ROM_QSTR(MP_QSTR_automount_status), MP_ROM_PTR(&emmcio_automount_status_obj) }, }; static MP_DEFINE_CONST_DICT(emmcio_module_globals, emmcio_module_globals_table); diff --git a/shared-module/emmcio/__init__.c b/shared-module/emmcio/__init__.c index c2757d81697..b1db429df72 100644 --- a/shared-module/emmcio/__init__.c +++ b/shared-module/emmcio/__init__.c @@ -40,7 +40,6 @@ static mp_vfs_mount_t _emmc_vfs; static fs_user_mount_t _emmc_usermount; static bool _tried; -static emmcio_automount_status_t _status = EMMCIO_AUTOMOUNT_NOT_TRIED; #define AUTOMOUNT_BUDGET_US 5000000u @@ -54,12 +53,11 @@ static struct { uint32_t in_progress; } _crumb __attribute__((section(".uninitialized"))); -static void automount_give_up(emmcio_automount_status_t status) { +static void automount_give_up(void) { common_hal_emmcio_emmc_clear_deadline(); // Leave the card powered down and the pins released emmcio_automount_abandon(); _crumb.in_progress = 0; - _status = status; } void automount_emmc(void) { @@ -69,20 +67,17 @@ void automount_emmc(void) { _tried = true; if (get_safe_mode() != SAFE_MODE_NONE) { - _status = EMMCIO_AUTOMOUNT_SAFE_MODE; return; } bool enabled = true; (void)settings_get_bool("CIRCUITPY_EMMC_USB", &enabled); if (!enabled) { - _status = EMMCIO_AUTOMOUNT_DISABLED; return; } if (_crumb.magic == AUTOMOUNT_CRUMB_MAGIC && _crumb.in_progress != 0) { _crumb.in_progress = 0; - _status = EMMCIO_AUTOMOUNT_SKIPPED_AFTER_FAULT; return; } _crumb.magic = AUTOMOUNT_CRUMB_MAGIC; @@ -93,7 +88,7 @@ void automount_emmc(void) { mp_obj_t dev = emmcio_automount_construct(DEFAULT_EMMC_CLOCK, DEFAULT_EMMC_COMMAND, DEFAULT_EMMC_DATA, DEFAULT_EMMC_RESET, DEFAULT_EMMC_VCCQ, true, true); if (dev == MP_OBJ_NULL) { - automount_give_up(EMMCIO_AUTOMOUNT_NO_CARD); + automount_give_up(); return; } @@ -105,7 +100,7 @@ void automount_emmc(void) { mp_vfs_blockdev_init(&vfs->blockdev, dev); if (f_mount(&vfs->fatfs) != FR_OK) { - automount_give_up(EMMCIO_AUTOMOUNT_NO_FILESYSTEM); + automount_give_up(); return; } @@ -123,11 +118,6 @@ void automount_emmc(void) { // The budget covers bring-up and the mount only common_hal_emmcio_emmc_clear_deadline(); _crumb.in_progress = 0; - _status = EMMCIO_AUTOMOUNT_OK; -} - -emmcio_automount_status_t emmcio_automount_get_status(void) { - return _status; } #endif // CIRCUITPY_EMMC_USB diff --git a/shared-module/emmcio/__init__.h b/shared-module/emmcio/__init__.h index a4e634b8851..5bef585e438 100644 --- a/shared-module/emmcio/__init__.h +++ b/shared-module/emmcio/__init__.h @@ -14,22 +14,10 @@ #define CIRCUITPY_EMMC_MOUNT_PATH "/sd" #endif -typedef enum { - EMMCIO_AUTOMOUNT_NOT_TRIED = 0, - EMMCIO_AUTOMOUNT_OK, - EMMCIO_AUTOMOUNT_DISABLED, // CIRCUITPY_EMMC_USB = 0 - EMMCIO_AUTOMOUNT_SAFE_MODE, - EMMCIO_AUTOMOUNT_NO_CARD, // bring-up failed or timed out - EMMCIO_AUTOMOUNT_NO_FILESYSTEM, // card came up, f_mount refused it - EMMCIO_AUTOMOUNT_SKIPPED_AFTER_FAULT, // last boot died in here -} emmcio_automount_status_t; - void automount_emmc(void); bool emmcio_is_automounted(void); -emmcio_automount_status_t emmcio_automount_get_status(void); - #else #include diff --git a/supervisor/shared/usb/usb_msc_flash.c b/supervisor/shared/usb/usb_msc_flash.c index 45483353567..5fcd2c8d62b 100644 --- a/supervisor/shared/usb/usb_msc_flash.c +++ b/supervisor/shared/usb/usb_msc_flash.c @@ -43,11 +43,6 @@ #define EMMC_COUNT 1 #define EMMC_LUN (1 + SAVES_COUNT + SDCARD_COUNT) - -// SCSI INQUIRY product id for the eMMC LUN. At most 16 characters. -#ifndef CIRCUITPY_EMMC_MSC_PRODUCT_ID -#define CIRCUITPY_EMMC_MSC_PRODUCT_ID "eMMC" -#endif #else #define EMMC_COUNT 0 #endif @@ -396,15 +391,7 @@ void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16 (void)lun; memcpy(vendor_id, CFG_TUD_MSC_VENDOR, strlen(CFG_TUD_MSC_VENDOR)); - #ifdef EMMC_LUN - if (lun == EMMC_LUN) { - static const char emmc_product_id[] = CIRCUITPY_EMMC_MSC_PRODUCT_ID; - memcpy(product_id, emmc_product_id, strlen(emmc_product_id)); - } else - #endif - { - memcpy(product_id, CFG_TUD_MSC_PRODUCT, strlen(CFG_TUD_MSC_PRODUCT)); - } + memcpy(product_id, CFG_TUD_MSC_PRODUCT, strlen(CFG_TUD_MSC_PRODUCT)); memcpy(product_rev, CFG_TUD_MSC_PRODUCT_REV, strlen(CFG_TUD_MSC_PRODUCT_REV)); } From 2f2aa74db968e79c2e5ec4afaa5233091ce4db88 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Wed, 26 Aug 2026 14:14:03 -0500 Subject: [PATCH 3/7] cleanup comments --- ports/nordic/common-hal/emmcio/EMMC.c | 66 +++++------------------- ports/nordic/common-hal/emmcio/emmc_hw.h | 25 ++------- shared-bindings/emmcio/EMMC.c | 2 +- shared-bindings/emmcio/__init__.c | 4 +- supervisor/shared/filesystem.c | 5 +- 5 files changed, 23 insertions(+), 79 deletions(-) diff --git a/ports/nordic/common-hal/emmcio/EMMC.c b/ports/nordic/common-hal/emmcio/EMMC.c index 3d5e5f7d1ba..c3aa61ad017 100644 --- a/ports/nordic/common-hal/emmcio/EMMC.c +++ b/ports/nordic/common-hal/emmcio/EMMC.c @@ -6,8 +6,6 @@ // ============================================================================ // eMMC flash driver (1-bit MMC protocol over the nRF52840) -// Read and write paths; writing is gated at runtime by -// EMMC(write_enabled=True). // ============================================================================ // Two layers: // @@ -19,7 +17,7 @@ // CLK is low, the card samples (and launches) on the rising edge, MSB // first. The start-bit hunt is bit-banged, then the payload + CRC16 is // exactly byte-aligned for one RX DMA; on ENABLE=0 the pins fall back to -// their GPIO latches, so the surrounding bit-bang continues seamlessly. +// their GPIO latches. // // INTEGRITY: every block read is verified against the card's CRC16 and the // caller retries on a mismatch. @@ -39,10 +37,9 @@ #include "peripherals/nrf/nrf52840/pins.h" #include "shared-module/emmcio/__init__.h" -#define CMD_SAFE_HALF_US 1u // slow clock for the IDENTIFICATION phase only +#define CMD_SAFE_HALF_US 1u -// Command-phase half-period: starts safe (eMMC identification requires a slow -// clock), switched to 0 (full-speed bit-bang, ~1-2 MHz) once init completes. +// Command-phase half-period: starts slow, switched to 0 (full-speed) after init. static uint32_t s_cmd_half_us = CMD_SAFE_HALF_US; static volatile uint32_t g_emmc_clk_half_us = CMD_SAFE_HALF_US; @@ -88,7 +85,6 @@ static bool emmc_deadline_expired(void) { return s_deadline_armed && ticks_since(s_deadline_t0) >= s_deadline_lim; } -// Long-wait service: run background tasks static inline void emmc_yield(void) { RUN_BACKGROUND_TASKS; } @@ -113,16 +109,14 @@ static void cmd_send_bit(uint8_t bit) { // SAMPLE POINT: read the line at the END of the low phase, i.e. before this // bit's clock pulse, not in the middle of it. That is the one point in the -// cycle where BOTH of the card's timing modes hold valid data, which is what -// makes this path work either side of an HS_TIMING switch: +// cycle where both of the card's timing modes hold valid data. // // * backward-compatible timing: the card launches on the FALLING edge and // holds the bit until the next one, so the whole low phase is valid. // tOSU(min) = tWL(min) - tODLY, data good from ~8 ns after the edge. // We read a full low phase later. // * high-speed timing: the card launches on the RISING edge (tODLY, 13.7 ns -// max, referenced to it) and holds until the next rising edge, so the low -// phase is again inside the window. +// max, referenced to it) and holds until the next rising edge. // static uint8_t cmd_recv_bit(void) { // caller sets CMD_IN() once before the response read @@ -251,9 +245,9 @@ static bool send_command_retry(uint8_t cmd, uint32_t arg, uint8_t *r1_out, int t } // DATA read: per-bit CLK toggle uses the configurable (possibly 0) half-period. -// -O2 opts up from the port's -Os default and is load-bearing: at -Os the GPIO +// -O2 opts up from the port's -Os default and is needed: at -Os the GPIO // and delay helpers stop being inlined and become calls inside the per-bit -// loop, where the instruction count is the bit timing. +// loop, and throw off the timing __attribute__((optimize("O2"))) static bool read_data_block(uint8_t *buf) { const uint32_t hd = g_emmc_clk_half_us; @@ -619,20 +613,10 @@ static bool emmc_set_high_speed(emmcio_emmc_obj_t *self) { } self->hs_stage = 4; - // THE DATA PATH'S HALF OF THE SWITCH. HS_TIMING moves the edge the card - // launches DAT0 on, from falling to rising, so SPIM has to move its sample - // edge with it (CPHA=1) or every block after this point comes back shifted - // by a bit and fails its CRC16. The command path needs no such flag, - // cmd_recv_bit() reads at a point that is valid in both timings. But, - // SPIM samples on an edge, and an edge has to pick one. - // - // This happens BEFORE the readback, because the readback is itself a block - // read off a card that has already switched. + NRF_SPIM3->CONFIG = SPIM_CONFIG_MODE1; - // Read the byte back AT THE OLD CLOCK. A card that ACKed the switch but did - // not take it would otherwise be met with a 32 MHz bus it never agreed to, - // and the only symptom would be CRC noise that looks like a wiring fault. + // Read the byte back AT THE OLD CLOCK. uint8_t ext_csd[EMMC_BLOCK_SIZE]; if (!common_hal_emmcio_emmc_read_ext_csd(ext_csd) || ext_csd[EMMC_EXT_CSD_HS_TIMING] != 1u) { @@ -641,10 +625,8 @@ static bool emmc_set_high_speed(emmcio_emmc_obj_t *self) { } self->hs_stage = 5; - // Only now does the host clock move. The re-read is a smoke test of the - // faster bus with the integrity layer watching: if the first fast transfer - // cannot even fetch a block the card just served correctly, fall straight - // back. + // Now host clock moves. The re-read is a smoke test of the + // faster bus with the integrity layer watching. NRF_SPIM3->FREQUENCY = SPIM_FREQ_M32; if (!common_hal_emmcio_emmc_read_ext_csd(ext_csd) || ext_csd[EMMC_EXT_CSD_HS_TIMING] != 1u) { // Back to the old CLOCK but NOT to the old phase: the card is in @@ -728,23 +710,7 @@ static bool write_data_block(const uint8_t *buf) { tx[2 + EMMC_BLOCK_SIZE] = (uint8_t)(crc >> 8); tx[2 + EMMC_BLOCK_SIZE + 1] = (uint8_t)crc; RCLK_LOW(clk_bit); - // Launch edge is mode 0's, always - // - // HS_TIMING moved the card's OUTPUT edge, and only that. Its input timing - // is unchanged: both of the datasheet's tables (p.18 high-speed, p.19 - // backward-compatible) give tISU = tIH = 3 ns for CMD/DAT "referenced to - // CLK", i.e. the card latches the host on the rising edge in either mode. - // So the read path has to follow the card to CPHA=1 and the write path - // must NOT: in mode 1 SPIM shifts MOSI on the leading edge, which is the - // very edge the card samples -- zero setup against a 3 ns requirement, - // and the card takes the previous bit. Mode 0 shifts on the trailing - // edge and hands the card a whole half period of setup: 31 ns at M16, - // 15.6 ns at M32, both an order of magnitude over tISU. - // - // Saving and restoring rather than assuming keeps "the peripheral - // register is the state" true for the read path (emmc_hw.h): this - // function borrows the phase for one DMA and gives it back. Two register - // writes against a ~130 us transfer. + const uint32_t saved_cfg = NRF_SPIM3->CONFIG; if (saved_cfg != SPIM_CONFIG_MODE0) { NRF_SPIM3->CONFIG = SPIM_CONFIG_MODE0; @@ -794,9 +760,8 @@ static bool write_data_block(const uint8_t *buf) { return false; // DAT0 left an INPUT -- see the wait } - // ENFORCE the token: 0b010 = accepted. Anything else -- including "never - // saw one" -- means the card did not take the block, and returning false - // makes the caller retry instead of believing a glitch was stored. + // ENFORCE the token: 0b010 = accepted. Anything else means the card did + // not take the block. if (wr_status != 0x2) { return false; } @@ -842,9 +807,6 @@ bool common_hal_emmcio_emmc_writeblocks(uint32_t block_addr, const uint8_t *buf, } uint32_t common_hal_emmcio_emmc_get_frequency(emmcio_emmc_obj_t *self) { - // SPIM3's M16/M32 codes are special values, NOT points on the linear scale - // the K125..M8 codes sit on (0x0A000000 would decode to 156 MHz there), so - // this is a lookup and not arithmetic. Only two values are ever written. (void)self; return NRF_SPIM3->FREQUENCY == SPIM_FREQ_M32 ? 32000000u : 16000000u; } diff --git a/ports/nordic/common-hal/emmcio/emmc_hw.h b/ports/nordic/common-hal/emmcio/emmc_hw.h index 2e465b3df75..6a8f9b1c88c 100644 --- a/ports/nordic/common-hal/emmcio/emmc_hw.h +++ b/ports/nordic/common-hal/emmcio/emmc_hw.h @@ -74,8 +74,7 @@ static inline void emmc_opt_pin_output(uint8_t pin) { // ---- pin control --------------------------------------------------------- // The command/init path uses the HAL macros; the data path uses the direct -// port-0 register accesses below (~3 cycles vs ~130 for the HAL, which is the -// difference between a usable bit-bang clock and a useless one). +// port-0 register accesses below #define CLK_HIGH() nrf_gpio_pin_set(emmc_pinout.clk) #define CLK_LOW() nrf_gpio_pin_clear(emmc_pinout.clk) #define CMD_HIGH() nrf_gpio_pin_set(emmc_pinout.cmd) @@ -124,17 +123,7 @@ static inline void emmc_pins_release(void) { // VCCQ stays an output, driven low: the rail must stay off, not float. } -// ---- the write path's DMA buffer (anomaly 198) ---------------------------- -// SPIM3 on the nRF52840 corrupts TX bytes when EasyDMA reads them out of the -// upper RAM regions while the CPU is busy elsewhere (errata 198). The port -// already reserves 8 KiB of low RAM for exactly this (mpconfigport.h:36, -// SPIM3_BUFFER_RAM_START_ADDR), and busio's SPI uses it for the same reason -- -// which is safe to share because an emmcio.EMMC object owns SPIM3 outright -// while it lives: busio's allocator asks emmcio_spim3_in_use() and falls back -// to SPIM0/1/2, so the two can never have a transfer in flight at once. -// -// The write path relies on this buffer rather than on retrying a bad CRC -// status; the status token is still enforced as the backstop. +// handle nrf52840 errata 198 SPIM3 data corruption #define EMMC_TX_FRAME ((uint8_t *)SPIM3_BUFFER_RAM_START_ADDR) // ---- time ---------------------------------------------------------------- @@ -157,8 +146,6 @@ static inline uint32_t ticks_since_raw(uint32_t t0) { // in backwards-compatible mode). The bus is fixed there; the ONE way it moves // is emmc_set_high_speed(), which first gets the card's own EXT_CSD to read // back HS_TIMING = 1 (52 MHz limit) and only then steps to M32. -// There is still no free-floating "speed knob": the two codes at the top of -// this file are the only values ever written. // // NRF_SPIM3->FREQUENCY and ->CONFIG are read and written directly: the // peripheral register is the state, and it survives @@ -186,12 +173,8 @@ static inline void emmc_spim_deinit(void) { // One blocking DMA transfer with the wires temporarily owned by SPIM. While // ENABLED the peripheral drives SCK (+MOSI for TX) / samples MISO; on disable -// the pins fall back to their GPIO latches (CLK low, DAT0 as configured), so -// the surrounding bit-bang phases continue seamlessly. -// -// A read is rx-only (MOSI unselected), so SPIM3 anomaly 198 (TX corruption) -// cannot bite there at all. Writes make TX real, which is why their frame is -// built in EMMC_TX_FRAME above. +// the pins fall back to their GPIO latches (CLK low, DAT0 as configured). + static inline void emmc_spim_xfer(const uint8_t *tx, uint32_t txlen, uint8_t *rx, uint32_t rxlen) { NRF_SPIM3->PSEL.MOSI = tx ? emmc_pinout.dat0 : 0xFFFFFFFFu; NRF_SPIM3->PSEL.MISO = rx ? emmc_pinout.dat0 : 0xFFFFFFFFu; diff --git a/shared-bindings/emmcio/EMMC.c b/shared-bindings/emmcio/EMMC.c index d095e6c7cc4..a32f32ce3bb 100644 --- a/shared-bindings/emmcio/EMMC.c +++ b/shared-bindings/emmcio/EMMC.c @@ -25,7 +25,7 @@ static void check_for_deinit(emmcio_emmc_obj_t *self) { } // | class EMMC: -// | """The on-board eMMC as a block device""" +// | """eMMC as a block device""" // | // | def __init__( // | self, diff --git a/shared-bindings/emmcio/__init__.c b/shared-bindings/emmcio/__init__.c index b3b3cc37713..f2e75fb4289 100644 --- a/shared-bindings/emmcio/__init__.c +++ b/shared-bindings/emmcio/__init__.c @@ -12,8 +12,8 @@ // | """Block device access to the on-board eMMC // | -// | The `emmcio` module exposes the board's soldered-down eMMC chip as a block -// | device. It provides no filesystem of its own: to read files, hand an `EMMC` +// | The `emmcio` module exposes an eMMC chip as a block device. +// | It provides no filesystem of its own: to read files, hand an `EMMC` // | object to `storage.VfsFat` and mount it. // | // | .. note:: This module is only available on boards with an eMMC wired to the diff --git a/supervisor/shared/filesystem.c b/supervisor/shared/filesystem.c index cefac51563f..4335e9afc6e 100644 --- a/supervisor/shared/filesystem.c +++ b/supervisor/shared/filesystem.c @@ -235,9 +235,8 @@ bool filesystem_init(bool create_allowed, bool force_create) { #endif // Same reason as the SD card above, mount it before USB enumerates rather than - // lazily from tud_msc_test_unit_ready_cb() -- and the same requirement, - // that settings.toml (just mounted, a few lines up) is readable, because - // this is where CIRCUITPY_EMMC_USB is honoured. + // lazily from tud_msc_test_unit_ready_cb(). Also the same requirement + // that settings.toml is readable. #if CIRCUITPY_EMMC_USB automount_emmc(); #endif From 38002d437790bfba67f5e253d8bd894723fbb7b9 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Wed, 26 Aug 2026 14:36:41 -0500 Subject: [PATCH 4/7] refactor error messages to eliminate new strings --- locale/circuitpython.pot | 33 +-- ports/nordic/common-hal/emmcio/EMMC.c | 109 +++++----- ports/nordic/common-hal/emmcio/EMMC.h | 6 +- shared-bindings/emmcio/EMMC.c | 298 ++++++++++++++------------ shared-bindings/emmcio/EMMC.h | 13 ++ shared-bindings/emmcio/__init__.c | 34 +-- 6 files changed, 265 insertions(+), 228 deletions(-) diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index 130bb695683..8c42cc6f1d5 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -990,7 +990,7 @@ msgstr "" #: ports/atmel-samd/common-hal/microcontroller/Pin.c #: ports/espressif/common-hal/dotclockframebuffer/DotClockFramebuffer.c #: ports/mimxrt10xx/common-hal/microcontroller/Pin.c -#: shared-bindings/microcontroller/Pin.c +#: shared-bindings/emmcio/EMMC.c shared-bindings/microcontroller/Pin.c msgid "Invalid %q pin" msgstr "" @@ -1032,7 +1032,8 @@ msgid "Another send is already active" msgstr "" #: ports/atmel-samd/common-hal/sdioio/SDCard.c -#: ports/espressif/common-hal/qspibus/QSPIBus.c +#: ports/espressif/common-hal/qspibus/QSPIBus.c shared-bindings/emmcio/EMMC.c +#: shared-bindings/socketpool/SocketPool.c msgid "%q failure: %d" msgstr "" @@ -1040,8 +1041,8 @@ msgstr "" #: ports/cxd56/common-hal/sdioio/SDCard.c #: ports/espressif/common-hal/sdioio/SDCard.c #: ports/raspberrypi/common-hal/sdioio/SDCard.c -#: ports/stm/common-hal/sdioio/SDCard.c shared-bindings/floppyio/__init__.c -#: shared-module/sdcardio/SDCard.c +#: ports/stm/common-hal/sdioio/SDCard.c shared-bindings/emmcio/EMMC.c +#: shared-bindings/floppyio/__init__.c shared-module/sdcardio/SDCard.c #, c-format msgid "Buffer must be a multiple of %d bytes" msgstr "" @@ -1153,6 +1154,7 @@ msgstr "" #: ports/espressif/common-hal/_bleio/Adapter.c #: ports/nordic/common-hal/_bleio/Adapter.c +#: ports/zephyr-cp/common-hal/_bleio/Adapter.c msgid "Data too large for advertisement packet" msgstr "" @@ -1325,7 +1327,7 @@ msgid "%q must be 8, 16, 24, or 32" msgstr "" #: ports/espressif/common-hal/audiobusio/__init__.c -#: ports/espressif/common-hal/audioi2sin/I2SIn.c +#: ports/espressif/common-hal/audioi2sin/I2SIn.c shared-bindings/emmcio/EMMC.c msgid "Peripheral in use" msgstr "" @@ -1518,7 +1520,7 @@ msgstr "" #: ports/raspberrypi/common-hal/picodvi/Framebuffer_RP2350.c #: ports/raspberrypi/common-hal/rp2pio/StateMachine.c #: ports/raspberrypi/common-hal/usb_host/Port.c -#: shared-bindings/digitalio/DigitalInOut.c +#: shared-bindings/digitalio/DigitalInOut.c shared-bindings/emmcio/EMMC.c #: shared-bindings/i2cioexpander/IOPin.c shared-bindings/microcontroller/Pin.c #: shared-module/max3421e/Max3421E.c msgid "%q in use" @@ -1691,6 +1693,7 @@ msgstr "" #: ports/mimxrt10xx/common-hal/busio/UART.c ports/stm/common-hal/busio/I2C.c #: ports/stm/common-hal/busio/SPI.c ports/stm/common-hal/busio/UART.c #: ports/stm/common-hal/canio/CAN.c ports/stm/common-hal/sdioio/SDCard.c +#: shared-bindings/emmcio/EMMC.c msgid "Hardware in use, try alternative pins" msgstr "" @@ -1817,7 +1820,7 @@ msgstr "" #: ports/raspberrypi/bindings/cyw43/__init__.c py/argcheck.c py/objexcept.c #: shared-bindings/bitmapfilter/__init__.c shared-bindings/canio/CAN.c #: shared-bindings/digitalio/Pull.c shared-bindings/supervisor/__init__.c -#: shared-module/audiofilters/Filter.c shared-module/displayio/__init__.c +#: shared-module/audiofilters/__init__.c shared-module/displayio/__init__.c #: shared-module/synthio/Synthesizer.c msgid "%q must be of type %q or %q, not %q" msgstr "" @@ -1902,7 +1905,7 @@ msgstr "" msgid "I2C peripheral in use" msgstr "" -#: ports/raspberrypi/common-hal/busio/SPI.c +#: ports/raspberrypi/common-hal/busio/SPI.c shared-bindings/emmcio/EMMC.c msgid "SPI peripheral in use" msgstr "" @@ -2353,7 +2356,7 @@ msgstr "" msgid "%q length must be %d" msgstr "" -#: py/argcheck.c shared-module/audiofilters/Filter.c +#: py/argcheck.c shared-module/audiofilters/__init__.c msgid "%q in %q must be of type %q, not %q" msgstr "" @@ -3613,7 +3616,7 @@ msgstr "" msgid "not a 128-bit UUID" msgstr "" -#: shared-bindings/_bleio/__init__.c +#: shared-bindings/_bleio/__init__.c shared-bindings/emmcio/EMMC.c #: shared-bindings/memorymonitor/AllocationSize.c #: shared-bindings/pulseio/PulseIn.c shared-module/bitmaptools/__init__.c #: shared-module/displayio/Bitmap.c shared-module/displayio/Group.c @@ -3773,7 +3776,7 @@ msgstr "" msgid "invalid destination buffer, must be an array of type: %c" msgstr "" -#: shared-bindings/audioio/AudioOut.c +#: shared-bindings/audioio/AudioOut.c shared-bindings/emmcio/EMMC.c msgid "%q and %q must be different" msgstr "" @@ -3996,6 +3999,10 @@ msgstr "" msgid "offset must be >= 0" msgstr "" +#: shared-bindings/emmcio/EMMC.c shared-bindings/i2cioexpander/IOExpander.c +msgid "address out of range" +msgstr "" + #: shared-bindings/epaperdisplay/EPaperDisplay.c msgid "Refresh too soon" msgstr "" @@ -4012,10 +4019,6 @@ msgstr "" msgid "Unsupported hash algorithm" msgstr "" -#: shared-bindings/i2cioexpander/IOExpander.c -msgid "address out of range" -msgstr "" - #: shared-bindings/i2cioexpander/IOExpander.c msgid "num_pins must be 8 or 16" msgstr "" diff --git a/ports/nordic/common-hal/emmcio/EMMC.c b/ports/nordic/common-hal/emmcio/EMMC.c index c3aa61ad017..d6bf9828f64 100644 --- a/ports/nordic/common-hal/emmcio/EMMC.c +++ b/ports/nordic/common-hal/emmcio/EMMC.c @@ -867,120 +867,115 @@ static void emmc_claim_pins(const mcu_pin_obj_t *clock, const mcu_pin_obj_t *com } } -static mp_rom_error_text_t emmc_check_pins(const mcu_pin_obj_t *clock, +static emmcio_construct_result_t emmc_check_pins(const mcu_pin_obj_t *clock, const mcu_pin_obj_t *command, const mcu_pin_obj_t *data, - const mcu_pin_obj_t *reset, const mcu_pin_obj_t *vccq) { + const mcu_pin_obj_t *reset, const mcu_pin_obj_t *vccq, int *detail) { // The data path drives CLK and DAT0 through NRF_P0 directly, so both have // to be on port 0. CMD, RESET and VCCQ go through the HAL and may be // anywhere. - if (clock->number >= P0_PIN_NUM || data->number >= P0_PIN_NUM) { - return MP_ERROR_TEXT("eMMC clock and data must be on port 0"); + if (clock->number >= P0_PIN_NUM) { + *detail = MP_QSTR_clock; + return EMMCIO_ERR_PIN_PORT; + } + if (data->number >= P0_PIN_NUM) { + *detail = MP_QSTR_data; + return EMMCIO_ERR_PIN_PORT; } if ((NRF_SPIM3->ENABLE & SPIM_ENABLE_ENABLE_Msk) != 0) { - return MP_ERROR_TEXT("SPI peripheral in use"); + return EMMCIO_ERR_SPI_IN_USE; } const mcu_pin_obj_t *pins[] = { clock, command, data, reset, vccq }; for (size_t i = 0; i < MP_ARRAY_SIZE(pins); i++) { if (pins[i] != NULL && !pin_number_is_free(pins[i]->number)) { - return MP_ERROR_TEXT("Hardware in use, try alternative pins"); + return EMMCIO_ERR_PIN_IN_USE; } } - return NULL; + return EMMCIO_OK; } -static const char *init_failure_stage(emmcio_emmc_obj_t *self) { +// The MMC command that never answered, so a failure names the step it stopped +// at. CMD8 covers the extended CSD. +static int init_failure_command(emmcio_emmc_obj_t *self) { if (!self->cmd0_sent) { - return "cmd0"; + return 0; } if (self->cmd1_retries < 0) { - return "cmd1 (card never ready)"; + return 1; // card never left busy } if (!self->cmd2_resp) { - return "cmd2 (no CID)"; + return 2; // no CID } if (!self->cmd3_resp) { - return "cmd3"; + return 3; } if (!self->cmd7_resp) { - return "cmd7 (select)"; + return 7; // select } if (!self->cmd16_resp) { - return "cmd16 (blocklen)"; + return 16; // blocklen } - return "ext_csd"; + return 8; // extended CSD } -static const char *hs_failure_stage(emmcio_emmc_obj_t *self) { - switch (self->hs_stage) { - case 0: - return "DEVICE_TYPE (card does not advertise 52 MHz)"; - case 1: - return "cmd6 (no response)"; - case 2: - return "cmd6 busy (card never released DAT0)"; - case 3: - return self->hs_switch_error - ? "cmd13 SWITCH_ERROR (card rejected HS_TIMING)" - : "cmd13 (card never came back to tran)"; - case 4: - return "readback (EXT_CSD[185] did not take)"; - default: - return "32 MHz smoke test (fell back to 16 MHz)"; +// How far the high-speed switch got: hs_stage as documented on the struct, +// except that a CMD13 SWITCH_ERROR (the card rejecting HS_TIMING) reports 7 to +// tell it apart from the card simply never coming back to tran. +static int hs_failure_stage(emmcio_emmc_obj_t *self) { + if (self->hs_stage == 3 && self->hs_switch_error) { + return 7; } + return self->hs_stage; } -static const char *emmc_power_up(emmcio_emmc_obj_t *self, bool high_speed, bool *hs_failed) { - *hs_failed = false; +static emmcio_construct_result_t emmc_power_up(emmcio_emmc_obj_t *self, bool high_speed, + int *detail) { s_constructed = true; if (!emmc_init(self)) { - const char *stage = init_failure_stage(self); + *detail = init_failure_command(self); emmcio_emmc_release_hardware(); - return stage; + return EMMCIO_ERR_INIT; } uint8_t ext_csd[EMMC_BLOCK_SIZE]; if (!common_hal_emmcio_emmc_read_ext_csd(ext_csd)) { emmcio_emmc_release_hardware(); - return "ext_csd"; + *detail = 8; + return EMMCIO_ERR_INIT; } if (high_speed && !emmc_set_high_speed(self)) { - const char *stage = hs_failure_stage(self); + *detail = hs_failure_stage(self); emmcio_emmc_release_hardware(); - *hs_failed = true; - return stage; + return EMMCIO_ERR_HIGH_SPEED; } - return NULL; + return EMMCIO_OK; } -mp_rom_error_text_t common_hal_emmcio_emmc_construct(emmcio_emmc_obj_t *self, +emmcio_construct_result_t common_hal_emmcio_emmc_construct(emmcio_emmc_obj_t *self, const mcu_pin_obj_t *clock, const mcu_pin_obj_t *command, const mcu_pin_obj_t *data, const mcu_pin_obj_t *reset, const mcu_pin_obj_t *vccq, - bool high_speed, bool write_enabled, const char **stage_out) { - *stage_out = NULL; + bool high_speed, bool write_enabled, int *detail) { + *detail = 0; if (emmcio_is_automounted()) { - return MP_ERROR_TEXT("eMMC owned by the USB drive; set CIRCUITPY_EMMC_USB = false in settings.toml"); + return EMMCIO_ERR_USB_OWNED; } if (s_constructed) { - return MP_ERROR_TEXT("Peripheral in use"); + return EMMCIO_ERR_IN_USE; } - mp_rom_error_text_t pin_err = emmc_check_pins(clock, command, data, reset, vccq); - if (pin_err != NULL) { + emmcio_construct_result_t pin_err = emmc_check_pins(clock, command, data, reset, vccq, detail); + if (pin_err != EMMCIO_OK) { return pin_err; } emmc_claim_pins(clock, command, data, reset, vccq, false); - bool hs_failed = false; - const char *stage = emmc_power_up(self, high_speed, &hs_failed); - if (stage != NULL) { - *stage_out = stage; - return hs_failed ? MP_ERROR_TEXT("eMMC high-speed switch failed at %s") - : MP_ERROR_TEXT("eMMC init failed at %s"); + emmcio_construct_result_t err = emmc_power_up(self, high_speed, detail); + if (err != EMMCIO_OK) { + return err; } self->deinited = false; self->write_enabled = write_enabled; - return NULL; + return EMMCIO_OK; } void common_hal_emmcio_emmc_deinit(emmcio_emmc_obj_t *self) { @@ -1006,13 +1001,13 @@ mp_obj_t emmcio_automount_construct(const mcu_pin_obj_t *clock, const mcu_pin_ob if (s_constructed) { return MP_OBJ_NULL; } - if (emmc_check_pins(clock, command, data, reset, vccq) != NULL) { + int detail; + if (emmc_check_pins(clock, command, data, reset, vccq, &detail) != EMMCIO_OK) { return MP_OBJ_NULL; } emmc_claim_pins(clock, command, data, reset, vccq, true); s_automount_obj.base.type = &emmcio_emmc_type; - bool hs_failed = false; - if (emmc_power_up(&s_automount_obj, high_speed, &hs_failed) != NULL) { + if (emmc_power_up(&s_automount_obj, high_speed, &detail) != EMMCIO_OK) { return MP_OBJ_NULL; } s_automount_obj.deinited = false; diff --git a/ports/nordic/common-hal/emmcio/EMMC.h b/ports/nordic/common-hal/emmcio/EMMC.h index 65055268da4..5bd0d6316c2 100644 --- a/ports/nordic/common-hal/emmcio/EMMC.h +++ b/ports/nordic/common-hal/emmcio/EMMC.h @@ -44,10 +44,12 @@ typedef struct { uint8_t hs_stage; } emmcio_emmc_obj_t; -mp_rom_error_text_t common_hal_emmcio_emmc_construct(emmcio_emmc_obj_t *self, +// EMMCIO_OK on success. On failure *detail carries the code's extra number, +// which the binding turns into the exception's argument. +emmcio_construct_result_t common_hal_emmcio_emmc_construct(emmcio_emmc_obj_t *self, const mcu_pin_obj_t *clock, const mcu_pin_obj_t *command, const mcu_pin_obj_t *data, const mcu_pin_obj_t *reset, const mcu_pin_obj_t *vccq, - bool high_speed, bool write_enabled, const char **stage_out); + bool high_speed, bool write_enabled, int *detail); void common_hal_emmcio_emmc_deinit(emmcio_emmc_obj_t *self); bool common_hal_emmcio_emmc_deinited(emmcio_emmc_obj_t *self); diff --git a/shared-bindings/emmcio/EMMC.c b/shared-bindings/emmcio/EMMC.c index a32f32ce3bb..8286daf9e20 100644 --- a/shared-bindings/emmcio/EMMC.c +++ b/shared-bindings/emmcio/EMMC.c @@ -24,61 +24,68 @@ static void check_for_deinit(emmcio_emmc_obj_t *self) { } } -// | class EMMC: -// | """eMMC as a block device""" -// | -// | def __init__( -// | self, -// | *, -// | clock: microcontroller.Pin, -// | command: microcontroller.Pin, -// | data: microcontroller.Pin, -// | reset: Optional[microcontroller.Pin] = None, -// | vccq: Optional[microcontroller.Pin] = None, -// | high_speed: bool = False, -// | write_enabled: bool = False, -// | ) -> None: -// | """Power up the card and make it ready for block access. -// | -// | Only one `EMMC` object may exist at a time. Call `deinit()`, or use -// | the object as a context manager, to release the card and its pins. -// | -// | :param ~microcontroller.Pin clock: the card's CLK pin -// | :param ~microcontroller.Pin command: the card's CMD pin -// | :param ~microcontroller.Pin data: the card's DAT0 pin. The bus is -// | 1-bit, so this is a single pin. -// | :param ~microcontroller.Pin reset: the card's RST_n pin, if the board -// | wires one -// | :param ~microcontroller.Pin vccq: a pin gating the card's I/O rail, -// | if the board has one -// | :param bool high_speed: Run the bus at its faster clock rate. Raises -// | an `OSError` if the card will not make the switch. -// | :param bool write_enabled: Allow `writeblocks()`. When `False`, the -// | object is read-only and every write path refuses. -// | -// | :raises ValueError: if the pins are unusable or already in use, or -// | if the card is owned by the USB drive. -// | :raises OSError: if the card does not come up. -// | -// | Mount the card's filesystem:: -// | -// | import board -// | import emmcio -// | import storage -// | -// | emmc = emmcio.EMMC( -// | clock=board.EMMC_CLK, -// | command=board.EMMC_CMD, -// | data=board.EMMC_DAT0, -// | reset=board.EMMC_RESET, -// | vccq=board.EMMC_VCCQ, -// | high_speed=True, -// | write_enabled=True, -// | ) -// | storage.mount(storage.VfsFat(emmc), "/sd") -// | """ -// | ... -// | +//| class EMMC: +//| """eMMC as a block device""" +//| +//| def __init__( +//| self, +//| *, +//| clock: microcontroller.Pin, +//| command: microcontroller.Pin, +//| data: microcontroller.Pin, +//| reset: Optional[microcontroller.Pin] = None, +//| vccq: Optional[microcontroller.Pin] = None, +//| high_speed: bool = False, +//| write_enabled: bool = False, +//| ) -> None: +//| """Power up the card and make it ready for block access. +//| +//| Only one `EMMC` object may exist at a time. Call `deinit()`, or use +//| the object as a context manager, to release the card and its pins. +//| +//| :param ~microcontroller.Pin clock: the card's CLK pin +//| :param ~microcontroller.Pin command: the card's CMD pin +//| :param ~microcontroller.Pin data: the card's DAT0 pin. The bus is +//| 1-bit, so this is a single pin. +//| :param ~microcontroller.Pin reset: the card's RST_n pin, if the board +//| wires one +//| :param ~microcontroller.Pin vccq: a pin gating the card's I/O rail, +//| if the board has one +//| :param bool high_speed: Run the bus at its faster clock rate. Raises +//| an `OSError` if the card will not make the switch. +//| :param bool write_enabled: Allow `writeblocks()`. When `False`, the +//| object is read-only and every write path refuses. +//| +//| :raises ValueError: if the pins are unusable or already in use, or +//| if the card is owned by the USB drive. +//| :raises OSError: if the card does not come up. ``emmcio failure: n`` +//| names the MMC command that did not answer (``8`` being the +//| extended CSD), and ``high_speed failure: n`` how far the switch +//| got: ``0`` the card does not advertise 52 MHz, ``1`` no CMD6 +//| response, ``2`` the card never released DAT0, ``3`` it never came +//| back to the transfer state, ``4`` the EXT_CSD readback did not +//| take, ``5`` the 32 MHz smoke test failed, ``7`` the card rejected +//| HS_TIMING. +//| +//| Mount the card's filesystem:: +//| +//| import board +//| import emmcio +//| import storage +//| +//| emmc = emmcio.EMMC( +//| clock=board.EMMC_CLK, +//| command=board.EMMC_CMD, +//| data=board.EMMC_DAT0, +//| reset=board.EMMC_RESET, +//| vccq=board.EMMC_VCCQ, +//| high_speed=True, +//| write_enabled=True, +//| ) +//| storage.mount(storage.VfsFat(emmc), "/sd") +//| """ +//| ... +//| static mp_obj_t emmcio_emmc_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { enum { ARG_clock, ARG_command, ARG_data, ARG_reset, ARG_vccq, ARG_high_speed, ARG_write_enabled }; static const mp_arg_t allowed_args[] = { @@ -113,41 +120,58 @@ static mp_obj_t emmcio_emmc_make_new(const mp_obj_type_t *type, size_t n_args, s } emmcio_emmc_obj_t *self = mp_obj_malloc(emmcio_emmc_obj_t, &emmcio_emmc_type); - // A stage means the card itself did not come up; anything else means the + // An OSError means the card itself did not come up; a ValueError means the // wiring or the hardware is unusable. - const char *stage = NULL; - mp_rom_error_text_t err = common_hal_emmcio_emmc_construct(self, - clock, command, data, reset, vccq, - args[ARG_high_speed].u_bool, args[ARG_write_enabled].u_bool, &stage); - if (err != NULL) { - if (stage != NULL) { - mp_raise_msg_varg(&mp_type_OSError, err, stage); - } - mp_raise_ValueError(err); + int detail = 0; + switch (common_hal_emmcio_emmc_construct(self, clock, command, data, reset, vccq, + args[ARG_high_speed].u_bool, args[ARG_write_enabled].u_bool, &detail)) { + case EMMCIO_OK: + break; + case EMMCIO_ERR_PIN_PORT: + mp_raise_ValueError_varg(MP_ERROR_TEXT("Invalid %q pin"), (qstr)detail); + break; + case EMMCIO_ERR_SPI_IN_USE: + mp_raise_ValueError(MP_ERROR_TEXT("SPI peripheral in use")); + break; + case EMMCIO_ERR_PIN_IN_USE: + mp_raise_ValueError(MP_ERROR_TEXT("Hardware in use, try alternative pins")); + break; + case EMMCIO_ERR_USB_OWNED: + mp_raise_ValueError_varg(MP_ERROR_TEXT("%q in use"), MP_QSTR_emmcio); + break; + case EMMCIO_ERR_IN_USE: + mp_raise_ValueError(MP_ERROR_TEXT("Peripheral in use")); + break; + case EMMCIO_ERR_INIT: + mp_raise_OSError_msg_varg(MP_ERROR_TEXT("%q failure: %d"), MP_QSTR_emmcio, detail); + break; + case EMMCIO_ERR_HIGH_SPEED: + mp_raise_OSError_msg_varg(MP_ERROR_TEXT("%q failure: %d"), MP_QSTR_high_speed, detail); + break; } return MP_OBJ_FROM_PTR(self); } -// | def deinit(self) -> None: -// | """Release the card and the pins it uses. Any further use of this -// | object raises a `ValueError`.""" -// | ... -// | +//| def deinit(self) -> None: +//| """Release the card and the pins it uses. Any further use of this +//| object raises a `ValueError`.""" +//| ... +//| static mp_obj_t emmcio_emmc_deinit(mp_obj_t self_in) { common_hal_emmcio_emmc_deinit(MP_OBJ_TO_PTR(self_in)); return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_1(emmcio_emmc_deinit_obj, emmcio_emmc_deinit); -// | def __enter__(self) -> EMMC: -// | """No-op used by Context Managers.""" -// | ... -// | -// | def __exit__(self) -> None: -// | """Automatically deinitializes the hardware when exiting a context. See -// | :ref:`lifetime-and-contextmanagers` for more info.""" -// | ... -// | +//| def __enter__(self) -> EMMC: +//| """No-op used by Context Managers.""" +//| ... +//| +//| def __exit__(self) -> None: +//| """Automatically deinitializes the hardware when exiting a context. See +//| :ref:`lifetime-and-contextmanagers` for more info.""" +//| ... +//| static mp_obj_t emmcio_emmc_obj___exit__(size_t n_args, const mp_obj_t *args) { return emmcio_emmc_deinit(args[0]); } @@ -178,18 +202,18 @@ static int emmc_read_chunked(emmcio_emmc_obj_t *self, uint8_t *out, mp_uint_t st return 0; } -// | def readblocks(self, start_block: int, buf: WriteableBuffer) -> None: -// | """Read into ``buf`` starting at ``start_block``. -// | -// | :param int start_block: the first block to read -// | :param WriteableBuffer buf: a buffer whose length is a non-zero -// | multiple of `block_size` -// | -// | :raises ValueError: if ``buf`` is the wrong length, or the requested -// | blocks run past the end of the card. -// | :raises OSError: if the card fails to deliver the data.""" -// | ... -// | +//| def readblocks(self, start_block: int, buf: WriteableBuffer) -> None: +//| """Read into ``buf`` starting at ``start_block``. +//| +//| :param int start_block: the first block to read +//| :param WriteableBuffer buf: a buffer whose length is a non-zero +//| multiple of `block_size` +//| +//| :raises ValueError: if ``buf`` is the wrong length, or the requested +//| blocks run past the end of the card. +//| :raises OSError: if the card fails to deliver the data.""" +//| ... +//| static mp_obj_t emmcio_emmc_readblocks(mp_obj_t self_in, mp_obj_t start_in, mp_obj_t buf_in) { emmcio_emmc_obj_t *self = MP_OBJ_TO_PTR(self_in); check_for_deinit(self); @@ -237,20 +261,20 @@ static int emmc_write_chunked(emmcio_emmc_obj_t *self, const uint8_t *src, mp_ui return 0; } -// | def writeblocks(self, start_block: int, buf: ReadableBuffer) -> None: -// | """Write ``buf`` to the card starting at ``start_block``. -// | -// | :param int start_block: the first block to write -// | :param ReadableBuffer buf: a buffer whose length is a non-zero -// | multiple of `block_size` -// | -// | :raises RuntimeError: if this object was not constructed with -// | ``write_enabled=True``. -// | :raises ValueError: if ``buf`` is the wrong length, or the requested -// | blocks run past the end of the card. -// | :raises OSError: if the write fails.""" -// | ... -// | +//| def writeblocks(self, start_block: int, buf: ReadableBuffer) -> None: +//| """Write ``buf`` to the card starting at ``start_block``. +//| +//| :param int start_block: the first block to write +//| :param ReadableBuffer buf: a buffer whose length is a non-zero +//| multiple of `block_size` +//| +//| :raises RuntimeError: if this object was not constructed with +//| ``write_enabled=True``. +//| :raises ValueError: if ``buf`` is the wrong length, or the requested +//| blocks run past the end of the card. +//| :raises OSError: if the write fails.""" +//| ... +//| static mp_obj_t emmcio_emmc_writeblocks(mp_obj_t self_in, mp_obj_t start_in, mp_obj_t buf_in) { emmcio_emmc_obj_t *self = MP_OBJ_TO_PTR(self_in); check_for_deinit(self); @@ -279,12 +303,12 @@ static mp_obj_t emmcio_emmc_writeblocks(mp_obj_t self_in, mp_obj_t start_in, mp_ } static MP_DEFINE_CONST_FUN_OBJ_3(emmcio_emmc_writeblocks_obj, emmcio_emmc_writeblocks); -// | def ioctl(self, op: int, arg: int) -> Optional[int]: -// | """Perform a block-device control operation, as required by the -// | block-device protocol. Returns `None` for operations this device does -// | not implement.""" -// | ... -// | +//| def ioctl(self, op: int, arg: int) -> Optional[int]: +//| """Perform a block-device control operation, as required by the +//| block-device protocol. Returns `None` for operations this device does +//| not implement.""" +//| ... +//| static mp_obj_t emmcio_emmc_ioctl(mp_obj_t self_in, mp_obj_t op_in, mp_obj_t arg_in) { emmcio_emmc_obj_t *self = MP_OBJ_TO_PTR(self_in); check_for_deinit(self); @@ -334,10 +358,10 @@ bool emmcio_emmc_is_write_enabled(mp_obj_t self_in) { return !common_hal_emmcio_emmc_deinited(self) && self->write_enabled; } -// | def read_ext_csd(self) -> bytes: -// | """Read the card's 512-byte extended CSD register.""" -// | ... -// | +//| def read_ext_csd(self) -> bytes: +//| """Read the card's 512-byte extended CSD register.""" +//| ... +//| static mp_obj_t emmcio_emmc_read_ext_csd(mp_obj_t self_in) { emmcio_emmc_obj_t *self = MP_OBJ_TO_PTR(self_in); check_for_deinit(self); @@ -350,10 +374,10 @@ static mp_obj_t emmcio_emmc_read_ext_csd(mp_obj_t self_in) { } static MP_DEFINE_CONST_FUN_OBJ_1(emmcio_emmc_read_ext_csd_obj, emmcio_emmc_read_ext_csd); -// | def status(self) -> int: -// | """Read the card's 32-bit status register.""" -// | ... -// | +//| def status(self) -> int: +//| """Read the card's 32-bit status register.""" +//| ... +//| static mp_obj_t emmcio_emmc_status(mp_obj_t self_in) { emmcio_emmc_obj_t *self = MP_OBJ_TO_PTR(self_in); check_for_deinit(self); @@ -368,9 +392,9 @@ static mp_obj_t emmcio_emmc_status(mp_obj_t self_in) { } static MP_DEFINE_CONST_FUN_OBJ_1(emmcio_emmc_status_obj, emmcio_emmc_status); -// | count: int -// | """The number of blocks on the card.""" -// | +//| count: int +//| """The number of blocks on the card.""" +//| static mp_obj_t emmcio_emmc_get_count(mp_obj_t self_in) { emmcio_emmc_obj_t *self = MP_OBJ_TO_PTR(self_in); check_for_deinit(self); @@ -379,18 +403,18 @@ static mp_obj_t emmcio_emmc_get_count(mp_obj_t self_in) { static MP_DEFINE_CONST_FUN_OBJ_1(emmcio_emmc_get_count_obj, emmcio_emmc_get_count); MP_PROPERTY_GETTER(emmcio_emmc_count_obj, (mp_obj_t)&emmcio_emmc_get_count_obj); -// | block_size: int -// | """The size of one block, in bytes.""" -// | +//| block_size: int +//| """The size of one block, in bytes.""" +//| static mp_obj_t emmcio_emmc_get_block_size(mp_obj_t self_in) { return MP_OBJ_NEW_SMALL_INT(EMMC_BLOCK_SIZE); } static MP_DEFINE_CONST_FUN_OBJ_1(emmcio_emmc_get_block_size_obj, emmcio_emmc_get_block_size); MP_PROPERTY_GETTER(emmcio_emmc_block_size_obj, (mp_obj_t)&emmcio_emmc_get_block_size_obj); -// | cid: bytes -// | """The card's 16-byte identification register.""" -// | +//| cid: bytes +//| """The card's 16-byte identification register.""" +//| static mp_obj_t emmcio_emmc_get_cid(mp_obj_t self_in) { emmcio_emmc_obj_t *self = MP_OBJ_TO_PTR(self_in); check_for_deinit(self); @@ -399,9 +423,9 @@ static mp_obj_t emmcio_emmc_get_cid(mp_obj_t self_in) { static MP_DEFINE_CONST_FUN_OBJ_1(emmcio_emmc_get_cid_obj, emmcio_emmc_get_cid); MP_PROPERTY_GETTER(emmcio_emmc_cid_obj, (mp_obj_t)&emmcio_emmc_get_cid_obj); -// | write_enabled: bool -// | """Whether `writeblocks()` is permitted on this object.""" -// | +//| write_enabled: bool +//| """Whether `writeblocks()` is permitted on this object.""" +//| static mp_obj_t emmcio_emmc_get_write_enabled(mp_obj_t self_in) { emmcio_emmc_obj_t *self = MP_OBJ_TO_PTR(self_in); check_for_deinit(self); @@ -410,9 +434,9 @@ static mp_obj_t emmcio_emmc_get_write_enabled(mp_obj_t self_in) { static MP_DEFINE_CONST_FUN_OBJ_1(emmcio_emmc_get_write_enabled_obj, emmcio_emmc_get_write_enabled); MP_PROPERTY_GETTER(emmcio_emmc_write_enabled_obj, (mp_obj_t)&emmcio_emmc_get_write_enabled_obj); -// | high_speed: bool -// | """Whether the card is running at its faster clock rate.""" -// | +//| high_speed: bool +//| """Whether the card is running at its faster clock rate.""" +//| static mp_obj_t emmcio_emmc_get_high_speed(mp_obj_t self_in) { emmcio_emmc_obj_t *self = MP_OBJ_TO_PTR(self_in); check_for_deinit(self); @@ -421,9 +445,9 @@ static mp_obj_t emmcio_emmc_get_high_speed(mp_obj_t self_in) { static MP_DEFINE_CONST_FUN_OBJ_1(emmcio_emmc_get_high_speed_obj, emmcio_emmc_get_high_speed); MP_PROPERTY_GETTER(emmcio_emmc_high_speed_obj, (mp_obj_t)&emmcio_emmc_get_high_speed_obj); -// | frequency: int -// | """The bus clock rate in Hz.""" -// | +//| frequency: int +//| """The bus clock rate in Hz.""" +//| static mp_obj_t emmcio_emmc_get_frequency(mp_obj_t self_in) { emmcio_emmc_obj_t *self = MP_OBJ_TO_PTR(self_in); check_for_deinit(self); diff --git a/shared-bindings/emmcio/EMMC.h b/shared-bindings/emmcio/EMMC.h index af04c1d1fc5..1adfdcafef2 100644 --- a/shared-bindings/emmcio/EMMC.h +++ b/shared-bindings/emmcio/EMMC.h @@ -12,6 +12,19 @@ extern const mp_obj_type_t emmcio_emmc_type; +// Why bring-up failed. The binding turns these into exceptions; the automount +// path just gives up. +typedef enum { + EMMCIO_OK = 0, + EMMCIO_ERR_PIN_PORT, // detail: qstr of the pin the port cannot drive + EMMCIO_ERR_SPI_IN_USE, + EMMCIO_ERR_PIN_IN_USE, + EMMCIO_ERR_USB_OWNED, + EMMCIO_ERR_IN_USE, + EMMCIO_ERR_INIT, // detail: the MMC command that did not answer + EMMCIO_ERR_HIGH_SPEED, // detail: the switch step it stopped at +} emmcio_construct_result_t; + // ---- native block-device protocol ----------------------------------------- // // The same shape sdcardio and sdioio present, so extmod/vfs_blockdev.c can diff --git a/shared-bindings/emmcio/__init__.c b/shared-bindings/emmcio/__init__.c index f2e75fb4289..a43e08f3135 100644 --- a/shared-bindings/emmcio/__init__.c +++ b/shared-bindings/emmcio/__init__.c @@ -10,24 +10,24 @@ #include "shared-bindings/emmcio/EMMC.h" #include "shared-module/emmcio/__init__.h" -// | """Block device access to the on-board eMMC -// | -// | The `emmcio` module exposes an eMMC chip as a block device. -// | It provides no filesystem of its own: to read files, hand an `EMMC` -// | object to `storage.VfsFat` and mount it. -// | -// | .. note:: This module is only available on boards with an eMMC wired to the -// | dedicated SPI peripheral, and only one `EMMC` object may exist at a time. -// | """ -// | +//| """Block device access to the on-board eMMC +//| +//| The `emmcio` module exposes an eMMC chip as a block device. +//| It provides no filesystem of its own: to read files, hand an `EMMC` +//| object to `storage.VfsFat` and mount it. +//| +//| .. note:: This module is only available on boards with an eMMC wired to the +//| dedicated SPI peripheral, and only one `EMMC` object may exist at a time. +//| """ +//| -// | def automounted() -> bool: -// | """`True` when the eMMC has been mounted as a filesystem for you at -// | startup, `False` when it is free for Python to open. -// | -// | While this is `True`, constructing `EMMC` raises a `ValueError`.""" -// | ... -// | +//| def automounted() -> bool: +//| """`True` when the eMMC has been mounted as a filesystem for you at +//| startup, `False` when it is free for Python to open. +//| +//| While this is `True`, constructing `EMMC` raises a `ValueError`.""" +//| ... +//| static mp_obj_t emmcio_automounted(void) { return mp_obj_new_bool(emmcio_is_automounted()); } From 3aff2816f52c74a10fd7cdc347469254c622a7d5 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Wed, 26 Aug 2026 15:01:45 -0500 Subject: [PATCH 5/7] remove emmc automount crumb --- shared-module/emmcio/__init__.c | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/shared-module/emmcio/__init__.c b/shared-module/emmcio/__init__.c index b1db429df72..075451a6ebc 100644 --- a/shared-module/emmcio/__init__.c +++ b/shared-module/emmcio/__init__.c @@ -43,21 +43,10 @@ static bool _tried; #define AUTOMOUNT_BUDGET_US 5000000u -// One word of RAM that survives a reset but not a power cycle. If it is still -// set when we get here, the previous boot faulted. Skip the card for this -// boot so the board enumerates, and clear the crumb so the next boot tries again. -#define AUTOMOUNT_CRUMB_MAGIC 0x454d4d43u // 'EMMC' - -static struct { - uint32_t magic; - uint32_t in_progress; -} _crumb __attribute__((section(".uninitialized"))); - static void automount_give_up(void) { common_hal_emmcio_emmc_clear_deadline(); // Leave the card powered down and the pins released emmcio_automount_abandon(); - _crumb.in_progress = 0; } void automount_emmc(void) { @@ -76,13 +65,6 @@ void automount_emmc(void) { return; } - if (_crumb.magic == AUTOMOUNT_CRUMB_MAGIC && _crumb.in_progress != 0) { - _crumb.in_progress = 0; - return; - } - _crumb.magic = AUTOMOUNT_CRUMB_MAGIC; - _crumb.in_progress = 1; - common_hal_emmcio_emmc_set_deadline(AUTOMOUNT_BUDGET_US); mp_obj_t dev = emmcio_automount_construct(DEFAULT_EMMC_CLOCK, DEFAULT_EMMC_COMMAND, @@ -117,7 +99,6 @@ void automount_emmc(void) { // The budget covers bring-up and the mount only common_hal_emmcio_emmc_clear_deadline(); - _crumb.in_progress = 0; } #endif // CIRCUITPY_EMMC_USB From 61a822a92f7a9a12fceb6cd6aad2ac0101d7bcee Mon Sep 17 00:00:00 2001 From: foamyguy Date: Thu, 27 Aug 2026 15:27:56 -0500 Subject: [PATCH 6/7] remove automount() getter, use macro and named constants in init_failure_command() --- ports/nordic/common-hal/emmcio/EMMC.c | 34 ++++++++++----------------- shared-bindings/emmcio/__init__.c | 16 ------------- 2 files changed, 13 insertions(+), 37 deletions(-) diff --git a/ports/nordic/common-hal/emmcio/EMMC.c b/ports/nordic/common-hal/emmcio/EMMC.c index d6bf9828f64..6fc401e3a44 100644 --- a/ports/nordic/common-hal/emmcio/EMMC.c +++ b/ports/nordic/common-hal/emmcio/EMMC.c @@ -30,6 +30,7 @@ #include #include "extmod/vfs.h" // MP_BLOCKDEV_IOCTL_* +#include "lib/sdmmc/include/sdmmc_defs.h" // MMC_* command numbers #include "py/mphal.h" #include "py/runtime.h" // RUN_BACKGROUND_TASKS #include "shared-bindings/microcontroller/__init__.h" @@ -893,30 +894,21 @@ static emmcio_construct_result_t emmc_check_pins(const mcu_pin_obj_t *clock, return EMMCIO_OK; } -// The MMC command that never answered, so a failure names the step it stopped -// at. CMD8 covers the extended CSD. +#define RETURN_CMD_UNLESS(cmd, done) do { if (!(done)) { return cmd; } } while (0) + +// The MMC command that never answered, so a failure names the step it stopped at. static int init_failure_command(emmcio_emmc_obj_t *self) { - if (!self->cmd0_sent) { - return 0; - } - if (self->cmd1_retries < 0) { - return 1; // card never left busy - } - if (!self->cmd2_resp) { - return 2; // no CID - } - if (!self->cmd3_resp) { - return 3; - } - if (!self->cmd7_resp) { - return 7; // select - } - if (!self->cmd16_resp) { - return 16; // blocklen - } - return 8; // extended CSD + RETURN_CMD_UNLESS(MMC_GO_IDLE_STATE, self->cmd0_sent); + RETURN_CMD_UNLESS(MMC_SEND_OP_COND, self->cmd1_retries >= 0); // never left busy + RETURN_CMD_UNLESS(MMC_ALL_SEND_CID, self->cmd2_resp); + RETURN_CMD_UNLESS(MMC_SET_RELATIVE_ADDR, self->cmd3_resp); + RETURN_CMD_UNLESS(MMC_SELECT_CARD, self->cmd7_resp); + RETURN_CMD_UNLESS(MMC_SET_BLOCKLEN, self->cmd16_resp); + return MMC_SEND_EXT_CSD; } +#undef RETURN_CMD_UNLESS + // How far the high-speed switch got: hs_stage as documented on the struct, // except that a CMD13 SWITCH_ERROR (the card rejecting HS_TIMING) reports 7 to // tell it apart from the card simply never coming back to tran. diff --git a/shared-bindings/emmcio/__init__.c b/shared-bindings/emmcio/__init__.c index a43e08f3135..2caf1f8aeb3 100644 --- a/shared-bindings/emmcio/__init__.c +++ b/shared-bindings/emmcio/__init__.c @@ -8,7 +8,6 @@ #include "py/runtime.h" #include "shared-bindings/emmcio/EMMC.h" -#include "shared-module/emmcio/__init__.h" //| """Block device access to the on-board eMMC //| @@ -16,27 +15,12 @@ //| It provides no filesystem of its own: to read files, hand an `EMMC` //| object to `storage.VfsFat` and mount it. //| -//| .. note:: This module is only available on boards with an eMMC wired to the -//| dedicated SPI peripheral, and only one `EMMC` object may exist at a time. //| """ //| -//| def automounted() -> bool: -//| """`True` when the eMMC has been mounted as a filesystem for you at -//| startup, `False` when it is free for Python to open. -//| -//| While this is `True`, constructing `EMMC` raises a `ValueError`.""" -//| ... -//| -static mp_obj_t emmcio_automounted(void) { - return mp_obj_new_bool(emmcio_is_automounted()); -} -static MP_DEFINE_CONST_FUN_OBJ_0(emmcio_automounted_obj, emmcio_automounted); - static const mp_rom_map_elem_t emmcio_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_emmcio) }, { MP_ROM_QSTR(MP_QSTR_EMMC), MP_ROM_PTR(&emmcio_emmc_type) }, - { MP_ROM_QSTR(MP_QSTR_automounted), MP_ROM_PTR(&emmcio_automounted_obj) }, }; static MP_DEFINE_CONST_DICT(emmcio_module_globals, emmcio_module_globals_table); From 61392e955ffb8338a435fe305f0e6043bb1bd250 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Thu, 27 Aug 2026 15:30:23 -0500 Subject: [PATCH 7/7] merge main, make translations --- locale/circuitpython.pot | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index c7275f7aee3..3e7acbf9dba 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -990,7 +990,7 @@ msgstr "" #: ports/atmel-samd/common-hal/microcontroller/Pin.c #: ports/espressif/common-hal/dotclockframebuffer/DotClockFramebuffer.c #: ports/mimxrt10xx/common-hal/microcontroller/Pin.c -#: shared-bindings/microcontroller/Pin.c +#: shared-bindings/emmcio/EMMC.c shared-bindings/microcontroller/Pin.c msgid "Invalid %q pin" msgstr "" @@ -1032,7 +1032,8 @@ msgid "Another send is already active" msgstr "" #: ports/atmel-samd/common-hal/sdioio/SDCard.c -#: ports/espressif/common-hal/qspibus/QSPIBus.c +#: ports/espressif/common-hal/qspibus/QSPIBus.c shared-bindings/emmcio/EMMC.c +#: shared-bindings/socketpool/SocketPool.c msgid "%q failure: %d" msgstr "" @@ -1040,8 +1041,9 @@ msgstr "" #: ports/cxd56/common-hal/sdioio/SDCard.c #: ports/espressif/common-hal/sdioio/SDCard.c #: ports/raspberrypi/common-hal/sdioio/SDCard.c -#: ports/stm/common-hal/sdioio/SDCard.c shared-bindings/floppyio/__init__.c -#: shared-bindings/picogame/Canvas.c shared-module/sdcardio/SDCard.c +#: ports/stm/common-hal/sdioio/SDCard.c shared-bindings/emmcio/EMMC.c +#: shared-bindings/floppyio/__init__.c shared-bindings/picogame/Canvas.c +#: shared-module/sdcardio/SDCard.c #, c-format msgid "Buffer must be a multiple of %d bytes" msgstr "" @@ -1153,6 +1155,7 @@ msgstr "" #: ports/espressif/common-hal/_bleio/Adapter.c #: ports/nordic/common-hal/_bleio/Adapter.c +#: ports/zephyr-cp/common-hal/_bleio/Adapter.c msgid "Data too large for advertisement packet" msgstr "" @@ -1325,7 +1328,7 @@ msgid "%q must be 8, 16, 24, or 32" msgstr "" #: ports/espressif/common-hal/audiobusio/__init__.c -#: ports/espressif/common-hal/audioi2sin/I2SIn.c +#: ports/espressif/common-hal/audioi2sin/I2SIn.c shared-bindings/emmcio/EMMC.c msgid "Peripheral in use" msgstr "" @@ -1477,6 +1480,7 @@ msgstr "" #: ports/espressif/common-hal/espidf/__init__.c #: ports/raspberrypi/common-hal/picogame/Display.c +#: shared-bindings/picogame/__init__.c msgid "Operation or feature not supported" msgstr "" @@ -1519,7 +1523,7 @@ msgstr "" #: ports/raspberrypi/common-hal/picodvi/Framebuffer_RP2350.c #: ports/raspberrypi/common-hal/rp2pio/StateMachine.c #: ports/raspberrypi/common-hal/usb_host/Port.c -#: shared-bindings/digitalio/DigitalInOut.c +#: shared-bindings/digitalio/DigitalInOut.c shared-bindings/emmcio/EMMC.c #: shared-bindings/i2cioexpander/IOPin.c shared-bindings/microcontroller/Pin.c #: shared-module/max3421e/Max3421E.c msgid "%q in use" @@ -1680,6 +1684,7 @@ msgstr "" #: ports/espressif/common-hal/wifi/__init__.c #: ports/raspberrypi/common-hal/wifi/__init__.c +#: ports/zephyr-cp/common-hal/wifi/Radio.c msgid "Only IPv4 addresses supported" msgstr "" @@ -1692,6 +1697,7 @@ msgstr "" #: ports/mimxrt10xx/common-hal/busio/UART.c ports/stm/common-hal/busio/I2C.c #: ports/stm/common-hal/busio/SPI.c ports/stm/common-hal/busio/UART.c #: ports/stm/common-hal/canio/CAN.c ports/stm/common-hal/sdioio/SDCard.c +#: shared-bindings/emmcio/EMMC.c msgid "Hardware in use, try alternative pins" msgstr "" @@ -1818,7 +1824,7 @@ msgstr "" #: ports/raspberrypi/bindings/cyw43/__init__.c py/argcheck.c py/objexcept.c #: shared-bindings/bitmapfilter/__init__.c shared-bindings/canio/CAN.c #: shared-bindings/digitalio/Pull.c shared-bindings/picogame/Sprite.c -#: shared-bindings/supervisor/__init__.c shared-module/audiofilters/Filter.c +#: shared-bindings/supervisor/__init__.c shared-module/audiofilters/__init__.c #: shared-module/displayio/__init__.c shared-module/synthio/Synthesizer.c msgid "%q must be of type %q or %q, not %q" msgstr "" @@ -1903,7 +1909,7 @@ msgstr "" msgid "I2C peripheral in use" msgstr "" -#: ports/raspberrypi/common-hal/busio/SPI.c +#: ports/raspberrypi/common-hal/busio/SPI.c shared-bindings/emmcio/EMMC.c msgid "SPI peripheral in use" msgstr "" @@ -2355,7 +2361,7 @@ msgstr "" msgid "%q length must be %d" msgstr "" -#: py/argcheck.c shared-module/audiofilters/Filter.c +#: py/argcheck.c shared-module/audiofilters/__init__.c msgid "%q in %q must be of type %q, not %q" msgstr "" @@ -3617,7 +3623,7 @@ msgstr "" msgid "not a 128-bit UUID" msgstr "" -#: shared-bindings/_bleio/__init__.c +#: shared-bindings/_bleio/__init__.c shared-bindings/emmcio/EMMC.c #: shared-bindings/memorymonitor/AllocationSize.c #: shared-bindings/pulseio/PulseIn.c shared-module/bitmaptools/__init__.c #: shared-module/displayio/Bitmap.c shared-module/displayio/Group.c @@ -3777,7 +3783,7 @@ msgstr "" msgid "invalid destination buffer, must be an array of type: %c" msgstr "" -#: shared-bindings/audioio/AudioOut.c +#: shared-bindings/audioio/AudioOut.c shared-bindings/emmcio/EMMC.c msgid "%q and %q must be different" msgstr "" @@ -4000,6 +4006,10 @@ msgstr "" msgid "offset must be >= 0" msgstr "" +#: shared-bindings/emmcio/EMMC.c shared-bindings/i2cioexpander/IOExpander.c +msgid "address out of range" +msgstr "" + #: shared-bindings/epaperdisplay/EPaperDisplay.c msgid "Refresh too soon" msgstr "" @@ -4016,10 +4026,6 @@ msgstr "" msgid "Unsupported hash algorithm" msgstr "" -#: shared-bindings/i2cioexpander/IOExpander.c -msgid "address out of range" -msgstr "" - #: shared-bindings/i2cioexpander/IOExpander.c msgid "num_pins must be 8 or 16" msgstr ""