Driver improvements (#535)

- New drivers:
  - SD SPI
  - spi_peripheral
  - touch_placeholder
  - display_placeholder
- Devicetree compiler:
  - Implement phandle-arrays
  - Implement device addresses
- Add placeholder drivers to all devices with a SPI display
- SPI driver: add `cs-pins` and set them to high on driver start
- FileSystem: add `file_system_set_owner()` and `file_system_get_owner()`
- File locking is now checking if the related `FileSystem` is part of a shared SPI bus
- Add `device_get_child_count()`
- SDMMC driver: Remove default of `slot` value
- Fix for Crowpanel Basic 3.5" display (add delay to booting)
- Fix for LilyGO T-HMI SD card mounting (delayed mounting by disabling it initially in the dts)
This commit is contained in:
Ken Van Hoeylandt
2026-06-27 17:32:05 +02:00
committed by GitHub
parent e50659a3fb
commit 599fa46766
222 changed files with 1713 additions and 2257 deletions
+42 -18
View File
@@ -1,26 +1,50 @@
#include <tactility/hal/Device.h>
#include "Tactility/hal/sdcard/SdCardDevice.h"
#include <Tactility/lvgl/LvglSync.h>
#include <tactility/device.h>
#include <tactility/drivers/sdcard.h>
#include <tactility/filesystem/file_system.h>
#include <string>
#include <memory>
namespace tt::hal::sdcard {
std::shared_ptr<SdCardDevice> find(const std::string& path) {
auto sdcards = findDevices<SdCardDevice>(Device::Type::SdCard);
for (auto& sdcard : sdcards) {
if (sdcard->isMounted() && path.starts_with(sdcard->getMountPath())) {
return sdcard;
}
}
return nullptr;
}
std::shared_ptr<Lock> findSdCardLock(const std::string& path) {
auto sdcard = find(path);
if (sdcard != nullptr) {
return sdcard->getLock();
}
struct Ctx {
const std::string& path;
std::shared_ptr<Lock> result;
};
Ctx ctx = { path, nullptr };
return nullptr;
file_system_for_each(&ctx, [](FileSystem* fs, void* context) {
auto* c = static_cast<Ctx*>(context);
char mount_path[64];
if (file_system_get_path(fs, mount_path, sizeof(mount_path)) != ERROR_NONE) return true;
if (!c->path.starts_with(mount_path)) return true;
auto* owner = file_system_get_owner(fs);
if (owner != nullptr) {
// Check for I2C controller: if it has more than 2 children, assume it's the display
// TODO: Improve this
auto* parent = device_get_parent(owner);
if (parent != nullptr && device_get_child_count(parent) >= 2) {
c->result = lvgl::getSyncLock();
}
}
return false;
});
return ctx.result;
}
void mountAll() {
device_for_each_of_type(&SDCARD_TYPE, nullptr, [](::Device* device, void*) -> bool {
if (!device_is_ready(device)) {
if (device_start(device) != ERROR_NONE) {
}
}
return true;
});
}
}
@@ -1,52 +0,0 @@
#include <Tactility/hal/sdcard/SdCardDevice.h>
#include <tactility/filesystem/file_system.h>
#include <cstring>
namespace tt::hal::sdcard {
static error_t mount(void* data) {
auto* device = static_cast<SdCardDevice*>(data);
auto path = device->getMountPath();
if (!device->mount(path)) return ERROR_UNDEFINED;
return ERROR_NONE;
}
static error_t unmount(void* data) {
auto* device = static_cast<SdCardDevice*>(data);
if (!device->unmount()) return ERROR_UNDEFINED;
return ERROR_NONE;
}
static bool is_mounted(void* data) {
auto* device = static_cast<SdCardDevice*>(data);
return device->isMounted();
}
static error_t get_path(void* data, char* out_path, size_t out_path_size) {
auto* device = static_cast<SdCardDevice*>(data);
const auto mount_path = device->getMountPath();
if (mount_path.size() >= out_path_size) return ERROR_BUFFER_OVERFLOW;
if (mount_path.empty()) return ERROR_INVALID_STATE;
strncpy(out_path, mount_path.c_str(), out_path_size);
return ERROR_NONE;
}
FileSystemApi sdCardDeviceApi = {
.mount = mount,
.unmount = unmount,
.is_mounted = is_mounted,
.get_path = get_path
};
SdCardDevice::SdCardDevice(MountBehaviour mountBehaviour) : mountBehaviour(mountBehaviour) {
fileSystem = file_system_add(&sdCardDeviceApi, this);
check(fileSystem != nullptr);
}
SdCardDevice::~SdCardDevice() {
file_system_remove(fileSystem);
}
}
@@ -1,36 +0,0 @@
#include <Tactility/hal/sdcard/SdCardMounting.h>
#include <Tactility/hal/sdcard/SdCardDevice.h>
#include <Tactility/Logger.h>
#include <format>
namespace tt::hal::sdcard {
static const auto LOGGER = Logger("SdCardMounting");
constexpr auto* TT_SDCARD_MOUNT_POINT = "/sdcard";
static void mount(const std::shared_ptr<SdCardDevice>& sdcard, const std::string& path) {
LOGGER.info("Mounting sdcard at {}", path);
if (!sdcard->mount(path)) {
LOGGER.warn("SD card mount failed for {} (init can continue)", path);
}
}
static std::string getMountPath(int index, int count) {
return (count == 1) ? TT_SDCARD_MOUNT_POINT : std::format("{}{}", TT_SDCARD_MOUNT_POINT, index);
}
void mountAll() {
const auto sdcards = hal::findDevices<SdCardDevice>(Device::Type::SdCard);
// Numbered mount path name
for (int i = 0; i < sdcards.size(); i++) {
auto sdcard = sdcards[i];
if (!sdcard->isMounted() && sdcard->getMountBehaviour() == SdCardDevice::MountBehaviour::AtBoot) {
std::string mount_path = getMountPath(i, sdcards.size());
mount(sdcard, mount_path);
}
}
}
}
@@ -1,156 +0,0 @@
#ifdef ESP_PLATFORM
#include <Tactility/hal/gpio/Gpio.h>
#include <Tactility/hal/sdcard/SpiSdCardDevice.h>
#include <Tactility/Logger.h>
#include <esp_vfs_fat.h>
#include <sdmmc_cmd.h>
namespace tt::hal::sdcard {
static const auto LOGGER = Logger("SpiSdCardDevice");
/**
* Before we can initialize the sdcard's SPI communications, we have to set all
* other SPI pins on the board high.
* See https://github.com/espressif/esp-idf/issues/1597
* See https://github.com/Xinyuan-LilyGO/T-Deck/blob/master/examples/UnitTest/UnitTest.ino
* @return success result
*/
bool SpiSdCardDevice::applyGpioWorkAround() {
LOGGER.info("applyGpioWorkAround");
uint64_t pin_bit_mask = config->spiPinCs != GPIO_NUM_NC ? BIT64(config->spiPinCs) : 0;
for (auto const& pin: config->csPinWorkAround) {
pin_bit_mask |= BIT64(pin);
}
// Nothing to do
if (pin_bit_mask == 0) {
return true;
}
if (!gpio::configureWithPinBitmask(pin_bit_mask, gpio::Mode::Output, false, false)) {
LOGGER.error("GPIO work-around failed");
return false;
}
for (auto const& pin: config->csPinWorkAround) {
if (!gpio::setLevel(pin, true)) {
LOGGER.error("Failed to set board CS pin high");
return false;
}
}
return true;
}
bool SpiSdCardDevice::mountInternal(const std::string& newMountPath) {
LOGGER.info("Mounting {}", newMountPath);
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
.format_if_mount_failed = config->formatOnMountFailed,
.max_files = config->maxOpenFiles,
.allocation_unit_size = config->allocUnitSize,
.disk_status_check_enable = config->statusCheckEnabled,
.use_one_fat = false
};
// Init without card detect (CD) and write protect (WD)
sdspi_device_config_t slot_config = SDSPI_DEVICE_CONFIG_DEFAULT();
slot_config.host_id = config->spiHost;
slot_config.gpio_cs = config->spiPinCs;
slot_config.gpio_cd = config->spiPinCd;
slot_config.gpio_wp = config->spiPinWp;
slot_config.gpio_int = config->spiPinInt;
sdmmc_host_t host = SDSPI_HOST_DEFAULT();
// The following value is from T-Deck repo's UnitTest.ino project:
// https://github.com/Xinyuan-LilyGO/T-Deck/blob/master/examples/UnitTest/UnitTest.ino
// Observation: Using this automatically sets the bus to 20MHz
host.max_freq_khz = config->spiFrequencyKhz;
host.slot = config->spiHost;
esp_err_t result = esp_vfs_fat_sdspi_mount(newMountPath.c_str(), &host, &slot_config, &mount_config, &card);
if (result != ESP_OK || card == nullptr) {
if (result == ESP_FAIL) {
LOGGER.error("Mounting failed. Ensure the card is formatted with FAT.");
} else {
LOGGER.error("Mounting failed ({})", esp_err_to_name(result));
}
return false;
}
mountPath = newMountPath;
return true;
}
bool SpiSdCardDevice::mount(const std::string& newMountPath) {
auto lock = getLock()->asScopedLock();
lock.lock();
if (!applyGpioWorkAround()) {
LOGGER.error("Failed to apply GPIO work-around");
return false;
}
if (mountInternal(newMountPath)) {
LOGGER.info("Mounted at {}", newMountPath);
sdmmc_card_print_info(stdout, card);
return true;
} else {
LOGGER.error("Mount failed for {}", newMountPath);
return false;
}
}
bool SpiSdCardDevice::unmount() {
auto lock = getLock()->asScopedLock();
lock.lock();
if (card == nullptr) {
LOGGER.error("Can't unmount: not mounted");
return false;
}
if (esp_vfs_fat_sdcard_unmount(mountPath.c_str(), card) != ESP_OK) {
LOGGER.error("Unmount failed for {}", mountPath);
return false;
}
LOGGER.info("Unmounted {}", mountPath);
mountPath = "";
card = nullptr;
return true;
}
// TODO: Refactor to "bool getStatus(Status* status)" method so that it can fail when the lvgl lock fails
SdCardDevice::State SpiSdCardDevice::getState(TickType_t timeout) const {
if (card == nullptr) {
return State::Unmounted;
}
/**
* The SD card and the screen are on the same SPI bus.
* Writing and reading to the bus from 2 devices at the same time causes crashes.
* This work-around ensures that this check is only happening when LVGL isn't rendering.
*/
auto lock = getLock()->asScopedLock();
bool locked = lock.lock(timeout);
if (!locked) {
return State::Timeout;
}
if (sdmmc_get_status(card) != ESP_OK) {
return State::Error;
}
return State::Mounted;
}
}
#endif