drivers: input: add driver for stmpe811 i2c touch controller

This commit adds STMPE811 I2C touch controller driver.

Signed-off-by: Brunon Blok <bblok@internships.antmicro.com>
Signed-off-by: Mateusz Sierszulski <msierszulski@antmicro.com>
This commit is contained in:
Brunon Blok 2023-08-11 16:03:03 +02:00 committed by Carles Cufí
parent 2857f781b4
commit bf830ba780
6 changed files with 697 additions and 0 deletions

View file

@ -10,6 +10,7 @@ zephyr_library_sources_ifdef(CONFIG_INPUT_GPIO_KEYS input_gpio_keys.c)
zephyr_library_sources_ifdef(CONFIG_INPUT_GPIO_QDEC input_gpio_qdec.c)
zephyr_library_sources_ifdef(CONFIG_INPUT_GT911 input_gt911.c)
zephyr_library_sources_ifdef(CONFIG_INPUT_NPCX_KBD input_npcx_kbd.c)
zephyr_library_sources_ifdef(CONFIG_INPUT_STMPE811 input_stmpe811.c)
zephyr_library_sources_ifdef(CONFIG_INPUT_XPT2046 input_xpt2046.c)
if (CONFIG_INPUT_SDL_TOUCH)

View file

@ -13,6 +13,7 @@ source "drivers/input/Kconfig.gpio_qdec"
source "drivers/input/Kconfig.gt911"
source "drivers/input/Kconfig.npcx"
source "drivers/input/Kconfig.sdl"
source "drivers/input/Kconfig.stmpe811"
source "drivers/input/Kconfig.xpt2046"
endmenu # Input Drivers

View file

@ -0,0 +1,10 @@
# Copyright (c) 2023 Antmicro <www.antmicro.com>
# SPDX-License-Identifier: Apache-2.0
config INPUT_STMPE811
bool "STMPE811 touch driver"
default y
depends on DT_HAS_ST_STMPE811_ENABLED
select I2C
help
Enable driver for STMPE811 touch panel.

View file

@ -0,0 +1,548 @@
/**
* Copyright (c) 2023 Antmicro <www.antmicro.com>
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT st_stmpe811
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/input/input.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(stmpe811, CONFIG_INPUT_LOG_LEVEL);
#define CHIP_ID 0x0811U
/* Touch Screen Pins definition */
#define STMPE811_GPIO_PIN_4 BIT(4)
#define STMPE811_GPIO_PIN_5 BIT(5)
#define STMPE811_GPIO_PIN_6 BIT(6)
#define STMPE811_GPIO_PIN_7 BIT(7)
#define STMPE811_TOUCH_YD STMPE811_GPIO_PIN_7
#define STMPE811_TOUCH_XD STMPE811_GPIO_PIN_6
#define STMPE811_TOUCH_YU STMPE811_GPIO_PIN_5
#define STMPE811_TOUCH_XU STMPE811_GPIO_PIN_4
#define STMPE811_TOUCH_IO_ALL \
(STMPE811_TOUCH_YD | STMPE811_TOUCH_XD | STMPE811_TOUCH_YU | STMPE811_TOUCH_XU)
/* Registers */
#define STMPE811_CHP_ID_LSB_REG 0x00U
#define STMPE811_ADC_CTRL1_REG 0x20U
#define STMPE811_ADC_CTRL2_REG 0x21U
#define STMPE811_SYS_CTRL1_REG 0x03U
#define STMPE811_SYS_CTRL2_REG 0x04U
#define STMPE811_TSC_CFG_REG 0x41U
#define STMPE811_IO_AF_REG 0x17U
#define STMPE811_FIFO_TH_REG 0x4AU
#define STMPE811_FIFO_STA_REG 0x4BU
#define STMPE811_FIFO_SIZE_REG 0x4CU
#define STMPE811_TSC_FRACT_XYZ_REG 0x56U
#define STMPE811_TSC_I_DRIVE_REG 0x58U
#define STMPE811_TSC_CTRL_REG 0x40U
#define STMPE811_INT_STA_REG 0x0BU
#define STMPE811_TSC_DATA_NON_INC_REG 0xD7U
#define STMPE811_INT_CTRL_REG 0x09U
#define STMPE811_INT_EN_REG 0x0AU
/* Touch detected bit */
#define STMPE811_TSC_CTRL_BIT_TOUCH_DET BIT(7)
/* Global interrupt enable bit */
#define STMPE811_INT_CTRL_BIT_GLOBAL_INT BIT(0)
/* IO expander functionalities */
#define STMPE811_SYS_CTRL2_BIT_ADC_FCT BIT(0)
#define STMPE811_SYS_CTRL2_BIT_TS_FCT BIT(1)
#define STMPE811_SYS_CTRL2_BIT_IO_FCT BIT(2)
/* Global Interrupts definitions */
#define STMPE811_INT_BIT_FIFO_THRESHOLD BIT(1) /* FIFO above threshold interrupt */
#define STMPE811_INT_BIT_TOUCH BIT(0) /* Touch/release is detected interrupt */
#define STMPE811_INT_ALL BIT_MASK(8) /* All interrupts */
/* Reset control */
#define STMPE811_SYS_CTRL1_RESET_ON 0
#define STMPE811_SYS_CTRL1_RESET_SOFT BIT(1) /* Soft reset */
/* Delays to ensure registers erasing */
#define STMPE811_RESET_DELAY_MS 10
#define STMPE811_WAIT_DELAY_MS 2
/* Configuration */
#define STMPE811_FIFO_TH_SINGLE_POINT 1
#define STMPE811_FIFO_STA_CLEAR 1
#define STMPE811_FIFO_STA_OPERATIONAL 0
#define STMPE811_TSC_I_DRIVE_LIMIT 1
/**
* Touch Screen Control
*
* - bits [1-3] X, Y only acquisition mode
*/
#define STMPE811_TSC_CTRL_CONF 3U
/**
* Analog-to-digital Converter
*
* - bit [3] selects 12 bit ADC
* - bits [4-6] select ADC conversion time = 80
*/
#define STMPE811_ADC_CTRL1_CONF 0x48U
/**
* ADC clock speed: 3.25 MHz
*
* - 00 : 1.625 MHz
* - 01 : 3.25 MHz
* - 10 : 6.5 MHz
* - 11 : 6.5 MHz
*/
#define STMPE811_ADC_CLOCK_SPEED 1
/**
* Range and accuracy of the pressure measurement (Z)
*
* - Fractional part : 7
* - Whole part : 1
*/
#define STMPE811_TSC_FRACT_XYZ_CONF 1
struct stmpe811_config {
struct i2c_dt_spec bus;
struct gpio_dt_spec int_gpio;
uint8_t panel_driver_settling_time_us;
uint8_t touch_detect_delay_us;
uint8_t touch_average_control;
uint8_t tracking_index;
uint16_t screen_width;
uint16_t screen_height;
int raw_x_min;
int raw_y_min;
uint16_t raw_x_max;
uint16_t raw_y_max;
};
struct stmpe811_data {
const struct device *dev;
struct k_work processing_work;
struct gpio_callback int_gpio_cb;
uint32_t touch_x;
uint32_t touch_y;
};
static int stmpe811_reset(const struct device *dev)
{
const struct stmpe811_config *config = dev->config;
/* Power down the stmpe811 */
int ret = i2c_reg_write_byte_dt(&config->bus, STMPE811_SYS_CTRL1_REG,
STMPE811_SYS_CTRL1_RESET_SOFT);
if (ret < 0) {
return ret;
}
k_msleep(STMPE811_RESET_DELAY_MS);
/* Power on the stmpe811 after the power off => all registers are reinitialized */
ret = i2c_reg_write_byte_dt(&config->bus, STMPE811_SYS_CTRL1_REG,
STMPE811_SYS_CTRL1_RESET_ON);
if (ret < 0) {
return ret;
}
k_msleep(STMPE811_WAIT_DELAY_MS);
return 0;
}
static int stmpe811_io_enable_af(const struct device *dev, uint32_t io_pin)
{
const struct stmpe811_config *config = dev->config;
/* Apply ~io_pin as a mask to the current register value */
return i2c_reg_update_byte_dt(&config->bus, STMPE811_IO_AF_REG, io_pin, 0);
}
static uint8_t stmpe811_tsc_config_bits(const struct device *dev)
{
const struct stmpe811_config *config = dev->config;
/**
* Configuration:
* - bits [0-2] : panel driver settling time
* - bits [3-5] : touch detect delay
* - bits [6-7] : touch average control
*/
return config->panel_driver_settling_time_us | config->touch_detect_delay_us << 3 |
config->touch_average_control << 6;
}
static uint8_t stmpe811_tsc_control_bits(const struct device *dev)
{
const struct stmpe811_config *config = dev->config;
/**
* Touch Screen Control
*
* - bit [0] enables TSC
* - bits [1-3] X, Y only acquisition mode
* - bits [4-6] window tracking index (set from config)
* - bit [7] TSC status (writing has no effect)
*/
return STMPE811_TSC_CTRL_CONF | config->tracking_index << 4;
}
static int stmpe811_ts_init(const struct device *dev)
{
const struct stmpe811_config *config = dev->config;
int err;
err = stmpe811_reset(dev);
if (err < 0) {
return err;
}
/* Select TSC pins in TSC alternate mode */
err = stmpe811_io_enable_af(dev, STMPE811_TOUCH_IO_ALL);
if (err < 0) {
return err;
}
/**
* Set the functionalities to be enabled
* Bits [0-3] disable functionalities if set to 1 (reset value: 0x0f)
*
* Apply inverted sum of chosen FCT bits as a mask to the currect register value
*/
err = i2c_reg_update_byte_dt(&config->bus, STMPE811_SYS_CTRL2_REG,
STMPE811_SYS_CTRL2_BIT_IO_FCT | STMPE811_SYS_CTRL2_BIT_TS_FCT |
STMPE811_SYS_CTRL2_BIT_ADC_FCT, 0);
if (err < 0) {
return err;
}
/* Select sample time, bit number and ADC reference */
err = i2c_reg_write_byte_dt(&config->bus, STMPE811_ADC_CTRL1_REG, STMPE811_ADC_CTRL1_CONF);
if (err < 0) {
return err;
}
/* Select the ADC clock speed */
err = i2c_reg_write_byte_dt(&config->bus, STMPE811_ADC_CTRL2_REG, STMPE811_ADC_CLOCK_SPEED);
if (err < 0) {
return err;
}
/* Touch screen configuration */
err = i2c_reg_write_byte_dt(&config->bus, STMPE811_TSC_CFG_REG,
stmpe811_tsc_config_bits(dev));
if (err < 0) {
return err;
}
/* Configure the touch FIFO threshold */
err = i2c_reg_write_byte_dt(&config->bus, STMPE811_FIFO_TH_REG,
STMPE811_FIFO_TH_SINGLE_POINT);
if (err < 0) {
return err;
}
/* Clear the FIFO memory content */
err = i2c_reg_write_byte_dt(&config->bus, STMPE811_FIFO_STA_REG, STMPE811_FIFO_STA_CLEAR);
if (err < 0) {
return err;
}
/* Set the range and accuracy of the pressure measurement (Z) */
err = i2c_reg_write_byte_dt(&config->bus, STMPE811_TSC_FRACT_XYZ_REG,
STMPE811_TSC_FRACT_XYZ_CONF);
if (err < 0) {
return err;
}
/* Set the driving capability (limit) of the device for TSC pins */
err = i2c_reg_write_byte_dt(&config->bus, STMPE811_TSC_I_DRIVE_REG,
STMPE811_TSC_I_DRIVE_LIMIT);
if (err < 0) {
return err;
}
/* Touch screen control configuration */
err = i2c_reg_write_byte_dt(&config->bus, STMPE811_TSC_CTRL_REG,
stmpe811_tsc_control_bits(dev));
if (err < 0) {
return err;
}
/**
* Clear all the status pending bits if any.
* Writing '1' to this register clears the corresponding bits.
* This is an 8-bit register, so writing 0xFF clears all.
*/
err = i2c_reg_write_byte_dt(&config->bus, STMPE811_INT_STA_REG, STMPE811_INT_ALL);
if (err < 0) {
return err;
}
/* Put the FIFO back into operation mode */
err = i2c_reg_write_byte_dt(&config->bus, STMPE811_FIFO_STA_REG,
STMPE811_FIFO_STA_OPERATIONAL);
if (err < 0) {
return err;
}
/* Enable FIFO and touch interrupts */
err = i2c_reg_write_byte_dt(&config->bus, STMPE811_INT_EN_REG,
STMPE811_INT_BIT_TOUCH | STMPE811_INT_BIT_FIFO_THRESHOLD);
if (err < 0) {
LOG_ERR("Could not enable interrupt types (%d)", err);
return err;
}
return 0;
}
static int stmpe811_ts_get_data(const struct device *dev)
{
const struct stmpe811_config *config = dev->config;
struct stmpe811_data *data = dev->data;
uint8_t data_xy[3];
uint32_t uldata_xy;
int ret = i2c_burst_read_dt(&config->bus, STMPE811_TSC_DATA_NON_INC_REG, data_xy,
sizeof(data_xy));
if (ret < 0) {
return ret;
}
/* Calculate positions values */
uldata_xy = (data_xy[0] << 16) | (data_xy[1] << 8) | data_xy[2];
data->touch_x = (uldata_xy >> 12U) & BIT_MASK(12);
data->touch_y = uldata_xy & BIT_MASK(12);
return 0;
}
static void stmpe811_report_touch(const struct device *dev)
{
const struct stmpe811_config *config = dev->config;
struct stmpe811_data *data = dev->data;
int x = data->touch_x;
int y = data->touch_y;
if (config->screen_width > 0 && config->screen_height > 0) {
x = (((int)data->touch_x - config->raw_x_min) * config->screen_width) /
(config->raw_x_max - config->raw_x_min);
y = (((int)data->touch_y - config->raw_y_min) * config->screen_height) /
(config->raw_y_max - config->raw_y_min);
x = CLAMP(x, 0, config->screen_width);
y = CLAMP(y, 0, config->screen_height);
}
input_report_abs(dev, INPUT_ABS_X, x, false, K_FOREVER);
input_report_abs(dev, INPUT_ABS_Y, y, false, K_FOREVER);
input_report_key(dev, INPUT_BTN_TOUCH, 1, true, K_FOREVER);
}
static int stmpe811_process(const struct device *dev)
{
const struct stmpe811_config *config = dev->config;
int err;
uint8_t int_sta, fifo_size, tsc_ctrl;
err = i2c_reg_read_byte_dt(&config->bus, STMPE811_INT_STA_REG, &int_sta);
if (err < 0) {
return err;
}
/* Clear processed interrupts */
err = i2c_reg_write_byte_dt(&config->bus, STMPE811_INT_STA_REG, int_sta);
if (err < 0) {
return err;
}
if (int_sta & STMPE811_INT_BIT_FIFO_THRESHOLD) {
/**
* Report every element in FIFO
*
* This requires a while loop to avoid a race condition
* in which an element is added after reading FIFO_SIZE.
*
* Exiting ISR without handling every element in FIFO
* would prevent FIFO_THRESHOLD interrupt from being triggered again.
*/
while (true) {
err = i2c_reg_read_byte_dt(&config->bus, STMPE811_FIFO_SIZE_REG,
&fifo_size);
if (err < 0) {
return err;
}
if (fifo_size == 0) {
break;
}
for (int i = 0; i < fifo_size; i++) {
err = stmpe811_ts_get_data(dev);
if (err < 0) {
return err;
}
stmpe811_report_touch(dev);
}
}
}
/* TOUCH interrupt also gets triggered at release */
if (int_sta & STMPE811_INT_BIT_TOUCH) {
err = i2c_reg_read_byte_dt(&config->bus, STMPE811_TSC_CTRL_REG, &tsc_ctrl);
if (err < 0) {
return err;
}
/* TOUCH interrupt + no touch detected in TSC_CTRL reg <=> release */
if (!(tsc_ctrl & STMPE811_TSC_CTRL_BIT_TOUCH_DET)) {
input_report_key(dev, INPUT_BTN_TOUCH, 0, true, K_FOREVER);
}
}
return 0;
}
static void stmpe811_work_handler(struct k_work *work)
{
struct stmpe811_data *data = CONTAINER_OF(work, struct stmpe811_data, processing_work);
const struct stmpe811_config *config = data->dev->config;
stmpe811_process(data->dev);
/**
* Reschedule ISR if there was an interrupt triggered during handling (race condition).
* IRQ is edge-triggered, so otherwise it would never be triggered again.
*/
if (gpio_pin_get_dt(&config->int_gpio)) {
k_work_submit(&data->processing_work);
}
}
static void stmpe811_interrupt_handler(const struct device *dev, struct gpio_callback *cb,
uint32_t pins)
{
struct stmpe811_data *data = CONTAINER_OF(cb, struct stmpe811_data, int_gpio_cb);
k_work_submit(&data->processing_work);
}
static int stmpe811_verify_chip_id(const struct device *dev)
{
const struct stmpe811_config *config = dev->config;
uint8_t buf[2];
i2c_burst_read_dt(&config->bus, STMPE811_CHP_ID_LSB_REG, buf, 2);
if (sys_get_be16(buf) != CHIP_ID) {
return -EINVAL;
}
return 0;
}
static int stmpe811_init(const struct device *dev)
{
const struct stmpe811_config *config = dev->config;
struct stmpe811_data *data = dev->data;
int err;
if (!i2c_is_ready_dt(&config->bus)) {
LOG_ERR("I2C controller device not ready");
return -ENODEV;
}
data->dev = dev;
k_work_init(&data->processing_work, stmpe811_work_handler);
/* Verify CHIP_ID */
err = stmpe811_verify_chip_id(dev);
if (err) {
LOG_ERR("CHIP ID verification failed (%d)", err);
return err;
}
/* Initialize */
err = stmpe811_ts_init(dev);
if (err) {
LOG_ERR("Touch screen controller initialization failed (%d)", err);
return err;
}
/* Initialize GPIO interrupt */
if (!gpio_is_ready_dt(&config->int_gpio)) {
LOG_ERR("Interrupt GPIO controller device not ready");
return -ENODEV;
}
err = gpio_pin_configure_dt(&config->int_gpio, GPIO_INPUT);
if (err < 0) {
LOG_ERR("Could not configure interrupt GPIO pin (%d)", err);
return err;
}
err = gpio_pin_interrupt_configure_dt(&config->int_gpio, GPIO_INT_EDGE_TO_ACTIVE);
if (err < 0) {
LOG_ERR("Could not configure GPIO interrupt (%d)", err);
return err;
}
gpio_init_callback(&data->int_gpio_cb, stmpe811_interrupt_handler,
BIT(config->int_gpio.pin));
err = gpio_add_callback_dt(&config->int_gpio, &data->int_gpio_cb);
if (err < 0) {
LOG_ERR("Could not set GPIO callback (%d)", err);
return err;
}
/* Enable global interrupts */
err = i2c_reg_write_byte_dt(&config->bus, STMPE811_INT_CTRL_REG,
STMPE811_INT_CTRL_BIT_GLOBAL_INT);
if (err < 0) {
LOG_ERR("Could not enable global interrupts (%d)", err);
return err;
}
return 0;
}
#define STMPE811_DEFINE(index) \
BUILD_ASSERT(DT_INST_PROP_OR(index, raw_x_max, 4096) > \
DT_INST_PROP_OR(index, raw_x_min, 0), \
"raw-x-max should be larger than raw-x-min"); \
BUILD_ASSERT(DT_INST_PROP_OR(index, raw_y_max, 4096) > \
DT_INST_PROP_OR(index, raw_y_min, 0), \
"raw-y-max should be larger than raw-y-min"); \
static const struct stmpe811_config stmpe811_config_##index = { \
.bus = I2C_DT_SPEC_INST_GET(index), \
.int_gpio = GPIO_DT_SPEC_INST_GET(index, int_gpios), \
.panel_driver_settling_time_us = \
DT_INST_ENUM_IDX(index, panel_driver_settling_time_us), \
.screen_width = DT_INST_PROP(index, screen_width), \
.screen_height = DT_INST_PROP(index, screen_height), \
.raw_x_min = DT_INST_PROP_OR(index, raw_x_min, 0), \
.raw_y_min = DT_INST_PROP_OR(index, raw_y_min, 0), \
.raw_x_max = DT_INST_PROP_OR(index, raw_x_max, 4096), \
.raw_y_max = DT_INST_PROP_OR(index, raw_y_max, 4096), \
.touch_detect_delay_us = DT_INST_ENUM_IDX(index, touch_detect_delay_us), \
.touch_average_control = DT_INST_ENUM_IDX(index, touch_average_control), \
.tracking_index = DT_INST_ENUM_IDX(index, tracking_index)}; \
static struct stmpe811_data stmpe811_data_##index; \
DEVICE_DT_INST_DEFINE(index, stmpe811_init, NULL, &stmpe811_data_##index, \
&stmpe811_config_##index, POST_KERNEL, CONFIG_INPUT_INIT_PRIORITY, \
NULL);
DT_INST_FOREACH_STATUS_OKAY(STMPE811_DEFINE)

View file

@ -0,0 +1,127 @@
# Copyright (c) 2023 Antmicro <www.antmicro.com>
# SPDX-License-Identifier: Apache-2.0
description: STMPE811 I2C touchscreen controller
compatible: "st,stmpe811"
include: i2c-device.yaml
properties:
int-gpios:
type: phandle-array
description: |
Interrupt GPIO. Used by the controller to signal touch data is
available. Active low.
screen-width:
type: int
default: 0
description: |
Screen width for scaling the reported coordinates.
Default: raw touchscreen resolution.
screen-height:
type: int
default: 0
description: |
Screen height for scaling the reported coordinates.
Default: raw touchscreen resolution.
raw-x-min:
type: int
description: |
Signed raw X axis start for scaling the reported coordinates.
No effect if screen size is not set.
raw-y-min:
type: int
description: |
Signed raw Y axis start for scaling the reported coordinates.
No effect if screen size is not set.
raw-x-max:
type: int
description: |
Raw X axis end for scaling the reported coordinates.
No effect if screen size is not set.
raw-y-max:
type: int
description: |
Raw Y axis end for scaling the reported coordinates.
No effect if screen size is not set.
panel-driver-settling-time-us:
type: int
enum:
- 10
- 100
- 500
- 1000
- 5000
- 10000
- 50000
- 100000
required: true
description: |
Panel driver settling time (microseconds). For large panels (> 6"), a capacitor of 10 nF
is recommended at the touchscreen terminals for noise filtering.
As a general rule, 1-5 nF capacitors require around 500 us settling time, and 5-10 nF need
around 1 ms. When a larger capacitor is used, this value should be changed, as it can
lead to inaccuracy of the measurement.
touch-detect-delay-us:
type: int
enum:
- 10
- 50
- 100
- 500
- 1000
- 5000
- 10000
- 50000
required: true
description: |
Touch detect delay (microseconds) is the delay from the activation of the pull-up resistor
in the X+ line to the time the device performs touch detection.
If no capacitor, or a smaller capacitor is used, this value can be lowered to
minimize detection latency, but it could lower the position stability.
touch-average-control:
type: int
enum:
- 1
- 2
- 4
- 8
required: true
description: |
Average control (number of samples).
This parameter can be set to any of the possible values.
Higher values result in more filtering of noise, but also introduce
more latency in the touch detection process.
Use cases that require low touch detection latency
may benefit from using a lower value for this parameter,
at the cost of less noise filtering.
tracking-index:
type: int
enum:
- 0
- 4
- 8
- 16
- 32
- 64
- 92
- 127
required: true
description: |
Tracking index determines the minimal distance between
the current touch position and the previous touch position.
If the distance is shorter than the tracking index, it is discarded.
Lowering the tracking index increases the frequency of touch events,
but also increases the load on the system.

View file

@ -78,6 +78,16 @@
int-gpios = <&gpio0 0 0>;
input-codes = <0 1 2>;
};
stmpe811@4 {
compatible = "st,stmpe811";
reg = <0x4>;
int-gpios = <&gpio0 0 0>;
panel-driver-settling-time-us = <10>;
touch-detect-delay-us = <10>;
touch-average-control = <1>;
tracking-index = <0>;
};
};
spi@2 {