PS2 GPIO: Added initial structure
PS2 GPIO: Added interrupt handler and pin reading/writing helper functions
PS2 GPIO: Added reading mode
PS2 GPIO: Add zephyr PS2 API support for callback and read function
PS2 GPIO: Fix reading not working with real PS2 device
PS2 GPIO: Added write with separate edge rising callback
PS2 GPIO: Made single interrupt handler
PS2 GPIO: Switch to inactive and falling handler only
PS2 GPIO: Switch back to edge falling
PS2 GPIO: Working without writing
PS2 GPIO: Enable both edges (working)
PS2 GPIO: Somewhat working write
PS2 GPIO: Write with ack working, but read always wrong stop bit
PS2 GPIO: Cleanup code
PS2 GPIO: Fix write not working by increasing clock holding time
PS2 GPIO: Adde scl clock timeout for reads
PS2 GPIO: Add scl clock timeout for writes
PS2 GPIO: Moved pin config into separate functions (not working)
PS2 GPIO: Make read work by removing some logging
PS2 GPIO: Fixed memory issue with fifo
PS2 GPIO: Kind of working sometimes except for ack bit
PS2 GPIO: Working without ack
PS2 GPIO: Fix crash: Disable resend command in read abort
PS2 GPIO: Make write work (But next read fails)
PS2 GPIO:
PS2 GPIO: I think the next read fails because we swallow the first 0 bit when reading the ack.
PS2 GPIO: Disable logging go speed up processing
PS2 GPIO: Made writing mostly work; read afterwards inconsistent
PS2 GPIO: Added post write debug log
PS2 GPIO: Improved post-write reading success rate
PS2 GPIO: Adjust timings
PS2 GPIO: Added logging of time between interrupts
PS2 GPIO: Instead of sleep() for write line inhibition, do delayable work
PS2 GPIO: Compensate for missing stop bit in read when it happens directly after write
PS2 GPIO: Adding interrupt logging
PS2 GPIO: Fix set_scl() setting sda and set_sda() setting scl
PS2 GPIO: Make interrupt logging work for sends
PS2 GPIO:
PS2 GPIO: Add interrupt logging to read functions
PS2 GPIO: Add ability to disable interrupt callback during write inhibition phase
PS2 GPIO: Rename inhibit slc duration config option
PS2 GPIO:
PS2 GPIO: Add missing inhibit setting rename
PS2 GPIO: Improve handling of spurious interrupts after writing better
PS2 GPIO:
PS2 GPIO: Adjust scl timeouts
PS2 GPIO: Added logging helper
PS2 GPIO:
PS2 GPIO: Logging helper
PS2 GPIO: Start fixing original write functions
PS2 GPIO: Cleanup read code
PS2 GPIO: Cleanup write code
PS2 GPIO: Adjust read/write scl timeouts
PS2 GPIO: Disable logger
PS2 GPIO: Add custom interrupt logging
PS2 GPIO: Enable interrupt logger
PS2 GPIO: Added separate write scl timeout for the first clock
PS2 GPIO: Fix running out of memory due to read queue
PS2 GPIO: Allow disabling of interrupt logs
PS2 GPIO: Improve logging and comments
PS2 GPIO: Removed old interrupt code
PS2 GPIO:
PS2 GPIO: interrupt stuff
PS2 GPIO:
PS2 GPIO: x
PS 2 Mouse: Add initial mouse driver for testing PS2 driver
PS2 GPIO: Add ps2 device to callback
PS2 Mouse: Added initial mouse movement cmd parsing
PS2 Mouse: Added actual mouse movement
PS2 Mouse:
PS2 Mouse: Inverted y for zmk as that is what zmk thinks is up
PS2 Mouse:
PS2 Mouse: comm
PS2 Mouse: Changed config options to match zmk mouse naming
PS2 Mouse: Added options to invert x and y mouse movements
PS2 Mouse: Rename mouse movement to mouse activity
PS2 Mouse: Added mouse button support
PS2 GPIO: Small cleanup
PS2 GPIO: Move interrupt logging around
PS2 GPIO: Added write re-try on fail
PS2 GPIO: Initial attempt at write await response (not working)
PS2 GPIO: Revert "Initial attempt at write await response (not working)"
PS2 GPIO:
PS2 GPIO: This reverts commit 6e77047ba8f8537e076f3a583d03c6222874998c.
PS2 GPIO: Added reason do abort_read()
PS2 GPIO: Disable resend on read failure for debugging
PS2 GPIO: Initialize current write byte
PS2 GPIO: Added max read retry
PS2 GPIO: Disable log interrupt
PS2 GPIO: Made timings based on ps2 protocol defines
PS2 GPIO:
PS2 GPIO: Move settings
PS2 GPIO: Added write_byte_await_response()
PS2 GPIO: Fix write retry incorrect retry number log
PS2 Mouse: Sort defines
PS2 Mouse: Improve init sequence
PS2 Mouse: Removed unneeded stream mode enabling command
PS2 GPIO: Make no response in write_byte_await_response an failure
PS2 Mouse: During init send reset command on every 4th attempt
PS2 Mouse: Add sample rate setting support
PS2 Mouse: Start use mouse events for movements
PS2 GPIO: Change most logging to debug
PS2 Mouse: Change logs from inf to dbg
PS2 Mouse: Experiment with different mouse movement methods
PS2 Mouse: Report mouse activity through timer instead of as it arrives.
PS2 Mouse: Use zmk log level instead of ps2
PS2 GPIO: Moved retry logic into separate function that also resends on error resp from device
PS2 GPIO:
PS2 GPIO: Enable retry send for ps2 interface
PS2 GPIO:
PS2 GPIO: Fix write failure error code
PS2 GPIO: Cleanup write code slightly
PS2 GPIO: Disable interrupt logging
PS2 GPIO: Use just one function for parity checking
PS2 GPIO: Small cleanup of read and write
PS2 GPIO:
PS2 GPIO: Small comment improvement
PS2 GPIO: In read_interrupt, moved data bits to a more logical position
PS2 GPIO:
PS2 GPIO: Read interrupt fix
PS2 GPIO: Call zephyr PS2 callback from a worker
PS2 GPIO: Remove unneeded gpio config flags
PS2 GPIO: Allow to enable and disable interrupt log through kconfig
PS2 Mouse: Re-enable resend request due to packet misalignment
PS2 Mouse: Parse cmd buffer using a struct instead of pointers
PS2 Mouse: Cleanup and improve documentation
PS2 Mouse: Silence error on read data queue timeout
PS2 Mouse: Improved init sequence
PS2 Mouse: Move init functions around
PS2 Mouse: Added scroll mode
PS2 Mouse: Make scroll mode configurable
PS2 Mouse: Added functions to incr and decr sampling rate
PS2 GPIO: Update blocking timeout for retry changes
PS2 GPIO: Increased timeout for write_byte_await_response()
PS2 GPIO: Added custom work queue
PS2 Mouse: Added mouse settings behavior (with queue)
PS2 Mouse: Removed queue from mouse setting behavior
PS2 Mouse:
PS2 Mouse: A queue was added to the ps2 gpio driver itself. So there is no need for it here anymore.
PS2 GPIO: Don’t log error on timed out ps2_read()
PS2 Mouse: Added reset-on-power function for trackpoints
PS2 Mouse: Add command to check if device is trackpoint
PS2 Mouse: Added send_cmd function that can send multibyte commands and read response
PS2 Mouse: Added trackpoint press to select and sensitivity setting
PS2 Mouse: Add config option for tap to select
PS2 Mouse: Fixed power-on-reset
PS2 Mouse: Add trackpoint swap xy command
PS2 Mouse: Add swap xy setting
PS2 Mouse: Added option to invert x and y directly on the trackpoint
PS2 Mouse: Added negative inertia setting
PS2 Mouse: Added upper plateau speed (value6)
PS2 Mouse: Added press-to-select threshold
PS2 Mouse: Added keybindings for TP settings
PS2 GPIO: Increased log break time
PS2 GPIO: Re-enable re-send request on read error
PS2 GPIO: Fix outdated comment
PS2 GPIO: Reduce debug logging
PS2 Mouse: Reduce logging
PS2 Mouse: Added config option to disable clicking
PS2 Mouse: Added config option for sampling rate
PS2 GPIO: Automatically adjust BT priorities when PS2 GPIO driver is enabled
PS2 Mouse: Lower default priority of NRF IRQs
PS2 Mouse: Add out of alignment detection
PS2 Mouse: Simplify out of alignment check
PS2 GPIO: Fixed deadlock on write due to wrong wokr queue priorities
PS2 GPIO: Log reason for write finish
PS2 GPIO: Increase SCL timeout
PS2 GPIO: Add pos to abort read logging
PS2 GPIO: For writes read ack bit before interrupt logging
PS2 GPIO: Fix interrupt_log_add crash
PS2 GPIO: Make interrupt_log. log in background
PS2 GPIO: Decrease priority for inhibition wait thread
PS2 GPIO: Increase timeout for first write clock
PS2 GPIO: Add argument to interrupt_log
PS2 GPIO: Improve write debug logging
PS2 Mouse: Improve misalignment detection by checking if there was prev movement
PS2 Mouse: Fix crash caused by zephyr LOG bug due to transient strings
PS2 Mouse:
PS2 Mouse: https://github.com/zephyrproject-rtos/zephyr/issues/44996
PS2 GPIO: Use main write function for 0xfe retry cmd
PS2 GPIO: Add resend callback to gpio driver
PS2 GPIO: Increase inhibition delay timer
PS2 GPIO: Properly fail write if semaphore times out
PS2 Mouse: Added resend callback
PS2 Mouse: Fix TP incr/decrease not remembering prev value
PS2 Mouse: Reset cmd buffer on misalignment
PS2 Mouse: Re-enable abort on sharp movement
PS2 Mouse: Fix mouse scrolling unintendedly if mouse mode is disabled
PS2 Mouse: Log button presses as info
PS2 GPIO: Re-enable scl interrupt on write failure
PS2 GPIO: Increase write max retry
PS2 Mouse: Make compatible with zephyr 3.2 update
PS2 GPIO: Make compatible with zephyr 3.2
PS2 GPIO: Use new zephyr gpio_*_dt apis
PS2 Mouse: Use new zephyr gpio_*_dt apis
PS2 GPIO: Prevent lockout on resend by using cb worker
PS2 GPIO: Add missing interrupt detection
PS2 GPIO: Added write error code for semaphore timeout
PS2 GPIO: Added mutex to prevent multiple threads writing at once
PS2 Mouse: Treat packet overflow as mistransmission
PS2 Mouse: If multiple mouse buttons are changed in one packet, treat it as mistransmission
PS2 Mouse: Fix value6 functions actually setting neg inertia
PS2 Mouse: Remove kconfig setting for mouse
PS2 Mouse: Reordered includes
PS2 Mouse: Switched tp sensitivity setting from float to int
PS2 Mouse: Added setting saving and restoring
PS2 Mouse: Created proper, automatic Kconfig
PS2 Mouse: Reorder includes
PS2 GPIO: Enable PS2 GPIO driver automatically if the devicetree node is present
PS2 Mouse: Rename ps2_mouse to mouse_ps2
PS2 Mouse: Fully rename ps2_mouse to mouse_ps2
PS2 Mouse: Cleanup of mouse_ps2 function naming and order
PS2 Mouse: Renamed activity cmd to packet
PS2 Mouse: Removed sampling rate from being set from keypresses
PS2 Mouse: Remove redudant decr/incr functions
PS2 Mouse: Rename array functions
PS2 Mouse: Ensure all functions have common prefix zmk_mouse_ps2
PS2 Mouse: Reduce logging
PS2 GPIO: Make sure all functions have the ps2_gpio prefix
PS2 UART: Added initial UART PS2 driver
PS2 UART: Made uart PS2 read work
PS2 UART: Refactored uart read code
PS2 UART: Added parity error checking
PS2 UART: Initial attempt at pincntrl switching
Revert "PS2 UART: Initial attempt at pincntrl switching"
This reverts commit 5538731a90031b7607f05775135385bbe14464dc.
PS2 UART: Initial version of GPIO write (not working)
PS2 UART: Kindoff working
PS2 UART: Working
PS2 UART: Small cleanup
PS2 UART: Adjust timings and improve comments
PS2 Mouse: Increase init priority to be after UART drivers
PS2 UART: Attempt to not block interrupt (not working, worker never called)
Revert "PS2 UART: Attempt to not block interrupt (not working, worker never called)"
This reverts commit 6c37f9a236137cc0ca873c6ee4afef3f69eb5708.
PS2 Mouse: Add config option to disable additional error mitigation
Auto Layer: Add automatic layer toggling on mouse movement
PS2 Mouse: Log transmission error reason
PS2 UART: Check read error in received_byte
PS2 UART: Added blocking write
Auto Layer: Fixes
Scroll Mode: Initial implementation
PS2 Mouse: Auto lint code
PS2 Mouse: Scroll mode improvements (uncommitted changes from 3 month ago)
PS2 GPIO: Apply new auto-format to ps2_gpio
PS2 UART: Apply new auto-format
PS2 GPIO: Make write mutex names unique
PS2 UART: Make write mutex names unique
PS2 GPIO: Fix zephyr 3.5 compile errors
PS2 UART: Fix zephyr 3.5 compile errors
Mouse Settings & Scroll Mode: Apply new auto-formatting
Mouse Settings: Silence zephyr 3.5 compile warnings
PS2 Mouse: Move to zephyr 3.5 input system
PS2 Mouse: Add mouse ps2 work queue
PS2 Mouse: Increase err_msg buffer size for zmk_mouse_ps2_send_cmd_resp to avoid truncating
PS2 UART: Attempt adding pinctrl states dynamically (unsuccessful)
Revert "PS2 UART: Attempt adding pinctrl states dynamically (unsuccessful)"
This reverts commit 1a2c3f6f6832a95a828675a093bef7070fbe5bac.
PS2 UART: Fix zephyr 3.5 pinctrl change error
the issue was that I was using the sleep pinctrl state, because I couldn't figure out how to create a custom state.
And in zephyr 3.2 that worked fine, but in 3.5 it disables the sleep state if CONFIG_PM_DEVICE=n.
So the state was missing.
Mouse Settings & Scroll Mode: Update mouse settings and scroll mode behaviors to new labelless behavior init method
PS2 Mouse: Disable restore of mouse PS2 settings
Mouse Auto Layer: Added toggle delay to prevent accidental activations
PS2 Mouse: Added script to generate interrupt priority overrides for zmk-config
PS2 Mouse: Don’t override interrupt priorities in all zmk builds by default
PS2 GPIO: Potentially fix SCL not being pulled down during write
PS2 UART: Potentially fix inhibition not being done correctly
PS2 Mouse: Reset device if invalid self-test result is received
PS2 UART: Fix PM_DEVICE not enabling
PS2 GPIO: Improve PS2 GPIO kconfig documentation
PS2 UART: Lower BT interrupt priorities
PS2 Mouse: Delay PS2 init sequence to give mouse time to send init data
PS2 UART: Fix first write not working
PS2 UART: Add debug write function
PS2 UART: Disable debug write function
PS2 UART: Log received bytes
PS2 UART: Use delayable instead of k_busy_wait
Revert "PS2 UART: Use delayable instead of k_busy_wait"
Realized that this is pointless, since that’s a blocking function anyways.
We need to not block in the interrupt handler rather than here.
This reverts commit eee0c35793ccccf40d72742d19cc85762423a103.
PS2 UART: Move scl interrupt into worker
PS2 UART: Add cur write pos
Revert "PS2 UART: Move scl interrupt into worker"
This reverts commit ea24c68b1af0f6e4a367622829d92f380d92a27f.
PS2 UART: Added async interrupt writing mode
PS2 UART: Ignore framing errors for 0xfa received bytes
PS2 UART: Log write and received bytes only in DBG
PS2 Mouse: Remove kconfig settings that are handled by input-config
PS2 Mouse: Moved sampling rate and press to select from kconfig to device tree
PS2 Mouse: Log when settings were set successfully
PS2 Mouse: Added devicetree settings for TP settings
Correct val6 default value
PS2 Mouse: Enable settings restore again
PS2 Mouse: Don’t restore runtime settings if device config is present
Mouse Settings: Added log and reset behavior
Adjust settings log
PS2 Mouse: Fix wrong type for tp press to select var
PS2 Mouse: Replace kconfig enable clicking with devicetree disable clicking
PS2 Mouse: Disable error mitigations by default
PS2 Mouse: Moved axis options from kconfig to devicetree
PS2 Mouse: Removed scroll mode behavior
PS2 Mouse: Move scroll mode from kconfig to devicetree
PS2 Mouse: Removed movement buffer / queue
Remove butter kconfigs
PS2 Mouse: Moved mouse_ps2 driver to module
PS2 Mouse: Convert clicking to zephyr 3.5 input system
Mouse Auto Layer: Removed it in preparation for zephyr 3.5 module
PS2 UART: Fix Linking error due to missing config options if logging is disabled
PS2 UART: Replace data queue fifo with message queue to get rid of heap use
PS2 GPIO: Replace data queue fifo with message queue to get rid of heap use
Input Listener: Added logging
Auto Layer Toggle: Added initial version
Auto Layer Toggle: Changed how config is retrieved
Auto Layer Toggle: Adjust logging
Auto Layer Toggle: Remove dependency on urob’s “Zen display & battery tweaks”
That commit adds an additional parameter to the `zmk_keymap_layer_activate` function.
Need to find a better way to make the code compatible whether urob is used or not.
5796aa0104
PS2 GPIO & UART: Apply zmk auto-formatting
PS2 UART, GPIO, Mouse: Make resend callback optional to allow compilation without zephyr fork
PS2 Mouse & Auto Layer Toggle: Add urob compatibility option
PS2 Mouse: Remove unneeded file
1362 lines
43 KiB
C
1362 lines
43 KiB
C
/*
|
|
* Copyright (c) 2019 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT gpio_ps2
|
|
|
|
#include <errno.h>
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/drivers/ps2.h>
|
|
#include <zephyr/drivers/gpio.h>
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/logging/log.h>
|
|
|
|
#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
|
|
|
|
#define PS2_GPIO_DATA_QUEUE_SIZE 100
|
|
|
|
// 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_data_queue_item {
|
|
uint8_t byte;
|
|
};
|
|
|
|
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;
|
|
|
|
#if IS_ENABLED(CONFIG_PS2_GPIO_ENABLE_PS2_RESEND_CALLBACK)
|
|
ps2_resend_callback_t resend_callback_isr;
|
|
#endif /* IS_ENABLED(CONFIG_PS2_GPIO_ENABLE_PS2_RESEND_CALLBACK) */
|
|
|
|
bool callback_enabled;
|
|
|
|
// Queue for ps2_read()
|
|
struct k_msgq data_queue;
|
|
char data_queue_buffer[PS2_GPIO_DATA_QUEUE_SIZE * sizeof(struct ps2_gpio_data_queue_item)];
|
|
|
|
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,
|
|
|
|
#if IS_ENABLED(CONFIG_PS2_GPIO_ENABLE_PS2_RESEND_CALLBACK)
|
|
.resend_callback_isr = NULL,
|
|
#endif /* IS_ENABLED(CONFIG_PS2_GPIO_ENABLE_PS2_RESEND_CALLBACK) */
|
|
|
|
.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_pin_interrupt_configure_dt(&data->scl_gpio, (GPIO_INT_EDGE_FALLING));
|
|
if (err) {
|
|
LOG_ERR("failed to enable interrupt on "
|
|
"SCL GPIO pin (err %d)",
|
|
err);
|
|
return err;
|
|
}
|
|
} else {
|
|
err = gpio_pin_interrupt_configure_dt(&data->scl_gpio, (GPIO_INT_DISABLE));
|
|
if (err) {
|
|
LOG_ERR("failed to disable interrupt on "
|
|
"SCL GPIO pin (err %d)",
|
|
err);
|
|
return 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_HIGH), "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_HIGH), "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;
|
|
struct ps2_gpio_data_queue_item queue_data;
|
|
int ret;
|
|
|
|
ret = k_msgq_get(&data->data_queue, &queue_data, timeout);
|
|
if (ret != 0) {
|
|
LOG_WRN("Data queue timed out...");
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
*dst_byte = queue_data.byte;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void ps2_gpio_data_queue_empty() {
|
|
struct ps2_gpio_data *data = &ps2_gpio_data;
|
|
|
|
k_msgq_purge(&data->data_queue);
|
|
}
|
|
|
|
void ps2_gpio_data_queue_add(uint8_t byte) {
|
|
struct ps2_gpio_data *data = &ps2_gpio_data;
|
|
|
|
int ret;
|
|
|
|
struct ps2_gpio_data_queue_item queue_data;
|
|
queue_data.byte = byte;
|
|
|
|
LOG_INF("Adding byte to data queue: 0x%x", byte);
|
|
|
|
for (int i = 0; i < 2; i++) {
|
|
ret = k_msgq_put(&data->data_queue, &queue_data, K_NO_WAIT);
|
|
if (ret == 0) {
|
|
break;
|
|
} else {
|
|
LOG_WRN("Data queue full. Removing oldest item.");
|
|
|
|
uint8_t tmp_byte;
|
|
ps2_gpio_data_queue_get_next(&tmp_byte, K_NO_WAIT);
|
|
}
|
|
}
|
|
|
|
if (ret != 0) {
|
|
LOG_ERR("Failed to add byte 0x%x to the data queue.", byte);
|
|
}
|
|
}
|
|
|
|
void ps2_gpio_send_cmd_resend_worker(struct k_work *item) {
|
|
|
|
#if IS_ENABLED(CONFIG_PS2_GPIO_ENABLE_PS2_RESEND_CALLBACK)
|
|
|
|
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);
|
|
}
|
|
|
|
#endif /* IS_ENABLED(CONFIG_PS2_GPIO_ENABLE_PS2_RESEND_CALLBACK) */
|
|
|
|
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 k_work_delayable *d_work = k_work_delayable_from_work(item);
|
|
struct ps2_gpio_data *data = CONTAINER_OF(d_work, 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(ps2_gpio_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(&ps2_gpio_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(&ps2_gpio_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_set_scl_callback_enabled(false);
|
|
ps2_gpio_configure_pin_scl_output();
|
|
ps2_gpio_configure_pin_sda_output();
|
|
|
|
LOG_PS2_INT("Starting write of byte ", &byte);
|
|
|
|
// 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 k_work_delayable *d_work = k_work_delayable_from_work(item);
|
|
struct ps2_gpio_data *data = CONTAINER_OF(d_work, 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);
|
|
|
|
#if IS_ENABLED(CONFIG_PS2_GPIO_ENABLE_PS2_RESEND_CALLBACK)
|
|
|
|
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;
|
|
}
|
|
|
|
#else
|
|
|
|
static int ps2_gpio_configure(const struct device *dev, ps2_callback_t callback_isr) {
|
|
struct ps2_gpio_data *data = dev->data;
|
|
|
|
if (!callback_isr) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
data->callback_isr = callback_isr;
|
|
ps2_gpio_enable_callback(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif /* IS_ENABLED(CONFIG_PS2_GPIO_ENABLE_PS2_RESEND_CALLBACK) */
|
|
|
|
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
|
|
*/
|
|
|
|
static int ps2_gpio_init_gpio(void) {
|
|
struct ps2_gpio_data *data = &ps2_gpio_data;
|
|
struct ps2_gpio_config *config = (struct ps2_gpio_config *)&ps2_gpio_config;
|
|
int err;
|
|
|
|
// Make pin info accessible through the data struct
|
|
data->scl_gpio = config->scl_gpio;
|
|
data->sda_gpio = config->sda_gpio;
|
|
|
|
// Overwrite any user-provided flags from the devicetree
|
|
data->scl_gpio.dt_flags = 0;
|
|
data->scl_gpio.dt_flags = 0;
|
|
|
|
// Setup interrupt callback for clock line
|
|
gpio_init_callback(&data->scl_cb_data, ps2_gpio_scl_interrupt_handler, BIT(data->scl_gpio.pin));
|
|
|
|
err = gpio_add_callback(config->scl_gpio.port, &data->scl_cb_data);
|
|
if (err) {
|
|
LOG_ERR("failed to enable interrupt callback on "
|
|
"SCL GPIO pin (err %d)",
|
|
err);
|
|
}
|
|
|
|
ps2_gpio_set_scl_callback_enabled(true);
|
|
ps2_gpio_configure_pin_scl_input();
|
|
ps2_gpio_configure_pin_sda_input();
|
|
|
|
// Check if this stuff is needed
|
|
// TODO: Figure out why this is requiered.
|
|
ps2_gpio_set_sda(1);
|
|
ps2_gpio_set_scl(1);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int ps2_gpio_init(const struct device *dev) {
|
|
|
|
struct ps2_gpio_data *data = dev->data;
|
|
|
|
// Set the ps2 device so we can retrieve it later for
|
|
// the ps2 callback
|
|
data->dev = dev;
|
|
|
|
ps2_gpio_init_gpio();
|
|
|
|
// Init data queue for synchronous read operations
|
|
k_msgq_init(&data->data_queue, data->data_queue_buffer, sizeof(struct ps2_gpio_data_queue_item),
|
|
PS2_GPIO_DATA_QUEUE_SIZE);
|
|
|
|
// 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);
|