diff --git a/boards/arm/pinetime_devkit0/Kconfig.defconfig b/boards/arm/pinetime_devkit0/Kconfig.defconfig index 28fbdb0a24a8..174f6e7d8df0 100644 --- a/boards/arm/pinetime_devkit0/Kconfig.defconfig +++ b/boards/arm/pinetime_devkit0/Kconfig.defconfig @@ -21,4 +21,7 @@ config ST7789V endif # DISPLAY +config I2C + default y if KSCAN + endif # BOARD_PINETIME_DEVKIT0 diff --git a/boards/arm/pinetime_devkit0/pinetime_devkit0.dts b/boards/arm/pinetime_devkit0/pinetime_devkit0.dts index fb894f0e002b..f5fa70429f6a 100644 --- a/boards/arm/pinetime_devkit0/pinetime_devkit0.dts +++ b/boards/arm/pinetime_devkit0/pinetime_devkit0.dts @@ -32,6 +32,7 @@ led2 = &blled2; /* backlight high */ led3 = &statusled; /* status led, may be not populated */ sw0 = &key_in; /* key in */ + kscan0 = &cst816s; }; leds { @@ -108,7 +109,7 @@ /* Hynitron CST816S Capacitive Touch Controller (400KHz) */ cst816s: cst816s@15 { - compatible = "hynitron-cst816s"; + compatible = "hynitron,cst816s"; reg = <0x15>; label = "CST816S"; irq-gpios = <&gpio0 28 GPIO_ACTIVE_LOW>; diff --git a/boards/arm/pinetime_devkit0/pinetime_devkit0.yaml b/boards/arm/pinetime_devkit0/pinetime_devkit0.yaml index 245346b1db89..cf98faaf53e0 100644 --- a/boards/arm/pinetime_devkit0/pinetime_devkit0.yaml +++ b/boards/arm/pinetime_devkit0/pinetime_devkit0.yaml @@ -8,3 +8,5 @@ toolchain: - xtools ram: 64 flash: 512 +supported: + - kscan:touch diff --git a/drivers/kscan/CMakeLists.txt b/drivers/kscan/CMakeLists.txt index 72db4bb49385..75e6c785b5c5 100644 --- a/drivers/kscan/CMakeLists.txt +++ b/drivers/kscan/CMakeLists.txt @@ -7,5 +7,6 @@ zephyr_library_sources_ifdef(CONFIG_KSCAN_ITE_IT8XXX2 kscan_ite_it8xxx2.c) zephyr_library_sources_ifdef(CONFIG_KSCAN_XEC kscan_mchp_xec.c) zephyr_library_sources_ifdef(CONFIG_KSCAN_SDL kscan_sdl.c) 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_USERSPACE kscan_handlers.c) diff --git a/drivers/kscan/Kconfig b/drivers/kscan/Kconfig index 1a950d61d9e8..13cae240717f 100644 --- a/drivers/kscan/Kconfig +++ b/drivers/kscan/Kconfig @@ -15,6 +15,7 @@ source "drivers/kscan/Kconfig.it8xxx2" source "drivers/kscan/Kconfig.xec" source "drivers/kscan/Kconfig.sdl" source "drivers/kscan/Kconfig.ht16k33" +source "drivers/kscan/Kconfig.cst816s" module = KSCAN module-str = kscan diff --git a/drivers/kscan/Kconfig.cst816s b/drivers/kscan/Kconfig.cst816s new file mode 100644 index 000000000000..faf9d50b8ccc --- /dev/null +++ b/drivers/kscan/Kconfig.cst816s @@ -0,0 +1,29 @@ +# Copyright (c) 2020 Qingsong Gou +# SPDX-License-Identifier: Apache-2.0 + +DT_COMPAT_HYNITRON_CST816S := hynitron,cst816s + +menuconfig KSCAN_CST816S + bool "CST816S capacitive touch panel driver" + default $(dt_compat_enabled,$(DT_COMPAT_HYNITRON_CST816S)) + depends on I2C + help + Enable driver for hynitron cst816s touch panel. + +if KSCAN_CST816S + +config KSCAN_CST816S_PERIOD + int "Sample period" + depends on !KSCAN_CST816S_INTERRUPT + default 20 + help + Sample period in milliseconds when in polling mode. + +config KSCAN_CST816S_INTERRUPT + bool "Enable interrupt support" + default y + depends on GPIO + help + Enable interrupt support (requires GPIO). + +endif # KSCAN_CST816S diff --git a/drivers/kscan/kscan_cst816s.c b/drivers/kscan/kscan_cst816s.c new file mode 100644 index 000000000000..d6ce97ece956 --- /dev/null +++ b/drivers/kscan/kscan_cst816s.c @@ -0,0 +1,327 @@ +/* + * Copyright (c) 2021 Qingsong Gou + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT hynitron_cst816s + +#include +#include +#include +#include +#include + +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) diff --git a/dts/bindings/kscan/hynitron,cst816s.yaml b/dts/bindings/kscan/hynitron,cst816s.yaml new file mode 100644 index 000000000000..20745a1ed472 --- /dev/null +++ b/dts/bindings/kscan/hynitron,cst816s.yaml @@ -0,0 +1,25 @@ +# Copyright (c) 2021 Qingsong Gou +# SPDX-License-Identifier: Apache-2.0 + +description: Hynitron CST816S touchscreen sensor + +compatible: "hynitron,cst816s" + +include: i2c-device.yaml + +properties: + irq-gpios: + type: phandle-array + required: false + description: | + The irq signal defaults to active low as produced by the + sensor. The property value should ensure the flags properly + describe the signal that is presented to the driver. + + rst-gpios: + type: phandle-array + required: false + description: | + The reset signal defaults to active low to the + sensor. The property value should ensure the flags properly + describe the signal that is presented to the driver. diff --git a/dts/bindings/vendor-prefixes.txt b/dts/bindings/vendor-prefixes.txt index 65a0f4869565..db6e158b1991 100644 --- a/dts/bindings/vendor-prefixes.txt +++ b/dts/bindings/vendor-prefixes.txt @@ -258,6 +258,7 @@ hugsun Shenzhen Hugsun Technology Co. Ltd. hwacom HwaCom Systems Inc. hycon Hycon Technology Corp. hydis Hydis Technologies +hynitron Hynitron hyundai Hyundai Technology i2se I2SE GmbH ibm International Business Machines (IBM)