|  | /* | 
|  | * Copyright (c) 2021 Qingsong Gou <gouqs@hotmail.com> | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #define DT_DRV_COMPAT hynitron_cst816s | 
|  |  | 
|  | #include <zephyr/sys/byteorder.h> | 
|  | #include <zephyr/drivers/kscan.h> | 
|  | #include <zephyr/drivers/i2c.h> | 
|  | #include <zephyr/drivers/gpio.h> | 
|  | #include <zephyr/logging/log.h> | 
|  |  | 
|  | LOG_MODULE_REGISTER(cst816s, CONFIG_KSCAN_LOG_LEVEL); | 
|  |  | 
|  | #define CST816S_CHIP_ID                 0xB4 | 
|  |  | 
|  | #define CST816S_REG_DATA                0x00 | 
|  | #define CST816S_REG_GESTURE_ID          0x01 | 
|  | #define CST816S_REG_FINGER_NUM          0x02 | 
|  | #define CST816S_REG_XPOS_H              0x03 | 
|  | #define CST816S_REG_XPOS_L              0x04 | 
|  | #define CST816S_REG_YPOS_H              0x05 | 
|  | #define CST816S_REG_YPOS_L              0x06 | 
|  | #define CST816S_REG_BPC0H               0xB0 | 
|  | #define CST816S_REG_BPC0L               0xB1 | 
|  | #define CST816S_REG_BPC1H               0xB2 | 
|  | #define CST816S_REG_BPC1L               0xB3 | 
|  | #define CST816S_REG_POWER_MODE          0xA5 | 
|  | #define CST816S_REG_CHIP_ID             0xA7 | 
|  | #define CST816S_REG_PROJ_ID             0xA8 | 
|  | #define CST816S_REG_FW_VERSION          0xA9 | 
|  | #define CST816S_REG_MOTION_MASK         0xEC | 
|  | #define CST816S_REG_IRQ_PULSE_WIDTH     0xED | 
|  | #define CST816S_REG_NOR_SCAN_PER        0xEE | 
|  | #define CST816S_REG_MOTION_S1_ANGLE     0xEF | 
|  | #define CST816S_REG_LP_SCAN_RAW1H       0xF0 | 
|  | #define CST816S_REG_LP_SCAN_RAW1L       0xF1 | 
|  | #define CST816S_REG_LP_SCAN_RAW2H       0xF2 | 
|  | #define CST816S_REG_LP_SCAN_RAW2L       0xF3 | 
|  | #define CST816S_REG_LP_AUTO_WAKEUP_TIME 0xF4 | 
|  | #define CST816S_REG_LP_SCAN_TH          0xF5 | 
|  | #define CST816S_REG_LP_SCAN_WIN         0xF6 | 
|  | #define CST816S_REG_LP_SCAN_FREQ        0xF7 | 
|  | #define CST816S_REG_LP_SCAN_I_DAC       0xF8 | 
|  | #define CST816S_REG_AUTOSLEEP_TIME      0xF9 | 
|  | #define CST816S_REG_IRQ_CTL             0xFA | 
|  | #define CST816S_REG_DEBOUNCE_TIME       0xFB | 
|  | #define CST816S_REG_LONG_PRESS_TIME     0xFC | 
|  | #define CST816S_REG_IOCTL               0xFD | 
|  | #define CST816S_REG_DIS_AUTO_SLEEP      0xFE | 
|  |  | 
|  | #define CST816S_MOTION_EN_CON_LR      BIT(2) | 
|  | #define CST816S_MOTION_EN_CON_UR      BIT(1) | 
|  | #define CST816S_MOTION_EN_DCLICK      BIT(0) | 
|  |  | 
|  | #define CST816S_IRQ_EN_TEST           BIT(7) | 
|  | #define CST816S_IRQ_EN_TOUCH          BIT(6) | 
|  | #define CST816S_IRQ_EN_CHANGE         BIT(5) | 
|  | #define CST816S_IRQ_EN_MOTION         BIT(4) | 
|  | #define CST816S_IRQ_ONCE_WLP          BIT(0) | 
|  |  | 
|  | #define CST816S_IOCTL_SOFT_RTS        BIT(2) | 
|  | #define CST816S_IOCTL_IIC_OD          BIT(1) | 
|  | #define CST816S_IOCTL_EN_1V8          BIT(0) | 
|  |  | 
|  | #define CST816S_POWER_MODE_SLEEP          (0x03) | 
|  | #define CST816S_POWER_MODE_EXPERIMENTAL   (0x05) | 
|  |  | 
|  | #define CST816S_EVENT_BITS_POS        (0x06) | 
|  |  | 
|  | #define CST816S_RESET_DELAY           (5) /* in ms */ | 
|  | #define CST816S_WAIT_DELAY            (50) /* in ms */ | 
|  |  | 
|  | #define EVENT_PRESS_DOWN	0x00U | 
|  | #define EVENT_LIFT_UP		0x01U | 
|  | #define EVENT_CONTACT		0x02U | 
|  | #define EVENT_NONE		0x03U | 
|  |  | 
|  |  | 
|  | /** cst816s configuration (DT). */ | 
|  | struct cst816s_config { | 
|  | struct i2c_dt_spec i2c; | 
|  | const struct gpio_dt_spec rst_gpio; | 
|  | #ifdef CONFIG_KSCAN_CST816S_INTERRUPT | 
|  | const struct gpio_dt_spec int_gpio; | 
|  | #endif | 
|  | }; | 
|  |  | 
|  | /** cst816s data. */ | 
|  | struct cst816s_data { | 
|  | /** Device pointer. */ | 
|  | const struct device *dev; | 
|  | /** KSCAN Callback. */ | 
|  | kscan_callback_t callback; | 
|  | /** Work queue (for deferred read). */ | 
|  | struct k_work work; | 
|  |  | 
|  | #ifdef CONFIG_KSCAN_CST816S_INTERRUPT | 
|  | /** Interrupt GPIO callback. */ | 
|  | struct gpio_callback int_gpio_cb; | 
|  | #else | 
|  | /** Timer (polling mode). */ | 
|  | struct k_timer timer; | 
|  | #endif | 
|  | }; | 
|  |  | 
|  | static int cst816s_process(const struct device *dev) | 
|  | { | 
|  | const struct cst816s_config *cfg = dev->config; | 
|  | struct cst816s_data *data = dev->data; | 
|  |  | 
|  | int r; | 
|  | uint8_t event; | 
|  | uint16_t row, col; | 
|  | bool pressed; | 
|  | uint16_t x; | 
|  | uint16_t y; | 
|  |  | 
|  | r = i2c_burst_read_dt(&cfg->i2c, CST816S_REG_XPOS_H, (uint8_t *)&x, sizeof(x)); | 
|  | if (r < 0) { | 
|  | LOG_ERR("Could not read x data"); | 
|  | return r; | 
|  | } | 
|  |  | 
|  | r = i2c_burst_read_dt(&cfg->i2c, CST816S_REG_YPOS_H, (uint8_t *)&y, sizeof(y)); | 
|  | if (r < 0) { | 
|  | LOG_ERR("Could not read y data"); | 
|  | return r; | 
|  | } | 
|  | col = sys_be16_to_cpu(x) & 0x0fff; | 
|  | row = sys_be16_to_cpu(y) & 0x0fff; | 
|  |  | 
|  | event = (x & 0xff) >> CST816S_EVENT_BITS_POS; | 
|  | pressed = (event == EVENT_PRESS_DOWN) || (event == EVENT_CONTACT); | 
|  |  | 
|  | LOG_DBG("event: %d, row: %d, col: %d", event, row, col); | 
|  |  | 
|  | if (data->callback) { | 
|  | data->callback(dev, row, col, pressed); | 
|  | } | 
|  | return r; | 
|  | } | 
|  |  | 
|  | static void cst816s_work_handler(struct k_work *work) | 
|  | { | 
|  | struct cst816s_data *data = CONTAINER_OF(work, struct cst816s_data, work); | 
|  |  | 
|  | cst816s_process(data->dev); | 
|  | } | 
|  |  | 
|  | #ifdef CONFIG_KSCAN_CST816S_INTERRUPT | 
|  | static void cst816s_isr_handler(const struct device *dev, | 
|  | struct gpio_callback *cb, uint32_t pins) | 
|  | { | 
|  | struct cst816s_data *data = CONTAINER_OF(cb, struct cst816s_data, int_gpio_cb); | 
|  |  | 
|  | k_work_submit(&data->work); | 
|  | } | 
|  | #else | 
|  | static void cst816s_timer_handler(struct k_timer *timer) | 
|  | { | 
|  | struct cst816s_data *data = CONTAINER_OF(timer, struct cst816s_data, timer); | 
|  |  | 
|  | k_work_submit(&data->work); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | static int cst816s_configure(const struct device *dev, | 
|  | kscan_callback_t callback) | 
|  | { | 
|  | struct cst816s_data *data = dev->data; | 
|  |  | 
|  | if (!callback) { | 
|  | LOG_ERR("Invalid callback (NULL)"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | data->callback = callback; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int cst816s_enable_callback(const struct device *dev) | 
|  | { | 
|  | struct cst816s_data *data = dev->data; | 
|  |  | 
|  | #ifdef CONFIG_KSCAN_CST816S_INTERRUPT | 
|  | const struct cst816s_config *config = dev->config; | 
|  |  | 
|  | gpio_add_callback(config->int_gpio.port, &data->int_gpio_cb); | 
|  | #else | 
|  | k_timer_start(&data->timer, K_MSEC(CONFIG_KSCAN_CST816S_PERIOD), | 
|  | K_MSEC(CONFIG_KSCAN_CST816S_PERIOD)); | 
|  | #endif | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int cst816s_disable_callback(const struct device *dev) | 
|  | { | 
|  | struct cst816s_data *data = dev->data; | 
|  |  | 
|  | #ifdef CONFIG_KSCAN_CST816S_INTERRUPT | 
|  | const struct cst816s_config *config = dev->config; | 
|  |  | 
|  | gpio_remove_callback(config->int_gpio.port, &data->int_gpio_cb); | 
|  | #else | 
|  | k_timer_stop(&data->timer); | 
|  | #endif | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void cst816s_chip_reset(const struct device *dev) | 
|  | { | 
|  | const struct cst816s_config *config = dev->config; | 
|  | int ret; | 
|  |  | 
|  | if (device_is_ready(config->rst_gpio.port)) { | 
|  | ret = gpio_pin_configure_dt(&config->rst_gpio, | 
|  | GPIO_OUTPUT_INACTIVE); | 
|  | if (ret < 0) { | 
|  | LOG_ERR("Could not configure reset GPIO pin"); | 
|  | return; | 
|  | } | 
|  | gpio_pin_set_dt(&config->rst_gpio, 1); | 
|  | k_msleep(CST816S_RESET_DELAY); | 
|  | gpio_pin_set_dt(&config->rst_gpio, 0); | 
|  | k_msleep(CST816S_WAIT_DELAY); | 
|  | } | 
|  | } | 
|  |  | 
|  | static int cst816s_chip_init(const struct device *dev) | 
|  | { | 
|  | const struct cst816s_config *cfg = dev->config; | 
|  | int ret = 0; | 
|  | uint8_t chip_id = 0; | 
|  |  | 
|  | cst816s_chip_reset(dev); | 
|  |  | 
|  | if (!device_is_ready(cfg->i2c.bus)) { | 
|  | LOG_ERR("I2C bus %s not ready", cfg->i2c.bus->name); | 
|  | return -ENODEV; | 
|  | } | 
|  | ret = i2c_reg_read_byte_dt(&cfg->i2c, CST816S_REG_CHIP_ID, &chip_id); | 
|  | if (ret < 0) { | 
|  | LOG_ERR("failed reading chip id"); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | if (chip_id != CST816S_CHIP_ID) { | 
|  | LOG_ERR("CST816S wrong chip id: returned 0x%x", chip_id); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | ret = i2c_reg_update_byte_dt(&cfg->i2c, | 
|  | CST816S_REG_IRQ_CTL, | 
|  | CST816S_IRQ_EN_TOUCH | CST816S_IRQ_EN_CHANGE, | 
|  | CST816S_IRQ_EN_TOUCH | CST816S_IRQ_EN_CHANGE); | 
|  | if (ret < 0) { | 
|  | LOG_ERR("Could not enable irq"); | 
|  | return ret; | 
|  | } | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int cst816s_init(const struct device *dev) | 
|  | { | 
|  | struct cst816s_data *data = dev->data; | 
|  |  | 
|  | data->dev = dev; | 
|  | k_work_init(&data->work, cst816s_work_handler); | 
|  |  | 
|  | #ifdef CONFIG_KSCAN_CST816S_INTERRUPT | 
|  | const struct cst816s_config *config = dev->config; | 
|  | int ret; | 
|  |  | 
|  | if (!device_is_ready(config->int_gpio.port)) { | 
|  | LOG_ERR("GPIO port %s not ready", config->int_gpio.port->name); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | ret = gpio_pin_configure_dt(&config->int_gpio, GPIO_INPUT); | 
|  | if (ret < 0) { | 
|  | LOG_ERR("Could not configure interrupt GPIO pin"); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | ret = gpio_pin_interrupt_configure_dt(&config->int_gpio, | 
|  | GPIO_INT_EDGE_TO_ACTIVE); | 
|  | if (ret < 0) { | 
|  | LOG_ERR("Could not configure interrupt GPIO interrupt."); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | gpio_init_callback(&data->int_gpio_cb, cst816s_isr_handler, | 
|  | BIT(config->int_gpio.pin)); | 
|  | #else | 
|  | k_timer_init(&data->timer, cst816s_timer_handler, NULL); | 
|  | #endif | 
|  |  | 
|  | return cst816s_chip_init(dev); | 
|  | } | 
|  |  | 
|  | static const struct kscan_driver_api cst816s_driver_api = { | 
|  | .config = cst816s_configure, | 
|  | .enable_callback = cst816s_enable_callback, | 
|  | .disable_callback = cst816s_disable_callback, | 
|  | }; | 
|  |  | 
|  | #define CST816S_DEFINE(index)                                                     \ | 
|  | static const struct cst816s_config cst816s_config_##index = {             \ | 
|  | .i2c = I2C_DT_SPEC_INST_GET(index),                               \ | 
|  | COND_CODE_1(CONFIG_KSCAN_CST816S_INTERRUPT,                       \ | 
|  | (.int_gpio = GPIO_DT_SPEC_INST_GET(index, irq_gpios),),\ | 
|  | ())                                                    \ | 
|  | .rst_gpio = GPIO_DT_SPEC_INST_GET_OR(index, rst_gpios, {}),       \ | 
|  | };                                                                        \ | 
|  | static struct cst816s_data cst816s_data_##index;                          \ | 
|  | DEVICE_DT_INST_DEFINE(index, cst816s_init, NULL,                          \ | 
|  | &cst816s_data_##index, &cst816s_config_##index,      \ | 
|  | POST_KERNEL, CONFIG_KSCAN_INIT_PRIORITY,             \ | 
|  | &cst816s_driver_api); | 
|  |  | 
|  | DT_INST_FOREACH_STATUS_OKAY(CST816S_DEFINE) |