Skip to content

SPI device migration#490

Merged
KenVanHoeylandt merged 5 commits intomainfrom
develop
Feb 8, 2026
Merged

SPI device migration#490
KenVanHoeylandt merged 5 commits intomainfrom
develop

Conversation

@KenVanHoeylandt
Copy link
Contributor

@KenVanHoeylandt KenVanHoeylandt commented Feb 8, 2026

  • Implement SPI devices in dts files for all devices
  • Removed tt::hal::spi HAL and its configurations
  • Fix for devicetree generator "boolean" support
  • Remove unused custom locks in all DisplayDevice implementations
  • Fixed some bugs with devices
  • Updated XPT2046 driver
  • Fix for WifiEsp deadlock
  • Export a lot of new math.h symbols with tt_init.cpp
  • Created SpiDeviceLock in TactilityCore as a wrapper for kernel SPI locking
  • Improved TactilityKernel SPI driver.

Summary by CodeRabbit

  • New Features

    • Added ESP32 SPI device tree bindings and explicit SPI controller definitions for better device isolation and runtime discovery.
    • Introduced SpiDeviceLock for improved SPI device synchronization.
  • Bug Fixes

    • Fixed LVGL lock initialization timing by making pre-initialization locks non-blocking.
  • Refactor

    • Restructured SPI subsystem for better device modularity and runtime configuration.
    • Simplified display driver initialization by removing redundant lock management.
    • Removed centralized SPI configuration in favor of device tree-based approach.
    • Consolidated I2C configuration by removing explicit pullup settings.
  • Tests

    • Updated math function exports for better C standard library compatibility.

@coderabbitai
Copy link

coderabbitai bot commented Feb 8, 2026

📝 Walkthrough

Walkthrough

The PR removes the tt::hal::spi subsystem (headers and implementation) and related compatibility shims, and deletes SpiInit/SpiCompat. Many device Configuration.cpp files drop inline SPI initialization. A new esp32 SPI binding, driver headers, and esp32_spi DTS bindings are added; numerous board DTS files gain spi nodes and many i2c pull-up properties are removed. SdCard creation sites now locate ::Device* SPI controllers at runtime and pass them to an updated SpiSdCardDevice constructor. Display drivers and the EspLcd stack no longer accept/propagate SPI lock parameters. A new SpiDeviceLock adapter and related kernel binding were introduced.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.49% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'SPI device migration' clearly summarizes the main change: migrating SPI device configurations to the device tree while removing the old HAL-based SPI setup.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch develop

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
Devices/wireless-tag-wt32-sc01-plus/wireless-tag,wt32-sc01-plus.dts (1)

25-34: Clarify intent of max-transfer-size = <0>.

On ESP-IDF, a value of 0 for max_transfer_sz silently defaults to 4094 bytes. If that default is the desired behavior, consider adding a brief comment or using the explicit value for readability. If there's truly no transfer-size constraint intended, this is fine as-is.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
Devices/guition-jc3248w535c/Source/Axs15231b/Axs15231bDisplay.h (1)

28-42: ⚠️ Potential issue | 🔴 Critical

Critical Bug: tePin parameter missing from constructor but member and initializer retained — self-initialization is undefined behavior.

The tePin constructor parameter was removed from the signature (should be after resetPin at Line 27), but tePin(tePin) remains in the initializer list (Line 42), causing the uninitialized member to read itself. The member is declared at Line 62.

Re-add the tePin parameter to the constructor. The TE functionality is still active: setupTeSync() and teardown code in the .cpp implementation actively use configuration->tePin (lines 172, 188, 207, 220, 225–227) to configure the interrupt handler and GPIO, so the parameter cannot be removed.

The parameter should be added before horizontalResolution at Line 29.

Devices/cyd-2432s028rv3/Source/devices/SdCard.cpp (1)

10-25: ⚠️ Potential issue | 🟡 Minor

The spiController parameter is redundant when customLock is set.

The constructor at line 21 ignores the spiController parameter because config->customLock (the RecursiveMutex passed in the Config) is non-null. The SpiSdCardDevice constructor only uses spiController to create an SpiDeviceLock when customLock is null:

if (config->customLock == nullptr) {
    auto spi_lock = std::make_shared<SpiDeviceLock>(spiController);
    lock = std::static_pointer_cast<Lock>(spi_lock);
} else {
    lock = config->customLock;  // Used; spiController ignored
}

Both spiHost and RecursiveMutex in the Config are necessary and actively used during mount. However, the pattern here is transitional: compare with cyd-8048s043c/SdCard.cpp, which uses minimal Config (only 5 args, no spiHost or customLock) and relies solely on the spiController parameter. Consider aligning this board with the newer pattern.

Drivers/ST7735/Source/St7735Display.h (1)

89-95: ⚠️ Potential issue | 🔴 Critical

Constructor passes undefined function to base class with no matching constructor.

The constructor at line 90 calls tt::hal::spi::getLock(inConfiguration->spiHostDevice) and attempts to pass its result to EspLcdDisplay, but:

  1. EspLcdDisplay only has a default constructor (EspLcdDisplay() = default;) that accepts no arguments.
  2. The tt::hal::spi::getLock function does not exist in the codebase—no definition, declaration, or namespace definition for tt::hal::spi can be found.
  3. The code also calls assert(getLock() != nullptr) but St7735Display has no getLock() method.

This will not compile. The SPI device migration is incomplete for this driver. The recent commit "933f8ad SPI device migration" indicates refactoring is in progress; this file needs to be updated to match the new SPI architecture.

🧹 Nitpick comments (5)
Drivers/EspLcdCompat/Source/EspLcdDisplayV2.h (1)

68-72: Consider marking this single-parameter constructor explicit.

A single-parameter constructor without explicit permits implicit conversions from std::shared_ptr<EspLcdConfiguration> to EspLcdDisplayV2. While unlikely to cause issues in practice (derived classes construct it explicitly), it's a good habit.

Proposed fix
-    EspLcdDisplayV2(const std::shared_ptr<EspLcdConfiguration>& configuration) :
+    explicit EspLcdDisplayV2(const std::shared_ptr<EspLcdConfiguration>& configuration) :
Devices/cyd-4848s040c/Source/devices/St7701Display.h (1)

3-3: Remove unused include: RecursiveMutex.h is never referenced in this file or its implementation.

The include on line 3 is stale and can be safely removed.

Proposed fix
-#include <Tactility/RecursiveMutex.h>
-
Drivers/EspLcdCompat/Source/EspLcdDisplay.h (1)

3-3: Remove unused include Tactility/Lock.h.

The Tactility/Lock.h header is not used anywhere in this file. There are no lock member variables or Lock-related methods. The include can be safely removed.

-#include <Tactility/Lock.h>
TactilityCore/Include/Tactility/kernel/SpiDeviceLock.h (1)

13-13: Mark single-argument constructor explicit.

Prevents unintended implicit conversions from Device* to SpiDeviceLock.

Proposed fix
-    SpiDeviceLock(::Device* device) : device(device) { }
+    explicit SpiDeviceLock(::Device* device) : device(device) { }
Drivers/ST7735/Source/St7735Display.h (1)

15-15: Remove unused member variable.

The lock member variable declared at line 15 is never used anywhere in this class. The lock is obtained via tt::hal::spi::getLock() and passed to the base class constructor (line 90), where it's managed. This member is redundant.

♻️ Proposed fix to remove unused member
 class St7735Display final : public EspLcdDisplay {
 
-    std::shared_ptr<tt::Lock> lock;
-
 public:

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
TactilityKernel/Include/tactility/concurrent/recursive_mutex.h (1)

18-26: ⚠️ Potential issue | 🟡 Minor

Removed precondition assertions weaken misuse detection.

Previously, recursive_mutex_construct asserted handle == NULL (catches double-init / semaphore leak) and recursive_mutex_destruct asserted handle != NULL (catches double-free / NULL passed to vSemaphoreDelete, which is undefined in FreeRTOS). Both guards were effectively free at runtime and caught real programming errors early.

Was the removal intentional, or a side-effect of the broader refactor? If intentional, consider at least keeping the destruct guard to avoid UB:

Proposed fix
 inline static void recursive_mutex_construct(struct RecursiveMutex* mutex) {
+    check(mutex->handle == NULL);
     mutex->handle = xSemaphoreCreateRecursiveMutex();
 }
 
 inline static void recursive_mutex_destruct(struct RecursiveMutex* mutex) {
     check(xPortInIsrContext() != pdTRUE);
+    check(mutex->handle != NULL);
     vSemaphoreDelete(mutex->handle);
     mutex->handle = NULL;
 }
🧹 Nitpick comments (9)
Devices/lilygo-tdisplay/lilygo,tdisplay.dts (1)

17-26: Verify max-transfer-size = <0> is intentional.

In ESP-IDF, a max_transfer_sz of 0 defaults to 4094 bytes. If that default is the desired behavior, consider adding a brief comment to make it explicit. Otherwise, set a specific value matching the display's needs (e.g., the frame buffer size for bulk transfers).

Devices/lilygo-tdongle-s3/Source/devices/Display.cpp (2)

12-12: Nit: wrap macro expression in parentheses.

Pre-existing, but since you're touching this file: the macro should be parenthesized to avoid surprises in compound expressions.

Suggested fix
-#define LCD_SPI_TRANSFER_HEIGHT LCD_VERTICAL_RESOLUTION / 4
+#define LCD_SPI_TRANSFER_HEIGHT (LCD_VERTICAL_RESOLUTION / 4)

20-21: Nit: use the existing macros instead of duplicated literals.

Lines 20–21 pass 80 and 160 directly, but LCD_HORIZONTAL_RESOLUTION and LCD_VERTICAL_RESOLUTION are already defined for this purpose.

Suggested fix
     auto configuration = std::make_unique<St7735Display::Configuration>(
         LCD_SPI_HOST,
         LCD_PIN_CS,
         LCD_PIN_DC,
         LCD_PIN_RESET,
-        80,
-        160,
+        LCD_HORIZONTAL_RESOLUTION,
+        LCD_VERTICAL_RESOLUTION,
TactilityKernel/Include/tactility/concurrent/recursive_mutex.h (1)

13-16: TODO without a tracking reference.

Consider linking this to an issue so it doesn't get lost (e.g., // TODO(#123): Add debugging functionality).

Drivers/ILI9488/Source/Ili9488Display.h (1)

31-31: Pre-existing comment mismatch: "1/20" vs actual "1/10".

The comment here says the default is 1/20 of the screen size, but line 44 computes / 10 and line 60's comment says 1/10. This comment is stale.

📝 Suggested fix
-            uint32_t bufferSize = 0 // Size in pixel count. 0 means default, which is 1/20 of the screen size
+            uint32_t bufferSize = 0 // Size in pixel count. 0 means default, which is 1/10 of the screen size
Devices/lilygo-tdisplay-s3/lilygo,tdisplay-s3.dts (1)

16-26: SPI node added without a label, unlike other device trees in this PR.

Other .dts files in this PR use labels like display_spi: spi0 and sdcard_spi: spi1. This node is just spi0 without a label. Since this device has no SD card and only one SPI bus, it's functional, but adding a display_spi: label would be more consistent with the rest of the PR.

Optional: add label for consistency
-	spi0 {
+	display_spi: spi0 {
Devices/guition-jc3248w535c/guition,jc3248w535c.dts (1)

46-46: Clarify semantics of max-transfer-size = <0>.

Both SPI nodes set max-transfer-size to 0. If 0 means "use driver default" or "unlimited," this is fine, but it would be worth a brief comment in the binding documentation or here to avoid confusion for future maintainers.

Also applies to: 57-57

Devices/guition-jc8048w550c/Source/devices/SdCard.cpp (1)

5-6: Unused includes likely left over from the SPI lock removal.

LvglSync.h and RecursiveMutex.h don't appear to be used anywhere in this file after the migration.

Proposed fix
 `#include` <tactility/device.h>
 `#include` <Tactility/hal/sdcard/SpiSdCardDevice.h>
-#include <Tactility/lvgl/LvglSync.h>
-#include <Tactility/RecursiveMutex.h>
Tactility/Include/Tactility/hal/Configuration.h (1)

22-26: Stale comment: SPI is no longer configured via this struct.

The comment says "Called before I2C/SPI/etc is initialized" but SPI configuration has been removed from Configuration. Consider updating to reflect that SPI is now handled via device-tree bindings, e.g., "Called before device-tree peripherals are initialized."

📝 Suggested comment update
-    /**
-     * Called before I2C/SPI/etc is initialized.
-     * Used for powering on the peripherals manually.
-     */
+    /**
+     * Called before device-tree peripherals are initialized.
+     * Used for powering on the peripherals manually.
+     */

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
Tactility/Source/service/wifi/WifiEsp.cpp (1)

892-898: ⚠️ Potential issue | 🔴 Critical

Null-pointer dereference when service is not running.

getIp() lacks the null guard that every other public function has. If wifi_singleton is null, line 895 dereferences a null pointer.

Proposed fix
 std::string getIp() {
     auto wifi = std::static_pointer_cast<Wifi>(wifi_singleton);
+    if (wifi == nullptr) {
+        return "0.0.0.0";
+    }
 
     auto lock = wifi->dataMutex.asScopedLock();
     if (!lock.lock(100 / portTICK_PERIOD_MS)) {
         return "0.0.0.0";
     }
Devices/m5stack-tab5/Source/devices/Ili9881cDisplay.h (1)

4-14: ⚠️ Potential issue | 🟡 Minor

Remove unused NoLock class and RecursiveMutex.h include.

The NoLock class is no longer referenced after the constructor change. The include of <Tactility/RecursiveMutex.h> (providing tt::Lock) is only needed for NoLock.

🧹 Remove unused code
 `#include` <EspLcdDisplayV2.h>
-#include <Tactility/RecursiveMutex.h>
 
 `#include` <esp_lcd_mipi_dsi.h>
 `#include` <esp_ldo_regulator.h>
 
 class Ili9881cDisplay final : public EspLcdDisplayV2 {
 
-    class NoLock final : public tt::Lock {
-        bool lock(TickType_t timeout) const override { return true; }
-        void unlock() const override { /* NO-OP */ }
-    };
-
     esp_lcd_dsi_bus_handle_t mipiDsiBus = nullptr;
Drivers/EspLcdCompat/Source/EspLcdDisplay.h (1)

16-16: ⚠️ Potential issue | 🟡 Minor

rgbElementOrder is uninitialized with the defaulted constructor.

The member on line 16 has no in-class initializer and the constructor is now defaulted, leaving this enum uninitialized. The code reads this member in getDisplayDriver() (cpp line 103), which is undefined behavior. Add a default initializer to match patterns in other display drivers:

Proposed fix
-    lcd_rgb_element_order_t rgbElementOrder;
+    lcd_rgb_element_order_t rgbElementOrder = LCD_RGB_ELEMENT_ORDER_RGB;
Devices/waveshare-s3-touch-lcd-128/Source/devices/SdCard.cpp (1)

9-27: ⚠️ Potential issue | 🟠 Major

Dead code: SdCard.cpp defines createSdCard() but it is never instantiated — missing from Configuration.cpp.

createSdCard() is declared in SdCard.h and defined in SdCard.cpp, but Configuration.cpp line 11-15 only calls createDisplay() in createDevices(). Other boards (e.g., m5stack-tab5, cyd-2432s032c) include createSdCard(). Either:

  1. Delete SdCard.cpp and SdCard.h if SD card is not supported on this board, or
  2. Add createSdCard() to the device vector in Configuration.cpp line 13.
🧹 Nitpick comments (13)
Devices/lilygo-tdongle-s3/Source/devices/Display.cpp (1)

20-21: Use the defined macros instead of magic numbers.

Lines 20–21 hardcode 80 and 160, duplicating the values already defined as LCD_HORIZONTAL_RESOLUTION and LCD_VERTICAL_RESOLUTION on lines 10–11.

Suggested diff
     auto configuration = std::make_unique<St7735Display::Configuration>(
         LCD_SPI_HOST,
         LCD_PIN_CS,
         LCD_PIN_DC,
         LCD_PIN_RESET,
-        80,
-        160,
+        LCD_HORIZONTAL_RESOLUTION,
+        LCD_VERTICAL_RESOLUTION,
         nullptr,
Tactility/Include/Tactility/hal/Configuration.h (1)

25-30: Nit: createDevices lacks const unlike sibling fields.

initBoot and uiScale are both declared const, but createDevices is not. If Configuration instances are meant to be immutable after construction, consider making it const as well for consistency. If mutability is intentional (e.g., for reassignment after construction), this is fine as-is.

Buildscripts/DevicetreeCompiler/source/generator.py (1)

66-68: Use elif instead of if to maintain the chain.

Line 66 uses if instead of elif, breaking the if/elif/…/else chain. It works today only because the preceding branch on line 65 returns early, but if that return is ever refactored the fall-through semantics change silently.

Proposed fix
     if type == "value":
         return property.value
-    if type == "boolean":
+    elif type == "boolean":
         return "true"
     elif type == "text":
Devices/waveshare-s3-touch-lcd-43/waveshare,s3-touch-lcd-43.dts (1)

18-24: I2C pull-up removal is a separate concern from SPI migration.

The removal of pin-sda-pullup and pin-scl-pullup is unrelated to the SPI device migration. This is fine if the board has external pull-ups, but consider splitting unrelated changes for clearer commit history.

Documentation/ideas.md (2)

17-17: Consider safety checks for automatic build directory deletion.

Automatically deleting build directories when the platform changes could lead to unintended data loss if the detection logic is faulty or if users have custom artifacts in those directories. When implementing this, ensure you:

  • Prompt for confirmation before deletion
  • Log what will be deleted
  • Provide a way to opt-out or disable this behavior
  • Consider backup or move-to-trash instead of permanent deletion

14-17: Consider tracking these higher-priority ideas as GitHub issues.

These four ideas represent actionable work items that emerged from the SPI migration. Creating dedicated GitHub issues for each would:

  • Enable better tracking and prioritization
  • Allow for discussion and refinement of requirements
  • Link related PRs and commits
  • Provide visibility to contributors
Tactility/Source/service/wifi/WifiEsp.cpp (1)

301-313: Mutex acquisition is now redundant for an atomic read.

secure_connection is std::atomic<bool>, so isSecureConnection() is already thread-safe without the lock. The other atomic accessors (getRadioState, isScanning) are called lock-free elsewhere. Consider dropping the lock here for consistency.

Suggested simplification
 bool isConnectionSecure() {
     auto wifi = wifi_singleton;
     if (wifi == nullptr) {
         return false;
     }
 
-    auto lock = wifi->dataMutex.asScopedLock();
-    if (!lock.lock(10 / portTICK_PERIOD_MS)) {
-        return false;
-    }
-
     return wifi->isSecureConnection();
 }
Devices/waveshare-s3-lcd-13/Source/devices/SdCard.cpp (2)

5-5: Remove unused RecursiveMutex.h include.

Line 5 includes <Tactility/RecursiveMutex.h>, but RecursiveMutex is not used anywhere in this file. Line 17 passes nullptr instead of constructing a RecursiveMutex instance, making this include unnecessary.

Proposed cleanup
-#include <Tactility/RecursiveMutex.h>

22-23: Add missing include for device_find_by_name.

The function device_find_by_name is called on line 22 but <tactility/device.h> is not included. Every other SdCard.cpp file in this codebase includes this header. While this may compile via transitive inclusion, adding the direct include improves robustness and consistency.

Proposed fix
 `#include` "SdCard.h"
 
+#include <tactility/device.h>
 `#include` <Tactility/lvgl/LvglSync.h>
 `#include` <Tactility/hal/sdcard/SpiSdCardDevice.h>
 `#include` <Tactility/RecursiveMutex.h>
 `#include` <driver/gpio.h>
Devices/m5stack-cores3/m5stack,cores3.dts (1)

35-51: Consider removing commented-out nodes or adding a note explaining why they're kept.

The commented-out i2c_port_b and i2c_port_c blocks are kept as dead code. If they serve as a reference for users who want to reconfigure the I2C ports, a brief comment explaining that would be helpful. Otherwise, removing them keeps the DTS clean.

Devices/lilygo-tdongle-s3/lilygo,tdongle-s3.dts (1)

26-35: New spi0 node looks correct; clarify max-transfer-size = <0> intent.

In ESP-IDF, a max_transfer_sz of 0 defaults to 4094 bytes. If that's the intended behavior, consider adding a brief comment (e.g., /* 0 = default (4094) */) for maintainability, or explicitly set the desired value. Other board DTS files in this PR should be checked for consistency.

Devices/waveshare-s3-lcd-13/Source/Configuration.cpp (1)

1-6: <driver/gpio.h> appears unused after removing the SPI configuration block.

With the SPI config removed, there's no remaining GPIO usage in this file. Consider removing this include.

Proposed fix
 `#include` "devices/Display.h"
 `#include` "devices/SdCard.h"
-#include <driver/gpio.h>
 
 `#include` <Tactility/hal/Configuration.h>
 `#include` <PwmBacklight.h>
Devices/waveshare-s3-touch-lcd-128/Source/devices/SdCard.cpp (1)

10-18: The spiHost parameter in Config is required and actively used by ESP-IDF for SD card initialization.

While the DTS node (spi1) and Config struct both specify SPI3_HOST, they serve different purposes and both are necessary:

  • spiHost (from Config) is passed to ESP-IDF APIs (esp_vfs_fat_sdspi_mount, sdspi_device_config_t) to identify the hardware SPI controller
  • spiController (from device_find_by_name) is used for SPI bus arbitration via SpiDeviceLock

The maintenance concern is valid—the DTS host value and Config spiHost must stay synchronized. Consider documenting this coupling or adding a validation check to ensure they match at runtime rather than removing the parameter.

@KenVanHoeylandt KenVanHoeylandt merged commit d274049 into main Feb 8, 2026
52 checks passed
@KenVanHoeylandt KenVanHoeylandt deleted the develop branch February 8, 2026 21:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants