drivers: input: Add TMA525B capacitive touch controller driver

This commit adds support for the Parade Tech TMA525B capacitive touch
controller. The driver supports both interrupt-driven and polling modes,
and can handle up to 4 simultaneous touch points.

Key features:
- I2C communication interface
- Multi-touch support (up to 4 touch points)
- Interrupt mode with GPIO callback support
- Polling mode with timer
- Power management support with PM notifier
- Reset and power control via GPIO
- Touch event tracking (down, contact, up)

Signed-off-by: Kate Wang <yumeng.wang@nxp.com>
This commit is contained in:
Kate Wang
2026-01-14 15:39:28 +08:00
committed by Anas Nashif
parent 2c1d2c8e83
commit 0e6c2aedaf
5 changed files with 497 additions and 0 deletions

View File

@@ -41,6 +41,7 @@ zephyr_library_sources_ifdef(CONFIG_INPUT_RENESAS_RX_CTSU input_renesas_rx_ctsu.
zephyr_library_sources_ifdef(CONFIG_INPUT_SBUS input_sbus.c)
zephyr_library_sources_ifdef(CONFIG_INPUT_STM32_TSC_KEYS input_tsc_keys.c)
zephyr_library_sources_ifdef(CONFIG_INPUT_STMPE811 input_stmpe811.c)
zephyr_library_sources_ifdef(CONFIG_INPUT_TMA525B input_tma525b.c)
zephyr_library_sources_ifdef(CONFIG_INPUT_TOUCH input_touch.c)
zephyr_library_sources_ifdef(CONFIG_INPUT_VS1838B input_vs1838b.c)
zephyr_library_sources_ifdef(CONFIG_INPUT_XEC_KBD input_xec_kbd.c)

View File

@@ -43,6 +43,7 @@ source "drivers/input/Kconfig.rts5912"
source "drivers/input/Kconfig.sbus"
source "drivers/input/Kconfig.sdl"
source "drivers/input/Kconfig.stmpe811"
source "drivers/input/Kconfig.tma525b"
source "drivers/input/Kconfig.touch"
source "drivers/input/Kconfig.tsc_keys"
source "drivers/input/Kconfig.vs1838b"

View File

@@ -0,0 +1,42 @@
# Copyright (c) 2025 NXP
# SPDX-License-Identifier: Apache-2.0
menuconfig INPUT_TMA525B
bool "TMA525B capacitive touch controller"
default y
depends on DT_HAS_PARADE_TMA525B_ENABLED
select I2C
select INPUT_TOUCH
help
Enable support for Parade Tech TMA525B capacitive touch controller.
if INPUT_TMA525B
config INPUT_TMA525B_INTERRUPT
bool "Interrupt mode"
default y if $(dt_compat_any_has_prop,$(DT_COMPAT_PARADE_TMA525B),int-gpios)
help
Enable interrupt mode for TMA525B touch controller. If disabled,
the driver will use polling mode.
config INPUT_TMA525B_PERIOD_MS
int "Polling period (ms)"
default 10
depends on !INPUT_TMA525B_INTERRUPT
help
Polling period in milliseconds when the controller is in polling mode.
config INPUT_TMA525B_MAX_TOUCH_POINTS
int "Maximum number of touch points"
default 1
range 1 4
help
Maximum number of touch points supported by the controller.
config INPUT_TMA525B_RETRY_TIMES
int "Retry times for controller to enter application mode"
default 10
help
Retry times for controller to enter application mode after power up.
endif # INPUT_TMA525B

View File

@@ -0,0 +1,429 @@
/*
* Copyright (c) 2025 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT parade_tma525b
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/input/input.h>
#include <zephyr/input/input_touch.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/pm/device.h>
#include <zephyr/pm/device_runtime.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(tma525b, CONFIG_INPUT_LOG_LEVEL);
#define TMA525B_BOOT_DELAY_MS 120U
/* TMA525B maximum number of simultaneously detected touches */
#define TMA525B_MAX_TOUCHES 4U
/* TMA525B register address where touch data begin */
#define TMA525B_TOUCH_DATA_SUBADDR 1U
/* TMA525B raw touch data length */
#define TMA525B_TOUCH_DATA_LEN 264U
/* TMA525B touch data header length to read first */
#define TMA525B_TOUCH_DATA_LEN_BYTES 2U
/* TMA525B report ID for touch data */
#define TMA525B_REPORT_ID_TOUCH 0x01U
/* Touch event types - matching fsl_tma525b.h */
enum touch_event {
TOUCH_RESERVED = 0, /* No touch event detected */
TOUCH_DOWN = 1, /* Touch down event detected */
TOUCH_CONTACT = 2, /* Touch point moving */
TOUCH_UP = 3 /* Touch event finished */
};
/* TMA525B touch point structure */
struct tma525b_touch_point {
uint8_t touch_type; /* 0 for standard finger/glove, 1 for proximity. Not used*/
uint8_t event_id; /* Bit 0-4: touch ID, bit 5-6: touch event */
uint16_t x;
uint16_t y;
uint8_t pressure; /* Touch intensity. Not used in current driver model. */
uint16_t axis_len_mm; /* Axis length. Not used in current driver model. */
/* Angle between panel vertical axis and major axis. Not used in current driver model. */
uint8_t orientation;
} __packed;
/* TMA525B touch data structure */
struct tma525b_touch_data {
uint16_t length; /* Packet length. */
uint8_t report_id;
/* Timestamp in 100us units. Not used in current driver model. */
uint16_t timestamp_100us;
uint8_t num_touch; /* Number of touch points detected */
/* 2 MSB: report counter, 3 LSB: noise effects. Not used in current driver model. */
uint8_t report_noise;
struct tma525b_touch_point touch[TMA525B_MAX_TOUCHES];
} __packed;
/* Macros for extracting touch data. */
#define TMA525B_TOUCH_POINT_GET_ID(event_id) (event_id & 0x1F)
#define TMA525B_TOUCH_POINT_GET_EVENT(event_id) ((event_id & 0x60) >> 5U)
/* TMA525B configuration (from device tree) */
struct tma525b_config {
struct input_touchscreen_common_config common;
struct i2c_dt_spec bus;
struct gpio_dt_spec pwr_gpio;
struct gpio_dt_spec rst_gpio;
struct gpio_dt_spec int_gpio;
};
/* TMA525B runtime data */
struct tma525b_data {
const struct device *dev;
struct k_work work;
uint8_t touch_buf[TMA525B_TOUCH_DATA_LEN];
uint8_t prev_touch_count;
struct {
uint8_t id;
uint16_t x;
uint16_t y;
} prev_touches[TMA525B_MAX_TOUCHES];
#ifdef CONFIG_INPUT_TMA525B_INTERRUPT
struct gpio_callback int_gpio_cb;
#else
struct k_timer timer;
#endif
};
INPUT_TOUCH_STRUCT_CHECK(struct tma525b_config);
static int tma525b_process(const struct device *dev)
{
const struct tma525b_config *config = dev->config;
struct tma525b_data *data = dev->data;
struct tma525b_touch_data *touch_data;
int ret;
uint8_t touch_id;
enum touch_event event;
uint8_t touch_count;
/* Read first two bytes to get the data length */
ret = i2c_burst_read_dt(&config->bus, TMA525B_TOUCH_DATA_SUBADDR, data->touch_buf,
TMA525B_TOUCH_DATA_LEN_BYTES);
if (ret < 0) {
LOG_ERR("Failed to read data length: %d", ret);
return ret;
}
touch_data = (struct tma525b_touch_data *)data->touch_buf;
/* Validate touch data length */
if (touch_data->length <= 0x02 || touch_data->length > TMA525B_TOUCH_DATA_LEN) {
LOG_DBG("Invalid touch data length: %d", touch_data->length);
return -EINVAL;
}
/* Read the full touch data according to length */
ret = i2c_burst_read_dt(&config->bus, TMA525B_TOUCH_DATA_SUBADDR, data->touch_buf,
touch_data->length);
if (ret < 0) {
LOG_ERR("Failed to read touch data: %d", ret);
return ret;
}
/* Only when report ID is 0x01 the touch data is valid */
if (touch_data->report_id != TMA525B_REPORT_ID_TOUCH) {
LOG_DBG("Invalid report ID: 0x%02x", touch_data->report_id);
return -EINVAL;
}
/* Get number of touches */
touch_count = MIN(touch_data->num_touch, CONFIG_INPUT_TMA525B_MAX_TOUCH_POINTS);
/* Process current touch points */
for (uint8_t i = 0; i < touch_count; i++) {
int x, y;
touch_id = TMA525B_TOUCH_POINT_GET_ID(touch_data->touch[i].event_id);
event = TMA525B_TOUCH_POINT_GET_EVENT(touch_data->touch[i].event_id);
x = touch_data->touch[i].x;
y = touch_data->touch[i].y;
/* Update coordinates only if there is valid touch event */
if (event == TOUCH_RESERVED) {
continue;
}
if (CONFIG_INPUT_TMA525B_MAX_TOUCH_POINTS > 1) {
input_report_abs(dev, INPUT_ABS_MT_SLOT, touch_id, true, K_FOREVER);
}
input_touchscreen_report_pos(dev, x, y, K_FOREVER);
/* Report touch state based on event type */
if (event == TOUCH_DOWN || event == TOUCH_CONTACT) {
input_report_key(dev, INPUT_BTN_TOUCH, 1, true, K_FOREVER);
} else if (event == TOUCH_UP) {
input_report_key(dev, INPUT_BTN_TOUCH, 0, true, K_FOREVER);
}
/* Store current touch for tracking */
data->prev_touches[i].id = touch_id;
data->prev_touches[i].x = x;
data->prev_touches[i].y = y;
}
/* Handle release events for touches that disappeared */
for (uint8_t i = 0; i < data->prev_touch_count; i++) {
bool touch_found = false;
/* Look for previous touch in current touch list */
for (uint8_t j = 0; j < touch_count; j++) {
if (data->prev_touches[i].id ==
TMA525B_TOUCH_POINT_GET_ID(touch_data->touch[j].event_id)) {
touch_found = true;
break;
}
}
/* If previous touch not found, report release */
if (!touch_found) {
if (CONFIG_INPUT_TMA525B_MAX_TOUCH_POINTS > 1) {
input_report_abs(dev, INPUT_ABS_MT_SLOT, data->prev_touches[i].id,
true, K_FOREVER);
}
input_touchscreen_report_pos(dev, data->prev_touches[i].x,
data->prev_touches[i].y, K_FOREVER);
input_report_key(dev, INPUT_BTN_TOUCH, 0, true, K_FOREVER);
}
}
/* Update previous touch count */
data->prev_touch_count = touch_count;
return 0;
}
static void tma525b_work_handler(struct k_work *work)
{
struct tma525b_data *data = CONTAINER_OF(work, struct tma525b_data, work);
tma525b_process(data->dev);
}
#ifdef CONFIG_INPUT_TMA525B_INTERRUPT
static void tma525b_isr_handler(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
{
struct tma525b_data *data = CONTAINER_OF(cb, struct tma525b_data, int_gpio_cb);
k_work_submit(&data->work);
}
#else
static void tma525b_timer_handler(struct k_timer *timer)
{
struct tma525b_data *data = CONTAINER_OF(timer, struct tma525b_data, timer);
k_work_submit(&data->work);
}
#endif
static int tma525b_chip_init(const struct device *dev)
{
const struct tma525b_config *config = dev->config;
uint8_t read_buf[2];
int ret;
int retry = 0;
/* Power on sequence */
if (config->pwr_gpio.port != NULL) {
ret = gpio_pin_set_dt(&config->pwr_gpio, 1);
if (ret < 0) {
LOG_ERR("Failed to enable power: %d", ret);
return ret;
}
k_sleep(K_MSEC(10));
}
/* Reset the controller */
if (config->rst_gpio.port != NULL) {
gpio_pin_set_dt(&config->rst_gpio, 1);
k_sleep(K_MSEC(5));
gpio_pin_set_dt(&config->rst_gpio, 0);
}
k_sleep(K_MSEC(TMA525B_BOOT_DELAY_MS));
/* Enter application mode - check if ready */
while (retry < CONFIG_INPUT_TMA525B_RETRY_TIMES) {
ret = i2c_burst_read_dt(&config->bus, TMA525B_TOUCH_DATA_SUBADDR, read_buf,
sizeof(read_buf));
if (ret == 0) {
/* Check for application mode signature */
if ((read_buf[0] == 0x02U && read_buf[1] == 0x00U) ||
read_buf[1] == 0xFFU) {
LOG_INF("TMA525B entered application mode");
break;
}
}
k_sleep(K_MSEC(TMA525B_BOOT_DELAY_MS));
retry++;
}
if (retry == 0U) {
LOG_ERR("TMA525B failed to enter application mode");
return -ENODEV;
}
return 0;
}
static int tma525b_init(const struct device *dev)
{
const struct tma525b_config *config = dev->config;
struct tma525b_data *data = dev->data;
int ret;
data->dev = dev;
if (!i2c_is_ready_dt(&config->bus)) {
LOG_ERR("I2C controller not ready");
return -ENODEV;
}
/* Configure power GPIO if available */
if (config->pwr_gpio.port != NULL) {
if (!gpio_is_ready_dt(&config->pwr_gpio)) {
LOG_ERR("Power GPIO controller not ready");
return -ENODEV;
}
ret = gpio_pin_configure_dt(&config->pwr_gpio, GPIO_OUTPUT_INACTIVE);
if (ret < 0) {
LOG_ERR("Failed to configure power GPIO: %d", ret);
return ret;
}
}
/* Configure reset GPIO if available */
if (config->rst_gpio.port != NULL) {
if (!gpio_is_ready_dt(&config->rst_gpio)) {
LOG_ERR("Reset GPIO controller not ready");
return -ENODEV;
}
ret = gpio_pin_configure_dt(&config->rst_gpio, GPIO_OUTPUT_INACTIVE);
if (ret < 0) {
LOG_ERR("Failed to configure reset GPIO: %d", ret);
return ret;
}
}
/* Initialize work queue */
k_work_init(&data->work, tma525b_work_handler);
/* Initialize the chip */
ret = tma525b_chip_init(dev);
if (ret < 0) {
LOG_ERR("Failed to initialize TMA525B chip: %d", ret);
return ret;
}
#ifdef CONFIG_INPUT_TMA525B_INTERRUPT
if (!gpio_is_ready_dt(&config->int_gpio)) {
LOG_ERR("Interrupt GPIO controller not ready");
return -ENODEV;
}
ret = gpio_pin_configure_dt(&config->int_gpio, GPIO_INPUT);
if (ret < 0) {
LOG_ERR("Failed to configure interrupt GPIO: %d", ret);
return ret;
}
ret = gpio_pin_interrupt_configure_dt(&config->int_gpio, GPIO_INT_EDGE_TO_ACTIVE);
if (ret < 0) {
LOG_ERR("Failed to configure interrupt: %d", ret);
return ret;
}
gpio_init_callback(&data->int_gpio_cb, tma525b_isr_handler, BIT(config->int_gpio.pin));
ret = gpio_add_callback(config->int_gpio.port, &data->int_gpio_cb);
if (ret < 0) {
LOG_ERR("Failed to add GPIO callback: %d", ret);
return ret;
}
LOG_DBG("TMA525B using interrupt mode");
#else
k_timer_init(&data->timer, tma525b_timer_handler, NULL);
k_timer_start(&data->timer, K_MSEC(CONFIG_INPUT_TMA525B_PERIOD_MS),
K_MSEC(CONFIG_INPUT_TMA525B_PERIOD_MS));
LOG_DBG("TMA525B using polling mode");
#endif
ret = pm_device_runtime_enable(dev);
if (ret < 0 && ret != -ENOTSUP) {
LOG_ERR("Failed to enable runtime power management: %d", ret);
return ret;
}
return 0;
}
#ifdef CONFIG_PM_DEVICE
static int tma525b_pm_action(const struct device *dev, enum pm_device_action action)
{
const struct tma525b_config *config = dev->config;
#ifndef CONFIG_INPUT_TMA525B_INTERRUPT
struct tma525b_data *data = dev->data;
#endif
int ret;
switch (action) {
case PM_DEVICE_ACTION_SUSPEND:
/* Power down the device */
if (config->pwr_gpio.port != NULL) {
ret = gpio_pin_set_dt(&config->pwr_gpio, 0);
if (ret < 0) {
return ret;
}
}
#ifndef CONFIG_INPUT_TMA525B_INTERRUPT
k_timer_stop(&data->timer);
#endif
break;
case PM_DEVICE_ACTION_RESUME:
/* Re-initialize the chip on resume */
ret = tma525b_chip_init(dev);
if (ret < 0) {
return ret;
}
#ifndef CONFIG_INPUT_TMA525B_INTERRUPT
k_timer_start(&data->timer, K_MSEC(CONFIG_INPUT_TMA525B_PERIOD_MS),
K_MSEC(CONFIG_INPUT_TMA525B_PERIOD_MS));
#endif
break;
default:
return -ENOTSUP;
}
return 0;
}
#endif
#define TMA525B_INIT(index) \
PM_DEVICE_DT_INST_DEFINE(index, tma525b_pm_action); \
static const struct tma525b_config tma525b_config_##index = { \
.common = INPUT_TOUCH_DT_INST_COMMON_CONFIG_INIT(index), \
.bus = I2C_DT_SPEC_INST_GET(index), \
.rst_gpio = GPIO_DT_SPEC_INST_GET_OR(index, reset_gpios, {0}), \
.int_gpio = GPIO_DT_SPEC_INST_GET_OR(index, int_gpios, {0}), \
.pwr_gpio = GPIO_DT_SPEC_INST_GET_OR(index, power_gpios, {0}), \
}; \
static struct tma525b_data tma525b_data_##index; \
DEVICE_DT_INST_DEFINE(index, tma525b_init, PM_DEVICE_DT_INST_GET(index), \
&tma525b_data_##index, &tma525b_config_##index, POST_KERNEL, \
CONFIG_INPUT_INIT_PRIORITY, NULL);
DT_INST_FOREACH_STATUS_OKAY(TMA525B_INIT)

View File

@@ -0,0 +1,24 @@
# Copyright (c) 2025 NXP
# SPDX-License-Identifier: Apache-2.0
description: TMA525b capacitive touch controller
compatible: "parade,tma525b"
include: [i2c-device.yaml, touchscreen-common.yaml]
properties:
int-gpios:
type: phandle-array
description: |
Interrupt GPIO. Used by the controller to signal touch data is
available. Active low.
reset-gpios:
type: phandle-array
description: |
Reset GPIO. Used to reset the controller during initialization, and
to wake it from hibernation mode. Active low.
power-gpios:
type: phandle-array
description: |
Power on GPIO. Used to enable the controller during initialization.