From 924ca5a8135dfc6856af1ac132110d295ee48d1f Mon Sep 17 00:00:00 2001 From: Arif Balik Date: Sun, 10 Nov 2024 09:49:55 +0000 Subject: [PATCH 1/4] dts: bindings: added stm32 tsc driver bindings Added tsc pripheral bindings Signed-off-by: Arif Balik --- dts/bindings/input/st,stm32-tsc.yaml | 121 ++++++++++++++++++ .../dt-bindings/input/stm32-tsc-defines.h | 14 ++ 2 files changed, 135 insertions(+) create mode 100644 dts/bindings/input/st,stm32-tsc.yaml create mode 100644 include/zephyr/dt-bindings/input/stm32-tsc-defines.h diff --git a/dts/bindings/input/st,stm32-tsc.yaml b/dts/bindings/input/st,stm32-tsc.yaml new file mode 100644 index 0000000000000..5e2c1ccfc32de --- /dev/null +++ b/dts/bindings/input/st,stm32-tsc.yaml @@ -0,0 +1,121 @@ +# Copyright (c) 2024 Arif Balik +# SPDX-License-Identifier: Apache-2.0 + +description: STM32 Tocuh Sensing Controller (TSC) driver + +compatible: "st,stm32-tsc" + +include: [base.yaml, pinctrl-device.yaml, reset-device.yaml] + +properties: + reg: + required: true + clocks: + required: true + resets: + required: true + interrupts: + required: true + pinctrl-0: + required: true + pinctrl-names: + required: true + + st,pulse-generator-prescaler: + type: int + required: true + description: | + Prescaler for the pulse generator clock (t_pgclk=f_hclk/prescaler). + The prescaler is used to generate the charge transfer pulse. The final + prescaled value is 2^N where N is the value of this property. + enum: [1, 2, 4, 8, 16, 32, 64, 128] + + st,charge-transfer-pulse-high: + type: int + required: true + description: | + Number of cycles for the high state of the + charge transfer pulse (1 to 16 cycles of t_pgclk). + enum: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] + + st,charge-transfer-pulse-low: + type: int + required: true + description: | + Number of cycles for the low state of the + charge transfer pulse (1 to 16 cycles of t_pgclk). + enum: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] + + st,spread-spectrum: + type: boolean + description: Spread spectrum enable + + st,spread-spectrum-prescaler: + type: int + required: true + description: Spread spectrum clock prescaler (t_ssclk) + enum: + - 1 + - 2 + + st,spread-spectrum-deviation: + type: int + required: true + description: Spread spectrum deviation (1 to 128 cycles of t_ssclk) + + st,max-count-value: + type: int + required: true + description: | + Max number of charge transfer pulses before max count error is generated. + The value of the counter is calculated as 256 * 2^max_count_value. + enum: [255, 511, 1023, 2047, 4095, 8191, 16383, 32767] + + st,synced-acquisition: + type: boolean + description: | + Synchronized acquisition enable. + Acquisition starts when START bit and signal on sync pin. + You have to provide a pinctrl for the sync pin. + + st,syncpol-rising: + type: boolean + description: Rising synchronization pin polarity, instead of falling + + st,iodef-float: + type: boolean + description: | + Set default state (not acqusition) of all channel I/Os to floating. + push-pull down by default. + +child-binding: + description: STM32 TSC group configuration + properties: + group: + type: int + required: true + description: Group number (0 to 7) + enum: [0, 1, 2, 3, 4, 5, 6, 7] + + channel-ios: + type: int + required: true + description: Channel I/Os to be enabled + enum: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] + + sampling-io: + type: int + required: true + description: Channel to be selected for sampling + enum: + - 1 + - 2 + - 4 + - 8 + + st,use-as-shield: + type: boolean + description: | + Use channel as shield. This configures group but + does not enable it for acqusition. channel-io is used + as shield pin and can only have values 1, 2, 4 or 8. diff --git a/include/zephyr/dt-bindings/input/stm32-tsc-defines.h b/include/zephyr/dt-bindings/input/stm32-tsc-defines.h new file mode 100644 index 0000000000000..5153e985c9570 --- /dev/null +++ b/include/zephyr/dt-bindings/input/stm32-tsc-defines.h @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2024 Arif Balik + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DT_BINDINGS_STM32_TSC_H_ +#define ZEPHYR_INCLUDE_DT_BINDINGS_STM32_TSC_H_ + +#define TSC_IO1 0x1UL +#define TSC_IO2 0x2UL +#define TSC_IO3 0x4UL +#define TSC_IO4 0x8UL + +#endif /* ZEPHYR_INCLUDE_DT_BINDINGS_STM32_TSC_H_ */ From 3ff6b02fac3cb00de072ac4a968b7bde5f30cd94 Mon Sep 17 00:00:00 2001 From: Arif Balik Date: Sun, 10 Nov 2024 09:50:11 +0000 Subject: [PATCH 2/4] boards: st: added tsc peripheral to U0 series update stm32u0 and stm32u083c_dk for TSC peripheral Signed-off-by: Arif Balik --- boards/st/stm32u083c_dk/stm32u083c_dk.dts | 28 ++++++++++++++++++++++ boards/st/stm32u083c_dk/stm32u083c_dk.yaml | 1 + dts/arm/st/u0/stm32u0.dtsi | 9 +++++++ 3 files changed, 38 insertions(+) diff --git a/boards/st/stm32u083c_dk/stm32u083c_dk.dts b/boards/st/stm32u083c_dk/stm32u083c_dk.dts index 4f340422d5152..be7d1930cdc64 100644 --- a/boards/st/stm32u083c_dk/stm32u083c_dk.dts +++ b/boards/st/stm32u083c_dk/stm32u083c_dk.dts @@ -9,6 +9,7 @@ #include #include "arduino_r3_connector.dtsi" #include +#include / { model = "STMicroelectronics STM32U83C-DK board"; @@ -124,3 +125,30 @@ status = "okay"; }; }; + +&tsc { + status = "okay"; + pinctrl-0 = <&tsc_g1_io1_pb12 &tsc_g1_io2_pb13 &tsc_g6_io1_pd10 &tsc_g6_io2_pd11>; + pinctrl-names = "default"; + + st,pulse-generator-prescaler = <2>; + st,charge-transfer-pulse-high = <2>; + st,charge-transfer-pulse-low = <2>; + st,spread-spectrum; + st,spread-spectrum-prescaler = <2>; + st,spread-spectrum-deviation = <100>; + st,max-count-value = <8191>; + + tsc_group1: g1 { + group = <1>; + st,use-as-shield; + channel-ios = ; + sampling-io = ; + }; + + tsc_group6: g6 { + group = <6>; + channel-ios = ; + sampling-io = ; + }; +}; diff --git a/boards/st/stm32u083c_dk/stm32u083c_dk.yaml b/boards/st/stm32u083c_dk/stm32u083c_dk.yaml index 3db304b10ef10..d878c23afb7d2 100644 --- a/boards/st/stm32u083c_dk/stm32u083c_dk.yaml +++ b/boards/st/stm32u083c_dk/stm32u083c_dk.yaml @@ -16,5 +16,6 @@ supported: - i2c - pwm - usart + - tsc ram: 40 flash: 256 diff --git a/dts/arm/st/u0/stm32u0.dtsi b/dts/arm/st/u0/stm32u0.dtsi index 904a61ab4174a..8481edd85f787 100644 --- a/dts/arm/st/u0/stm32u0.dtsi +++ b/dts/arm/st/u0/stm32u0.dtsi @@ -541,6 +541,15 @@ reg = <0x40009400 0x400>; interrupts = <18 1>; interrupt-names = "combined"; + }; + + tsc: tsc@40024000 { + compatible = "st,stm32-tsc"; + reg = <0x40024000 0x400>; + clocks = <&rcc STM32_CLOCK(AHB1, 24U)>; + resets = <&rctl STM32_RESET(AHB1, 24U)>; + interrupts = <21 0>; + interrupt-names = "global"; status = "disabled"; }; }; From d84c2e2b5872d946839c6dffb3cbb85133d14860 Mon Sep 17 00:00:00 2001 From: Arif Balik Date: Mon, 2 Dec 2024 20:55:03 +0300 Subject: [PATCH 3/4] driver: input: added input_tsc_keys input_tsc_keys to detect key press releases using STM32 TSC Signed-off-by: Arif Balik --- boards/st/stm32u083c_dk/stm32u083c_dk.dts | 8 + drivers/input/CMakeLists.txt | 1 + drivers/input/Kconfig | 1 + drivers/input/Kconfig.tsc_keys | 21 + drivers/input/input_tsc_keys.c | 446 ++++++++++++++++++++++ dts/bindings/input/tsc-keys.yaml | 71 ++++ 6 files changed, 548 insertions(+) create mode 100644 drivers/input/Kconfig.tsc_keys create mode 100644 drivers/input/input_tsc_keys.c create mode 100644 dts/bindings/input/tsc-keys.yaml diff --git a/boards/st/stm32u083c_dk/stm32u083c_dk.dts b/boards/st/stm32u083c_dk/stm32u083c_dk.dts index be7d1930cdc64..c5cbfb93a2e07 100644 --- a/boards/st/stm32u083c_dk/stm32u083c_dk.dts +++ b/boards/st/stm32u083c_dk/stm32u083c_dk.dts @@ -150,5 +150,13 @@ group = <6>; channel-ios = ; sampling-io = ; + ts1 { + compatible = "tsc-keys"; + sampling-interval-ms = <10>; + oversampling = <10>; + noise-threshold = <50>; + sticky-key-timeout-ms = <10000>; + zephyr,code = ; + }; }; }; diff --git a/drivers/input/CMakeLists.txt b/drivers/input/CMakeLists.txt index 9215060d8ad65..8cb95b9e0c53f 100644 --- a/drivers/input/CMakeLists.txt +++ b/drivers/input/CMakeLists.txt @@ -28,6 +28,7 @@ zephyr_library_sources_ifdef(CONFIG_INPUT_PAW32XX input_paw32xx.c) zephyr_library_sources_ifdef(CONFIG_INPUT_PINNACLE input_pinnacle.c) zephyr_library_sources_ifdef(CONFIG_INPUT_PMW3610 input_pmw3610.c) zephyr_library_sources_ifdef(CONFIG_INPUT_SBUS input_sbus.c) +zephyr_library_sources_ifdef(CONFIG_INPUT_STM32_TSC_KEYS input_tsc_keys.c) zephyr_library_sources_ifdef(CONFIG_INPUT_STMPE811 input_stmpe811.c) zephyr_library_sources_ifdef(CONFIG_INPUT_TOUCH input_touch.c) zephyr_library_sources_ifdef(CONFIG_INPUT_XEC_KBD input_xec_kbd.c) diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig index 85a316ac12e53..eb3f9cfc26b06 100644 --- a/drivers/input/Kconfig +++ b/drivers/input/Kconfig @@ -33,6 +33,7 @@ source "drivers/input/Kconfig.sbus" source "drivers/input/Kconfig.sdl" source "drivers/input/Kconfig.stmpe811" source "drivers/input/Kconfig.touch" +source "drivers/input/Kconfig.tsc_keys" source "drivers/input/Kconfig.xec" source "drivers/input/Kconfig.xpt2046" # zephyr-keep-sorted-stop diff --git a/drivers/input/Kconfig.tsc_keys b/drivers/input/Kconfig.tsc_keys new file mode 100644 index 0000000000000..57b05321a6731 --- /dev/null +++ b/drivers/input/Kconfig.tsc_keys @@ -0,0 +1,21 @@ +# Copyright (c) 2024 Arif Balik +# SPDX-License-Identifier: Apache-2.0 + +config INPUT_STM32_TSC_KEYS + bool "STM32 TSC touch library" + default y + depends on DT_HAS_ST_STM32_TSC_ENABLED + select RING_BUFFER + help + Enable support for STM32 TSC touch library. + +config INPUT_STM32_TSC_KEYS_BUFFER_WORD_SIZE + int "STM32 TSC touch buffer size in words" + default 10 + depends on INPUT_STM32_TSC_KEYS + help + Size of the ring buffer for the STM32 TSC touch library. The size of this + buffer together with the sampling-interval property in the tsc-keys + device tree node determines the time window of interest. A slope value is + calculated between the oldest and the newest value in the buffer to + generate a press or release input event. diff --git a/drivers/input/input_tsc_keys.c b/drivers/input/input_tsc_keys.c new file mode 100644 index 0000000000000..a09afc53782b4 --- /dev/null +++ b/drivers/input/input_tsc_keys.c @@ -0,0 +1,446 @@ +/* + * Copyright (c) 2024 Arif Balik + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(tsc_keys, CONFIG_INPUT_LOG_LEVEL); + +#define DT_DRV_COMPAT st_stm32_tsc + +/* each group only has 4 configurable I/O */ +#define GET_GROUP_BITS(val, group) (uint32_t)(((val) & 0x0f) << ((group - 1) * 4)) + +struct stm32_tsc_group_config { + uint8_t group; + uint8_t channel_ios; + uint8_t sampling_io; + bool use_as_shield; +}; + +typedef void (*stm32_tsc_group_ready_cb)(uint32_t count_value, void *user_data); + +struct stm32_tsc_group_data { + stm32_tsc_group_ready_cb cb; + void *user_data; +}; + +struct stm32_tsc_config { + const TSC_TypeDef *tsc; + const struct stm32_pclken *pclken; + struct reset_dt_spec reset; + const struct pinctrl_dev_config *pcfg; + const struct stm32_tsc_group_config *group_config; + struct stm32_tsc_group_data *group_data; + uint8_t group_cnt; + + uint32_t pgpsc; + uint8_t ctph; + uint8_t ctpl; + bool spread_spectrum; + uint8_t sscpsc; + uint8_t ssd; + uint16_t max_count; + bool iodef; + bool sync_acq; + bool sync_pol; + void (*irq_func)(void); +}; + +int stm32_tsc_group_register_callback(const struct device *dev, uint8_t group_idx, + stm32_tsc_group_ready_cb cb, void *user_data) +{ + const struct stm32_tsc_config *config = dev->config; + + if (group_idx >= config->group_cnt) { + LOG_ERR("%s: group index %d is out of range", dev->name, group_idx); + return -EINVAL; + } + + struct stm32_tsc_group_data *group_data = &config->group_data[group_idx]; + + group_data->cb = cb; + group_data->user_data = user_data; + + return 0; +} + +void stm32_tsc_start(const struct device *dev) +{ + const struct stm32_tsc_config *config = dev->config; + + /* clear interrupts */ + sys_set_bits((mem_addr_t)&config->tsc->ICR, TSC_ICR_EOAIC | TSC_ICR_MCEIC); + + /* enable end of acquisition and max count error interrupts */ + sys_set_bits((mem_addr_t)&config->tsc->IER, TSC_IER_EOAIE | TSC_IER_MCEIE); + + /* TODO: When sync acqusition mode is enabled, both this bit and an external input signal + * should be set. When the acqusition stops this bit is cleared, so even if a sync signal is + * present, the next acqusition will not start until this bit is set again. + */ + /* start acquisition */ + sys_set_bit((mem_addr_t)&config->tsc->CR, TSC_CR_START_Pos); +} + +static int get_group_index(const struct device *dev, uint8_t group, uint8_t *group_idx) +{ + const struct stm32_tsc_config *config = dev->config; + const struct stm32_tsc_group_config *groups = config->group_config; + + for (int i = 0; i < config->group_cnt; i++) { + if (groups[i].group == group) { + *group_idx = i; + return 0; + } + } + + return -ENODEV; +} + +static int stm32_tsc_handle_incoming_data(const struct device *dev) +{ + const struct stm32_tsc_config *config = dev->config; + + if (sys_test_bit((mem_addr_t)&config->tsc->ISR, TSC_ISR_MCEF_Pos)) { + /* clear max count error flag */ + sys_set_bit((mem_addr_t)&config->tsc->ICR, TSC_ICR_MCEIC_Pos); + LOG_ERR("%s: max count error", dev->name); + LOG_HEXDUMP_DBG(config->tsc, sizeof(TSC_TypeDef), "TSC Registers"); + return -EIO; + } + + if (sys_test_bit((mem_addr_t)&config->tsc->ISR, TSC_ISR_EOAF_Pos)) { + /* clear end of acquisition flag */ + sys_set_bit((mem_addr_t)&config->tsc->ICR, TSC_ICR_EOAIC_Pos); + + /* read values */ + for (uint8_t i = 0; i < config->group_cnt; i++) { + const struct stm32_tsc_group_config *group = &config->group_config[i]; + uint32_t group_bit = BIT(group->group - 1) << 16; + + if (config->tsc->IOGCSR & group_bit) { + uint32_t count_value = sys_read32( + (mem_addr_t)&config->tsc->IOGXCR[group->group - 1]); + + uint8_t group_idx = 0; + + int ret = get_group_index(dev, group->group, &group_idx); + + if (ret < 0) { + LOG_ERR("%s: group %d not found", dev->name, group->group); + return ret; + } + + struct stm32_tsc_group_data *data = &config->group_data[group_idx]; + + if (data->cb) { + data->cb(count_value, data->user_data); + } + } + } + } + + return 0; +} + +static void stm32_tsc_isr(const struct device *dev) +{ + const struct stm32_tsc_config *config = dev->config; + + /* disable interrupts */ + sys_clear_bits((mem_addr_t)&config->tsc->IER, TSC_IER_EOAIE | TSC_IER_MCEIE); + + stm32_tsc_handle_incoming_data(dev); +} + +static int stm32_tsc_init(const struct device *dev) +{ + const struct stm32_tsc_config *config = dev->config; + const struct device *const clk = DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE); + + int ret; + + if (!device_is_ready(clk)) { + LOG_ERR("%s: clock controller device not ready", dev->name); + return -ENODEV; + } + + /* reset TSC values to default */ + ret = reset_line_toggle_dt(&config->reset); + if (ret < 0) { + LOG_ERR("Failed to reset %s (%d)", dev->name, ret); + return ret; + } + + ret = clock_control_on(clk, (clock_control_subsys_t)&config->pclken[0]); + if (ret < 0) { + LOG_ERR("Failed to enable clock for %s (%d)", dev->name, ret); + return ret; + } + + ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT); + if (ret < 0) { + LOG_ERR("Failed to configure %s pins (%d)", dev->name, ret); + return ret; + } + + /* set ctph (bits 31:28) and ctpl (bits 27:24) */ + sys_set_bits((mem_addr_t)&config->tsc->CR, (((config->ctph - 1) << 4) | (config->ctpl - 1)) + << TSC_CR_CTPL_Pos); + + /* set spread spectrum deviation (bits 23:17) */ + sys_set_bits((mem_addr_t)&config->tsc->CR, config->ssd << TSC_CR_SSD_Pos); + + /* set pulse generator prescaler (bits 14:12) */ + sys_set_bits((mem_addr_t)&config->tsc->CR, config->pgpsc << TSC_CR_PGPSC_Pos); + + /* set max count value (bits 7:5) */ + sys_set_bits((mem_addr_t)&config->tsc->CR, config->max_count << TSC_CR_MCV_Pos); + + /* set spread spectrum prescaler (bit 15) */ + if (config->sscpsc == 2) { + sys_set_bit((mem_addr_t)&config->tsc->CR, TSC_CR_SSPSC_Pos); + } + + /* set sync bit polarity */ + if (config->sync_pol) { + sys_set_bit((mem_addr_t)&config->tsc->CR, TSC_CR_SYNCPOL_Pos); + } + + /* set sync acquisition */ + if (config->sync_acq) { + sys_set_bit((mem_addr_t)&config->tsc->CR, TSC_CR_AM_Pos); + } + + /* set I/O default mode */ + if (config->iodef) { + sys_set_bit((mem_addr_t)&config->tsc->CR, TSC_CR_IODEF_Pos); + } + + /* set spread spectrum */ + if (config->spread_spectrum) { + sys_set_bit((mem_addr_t)&config->tsc->CR, TSC_CR_SSE_Pos); + } + + /* group configuration */ + for (int i = 0; i < config->group_cnt; i++) { + const struct stm32_tsc_group_config *group = &config->group_config[i]; + + if (group->channel_ios & group->sampling_io) { + LOG_ERR("%s: group %d has the same channel and sampling I/O", dev->name, + group->group); + return -EINVAL; + } + + /* if use_as_shield is true, the channel I/Os are used as shield, and can only have + * values 1,2,4,8 + */ + if (group->use_as_shield && group->channel_ios != 1 && group->channel_ios != 2 && + group->channel_ios != 4 && group->channel_ios != 8) { + LOG_ERR("%s: group %d is used as shield, but has invalid channel I/Os. " + "Can only have one", + dev->name, group->group); + return -EINVAL; + } + + /* clear schmitt trigger hysteresis for enabled I/Os */ + sys_clear_bits( + (mem_addr_t)&config->tsc->IOHCR, + GET_GROUP_BITS(group->channel_ios | group->sampling_io, group->group)); + + /* set channel I/Os */ + sys_set_bits((mem_addr_t)&config->tsc->IOCCR, + GET_GROUP_BITS(group->channel_ios, group->group)); + + /* set sampling I/O */ + sys_set_bits((mem_addr_t)&config->tsc->IOSCR, + GET_GROUP_BITS(group->sampling_io, group->group)); + + /* enable group */ + if (!group->use_as_shield) { + sys_set_bit((mem_addr_t)&config->tsc->IOGCSR, group->group - 1); + } + } + + /* disable interrupts */ + sys_clear_bits((mem_addr_t)&config->tsc->IER, TSC_IER_EOAIE | TSC_IER_MCEIE); + + /* clear interrupts */ + sys_set_bits((mem_addr_t)&config->tsc->ICR, TSC_ICR_EOAIC | TSC_ICR_MCEIC); + + /* enable peripheral */ + sys_set_bit((mem_addr_t)&config->tsc->CR, TSC_CR_TSCE_Pos); + + config->irq_func(); + + return 0; +} + +#define STM32_TSC_GROUP_DEFINE(node_id) \ + { \ + .group = DT_PROP(node_id, group), \ + .channel_ios = DT_PROP(node_id, channel_ios), \ + .sampling_io = DT_PROP(node_id, sampling_io), \ + .use_as_shield = DT_PROP(node_id, st_use_as_shield), \ + } + +#define STM32_TSC_INIT(index) \ + static const struct stm32_pclken pclken_##index[] = STM32_DT_INST_CLOCKS(index); \ + \ + PINCTRL_DT_INST_DEFINE(index); \ + \ + static void stm32_tsc_irq_init_##index(void) \ + { \ + IRQ_CONNECT(DT_INST_IRQN(index), DT_INST_IRQ(index, priority), stm32_tsc_isr, \ + DEVICE_DT_INST_GET(index), 0); \ + irq_enable(DT_INST_IRQN(index)); \ + }; \ + \ + static const struct stm32_tsc_group_config group_config_cfg_##index[] = { \ + DT_INST_FOREACH_CHILD_STATUS_OKAY_SEP(index, STM32_TSC_GROUP_DEFINE, (,))}; \ + \ + static struct stm32_tsc_group_data \ + group_data_cfg_##index[DT_INST_CHILD_NUM_STATUS_OKAY(index)]; \ + \ + static const struct stm32_tsc_config stm32_tsc_cfg_##index = { \ + .tsc = (TSC_TypeDef *)DT_INST_REG_ADDR(index), \ + .pclken = pclken_##index, \ + .reset = RESET_DT_SPEC_INST_GET(index), \ + .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(index), \ + .group_config = group_config_cfg_##index, \ + .group_data = group_data_cfg_##index, \ + .group_cnt = DT_INST_CHILD_NUM_STATUS_OKAY(index), \ + .pgpsc = LOG2CEIL(DT_INST_PROP(index, st_pulse_generator_prescaler)), \ + .ctph = DT_INST_PROP(index, st_charge_transfer_pulse_high), \ + .ctpl = DT_INST_PROP(index, st_charge_transfer_pulse_low), \ + .spread_spectrum = DT_INST_PROP(index, st_spread_spectrum), \ + .sscpsc = DT_INST_PROP(index, st_spread_spectrum_prescaler), \ + .ssd = DT_INST_PROP(index, st_spread_spectrum_deviation), \ + .max_count = LOG2CEIL(DT_INST_PROP(index, st_max_count_value) + 1) - 8, \ + .iodef = DT_INST_PROP(index, st_iodef_float), \ + .sync_acq = DT_INST_PROP(index, st_synced_acquisition), \ + .sync_pol = DT_INST_PROP(index, st_syncpol_rising), \ + .irq_func = stm32_tsc_irq_init_##index, \ + }; \ + DEVICE_DT_INST_DEFINE(index, stm32_tsc_init, NULL, NULL, &stm32_tsc_cfg_##index, \ + POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, NULL); + +DT_INST_FOREACH_STATUS_OKAY(STM32_TSC_INIT) + +struct input_tsc_keys_data { + uint32_t buffer[CONFIG_INPUT_STM32_TSC_KEYS_BUFFER_WORD_SIZE]; + struct ring_buf rb; + bool expect_release; + struct k_timer sampling_timer; +}; + +struct input_tsc_keys_config { + const struct device *tsc_dev; + uint32_t sampling_interval_ms; + int32_t noise_threshold; + int zephyr_code; + uint8_t group; +}; + +static void input_tsc_sampling_timer_callback(struct k_timer *timer) +{ + const struct device *dev = k_timer_user_data_get(timer); + + stm32_tsc_start(dev); +} + +static void input_tsc_callback_handler(uint32_t count_value, void *user_data) +{ + const struct device *dev = (const struct device *)user_data; + const struct input_tsc_keys_config *config = + (const struct input_tsc_keys_config *)dev->config; + struct input_tsc_keys_data *data = (struct input_tsc_keys_data *)dev->data; + + if (ring_buf_item_space_get(&data->rb) == 0) { + uint32_t oldest_point; + int32_t slope; + + (void)ring_buf_get(&data->rb, (uint8_t *)&oldest_point, sizeof(oldest_point)); + + slope = count_value - oldest_point; + if (slope < -config->noise_threshold && !data->expect_release) { + data->expect_release = true; + input_report_key(dev, config->zephyr_code, 1, false, K_NO_WAIT); + } else if (slope > config->noise_threshold && data->expect_release) { + data->expect_release = false; + input_report_key(dev, config->zephyr_code, 0, false, K_NO_WAIT); + } + } + + (void)ring_buf_put(&data->rb, (uint8_t *)&count_value, sizeof(count_value)); +} + +static int input_tsc_keys_init(const struct device *dev) +{ + const struct input_tsc_keys_config *config = dev->config; + struct input_tsc_keys_data *data = dev->data; + + if (!device_is_ready(config->tsc_dev)) { + LOG_ERR("%s: TSC device not ready", config->tsc_dev->name); + return -ENODEV; + } + + ring_buf_item_init(&data->rb, CONFIG_INPUT_STM32_TSC_KEYS_BUFFER_WORD_SIZE, data->buffer); + + uint8_t group_index = 0; + + int ret = get_group_index(config->tsc_dev, config->group, &group_index); + + if (ret) { + LOG_ERR("%s: group %d not found", config->tsc_dev->name, config->group); + return ret; + } + + ret = stm32_tsc_group_register_callback(config->tsc_dev, group_index, + input_tsc_callback_handler, (void *)dev); + + if (ret) { + LOG_ERR("%s: failed to register callback for group %d", config->tsc_dev->name, + config->group); + return ret; + } + + k_timer_init(&data->sampling_timer, input_tsc_sampling_timer_callback, NULL); + k_timer_user_data_set(&data->sampling_timer, (void *)config->tsc_dev); + k_timer_start(&data->sampling_timer, K_NO_WAIT, K_MSEC(config->sampling_interval_ms)); + + return 0; +} + +#define TSC_KEYS_INIT(inst) \ + \ + static struct input_tsc_keys_data tsc_keys_data_##inst; \ + \ + static const struct input_tsc_keys_config tsc_keys_config_##inst = { \ + .tsc_dev = DEVICE_DT_GET(DT_GPARENT(inst)), \ + .sampling_interval_ms = DT_PROP(inst, sampling_interval_ms), \ + .zephyr_code = DT_PROP(inst, zephyr_code), \ + .noise_threshold = DT_PROP(inst, noise_threshold), \ + .group = DT_PROP(DT_PARENT(inst), group), \ + }; \ + \ + DEVICE_DT_DEFINE(inst, input_tsc_keys_init, NULL, &tsc_keys_data_##inst, \ + &tsc_keys_config_##inst, POST_KERNEL, CONFIG_INPUT_INIT_PRIORITY, NULL); + +DT_FOREACH_STATUS_OKAY(tsc_keys, TSC_KEYS_INIT); diff --git a/dts/bindings/input/tsc-keys.yaml b/dts/bindings/input/tsc-keys.yaml new file mode 100644 index 0000000000000..d3b4cb06c189b --- /dev/null +++ b/dts/bindings/input/tsc-keys.yaml @@ -0,0 +1,71 @@ +# Copyright (c) 2024 Arif Balik +# SPDX-License-Identifier: Apache-2.0 + +description: | + Input driver for STM32 Tocuh Sensing Controller (TSC). + + This node is a st,stm32-tsc grandchild node and applies filters and + calculations to detect an input event on a group which is the child of + st,stm32-tsc. For more information see drivers/misc/st,stm32-tsc.yaml + + Example: + + #include + + &tsc { + compatible = "st,stm32-tsc"; + + tsc_group6: g6 { + ... + ts1 { + compatible = "tsc-keys"; + sampling-interval-ms = <10>; + oversampling = <10>; + noise-threshold = <50>; + zephyr,code = ; + }; + }; + }; + +compatible: "tsc-keys" + +include: + - name: base.yaml + property-allowlist: + - compatible + - zephyr,deferred-init + - label + - status + +properties: + sampling-interval-ms: + type: int + required: true + description: | + Sampling interval in milliseconds. + oversampling: + type: int + required: true + description: | + Over sampling factor. The driver will take the average of the samples + taken in the sampling interval and compare it with the previous sample. + Larger values will reduce the noise but will increase the latency. The + default value is 10 so the slope will be calculated every + 10 * sampling-interval-ms milliseconds. + noise-threshold: + type: int + required: true + description: | + This value will be used to reject the noise for both + directions of the slope. + sticky-key-timeout-ms: + type: int + required: true + description: | + Time in milliseconds to wait before releasing a key. By default a release + event will be generated after 10 seconds of the last press event if + the key is still pressed. + zephyr,code: + type: int + required: true + description: Key code to emit. From 5ea72633dd0cadf1dd6b26360dc8e5ff18cedacf Mon Sep 17 00:00:00 2001 From: Arif Balik Date: Sun, 17 Nov 2024 12:57:46 +0300 Subject: [PATCH 4/4] tests: misc: added stm32 tsc driver test implementation integration tests for stm32 tsc Signed-off-by: Arif Balik --- tests/drivers/input/tsc_keys/CMakeLists.txt | 9 + .../tsc_keys/boards/stm32u083c_dk.overlay | 32 +++ tests/drivers/input/tsc_keys/prj.conf | 2 + tests/drivers/input/tsc_keys/src/main.c | 21 ++ .../input/tsc_keys/src/test_stm32_tsc.c | 216 ++++++++++++++++++ tests/drivers/input/tsc_keys/testcase.yaml | 6 + 6 files changed, 286 insertions(+) create mode 100644 tests/drivers/input/tsc_keys/CMakeLists.txt create mode 100644 tests/drivers/input/tsc_keys/boards/stm32u083c_dk.overlay create mode 100644 tests/drivers/input/tsc_keys/prj.conf create mode 100644 tests/drivers/input/tsc_keys/src/main.c create mode 100644 tests/drivers/input/tsc_keys/src/test_stm32_tsc.c create mode 100644 tests/drivers/input/tsc_keys/testcase.yaml diff --git a/tests/drivers/input/tsc_keys/CMakeLists.txt b/tests/drivers/input/tsc_keys/CMakeLists.txt new file mode 100644 index 0000000000000..5746edeb2e07f --- /dev/null +++ b/tests/drivers/input/tsc_keys/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) 2024 Arif Balik +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(stm32_tsc) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/drivers/input/tsc_keys/boards/stm32u083c_dk.overlay b/tests/drivers/input/tsc_keys/boards/stm32u083c_dk.overlay new file mode 100644 index 0000000000000..1ef9dc0534106 --- /dev/null +++ b/tests/drivers/input/tsc_keys/boards/stm32u083c_dk.overlay @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 Arif Balik + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + zephyr,user { + /* This will be used to trigger the SYNC pin high and low programmatically. + * They have to be connected. + */ + signal-gpios = <&gpioa 10 GPIO_ACTIVE_HIGH>; + }; +}; + +&tsc { + status = "okay"; + + pinctrl-0 = <&tsc_g1_io1_pb12 &tsc_g1_io2_pb13 &tsc_g6_io1_pd10 + &tsc_g6_io2_pd11 &tsc_sync_pd2>; + pinctrl-names = "default"; + + st,pulse-generator-prescaler = <128>; + + st,spread-spectrum; + st,spread-spectrum-prescaler = <2>; + st,spread-spectrum-deviation = <10>; + + st,max-count-value = <16383>; + + st,synced-acquisition; + st,syncpol-rising; +}; diff --git a/tests/drivers/input/tsc_keys/prj.conf b/tests/drivers/input/tsc_keys/prj.conf new file mode 100644 index 0000000000000..5b12463c96039 --- /dev/null +++ b/tests/drivers/input/tsc_keys/prj.conf @@ -0,0 +1,2 @@ +CONFIG_ZTEST=y +CONFIG_INPUT=y diff --git a/tests/drivers/input/tsc_keys/src/main.c b/tests/drivers/input/tsc_keys/src/main.c new file mode 100644 index 0000000000000..e70bb05025f9c --- /dev/null +++ b/tests/drivers/input/tsc_keys/src/main.c @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2024 Arif Balik + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +extern const struct gpio_dt_spec signal_mock; + +static void *stm32_tsc_setup(void) +{ + zexpect_ok(gpio_pin_configure_dt(&signal_mock, GPIO_OUTPUT_INACTIVE), + "Failed to configure signal_mock pin"); + + return NULL; +} + +ZTEST_SUITE(stm32_tsc, NULL, stm32_tsc_setup, NULL, NULL, NULL); diff --git a/tests/drivers/input/tsc_keys/src/test_stm32_tsc.c b/tests/drivers/input/tsc_keys/src/test_stm32_tsc.c new file mode 100644 index 0000000000000..3ca3d2c462cf6 --- /dev/null +++ b/tests/drivers/input/tsc_keys/src/test_stm32_tsc.c @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2024 Arif Balik + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Verify STM32 TSC peripheral is configured properly and can be started + * + * @details + * This test requires an external connection on the stm32u083c_dk board. The + * pin GPIOA 10 should be connected to GPIOD 2 manually so that sync signal can + * be generated. Also make sure to press TS1 pad on the board in order to + * generate touch signal on test 5. + * - Test Steps + * -# Get a TSC device + * -# Verify the device is ready + * -# Verify MIMO region with device tree values + * -# Test the acquisition in polling mode + * -# Test the acquisition in interrupt mode + * - Expected Results + * -# The device is ready + * -# The device tree values are correctly mapped to the TSC registers + * -# The acquisition is successful in polling mode + * -# The acquisition is successful in interrupt mode + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#if DT_HAS_COMPAT_STATUS_OKAY(st_stm32_tsc) +#define TSC_NODE DT_INST(0, st_stm32_tsc) +#else +#error "Could not find an st,stm32-tsc compatible device in DT" +#endif + +#define ZEPHYR_USER_NODE DT_PATH(zephyr_user) + +const struct gpio_dt_spec signal_mock = GPIO_DT_SPEC_GET(ZEPHYR_USER_NODE, signal_gpios); + +ZTEST(stm32_tsc, test_1_device_ready) +{ + const struct device *dev = DEVICE_DT_GET(TSC_NODE); + + zassert_true(device_is_ready(dev), "STM32 TSC device is not ready"); +} + +ZTEST(stm32_tsc, test_2_cr_reg) +{ + TSC_TypeDef *tsc = (TSC_TypeDef *)DT_REG_ADDR(TSC_NODE); + + volatile const uint32_t *tsc_cr = &tsc->CR; + const uint32_t pgpsc = LOG2CEIL(DT_PROP(TSC_NODE, st_pulse_generator_prescaler)); + const uint8_t ctph = DT_PROP(TSC_NODE, st_charge_transfer_pulse_high); + const uint8_t ctpl = DT_PROP(TSC_NODE, st_charge_transfer_pulse_low); + const uint8_t ssd = DT_PROP(TSC_NODE, st_spread_spectrum_deviation); + const bool spread_spectrum = DT_PROP(TSC_NODE, st_spread_spectrum); + const uint8_t sscpsc = DT_PROP(TSC_NODE, st_spread_spectrum_prescaler); + const uint16_t max_count = LOG2CEIL(DT_PROP(TSC_NODE, st_max_count_value) + 1) - 8; + const bool iodef = DT_PROP(TSC_NODE, st_iodef_float); + const bool sync_pol = DT_PROP(TSC_NODE, st_syncpol_rising); + const bool sync_acq = DT_PROP(TSC_NODE, st_synced_acquisition); + + /* check charge transfer pulse high value (bits 31:28) */ + zassert_equal((*tsc_cr & TSC_CR_CTPL_Msk) >> TSC_CR_CTPL_Pos, ctph - 1, + "CTPH value is not correct, expected %d, got %d", ctph - 1, + (*tsc_cr & TSC_CR_CTPL_Msk) >> TSC_CR_CTPL_Pos); + + /* check charge transfer pulse low value (bits 27:24) */ + zassert_equal((*tsc_cr & TSC_CR_CTPL_Msk) >> TSC_CR_CTPL_Pos, ctpl - 1, + "CTPL value is not correct, expected %d, got %d", ctpl - 1, + (*tsc_cr & TSC_CR_CTPL_Msk) >> TSC_CR_CTPL_Pos); + + /* check spread spectrum deviation value (bits 23:17) */ + zassert_equal((*tsc_cr & TSC_CR_SSD_Msk) >> TSC_CR_SSD_Pos, ssd, + "SSD value is not correct, expected %d, got %d", ssd, + (*tsc_cr & TSC_CR_SSD_Msk) >> TSC_CR_SSD_Pos); + + /* check spread spectrum enable bit (bit 16) */ + if (spread_spectrum) { + zexpect_true(*tsc_cr & TSC_CR_SSE_Msk); + } else { + zexpect_false(*tsc_cr & TSC_CR_SSE_Msk); + } + + /* check spread spectrum prescaler value (bits 15) */ + if (sscpsc == 2) { + zexpect_true(*tsc_cr & TSC_CR_SSPSC_Msk); + } else { + zexpect_false(*tsc_cr & TSC_CR_SSPSC_Msk); + } + + /* check pulse generator prescaler value (bits 14:12) */ + zassert_equal((*tsc_cr & TSC_CR_PGPSC_Msk), pgpsc << TSC_CR_PGPSC_Pos, + "PGPSC value is not correct, expected %d, got %d", pgpsc, + (*tsc_cr & TSC_CR_PGPSC_Msk)); + + /* check max count value (bits 7:5) */ + zassert_equal((*tsc_cr & TSC_CR_MCV_Msk), max_count << TSC_CR_MCV_Pos, + "MCV value is not correct, expected %d, got %d", max_count, + (*tsc_cr & TSC_CR_MCV_Msk)); + + /* check I/O default mode bit (bit 4) */ + if (iodef) { + zexpect_true(*tsc_cr & TSC_CR_IODEF_Msk); + } else { + zexpect_false(*tsc_cr & TSC_CR_IODEF_Msk); + } + + /* check sync polarity bit (bit 3) */ + if (sync_pol) { + zexpect_true(*tsc_cr & TSC_CR_SYNCPOL_Msk); + } else { + zexpect_false(*tsc_cr & TSC_CR_SYNCPOL_Msk); + } + + /* check sync acquisition bit (bit 2) */ + if (sync_acq) { + zexpect_true(*tsc_cr & TSC_CR_AM_Msk); + } else { + zexpect_false(*tsc_cr & TSC_CR_AM_Msk); + } + + /* check TSC enable bit (bit 0) */ + zexpect_true(*tsc_cr & TSC_CR_TSCE_Msk); +} + +ZTEST(stm32_tsc, test_3_group_registers) +{ + TSC_TypeDef *tsc = (TSC_TypeDef *)DT_REG_ADDR(TSC_NODE); + volatile const uint32_t *tsc_iohcr = &tsc->IOHCR; + volatile const uint32_t *tsc_ioscr = &tsc->IOSCR; + volatile const uint32_t *tsc_ioccr = &tsc->IOCCR; + volatile const uint32_t *tsc_iogcsr = &tsc->IOGCSR; + +#define GET_GROUP_BITS(val) (uint32_t)(((val) & 0x0f) << ((group - 1) * 4)) + +#define STM32_TSC_GROUP_TEST(node) \ + do { \ + const uint8_t group = DT_PROP(node, group); \ + const uint8_t channel_ios = DT_PROP(node, channel_ios); \ + const uint8_t sampling_io = DT_PROP(node, sampling_io); \ + const bool use_as_shield = DT_PROP(node, st_use_as_shield); \ + \ + /* check schmitt trigger hysteresis for enabled I/Os */ \ + zassert_equal((*tsc_iohcr & GET_GROUP_BITS(channel_ios | sampling_io)), 0, \ + "Schmitt trigger hysteresis not disabled, expected %d, got %d", 0, \ + (*tsc_iohcr & GET_GROUP_BITS(channel_ios | sampling_io))); \ + \ + /* check channel I/Os */ \ + zassert_equal( \ + (*tsc_ioccr & GET_GROUP_BITS(channel_ios)), GET_GROUP_BITS(channel_ios), \ + "Channel I/Os value is not correct, expected %d, got %d", \ + GET_GROUP_BITS(channel_ios), (*tsc_ioccr & GET_GROUP_BITS(channel_ios))); \ + \ + /* check sampling I/O */ \ + zassert_equal( \ + (*tsc_ioscr & GET_GROUP_BITS(sampling_io)), GET_GROUP_BITS(sampling_io), \ + "Sampling I/O value is not correct, expected %d, got %d", \ + GET_GROUP_BITS(sampling_io), (*tsc_ioscr & GET_GROUP_BITS(sampling_io))); \ + \ + /* check enabled groups */ \ + if (use_as_shield) { \ + zassert_not_equal((*tsc_iogcsr & BIT(group - 1)), BIT(group - 1), \ + "Group %d is a shield group and should not be enabled", \ + group); \ + } else { \ + zassert_equal((*tsc_iogcsr & BIT(group - 1)), BIT(group - 1), \ + "Group %d is not enabled", group); \ + } \ + } while (0) + +#define GROUP_TEST_RUN(node) STM32_TSC_GROUP_TEST(node); + + DT_FOREACH_CHILD_STATUS_OKAY(TSC_NODE, GROUP_TEST_RUN); +} + +static volatile bool tsc_input_received; + +static void tsc_input_callback(struct input_event *evt, void *user_data) +{ + ARG_UNUSED(evt); + ARG_UNUSED(user_data); + + tsc_input_received = true; +} +INPUT_CALLBACK_DEFINE(NULL, tsc_input_callback, NULL); + +ZTEST(stm32_tsc, test_5_acquisition_interrupt) +{ + TSC_TypeDef *tsc = (TSC_TypeDef *)DT_REG_ADDR(TSC_NODE); + volatile const uint32_t *tsc_isr = &tsc->ISR; + + k_sleep(K_MSEC(100)); + + /* test ISR register max count error flag */ + zexpect_false((*tsc_isr & TSC_ISR_MCEF_Msk) >> TSC_ISR_MCEF_Pos); + + /* this should fail because of the sync pin */ + zexpect_false(tsc_input_received); + + zexpect_ok(gpio_pin_toggle_dt(&signal_mock)); + + /* press the TS1 pad */ + + k_sleep(K_MSEC(3000)); + + zexpect_true(tsc_input_received); +} diff --git a/tests/drivers/input/tsc_keys/testcase.yaml b/tests/drivers/input/tsc_keys/testcase.yaml new file mode 100644 index 0000000000000..4122f550ad88c --- /dev/null +++ b/tests/drivers/input/tsc_keys/testcase.yaml @@ -0,0 +1,6 @@ +tests: + drivers.input.stm32_tsc: + tags: drivers input stm32 tsc + filter: dt_compat_enabled("st,stm32-tsc") + depends_on: tsc + integration_platforms: [stm32u083c_dk]