diff --git a/ports/esp32/Makefile b/ports/esp32/Makefile index ea90c9f3f5..98ad196c1d 100644 --- a/ports/esp32/Makefile +++ b/ports/esp32/Makefile @@ -111,6 +111,7 @@ INC_ESPCOMP += -I$(ESPCOMP)/app_trace/include INC_ESPCOMP += -I$(ESPCOMP)/app_update/include INC_ESPCOMP += -I$(ESPCOMP)/pthread/include INC_ESPCOMP += -I$(ESPCOMP)/smartconfig_ack/include +INC_ESPCOMP += -I$(ESPCOMP)/sdmmc/include # these flags are common to C and C++ compilation CFLAGS_COMMON = -Os -ffunction-sections -fdata-sections -fstrict-volatile-bitfields \ @@ -192,6 +193,7 @@ SRC_C = \ machine_wdt.c \ mpthreadport.c \ machine_rtc.c \ + machine_sdcard.c \ $(SRC_MOD) EXTMOD_SRC_C = $(addprefix extmod/,\ @@ -257,6 +259,11 @@ ESPIDF_DRIVER_O = $(addprefix $(ESPCOMP)/driver/,\ ledc.o \ gpio.o \ timer.o \ + sdmmc_host.o \ + sdmmc_transaction.o \ + sdspi_crc.o \ + sdspi_host.o \ + sdspi_transaction.o \ spi_master.o \ spi_common.o \ rtc_module.o \ @@ -329,6 +336,7 @@ ESPIDF_SOC_O = $(addprefix $(ESPCOMP)/soc/,\ esp32/rtc_sleep.o \ esp32/rtc_time.o \ esp32/rtc_wdt.o \ + esp32/sdmmc_periph.o \ esp32/soc_memory_layout.o \ esp32/spi_periph.o \ src/memory_layout_utils.o \ @@ -665,6 +673,15 @@ ESPIDF_WPA_SUPPLICANT_O = $(addprefix $(ESPCOMP)/wpa_supplicant/,\ port/os_xtensa.o \ ) +ESPIDF_SDMMC_O = $(addprefix $(ESPCOMP)/sdmmc/,\ + sdmmc_cmd.o \ + sdmmc_common.o \ + sdmmc_init.o \ + sdmmc_mmc.o \ + sdmmc_sd.o \ + sdmmc_io.o \ + ) + OBJ_ESPIDF = OBJ_ESPIDF += $(addprefix $(BUILD)/, $(ESPIDF_NEWLIB_O)) OBJ_ESPIDF += $(addprefix $(BUILD)/, $(ESPIDF_DRIVER_O)) @@ -693,6 +710,7 @@ OBJ_ESPIDF += $(addprefix $(BUILD)/, $(ESPIDF_SMARTCONFIG_ACK_O)) OBJ_ESPIDF += $(addprefix $(BUILD)/, $(ESPIDF_SPI_FLASH_O)) OBJ_ESPIDF += $(addprefix $(BUILD)/, $(ESPIDF_ULP_O)) OBJ_ESPIDF += $(addprefix $(BUILD)/, $(ESPIDF_WPA_SUPPLICANT_O)) +OBJ_ESPIDF += $(addprefix $(BUILD)/, $(ESPIDF_SDMMC_O)) $(OBJ_ESPIDF): $(SDKCONFIG_H) @@ -723,6 +741,7 @@ LIB_ESPIDF += ulp LIB_ESPIDF += lwip LIB_ESPIDF += mbedtls LIB_ESPIDF += wpa_supplicant +LIB_ESPIDF += sdmmc BUILD_ESPIDF_LIB = $(BUILD)/esp-idf @@ -763,6 +782,7 @@ $(eval $(call gen_espidf_lib_rule,ulp,$(ESPIDF_ULP_O))) $(eval $(call gen_espidf_lib_rule,lwip,$(ESPIDF_LWIP_O))) $(eval $(call gen_espidf_lib_rule,mbedtls,$(ESPIDF_MBEDTLS_O))) $(eval $(call gen_espidf_lib_rule,wpa_supplicant,$(ESPIDF_WPA_SUPPLICANT_O))) +$(eval $(call gen_espidf_lib_rule,sdmmc,$(ESPIDF_SDMMC_O))) LIB = $(foreach lib,$(LIB_ESPIDF),$(BUILD_ESPIDF_LIB)/$(lib)/lib$(lib).a) diff --git a/ports/esp32/machine_sdcard.c b/ports/esp32/machine_sdcard.c new file mode 100644 index 0000000000..633d4031d6 --- /dev/null +++ b/ports/esp32/machine_sdcard.c @@ -0,0 +1,391 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Nicko van Someren + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include "py/runtime.h" +#include "py/mphal.h" +#include "py/mperrno.h" +#include "extmod/vfs_fat.h" + +#include "driver/sdmmc_host.h" +#include "driver/sdspi_host.h" +#include "sdmmc_cmd.h" +#include "esp_log.h" + +#define DEBUG 0 +#if DEBUG +#define DEBUG_printf(...) ESP_LOGI("modsdcard", __VA_ARGS__) +#else +#define DEBUG_printf(...) (void)0 +#endif + +// +// There are three layers of abstraction: host, slot and card. +// Creating an SD Card object will initialise the host and slot. +// Cards gets initialised by ioctl op==1 and de-inited by ioctl 2 +// Hosts are de-inited in __del__. Slots do not need de-initing. +// + +// Currently the ESP32 Library doesn't support MMC cards, so +// we don't enable on MICROPY_HW_ENABLE_MMCARD. +#if MICROPY_HW_ENABLE_SDCARD + +// Forward declaration +const mp_obj_type_t machine_sdcard_type; + +typedef struct _sdcard_obj_t { + mp_obj_base_t base; + mp_int_t flags; + sdmmc_host_t host; + // The card structure duplicates the host. It's not clear if we + // can avoid this given the way that it is copied. + sdmmc_card_t card; +} sdcard_card_obj_t; + +#define SDCARD_CARD_FLAGS_HOST_INIT_DONE 0x01 +#define SDCARD_CARD_FLAGS_CARD_INIT_DONE 0x02 + +#define _SECTOR_SIZE(self) (self->card.csd.sector_size) + +STATIC esp_err_t check_esp_err(esp_err_t code) { + switch(code) { + case ESP_OK: + return ESP_OK; + case ESP_ERR_NO_MEM: + code = MP_ENOMEM; + break; + case ESP_ERR_TIMEOUT: + code = MP_ETIMEDOUT; + break; + case ESP_ERR_NOT_SUPPORTED: + code = MP_EOPNOTSUPP; + break; + } + + mp_raise_OSError(code); +} + +STATIC gpio_num_t pin_or_int(const mp_obj_t arg) { + if (mp_obj_is_small_int(arg)) { + return MP_OBJ_SMALL_INT_VALUE(arg); + } else { + // This raises a value error if the argument is not a Pin. + return machine_pin_get_id(arg); + } +} + +#define SET_CONFIG_PIN(config, pin_var, arg_id) \ + if (arg_vals[arg_id].u_obj != mp_const_none) \ + config.pin_var = pin_or_int(arg_vals[arg_id].u_obj) + +STATIC esp_err_t sdcard_ensure_card_init(sdcard_card_obj_t *self, bool force) { + if (force || !(self->flags & SDCARD_CARD_FLAGS_CARD_INIT_DONE)) { + DEBUG_printf(" Calling card init"); + + esp_err_t err = sdmmc_card_init(&(self->host), &(self->card)); + if (err == ESP_OK) { + self->flags |= SDCARD_CARD_FLAGS_CARD_INIT_DONE; + } else { + self->flags &= ~SDCARD_CARD_FLAGS_CARD_INIT_DONE; + } + DEBUG_printf(" Card init returned: %i", err); + + return err; + } else { + return ESP_OK; + } +} + +/******************************************************************************/ +// MicroPython bindings +// +// Expose the SD card or MMC as an object with the block protocol. + +// Create a new SDCard object +// The driver supports either the host SD/MMC controller (default) or SPI mode +// In both cases there are two "slots". Slot 0 on the SD/MMC controller is +// typically tied up with the flash interface in most ESP32 modules but in +// theory supports 1, 4 or 8-bit transfers. Slot 1 supports only 1 and 4-bit +// transfers. Only 1-bit is supported on the SPI interfaces. +// card = SDCard(slot=1, width=None, present_pin=None, wp_pin=None) + +STATIC mp_obj_t machine_sdcard_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + // check arguments + enum { + ARG_slot, + ARG_width, + ARG_cd, + ARG_wp, + ARG_miso, + ARG_mosi, + ARG_sck, + ARG_cs, + }; + STATIC const mp_arg_t allowed_args[] = { + { MP_QSTR_slot, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1} }, + { MP_QSTR_width, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1} }, + { MP_QSTR_cd, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_wp, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + // These are only needed if using SPI mode + { MP_QSTR_miso, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_mosi, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_sck, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + { MP_QSTR_cs, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + }; + mp_arg_val_t arg_vals[MP_ARRAY_SIZE(allowed_args)]; + mp_map_t kw_args; + + DEBUG_printf("Making new SDCard:n"); + DEBUG_printf(" Unpacking arguments"); + + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + + mp_arg_parse_all(n_args, args, &kw_args, + MP_ARRAY_SIZE(allowed_args), allowed_args, arg_vals); + + DEBUG_printf(" slot=%d, width=%d, cd=%p, wp=%p", + arg_vals[ARG_slot].u_int, arg_vals[ARG_width].u_int, + arg_vals[ARG_cd].u_obj, arg_vals[ARG_wp].u_obj); + + DEBUG_printf(" miso=%p, mosi=%p, sck=%p, cs=%p", + arg_vals[ARG_miso].u_obj, arg_vals[ARG_mosi].u_obj, + arg_vals[ARG_sck].u_obj, arg_vals[ARG_cs].u_obj); + + int slot_num = arg_vals[ARG_slot].u_int; + if (slot_num < 0 || slot_num > 3) { + mp_raise_ValueError("Slot number must be between 0 and 3 inclusive"); + } + + // Slots 0 and 1 are native SD/MMC, slots 2 and 3 are SPI + bool is_spi = (slot_num >= 2); + if (is_spi) { + slot_num -= 2; + } + + DEBUG_printf(" Setting up host configuration"); + + sdcard_card_obj_t *self = m_new_obj_with_finaliser(sdcard_card_obj_t); + self->base.type = &machine_sdcard_type; + self->flags = 0; + // Note that these defaults are macros that expand to structure + // constants so we can't directly assign them to fields. + if (is_spi) { + sdmmc_host_t _temp_host = SDSPI_HOST_DEFAULT(); + self->host = _temp_host; + } else { + sdmmc_host_t _temp_host = SDMMC_HOST_DEFAULT(); + self->host = _temp_host; + } + + if (is_spi) { + self->host.slot = slot_num ? HSPI_HOST : VSPI_HOST; + slot_num -= 2; + } + + DEBUG_printf(" Calling host.init()"); + + check_esp_err(self->host.init()); + self->flags |= SDCARD_CARD_FLAGS_HOST_INIT_DONE; + + if (is_spi) { + // SPI interface + STATIC const sdspi_slot_config_t slot_defaults[2] = { + { + .gpio_miso = GPIO_NUM_19, + .gpio_mosi = GPIO_NUM_23, + .gpio_sck = GPIO_NUM_18, + .gpio_cs = GPIO_NUM_5, + .gpio_cd = SDSPI_SLOT_NO_CD, + .gpio_wp = SDSPI_SLOT_NO_WP, + .dma_channel = 2 + }, + SDSPI_SLOT_CONFIG_DEFAULT() }; + + DEBUG_printf(" Setting up SPI slot configuration"); + sdspi_slot_config_t slot_config = slot_defaults[slot_num]; + + SET_CONFIG_PIN(slot_config, gpio_cd, ARG_cd); + SET_CONFIG_PIN(slot_config, gpio_wp, ARG_wp); + SET_CONFIG_PIN(slot_config, gpio_miso, ARG_miso); + SET_CONFIG_PIN(slot_config, gpio_mosi, ARG_mosi); + SET_CONFIG_PIN(slot_config, gpio_sck, ARG_sck); + SET_CONFIG_PIN(slot_config, gpio_cs, ARG_cs); + + DEBUG_printf(" Calling init_slot()"); + check_esp_err(sdspi_host_init_slot(self->host.slot, &slot_config)); + } else { + // SD/MMC interface + DEBUG_printf(" Setting up SDMMC slot configuration"); + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + + // Stronger external pull-ups are still needed but apparently + // it is a good idea to set the internal pull-ups anyway. + // slot_config.flags = SDMMC_SLOT_FLAG_INTERNAL_PULLUP; + + SET_CONFIG_PIN(slot_config, gpio_cd, ARG_cd); + SET_CONFIG_PIN(slot_config, gpio_wp, ARG_wp); + + int width = arg_vals[ARG_width].u_int; + if (width == 1 || width == 4 || (width == 8 && slot_num == 0)) { + slot_config.width = width; + } else { + mp_raise_ValueError("Width must be 1 or 4 (or 8 on slot 0)"); + } + + DEBUG_printf(" Calling init_slot()"); + check_esp_err(sdmmc_host_init_slot(self->host.slot, &slot_config)); + } + + DEBUG_printf(" Returning new card object: %p", self); + return MP_OBJ_FROM_PTR(self); +} + +STATIC mp_obj_t sd_deinit(mp_obj_t self_in) { + sdcard_card_obj_t *self = self_in; + + DEBUG_printf("De-init host\n"); + + if (self->flags & SDCARD_CARD_FLAGS_HOST_INIT_DONE) { + self->host.deinit(); + self->flags &= ~SDCARD_CARD_FLAGS_HOST_INIT_DONE; + } + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(sd_deinit_obj, sd_deinit); + +STATIC mp_obj_t sd_info(mp_obj_t self_in) { + sdcard_card_obj_t *self = self_in; + // We could potential return a great deal more SD card data but it + // is not clear that it is worth the extra code space to do + // so. For the most part people only care about the card size and + // block size. + + check_esp_err(sdcard_ensure_card_init((sdcard_card_obj_t *) self, false)); + + uint32_t log_block_nbr = self->card.csd.capacity; + uint32_t log_block_size = _SECTOR_SIZE(self); + + mp_obj_t tuple[2] = { + mp_obj_new_int_from_ull((uint64_t)log_block_nbr * (uint64_t)log_block_size), + mp_obj_new_int_from_uint(log_block_size), + }; + return mp_obj_new_tuple(2, tuple); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(sd_info_obj, sd_info); + +STATIC mp_obj_t machine_sdcard_readblocks(mp_obj_t self_in, mp_obj_t block_num, mp_obj_t buf) { + sdcard_card_obj_t *self = self_in; + mp_buffer_info_t bufinfo; + esp_err_t err; + + err = sdcard_ensure_card_init((sdcard_card_obj_t *) self, false); + if (err != ESP_OK) { + return false; + } + + mp_get_buffer_raise(buf, &bufinfo, MP_BUFFER_WRITE); + err = sdmmc_read_sectors(&(self->card), bufinfo.buf, mp_obj_get_int(block_num), bufinfo.len / _SECTOR_SIZE(self) ); + + return mp_obj_new_bool(err == ESP_OK); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(machine_sdcard_readblocks_obj, machine_sdcard_readblocks); + +STATIC mp_obj_t machine_sdcard_writeblocks(mp_obj_t self_in, mp_obj_t block_num, mp_obj_t buf) { + sdcard_card_obj_t *self = self_in; + mp_buffer_info_t bufinfo; + esp_err_t err; + + err = sdcard_ensure_card_init((sdcard_card_obj_t *) self, false); + if (err != ESP_OK) { + return false; + } + + mp_get_buffer_raise(buf, &bufinfo, MP_BUFFER_READ); + err = sdmmc_write_sectors(&(self->card), bufinfo.buf, mp_obj_get_int(block_num), bufinfo.len / _SECTOR_SIZE(self) ); + + return mp_obj_new_bool(err == ESP_OK); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(machine_sdcard_writeblocks_obj, machine_sdcard_writeblocks); + +STATIC mp_obj_t machine_sdcard_ioctl(mp_obj_t self_in, mp_obj_t cmd_in, mp_obj_t arg_in) { + sdcard_card_obj_t *self = self_in; + esp_err_t err = ESP_OK; + mp_int_t cmd = mp_obj_get_int(cmd_in); + + switch (cmd) { + case BP_IOCTL_INIT: + err = sdcard_ensure_card_init(self, false); + return MP_OBJ_NEW_SMALL_INT((err == ESP_OK) ? 0 : -1); + + case BP_IOCTL_DEINIT: + // Ensure that future attempts to look at info re-read the card + self->flags &= ~SDCARD_CARD_FLAGS_CARD_INIT_DONE; + return MP_OBJ_NEW_SMALL_INT(0); // success + + case BP_IOCTL_SYNC: + // nothing to do + return MP_OBJ_NEW_SMALL_INT(0); // success + + case BP_IOCTL_SEC_COUNT: + err = sdcard_ensure_card_init(self, false); + if (err != ESP_OK) + return MP_OBJ_NEW_SMALL_INT(-1); + return MP_OBJ_NEW_SMALL_INT(self->card.csd.capacity); + + case BP_IOCTL_SEC_SIZE: + err = sdcard_ensure_card_init(self, false); + if (err != ESP_OK) + return MP_OBJ_NEW_SMALL_INT(-1); + return MP_OBJ_NEW_SMALL_INT(_SECTOR_SIZE(self)); + + default: // unknown command + return MP_OBJ_NEW_SMALL_INT(-1); // error + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(machine_sdcard_ioctl_obj, machine_sdcard_ioctl); + +STATIC const mp_rom_map_elem_t machine_sdcard_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_info), MP_ROM_PTR(&sd_info_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&sd_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&sd_deinit_obj) }, + // block device protocol + { MP_ROM_QSTR(MP_QSTR_readblocks), MP_ROM_PTR(&machine_sdcard_readblocks_obj) }, + { MP_ROM_QSTR(MP_QSTR_writeblocks), MP_ROM_PTR(&machine_sdcard_writeblocks_obj) }, + { MP_ROM_QSTR(MP_QSTR_ioctl), MP_ROM_PTR(&machine_sdcard_ioctl_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(machine_sdcard_locals_dict, machine_sdcard_locals_dict_table); + +const mp_obj_type_t machine_sdcard_type = { + { &mp_type_type }, + .name = MP_QSTR_SDCard, + .make_new = machine_sdcard_make_new, + .locals_dict = (mp_obj_dict_t*)&machine_sdcard_locals_dict, +}; + +#endif // MICROPY_HW_ENABLE_SDCARD diff --git a/ports/esp32/modmachine.c b/ports/esp32/modmachine.c index bc459ee5c2..fb864947d2 100644 --- a/ports/esp32/modmachine.c +++ b/ports/esp32/modmachine.c @@ -235,6 +235,9 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_Timer), MP_ROM_PTR(&machine_timer_type) }, { MP_ROM_QSTR(MP_QSTR_WDT), MP_ROM_PTR(&machine_wdt_type) }, + #if MICROPY_HW_ENABLE_SDCARD + { MP_ROM_QSTR(MP_QSTR_SDCard), MP_ROM_PTR(&machine_sdcard_type) }, + #endif // wake abilities { MP_ROM_QSTR(MP_QSTR_SLEEP), MP_ROM_INT(MACHINE_WAKE_SLEEP) }, diff --git a/ports/esp32/modmachine.h b/ports/esp32/modmachine.h index b65b427b4e..ca1776d8bf 100644 --- a/ports/esp32/modmachine.h +++ b/ports/esp32/modmachine.h @@ -19,6 +19,7 @@ extern const mp_obj_type_t machine_pwm_type; extern const mp_obj_type_t machine_hw_spi_type; extern const mp_obj_type_t machine_uart_type; extern const mp_obj_type_t machine_rtc_type; +extern const mp_obj_type_t machine_sdcard_type; void machine_pins_init(void); void machine_pins_deinit(void); diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index 7b9b400250..bd19c6bfbd 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -141,6 +141,7 @@ #define MICROPY_PY_MACHINE_SPI_MSB (0) #define MICROPY_PY_MACHINE_SPI_LSB (1) #define MICROPY_PY_MACHINE_SPI_MAKE_NEW machine_hw_spi_make_new +#define MICROPY_HW_ENABLE_SDCARD (1) #define MICROPY_HW_SOFTSPI_MIN_DELAY (0) #define MICROPY_HW_SOFTSPI_MAX_BAUDRATE (ets_get_cpu_frequency() * 1000000 / 200) // roughly #define MICROPY_PY_USSL (1)