Device migrations and driver implementations (#564)

This commit is contained in:
Ken Van Hoeylandt
2026-07-15 12:46:24 +02:00
committed by GitHub
parent 8af6204ba1
commit bd8fdfd858
55 changed files with 1695 additions and 447 deletions
+11
View File
@@ -0,0 +1,11 @@
cmake_minimum_required(VERSION 3.20)
include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake")
file(GLOB_RECURSE SOURCE_FILES "source/*.c*")
tactility_add_module(rgb-display-module
SRCS ${SOURCE_FILES}
INCLUDE_DIRS include/
REQUIRES TactilityKernel platform-esp32 esp_lcd driver
)
@@ -0,0 +1,195 @@
Apache License
==============
_Version 2.0, January 2004_
_&lt;<http://www.apache.org/licenses/>&gt;_
### Terms and Conditions for use, reproduction, and distribution
#### 1. Definitions
“License” shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
“Licensor” shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
“Legal Entity” shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, “control” means **(i)** the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the
outstanding shares, or **(iii)** beneficial ownership of such entity.
“You” (or “Your”) shall mean an individual or Legal Entity exercising
permissions granted by this License.
“Source” form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
“Object” form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
“Work” shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
“Derivative Works” shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
“Contribution” shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
“submitted” means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as “Not a Contribution.”
“Contributor” shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
#### 2. Grant of Copyright License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
#### 3. Grant of Patent License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
#### 4. Redistribution
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
* **(a)** You must give any other recipients of the Work or Derivative Works a copy of
this License; and
* **(b)** You must cause any modified files to carry prominent notices stating that You
changed the files; and
* **(c)** You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
#### 5. Submission of Contributions
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
#### 6. Trademarks
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
#### 7. Disclaimer of Warranty
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
#### 8. Limitation of Liability
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
#### 9. Accepting Warranty or Additional Liability
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
_END OF TERMS AND CONDITIONS_
### APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets `[]` replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same “printed page” as the copyright notice for easier identification within
third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+5
View File
@@ -0,0 +1,5 @@
# RGB Display Driver
A kernel driver for ESP32 RGB (parallel timing-driven) LCD panels, built on ESP-IDF's `esp_lcd_rgb_panel` component. Unlike SPI/i8080 panels, an RGB panel owns its GPIOs directly and has no command interface, so it doesn't sit behind a shared bus controller device - kernel driver equivalent of the deprecated HAL's `RgbDisplay`.
License: [Apache v2.0](LICENSE-Apache-2.0.md)
@@ -0,0 +1,224 @@
description: >
ESP32 RGB (parallel timing-driven) LCD panel, built on ESP-IDF's esp_lcd_rgb_panel
component. Unlike SPI/i8080 panels, an RGB panel has no command interface and owns its
GPIOs directly rather than sitting behind a shared bus controller device.
compatible: "espressif,esp32-rgb-display"
properties:
horizontal-resolution:
type: int
required: true
description: Horizontal resolution in pixels
vertical-resolution:
type: int
required: true
description: Vertical resolution in pixels
pixel-clock-hz:
type: int
required: true
description: Pixel clock frequency in Hz
hsync-pulse-width:
type: int
required: true
description: Horizontal sync width, in PCLK periods
hsync-back-porch:
type: int
required: true
description: Number of PCLK periods between hsync and the start of line active data
hsync-front-porch:
type: int
required: true
description: Number of PCLK periods between the end of active data and the next hsync
vsync-pulse-width:
type: int
required: true
description: Vertical sync width, in lines
vsync-back-porch:
type: int
required: true
description: Number of invalid lines between vsync and the start of the frame
vsync-front-porch:
type: int
required: true
description: Number of invalid lines between the end of the frame and the next vsync
hsync-idle-low:
type: boolean
default: false
description: The hsync signal is low in the idle state
vsync-idle-low:
type: boolean
default: false
description: The vsync signal is low in the idle state
de-idle-high:
type: boolean
default: false
description: The DE signal is high in the idle state
pclk-active-neg:
type: boolean
default: false
description: Whether display data is clocked out on the falling edge of PCLK
pclk-idle-high:
type: boolean
default: false
description: The PCLK signal stays high in the idle phase
data-width:
type: int
default: 16
description: Number of parallel data lines wired (8 or 16)
bits-per-pixel:
type: int
default: 0
description: Frame buffer color depth in bpp. 0 defaults to data-width.
num-fbs:
type: int
default: 1
description: Number of screen-sized frame buffers to allocate (0 or 1 = single-buffered)
bounce-buffer-size-px:
type: int
default: 0
description: Non-zero enables the DRAM bounce-buffer DMA path, sized in pixels
sram-trans-align:
type: int
default: 8
description: Alignment of buffers allocated in internal SRAM
psram-trans-align:
type: int
default: 64
description: Alignment of buffers allocated in PSRAM
pin-hsync:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: HSYNC GPIO pin, optional for panels that only need DE (data-enable) sync
pin-vsync:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: VSYNC GPIO pin, optional for panels that only need DE (data-enable) sync
pin-de:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Data-enable GPIO pin, optional
pin-pclk:
type: phandles
required: true
description: Pixel-clock GPIO pin
pin-disp:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Optional display-enable control GPIO pin
pin-reset:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Optional hardware reset pin for the panel's own driver IC, pulsed once at start()
reset-active-high:
type: boolean
default: false
description: Whether the reset pin is active high
pin-data0:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Data bus bit 0 GPIO pin
pin-data1:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Data bus bit 1 GPIO pin
pin-data2:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Data bus bit 2 GPIO pin
pin-data3:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Data bus bit 3 GPIO pin
pin-data4:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Data bus bit 4 GPIO pin
pin-data5:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Data bus bit 5 GPIO pin
pin-data6:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Data bus bit 6 GPIO pin
pin-data7:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Data bus bit 7 GPIO pin
pin-data8:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Data bus bit 8 GPIO pin
pin-data9:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Data bus bit 9 GPIO pin
pin-data10:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Data bus bit 10 GPIO pin
pin-data11:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Data bus bit 11 GPIO pin
pin-data12:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Data bus bit 12 GPIO pin
pin-data13:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Data bus bit 13 GPIO pin
pin-data14:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Data bus bit 14 GPIO pin
pin-data15:
type: phandles
default: GPIO_PIN_SPEC_NONE
description: Data bus bit 15 GPIO pin
disp-active-low:
type: boolean
default: false
description: A low level on the display-enable control signal turns the screen on
refresh-on-demand:
type: boolean
default: false
description: Only refresh the frame buffer on explicit draw_bitmap calls
fb-in-psram:
type: boolean
default: true
description: Allocate the frame buffer from PSRAM, preferentially
double-fb:
type: boolean
default: false
description: Allocate two screen-sized frame buffers (equivalent to num-fbs = 2)
no-fb:
type: boolean
default: false
description: Don't allocate a frame buffer; bounce buffer must be filled manually
bb-invalidate-cache:
type: boolean
default: false
description: Invalidate cache on bounce-buffer reads (can be dangerous with multi-core writers)
swap-xy:
type: boolean
default: false
description: Swap the X and Y axes
mirror-x:
type: boolean
default: false
description: Mirror the X axis
mirror-y:
type: boolean
default: false
description: Mirror the Y axis
invert-color:
type: boolean
default: false
description: Invert the panel's color output
backlight:
type: phandle
default: "NULL"
description: Optional reference to this display's backlight device
@@ -0,0 +1,3 @@
dependencies:
- TactilityKernel
bindings: bindings
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/bindings/bindings.h>
#include <drivers/rgb_display.h>
// The devicetree compiler derives the expected config typedef name from the compatible
// string's suffix (e.g. "espressif,esp32-rgb-display" -> esp32_rgb_display_config_dt), not
// from the node name or driver name, so the tag here must match that exactly.
DEFINE_DEVICETREE(esp32_rgb_display, struct RgbDisplayConfig)
@@ -0,0 +1,91 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdbool.h>
#include <tactility/device.h>
#include <tactility/drivers/gpio.h>
struct RgbDisplayConfig {
uint16_t horizontal_resolution;
uint16_t vertical_resolution;
uint32_t pixel_clock_hz;
uint32_t hsync_pulse_width;
uint32_t hsync_back_porch;
uint32_t hsync_front_porch;
uint32_t vsync_pulse_width;
uint32_t vsync_back_porch;
uint32_t vsync_front_porch;
bool hsync_idle_low;
bool vsync_idle_low;
bool de_idle_high;
bool pclk_active_neg;
bool pclk_idle_high;
// Number of parallel data lines actually wired (8 or 16). Only the first data_width
// pin-dataN fields below are used; the rest are ignored.
uint8_t data_width;
// Frame buffer color depth in bpp. 0 defaults to data_width.
uint8_t bits_per_pixel;
// Number of screen-sized frame buffers the driver allocates (0 or 1 = single-buffered).
uint8_t num_fbs;
// Non-zero enables the DRAM bounce-buffer DMA path, sized in pixels.
uint32_t bounce_buffer_size_px;
uint32_t sram_trans_align;
uint32_t psram_trans_align;
struct GpioPinSpec pin_hsync;
struct GpioPinSpec pin_vsync;
struct GpioPinSpec pin_de;
struct GpioPinSpec pin_pclk;
// Optional display-enable control pin, GPIO_PIN_SPEC_NONE if unused.
struct GpioPinSpec pin_disp;
// Optional hardware reset pin for the panel's own driver IC, pulsed once at start() before
// the RGB peripheral is brought up. GPIO_PIN_SPEC_NONE if the panel has none (esp_lcd_rgb_panel
// itself has no reset concept - some RGB panels still have a separate reset line on their
// internal driver IC even though pixel data is fed over the RGB bus).
struct GpioPinSpec pin_reset;
bool reset_active_high;
struct GpioPinSpec pin_data0;
struct GpioPinSpec pin_data1;
struct GpioPinSpec pin_data2;
struct GpioPinSpec pin_data3;
struct GpioPinSpec pin_data4;
struct GpioPinSpec pin_data5;
struct GpioPinSpec pin_data6;
struct GpioPinSpec pin_data7;
struct GpioPinSpec pin_data8;
struct GpioPinSpec pin_data9;
struct GpioPinSpec pin_data10;
struct GpioPinSpec pin_data11;
struct GpioPinSpec pin_data12;
struct GpioPinSpec pin_data13;
struct GpioPinSpec pin_data14;
struct GpioPinSpec pin_data15;
bool disp_active_low;
bool refresh_on_demand;
bool fb_in_psram;
bool double_fb;
bool no_fb;
bool bb_invalidate_cache;
bool swap_xy;
bool mirror_x;
bool mirror_y;
bool invert_color;
// Optional reference to this display's backlight device, NULL if none.
struct Device* backlight;
};
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/module.h>
#ifdef __cplusplus
extern "C" {
#endif
extern struct Module rgb_display_module;
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: Apache-2.0
#include <tactility/check.h>
#include <tactility/driver.h>
#include <tactility/module.h>
extern "C" {
extern Driver rgb_display_driver;
static error_t start() {
/* We crash when construct fails, because if a single driver fails to construct,
* there is no guarantee that the previously constructed drivers can be destroyed */
check(driver_construct_add(&rgb_display_driver) == ERROR_NONE);
return ERROR_NONE;
}
static error_t stop() {
/* We crash when destruct fails, because if a single driver fails to destruct,
* there is no guarantee that the previously destroyed drivers can be recovered */
check(driver_remove_destruct(&rgb_display_driver) == ERROR_NONE);
return ERROR_NONE;
}
Module rgb_display_module = {
.name = "rgb_display",
.start = start,
.stop = stop,
.symbols = nullptr,
.internal = nullptr
};
} // extern "C"
@@ -0,0 +1,413 @@
// SPDX-License-Identifier: Apache-2.0
#include <soc/soc_caps.h>
#if SOC_LCD_RGB_SUPPORTED
#include <drivers/rgb_display.h>
#include <rgb_display_module.h>
#include <tactility/delay.h>
#include <tactility/device.h>
#include <tactility/driver.h>
#include <tactility/drivers/display.h>
#include <tactility/drivers/gpio_controller.h>
#include <tactility/drivers/gpio_descriptor.h>
#include <tactility/error.h>
#include <tactility/log.h>
#include <esp_err.h>
#include <esp_lcd_panel_rgb.h>
#include <esp_lcd_panel_ops.h>
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
#include <cstdlib>
#define TAG "RgbDisplay"
#define GET_CONFIG(device) (static_cast<const RgbDisplayConfig*>((device)->config))
// Generic lvgl-module display glue (Modules/lvgl-module/source/lvgl_display.c) only ever asks
// for frame buffer index 0 and 1, so caching more than that would be dead weight.
constexpr size_t MAX_CACHED_FRAME_BUFFERS = 2;
struct RgbDisplayInternal {
esp_lcd_panel_handle_t panel_handle;
void* frame_buffers[MAX_CACHED_FRAME_BUFFERS];
uint8_t frame_buffer_count;
// Signaled by on_frame_buf_complete once per real DMA scan-out of a whole frame. Only
// waited on in draw_bitmap() when frame_buffer_count > 0 - see the comment there for why.
SemaphoreHandle_t frame_complete_semaphore;
};
// esp_lcd_rgb_panel's draw_bitmap() has a zero-copy path when color_data is one of the panel's
// own frame buffers (as returned by esp_lcd_rgb_panel_get_frame_buffer()): it just repoints which
// buffer is scanned out and returns almost instantly - well before the RGB peripheral's DMA has
// actually finished scanning out the *previous* buffer, let alone started on this one. Callers in
// full/direct LVGL render mode render straight into these real frame buffers, so if draw_bitmap()
// returned that quickly, LVGL would be free to start overwriting the *other* buffer - which may
// still be mid-scanout - producing visible tearing/flashing. on_frame_buf_complete fires once per
// actual whole-frame DMA completion (continuously, at the panel's refresh rate, independent of
// draw_bitmap calls), so waiting for the next occurrence after each draw_bitmap() genuinely
// blocks until it's safe to start writing into the frame buffers again.
static bool IRAM_ATTR on_frame_buf_complete(esp_lcd_panel_handle_t, const esp_lcd_rgb_panel_event_data_t*, void* user_ctx) {
auto* internal = static_cast<RgbDisplayInternal*>(user_ctx);
BaseType_t high_task_woken = pdFALSE;
xSemaphoreGiveFromISR(internal->frame_complete_semaphore, &high_task_woken);
return high_task_woken == pdTRUE;
}
static int pin_or_unused(const GpioPinSpec& pin) {
return pin.gpio_controller == nullptr ? -1 : static_cast<int>(pin.pin);
}
// Pulses the panel's own driver-IC reset line, if configured. Transient: the descriptor is
// released immediately after, since nothing else needs to touch this pin afterward.
static error_t perform_hardware_reset(const RgbDisplayConfig* config) {
if (config->pin_reset.gpio_controller == nullptr) {
return ERROR_NONE;
}
auto* descriptor = gpio_descriptor_acquire(config->pin_reset.gpio_controller, config->pin_reset.pin, GPIO_OWNER_GPIO);
if (descriptor == nullptr) {
LOG_E(TAG, "Failed to acquire reset GPIO descriptor");
return ERROR_RESOURCE;
}
bool ok = gpio_descriptor_set_flags(descriptor, GPIO_FLAG_DIRECTION_OUTPUT) == ERROR_NONE;
ok = ok && gpio_descriptor_set_level(descriptor, config->reset_active_high) == ERROR_NONE;
if (ok) {
delay_millis(100);
ok = gpio_descriptor_set_level(descriptor, !config->reset_active_high) == ERROR_NONE;
delay_millis(10);
}
gpio_descriptor_release(descriptor);
return ok ? ERROR_NONE : ERROR_RESOURCE;
}
// region Driver lifecycle
static error_t cache_frame_buffers(RgbDisplayInternal* internal, const RgbDisplayConfig* config) {
internal->frame_buffer_count = 0;
if (config->num_fbs == 0) {
return ERROR_NONE;
}
// esp_lcd_rgb_panel_get_frame_buffer() is variadic: the number of out-pointer arguments
// passed must match fb_num exactly, so this can't be a loop.
size_t fb_num = config->num_fbs < MAX_CACHED_FRAME_BUFFERS ? config->num_fbs : MAX_CACHED_FRAME_BUFFERS;
esp_err_t ret;
switch (fb_num) {
case 1:
ret = esp_lcd_rgb_panel_get_frame_buffer(internal->panel_handle, 1, &internal->frame_buffers[0]);
break;
case 2:
ret = esp_lcd_rgb_panel_get_frame_buffer(internal->panel_handle, 2, &internal->frame_buffers[0], &internal->frame_buffers[1]);
break;
default:
return ERROR_NONE;
}
if (ret != ESP_OK) {
LOG_E(TAG, "Failed to get frame buffer(s): %s", esp_err_to_name(ret));
return ERROR_RESOURCE;
}
internal->frame_buffer_count = (uint8_t)fb_num;
return ERROR_NONE;
}
static error_t start(Device* device) {
const auto* config = GET_CONFIG(device);
auto* internal = static_cast<RgbDisplayInternal*>(malloc(sizeof(RgbDisplayInternal)));
if (internal == nullptr) {
return ERROR_OUT_OF_MEMORY;
}
error_t reset_error = perform_hardware_reset(config);
if (reset_error != ERROR_NONE) {
LOG_E(TAG, "Failed to reset panel");
free(internal);
return reset_error;
}
esp_lcd_rgb_panel_config_t panel_config = {
.clk_src = LCD_CLK_SRC_DEFAULT,
.timings = {
.pclk_hz = config->pixel_clock_hz,
.h_res = config->horizontal_resolution,
.v_res = config->vertical_resolution,
.hsync_pulse_width = config->hsync_pulse_width,
.hsync_back_porch = config->hsync_back_porch,
.hsync_front_porch = config->hsync_front_porch,
.vsync_pulse_width = config->vsync_pulse_width,
.vsync_back_porch = config->vsync_back_porch,
.vsync_front_porch = config->vsync_front_porch,
.flags = {
.hsync_idle_low = config->hsync_idle_low,
.vsync_idle_low = config->vsync_idle_low,
.de_idle_high = config->de_idle_high,
.pclk_active_neg = config->pclk_active_neg,
.pclk_idle_high = config->pclk_idle_high,
}
},
.data_width = config->data_width,
.bits_per_pixel = config->bits_per_pixel,
.num_fbs = config->num_fbs,
.bounce_buffer_size_px = config->bounce_buffer_size_px,
.sram_trans_align = config->sram_trans_align,
.psram_trans_align = config->psram_trans_align,
.hsync_gpio_num = pin_or_unused(config->pin_hsync),
.vsync_gpio_num = pin_or_unused(config->pin_vsync),
.de_gpio_num = pin_or_unused(config->pin_de),
.pclk_gpio_num = pin_or_unused(config->pin_pclk),
.disp_gpio_num = pin_or_unused(config->pin_disp),
.data_gpio_nums = {
pin_or_unused(config->pin_data0),
pin_or_unused(config->pin_data1),
pin_or_unused(config->pin_data2),
pin_or_unused(config->pin_data3),
pin_or_unused(config->pin_data4),
pin_or_unused(config->pin_data5),
pin_or_unused(config->pin_data6),
pin_or_unused(config->pin_data7),
pin_or_unused(config->pin_data8),
pin_or_unused(config->pin_data9),
pin_or_unused(config->pin_data10),
pin_or_unused(config->pin_data11),
pin_or_unused(config->pin_data12),
pin_or_unused(config->pin_data13),
pin_or_unused(config->pin_data14),
pin_or_unused(config->pin_data15),
},
.flags = {
.disp_active_low = config->disp_active_low,
.refresh_on_demand = config->refresh_on_demand,
.fb_in_psram = config->fb_in_psram,
.double_fb = config->double_fb,
.no_fb = config->no_fb,
.bb_invalidate_cache = config->bb_invalidate_cache,
}
};
// This Config struct only exposes 16 named data pins, so on chips whose RGB peripheral has
// more data lines than that (e.g. ESP32-P4's 24), the tail of the array must be explicitly
// marked unused rather than left as the aggregate-init default of 0 (which would look like
// "GPIO0 is wired to this line").
for (size_t i = 16; i < sizeof(panel_config.data_gpio_nums) / sizeof(panel_config.data_gpio_nums[0]); i++) {
panel_config.data_gpio_nums[i] = -1;
}
esp_err_t ret = esp_lcd_new_rgb_panel(&panel_config, &internal->panel_handle);
if (ret != ESP_OK) {
LOG_E(TAG, "Failed to create panel: %s", esp_err_to_name(ret));
free(internal);
return ERROR_RESOURCE;
}
bool ok =
esp_lcd_panel_reset(internal->panel_handle) == ESP_OK &&
esp_lcd_panel_init(internal->panel_handle) == ESP_OK &&
esp_lcd_panel_swap_xy(internal->panel_handle, config->swap_xy) == ESP_OK &&
esp_lcd_panel_mirror(internal->panel_handle, config->mirror_x, config->mirror_y) == ESP_OK &&
esp_lcd_panel_invert_color(internal->panel_handle, config->invert_color) == ESP_OK;
if (!ok) {
LOG_E(TAG, "Failed to bring up panel");
esp_lcd_panel_del(internal->panel_handle);
free(internal);
return ERROR_RESOURCE;
}
error_t error = cache_frame_buffers(internal, config);
if (error != ERROR_NONE) {
esp_lcd_panel_del(internal->panel_handle);
free(internal);
return error;
}
internal->frame_complete_semaphore = xSemaphoreCreateBinary();
if (internal->frame_complete_semaphore == nullptr) {
esp_lcd_panel_del(internal->panel_handle);
free(internal);
return ERROR_OUT_OF_MEMORY;
}
esp_lcd_rgb_panel_event_callbacks_t callbacks = {};
callbacks.on_frame_buf_complete = on_frame_buf_complete;
if (esp_lcd_rgb_panel_register_event_callbacks(internal->panel_handle, &callbacks, internal) != ESP_OK) {
LOG_E(TAG, "Failed to register panel event callbacks");
vSemaphoreDelete(internal->frame_complete_semaphore);
esp_lcd_panel_del(internal->panel_handle);
free(internal);
return ERROR_RESOURCE;
}
device_set_driver_data(device, internal);
return ERROR_NONE;
}
static error_t stop(Device* device) {
auto* internal = static_cast<RgbDisplayInternal*>(device_get_driver_data(device));
if (esp_lcd_panel_del(internal->panel_handle) != ESP_OK) {
LOG_E(TAG, "Failed to delete panel");
vSemaphoreDelete(internal->frame_complete_semaphore);
free(internal);
return ERROR_RESOURCE;
}
vSemaphoreDelete(internal->frame_complete_semaphore);
free(internal);
return ERROR_NONE;
}
// endregion
// region DisplayApi
static error_t rgb_display_reset(Device* device) {
auto* internal = static_cast<RgbDisplayInternal*>(device_get_driver_data(device));
return esp_lcd_panel_reset(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static error_t rgb_display_init(Device* device) {
auto* internal = static_cast<RgbDisplayInternal*>(device_get_driver_data(device));
return esp_lcd_panel_init(internal->panel_handle) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static error_t rgb_display_draw_bitmap(Device* device, int32_t x_start, int32_t y_start, int32_t x_end, int32_t y_end, const void* color_data) {
auto* internal = static_cast<RgbDisplayInternal*>(device_get_driver_data(device));
// Only block for scan-out completion when the caller could be writing straight into one of
// the panel's own frame buffers (see on_frame_buf_complete's comment above for why that
// matters). With no real frame buffer of ours involved (frame_buffer_count == 0, e.g. LVGL
// partial-render mode with its own separate buffer), draw_bitmap does a real memcpy into the
// panel's buffer and there's no reuse race to guard against, so don't pay the up-to-one-frame
// latency cost for every small partial update.
bool wait_for_scanout = internal->frame_buffer_count > 0;
if (wait_for_scanout) {
xSemaphoreTake(internal->frame_complete_semaphore, 0); // clear any already-pending signal
}
if (esp_lcd_panel_draw_bitmap(internal->panel_handle, x_start, y_start, x_end, y_end, color_data) != ESP_OK) {
return ERROR_RESOURCE;
}
if (wait_for_scanout) {
xSemaphoreTake(internal->frame_complete_semaphore, portMAX_DELAY);
}
return ERROR_NONE;
}
static error_t rgb_display_mirror(Device* device, bool x_axis, bool y_axis) {
auto* internal = static_cast<RgbDisplayInternal*>(device_get_driver_data(device));
return esp_lcd_panel_mirror(internal->panel_handle, x_axis, y_axis) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static error_t rgb_display_swap_xy(Device* device, bool swap_axes) {
auto* internal = static_cast<RgbDisplayInternal*>(device_get_driver_data(device));
return esp_lcd_panel_swap_xy(internal->panel_handle, swap_axes) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static bool rgb_display_get_swap_xy(Device* device) {
return GET_CONFIG(device)->swap_xy;
}
static bool rgb_display_get_mirror_x(Device* device) {
return GET_CONFIG(device)->mirror_x;
}
static bool rgb_display_get_mirror_y(Device* device) {
return GET_CONFIG(device)->mirror_y;
}
// RGB panels are raw scan-out framebuffers with no addressable-window concept the way MIPI/SPI
// panels have, so there's no gap to set.
static error_t rgb_display_set_gap(Device*, int32_t, int32_t) {
return ERROR_NOT_SUPPORTED;
}
static error_t rgb_display_invert_color(Device* device, bool invert_color_data) {
auto* internal = static_cast<RgbDisplayInternal*>(device_get_driver_data(device));
return esp_lcd_panel_invert_color(internal->panel_handle, invert_color_data) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
static error_t rgb_display_disp_on_off(Device* device, bool on_off) {
auto* internal = static_cast<RgbDisplayInternal*>(device_get_driver_data(device));
return esp_lcd_panel_disp_on_off(internal->panel_handle, on_off) == ESP_OK ? ERROR_NONE : ERROR_RESOURCE;
}
// RGB panels have no MIPI DCS command interface, so there's no sleep mode to enter.
static error_t rgb_display_disp_sleep(Device*, bool) {
return ERROR_NOT_SUPPORTED;
}
static enum DisplayColorFormat rgb_display_get_color_format(Device*) {
return DISPLAY_COLOR_FORMAT_RGB565;
}
static uint16_t rgb_display_get_resolution_x(Device* device) {
return GET_CONFIG(device)->horizontal_resolution;
}
static uint16_t rgb_display_get_resolution_y(Device* device) {
return GET_CONFIG(device)->vertical_resolution;
}
static void rgb_display_get_frame_buffer(Device* device, uint8_t index, void** out_buffer) {
auto* internal = static_cast<RgbDisplayInternal*>(device_get_driver_data(device));
*out_buffer = index < internal->frame_buffer_count ? internal->frame_buffers[index] : nullptr;
}
static uint8_t rgb_display_get_frame_buffer_count(Device* device) {
auto* internal = static_cast<RgbDisplayInternal*>(device_get_driver_data(device));
return internal->frame_buffer_count;
}
static error_t rgb_display_get_backlight(Device* device, Device** backlight) {
auto* configured_backlight = GET_CONFIG(device)->backlight;
if (configured_backlight == nullptr) {
return ERROR_NOT_SUPPORTED;
}
*backlight = configured_backlight;
return ERROR_NONE;
}
// endregion
static const DisplayApi rgb_display_api = {
.reset = rgb_display_reset,
.init = rgb_display_init,
.draw_bitmap = rgb_display_draw_bitmap,
.mirror = rgb_display_mirror,
.swap_xy = rgb_display_swap_xy,
.get_swap_xy = rgb_display_get_swap_xy,
.get_mirror_x = rgb_display_get_mirror_x,
.get_mirror_y = rgb_display_get_mirror_y,
.set_gap = rgb_display_set_gap,
.invert_color = rgb_display_invert_color,
.disp_on_off = rgb_display_disp_on_off,
.disp_sleep = rgb_display_disp_sleep,
.get_color_format = rgb_display_get_color_format,
.get_resolution_x = rgb_display_get_resolution_x,
.get_resolution_y = rgb_display_get_resolution_y,
.get_frame_buffer = rgb_display_get_frame_buffer,
.get_frame_buffer_count = rgb_display_get_frame_buffer_count,
.get_backlight = rgb_display_get_backlight,
};
Driver rgb_display_driver = {
.name = "rgb_display",
.compatible = (const char*[]) { "espressif,esp32-rgb-display", nullptr },
.start_device = start,
.stop_device = stop,
.api = &rgb_display_api,
.device_type = &DISPLAY_TYPE,
.owner = &rgb_display_module,
.internal = nullptr
};
#endif // SOC_LCD_RGB_SUPPORTED
+11
View File
@@ -0,0 +1,11 @@
cmake_minimum_required(VERSION 3.20)
include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake")
file(GLOB_RECURSE SOURCE_FILES "source/*.c*")
tactility_add_module(tca9534-module
SRCS ${SOURCE_FILES}
INCLUDE_DIRS include/
REQUIRES TactilityKernel
)
@@ -0,0 +1,195 @@
Apache License
==============
_Version 2.0, January 2004_
_&lt;<http://www.apache.org/licenses/>&gt;_
### Terms and Conditions for use, reproduction, and distribution
#### 1. Definitions
“License” shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
“Licensor” shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
“Legal Entity” shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, “control” means **(i)** the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the
outstanding shares, or **(iii)** beneficial ownership of such entity.
“You” (or “Your”) shall mean an individual or Legal Entity exercising
permissions granted by this License.
“Source” form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
“Object” form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
“Work” shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
“Derivative Works” shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
“Contribution” shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
“submitted” means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as “Not a Contribution.”
“Contributor” shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
#### 2. Grant of Copyright License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
#### 3. Grant of Patent License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
#### 4. Redistribution
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
* **(a)** You must give any other recipients of the Work or Derivative Works a copy of
this License; and
* **(b)** You must cause any modified files to carry prominent notices stating that You
changed the files; and
* **(c)** You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
#### 5. Submission of Contributions
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
#### 6. Trademarks
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
#### 7. Disclaimer of Warranty
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
#### 8. Limitation of Liability
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
#### 9. Accepting Warranty or Additional Liability
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
_END OF TERMS AND CONDITIONS_
### APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets `[]` replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same “printed page” as the copyright notice for easier identification within
third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+7
View File
@@ -0,0 +1,7 @@
# TCA9534 I/O expander
A driver for the `TCA9534` 8-bit I2C-bus I/O expander by Texas Instruments.
See https://www.ti.com/product/TCA9534
License: [Apache v2.0](LICENSE-Apache-2.0.md)
@@ -0,0 +1,5 @@
description: TI TCA9534 8-bit I2C-bus I/O expander
include: ["i2c-device.yaml"]
compatible: "ti,tca9534"
+3
View File
@@ -0,0 +1,3 @@
dependencies:
- TactilityKernel
bindings: bindings
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/bindings/bindings.h>
#include <drivers/tca9534.h>
#ifdef __cplusplus
extern "C" {
#endif
DEFINE_DEVICETREE(tca9534, struct Tca9534Config)
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
struct Tca9534Config {
/** Address on bus */
uint8_t address;
};
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/module.h>
#ifdef __cplusplus
extern "C" {
#endif
extern struct Module tca9534_module;
#ifdef __cplusplus
}
#endif
+32
View File
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: Apache-2.0
#include <tactility/check.h>
#include <tactility/driver.h>
#include <tactility/module.h>
extern "C" {
extern Driver tca9534_driver;
static error_t start() {
/* We crash when construct fails, because if a single driver fails to construct,
* there is no guarantee that the previously constructed drivers can be destroyed */
check(driver_construct_add(&tca9534_driver) == ERROR_NONE);
return ERROR_NONE;
}
static error_t stop() {
/* We crash when destruct fails, because if a single driver fails to destruct,
* there is no guarantee that the previously destroyed drivers can be recovered */
check(driver_remove_destruct(&tca9534_driver) == ERROR_NONE);
return ERROR_NONE;
}
Module tca9534_module = {
.name = "tca9534",
.start = start,
.stop = stop,
.symbols = nullptr,
.internal = nullptr
};
}
+169
View File
@@ -0,0 +1,169 @@
// SPDX-License-Identifier: Apache-2.0
#include <drivers/tca9534.h>
#include <tca9534_module.h>
#include <tactility/device.h>
#include <tactility/driver.h>
#include <tactility/drivers/gpio_controller.h>
#include <tactility/drivers/gpio_descriptor.h>
#include <tactility/drivers/i2c_controller.h>
#include <tactility/log.h>
#define TAG "TCA9534"
#define GET_CONFIG(device) (static_cast<const Tca9534Config*>((device)->config))
constexpr auto TCA9534_REGISTER_INPUT = 0x00;
constexpr auto TCA9534_REGISTER_OUTPUT = 0x01;
constexpr auto TCA9534_REGISTER_POLARITY = 0x02;
constexpr auto TCA9534_REGISTER_CONFIG = 0x03;
static error_t start(Device* device) {
auto* parent = device_get_parent(device);
if (device_get_type(parent) != &I2C_CONTROLLER_TYPE) {
LOG_E(TAG, "Parent device is not I2C");
return ERROR_RESOURCE;
}
return gpio_controller_init_descriptors(device, 8, nullptr);
}
static error_t stop(Device* device) {
check(gpio_controller_deinit_descriptors(device) == ERROR_NONE);
return ERROR_NONE;
}
extern "C" {
static error_t set_level(GpioDescriptor* descriptor, bool high) {
auto* device = descriptor->controller;
auto* parent = device_get_parent(device);
auto address = GET_CONFIG(device)->address;
uint8_t bit = 1 << descriptor->pin;
return high
? i2c_controller_register8_set_bits(parent, address, TCA9534_REGISTER_OUTPUT, bit, portMAX_DELAY)
: i2c_controller_register8_reset_bits(parent, address, TCA9534_REGISTER_OUTPUT, bit, portMAX_DELAY);
}
static error_t get_level(GpioDescriptor* descriptor, bool* high) {
auto* device = descriptor->controller;
auto* parent = device_get_parent(device);
auto address = GET_CONFIG(device)->address;
uint8_t bits;
error_t err = i2c_controller_register8_get(parent, address, TCA9534_REGISTER_INPUT, &bits, portMAX_DELAY);
if (err != ERROR_NONE) {
return err;
}
*high = (bits & (1 << descriptor->pin)) != 0;
return ERROR_NONE;
}
static error_t set_flags(GpioDescriptor* descriptor, gpio_flags_t flags) {
// The TCA9534 only supports direction and polarity inversion. Pull-up/down and
// high-impedance are not present in its register map.
if (flags & (GPIO_FLAG_PULL_UP | GPIO_FLAG_PULL_DOWN | GPIO_FLAG_HIGH_IMPEDANCE)) {
return ERROR_NOT_SUPPORTED;
}
// The polarity register only inverts what's read back from an input pin;
// set_level() still drives outputs at the raw level. Accepting ACTIVE_LOW
// on an output would silently not do what it implies.
if ((flags & GPIO_FLAG_ACTIVE_LOW) && (flags & GPIO_FLAG_DIRECTION_OUTPUT)) {
return ERROR_NOT_SUPPORTED;
}
auto* device = descriptor->controller;
auto* parent = device_get_parent(device);
auto address = GET_CONFIG(device)->address;
uint8_t bit = 1 << descriptor->pin;
error_t err;
// Direction: configuration bit is 1 for input, 0 for output.
if (flags & GPIO_FLAG_DIRECTION_OUTPUT) {
err = i2c_controller_register8_reset_bits(parent, address, TCA9534_REGISTER_CONFIG, bit, portMAX_DELAY);
} else {
err = i2c_controller_register8_set_bits(parent, address, TCA9534_REGISTER_CONFIG, bit, portMAX_DELAY);
}
if (err != ERROR_NONE) {
return err;
}
// Polarity inversion (mainly relevant for active-low inputs).
if (flags & GPIO_FLAG_ACTIVE_LOW) {
err = i2c_controller_register8_set_bits(parent, address, TCA9534_REGISTER_POLARITY, bit, portMAX_DELAY);
} else {
err = i2c_controller_register8_reset_bits(parent, address, TCA9534_REGISTER_POLARITY, bit, portMAX_DELAY);
}
return err;
}
static error_t get_flags(GpioDescriptor* descriptor, gpio_flags_t* flags) {
auto* device = descriptor->controller;
auto* parent = device_get_parent(device);
auto address = GET_CONFIG(device)->address;
uint8_t bit = 1 << descriptor->pin;
uint8_t val;
error_t err;
gpio_flags_t f = GPIO_FLAG_NONE;
err = i2c_controller_register8_get(parent, address, TCA9534_REGISTER_CONFIG, &val, portMAX_DELAY);
if (err != ERROR_NONE) return err;
f |= (val & bit) ? GPIO_FLAG_DIRECTION_INPUT : GPIO_FLAG_DIRECTION_OUTPUT;
err = i2c_controller_register8_get(parent, address, TCA9534_REGISTER_POLARITY, &val, portMAX_DELAY);
if (err != ERROR_NONE) return err;
f |= (val & bit) ? GPIO_FLAG_ACTIVE_LOW : GPIO_FLAG_ACTIVE_HIGH;
*flags = f;
return ERROR_NONE;
}
static error_t get_native_pin_number(GpioDescriptor* descriptor, void* pin_number) {
return ERROR_NOT_SUPPORTED;
}
static error_t add_callback(GpioDescriptor* descriptor, void (*callback)(void*), void* arg) {
return ERROR_NOT_SUPPORTED;
}
static error_t remove_callback(GpioDescriptor* descriptor) {
return ERROR_NOT_SUPPORTED;
}
static error_t enable_interrupt(GpioDescriptor* descriptor) {
return ERROR_NOT_SUPPORTED;
}
static error_t disable_interrupt(GpioDescriptor* descriptor) {
return ERROR_NOT_SUPPORTED;
}
const static GpioControllerApi tca9534_gpio_api = {
.set_level = set_level,
.get_level = get_level,
.set_flags = set_flags,
.get_flags = get_flags,
.get_native_pin_number = get_native_pin_number,
.add_callback = add_callback,
.remove_callback = remove_callback,
.enable_interrupt = enable_interrupt,
.disable_interrupt = disable_interrupt
};
Driver tca9534_driver = {
.name = "tca9534",
.compatible = (const char*[]) { "ti,tca9534", nullptr },
.start_device = start,
.stop_device = stop,
.api = static_cast<const void*>(&tca9534_gpio_api),
.device_type = &GPIO_CONTROLLER_TYPE,
.owner = &tca9534_module,
.internal = nullptr
};
}