Skip to content

New kernel drivers#560

Open
KenVanHoeylandt wants to merge 10 commits into
mainfrom
develop
Open

New kernel drivers#560
KenVanHoeylandt wants to merge 10 commits into
mainfrom
develop

Conversation

@KenVanHoeylandt

@KenVanHoeylandt KenVanHoeylandt commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Added support for ST7789 displays, GT911 touch input, keyboard, keyboard backlight, trackball, and battery sensing.
    • Integrated kernel-managed displays and input devices with LVGL.
    • Added display settings for brightness, orientation, screen timeout, and screensaver behavior.
    • Added power-supply monitoring, charging controls, quick-charge support, and power-off handling.
    • Added automatic LilyGO T-Deck startup, GPS setup, and trackball initialization.
  • Bug Fixes

    • Improved handling of null device-tree phandle values.
    • Corrected pointer input configuration across supported boards.

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This change replaces touch placeholders with pointer placeholders, adds GT911, ST7789, ADC, backlight, keyboard, trackball, and battery-sense drivers, and introduces kernel driver APIs. LVGL gains device-backed display and input adapters. The LilyGO T-Deck device tree now describes its display, touch controller, keyboard, backlight, trackball, ADC, and battery sensing hardware. Boot, power, display, keyboard, status bar, launcher, and device registration paths are updated to use the new kernel devices.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.06% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main change: adding new kernel drivers.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch develop

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (11)
Drivers/st7789-module/source/st7789.cpp (1)

106-119: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicate esp_lcd_panel_invert_color call during bring-up.

When config->invert_color is true, esp_lcd_panel_invert_color(panel, true) is called on line 109 (in the reset→init→invert chain) and again on line 118 (after swap_xy/mirror). On the ST7789, inversion (INVON/INVOFF, 0x20/0x21) is independent of the MADCTL register (0x36) that swap_xy/mirror modify, so the second call is a no-op. If the first call succeeds, the second is redundant; if the first fails, ok is already false and the second is skipped via short-circuit. Line 118 adds no value.

♻️ Remove redundant invert call
 ok = ok && (!config->swap_xy || esp_lcd_panel_swap_xy(internal->panel_handle, true) == ESP_OK);
 ok = ok && ((!config->mirror_x && !config->mirror_y) || esp_lcd_panel_mirror(internal->panel_handle, config->mirror_x, config->mirror_y) == ESP_OK);
-ok = ok && (!config->invert_color || esp_lcd_panel_invert_color(internal->panel_handle, true) == ESP_OK);
 ok = ok && esp_lcd_panel_disp_on_off(internal->panel_handle, true) == ESP_OK;
Devices/lilygo-tdeck/Source/tdeck_keyboard_backlight.cpp (2)

60-64: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Clear driver data after freeing in stop.

After free(internal), the device still holds a dangling pointer via device_get_driver_data. Clearing it prevents use-after-free if any API call reaches the device post-stop.

🛡️ Proposed fix
 static error_t stop(Device* device) {
     auto* internal = static_cast<TdeckKeyboardBacklightInternal*>(device_get_driver_data(device));
     free(internal);
+    device_set_driver_data(device, nullptr);
     return ERROR_NONE;
 }

44-57: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

State desync when initial brightness write fails.

start returns ERROR_NONE even if the I2C brightness writes at lines 47–55 fail. When the CMD_BRIGHTNESS write fails, current_brightness stays at 0 while the hardware may have a different value, causing get_brightness to report stale data.

Consider either returning an error when the initial brightness write fails, or reading back the hardware state to initialize the cache.

Drivers/gt911-module/CMakeLists.txt (1)

5-5: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider adding CONFIGURE_DEPENDS to GLOB_RECURSE.

Without CONFIGURE_DEPENDS, CMake won't re-run when source files are added or removed under source/, leading to stale builds. Since cmake_minimum_required is 3.20 (well above the 3.12 requirement), the flag is available.

♻️ Proposed fix
-file(GLOB_RECURSE SOURCE_FILES "source/*.c*")
+file(GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS "source/*.c*")
Tactility/Source/app/kerneldisplay/KernelDisplay.cpp (1)

1-2: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace fragile relative includes with proper include paths.

Lines 1–2 use ../../../../TactilityKernel/include/... relative paths, while every other include in the file uses proper angle-bracket paths (e.g., <tactility/drivers/backlight.h>, <tactility/device.h>). The project clearly has include directories configured for these paths. The relative includes will break if the file moves and are inconsistent with the codebase convention.

♻️ Proposed fix
-#include "../../../../TactilityKernel/include/tactility/drivers/display.h"
-#include "../../../../TactilityKernel/include/tactility/error.h"
+#include <tactility/drivers/display.h>
+#include <tactility/error.h>
TactilityKernel/include/tactility/drivers/display.h (1)

226-227: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove duplicate display_get_color_format declaration.

Lines 226 and 227 both declare display_get_color_format with identical signatures. This is a copy-paste error — while valid in C, it's clearly unintentional.

♻️ Proposed fix
 enum DisplayColorFormat display_get_color_format(struct Device* device);
-enum DisplayColorFormat display_get_color_format(struct Device* device);
TactilityKernel/source/drivers/pointer.cpp (2)

9-57: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use consistent Device* (without struct keyword) across all function signatures.

Line 9 uses struct Device* while all other functions (lines 14–54) use Device*. Both are equivalent in C++, but the inconsistency is noticeable.

♻️ Consistency fix
-error_t pointer_enter_sleep(struct Device* device) {
+error_t pointer_enter_sleep(Device* device) {

59-61: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Drop struct from DeviceType declaration for consistency with sdcard.cpp.

sdcard.cpp was just changed from const struct DeviceType to const DeviceType in this PR. The new POINTER_TYPE definition here still uses struct.

♻️ Consistency fix
-const struct DeviceType POINTER_TYPE {
+const DeviceType POINTER_TYPE {
Tactility/Source/app/boot/Boot.cpp (1)

85-113: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Skip backlight_set_brightness when device_start fails.

If device_start(backlight) fails on line 91, the code proceeds to call backlight_set_brightness(backlight, ...) on line 102, which will also fail and produce a redundant error log. Consider early-returning or skipping the brightness call when the backlight device failed to start.

♻️ Proposed refactor
             if (!device_is_ready(backlight)) {
                 if (device_start(backlight) != ERROR_NONE) {
                     LOG_E(TAG, "Failed to start %s", backlight->name);
+                    return;
                 }
             }
Devices/lilygo-tdeck/Source/module.cpp (1)

49-49: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add static to subscribe_events() for consistency.

All other file-scope functions (power_on, start, stop) are declared static, but subscribe_events() is not. This gives it unnecessary external linkage and risks symbol collision.

♻️ Proposed fix
-void subscribe_events() {
+static void subscribe_events() {
Modules/lvgl-module/source/lvgl_display.c (1)

130-130: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Rename bpp to bytes_per_pixel. lv_color_format_get_size returns bytes per pixel, so bpp reads like bits-per-pixel and makes the buffer-size math easier to misread.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4eb3e1c7-91ef-48f9-a545-4a8a2fe3f52e

📥 Commits

Reviewing files that changed from the base of the PR and between c4406b2 and 77e6872.

📒 Files selected for processing (100)
  • Buildscripts/DevicetreeCompiler/source/generator.py
  • Devices/cyd-2432s024r/cyd,2432s024r.dts
  • Devices/cyd-2432s028r/cyd,2432s028r.dts
  • Devices/cyd-2432s028rv3/cyd,2432s028rv3.dts
  • Devices/cyd-e32r28t/cyd,e32r28t.dts
  • Devices/cyd-e32r32p/cyd,e32r32p.dts
  • Devices/elecrow-crowpanel-basic-28/elecrow,crowpanel-basic-28.dts
  • Devices/elecrow-crowpanel-basic-35/elecrow,crowpanel-basic-35.dts
  • Devices/lilygo-tdeck/CMakeLists.txt
  • Devices/lilygo-tdeck/Source/Configuration.cpp
  • Devices/lilygo-tdeck/Source/Drivers.cpp
  • Devices/lilygo-tdeck/Source/Init.cpp
  • Devices/lilygo-tdeck/Source/KeyboardBacklight/KeyboardBacklight.cpp
  • Devices/lilygo-tdeck/Source/KeyboardBacklight/KeyboardBacklight.h
  • Devices/lilygo-tdeck/Source/bindings/tdeck_keyboard.h
  • Devices/lilygo-tdeck/Source/bindings/tdeck_keyboard_backlight.h
  • Devices/lilygo-tdeck/Source/devices/Display.cpp
  • Devices/lilygo-tdeck/Source/devices/Display.h
  • Devices/lilygo-tdeck/Source/devices/KeyboardBacklight.cpp
  • Devices/lilygo-tdeck/Source/devices/KeyboardBacklight.h
  • Devices/lilygo-tdeck/Source/devices/TdeckKeyboard.cpp
  • Devices/lilygo-tdeck/Source/devices/TdeckKeyboard.h
  • Devices/lilygo-tdeck/Source/drivers/tdeck_keyboard.h
  • Devices/lilygo-tdeck/Source/drivers/tdeck_keyboard_backlight.h
  • Devices/lilygo-tdeck/Source/module.cpp
  • Devices/lilygo-tdeck/Source/tdeck_keyboard.cpp
  • Devices/lilygo-tdeck/Source/tdeck_keyboard_backlight.cpp
  • Devices/lilygo-tdeck/bindings/lilygo,tdeck-keyboard-backlight.yaml
  • Devices/lilygo-tdeck/bindings/lilygo,tdeck-keyboard.yaml
  • Devices/lilygo-tdeck/devicetree.yaml
  • Devices/lilygo-tdeck/lilygo,tdeck.dts
  • Devices/lilygo-thmi/lilygo,thmi.dts
  • Devices/unphone/unphone.dts
  • Drivers/gt911-module/CMakeLists.txt
  • Drivers/gt911-module/bindings/goodix,gt911.yaml
  • Drivers/gt911-module/devicetree.yaml
  • Drivers/gt911-module/include/bindings/gt911.h
  • Drivers/gt911-module/include/drivers/gt911.h
  • Drivers/gt911-module/include/gt911_module.h
  • Drivers/gt911-module/source/gt911.cpp
  • Drivers/gt911-module/source/module.cpp
  • Drivers/st7789-module/CMakeLists.txt
  • Drivers/st7789-module/bindings/sitronix,st7789.yaml
  • Drivers/st7789-module/devicetree.yaml
  • Drivers/st7789-module/include/bindings/st7789.h
  • Drivers/st7789-module/include/drivers/st7789.h
  • Drivers/st7789-module/include/st7789_module.h
  • Drivers/st7789-module/source/module.cpp
  • Drivers/st7789-module/source/st7789.cpp
  • Modules/lvgl-module/include/tactility/lvgl_display.h
  • Modules/lvgl-module/include/tactility/lvgl_keyboard.h
  • Modules/lvgl-module/include/tactility/lvgl_pointer.h
  • Modules/lvgl-module/source/arch/lvgl_esp32.c
  • Modules/lvgl-module/source/lvgl_devices.c
  • Modules/lvgl-module/source/lvgl_display.c
  • Modules/lvgl-module/source/lvgl_keyboard.c
  • Modules/lvgl-module/source/lvgl_pointer.c
  • Platforms/platform-esp32/bindings/espressif,esp32-ledc-backlight.yaml
  • Platforms/platform-esp32/include/tactility/bindings/esp32_ledc_backlight.h
  • Platforms/platform-esp32/include/tactility/drivers/esp32_ledc_backlight.h
  • Platforms/platform-esp32/source/drivers/esp32_ledc_backlight.cpp
  • Platforms/platform-esp32/source/module.cpp
  • Tactility/Include/Tactility/hal/display/KernelDisplayDriver.h
  • Tactility/Include/Tactility/hal/power/PowerDevice.h
  • Tactility/Include/Tactility/hal/touch/KernelTouchDriver.h
  • Tactility/Include/Tactility/hal/touch/TouchDriver.h
  • Tactility/Source/Tactility.cpp
  • Tactility/Source/app/boot/Boot.cpp
  • Tactility/Source/app/display/Display.cpp
  • Tactility/Source/app/kerneldisplay/KernelDisplay.cpp
  • Tactility/Source/app/keyboard/KeyboardSettings.cpp
  • Tactility/Source/app/power/Power.cpp
  • Tactility/Source/app/poweroff/PowerOff.cpp
  • Tactility/Source/hal/display/KernelDisplayDriver.cpp
  • Tactility/Source/hal/power/PowerDevice.cpp
  • Tactility/Source/hal/touch/KernelTouchDriver.cpp
  • Tactility/Source/lvgl/Keyboard.cpp
  • Tactility/Source/lvgl/Lvgl.cpp
  • Tactility/Source/service/keyboardidle/KeyboardIdle.cpp
  • TactilityKernel/bindings/pointer-placeholder.yaml
  • TactilityKernel/bindings/touch-placeholder.yaml
  • TactilityKernel/include/tactility/bindings/pointer_placeholder.h
  • TactilityKernel/include/tactility/drivers/backlight.h
  • TactilityKernel/include/tactility/drivers/display.h
  • TactilityKernel/include/tactility/drivers/keyboard.h
  • TactilityKernel/include/tactility/drivers/pointer.h
  • TactilityKernel/include/tactility/drivers/pointer_placeholder.h
  • TactilityKernel/include/tactility/drivers/power_supply.h
  • TactilityKernel/source/drivers/backlight.cpp
  • TactilityKernel/source/drivers/display.cpp
  • TactilityKernel/source/drivers/grove.cpp
  • TactilityKernel/source/drivers/keyboard.cpp
  • TactilityKernel/source/drivers/pointer.cpp
  • TactilityKernel/source/drivers/pointer_placeholder.cpp
  • TactilityKernel/source/drivers/power_supply.cpp
  • TactilityKernel/source/drivers/rtc.cpp
  • TactilityKernel/source/drivers/sdcard.cpp
  • TactilityKernel/source/drivers/spi_controller.cpp
  • TactilityKernel/source/drivers/touch.cpp
  • TactilityKernel/source/kernel_init.cpp
💤 Files with no reviewable changes (12)
  • TactilityKernel/bindings/touch-placeholder.yaml
  • Devices/lilygo-tdeck/Source/devices/Display.h
  • Devices/lilygo-tdeck/Source/Drivers.cpp
  • Devices/lilygo-tdeck/Source/KeyboardBacklight/KeyboardBacklight.h
  • TactilityKernel/source/drivers/touch.cpp
  • Devices/lilygo-tdeck/Source/devices/KeyboardBacklight.h
  • Devices/lilygo-tdeck/Source/Init.cpp
  • Devices/lilygo-tdeck/Source/devices/TdeckKeyboard.cpp
  • Devices/lilygo-tdeck/Source/devices/KeyboardBacklight.cpp
  • Devices/lilygo-tdeck/Source/devices/Display.cpp
  • Devices/lilygo-tdeck/Source/devices/TdeckKeyboard.h
  • Devices/lilygo-tdeck/Source/KeyboardBacklight/KeyboardBacklight.cpp

Comment on lines +114 to +124
static error_t stop(Device* device) {
auto* internal = static_cast<Gt911Internal*>(device_get_driver_data(device));

if (esp_lcd_touch_del(internal->touch_handle) != ESP_OK) {
LOG_E(TAG, "Failed to delete touch handle");
return ERROR_RESOURCE;
}

free(internal);
return ERROR_NONE;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the driver and related cleanup calls.
git ls-files 'Drivers/gt911-module/source/gt911.cpp' 'Drivers/gt911-module/**' | sed 's#^`#FILE` #'
echo
rg -n "esp_lcd_touch_del|esp_lcd_panel_io_del|stop\\(|start\\(" Drivers/gt911-module/source/gt911.cpp Drivers/gt911-module -S

echo
# Show the relevant section with line numbers.
cat -n Drivers/gt911-module/source/gt911.cpp | sed -n '1,220p'

Repository: TactilityProject/Tactility

Length of output: 10697


🌐 Web query:

esp_lcd_touch_del does it delete esp_lcd_panel_io handle GT911 ESP-IDF

💡 Result:

No, esp_lcd_touch_del does not delete or free the esp_lcd_panel_io_handle_t handle [1][2]. The function esp_lcd_touch_del is responsible only for cleaning up resources specific to the touch controller itself, such as resetting GPIO pins (e.g., interrupt and reset pins) and freeing the memory allocated for the touch controller handle [1][3]. It does not have ownership of the communication interface (panel IO) that was passed into the touch initialization function [4][5]. To properly clean up resources, you must delete the handles in the correct order: 1. Call esp_lcd_touch_del(touch_handle) to free the touch controller resources [1][6]. 2. Call esp_lcd_panel_io_del(io_handle) to free the LCD panel IO handle [7][8]. This separation of concerns is necessary because the same panel IO handle might be shared or used by other components, and the touch driver simply consumes it as a dependency for communication [4][5].

Citations:


Delete the panel IO handle in stop() and always free internal
esp_lcd_touch_del() only releases the touch-side resources; internal->io_handle is still owned here and needs its own esp_lcd_panel_io_del(). Also, the early return on esp_lcd_touch_del() failure leaves internal allocated.

Comment on lines 47 to 51
initialized = true;

lvgl_devices_attach();

if (lvgl_module_config.on_start) lvgl_module_config.on_start();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if lvgl_lock is called anywhere in the arch start path
rg -n "lvgl_lock\|lv_lock\|port_lock" Modules/lvgl-module/source/ --type c
# Check the esp_lvgl_port init behavior regarding task creation
rg -n "lvgl_port_init\|xTaskCreate\|task_create" Modules/lvgl-module/ Platforms/platform-esp32/ --type c --type cpp

Repository: TactilityProject/Tactility

Length of output: 164


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate relevant files
fd -a 'lvgl_esp32.c|lvgl_devices_attach|esp_lvgl_port' Modules/lvgl-module Platforms . || true

# Find exact definitions/usages
rg -n "lvgl_devices_attach|lvgl_port_init|lvgl_lock|lv_lock|port_lock|xTaskCreate" Modules/lvgl-module Platforms -S || true

# Show file outline for the target file if present
if [ -f "Modules/lvgl-module/source/arch/lvgl_esp32.c" ]; then
  ast-grep outline Modules/lvgl-module/source/arch/lvgl_esp32.c --view expanded || true
fi

Repository: TactilityProject/Tactility

Length of output: 3120


🏁 Script executed:

#!/bin/bash
set -euo pipefail

fd -a 'lvgl_esp32.c|lvgl_devices_attach|esp_lvgl_port' Modules/lvgl-module Platforms . || true

rg -n "lvgl_devices_attach|lvgl_port_init|lvgl_lock|lv_lock|port_lock|xTaskCreate" Modules/lvgl-module Platforms -S || true

if [ -f "Modules/lvgl-module/source/arch/lvgl_esp32.c" ]; then
  ast-grep outline Modules/lvgl-module/source/arch/lvgl_esp32.c --view expanded || true
fi

Repository: TactilityProject/Tactility

Length of output: 3120


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== target file =="
git ls-files 'Modules/lvgl-module/source/arch/lvgl_esp32.c' || true

echo "== possible attach definition/usages =="
rg -n "lvgl_devices_attach" Modules/lvgl-module -S || true

echo "== likely init paths =="
rg -n "lvgl_port_init|lvgl_arch_start|on_start|lvgl_pointer_add|lvgl_keyboard_add" Modules/lvgl-module -S || true

echo "== target file excerpt =="
if [ -f "Modules/lvgl-module/source/arch/lvgl_esp32.c" ]; then
  sed -n '1,140p' Modules/lvgl-module/source/arch/lvgl_esp32.c
fi

Repository: TactilityProject/Tactility

Length of output: 5092


🌐 Web query:

esp_lvgl_port lvgl_port_init task starts before return lock requirement lvgl_port_lock docs

💡 Result:

The esp_lvgl_port component is designed so that lvgl_port_init initializes the LVGL subsystem and spawns a background task to handle LVGL processing (such as timers and input devices) [1][2][3]. Regarding your query on initialization and locking, here is the breakdown: Initialization Timing and Task Synchronization: Historical versions of esp_lvgl_port had issues where the background task would start before initialization was fully completed, potentially causing crashes if called from high-priority tasks [4][5][6]. Recent versions have addressed this by implementing task notification and synchronization mechanisms to ensure lvgl_port_init completes its necessary setup before returning [4]. You should ensure you are using a recent version of the esp_lvgl_port component to benefit from these fixes [6][7]. LVGL Port Lock Requirement: The lvgl_port_lock and lvgl_port_unlock functions are mandatory for protecting all LVGL API calls [7][8][9]. Because LVGL is not thread-safe by default, any interaction with LVGL objects (like creating UI elements, updating labels, or changing screens) from a task other than the one running the LVGL background handler must be enclosed within these lock/unlock commands [8][9]. Usage Pattern: 1. Initialize the port: Call lvgl_port_init in your application setup (typically in app_main) [3]. 2. Protect API calls: Always wrap your LVGL function calls with a lock/unlock pair [7][8]. Example: lvgl_port_lock(0); // 0 blocks indefinitely until the mutex is acquired lv_obj_t * screen = lv_disp_get_scr_act(disp_handle); lv_obj_t * obj = lv_label_create(screen); lvgl_port_unlock; // Release the mutex Always refer to the official esp_lvgl_port component documentation for the version you are using, as implementation details can evolve [7][3].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,120p' Modules/lvgl-module/source/lvgl_devices.c

Repository: TactilityProject/Tactility

Length of output: 2271


Hold the LVGL lock around lvgl_devices_attach(). lvgl_pointer_add() and lvgl_keyboard_add() require the caller to own the lock, and this runs after lvgl_port_init() has already brought up the LVGL task. Wrap the attach call in lvgl_lock()/lvgl_unlock() or move it into a locked start hook.

Comment on lines +14 to +45
void lvgl_devices_attach() {
lv_disp_t* lvgl_display = NULL;
struct Device* kernel_display_device = device_find_first_by_type(&DISPLAY_TYPE);
if (kernel_display_device != NULL) {
struct LvglDisplayConfig lvgl_display_config = {};
if (lvgl_display_add(kernel_display_device, &lvgl_display_config, &lvgl_display) == ERROR_NONE) {
LOG_I(TAG, "Bound %s to LVGL", kernel_display_device->name);
} else {
LOG_E(TAG, "Failed to bind %s to LVGL", kernel_display_device->name);
}
}

struct Device* kernel_pointer_device = device_find_first_by_type(&POINTER_TYPE);
lv_indev_t* lvgl_pointer_device;
if (kernel_pointer_device != NULL) {
if (lvgl_pointer_add(kernel_pointer_device, lvgl_display, &lvgl_pointer_device) == ERROR_NONE) {
LOG_I(TAG, "Bound %s to LVGL", kernel_pointer_device->name);
} else {
LOG_E(TAG, "Failed to bind %s to LVG", kernel_pointer_device->name);
}
}

struct Device* kernel_keyboard_device = device_find_first_by_type(&KEYBOARD_TYPE);
lv_indev_t* lvgl_keyboard_device;
if (kernel_keyboard_device != NULL) {
if (lvgl_keyboard_add(kernel_keyboard_device, lvgl_display, &lvgl_keyboard_device) == ERROR_NONE) {
LOG_I(TAG, "Bound %s to LVGL", kernel_keyboard_device->name);
} else {
LOG_E(TAG, "Failed to bind %s to LVGL", kernel_keyboard_device->name);
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if lvgl_display_remove exists
rg -n "lvgl_display_remove\|lv_display_remove\|lv_disp_remove" Modules/lvgl-module/ --type c --type h
# Check what lvgl_display_add allocates
rg -n "lvgl_display_add" Modules/lvgl-module/ --type c --type h -C5

Repository: TactilityProject/Tactility

Length of output: 3686


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect detach implementation and related display lifecycle code
ast-grep outline Modules/lvgl-module/source/lvgl_devices.c --view expanded
printf '\n--- lvgl_devices.c (detach region) ---\n'
sed -n '1,220p' Modules/lvgl-module/source/lvgl_devices.c

printf '\n--- lvgl_display.h ---\n'
sed -n '1,140p' Modules/lvgl-module/include/tactility/lvgl_display.h

printf '\n--- lvgl_display.c (remove implementation) ---\n'
rg -n "lvgl_display_remove|lvgl_display_add|lv_display_delete|lv_display_remove|lv_indev_delete" Modules/lvgl-module/source/lvgl_display.c -C 6

Repository: TactilityProject/Tactility

Length of output: 5878


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find all callers/usages of the attach/detach and display remove APIs
rg -n "lvgl_devices_attach|lvgl_devices_detach|lvgl_display_remove|lvgl_display_add" Modules/lvgl-module/ -C 3

printf '\n--- module start/stop wiring ---\n'
rg -n "on_start|on_stop|lvgl_lock|lvgl_devices_" Modules/lvgl-module/ -C 3

printf '\n--- LVGL display lifecycle details ---\n'
sed -n '196,240p' Modules/lvgl-module/source/lvgl_display.c

Repository: TactilityProject/Tactility

Length of output: 17113


Call lvgl_display_remove during detach. lvgl_devices_attach() creates an lv_display_t, but lvgl_devices_detach() only deletes indevs. lvgl_display_remove() already frees the display and any owned buffers, so keep that handle around and remove it before shutdown.

Comment on lines +47 to +54
void lvgl_devices_detach() {
lv_indev_t* device = lv_indev_get_next(NULL);
while (device != NULL) {
lv_indev_delete(device);
// Always get the first item, because getting the next one doesn't work as the current pointer just became corrupt
device = lv_indev_get_next(NULL);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Memory leak: lvgl_devices_detach bypasses context cleanup.

lv_indev_delete does not free the LvglPointerCtx/LvglKeyboardCtx structs that were malloc'd in lvgl_pointer_add/lvgl_keyboard_add. Every attach/detach cycle leaks these allocations. Use lvgl_pointer_remove and lvgl_keyboard_remove instead, which properly retrieve and free the driver data before deleting the indev.

🔒 Proposed fix: use typed remove functions in detach
 void lvgl_devices_detach() {
     lv_indev_t* device = lv_indev_get_next(NULL);
     while (device != NULL) {
-        lv_indev_delete(device);
-        // Always get the first item, because getting the next one doesn't work as the current pointer just became corrupt
-        device = lv_indev_get_next(NULL);
+        lv_indev_type_t type = lv_indev_get_type(device);
+        if (type == LV_INDEV_TYPE_POINTER) {
+            lvgl_pointer_remove(device);
+        } else if (type == LV_INDEV_TYPE_KEYPAD) {
+            lvgl_keyboard_remove(device);
+        } else {
+            lv_indev_delete(device);
+        }
+        // Always get the first item, because getting the next one doesn't work as the current pointer just became corrupt
+        device = lv_indev_get_next(NULL);
     }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
void lvgl_devices_detach() {
lv_indev_t* device = lv_indev_get_next(NULL);
while (device != NULL) {
lv_indev_delete(device);
// Always get the first item, because getting the next one doesn't work as the current pointer just became corrupt
device = lv_indev_get_next(NULL);
}
}
void lvgl_devices_detach() {
lv_indev_t* device = lv_indev_get_next(NULL);
while (device != NULL) {
lv_indev_type_t type = lv_indev_get_type(device);
if (type == LV_INDEV_TYPE_POINTER) {
lvgl_pointer_remove(device);
} else if (type == LV_INDEV_TYPE_KEYPAD) {
lvgl_keyboard_remove(device);
} else {
lv_indev_delete(device);
}
// Always get the first item, because getting the next one doesn't work as the current pointer just became corrupt
device = lv_indev_get_next(NULL);
}
}

Comment on lines +67 to +71
static error_t stop(Device* device) {
auto* internal = static_cast<Esp32LedcBacklightInternal*>(device_get_driver_data(device));
free(internal);
return ERROR_NONE;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the target file around the reported lines.
FILE="Platforms/platform-esp32/source/drivers/esp32_ledc_backlight.cpp"
wc -l "$FILE"
sed -n '1,180p' "$FILE"

# Find LEDC usage and any cleanup paths in the esp32 platform sources.
rg -n "ledc_stop|ledc_timer|ledc_channel|stop\(Device\* device\)|Esp32LedcBacklightInternal|device_get_driver_data" Platforms/platform-esp32 -S

# If there are other backlight drivers, inspect their stop/cleanup pattern.
fd -a ".*backlight.*\.cpp$" Platforms/platform-esp32/source || true

Repository: TactilityProject/Tactility

Length of output: 15882


🌐 Web query:

ESP-IDF ledc_stop semantics channel timer remain configured reuse output off documentation

💡 Result:

In the ESP-IDF framework, the function ledc_stop is used to suspend the signal generation on a specific LEDC channel [1][2]. Its semantics regarding the channel and timer configuration are as follows: 1. Channel Configuration: Calling ledc_stop disables the LEDC output for the specified channel and sets the output to the idle level defined by the idle_level parameter [1][3][2]. It does not deconfigure or destroy the channel's association with its assigned timer; the channel remains configured and ready to be restarted (typically by re-initializing the duty cycle or calling ledc_channel_config if necessary) [1][4]. 2. Timer Configuration: The function ledc_stop does not affect the configuration or the running state of the timer bound to the channel [1][4]. The timer continues to operate independently of the stop status of individual channels [1][4]. If you need to stop the timer, you must use ledc_timer_pause [5][6]. 3. Reusability: Because ledc_stop only affects the output state of the channel, the channel and its assigned timer remain fully configured in the driver's internal state [1][4]. If your goal is to "reuse" these resources, they remain reserved and configured as they were before the stop command [1][4]. If you intend to completely remove a channel's configuration or a timer to free it for a different use (e.g., re-binding to a different pin or changing settings entirely), you generally do not need to call ledc_stop as a prerequisite for reconfiguration, but you may need to call ledc_channel_config again with new parameters to overwrite the old settings [1][4]. Timers, specifically, must be paused before they can be deconfigured using the deconfigure field in ledc_timer_config_t [1][4]. In summary, ledc_stop is a light-weight suspension mechanism that does not release or clear the underlying hardware configuration [1][2].

Citations:


Turn off the LEDC output in stop()
Call ledc_stop() before freeing the internal state; otherwise the backlight stays at its last duty cycle after the device is stopped. ledc_stop() only disables the channel output, so it won’t deconfigure the timer/channel.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
TactilityKernel/source/drivers/battery_sense.cpp (1)

11-12: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

ADC_MAX_RAW is hardcoded for 12-bit ADC.

The comment acknowledges this limitation. For the T-Deck's ADC_BITWIDTH_DEFAULT on ESP32 (12-bit), this is correct. However, if this driver is reused on a board configured with a different ADC bitwidth, the voltage calculation will be silently incorrect. Consider documenting this as a driver constraint or exposing the ADC resolution through the generic ADC API in a future iteration.

Platforms/platform-esp32/source/drivers/esp32_adc_oneshot.cpp (1)

25-38: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Document the ignored timeout argument in the ESP32 oneshot ADC backend.
adc_oneshot_read() has no timeout parameter, so this driver always drops the API’s timeout value. Add a note in the backend or interface so callers know this path does not enforce per-call deadlines.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b658f5a7-eb3b-41df-b037-8198069b4daf

📥 Commits

Reviewing files that changed from the base of the PR and between 77e6872 and f9384d3.

📒 Files selected for processing (24)
  • Devices/lilygo-tdeck/CMakeLists.txt
  • Devices/lilygo-tdeck/Source/Configuration.cpp
  • Devices/lilygo-tdeck/Source/devices/Power.cpp
  • Devices/lilygo-tdeck/Source/devices/Power.h
  • Devices/lilygo-tdeck/Source/module.cpp
  • Devices/lilygo-tdeck/Source/tdeck_keyboard.cpp
  • Devices/lilygo-tdeck/lilygo,tdeck.dts
  • Platforms/platform-esp32/CMakeLists.txt
  • Platforms/platform-esp32/bindings/espressif,esp32-adc-oneshot.yaml
  • Platforms/platform-esp32/include/tactility/bindings/esp32_adc_oneshot.h
  • Platforms/platform-esp32/include/tactility/drivers/esp32_adc_oneshot.h
  • Platforms/platform-esp32/source/drivers/esp32_adc_oneshot.cpp
  • Platforms/platform-esp32/source/module.cpp
  • Tactility/Source/Tactility.cpp
  • Tactility/Source/service/statusbar/Statusbar.cpp
  • TactilityKernel/bindings/adc-controller.yaml
  • TactilityKernel/bindings/battery-sense.yaml
  • TactilityKernel/include/tactility/bindings/battery_sense.h
  • TactilityKernel/include/tactility/drivers/adc.h
  • TactilityKernel/include/tactility/drivers/adc_controller.h
  • TactilityKernel/include/tactility/drivers/battery_sense.h
  • TactilityKernel/source/drivers/adc_controller.cpp
  • TactilityKernel/source/drivers/battery_sense.cpp
  • TactilityKernel/source/kernel_init.cpp
💤 Files with no reviewable changes (3)
  • Devices/lilygo-tdeck/Source/devices/Power.cpp
  • Devices/lilygo-tdeck/Source/devices/Power.h
  • Devices/lilygo-tdeck/Source/Configuration.cpp
✅ Files skipped from review due to trivial changes (2)
  • TactilityKernel/bindings/adc-controller.yaml
  • TactilityKernel/include/tactility/bindings/battery_sense.h
🚧 Files skipped from review as they are similar to previous changes (4)
  • Devices/lilygo-tdeck/CMakeLists.txt
  • Tactility/Source/Tactility.cpp
  • Devices/lilygo-tdeck/Source/tdeck_keyboard.cpp
  • Devices/lilygo-tdeck/Source/module.cpp

Comment on lines +75 to +80
static error_t stop(Device* device) {
LOG_I(TAG, "stop %s", device->name);
adc_oneshot_del_unit(GET_HANDLE(device));
device_set_driver_data(device, nullptr);
return ERROR_NONE;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Unchecked return value of adc_oneshot_del_unit in stop().

If adc_oneshot_del_unit fails, the function still returns ERROR_NONE, potentially masking a resource leak. Consider logging the error for debuggability.

🔧 Suggested improvement
 static error_t stop(Device* device) {
     LOG_I(TAG, "stop %s", device->name);
-    adc_oneshot_del_unit(GET_HANDLE(device));
+    esp_err_t esp_error = adc_oneshot_del_unit(GET_HANDLE(device));
+    if (esp_error != ESP_OK) {
+        LOG_E(TAG, "Failed to delete ADC unit: %s", esp_err_to_name(esp_error));
+    }
     device_set_driver_data(device, nullptr);
     return ERROR_NONE;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
static error_t stop(Device* device) {
LOG_I(TAG, "stop %s", device->name);
adc_oneshot_del_unit(GET_HANDLE(device));
device_set_driver_data(device, nullptr);
return ERROR_NONE;
}
static error_t stop(Device* device) {
LOG_I(TAG, "stop %s", device->name);
esp_err_t esp_error = adc_oneshot_del_unit(GET_HANDLE(device));
if (esp_error != ESP_OK) {
LOG_E(TAG, "Failed to delete ADC unit: %s", esp_err_to_name(esp_error));
}
device_set_driver_data(device, nullptr);
return ERROR_NONE;
}

Comment on lines +6 to +9
io-channels:
type: phandles
required: true
description: The ADC channel connected to the battery sense circuit, e.g. `<&adc1 3>`

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

io-channels type may not parse the channel index argument.

The DTS uses io-channels = <&adc0 0> where 0 is the channel index, and BatterySenseConfig.io_channel is an AdcChannelSpec with both adc_controller and channel fields. However, the binding declares type: phandles, which in standard devicetree semantics only parses phandle references without arguments. The ADC oneshot binding uses type: phandle-array with element-type for similar phandle+argument patterns.

If the custom devicetree compiler's phandles type doesn't parse the argument, the channel field would default to 0 — which happens to match the T-Deck DTS but would silently break on boards with a non-zero channel index.

#!/bin/bash
# Check the battery_sense binding header to see how io-channels is mapped
cat TactilityKernel/include/tactility/bindings/battery_sense.h

# Check the adc-controller base binding for reference
cat TactilityKernel/bindings/adc-controller.yaml

# Check how the devicetree compiler handles phandles vs phandle-array
rg -n "phandle" Buildscripts/DevicetreeCompiler/source/generator.py

# Check the esp32_adc_oneshot binding header for comparison
cat Platforms/platform-esp32/include/tactility/bindings/esp32_adc_oneshot.h

# Search for other bindings using io-channels or phandle-array for comparison
rg -n "io-channels|phandle-array|phandles" TactilityKernel/bindings/ Platforms/*/bindings/ Drivers/*/bindings/ Devices/*/bindings/ --type yaml

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
Devices/lilygo-tdeck/source/module.cpp (1)

99-112: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Missing driver cleanup on error path in start().

If driver_construct_add(&tdeck_keyboard_backlight_driver) or driver_construct_add(&tdeck_trackball_driver) fails, previously registered drivers are not unregistered, leaking resources. Add rollback for each failure case.

🔧 Proposed fix: add cleanup on error paths
 static error_t start() {
     LOG_I(TAG, LOG_MESSAGE_POWER_ON_START);
     if (!power_on()) {
         LOG_E(TAG, LOG_MESSAGE_POWER_ON_FAILED);
         return ERROR_RESOURCE;
     }

     if (driver_construct_add(&tdeck_keyboard_driver) != ERROR_NONE) {
         LOG_E(TAG, "Failed to register keyboard driver");
         return ERROR_RESOURCE;
     }

     if (driver_construct_add(&tdeck_keyboard_backlight_driver) != ERROR_NONE) {
         LOG_E(TAG, "Failed to register keyboard backlight driver");
+        driver_remove_destruct(&tdeck_keyboard_driver);
         return ERROR_RESOURCE;
     }

     if (driver_construct_add(&tdeck_trackball_driver) != ERROR_NONE) {
         LOG_E(TAG, "Failed to register trackball driver");
+        driver_remove_destruct(&tdeck_keyboard_backlight_driver);
+        driver_remove_destruct(&tdeck_keyboard_driver);
         return ERROR_RESOURCE;
     }

     subscribe_events();
     return ERROR_NONE;
 }

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ef94159f-7c42-4f25-9df2-8f8c860e1727

📥 Commits

Reviewing files that changed from the base of the PR and between f9384d3 and 68469df.

📒 Files selected for processing (20)
  • Devices/lilygo-tdeck/CMakeLists.txt
  • Devices/lilygo-tdeck/Source/Trackball/Trackball.cpp
  • Devices/lilygo-tdeck/Source/devices/TrackballDevice.cpp
  • Devices/lilygo-tdeck/Source/devices/TrackballDevice.h
  • Devices/lilygo-tdeck/bindings/lilygo,tdeck-trackball.yaml
  • Devices/lilygo-tdeck/include/bindings/tdeck_keyboard.h
  • Devices/lilygo-tdeck/include/bindings/tdeck_keyboard_backlight.h
  • Devices/lilygo-tdeck/include/bindings/tdeck_trackball.h
  • Devices/lilygo-tdeck/include/drivers/tdeck_keyboard.h
  • Devices/lilygo-tdeck/include/drivers/tdeck_keyboard_backlight.h
  • Devices/lilygo-tdeck/include/drivers/tdeck_trackball.h
  • Devices/lilygo-tdeck/include/drivers/trackball.h
  • Devices/lilygo-tdeck/lilygo,tdeck.dts
  • Devices/lilygo-tdeck/source/drivers/tdeck_keyboard.cpp
  • Devices/lilygo-tdeck/source/drivers/tdeck_keyboard_backlight.cpp
  • Devices/lilygo-tdeck/source/drivers/tdeck_trackball.cpp
  • Devices/lilygo-tdeck/source/drivers/trackball.cpp
  • Devices/lilygo-tdeck/source/module.cpp
  • Tactility/Source/app/launcher/Launcher.cpp
  • TactilityKernel/source/drivers/battery_sense.cpp
💤 Files with no reviewable changes (3)
  • Devices/lilygo-tdeck/Source/devices/TrackballDevice.h
  • Devices/lilygo-tdeck/Source/devices/TrackballDevice.cpp
  • Devices/lilygo-tdeck/Source/Trackball/Trackball.cpp
✅ Files skipped from review due to trivial changes (1)
  • Devices/lilygo-tdeck/include/drivers/tdeck_trackball.h
🚧 Files skipped from review as they are similar to previous changes (2)
  • Devices/lilygo-tdeck/CMakeLists.txt
  • Devices/lilygo-tdeck/lilygo,tdeck.dts

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.

1 participant