SDMMC driver WIP

This commit is contained in:
Ken Van Hoeylandt 2026-03-02 23:44:46 +01:00
parent 57016baac8
commit 1ea2f97305
10 changed files with 460 additions and 13 deletions

View File

@ -3,6 +3,7 @@
#include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h>
#include <tactility/bindings/esp32_sdmmc.h>
#include <tactility/bindings/esp32_spi.h>
#include <tactility/bindings/esp32_uart.h>
@ -30,6 +31,18 @@
pin-sclk = <&gpio0 5 GPIO_FLAG_NONE>;
};
sdmmc0 {
compatible = "espressif,sdmmc";
pin-clk = <&gpio 12 GPIO_FLAG_NONE>;
pin-cmd = <&gpio 16 GPIO_FLAG_NONE>;
pin-d0 = <&gpio 14 GPIO_FLAG_NONE>;
pin-d1 = <&gpio 17 GPIO_FLAG_NONE>;
pin-d2 = <&gpio 21 GPIO_FLAG_NONE>;
pin-d3 = <&gpio 18 GPIO_FLAG_NONE>;
bus-width = <4>;
max-open-files = <4>;
};
stemma_qt: uart1 {
compatible = "espressif,esp32-uart";
port = <UART_NUM_1>;

View File

@ -6,5 +6,5 @@ idf_component_register(
SRCS ${SOURCES}
INCLUDE_DIRS "include/"
PRIV_INCLUDE_DIRS "private/"
REQUIRES TactilityKernel driver
REQUIRES TactilityKernel driver vfs fatfs
)

View File

@ -0,0 +1,57 @@
description: ESP32 SDMMC
compatible: "espressif,sdmmc"
properties:
pin-clk:
type: phandle-array
required: true
pin-cmd:
type: phandle-array
required: true
pin-d0:
type: phandle-array
required: true
pin-d1:
type: phandle-array
default: GPIO_PIN_SPEC_NONE
pin-d2:
type: phandle-array
default: GPIO_PIN_SPEC_NONE
pin-d3:
type: phandle-array
default: GPIO_PIN_SPEC_NONE
pin-d4:
type: phandle-array
default: GPIO_PIN_SPEC_NONE
pin-d5:
type: phandle-array
default: GPIO_PIN_SPEC_NONE
pin-d6:
type: phandle-array
default: GPIO_PIN_SPEC_NONE
pin-d7:
type: phandle-array
default: GPIO_PIN_SPEC_NONE
pin-cd:
type: phandle-array
default: GPIO_PIN_SPEC_NONE
pin-wp:
type: phandle-array
default: GPIO_PIN_SPEC_NONE
bus-width:
type: int
required: true
description: Bus width in bits
max-open-files:
type: int
required: true
description: Maximum number of open files
wp-active-high:
type: boolean
default: false
description: Whether the WP pin is active high
enable-uhs:
type: boolean
default: false
description: Enable UHS mode

View File

@ -0,0 +1,15 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/bindings/bindings.h>
#include <tactility/drivers/esp32_sdmmc.h>
#ifdef __cplusplus
extern "C" {
#endif
DEFINE_DEVICETREE(esp32_sdmmc, struct Esp32SdmmcConfig)
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,33 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/drivers/gpio.h>
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
struct Esp32SdmmcConfig {
struct GpioPinSpec pin_clk;
struct GpioPinSpec pin_cmd;
struct GpioPinSpec pin_d0;
struct GpioPinSpec pin_d1;
struct GpioPinSpec pin_d2;
struct GpioPinSpec pin_d3;
struct GpioPinSpec pin_d4;
struct GpioPinSpec pin_d5;
struct GpioPinSpec pin_d6;
struct GpioPinSpec pin_d7;
struct GpioPinSpec pin_cd;
struct GpioPinSpec pin_wp;
uint8_t bus_width;
uint8_t max_open_files;
bool wp_active_high;
bool enable_uhs;
};
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,157 @@
// SPDX-License-Identifier: Apache-2.0
#include <tactility/device.h>
#include <tactility/drivers/esp32_sdmmc.h>
#include <tactility/concurrent/recursive_mutex.h>
#include <tactility/log.h>
#include "tactility/drivers/gpio_descriptor.h"
#include <new>
#include <tactility/drivers/esp32_gpio_helpers.h>
#include <tactility/drivers/file_system.h>
#define TAG "esp32_sdmmc"
#define GET_CONFIG(device) ((const struct Esp32SdmmcConfig*)device->config)
#define GET_DATA(device) ((struct Esp32SdmmcInternal*)device_get_driver_data(device))
extern "C" {
error_t mount(Device* device) {
return ERROR_NONE;
}
error_t unmount(Device* device) {
return ERROR_NONE;
}
bool is_mounted(Device* device) {
return true;
}
error_t get_mount_path(Device*, char* out_path) {
return ERROR_NONE;
}
static const FileSystemApi sdmmc_filesystem_api = {
.mount = mount,
.unmount = unmount,
.is_mounted = is_mounted,
.get_mount_path = get_mount_path
};
struct Esp32SdmmcInternal {
RecursiveMutex mutex = {};
bool initialized = false;
// Pin descriptors
GpioDescriptor* pin_clk_descriptor = nullptr;
GpioDescriptor* pin_cmd_descriptor = nullptr;
GpioDescriptor* pin_d0_descriptor = nullptr;
GpioDescriptor* pin_d1_descriptor = nullptr;
GpioDescriptor* pin_d2_descriptor = nullptr;
GpioDescriptor* pin_d3_descriptor = nullptr;
GpioDescriptor* pin_d4_descriptor = nullptr;
GpioDescriptor* pin_d5_descriptor = nullptr;
GpioDescriptor* pin_d6_descriptor = nullptr;
GpioDescriptor* pin_d7_descriptor = nullptr;
GpioDescriptor* pin_cd_descriptor = nullptr;
GpioDescriptor* pin_wp_descriptor = nullptr;
explicit Esp32SdmmcInternal() {
recursive_mutex_construct(&mutex);
}
~Esp32SdmmcInternal() {
cleanup_pins();
recursive_mutex_destruct(&mutex);
}
void cleanup_pins() {
release_pin(&pin_clk_descriptor);
release_pin(&pin_cmd_descriptor);
release_pin(&pin_d0_descriptor);
release_pin(&pin_d1_descriptor);
release_pin(&pin_d2_descriptor);
release_pin(&pin_d3_descriptor);
release_pin(&pin_d4_descriptor);
release_pin(&pin_d5_descriptor);
release_pin(&pin_d6_descriptor);
release_pin(&pin_d7_descriptor);
release_pin(&pin_cd_descriptor);
release_pin(&pin_wp_descriptor);
}
void lock() { recursive_mutex_lock(&mutex); }
error_t try_lock(TickType_t timeout) { return recursive_mutex_try_lock(&mutex, timeout); }
void unlock() { recursive_mutex_unlock(&mutex); }
};
static error_t start(Device* device) {
LOG_I(TAG, "start %s", device->name);
auto* data = new (std::nothrow) Esp32SdmmcInternal();
if (!data) return ERROR_OUT_OF_MEMORY;
device_set_driver_data(device, data);
auto* sdmmc_config = GET_CONFIG(device);
// Acquire pins from the specified GPIO pin specs. Optional pins are allowed.
bool pins_ok =
acquire_pin_or_set_null(sdmmc_config->pin_clk, &data->pin_clk_descriptor) &&
acquire_pin_or_set_null(sdmmc_config->pin_cmd, &data->pin_cmd_descriptor) &&
acquire_pin_or_set_null(sdmmc_config->pin_d0, &data->pin_d0_descriptor) &&
acquire_pin_or_set_null(sdmmc_config->pin_d1, &data->pin_d1_descriptor) &&
acquire_pin_or_set_null(sdmmc_config->pin_d2, &data->pin_d2_descriptor) &&
acquire_pin_or_set_null(sdmmc_config->pin_d3, &data->pin_d3_descriptor) &&
acquire_pin_or_set_null(sdmmc_config->pin_d4, &data->pin_d4_descriptor) &&
acquire_pin_or_set_null(sdmmc_config->pin_d5, &data->pin_d5_descriptor) &&
acquire_pin_or_set_null(sdmmc_config->pin_d6, &data->pin_d6_descriptor) &&
acquire_pin_or_set_null(sdmmc_config->pin_d7, &data->pin_d7_descriptor) &&
acquire_pin_or_set_null(sdmmc_config->pin_cd, &data->pin_cd_descriptor) &&
acquire_pin_or_set_null(sdmmc_config->pin_wp, &data->pin_wp_descriptor);
if (!pins_ok) {
LOG_E(TAG, "Failed to acquire required one or more pins");
data->cleanup_pins();
device_set_driver_data(device, nullptr);
delete data;
return ERROR_RESOURCE;
}
// TODO: filesystem
data->initialized = true;
return ERROR_NONE;
}
static error_t stop(Device* device) {
ESP_LOGI(TAG, "stop %s", device->name);
auto* driver_data = GET_DATA(device);
auto* dts_config = GET_CONFIG(device);
// TODO: filesystem
driver_data->cleanup_pins();
device_set_driver_data(device, nullptr);
delete driver_data;
return ERROR_NONE;
}
extern Module platform_esp32_module;
Driver esp32_spi_driver = {
.name = "esp32_sdmmc",
.compatible = (const char*[]) { "espressif,esp32-sdmmc", nullptr },
.start_device = start,
.stop_device = stop,
.api = nullptr,
.device_type = nullptr,
.owner = &platform_esp32_module,
.internal = nullptr
};
} // extern "C"

View File

@ -0,0 +1,156 @@
// SPDX-License-Identifier: Apache-2.0
#include <tactility/device.h>
#include <tactility/drivers/esp32_sdmmc.h>
#include <tactility/drivers/file_system.h>
#include <tactility/log.h>
#include <driver/sdmmc_host.h>
#include <esp_vfs_fat.h>
#include <new>
#include <sdmmc_cmd.h>
#include <string>
#include <tactility/drivers/gpio_descriptor.h>
#define TAG "esp32_sdmmc_fs"
#define GET_DATA(device) ((struct Esp32SdmmcFsInternal*)device_get_driver_data(device))
// We re-use the config from the parent device
#define GET_CONFIG(device) ((const struct Esp32SdmmcConfig*)device->config)
struct Esp32SdmmcFsInternal {
std::string mount_path {};
sdmmc_card_t* card = nullptr;
};
static gpio_num_t to_native_pin(GpioPinSpec pin_spec) {
if (pin_spec.gpio_controller == nullptr) { return GPIO_NUM_NC; }
return static_cast<gpio_num_t>(pin_spec.pin);
}
extern "C" {
error_t mount(Device* device, const char* mount_path) {
LOG_I(TAG, "Mounting %s", mount_path);
auto* data = GET_DATA(device);
auto* config = GET_CONFIG(device);
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
.format_if_mount_failed = false,
.max_files = config->max_open_files,
.allocation_unit_size = 0, // Default is sector size
.disk_status_check_enable = false,
.use_one_fat = false
};
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
uint32_t slot_config_flags = 0;
if (config->enable_uhs) slot_config_flags |= SDMMC_SLOT_FLAG_UHS1;
if (config->wp_active_high) slot_config_flags |= SDMMC_SLOT_FLAG_WP_ACTIVE_HIGH;
sdmmc_slot_config_t slot_config = {
.clk = to_native_pin(config->pin_clk),
.cmd = to_native_pin(config->pin_cmd),
.d0 = to_native_pin(config->pin_d0),
.d1 = to_native_pin(config->pin_d1),
.d2 = to_native_pin(config->pin_d2),
.d3 = to_native_pin(config->pin_d3),
.d4 = to_native_pin(config->pin_d4),
.d5 = to_native_pin(config->pin_d5),
.d6 = to_native_pin(config->pin_d6),
.d7 = to_native_pin(config->pin_d7),
.cd = to_native_pin(config->pin_cd),
.wp = to_native_pin(config->pin_wp),
.width = config->bus_width,
.flags = slot_config_flags
};
esp_err_t result = esp_vfs_fat_sdmmc_mount(mount_path, &host, &slot_config, &mount_config, &data->card);
if (result != ESP_OK || data->card == nullptr) {
if (result == ESP_FAIL) {
LOG_E(TAG, "Mounting failed. Ensure the card is formatted with FAT.");
} else {
LOG_E(TAG, "Mounting failed: %s", esp_err_to_name(result));
}
return false;
}
data->mount_path = mount_path;
return ERROR_NONE;
}
error_t unmount(Device* device) {
auto* data = GET_DATA(device);
if (esp_vfs_fat_sdcard_unmount(data->mount_path.c_str(), data->card) != ESP_OK) {
LOG_E(TAG, "Unmount failed for %s", data->mount_path.c_str());
return ERROR_UNDEFINED;
}
LOG_I(TAG, "Unmounted %s", data->mount_path.c_str());
data->mount_path = "";
data->card = nullptr;
return ERROR_NONE;
}
bool is_mounted(Device* device) {
const auto* data = GET_DATA(device);
return data != nullptr && data->card != nullptr;
}
error_t get_mount_path(Device* device, char* out_path, size_t out_path_size) {
const auto* data = GET_DATA(device);
if (data == nullptr || data->card == nullptr) return ERROR_INVALID_STATE;
if (data->mount_path.size() >= out_path_size) return ERROR_BUFFER_OVERFLOW;
strncpy(out_path, data->mount_path.c_str(), out_path_size);
return ERROR_NONE;
}
static error_t start(Device* device) {
LOG_I(TAG, "start %s", device->name);
auto* data = new (std::nothrow) Esp32SdmmcFsInternal();
if (!data) return ERROR_OUT_OF_MEMORY;
device_set_driver_data(device, data);
// TODO: filesystem
return ERROR_NONE;
}
static error_t stop(Device* device) {
ESP_LOGI(TAG, "stop %s", device->name);
auto* driver_data = GET_DATA(device);
// TODO: filesystem
device_set_driver_data(device, nullptr);
delete driver_data;
return ERROR_NONE;
}
extern Module platform_esp32_module;
static const FileSystemApi sdmmc_fs_api = {
.mount = mount,
.unmount = unmount,
.is_mounted = is_mounted,
.get_mount_path = get_mount_path
};
Driver esp32_sdmmc_fs_driver = {
.name = "esp32_sdmmc_fs",
.compatible = (const char*[]) { "espressif,esp32-sdmmc-fs", nullptr },
.start_device = start,
.stop_device = stop,
.api = &sdmmc_fs_api,
.device_type = &FILE_SYSTEM_TYPE,
.owner = &platform_esp32_module,
.internal = nullptr
};
} // extern "C"

View File

@ -2,11 +2,11 @@
#include <tactility/device.h>
#include <tactility/drivers/esp32_spi.h>
#include <tactility/concurrent/recursive_mutex.h>
#include <tactility/log.h>
#include "tactility/drivers/gpio_descriptor.h"
#include <tactility/drivers/esp32_gpio_helpers.h>
#include <cstring>
#include <esp_log.h>
#include <new>
#include <soc/gpio_num.h>
@ -84,7 +84,7 @@ static error_t start(Device* device) {
acquire_pin_or_set_null(dts_config->pin_hd, &data->hd_descriptor);
if (!pins_ok) {
ESP_LOGE(TAG, "Failed to acquire required SPI pins");
LOG_E(TAG, "Failed to acquire required SPI pins");
data->cleanup_pins();
device_set_driver_data(device, nullptr);
delete data;
@ -113,7 +113,7 @@ static error_t start(Device* device) {
data->cleanup_pins();
device_set_driver_data(device, nullptr);
delete data;
ESP_LOGE(TAG, "Failed to initialize SPI bus: %s", esp_err_to_name(ret));
LOG_E(TAG, "Failed to initialize SPI bus: %s", esp_err_to_name(ret));
return ERROR_RESOURCE;
}
@ -122,7 +122,7 @@ static error_t start(Device* device) {
}
static error_t stop(Device* device) {
ESP_LOGI(TAG, "stop %s", device->name);
LOG_I(TAG, "stop %s", device->name);
auto* driver_data = GET_DATA(device);
auto* dts_config = GET_CONFIG(device);

View File

@ -1,14 +1,21 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <tactility/error.h>
#ifdef __cplusplus
extern "C" {
#endif
struct Device;
struct FileSystemApi {
error_t (*mount)(struct Device* device);
error_t (*mount)(struct Device* device, const char* mount_path);
error_t (*unmount)(struct Device* device);
bool (*is_mounted)(struct Device* device);
error_t (*get_mount_path)(struct Device*, char* out_path);
error_t (*get_mount_path)(struct Device*, char* out_path, size_t out_path_size);
};
extern const struct DeviceType FILE_SYSTEM_TYPE;
@ -20,3 +27,7 @@ error_t file_system_unmount(struct Device* device);
bool file_system_is_mounted(struct Device* device);
error_t file_system_get_mount_path(struct Device*, char* out_path);
#ifdef __cplusplus
}
#endif

View File

@ -1,27 +1,32 @@
#include <tactility/drivers/file_system.h>
#include <tactility/device.h>
#define INTERNAL_API(driver) ((struct FileSystem*)(driver)->api)
#define INTERNAL_API(driver) ((FileSystemApi*)(driver)->api)
error_t file_system_mount(struct Device* device) {
extern "C" {
error_t file_system_mount(Device* device) {
const auto* driver = device_get_driver(device);
return INTERNAL_API(driver)->mount(device);
}
error_t file_system_unmount(struct Device* device) {
error_t file_system_unmount(Device* device) {
const auto* driver = device_get_driver(device);
return INTERNAL_API(driver)->unmount(device);
}
bool file_system_is_mounted(struct Device* device) {
bool file_system_is_mounted(Device* device) {
const auto* driver = device_get_driver(device);
return INTERNAL_API(driver)->is_mounted(device);
}
error_t file_system_get_mount_path(struct Device*, char* out_path) {
error_t file_system_get_mount_path(Device* device, char* out_path) {
const auto* driver = device_get_driver(device);
return INTERNAL_API(driver)->get_mount_path(device, out_path);
}
const struct DeviceType FILE_SYSTEM_TYPE {
const DeviceType FILE_SYSTEM_TYPE {
.name = "file-system"
};
} // extern "C"