drivers: kscan: Add driver for XPT2046

Add driver for Xptek XPT2046 resistive touch controller on SPI.
Only interrupt driven mode supported, does not do polling.

Signed-off-by: Seppo Takalo <seppo.takalo@iki.fi>
This commit is contained in:
Seppo Takalo 2023-02-13 13:24:54 +02:00 committed by Carles Cufí
parent 0f601b8c00
commit edae1bed3d
6 changed files with 363 additions and 0 deletions

View file

@ -11,5 +11,6 @@ zephyr_library_sources_ifdef(CONFIG_KSCAN_HT16K33 kscan_ht16k33.c)
zephyr_library_sources_ifdef(CONFIG_KSCAN_CST816S kscan_cst816s.c) zephyr_library_sources_ifdef(CONFIG_KSCAN_CST816S kscan_cst816s.c)
zephyr_library_sources_ifdef(CONFIG_KSCAN_CAP1203 kscan_cap1203.c) zephyr_library_sources_ifdef(CONFIG_KSCAN_CAP1203 kscan_cap1203.c)
zephyr_library_sources_ifdef(CONFIG_KSCAN_NPCX kscan_npcx.c) zephyr_library_sources_ifdef(CONFIG_KSCAN_NPCX kscan_npcx.c)
zephyr_library_sources_ifdef(CONFIG_KSCAN_XPT2046 kscan_xpt2046.c)
zephyr_library_sources_ifdef(CONFIG_USERSPACE kscan_handlers.c) zephyr_library_sources_ifdef(CONFIG_USERSPACE kscan_handlers.c)

View file

@ -19,6 +19,7 @@ source "drivers/kscan/Kconfig.ht16k33"
source "drivers/kscan/Kconfig.cst816s" source "drivers/kscan/Kconfig.cst816s"
source "drivers/kscan/Kconfig.cap1203" source "drivers/kscan/Kconfig.cap1203"
source "drivers/kscan/Kconfig.npcx" source "drivers/kscan/Kconfig.npcx"
source "drivers/kscan/Kconfig.xpt2046"
module = KSCAN module = KSCAN
module-str = kscan module-str = kscan

View file

@ -0,0 +1,11 @@
# Copyright (c) 2023 Seppo Takalo
# SPDX-License-Identifier: Apache-2.0
config KSCAN_XPT2046
bool "XPT2046 resistive touch panel driver"
default y
depends on DT_HAS_XPTEK_XPT2046_ENABLED
select SPI
help
Enable driver for Xptek XPT2046 resistive touch panel.
This driver is very similar to ADS7843, but differs on channel numbering.

View file

@ -0,0 +1,296 @@
/*
* Copyright (c) 2023 Seppo Takalo
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT xptek_xpt2046
#include <zephyr/kernel.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/drivers/kscan.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(xpt2046, CONFIG_KSCAN_LOG_LEVEL);
struct xpt2046_config {
const struct spi_dt_spec bus;
const struct gpio_dt_spec int_gpio;
uint16_t min_x;
uint16_t min_y;
uint16_t max_x;
uint16_t max_y;
uint16_t threshold;
uint16_t screen_size_x;
uint16_t screen_size_y;
uint16_t reads;
};
struct xpt2046_data {
const struct device *dev;
kscan_callback_t callback;
struct gpio_callback int_gpio_cb;
bool enabled;
struct k_work work;
struct k_work_delayable dwork;
uint8_t rbuf[9];
uint32_t last_x;
uint32_t last_y;
bool pressed;
};
enum xpt2046_channel {
CH_TEMP0 = 0,
CH_Y,
CH_VBAT,
CH_Z1,
CH_Z2,
CH_X,
CH_AUXIN,
CH_TEMP1
};
struct measurement {
uint32_t x;
uint32_t y;
uint32_t z;
};
#define START BIT(7)
#define CHANNEL(ch) ((ch & 0x7) << 4)
#define MODE_8_BIT BIT(3)
#define SINGLE_ENDED BIT(2)
#define POWER_OFF 0
#define POWER_ON 0x03
#define CONVERT_U16(buf, idx) ((uint16_t)((buf[idx] & 0x7f) << 5) | (buf[idx + 1] >> 3))
/* Read all Z1, X, Y, Z2 channels using 16 Clocks-per-Conversion mode.
* See the manual https://www.waveshare.com/w/upload/9/98/XPT2046-EN.pdf for details.
* Each follow-up command interleaves with previous conversion.
* So first command starts at byte 0. Second command starts at byte 2.
*/
static uint8_t tbuf[9] = {
[0] = START | CHANNEL(CH_Z1) | POWER_ON,
[2] = START | CHANNEL(CH_Z2) | POWER_ON,
[4] = START | CHANNEL(CH_X) | POWER_ON,
[6] = START | CHANNEL(CH_Y) | POWER_OFF,
};
static void xpt2046_isr_handler(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
{
struct xpt2046_data *data = CONTAINER_OF(cb, struct xpt2046_data, int_gpio_cb);
const struct xpt2046_config *config = data->dev->config;
gpio_remove_callback(config->int_gpio.port, &data->int_gpio_cb);
k_work_submit(&data->work);
}
static int xpt2046_read_and_cumulate(const struct spi_dt_spec *bus, const struct spi_buf_set *tx,
const struct spi_buf_set *rx, struct measurement *meas)
{
int ret = spi_transceive_dt(bus, tx, rx);
if (ret < 0) {
LOG_ERR("spi_transceive() %d\n", ret);
return ret;
}
uint8_t *buf = rx->buffers->buf;
meas->z += CONVERT_U16(buf, 1) + 4096 - CONVERT_U16(buf, 3);
meas->x += CONVERT_U16(buf, 5);
meas->y += CONVERT_U16(buf, 7);
return 0;
}
static void xpt2046_release_handler(struct k_work *kw)
{
struct k_work_delayable *dw = k_work_delayable_from_work(kw);
struct xpt2046_data *data = CONTAINER_OF(dw, struct xpt2046_data, dwork);
struct xpt2046_config *config = (struct xpt2046_config *)data->dev->config;
if (!data->pressed || !data->enabled) {
return;
}
/* Check if touch is still pressed */
if (gpio_pin_get_dt(&config->int_gpio) == 0) {
data->pressed = false;
data->callback(data->dev, data->last_y, data->last_y, false);
} else {
/* Re-check later */
k_work_reschedule(&data->dwork, K_MSEC(10));
}
}
static void xpt2046_work_handler(struct k_work *kw)
{
struct xpt2046_data *data = CONTAINER_OF(kw, struct xpt2046_data, work);
struct xpt2046_config *config = (struct xpt2046_config *)data->dev->config;
const struct spi_buf txb = {.buf = tbuf, .len = sizeof(tbuf)};
const struct spi_buf rxb = {.buf = data->rbuf, .len = sizeof(data->rbuf)};
const struct spi_buf_set tx_bufs = {.buffers = &txb, .count = 1};
const struct spi_buf_set rx_bufs = {.buffers = &rxb, .count = 1};
/* Run number of reads and calculate average */
int rounds = config->reads;
struct measurement meas = {0};
for (int i = 0; i < rounds; i++) {
if (xpt2046_read_and_cumulate(&config->bus, &tx_bufs, &rx_bufs, &meas) != 0) {
return;
}
}
meas.x /= rounds;
meas.y /= rounds;
meas.z /= rounds;
/* Calculate Xp = M * Xt + C using fixed point aritchmetics, where
* Xp is the point in screen coordinates, Xt is the touch coordinates.
* Use signed int32_t for calculation to ensure that we cover the roll-over to negative
* values and return zero instead.
*/
int32_t mx = (config->screen_size_x << 16) / (config->max_x - config->min_x);
int32_t cx = (config->screen_size_x << 16) - mx * config->max_x;
int32_t x = mx * meas.x + cx;
x = (x < 0 ? 0 : x) >> 16;
int32_t my = (config->screen_size_y << 16) / (config->max_y - config->min_y);
int32_t cy = (config->screen_size_y << 16) - my * config->max_y;
int32_t y = my * meas.y + cy;
y = (y < 0 ? 0 : y) >> 16;
bool pressed = meas.z > config->threshold;
/* Don't send any other than "pressed" events.
* releasing seem to cause just random noise
*/
if (data->enabled && pressed) {
LOG_DBG("raw: x=%4u y=%4u ==> x=%4d y=%4d", meas.x, meas.y, x, y);
data->last_x = x;
data->last_y = y;
data->pressed = pressed;
data->callback(data->dev, (uint32_t)y, (uint32_t)x, pressed);
/* Ensure that we send released event */
k_work_reschedule(&data->dwork, K_MSEC(100));
}
gpio_add_callback(config->int_gpio.port, &data->int_gpio_cb);
}
static int xpt2046_configure(const struct device *dev, kscan_callback_t callback)
{
struct xpt2046_data *data = dev->data;
if (!callback) {
LOG_ERR("Callback is null");
return -EINVAL;
}
LOG_DBG("%s: set callback", dev->name);
data->callback = callback;
return 0;
}
static int xpt2046_enable_callback(const struct device *dev)
{
struct xpt2046_data *data = dev->data;
const struct xpt2046_config *config = dev->config;
LOG_DBG("%s: enable cb", dev->name);
data->enabled = true;
gpio_add_callback(config->int_gpio.port, &data->int_gpio_cb);
return 0;
}
static int xpt2046_disable_callback(const struct device *dev)
{
struct xpt2046_data *data = dev->data;
const struct xpt2046_config *config = dev->config;
gpio_remove_callback(config->int_gpio.port, &data->int_gpio_cb);
data->enabled = false;
LOG_DBG("%s: disable cb", dev->name);
return 0;
}
static int xpt2046_init(const struct device *dev)
{
int r;
const struct xpt2046_config *config = dev->config;
struct xpt2046_data *data = dev->data;
if (!spi_is_ready_dt(&config->bus)) {
LOG_ERR("SPI controller device not ready");
return -ENODEV;
}
data->dev = dev;
k_work_init(&data->work, xpt2046_work_handler);
k_work_init_delayable(&data->dwork, xpt2046_release_handler);
if (!gpio_is_ready_dt(&config->int_gpio)) {
LOG_ERR("Interrupt GPIO controller device not ready");
return -ENODEV;
}
r = gpio_pin_configure_dt(&config->int_gpio, GPIO_INPUT);
if (r < 0) {
LOG_ERR("Could not configure interrupt GPIO pin");
return r;
}
r = gpio_pin_interrupt_configure_dt(&config->int_gpio, GPIO_INT_EDGE_TO_ACTIVE);
if (r < 0) {
LOG_ERR("Could not configure interrupt GPIO interrupt.");
return r;
}
gpio_init_callback(&data->int_gpio_cb, xpt2046_isr_handler, BIT(config->int_gpio.pin));
LOG_INF("Init '%s' device", dev->name);
return 0;
}
static const struct kscan_driver_api xpt2046_driver_api = {
.config = xpt2046_configure,
.enable_callback = xpt2046_enable_callback,
.disable_callback = xpt2046_disable_callback,
};
#define XPT2046_INIT(index) \
static const struct xpt2046_config xpt2046_config_##index = { \
.bus = SPI_DT_SPEC_INST_GET( \
index, SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | SPI_WORD_SET(8), 0), \
.int_gpio = GPIO_DT_SPEC_INST_GET(index, int_gpios), \
.min_x = DT_INST_PROP(index, min_x), \
.min_y = DT_INST_PROP(index, min_y), \
.max_x = DT_INST_PROP(index, max_x), \
.max_y = DT_INST_PROP(index, max_y), \
.threshold = DT_INST_PROP(index, z_threshold), \
.screen_size_x = DT_INST_PROP(index, touchscreen_size_x), \
.screen_size_y = DT_INST_PROP(index, touchscreen_size_y), \
.reads = DT_INST_PROP(index, reads), \
}; \
static struct xpt2046_data xpt2046_data_##index; \
DEVICE_DT_INST_DEFINE(index, xpt2046_init, NULL, &xpt2046_data_##index, \
&xpt2046_config_##index, POST_KERNEL, CONFIG_KSCAN_INIT_PRIORITY, \
&xpt2046_driver_api); \
BUILD_ASSERT(DT_INST_PROP(index, min_x) < DT_INST_PROP(index, max_x), \
"min_x must be less than max_x"); \
BUILD_ASSERT(DT_INST_PROP(index, min_y) < DT_INST_PROP(index, max_y), \
"min_y must be less than max_y"); \
BUILD_ASSERT(DT_INST_PROP(index, z_threshold) > 10, "Too small threshold"); \
BUILD_ASSERT(DT_INST_PROP(index, touchscreen_size_x) > 1 && \
DT_INST_PROP(index, touchscreen_size_y) > 1, \
"Screen size undefined"); \
BUILD_ASSERT(DT_INST_PROP(index, reads) > 0, "Number of reads must be at least one");
DT_INST_FOREACH_STATUS_OKAY(XPT2046_INIT)

View file

@ -0,0 +1,53 @@
# Copyright (c) 2023 Seppo Takalo
# SPDX-License-Identifier: Apache-2.0
description: Driver for XPT2046 touch IC
compatible: "xptek,xpt2046"
include: [kscan.yaml, spi-device.yaml]
properties:
int-gpios:
type: phandle-array
required: true
description: Interrupt GPIO.
touchscreen-size-x:
type: int
required: true
description: horizontal resolution of screen
touchscreen-size-y:
type: int
required: true
description: vertical resolution of screen
min-x:
type: int
required: true
description: minimum raw X value reported.
min-y:
type: int
required: true
description: minimum raw Y value reported.
max-x:
type: int
required: true
description: maximum raw X value reported.
max-y:
type: int
required: true
description: maximum raw Y value reported.
z-threshold:
type: int
description: Z value threshold to trigger a touch
default: 100
reads:
type: int
description: How many reads per touch to average the value
default: 1

View file

@ -680,6 +680,7 @@ xinpeng Shenzhen Xinpeng Technology Co., Ltd
xiphera Xiphera Ltd. xiphera Xiphera Ltd.
xlnx Xilinx xlnx Xilinx
xnano Xnano xnano Xnano
xptek Shenzhen Xptek Technology Co., Ltd
xunlong Shenzhen Xunlong Software CO.,Limited xunlong Shenzhen Xunlong Software CO.,Limited
xylon Xylon xylon Xylon
yamaha Yamaha Corporation yamaha Yamaha Corporation