From 3d0d1bfd2207af3464056f84dc2163b801dfa831 Mon Sep 17 00:00:00 2001 From: Kim Streich Date: Wed, 12 Apr 2023 12:02:16 -0600 Subject: [PATCH 1/2] drivers: ps2: Add resend callback to ps/2 interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the PS/2 driver detects an error in an incoming transmission, it’s supposed to send 0xfe to the device to ask it to resend the last packet. But a PS/2 packet can be more than one byte (such as a 3 or 4-byte mouse packet). So, on reception of the 0xfe resend command, the PS/2 device resends ALL bytes in the packet and not just the one where the transmission failed. This can cause the higher level driver’s packet buffer to become misaligned. This PR adds a callback that notifies the higher level driver when the PS/2 driver requested a resend so that the higher level driver can expect a new packet and doesn’t get misaligned. --- drivers/ps2/ps2_handlers.c | 8 ++++++-- drivers/ps2/ps2_mchp_xec.c | 3 ++- drivers/ps2/ps2_npcx_channel.c | 3 ++- include/zephyr/drivers/ps2.h | 24 ++++++++++++++++++++---- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/drivers/ps2/ps2_handlers.c b/drivers/ps2/ps2_handlers.c index 71d1181a2d2955..e9501e92fcbbf1 100644 --- a/drivers/ps2/ps2_handlers.c +++ b/drivers/ps2/ps2_handlers.c @@ -8,12 +8,16 @@ #include static inline int z_vrfy_ps2_config(const struct device *dev, - ps2_callback_t callback_isr) + ps2_callback_t callback_isr, + ps2_resend_callback_t resend_callback_isr) + { Z_OOPS(Z_SYSCALL_DRIVER_PS2(dev, config)); Z_OOPS(Z_SYSCALL_VERIFY_MSG(callback_isr == NULL, "callback not be set from user mode")); - return z_impl_ps2_config(dev, callback_isr); + Z_OOPS(Z_SYSCALL_VERIFY_MSG(resend_callback_isr == NULL, + "resend callback not be set from user mode")); + return z_impl_ps2_config(dev, callback_isr, resend_callback_isr); } #include diff --git a/drivers/ps2/ps2_mchp_xec.c b/drivers/ps2/ps2_mchp_xec.c index c9b43a7ea401d7..76cec3dedd4190 100644 --- a/drivers/ps2/ps2_mchp_xec.c +++ b/drivers/ps2/ps2_mchp_xec.c @@ -94,7 +94,8 @@ static inline void ps2_xec_girq_en(const struct device *dev) #endif /* CONFIG_SOC_SERIES_MEC172X */ static int ps2_xec_configure(const struct device *dev, - ps2_callback_t callback_isr) + ps2_callback_t callback_isr, + ps2_resend_callback_t resend_callback_isr) { const struct ps2_xec_config * const config = dev->config; struct ps2_xec_data * const data = dev->data; diff --git a/drivers/ps2/ps2_npcx_channel.c b/drivers/ps2/ps2_npcx_channel.c index b7188601399823..7bf29d6f38d2df 100644 --- a/drivers/ps2/ps2_npcx_channel.c +++ b/drivers/ps2/ps2_npcx_channel.c @@ -37,7 +37,8 @@ struct ps2_npcx_ch_config { /* PS/2 api functions */ static int ps2_npcx_ch_configure(const struct device *dev, - ps2_callback_t callback_isr) + ps2_callback_t callback_isr, + ps2_resend_callback_t resend_callback_isr) { const struct ps2_npcx_ch_config *const config = dev->config; int ret; diff --git a/include/zephyr/drivers/ps2.h b/include/zephyr/drivers/ps2.h index a48195020c83cc..004d8047e3b570 100644 --- a/include/zephyr/drivers/ps2.h +++ b/include/zephyr/drivers/ps2.h @@ -37,6 +37,18 @@ extern "C" { */ typedef void (*ps2_callback_t)(const struct device *dev, uint8_t data); +/** + * @brief PS/2 callback that is called when the PS/2 driver detects a + * transmission error and instructs the PS/2 device to re-send the last PS/2 + * packet. A PS/2 packet can be more than one byte and when the device is + * instructed to resend the last packet, it will resend all the bytes. This + * can cause the PS/2 driver to become misaligned. This callback lets the PS/2 + * driver know that it should expect a resend of an entire packet. + * + * @param dev Pointer to the device structure for the driver instance. + */ +typedef void (*ps2_resend_callback_t)(const struct device *dev); + /** * @cond INTERNAL_HIDDEN * @@ -45,7 +57,8 @@ typedef void (*ps2_callback_t)(const struct device *dev, uint8_t data); * (Internal use only.) */ typedef int (*ps2_config_t)(const struct device *dev, - ps2_callback_t callback_isr); + ps2_callback_t callback_isr, + ps2_resend_callback_t resend_callback_isr); typedef int (*ps2_read_t)(const struct device *dev, uint8_t *value); typedef int (*ps2_write_t)(const struct device *dev, uint8_t value); typedef int (*ps2_disable_callback_t)(const struct device *dev); @@ -73,15 +86,18 @@ __subsystem struct ps2_driver_api { * @retval Negative errno code if failure. */ __syscall int ps2_config(const struct device *dev, - ps2_callback_t callback_isr); + ps2_callback_t callback_isr, + ps2_resend_callback_t resend_callback_isr); static inline int z_impl_ps2_config(const struct device *dev, - ps2_callback_t callback_isr) + ps2_callback_t callback_isr, + ps2_resend_callback_t resend_callback_isr) + { const struct ps2_driver_api *api = (struct ps2_driver_api *)dev->api; - return api->config(dev, callback_isr); + return api->config(dev, callback_isr, resend_callback_isr); } /** From d1d49cecc4f93a0a938d3d4f18e58b5cdfe4a329 Mon Sep 17 00:00:00 2001 From: Kim Streich Date: Wed, 12 Apr 2023 12:22:21 -0600 Subject: [PATCH 2/2] drivers: ps2: Added gpio bit-banging driver --- drivers/ps2/CMakeLists.txt | 1 + drivers/ps2/Kconfig | 1 + drivers/ps2/Kconfig.gpio | 41 + drivers/ps2/ps2_gpio.c | 1582 ++++++++++++++++++++++++++++++++ dts/bindings/ps2/gpio-ps2.yaml | 19 + 5 files changed, 1644 insertions(+) create mode 100644 drivers/ps2/Kconfig.gpio create mode 100644 drivers/ps2/ps2_gpio.c create mode 100644 dts/bindings/ps2/gpio-ps2.yaml diff --git a/drivers/ps2/CMakeLists.txt b/drivers/ps2/CMakeLists.txt index 006c70d63f6215..dcd1dace451bea 100644 --- a/drivers/ps2/CMakeLists.txt +++ b/drivers/ps2/CMakeLists.txt @@ -6,3 +6,4 @@ zephyr_library_sources_ifdef(CONFIG_PS2_XEC ps2_mchp_xec.c) zephyr_library_sources_ifdef(CONFIG_USERSPACE ps2_handlers.c) zephyr_library_sources_ifdef(CONFIG_PS2_NPCX ps2_npcx_channel.c) zephyr_library_sources_ifdef(CONFIG_PS2_NPCX ps2_npcx_controller.c) +zephyr_library_sources_ifdef(CONFIG_PS2_GPIO ps2_gpio.c) diff --git a/drivers/ps2/Kconfig b/drivers/ps2/Kconfig index 8677ada48306ca..7decc2b86b8e7f 100644 --- a/drivers/ps2/Kconfig +++ b/drivers/ps2/Kconfig @@ -12,6 +12,7 @@ if PS2 source "drivers/ps2/Kconfig.xec" source "drivers/ps2/Kconfig.npcx" +source "drivers/ps2/Kconfig.gpio" module = PS2 module-str = ps2 diff --git a/drivers/ps2/Kconfig.gpio b/drivers/ps2/Kconfig.gpio new file mode 100644 index 00000000000000..2895650fa4370f --- /dev/null +++ b/drivers/ps2/Kconfig.gpio @@ -0,0 +1,41 @@ +# Copyright (c) 2017 Linaro Ltd. +# SPDX-License-Identifier: Apache-2.0 + +DT_COMPAT_PS2_GPIO := gpio-ps2 + +config PS2_GPIO + bool "GPIO bit banging PS/2 support" + default $(dt_compat_enabled,$(DT_COMPAT_PS2_GPIO)) + help + Enable software driven (bit banging) PS/2 support using GPIO pins + +if PS2_GPIO + +# PS/2 events must be processed within 30-50us. Therefore we shift the +# BT_CTLR_LLL_PRIO from 0 to 1 and BT_CTLR_ULL_* priorities from 1 to 2. +# We then set the gpio priority to 0. +# +# On top of this, the following has to be added to the device tree: +# &gpiote { +# interrupts = < 0x6 0 >; +# }; +# +# This allows the PS/2 interrupts to be triggered in time. + +config BT_CTLR_ADVANCED_FEATURES + default y + +config BT_CTLR_LLL_PRIO + default 1 + +config BT_CTLR_ULL_HIGH_PRIO + default 2 + +config BT_CTLR_ULL_LOW_PRIO + default 2 + +config PS2_GPIO_INTERRUPT_LOG_ENABLED + bool "Enable extensive interrupt logging." + default n + +endif # PS2_GPIO diff --git a/drivers/ps2/ps2_gpio.c b/drivers/ps2/ps2_gpio.c new file mode 100644 index 00000000000000..74446e749230e5 --- /dev/null +++ b/drivers/ps2/ps2_gpio.c @@ -0,0 +1,1582 @@ +/* + * Copyright (c) 2019 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT gpio_ps2 + +#include +#include +#include +#include +#include + +#define LOG_LEVEL CONFIG_PS2_LOG_LEVEL +LOG_MODULE_REGISTER(ps2_gpio); + + +/* + * Settings + */ + +#define PS2_GPIO_WRITE_MAX_RETRY 5 +#define PS2_GPIO_READ_MAX_RETRY 3 + +// Custom queue for background PS/2 processing work at low priority +// We purposefully want this to be a fairly low priority, because +// this queue is used while we wait to start a write. +// If the system is very busy with interrupts and other threads, then we +// want to wait until that is over so that our write interrupts don't get +// missed. +#define PS2_GPIO_WORK_QUEUE_PRIORITY 10 +#define PS2_GPIO_WORK_QUEUE_STACK_SIZE 1024 + +// Custom queue for calling the zephyr ps/2 callback. +// We don't want to hand it off to that API in an ISR since that callback +// could be using blocking functions. +// But we also don't want to hand it off at a low priority, since the PS/2 +// packets must be dealt with quickly. So we use a fairly high priority. +#define PS2_GPIO_WORK_QUEUE_CB_PRIORITY 2 +#define PS2_GPIO_WORK_QUEUE_CB_STACK_SIZE 1024 + +/* + * PS/2 Defines + */ + +#define PS2_GPIO_POS_START 0 +// 1-8 are the data bits +#define PS2_GPIO_POS_PARITY 9 +#define PS2_GPIO_POS_STOP 10 +#define PS2_GPIO_POS_ACK 11 // Write mode only + +#define PS2_GPIO_RESP_ACK 0xfa +#define PS2_GPIO_RESP_RESEND 0xfe +#define PS2_GPIO_RESP_FAILURE 0xfc + +/* + * PS/2 Timings + */ + +// PS2 uses a frequency between 10 kHz and 16.7 kHz. So clocks should arrive +// within 60-100us. +#define PS2_GPIO_TIMING_SCL_CYCLE_MIN 60 +#define PS2_GPIO_TIMING_SCL_CYCLE_MAX 100 + +// The minimum time needed to inhibit clock to start a write +// is 100us, but we triple it just in case. +#define PS2_GPIO_TIMING_SCL_INHIBITION_MIN 100 +#define PS2_GPIO_TIMING_SCL_INHIBITION ( \ + 3 * PS2_GPIO_TIMING_SCL_INHIBITION_MIN \ +) + +// When we start the inhibition timer for PS2_GPIO_TIMING_SCL_INHIBITION us, +// it doesn't mean it will be called after exactly that time. +// If there are higher priority threads, it will be delayed and might stay +// inhibitied much longer. So we account for that delay and add a maximum +// allowed delay. +#define PS2_GPIO_TIMING_SCL_INHIBITION_TIMER_DELAY_MAX 1000 + +// After inhibiting and releasing the clock, the device starts sending +// the clock. It's supposed to start immediately, but some devices +// need much longer if you are asking them to interrupt an +// ongoing read. +#define PS2_GPIO_TIMING_SCL_INHIBITION_RESP_MAX 10000 + +// Writes start with us inhibiting the line and then respond +// with 11 bits (start bit included in inhibition time). +// To be conservative we give it another 2 cycles to complete +#define PS2_GPIO_TIMING_WRITE_MAX_TIME ( \ + PS2_GPIO_TIMING_SCL_INHIBITION \ + + PS2_GPIO_TIMING_SCL_INHIBITION_TIMER_DELAY_MAX \ + + PS2_GPIO_TIMING_SCL_INHIBITION_RESP_MAX \ + + 11 * PS2_GPIO_TIMING_SCL_CYCLE_MAX \ + + 2 * PS2_GPIO_TIMING_SCL_CYCLE_MAX \ +) + +// Reads are 11bit and we give it another 2 cycles to start and stop +#define PS2_GPIO_TIMING_READ_MAX_TIME (\ + 11 * PS2_GPIO_TIMING_SCL_CYCLE_MAX \ + + 2 * PS2_GPIO_TIMING_SCL_CYCLE_MAX \ +) + + +/* + * Driver Defines + */ + +// Timeout for blocking read using the zephyr PS2 ps2_read() function +// This is not a matter of PS/2 timings, but a preference of how long we let +// the user wait until we give up on reading. +#define PS2_GPIO_TIMEOUT_READ K_SECONDS(2) + +// Timeout for write_byte_blocking() +#define PS2_GPIO_TIMEOUT_WRITE_BLOCKING K_USEC( \ + PS2_GPIO_TIMING_WRITE_MAX_TIME \ +) + +// Timeout for write_byte_await_response() +// PS/2 spec says that device must respond within 20msec, +// but real life devices take much longer. Especially if +// you interrupt existing transmissions. +#define PS2_GPIO_TIMEOUT_WRITE_AWAIT_RESPONSE K_MSEC(300) + +// Max time we allow the device to send the next clock signal during reads +// and writes. +// Even though PS/2 devices send the clock at most every 100us, it doesn't mean +// that the interrupts always get triggered within that time. So we allow a +// little extra time. +#define PS2_GPIO_TIMEOUT_READ_SCL K_USEC(PS2_GPIO_TIMING_SCL_CYCLE_MAX + 50) +#define PS2_GPIO_TIMEOUT_WRITE_SCL K_USEC(PS2_GPIO_TIMING_SCL_CYCLE_MAX + 50) + +// But after inhibiting the clock line, sometimes clocks take a little longer +// to start. So we allow a bit more time for the first write clock. +#define PS2_GPIO_TIMEOUT_WRITE_SCL_START K_USEC( \ + PS2_GPIO_TIMING_SCL_INHIBITION_RESP_MAX \ +) + +#define PS2_GPIO_WRITE_INHIBIT_SLC_DURATION K_USEC( \ + PS2_GPIO_TIMING_SCL_INHIBITION \ +) + +/* + * Global Variables + */ + +typedef enum +{ + PS2_GPIO_MODE_READ, + PS2_GPIO_MODE_WRITE +} ps2_gpio_mode; + +// Used to keep track of blocking write status +typedef enum +{ + PS2_GPIO_WRITE_STATUS_INACTIVE, + PS2_GPIO_WRITE_STATUS_ACTIVE, + PS2_GPIO_WRITE_STATUS_SUCCESS, + PS2_GPIO_WRITE_STATUS_FAILURE, +} ps2_gpio_write_status; + +struct ps2_gpio_config { + struct gpio_dt_spec scl_gpio; + struct gpio_dt_spec sda_gpio; +}; + +struct ps2_gpio_data { + const struct device *dev; + struct gpio_dt_spec scl_gpio; /* GPIO used for PS2 SCL line */ + struct gpio_dt_spec sda_gpio; /* GPIO used for PS2 SDA line */ + + // Interrupt callback + struct gpio_callback scl_cb_data; + + // PS2 driver interface callback + struct k_work callback_work; + uint8_t callback_byte; + ps2_callback_t callback_isr; + ps2_resend_callback_t resend_callback_isr; + + bool callback_enabled; + + // Queue for ps2_read() + struct k_fifo data_queue; + + ps2_gpio_mode mode; + + uint8_t cur_read_byte; + int cur_read_pos; + int cur_read_try; + uint32_t last_read_cycle_cnt; + struct k_work_delayable read_scl_timout; + + ps2_gpio_write_status cur_write_status; + uint8_t cur_write_byte; + int cur_write_pos; + struct k_work_delayable write_inhibition_wait; + struct k_work_delayable write_scl_timout; + struct k_sem write_lock; + + bool write_awaits_resp; + uint8_t write_awaits_resp_byte; + struct k_sem write_awaits_resp_sem; + + struct k_work resend_cmd_work; +}; + + +static const struct ps2_gpio_config ps2_gpio_config = { + .scl_gpio = GPIO_DT_SPEC_INST_GET(0, scl_gpios), \ + .sda_gpio = GPIO_DT_SPEC_INST_GET(0, sda_gpios), \ +}; + +static struct ps2_gpio_data ps2_gpio_data = { + .callback_byte = 0x0, + .callback_isr = NULL, + .resend_callback_isr = NULL, + .callback_enabled = false, + .mode = PS2_GPIO_MODE_READ, + + .cur_read_byte = 0x0, + .cur_read_pos = 0, + .cur_read_try = 0, + + .cur_write_byte = 0x0, + .cur_write_pos = 0, + .cur_write_status = PS2_GPIO_WRITE_STATUS_INACTIVE, + + .write_awaits_resp = false, + .write_awaits_resp_byte = 0x0, +}; + +K_THREAD_STACK_DEFINE( + ps2_gpio_work_queue_stack_area, + PS2_GPIO_WORK_QUEUE_STACK_SIZE +); +static struct k_work_q ps2_gpio_work_queue; + +K_THREAD_STACK_DEFINE( + ps2_gpio_work_queue_cb_stack_area, + PS2_GPIO_WORK_QUEUE_CB_STACK_SIZE +); +static struct k_work_q ps2_gpio_work_queue_cb; + +/* + * Function Definitions + */ + +int ps2_gpio_write_byte(uint8_t byte); + +/* + * Helpers functions + */ + +#define PS2_GPIO_GET_BIT(data, bit_pos) ( (data >> bit_pos) & 0x1 ) +#define PS2_GPIO_SET_BIT(data, bit_val, bit_pos) ( \ + data |= (bit_val) << bit_pos \ +) + +int ps2_gpio_get_scl() +{ + const struct ps2_gpio_data *data = &ps2_gpio_data; + int rc = gpio_pin_get_dt(&data->scl_gpio); + + return rc; +} + +int ps2_gpio_get_sda() +{ + const struct ps2_gpio_data *data = &ps2_gpio_data; + int rc = gpio_pin_get_dt(&data->sda_gpio); + + return rc; +} + +void ps2_gpio_set_scl(int state) +{ + const struct ps2_gpio_data *data = &ps2_gpio_data; + + // LOG_INF("Setting scl to %d", state); + gpio_pin_set_dt(&data->scl_gpio, state); +} + +void ps2_gpio_set_sda(int state) +{ + const struct ps2_gpio_data *data = &ps2_gpio_data; + + // LOG_INF("Seting sda to %d", state); + gpio_pin_set_dt(&data->sda_gpio, state); +} + +int ps2_gpio_set_scl_callback_enabled(bool enabled) +{ + struct ps2_gpio_data *data = &ps2_gpio_data; + int err; + + if(enabled) { + err = gpio_add_callback(data->scl_gpio.port, &data->scl_cb_data); + if (err) { + LOG_ERR( + "failed to enable interrupt callback on " + "SCL GPIO pin (err %d)", err + ); + } + } else { + err = gpio_remove_callback(data->scl_gpio.port, &data->scl_cb_data); + if (err) { + LOG_ERR( + "failed to disable interrupt callback on " + "SCL GPIO pin (err %d)", err + ); + } + } + + return err; +} + + +int ps2_gpio_configure_pin_scl(gpio_flags_t flags, char *descr) +{ + struct ps2_gpio_data *data = &ps2_gpio_data; + int err; + + err = gpio_pin_configure_dt( + &data->scl_gpio, + flags + ); + if (err) { + LOG_ERR("failed to configure SCL GPIO pin to %s (err %d)", descr, err); + } + + return err; +} + +int ps2_gpio_configure_pin_scl_input() +{ + return ps2_gpio_configure_pin_scl( + (GPIO_INPUT), + "input" + ); +} + +int ps2_gpio_configure_pin_scl_output() +{ + return ps2_gpio_configure_pin_scl( + (GPIO_OUTPUT), + "output" + ); +} + +int ps2_gpio_configure_pin_sda(gpio_flags_t flags, char *descr) +{ + struct ps2_gpio_data *data = &ps2_gpio_data; + int err; + + err = gpio_pin_configure_dt( + &data->sda_gpio, + flags + ); + if (err) { + LOG_ERR("failed to configure SDA GPIO pin to %s (err %d)", descr, err); + } + + return err; +} + +int ps2_gpio_configure_pin_sda_input() +{ + return ps2_gpio_configure_pin_sda( + (GPIO_INPUT), + "input" + ); +} + +int ps2_gpio_configure_pin_sda_output() +{ + return ps2_gpio_configure_pin_sda( + (GPIO_OUTPUT), + "output" + ); +} + +bool ps2_gpio_get_byte_parity(uint8_t byte) +{ + int byte_parity = __builtin_parity(byte); + + // gcc parity returns 1 if there is an odd number of bits in byte + // But the PS2 protocol sets the parity bit to 0 if there is an odd number + return !byte_parity; +} + +uint8_t ps2_gpio_data_queue_get_next(uint8_t *dst_byte, k_timeout_t timeout) +{ + struct ps2_gpio_data *data = &ps2_gpio_data; + + uint8_t *queue_byte = k_fifo_get(&data->data_queue, timeout); + if(queue_byte == NULL) { + return -ETIMEDOUT; + } + + *dst_byte = *queue_byte; + + k_free(queue_byte); + + return 0; +} + +void ps2_gpio_data_queue_empty() +{ + while(true) { + uint8_t byte; + int err = ps2_gpio_data_queue_get_next(&byte, K_NO_WAIT); + if(err) { // No more items in queue + break; + } + } +} + +void ps2_gpio_data_queue_add(uint8_t byte) +{ + struct ps2_gpio_data *data = &ps2_gpio_data; + + uint8_t *byte_heap = (uint8_t *) k_malloc(sizeof(byte)); + if(byte_heap == NULL) { + LOG_WRN( + "Could not allocate heap space to add byte to fifo. " + "Clearing fifo." + ); + + // TODO: Define max amount for read data queue instead of emptying it + // when memory runs out. + // But unfortunately it seems like there is no official way to query + // how many items are currently in the fifo. + ps2_gpio_data_queue_empty(); + + byte_heap = (uint8_t *) k_malloc(sizeof(byte)); + if(byte_heap == NULL) { + LOG_ERR( + "Could not allocate heap space after clearing fifo. " + "Losing received byte 0x%x", byte + ); + return; + } + } + + *byte_heap = byte; + k_fifo_alloc_put(&data->data_queue, byte_heap); +} + +void ps2_gpio_send_cmd_resend_worker(struct k_work *item) +{ + struct ps2_gpio_data *data = &ps2_gpio_data; + + // Notify the PS/2 device driver that we are requesting a resend. + // PS/2 devices don't just resend the last byte that was sent, but the + // entire command packet, which can be multiple bytes. + if(data->resend_callback_isr != NULL && data->callback_enabled) { + + data->resend_callback_isr(data->dev); + } + + uint8_t cmd = 0xfe; + // LOG_DBG("Requesting resend of data with command: 0x%x", cmd); + ps2_gpio_write_byte(cmd); +} + +void ps2_gpio_send_cmd_resend() +{ + struct ps2_gpio_data *data = &ps2_gpio_data; + + if (k_is_in_isr()) { + + // It's important to submit this on the cb queue and not on the + // same queue as the inhibition delay. + // Otherwise the queue will be blocked by the semaphore and the + // inhibition delay worker will never be called. + k_work_submit_to_queue( + &ps2_gpio_work_queue_cb, + &data->resend_cmd_work + ); + } else { + ps2_gpio_send_cmd_resend_worker(NULL); + } +} + + +/* + * Interrupt logging + * + * Zephyr logs don't process fast enough and slow down the interrupts enough + * to make the writes and reads fail. + * + * This simple logging process allows us to debug the interrupts in detail. + */ + +#define PS2_GPIO_INTERRUPT_LOG_SCL_TIMEOUT K_SECONDS(1) +#define PS2_GPIO_INTERRUPT_LOG_MAX_ITEMS 1000 + +#if IS_ENABLED(CONFIG_PS2_GPIO_INTERRUPT_LOG_ENABLED) + +#define LOG_PS2_INT(...) ps2_gpio_interrupt_log_add(__VA_ARGS__) + +struct interrupt_log { + int64_t uptime_ticks; + char msg[50]; + int scl; + int sda; + ps2_gpio_mode mode; + int pos; +}; + +int interrupt_log_offset = 0; +int interrupt_log_idx = 0; +struct interrupt_log interrupt_log[PS2_GPIO_INTERRUPT_LOG_MAX_ITEMS]; + +struct k_work_delayable interrupt_log_scl_timout; +struct k_work interrupt_log_print_worker; + +void ps2_gpio_interrupt_log_add(char *msg, uint8_t *arg); +void ps2_gpio_interrupt_log_print(); +void ps2_gpio_interrupt_log_clear(); +void ps2_gpio_strncat_hex(char *dst, uint8_t val, size_t dst_size); +char *ps2_gpio_interrupt_log_get_mode_str(); +void ps2_gpio_interrupt_log_get_pos_str(int pos, + char *pos_str, + int pos_str_size); + +void ps2_gpio_interrupt_log_add(char *msg, uint8_t *arg) +{ + struct ps2_gpio_data *data = &ps2_gpio_data; + struct interrupt_log l; + + l.uptime_ticks = k_uptime_ticks(); + + // va_list arglist; + // va_start(arglist, format); + // vsnprintfcb(l.msg, sizeof(l.msg) - 1, format, arglist); + // va_end(arglist); + strncpy(l.msg, msg, sizeof(l.msg) - 1); + if(arg != NULL) { + ps2_gpio_strncat_hex(l.msg, *arg, sizeof(l.msg)); + } + + l.scl = ps2_gpio_get_scl(); + l.sda = ps2_gpio_get_sda(); + l.mode = data->mode; + if(data->mode == PS2_GPIO_MODE_READ) { + l.pos = data->cur_read_pos; + } else { + l.pos = data->cur_write_pos; + } + + if(interrupt_log_idx == (PS2_GPIO_INTERRUPT_LOG_MAX_ITEMS * 0.80)) { + ps2_gpio_interrupt_log_print(); + } else if(interrupt_log_idx >= PS2_GPIO_INTERRUPT_LOG_MAX_ITEMS) { + interrupt_log_offset++; + return; + } + + interrupt_log[interrupt_log_idx] = l; + interrupt_log_idx += 1; +} + +void ps2_gpio_interrupt_log_print() +{ + // ps2_gpio_interrupt_log_print_worker(NULL); + k_work_submit_to_queue( + &ps2_gpio_work_queue_cb, + &interrupt_log_print_worker + ); +} + +void ps2_gpio_interrupt_log_print_worker(struct k_work *item) +{ + + LOG_INF("===== Interrupt Log ====="); + for(int i = 0; i < interrupt_log_idx; i++) { + struct interrupt_log *l = &interrupt_log[i]; + char pos_str[50]; + + ps2_gpio_interrupt_log_get_pos_str(l->pos, pos_str, sizeof(pos_str)); + + LOG_INF( + "%d - %" PRIu64 ": %s " + "(mode=%s, pos=%s, scl=%d, sda=%d)" , + interrupt_log_offset + i + 1, l->uptime_ticks, l->msg, + ps2_gpio_interrupt_log_get_mode_str(), pos_str, l->scl, l->sda + ); + k_sleep(K_MSEC(15)); + } + LOG_INF("======== End Log ========"); + + ps2_gpio_interrupt_log_clear(); +} + +void ps2_gpio_interrupt_log_clear() +{ + memset(&interrupt_log, 0x0, sizeof(interrupt_log)); + interrupt_log_offset += interrupt_log_idx; + interrupt_log_idx = 0; +} + +void ps2_gpio_interrupt_log_scl_timeout(struct k_work *item) +{ + // Called if there is no interrupt for + // PS2_GPIO_INTERRUPT_LOG_SCL_TIMEOUT ms + ps2_gpio_interrupt_log_print(); + +void ps2_gpio_strncat_hex(char *dst, uint8_t val, size_t dst_size) { + const char hex_chars[] = "0123456789abcdef"; + char val_hex[5]; + + val_hex[0] = '0'; + val_hex[1] = 'x'; + + val_hex[2] = hex_chars[(val >> (4 * (1 - 0))) & 0xf]; + val_hex[3] = hex_chars[(val >> (4 * (1 - 1))) & 0xf]; + + val_hex[4] = '\0'; + + strncat(dst, val_hex, dst_size - strlen(dst) - 1); +} + +char *ps2_gpio_interrupt_log_get_mode_str() { + struct ps2_gpio_data *data = &ps2_gpio_data; + + if(data->mode == PS2_GPIO_MODE_READ) { + return "r"; + } else if(data->mode == PS2_GPIO_MODE_WRITE) { + return "w"; + } else { + return "?"; + } +} + +void ps2_gpio_interrupt_log_get_pos_str(int pos, + char *pos_str, + int pos_str_size) +{ + char *pos_names[] = { + "start", + "data_1", + "data_2", + "data_3", + "data_4", + "data_5", + "data_6", + "data_7", + "data_8", + "parity", + "stop", + "ack", + }; + + if(pos >= (sizeof(pos_names) / sizeof(pos_names[0]))) { + snprintf(pos_str, pos_str_size - 1, "%d", pos); + } else { + strncpy(pos_str, pos_names[pos], pos_str_size - 1); + } +} + +#else + +#define LOG_PS2_INT(...) + +#endif /* IS_ENABLED(CONFIG_PS2_GPIO_INTERRUPT_LOG_ENABLED) */ + + +/* + * Reading PS/2 data + */ + +void ps2_gpio_read_interrupt_handler(); +void ps2_gpio_read_scl_timeout(struct k_work *item); +void ps2_gpio_read_abort(bool should_resend, char *reason); +void ps2_gpio_read_process_received_byte(uint8_t byte); +void ps2_gpio_read_finish(); + +// Reading doesn't need to be initiated. It happens automatically whenever +// the device sends data. +// Once a full byte has been received successfully it is processed in +// ps2_gpio_read_process_received_byte, which decides what should happen +// with it. +void ps2_gpio_read_interrupt_handler() +{ + struct ps2_gpio_data *data = &ps2_gpio_data; + + uint32_t cur_read_cycle_cnt = k_cycle_get_32(); + uint32_t last_read_cycle_cnt = data->last_read_cycle_cnt; + + data->last_read_cycle_cnt = cur_read_cycle_cnt; + + if(data->cur_read_pos > 0) { + uint32_t prev_cycle_delta_us = k_cyc_to_us_floor32( + cur_read_cycle_cnt - last_read_cycle_cnt + ); + + if(prev_cycle_delta_us > PS2_GPIO_TIMING_SCL_CYCLE_MAX) { + ps2_gpio_read_abort(true, "missed interrupt"); + } + } + + k_work_cancel_delayable(&data->read_scl_timout); + + LOG_PS2_INT("Read interrupt", NULL); + + int sda_val = ps2_gpio_get_sda(); + + if(data->cur_read_pos == PS2_GPIO_POS_START) { + // The first bit of every transmission should be 0. + // If it is not, it means we are out of sync with the device. + // So we abort the transmission and start from scratch. + if(sda_val != 0) { + LOG_PS2_INT( + "Ignoring read interrupt due to invalid start bit.", NULL + ); + + // We don't request a resend here, because sometimes after writes + // devices send some unintended interrupts. If this is a "real + // transmission" and we are out of sync, we will catch it with the + // parity and stop bits and then request a resend. + ps2_gpio_read_abort(false, "invalid start bit"); + return; + } + } else if(data->cur_read_pos > PS2_GPIO_POS_START && + data->cur_read_pos < PS2_GPIO_POS_PARITY) + { // Data Bits + + // Current position, minus start bit + int bit_pos = data->cur_read_pos - 1; + PS2_GPIO_SET_BIT(data->cur_read_byte, sda_val, bit_pos); + } else if(data->cur_read_pos == PS2_GPIO_POS_PARITY) { + bool read_byte_parity = ps2_gpio_get_byte_parity(data->cur_read_byte); + + if(read_byte_parity != sda_val) { + LOG_PS2_INT( + "Requesting re-send due to invalid parity bit.", NULL + ); + + // If we got to the parity bit and it's incorrect then we + // are definitly in a transmission and out of sync. So we + // request a resend. + ps2_gpio_read_abort(true, "invalid parity bit"); + return; + } + } else if(data->cur_read_pos == PS2_GPIO_POS_STOP) { + if(sda_val != 1) { + LOG_PS2_INT( + "Requesting re-send due to invalid stop bit.", NULL + ); + + // If we got to the stop bit and it's incorrect then we + // are definitly in a transmission and out of sync. So we + // request a resend. + ps2_gpio_read_abort(true, "invalid stop bit"); + return; + } + + ps2_gpio_read_process_received_byte(data->cur_read_byte); + + return; + } else { + LOG_PS2_INT( + "Invalid read clock triggered", NULL + ); + + return; + } + + data->cur_read_pos += 1; + k_work_schedule(&data->read_scl_timout, PS2_GPIO_TIMEOUT_READ_SCL); +} + +void ps2_gpio_read_scl_timeout(struct k_work *item) +{ + // Once we are receiving a transmission we expect the device to + // to send a new clock/interrupt within 100us. + // If we don't receive the next interrupt within that timeframe, + // we abort the read. + struct ps2_gpio_data *data = CONTAINER_OF( + item, + struct ps2_gpio_data, + read_scl_timout + ); + + LOG_PS2_INT("Read SCL timeout", NULL); + + // We don't request a resend if the timeout happens in the early + // stage of the transmission. + // + // Because, sometimes after writes devices send some unintended + // interrupts and start the "real response" after one or two cycles. + // + // If we are really out of sync the parity and stop bits should catch + // it and request a re-transmission. + if(data->cur_read_pos <= 3) { + ps2_gpio_read_abort(false, "scl timeout"); + } else { + ps2_gpio_read_abort(true, "scl timeout"); + } +} + +void ps2_gpio_read_abort(bool should_resend, char *reason) +{ + struct ps2_gpio_data *data = &ps2_gpio_data; + + if(should_resend == true) { + LOG_ERR( + "Aborting read with resend request on pos=%d: %s", + data->cur_read_pos, reason + ); + LOG_PS2_INT("Aborting read with resend request.", NULL); + } else { + LOG_PS2_INT("Aborting read without resend request.", NULL); + } + + ps2_gpio_read_finish(); + + k_work_cancel_delayable(&data->read_scl_timout); + + if(should_resend == true) { + if(data->cur_read_try < PS2_GPIO_READ_MAX_RETRY) { + + data->cur_read_try++; + ps2_gpio_send_cmd_resend(); + } else { + LOG_ERR( + "Failed to read value %d times. Stopping asking the device " + "to resend.", + data->cur_read_try + ); + + data->cur_read_try = 0; + } + } +} + +void ps2_gpio_read_process_received_byte(uint8_t byte) +{ + struct ps2_gpio_data *data = &ps2_gpio_data; + + LOG_DBG("Successfully received value: 0x%x", byte); + LOG_PS2_INT("Successfully received value: ", &byte); + + // Since read was successful we reset the read try + data->cur_read_try = 0; + ps2_gpio_read_finish(); + + // If write_byte_await_response() is waiting, we notify + // the blocked write process of whether it was a success or not. + if(data->write_awaits_resp) { + data->write_awaits_resp_byte = byte; + data->write_awaits_resp = false; + k_sem_give(&data->write_awaits_resp_sem); + + // Don't send ack and err responses to the callback and read + // data queue. + // If it's an ack, the write process will return success. + // If it's an error, the write process will return failure. + if(byte == PS2_GPIO_RESP_ACK || + byte == PS2_GPIO_RESP_RESEND || + byte == PS2_GPIO_RESP_FAILURE) { + + return; + } + } + + // If no callback is set, we add the data to a fifo queue + // that can be read later with the read using `ps2_read` + if(data->callback_isr != NULL && data->callback_enabled) { + + // Call callback from a worker to make sure the callback + // doesn't block the interrupt. + // Will call ps2_gpio_read_callback_work_handler + data->callback_byte = byte; + k_work_submit_to_queue(&ps2_gpio_work_queue_cb, &data->callback_work); + } else { + ps2_gpio_data_queue_add(byte); + } +} + +void ps2_gpio_read_callback_work_handler(struct k_work *work) +{ + struct ps2_gpio_data *data = &ps2_gpio_data; + + data->callback_isr(data->dev, data->callback_byte); + data->callback_byte = 0x0; +} + +void ps2_gpio_read_finish() +{ + struct ps2_gpio_data *data = &ps2_gpio_data; + + data->cur_read_pos = PS2_GPIO_POS_START; + data->cur_read_byte = 0x0; + + k_work_cancel_delayable(&data->read_scl_timout); +} + + +/* + * Writing PS2 data + */ + +int ps2_gpio_write_byte_await_response(uint8_t byte); +int ps2_gpio_write_byte_blocking(uint8_t byte); +int ps2_gpio_write_byte_start(uint8_t byte); +void ps2_gpio_write_inhibition_wait(struct k_work *item); +void ps2_gpio_write_interrupt_handler(); +void ps2_gpio_write_finish(bool successful, char *descr); +void ps2_gpio_write_scl_timeout(struct k_work *item); +bool ps2_gpio_get_byte_parity(uint8_t byte); + +// Returned when there was an error writing to the PS2 device, such +// as not getting a clock from the device or receiving an invalid +// ack bit. +#define PS2_GPIO_E_WRITE_TRANSMIT 1 + +// Returned when the semaphore times out. Theoretically this shouldn't be +// happening. But it can happen if the same thread is used for both the +// semaphore wait and the inhibition timeout. +#define PS2_GPIO_E_WRITE_SEM_TIMEOUT 2 + +// Returned when the write finished seemingly successful, but the +// device didn't send a response in time. +#define PS2_GPIO_E_WRITE_RESPONSE 3 + +// Returned when the write finished seemingly successful, but the +// device responded with 0xfe (request to resend) and we ran out of +// retry attempts. +#define PS2_GPIO_E_WRITE_RESEND 4 + +// Returned when the write finished seemingly successful, but the +// device responded with 0xfc (failure / cancel). +#define PS2_GPIO_E_WRITE_FAILURE 5 + +K_MUTEX_DEFINE(write_mutex); + + +int ps2_gpio_write_byte(uint8_t byte) +{ + int err; + + LOG_DBG("\n"); + LOG_DBG("START WRITE: 0x%x", byte); + + k_mutex_lock(&write_mutex, K_FOREVER); + + for(int i = 0; i < PS2_GPIO_WRITE_MAX_RETRY; i++) { + if(i > 0) { + LOG_WRN( + "Attempting write re-try #%d of %d...", + i + 1, PS2_GPIO_WRITE_MAX_RETRY + ); + } + + err = ps2_gpio_write_byte_await_response(byte); + + if(err == 0) { + if(i > 0) { + LOG_WRN( + "Successfully wrote 0x%x on try #%d of %d...", + byte, i + 1, PS2_GPIO_WRITE_MAX_RETRY + ); + } + break; + } else if(err == PS2_GPIO_E_WRITE_FAILURE) { + // Write failed and the device requested to stop trying + // to resend. + break; + } + } + + LOG_DBG("END WRITE: 0x%x\n", byte); + k_mutex_unlock(&write_mutex); + + return err; +} + +// Writes the byte and blocks execution until we read the +// response byte. +// Returns failure if the write fails or the response is 0xfe/0xfc (error) +// Returns success if the response is 0xfa (ack) or any value except of +// 0xfe. +// 0xfe, 0xfc and 0xfa are not passed on to the read data queue or callback. +int ps2_gpio_write_byte_await_response(uint8_t byte) +{ + struct ps2_gpio_data *data = &ps2_gpio_data; + int err; + + err = ps2_gpio_write_byte_blocking(byte); + if(err) { + return err; + } + + data->write_awaits_resp = true; + + err = k_sem_take( + &data->write_awaits_resp_sem, PS2_GPIO_TIMEOUT_WRITE_AWAIT_RESPONSE + ); + + uint8_t resp_byte = data->write_awaits_resp_byte; + data->write_awaits_resp_byte = 0x0; + data->write_awaits_resp = false; + + if (err) { + LOG_WRN( + "Write response didn't arrive in time for byte " + "0x%x. Considering send a failure.", byte + ); + + return PS2_GPIO_E_WRITE_RESPONSE; + } + + LOG_DBG( + "Write for byte 0x%x received response: 0x%x", + byte, resp_byte + ); + + // We fail the write since we got an error response + if(resp_byte == PS2_GPIO_RESP_RESEND) { + + return PS2_GPIO_E_WRITE_RESEND; + } else if(resp_byte == PS2_GPIO_RESP_FAILURE) { + + return PS2_GPIO_E_WRITE_FAILURE; + } + + // Most of the time when a write was successful the device + // responds with an 0xfa (ack), but for some commands it doesn't. + // So we consider all non-0xfe and 0xfc responses as successful. + return 0; +} + +int ps2_gpio_write_byte_blocking(uint8_t byte) +{ + struct ps2_gpio_data *data = &ps2_gpio_data; + int err; + + // LOG_DBG("ps2_gpio_write_byte_blocking called with byte=0x%x", byte); + + err = ps2_gpio_write_byte_start(byte); + if (err) { + LOG_ERR("Could not initiate writing of byte."); + return PS2_GPIO_E_WRITE_TRANSMIT; + } + + // The async `write_byte_start` function takes the only available semaphor. + // This causes the `k_sem_take` call below to block until + // `ps2_gpio_write_finish` gives it back. + err = k_sem_take(&data->write_lock, PS2_GPIO_TIMEOUT_WRITE_BLOCKING); + if (err) { + + // This usually means the controller is busy with other interrupts, + // timed out processing the interrupts and even the scl timeout + // delayable wasn't called due to the delay. + // + // So we abort the write and try again. + LOG_ERR( + "Blocking write failed due to semaphore timeout for byte " + "0x%x: %d", byte, err + ); + + ps2_gpio_write_finish(false, "semaphore timeout"); + return PS2_GPIO_E_WRITE_SEM_TIMEOUT; + } + + if(data->cur_write_status == PS2_GPIO_WRITE_STATUS_SUCCESS) { + // LOG_DBG("Blocking write finished successfully for byte 0x%x", byte); + err = 0; + } else { + LOG_ERR( + "Blocking write finished with failure for byte 0x%x status: %d", + byte, data->cur_write_status + ); + err = -data->cur_write_status; + } + + data->cur_write_status = PS2_GPIO_WRITE_STATUS_INACTIVE; + + return err; +} + +int ps2_gpio_write_byte_start(uint8_t byte) { + struct ps2_gpio_data *data = &ps2_gpio_data; + int err; + + LOG_DBG("ps2_gpio_write_byte_start called with byte=0x%x", byte); + + if(data->mode == PS2_GPIO_MODE_WRITE) + { + LOG_ERR( + "Preventing write off byte 0x%x: " + "Another write in progress for 0x%x", + byte, data->cur_write_byte + ); + + return -EBUSY; + } + + // Take semaphore so that when `ps2_gpio_write_byte_blocking` attempts + // taking it, the process gets blocked. + // It is released in `ps2_gpio_write_finish`. + err = k_sem_take(&data->write_lock, K_NO_WAIT); + if (err != 0 && err != -EBUSY) { + LOG_ERR("ps2_gpio_write_byte_start could not take semaphore: %d", err); + + return err; + } + + // Change mode and set write_pos so that the read interrupt handler + // doesn't trigger when we bring the clock line low. + data->mode = PS2_GPIO_MODE_WRITE; + data->cur_write_pos = PS2_GPIO_POS_START; + data->cur_write_byte = byte; + + // Initiating a send aborts any in-progress reads, so we + // reset the current read byte + data->cur_write_status = PS2_GPIO_WRITE_STATUS_ACTIVE; + if(data->cur_read_pos != PS2_GPIO_POS_START || + data->cur_read_byte != 0x0) + { + LOG_WRN("Aborting in-progress read due to write of byte 0x%x", byte); + ps2_gpio_read_abort(false, "starting write"); + } + + // Configure data and clock lines for output + ps2_gpio_configure_pin_scl_output(); + ps2_gpio_configure_pin_sda_output(); + + LOG_PS2_INT("Starting write of byte ", &byte); + + // Disable interrupt so that we don't trigger it when we + // pull the clock low to inhibit the line + ps2_gpio_set_scl_callback_enabled(false); + + // Inhibit the line by setting clock low and data high + ps2_gpio_set_scl(0); + ps2_gpio_set_sda(1); + + LOG_PS2_INT("Inhibited clock line", NULL); + + // Keep the line inhibited for at least 100 microseconds + k_work_schedule_for_queue( + &ps2_gpio_work_queue, + &data->write_inhibition_wait, + PS2_GPIO_WRITE_INHIBIT_SLC_DURATION + ); + + // The code continues in ps2_gpio_write_inhibition_wait + return 0; +} + +void ps2_gpio_write_inhibition_wait(struct k_work *item) +{ + LOG_PS2_INT("Inhibition timer finished", NULL); + + struct ps2_gpio_data *data = CONTAINER_OF( + item, + struct ps2_gpio_data, + write_inhibition_wait + ); + + // Enable the scl interrupt again + ps2_gpio_set_scl_callback_enabled(true); + + // Set data to value of start bit + ps2_gpio_set_sda(0); + + LOG_PS2_INT("Set sda to start bit", NULL); + + // The start bit was sent by setting sda to low + // So the next scl interrupt will be for the first + // data bit. + data->cur_write_pos += 1; + + // Release the clock line and configure it as input + // This let's the device take control of the clock again + ps2_gpio_set_scl(1); + ps2_gpio_configure_pin_scl_input(); + + LOG_PS2_INT("Released clock", NULL); + + k_work_schedule_for_queue( + &ps2_gpio_work_queue, + &data->write_scl_timout, + PS2_GPIO_TIMEOUT_WRITE_SCL_START + ); + + // From here on the device takes over the control of the clock again + // Every time it is ready for the next bit to be trasmitted, it will... + // - Pull the clock line low + // - Which will trigger our `ps2_gpio_write_interrupt_handler` + // - Which will send the correct bit + // - After all bits are sent `ps2_gpio_write_finish` is called +} + +void ps2_gpio_write_interrupt_handler() +{ + // After initiating writing, the device takes over + // the clock and asks us for a new bit of data on + // each falling edge. + struct ps2_gpio_data *data = &ps2_gpio_data; + + if(data->cur_write_pos == PS2_GPIO_POS_START) + { + // This should not be happening, because the PS2_GPIO_POS_START bit + // is sent in ps2_gpio_write_byte_start during inhibition + LOG_PS2_INT("Write interrupt", NULL); + return; + } + + k_work_cancel_delayable(&data->write_scl_timout); + + if(data->cur_write_pos > PS2_GPIO_POS_START && + data->cur_write_pos < PS2_GPIO_POS_PARITY) + { + // Set it to the data bit corresponding to the current + // write position (subtract start bit postion) + ps2_gpio_set_sda( + PS2_GPIO_GET_BIT(data->cur_write_byte, (data->cur_write_pos - 1)) + ); + } else if(data->cur_write_pos == PS2_GPIO_POS_PARITY) + { + ps2_gpio_set_sda( + ps2_gpio_get_byte_parity(data->cur_write_byte) + ); + } else if(data->cur_write_pos == PS2_GPIO_POS_STOP) + { + // Send the stop bit (always 1) + ps2_gpio_set_sda(1); + + // Give control over data pin back to device after sending stop bit + // so that we can receive the ack bit from the device + ps2_gpio_configure_pin_sda_input(); + } else if(data->cur_write_pos == PS2_GPIO_POS_ACK) + { + int ack_val = ps2_gpio_get_sda(); + + LOG_PS2_INT("Write interrupt", NULL); + + if(ack_val == 0) { + LOG_PS2_INT("Write was successful on ack: ", NULL); + ps2_gpio_write_finish(true, "valid ack bit"); + } else { + LOG_PS2_INT("Write failed on ack", NULL); + ps2_gpio_write_finish(false, "invalid ack bit"); + } + + return; + } else { + LOG_PS2_INT( + "Invalid write clock triggered", NULL + ); + + return; + } + + LOG_PS2_INT("Write interrupt", NULL); + + data->cur_write_pos += 1; + k_work_schedule_for_queue( + &ps2_gpio_work_queue, + &data->write_scl_timout, + PS2_GPIO_TIMEOUT_WRITE_SCL + ); +} + +void ps2_gpio_write_scl_timeout(struct k_work *item) +{ + // Once we start a transmission we expect the device to + // to send a new clock/interrupt within 100us. + // If we don't receive the next interrupt within that timeframe, + // we abort the writ). + + LOG_PS2_INT("Write SCL timeout", NULL); + ps2_gpio_write_finish(false, "scl timeout"); +} + +void ps2_gpio_write_finish(bool successful, char *descr) +{ + struct ps2_gpio_data *data = &ps2_gpio_data; + + k_work_cancel_delayable(&data->write_scl_timout); + + if(successful) { + LOG_DBG( + "Successfully wrote value 0x%x", + data->cur_write_byte + ); + LOG_PS2_INT( + "Successfully wrote value ", + &data->cur_write_byte + ); + data->cur_write_status = PS2_GPIO_WRITE_STATUS_SUCCESS; + } else { // Failure + LOG_ERR( + "Failed to write value 0x%x at pos=%d: %s", + data->cur_write_byte, data->cur_write_pos, descr + ); + LOG_PS2_INT( + "Failed to write value ", + &data->cur_write_byte + ); + + data->cur_write_status = PS2_GPIO_WRITE_STATUS_FAILURE; + + // Make sure the scl callback is enabled + // It's possible that all threads are busy, + // write_inhibition_wait doesn't get called in time + // and the semaphore times out. + // In that case we want to make sure the interrupt + // callback is enabled again. + ps2_gpio_set_scl_callback_enabled(true); + } + + data->mode = PS2_GPIO_MODE_READ; + data->cur_read_pos = PS2_GPIO_POS_START; + data->cur_write_pos = PS2_GPIO_POS_START; + data->cur_write_byte = 0x0; + + // Give back control over data and clock line if we still hold on to it + ps2_gpio_configure_pin_sda_input(); + ps2_gpio_configure_pin_scl_input(); + + // Give the semaphore to allow write_byte_blocking to continue + k_sem_give(&data->write_lock); +} + + +/* + * Interrupt Handler + */ + +void ps2_gpio_scl_interrupt_handler(const struct device *dev, + struct gpio_callback *cb, + uint32_t pins) +{ + struct ps2_gpio_data *data = &ps2_gpio_data; + +#if IS_ENABLED(CONFIG_PS2_GPIO_INTERRUPT_LOG_ENABLED) + k_work_cancel_delayable(&interrupt_log_scl_timout); +#endif /* IS_ENABLED(CONFIG_PS2_GPIO_INTERRUPT_LOG_ENABLED) */ + + if(data->mode == PS2_GPIO_MODE_READ) { + ps2_gpio_read_interrupt_handler(); + } else { + ps2_gpio_write_interrupt_handler(); + } + +#if IS_ENABLED(CONFIG_PS2_GPIO_INTERRUPT_LOG_ENABLED) + k_work_schedule_for_queue( + &ps2_gpio_work_queue_cb, + &interrupt_log_scl_timout, + PS2_GPIO_INTERRUPT_LOG_SCL_TIMEOUT + ); +#endif /* IS_ENABLED(CONFIG_PS2_GPIO_INTERRUPT_LOG_ENABLED) */ +} + + +/* + * Zephyr PS/2 driver interface + */ +static int ps2_gpio_enable_callback(const struct device *dev); + +static int ps2_gpio_configure(const struct device *dev, + ps2_callback_t callback_isr, + ps2_resend_callback_t resend_callback_isr) +{ + struct ps2_gpio_data *data = dev->data; + + if (!callback_isr && !resend_callback_isr) { + return -EINVAL; + } + + if(callback_isr) { + data->callback_isr = callback_isr; + ps2_gpio_enable_callback(dev); + } + + if(resend_callback_isr) { + data->resend_callback_isr = resend_callback_isr; + } + + return 0; +} + +int ps2_gpio_read(const struct device *dev, uint8_t *value) +{ + // TODO: Add a way to not return old queue items + // Maybe only bytes that were received within past 10 seconds. + uint8_t queue_byte; + int err = ps2_gpio_data_queue_get_next(&queue_byte, PS2_GPIO_TIMEOUT_READ); + if(err) { // Timeout due to no data to read in data queue + // LOG_DBG("ps2_gpio_read: Fifo timed out..."); + + return -ETIMEDOUT; + } + + // LOG_DBG("ps2_gpio_read: Returning 0x%x", queue_byte); + *value = queue_byte; + + return 0; +} + +static int ps2_gpio_write(const struct device *dev, uint8_t value) +{ + return ps2_gpio_write_byte(value); +} + +static int ps2_gpio_disable_callback(const struct device *dev) +{ + struct ps2_gpio_data *data = dev->data; + + // Make sure there are no stale items in the data queue + // from before the callback was disabled. + ps2_gpio_data_queue_empty(); + + data->callback_enabled = false; + + // LOG_DBG("Disabled PS2 callback."); + + return 0; +} + +static int ps2_gpio_enable_callback(const struct device *dev) +{ + struct ps2_gpio_data *data = dev->data; + data->callback_enabled = true; + + // LOG_DBG("Enabled PS2 callback."); + + ps2_gpio_data_queue_empty(); + + return 0; +} + +static const struct ps2_driver_api ps2_gpio_driver_api = { + .config = ps2_gpio_configure, + .read = ps2_gpio_read, + .write = ps2_gpio_write, + .disable_callback = ps2_gpio_disable_callback, + .enable_callback = ps2_gpio_enable_callback, +}; + +/* + * PS/2 GPIO Driver Init + */ + +int ps2_gpio_configure_scl_pin(struct ps2_gpio_data *data, + const struct ps2_gpio_config *config) +{ + int err; + + // Make pin info accessible through the data struct + data->scl_gpio = config->scl_gpio; + + // Overwrite any user-provided flags from the devicetree + data->scl_gpio.dt_flags = 0; + + ps2_gpio_configure_pin_scl_input(); + + // Interrupt for clock line + err = gpio_pin_interrupt_configure_dt( + &data->scl_gpio, + (GPIO_INT_EDGE_FALLING) + ); + if (err) { + LOG_ERR( + "failed to configure interrupt on " + "SCL GPIO pin (err %d)", err + ); + return err; + } + + gpio_init_callback( + &data->scl_cb_data, + ps2_gpio_scl_interrupt_handler, + BIT(data->scl_gpio.pin) + ); + + ps2_gpio_set_scl_callback_enabled(true); + + return 0; +} + +int ps2_gpio_configure_sda_pin(struct ps2_gpio_data *data, + const struct ps2_gpio_config *config) +{ + // Make pin info accessible through the data struct + data->sda_gpio = config->sda_gpio; + + // Overwrite any user-provided flags from the devicetree + data->scl_gpio.dt_flags = 0; + + ps2_gpio_configure_pin_sda_input(); + + return 0; +} + +static int ps2_gpio_init(const struct device *dev) +{ + + struct ps2_gpio_data *data = dev->data; + const struct ps2_gpio_config *config = dev->config; + int err; + + // Set the ps2 device so we can retrieve it later for + // the ps2 callback + data->dev = dev; + + err = ps2_gpio_configure_scl_pin(data, config); + if (err) { + return err; + } + err = ps2_gpio_configure_sda_pin(data, config); + if (err) { + return err; + } + + // Check if this stuff is needed + // TODO: Figure out why this is requiered. + ps2_gpio_set_sda(1); + ps2_gpio_set_scl(1); + + // Init fifo for synchronous read operations + k_fifo_init(&data->data_queue); + + // Init semaphore for blocking writes + k_sem_init(&data->write_lock, 0, 1); + + // Init semaphore that waits for read after write + k_sem_init(&data->write_awaits_resp_sem, 0, 1); + + // Custom queue for background PS/2 processing work at high priority + k_work_queue_start( + &ps2_gpio_work_queue, + ps2_gpio_work_queue_stack_area, + K_THREAD_STACK_SIZEOF(ps2_gpio_work_queue_stack_area), + PS2_GPIO_WORK_QUEUE_PRIORITY, + NULL + ); + + // Custom queue for calling the zephyr ps/2 callback at lower priority + k_work_queue_start( + &ps2_gpio_work_queue_cb, + ps2_gpio_work_queue_cb_stack_area, + K_THREAD_STACK_SIZEOF(ps2_gpio_work_queue_cb_stack_area), + PS2_GPIO_WORK_QUEUE_CB_PRIORITY, + NULL + ); + + // Timeouts for clock pulses during read and write + k_work_init_delayable(&data->read_scl_timout, ps2_gpio_read_scl_timeout); + k_work_init_delayable(&data->write_scl_timout, ps2_gpio_write_scl_timeout); + k_work_init_delayable( + &data->write_inhibition_wait, ps2_gpio_write_inhibition_wait + ); + +#if IS_ENABLED(CONFIG_PS2_GPIO_INTERRUPT_LOG_ENABLED) + k_work_init_delayable( + &interrupt_log_scl_timout, ps2_gpio_interrupt_log_scl_timeout + ); + k_work_init( + &interrupt_log_print_worker, + ps2_gpio_interrupt_log_print_worker + ); +#endif /* IS_ENABLED(CONFIG_PS2_GPIO_INTERRUPT_LOG_ENABLED) */ + + k_work_init(&data->callback_work, ps2_gpio_read_callback_work_handler); + k_work_init(&data->resend_cmd_work, ps2_gpio_send_cmd_resend_worker); + + return 0; +} + +DEVICE_DT_INST_DEFINE( + 0, + &ps2_gpio_init, + NULL, + &ps2_gpio_data, &ps2_gpio_config, + POST_KERNEL, CONFIG_PS2_INIT_PRIORITY, + &ps2_gpio_driver_api +); diff --git a/dts/bindings/ps2/gpio-ps2.yaml b/dts/bindings/ps2/gpio-ps2.yaml new file mode 100644 index 00000000000000..f6181806dd4507 --- /dev/null +++ b/dts/bindings/ps2/gpio-ps2.yaml @@ -0,0 +1,19 @@ +# Copyright (c) 2019, Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +description: GPIO PS2 + +compatible: "gpio-ps2" + +include: base.yaml + +properties: + scl-gpios: + type: phandle-array + required: true + description: GPIO to which the SCL pin of the I2C bus is connected. + sda-gpios: + type: phandle-array + required: true + description: GPIO to which the SDA pin of the I2C bus is connected. +