zephyr/drivers/input/input_gt911.c
Jason Yu d5b1a7d929 driver: input: gt911: Support case that no dedicated reset pin
On some boards, there is not dedicated reset pin for GT911, it might
be the same pin with display IC, or might be tighted to a high level
voltage.
This patch makes the rst_gpio can be empty.

Signed-off-by: Jason Yu <zejiang.yu@nxp.com>
2024-04-02 15:35:50 +01:00

377 lines
10 KiB
C

/*
* Copyright (c) 2020 NXP
* Copyright (c) 2020 Mark Olsson <mark@markolsson.se>
* Copyright (c) 2020 Teslabs Engineering S.L.
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT goodix_gt911
#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(gt911, CONFIG_INPUT_LOG_LEVEL);
/* GT911 used registers */
#define DEVICE_ID BSWAP_16(0x8140U)
#define REG_STATUS BSWAP_16(0x814EU)
#define REG_FIRST_POINT BSWAP_16(0x814FU)
/* REG_TD_STATUS: Touch points. */
#define TOUCH_POINTS_MSK 0x0FU
/* REG_TD_STATUS: Pressed. */
#define TOUCH_STATUS_MSK (1 << 7U)
/* The GT911's config */
#define GT911_CONFIG_REG BSWAP_16(0x8047U)
#define REG_CONFIG_VERSION GT911_CONFIG_REG
#define REG_CONFIG_SIZE (186U)
#define GT911_PRODUCT_ID (0x00313139U)
/** GT911 configuration (DT). */
struct gt911_config {
/** I2C bus. */
struct i2c_dt_spec bus;
struct gpio_dt_spec rst_gpio;
/** Interrupt GPIO information. */
struct gpio_dt_spec int_gpio;
/* Alternate fallback I2C address */
uint8_t alt_addr;
};
/** GT911 data. */
struct gt911_data {
/** Device pointer. */
const struct device *dev;
/** Work queue (for deferred read). */
struct k_work work;
/** Actual device I2C address */
uint8_t actual_address;
#ifdef CONFIG_INPUT_GT911_INTERRUPT
/** Interrupt GPIO callback. */
struct gpio_callback int_gpio_cb;
#else
/** Timer (polling mode). */
struct k_timer timer;
#endif
};
/** gt911 point reg */
struct gt911_point_reg_t {
uint8_t id; /*!< Track ID. */
uint8_t lowX; /*!< Low byte of x coordinate. */
uint8_t highX; /*!< High byte of x coordinate. */
uint8_t lowY; /*!< Low byte of y coordinate. */
uint8_t highY; /*!< High byte of x coordinate. */
uint8_t lowSize; /*!< Low byte of point size. */
uint8_t highSize; /*!< High byte of point size. */
uint8_t reserved; /*!< Reserved. */
};
/*
* Device-specific wrappers around i2c_write_dt and i2c_write_read_dt.
* These wrappers handle the case where the GT911 did not accept the requested
* I2C address, and the alternate I2C address is used.
*/
static int gt911_i2c_write(const struct device *dev,
const uint8_t *buf, uint32_t num_bytes)
{
const struct gt911_config *config = dev->config;
struct gt911_data *data = dev->data;
return i2c_write(config->bus.bus, buf, num_bytes, data->actual_address);
}
static int gt911_i2c_write_read(const struct device *dev,
const void *write_buf, size_t num_write,
void *read_buf, size_t num_read)
{
const struct gt911_config *config = dev->config;
struct gt911_data *data = dev->data;
return i2c_write_read(config->bus.bus, data->actual_address, write_buf,
num_write, read_buf, num_read);
}
static int gt911_process(const struct device *dev)
{
int r;
uint16_t reg_addr;
uint8_t status;
uint8_t points;
struct gt911_point_reg_t pointRegs;
uint16_t row, col;
bool pressed;
/* obtain number of touch points (NOTE: multi-touch ignored) */
reg_addr = REG_STATUS;
r = gt911_i2c_write_read(dev, &reg_addr, sizeof(reg_addr),
&status, sizeof(status));
if (r < 0) {
return r;
}
points = status & TOUCH_POINTS_MSK;
if (points != 0U && points != 1U && (0 != (status & TOUCH_STATUS_MSK))) {
points = 1;
}
if (!(status & TOUCH_STATUS_MSK)) {
/* Status bit not set, ignore this event */
return 0;
}
/* need to clear the status */
uint8_t clear_buffer[3] = {(uint8_t)REG_STATUS, (uint8_t)(REG_STATUS >> 8), 0};
r = gt911_i2c_write(dev, clear_buffer, sizeof(clear_buffer));
if (r < 0) {
return r;
}
/* obtain first point X, Y coordinates and event from:
* REG_P1_XH, REG_P1_XL, REG_P1_YH, REG_P1_YL.
*/
reg_addr = REG_FIRST_POINT;
r = gt911_i2c_write_read(dev, &reg_addr, sizeof(reg_addr),
&pointRegs, sizeof(pointRegs));
if (r < 0) {
return r;
}
pressed = (points == 1);
row = ((pointRegs.highY) << 8U) | pointRegs.lowY;
col = ((pointRegs.highX) << 8U) | pointRegs.lowX;
LOG_DBG("pressed: %d, row: %d, col: %d", pressed, row, col);
if (pressed) {
input_report_abs(dev, INPUT_ABS_X, col, false, K_FOREVER);
input_report_abs(dev, INPUT_ABS_Y, row, false, K_FOREVER);
input_report_key(dev, INPUT_BTN_TOUCH, 1, true, K_FOREVER);
} else {
input_report_key(dev, INPUT_BTN_TOUCH, 0, true, K_FOREVER);
}
return 0;
}
static void gt911_work_handler(struct k_work *work)
{
struct gt911_data *data = CONTAINER_OF(work, struct gt911_data, work);
gt911_process(data->dev);
}
#ifdef CONFIG_INPUT_GT911_INTERRUPT
static void gt911_isr_handler(const struct device *dev,
struct gpio_callback *cb, uint32_t pins)
{
struct gt911_data *data = CONTAINER_OF(cb, struct gt911_data, int_gpio_cb);
k_work_submit(&data->work);
}
#else
static void gt911_timer_handler(struct k_timer *timer)
{
struct gt911_data *data = CONTAINER_OF(timer, struct gt911_data, timer);
k_work_submit(&data->work);
}
#endif
static uint8_t gt911_get_firmware_checksum(const uint8_t *firmware)
{
uint8_t sum = 0;
uint16_t i = 0;
for (i = 0; i < REG_CONFIG_SIZE - 2U; i++) {
sum += (*firmware);
firmware++;
}
return (~sum + 1U);
}
static bool gt911_verify_firmware(const uint8_t *firmware)
{
return ((firmware[REG_CONFIG_VERSION - GT911_CONFIG_REG] != 0U) &&
(gt911_get_firmware_checksum(firmware) == firmware[REG_CONFIG_SIZE - 2U]));
}
static int gt911_init(const struct device *dev)
{
const struct gt911_config *config = dev->config;
struct gt911_data *data = dev->data;
if (!i2c_is_ready_dt(&config->bus)) {
LOG_ERR("I2C controller device not ready");
return -ENODEV;
}
data->dev = dev;
data->actual_address = config->bus.addr;
k_work_init(&data->work, gt911_work_handler);
int r;
if (!gpio_is_ready_dt(&config->int_gpio)) {
LOG_ERR("Interrupt GPIO controller device not ready");
return -ENODEV;
}
if (config->rst_gpio.port != NULL) {
if (!gpio_is_ready_dt(&config->rst_gpio)) {
LOG_ERR("Reset GPIO controller device not ready");
return -ENODEV;
}
r = gpio_pin_configure_dt(&config->rst_gpio, GPIO_OUTPUT_ACTIVE);
if (r < 0) {
LOG_ERR("Could not configure reset GPIO pin");
return r;
}
}
if (config->alt_addr == 0x0) {
/*
* We need to configure the int-pin to 0, in order to enter the
* AddressMode0. Keeping the INT pin low during the reset sequence
* should result in the device selecting an I2C address of 0x5D.
* Note we skip this step if an alternate I2C address is set,
* and fall through to probing for the actual address.
*/
r = gpio_pin_configure_dt(&config->int_gpio, GPIO_OUTPUT_INACTIVE);
if (r < 0) {
LOG_ERR("Could not configure int GPIO pin");
return r;
}
}
/* Delay at least 10 ms after power on before we configure gt911 */
k_sleep(K_MSEC(20));
if (config->rst_gpio.port != NULL) {
/* reset the device and confgiure the addr mode0 */
gpio_pin_set_dt(&config->rst_gpio, 1);
/* hold down at least 1us, 1ms here */
k_sleep(K_MSEC(1));
gpio_pin_set_dt(&config->rst_gpio, 0);
/* hold down at least 5ms. This is the point the INT pin must be low. */
k_sleep(K_MSEC(5));
}
/* hold down 50ms to make sure the address available */
k_sleep(K_MSEC(50));
r = gpio_pin_configure_dt(&config->int_gpio, GPIO_INPUT);
if (r < 0) {
LOG_ERR("Could not configure interrupt GPIO pin");
return r;
}
#ifdef CONFIG_INPUT_GT911_INTERRUPT
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, gt911_isr_handler,
BIT(config->int_gpio.pin));
#else
k_timer_init(&data->timer, gt911_timer_handler, NULL);
#endif
/* check the Device ID first: '911' */
uint32_t reg_id = 0;
uint16_t reg_addr = DEVICE_ID;
if (config->alt_addr != 0x0) {
/*
* The level of the INT pin during reset is used by the GT911
* to select the I2C address mode. If an alternate I2C address
* is set, we should probe the GT911 to determine which address
* it actually selected. This is useful for boards that do not
* route the INT pin, or can only read it as an input (IE when
* using a level shifter).
*/
r = gt911_i2c_write_read(dev, &reg_addr, sizeof(reg_addr),
&reg_id, sizeof(reg_id));
if (r < 0) {
/* Try alternate address */
data->actual_address = config->alt_addr;
r = gt911_i2c_write_read(dev, &reg_addr,
sizeof(reg_addr),
&reg_id, sizeof(reg_id));
LOG_INF("Device did not accept I2C address, "
"updated to 0x%02X", data->actual_address);
}
} else {
r = gt911_i2c_write_read(dev, &reg_addr, sizeof(reg_addr),
&reg_id, sizeof(reg_id));
}
if (r < 0) {
LOG_ERR("Device did not respond to I2C request");
return r;
}
if (reg_id != GT911_PRODUCT_ID) {
LOG_ERR("The Device ID is not correct");
return -ENODEV;
}
/* need to setup the firmware first: read and write */
uint8_t gt911Config[REG_CONFIG_SIZE + 2] = {
(uint8_t)GT911_CONFIG_REG, (uint8_t)(GT911_CONFIG_REG >> 8)
};
reg_addr = GT911_CONFIG_REG;
r = gt911_i2c_write_read(dev, &reg_addr, sizeof(reg_addr),
gt911Config + 2, REG_CONFIG_SIZE);
if (r < 0) {
return r;
}
if (!gt911_verify_firmware(gt911Config + 2)) {
return -ENODEV;
}
gt911Config[REG_CONFIG_SIZE] = gt911_get_firmware_checksum(gt911Config + 2);
gt911Config[REG_CONFIG_SIZE + 1] = 1;
r = gt911_i2c_write(dev, gt911Config, sizeof(gt911Config));
if (r < 0) {
return r;
}
#ifdef CONFIG_INPUT_GT911_INTERRUPT
r = gpio_add_callback(config->int_gpio.port, &data->int_gpio_cb);
if (r < 0) {
LOG_ERR("Could not set gpio callback");
return r;
}
#else
k_timer_start(&data->timer, K_MSEC(CONFIG_INPUT_GT911_PERIOD_MS),
K_MSEC(CONFIG_INPUT_GT911_PERIOD_MS));
#endif
return 0;
}
#define GT911_INIT(index) \
static const struct gt911_config gt911_config_##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(index, irq_gpios), \
.alt_addr = DT_INST_PROP_OR(index, alt_addr, 0), \
}; \
static struct gt911_data gt911_data_##index; \
DEVICE_DT_INST_DEFINE(index, gt911_init, NULL, \
&gt911_data_##index, &gt911_config_##index, \
POST_KERNEL, CONFIG_INPUT_INIT_PRIORITY, \
NULL);
DT_INST_FOREACH_STATUS_OKAY(GT911_INIT)