From 2794f122b909eaaf56942bd463ba8451a333321a Mon Sep 17 00:00:00 2001 From: Kim Streich Date: Wed, 12 Apr 2023 12:39:46 -0600 Subject: [PATCH] feat(mouse): Add PS/2 mouse/trackpoint/etc support --- app/CMakeLists.txt | 2 + app/dts/arm/nordic/override.dtsi | 1 + app/dts/behaviors.dtsi | 1 + app/dts/behaviors/mouse_setting.dtsi | 15 + .../behaviors/zmk,behavior-mouse-setting.yaml | 6 + app/dts/bindings/zmk,mouse-ps2.yaml | 15 + app/include/dt-bindings/zmk/mouse_settings.h | 18 + app/include/zmk/mouse_ps2.h | 12 + app/src/behaviors/behavior_mouse_setting.c | 86 + app/src/mouse/Kconfig | 61 + app/src/mouse/mouse_ps2.c | 2051 +++++++++++++++++ 11 files changed, 2268 insertions(+) create mode 100644 app/dts/arm/nordic/override.dtsi create mode 100644 app/dts/behaviors/mouse_setting.dtsi create mode 100644 app/dts/bindings/behaviors/zmk,behavior-mouse-setting.yaml create mode 100644 app/dts/bindings/zmk,mouse-ps2.yaml create mode 100644 app/include/dt-bindings/zmk/mouse_settings.h create mode 100644 app/include/zmk/mouse_ps2.h create mode 100644 app/src/behaviors/behavior_mouse_setting.c create mode 100644 app/src/mouse/Kconfig create mode 100644 app/src/mouse/mouse_ps2.c diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 6ef00311027..84c131a585e 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -96,6 +96,8 @@ target_sources_ifdef(CONFIG_ZMK_USB app PRIVATE src/usb_hid.c) target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/rgb_underglow.c) target_sources_ifdef(CONFIG_ZMK_BACKLIGHT app PRIVATE src/backlight.c) target_sources_ifdef(CONFIG_ZMK_LOW_PRIORITY_WORK_QUEUE app PRIVATE src/workqueue.c) +target_sources_ifdef(CONFIG_ZMK_MOUSE_PS2 app PRIVATE src/mouse/mouse_ps2.c) +target_sources_ifdef(CONFIG_ZMK_MOUSE_PS2 app PRIVATE src/behaviors/behavior_mouse_setting.c) target_sources(app PRIVATE src/main.c) add_subdirectory(src/display/) diff --git a/app/dts/arm/nordic/override.dtsi b/app/dts/arm/nordic/override.dtsi new file mode 100644 index 00000000000..0138f4869a9 --- /dev/null +++ b/app/dts/arm/nordic/override.dtsi @@ -0,0 +1 @@ +#define NRF_DEFAULT_IRQ_PRIORITY 3 diff --git a/app/dts/behaviors.dtsi b/app/dts/behaviors.dtsi index 23f2fee2806..a9735787ef4 100644 --- a/app/dts/behaviors.dtsi +++ b/app/dts/behaviors.dtsi @@ -19,4 +19,5 @@ #include #include #include +#include #include diff --git a/app/dts/behaviors/mouse_setting.dtsi b/app/dts/behaviors/mouse_setting.dtsi new file mode 100644 index 00000000000..3bf6e1c0335 --- /dev/null +++ b/app/dts/behaviors/mouse_setting.dtsi @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +/ { + behaviors { + mms: behavior_mouse_setting { + compatible = "zmk,behavior-mouse-setting"; + label = "MOUSE_SETTING"; + #binding-cells = <1>; + }; + }; +}; diff --git a/app/dts/bindings/behaviors/zmk,behavior-mouse-setting.yaml b/app/dts/bindings/behaviors/zmk,behavior-mouse-setting.yaml new file mode 100644 index 00000000000..2388cbef1da --- /dev/null +++ b/app/dts/bindings/behaviors/zmk,behavior-mouse-setting.yaml @@ -0,0 +1,6 @@ +description: Mouse Setting + +compatible: "zmk,behavior-mouse-setting" + +include: one_param.yaml + diff --git a/app/dts/bindings/zmk,mouse-ps2.yaml b/app/dts/bindings/zmk,mouse-ps2.yaml new file mode 100644 index 00000000000..fe67de43bdd --- /dev/null +++ b/app/dts/bindings/zmk,mouse-ps2.yaml @@ -0,0 +1,15 @@ +description: PS2 mouse configuration + +compatible: "zmk,mouse-ps2" + +properties: + ps2-device: + type: phandle + required: true + description: | + The ps2 device the mouse should use. + + rst-gpios: + type: phandle-array + required: false + description: GPIO to which the RST pin of the device is connected and on which the Power-On-Reset will be performed. diff --git a/app/include/dt-bindings/zmk/mouse_settings.h b/app/include/dt-bindings/zmk/mouse_settings.h new file mode 100644 index 00000000000..5a6fc324c55 --- /dev/null +++ b/app/include/dt-bindings/zmk/mouse_settings.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ +#pragma once + +#define MS_TP_SENSITIVITY_INCR 10 +#define MS_TP_SENSITIVITY_DECR 11 + +#define MS_TP_NEG_INERTIA_INCR 12 +#define MS_TP_NEG_INERTIA_DECR 13 + +#define MS_TP_VALUE6_INCR 14 +#define MS_TP_VALUE6_DECR 15 + +#define MS_TP_PTS_THRESHOLD_INCR 16 +#define MS_TP_PTS_THRESHOLD_DECR 17 diff --git a/app/include/zmk/mouse_ps2.h b/app/include/zmk/mouse_ps2.h new file mode 100644 index 00000000000..e641f28b1af --- /dev/null +++ b/app/include/zmk/mouse_ps2.h @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +int zmk_mouse_ps2_tp_sensitivity_change(int amount); +int zmk_mouse_ps2_tp_neg_inertia_change(int amount); +int zmk_mouse_ps2_tp_value6_upper_plateau_speed_change(int amount); +int zmk_mouse_ps2_tp_pts_threshold_change(int amount); diff --git a/app/src/behaviors/behavior_mouse_setting.c b/app/src/behaviors/behavior_mouse_setting.c new file mode 100644 index 00000000000..9ec9ddb9190 --- /dev/null +++ b/app/src/behaviors/behavior_mouse_setting.c @@ -0,0 +1,86 @@ +#define DT_DRV_COMPAT zmk_behavior_mouse_setting + +#include +#include +#include + +#include +#include +#include + +#define INCREMENT_TP_SENSITIVITY 10 +#define INCREMENT_TP_NEG_INERTIA 1 +#define INCREMENT_TP_VALUE6 5 +#define INCREMENT_TP_PTS_THRESHOLD 1 + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) +{ + switch (binding->param1) { + + case MS_TP_SENSITIVITY_INCR: + return zmk_mouse_ps2_tp_sensitivity_change( + INCREMENT_TP_SENSITIVITY + ); + case MS_TP_SENSITIVITY_DECR: + return zmk_mouse_ps2_tp_sensitivity_change( + -INCREMENT_TP_SENSITIVITY + ); + + case MS_TP_NEG_INERTIA_INCR: + return zmk_mouse_ps2_tp_neg_inertia_change( + INCREMENT_TP_NEG_INERTIA + ); + case MS_TP_NEG_INERTIA_DECR: + return zmk_mouse_ps2_tp_neg_inertia_change( + -INCREMENT_TP_NEG_INERTIA + ); + + case MS_TP_VALUE6_INCR: + return zmk_mouse_ps2_tp_value6_upper_plateau_speed_change( + INCREMENT_TP_VALUE6 + ); + case MS_TP_VALUE6_DECR: + return zmk_mouse_ps2_tp_value6_upper_plateau_speed_change( + -INCREMENT_TP_VALUE6 + ); + + case MS_TP_PTS_THRESHOLD_INCR: + return zmk_mouse_ps2_tp_pts_threshold_change( + INCREMENT_TP_PTS_THRESHOLD + ); + case MS_TP_PTS_THRESHOLD_DECR: + return zmk_mouse_ps2_tp_pts_threshold_change( + -INCREMENT_TP_PTS_THRESHOLD + ); + } + + return -ENOTSUP; +} + +static int on_keymap_binding_released(struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event) +{ + return ZMK_BEHAVIOR_OPAQUE; +} + +// Initialization Function +static int zmk_behavior_mouse_setting_init(const struct device *dev) { + return 0; +}; + +static const struct behavior_driver_api +zmk_behavior_mouse_setting_driver_api = { + .binding_pressed = on_keymap_binding_pressed, + .binding_released = on_keymap_binding_released +}; + +DEVICE_DT_INST_DEFINE( + 0, + zmk_behavior_mouse_setting_init, NULL, + NULL, NULL, + APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, + &zmk_behavior_mouse_setting_driver_api +); diff --git a/app/src/mouse/Kconfig b/app/src/mouse/Kconfig new file mode 100644 index 00000000000..c35c00d49c6 --- /dev/null +++ b/app/src/mouse/Kconfig @@ -0,0 +1,61 @@ +# Copyright (c) 2023 The ZMK Contributors +# SPDX-License-Identifier: MIT + +DT_COMPAT_ZMK_PS2_MOUSE := zmk,mouse-ps2 + +config ZMK_MOUSE + bool "Enable ZMK mouse emulation" + default n + select INPUT + select INPUT_THREAD_PRIORITY_OVERRIDE + +config ZMK_MOUSE_PS2 + bool + default $(dt_compat_enabled,$(DT_COMPAT_ZMK_PS2_MOUSE)) + depends on (!ZMK_SPLIT || ZMK_SPLIT_ROLE_CENTRAL) + select ZMK_MOUSE + select PS2 + +if ZMK_MOUSE_PS2 + +config ZMK_MOUSE_PS2_SAMPLING_RATE + int "Sets how many mouse activity reports should be sent per second. The default is 100. You can reduce this setting if you see a lot of PS/2 transmission errors. Increasing it will not lead to significant improvements, because mouse reports are accumulated and only sent over bluetooth every `CONFIG_ZMK_MOUSE_TICK_DURATION` ms." + default 100 + +config ZMK_MOUSE_PS2_ENABLE_CLICKING + bool "Enables clicking events." + default y + +config ZMK_MOUSE_PS2_INVERT_X + bool "Invert the mouse movement x axis." + default n + +config ZMK_MOUSE_PS2_INVERT_Y + bool "Invert the mouse movement y axis." + default n + +config ZMK_MOUSE_PS2_SWAP_XY + bool "Swaps the X and Y axis." + default n + +config ZMK_MOUSE_PS2_SCROLL + bool "Enable scroll wheel on mouse devices supporting the Intellimouse extension." + default n + +config ZMK_MOUSE_PS2_TP_TAP_TO_SELECT + bool "Enables the ability to left-click by tapping the trackpoint." + default n + +config ZMK_MOUSE_PS2_TP_INVERT_X + bool "Inverts x on the trackpoint. This is sets the setting directly in the trackpoint firmware and should therefore correctly impact the trackpoint algorithms." + default n + +config ZMK_MOUSE_PS2_TP_INVERT_Y + bool "Inverts y on the trackpoint. This is sets the setting directly in the trackpoint firmware and should therefore correctly impact the trackpoint algorithms." + default n + +config ZMK_MOUSE_PS2_TP_SWAP_XY + bool "Swaps the x and y axis on the trackpoint. This is sets the swap settingin the trackpoint firmware and should therefore correctly impact the trackpoint algorithms. But this setting is not supported by all trackpoints." + default n + +endif # ZMK_MOUSE_PS2 diff --git a/app/src/mouse/mouse_ps2.c b/app/src/mouse/mouse_ps2.c new file mode 100644 index 00000000000..67936be3c8f --- /dev/null +++ b/app/src/mouse/mouse_ps2.c @@ -0,0 +1,2051 @@ +/* + * Copyright (c) 2019 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT zmk_mouse_ps2 + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +/* + * Settings + */ + +// How often the driver try to initialize a mouse before we give up. +#define MOUSE_PS2_INIT_ATTEMPTS 10 + +// Mouse activity packets are at least three bytes. +// This defines how much time between bytes can pass before +// we give up on the packet and start fresh. +#define MOUSE_PS2_TIMEOUT_ACTIVITY_PACKET K_MSEC(500) + +/* + * PS/2 Defines + */ + +// According to the `IBM TrackPoint System Version 4.0 Engineering +// Specification`... +// "The POR shall be timed to occur 600 ms ± 20 % from the time power is +// applied to the TrackPoint controller." +#define MOUSE_PS2_POWER_ON_RESET_TIME K_MSEC(600) + +// Common PS/2 Mouse commands +#define MOUSE_PS2_CMD_GET_SECONDARY_ID "\xe1" +#define MOUSE_PS2_CMD_GET_SECONDARY_ID_RESP_LEN 2 + +#define MOUSE_PS2_CMD_GET_DEVICE_ID "\xf2" +#define MOUSE_PS2_CMD_GET_DEVICE_ID_RESP_LEN 1 + +#define MOUSE_PS2_CMD_SET_SAMPLING_RATE "\xf3" +#define MOUSE_PS2_CMD_SET_SAMPLING_RATE_RESP_LEN 0 +#define MOUSE_PS2_CMD_SET_SAMPLING_RATE_DEFAULT 100 + +#define MOUSE_PS2_CMD_ENABLE_REPORTING "\xf4" +#define MOUSE_PS2_CMD_ENABLE_REPORTING_RESP_LEN 0 + +#define MOUSE_PS2_CMD_DISABLE_REPORTING "\xf5" +#define MOUSE_PS2_CMD_DISABLE_REPORTING_RESP_LEN 0 + +#define MOUSE_PS2_CMD_RESEND "\xfe" +#define MOUSE_PS2_CMD_RESEND_RESP_LEN 0 + +#define MOUSE_PS2_CMD_RESET "\xff" +#define MOUSE_PS2_CMD_RESET_RESP_LEN 0 + +// Trackpoint Commands +// They can be found in the `IBM TrackPoint System Version 4.0 Engineering +// Specification` (YKT3Eext.pdf)... + +#define MOUSE_PS2_CMD_TP_GET_CONFIG_BYTE "\xe2\x80\x2c" +#define MOUSE_PS2_CMD_TP_GET_CONFIG_BYTE_RESP_LEN 1 + +#define MOUSE_PS2_CMD_TP_SET_CONFIG_BYTE "\xe2\x81\x2c" +#define MOUSE_PS2_CMD_TP_SET_CONFIG_BYTE_RESP_LEN 0 + +#define MOUSE_PS2_ST_TP_SENSITIVITY "tp_sensitivity" +#define MOUSE_PS2_CMD_TP_GET_SENSITIVITY "\xe2\x80\x4a" +#define MOUSE_PS2_CMD_TP_GET_SENSITIVITY_RESP_LEN 1 + +#define MOUSE_PS2_CMD_TP_SET_SENSITIVITY "\xe2\x81\x4a" +#define MOUSE_PS2_CMD_TP_SET_SENSITIVITY_RESP_LEN 0 +#define MOUSE_PS2_CMD_TP_SET_SENSITIVITY_MIN 0 +#define MOUSE_PS2_CMD_TP_SET_SENSITIVITY_MAX 255 +#define MOUSE_PS2_CMD_TP_SET_SENSITIVITY_DEFAULT 128 + +#define MOUSE_PS2_ST_TP_NEG_INERTIA "tp_neg_inertia" +#define MOUSE_PS2_CMD_TP_GET_NEG_INERTIA "\xe2\x80\x4d" +#define MOUSE_PS2_CMD_TP_GET_NEG_INERTIA_RESP_LEN 1 + +#define MOUSE_PS2_CMD_TP_SET_NEG_INERTIA "\xe2\x81\x4d" +#define MOUSE_PS2_CMD_TP_SET_NEG_INERTIA_RESP_LEN 0 +#define MOUSE_PS2_CMD_TP_SET_NEG_INERTIA_MIN 0 +#define MOUSE_PS2_CMD_TP_SET_NEG_INERTIA_MAX 255 +#define MOUSE_PS2_CMD_TP_SET_NEG_INERTIA_DEFAULT 0x06 + +#define MOUSE_PS2_ST_TP_VALUE6 "tp_value6" +#define MOUSE_PS2_CMD_TP_GET_VALUE6_UPPER_PLATEAU_SPEED "\xe2\x80\x60" +#define MOUSE_PS2_CMD_TP_GET_VALUE6_UPPER_PLATEAU_SPEED_RESP_LEN 1 + +#define MOUSE_PS2_CMD_TP_SET_VALUE6_UPPER_PLATEAU_SPEED "\xe2\x81\x60" +#define MOUSE_PS2_CMD_TP_SET_VALUE6_UPPER_PLATEAU_SPEED_RESP_LEN 0 +#define MOUSE_PS2_CMD_TP_SET_VALUE6_UPPER_PLATEAU_SPEED_MIN 0 +#define MOUSE_PS2_CMD_TP_SET_VALUE6_UPPER_PLATEAU_SPEED_MAX 255 +#define MOUSE_PS2_CMD_TP_SET_VALUE6_UPPER_PLATEAU_SPEED_DEFAULT 0x61 + +#define MOUSE_PS2_ST_TP_PTS_THRESHOLD "tp_pts_threshold" +#define MOUSE_PS2_CMD_TP_GET_PTS_THRESHOLD "\xe2\x80\x5c" +#define MOUSE_PS2_CMD_TP_GET_PTS_THRESHOLD_RESP_LEN 1 + +#define MOUSE_PS2_CMD_TP_SET_PTS_THRESHOLD "\xe2\x81\x5c" +#define MOUSE_PS2_CMD_TP_SET_PTS_THRESHOLD_RESP_LEN 0 +#define MOUSE_PS2_CMD_TP_SET_PTS_THRESHOLD_MIN 0 +#define MOUSE_PS2_CMD_TP_SET_PTS_THRESHOLD_MAX 255 +#define MOUSE_PS2_CMD_TP_SET_PTS_THRESHOLD_DEFAULT 0x08 + +// Trackpoint Config Bits +#define MOUSE_PS2_TP_CONFIG_BIT_PRESS_TO_SELECT 0x00 +#define MOUSE_PS2_TP_CONFIG_BIT_RESERVED 0x01 +#define MOUSE_PS2_TP_CONFIG_BIT_BUTTON2 0x02 +#define MOUSE_PS2_TP_CONFIG_BIT_INVERT_X 0x03 +#define MOUSE_PS2_TP_CONFIG_BIT_INVERT_Y 0x04 +#define MOUSE_PS2_TP_CONFIG_BIT_INVERT_Z 0x05 +#define MOUSE_PS2_TP_CONFIG_BIT_SWAP_XY 0x06 +#define MOUSE_PS2_TP_CONFIG_BIT_FORCE_TRANSPARENT 0x07 + +// Responses +#define MOUSE_PS2_RESP_SELF_TEST_PASS 0xaa +#define MOUSE_PS2_RESP_SELF_TEST_FAIL 0xfc + +/* + * ZMK Defines + */ + +#define MOUSE_PS2_BUTTON_L_IDX 0 +#define MOUSE_PS2_BUTTON_R_IDX 1 +#define MOUSE_PS2_BUTTON_M_IDX 3 + +#define MOUSE_PS2_THREAD_STACK_SIZE 1024 +#define MOUSE_PS2_THREAD_PRIORITY 10 + +/* + * Global Variables + */ + +#define MOUSE_PS2_SETTINGS_SUBTREE "mouse_ps2" + +typedef enum +{ + MOUSE_PS2_PACKET_MODE_PS2_DEFAULT, + MOUSE_PS2_PACKET_MODE_SCROLL, +} zmk_mouse_ps2_packet_mode; + +struct zmk_mouse_ps2_config { + const struct device *ps2_device; + struct gpio_dt_spec rst_gpio; +}; + +struct zmk_mouse_ps2_packet { + int16_t mov_x; + int16_t mov_y; + int8_t scroll; + bool overflow_x; + bool overflow_y; + bool button_l; + bool button_m; + bool button_r; +}; + +struct zmk_mouse_ps2_data { + struct gpio_dt_spec rst_gpio; /* GPIO used for Power-On-Reset line */ + + K_THREAD_STACK_MEMBER(thread_stack, MOUSE_PS2_THREAD_STACK_SIZE); + struct k_thread thread; + + zmk_mouse_ps2_packet_mode packet_mode; + uint8_t packet_buffer[4]; + int packet_idx; + struct zmk_mouse_ps2_packet prev_packet; + struct k_work_delayable packet_buffer_timeout; + + // Stores the x and y coordinates between reporting to the os + struct vector2d move_speed; + struct vector2d scroll_speed; + struct k_timer mouse_timer; + struct k_work mouse_tick; + + bool button_l_is_held; + bool button_m_is_held; + bool button_r_is_held; + + bool activity_reporting_on; + bool is_trackpoint; + + uint8_t sampling_rate; + uint8_t tp_sensitivity; + uint8_t tp_neg_inertia; + uint8_t tp_value6; + uint8_t tp_pts_threshold; +}; + + +static const struct zmk_mouse_ps2_config zmk_mouse_ps2_config = { + .ps2_device = DEVICE_DT_GET(DT_INST_PHANDLE(0, ps2_device)), + +#if DT_INST_NODE_HAS_PROP(0, rst_gpios) + .rst_gpio = GPIO_DT_SPEC_INST_GET(0, rst_gpios), +#else + .rst_gpio = { + .port = NULL, + .pin = 0, + .dt_flags = 0, + }, +#endif + +}; + +static struct zmk_mouse_ps2_data zmk_mouse_ps2_data = { + .packet_mode = MOUSE_PS2_PACKET_MODE_PS2_DEFAULT, + .packet_idx = 0, + .prev_packet = { + .button_l = false, + .button_r = false, + .button_m = false, + .overflow_x = 0, + .overflow_y = 0, + .mov_x = 0, + .mov_y = 0, + .scroll = 0, + }, + + .button_l_is_held = false, + .button_m_is_held = false, + .button_r_is_held = false, + + .move_speed = {0}, + .scroll_speed = {0}, + + // Data reporting is disabled on init + .activity_reporting_on = false, + .is_trackpoint = false, + + // PS2 devices initialize with this rate + .sampling_rate = MOUSE_PS2_CMD_SET_SAMPLING_RATE_DEFAULT, + .tp_sensitivity = MOUSE_PS2_CMD_TP_SET_SENSITIVITY_DEFAULT, + .tp_neg_inertia = MOUSE_PS2_CMD_TP_SET_NEG_INERTIA_DEFAULT, + .tp_value6 = MOUSE_PS2_CMD_TP_SET_VALUE6_UPPER_PLATEAU_SPEED_DEFAULT, + .tp_pts_threshold = MOUSE_PS2_CMD_TP_SET_PTS_THRESHOLD_DEFAULT, +}; + +static int allowed_sampling_rates[] = { + 10, + 20, + 40, + 60, + 80, + 100, + 200, +}; + + +/* + * Function Definitions + */ + +int zmk_mouse_ps2_settings_save(); + +/* + * Helpers + */ + +#define MOUSE_PS2_GET_BIT(data, bit_pos) ( (data >> bit_pos) & 0x1 ) +#define MOUSE_PS2_SET_BIT(data, bit_val, bit_pos) ( \ + data |= (bit_val) << bit_pos \ +) + +/* + * Mouse Activity Packet Reading + */ + +void zmk_mouse_ps2_activity_process_cmd( + zmk_mouse_ps2_packet_mode packet_mode, + uint8_t packet_state, + uint8_t packet_x, + uint8_t packet_y, + uint8_t packet_extra +); +void zmk_mouse_ps2_activity_abort_cmd(); +void zmk_mouse_ps2_activity_move_mouse(int16_t mov_x, int16_t mov_y); +void zmk_mouse_ps2_activity_scroll(int8_t scroll_y); +void zmk_mouse_ps2_activity_click_buttons(bool button_l, + bool button_m, + bool button_r); +void zmk_mouse_ps2_activity_reset_packet_buffer(); +struct zmk_mouse_ps2_packet zmk_mouse_ps2_activity_parse_packet_buffer( + zmk_mouse_ps2_packet_mode packet_mode, + uint8_t packet_state, + uint8_t packet_x, + uint8_t packet_y, + uint8_t packet_extra +); + + +// Called by the PS/2 driver whenver the mouse sends a byte and +// reporting is enabled through `zmk_mouse_ps2_activity_reporting_enable`. +void zmk_mouse_ps2_activity_callback(const struct device *ps2_device, + uint8_t byte) +{ + struct zmk_mouse_ps2_data *data = &zmk_mouse_ps2_data; + + k_work_cancel_delayable(&data->packet_buffer_timeout); + + // LOG_DBG("Received mouse movement data: 0x%x", byte); + + data->packet_buffer[data->packet_idx] = byte; + + if(data->packet_idx == 0) { + + // Bit 3 of the first command byte should always be 1 + // If it is not, then we are definitely out of alignment. + // So we ask the device to resend the entire 3-byte command + // again. + int alignment_bit = MOUSE_PS2_GET_BIT(byte, 3); + if(alignment_bit != 1) { + + zmk_mouse_ps2_activity_abort_cmd(); + return; + } + } else if(data->packet_idx == 1) { + // Do nothing + } else if( + (data->packet_mode == MOUSE_PS2_PACKET_MODE_PS2_DEFAULT && + data->packet_idx == 2) || + (data->packet_mode == MOUSE_PS2_PACKET_MODE_SCROLL && + data->packet_idx == 3) + ) { + + zmk_mouse_ps2_activity_process_cmd( + data->packet_mode, + data->packet_buffer[0], + data->packet_buffer[1], + data->packet_buffer[2], + data->packet_buffer[3] + ); + zmk_mouse_ps2_activity_reset_packet_buffer(); + return; + } + + data->packet_idx += 1; + + k_work_schedule(&data->packet_buffer_timeout, MOUSE_PS2_TIMEOUT_ACTIVITY_PACKET); +} + +void zmk_mouse_ps2_activity_abort_cmd() { + struct zmk_mouse_ps2_data *data = &zmk_mouse_ps2_data; + const struct zmk_mouse_ps2_config *config = &zmk_mouse_ps2_config; + const struct device *ps2_device = config->ps2_device; + + LOG_ERR( + "PS/2 Mouse cmd buffer is out of aligment. Requesting resend." + ); + + data->packet_idx = 0; + ps2_write(ps2_device, MOUSE_PS2_CMD_RESEND[0]); + + zmk_mouse_ps2_activity_reset_packet_buffer(); +} + +// Called if the PS/2 driver encounters a transmission error and asks the +// device to resend the packet. +// The device will resend all bytes of the packet. So we need to reset our +// buffer. +void zmk_mouse_ps2_activity_resend_callback(const struct device *ps2_device) +{ + struct zmk_mouse_ps2_data *data = &zmk_mouse_ps2_data; + + LOG_WRN( + "Mouse movement cmd had transmission error on idx=%d", data->packet_idx + ); + + zmk_mouse_ps2_activity_reset_packet_buffer(); +} + +// Called if no new byte arrives within +// MOUSE_PS2_TIMEOUT_ACTIVITY_PACKET +void zmk_mouse_ps2_activity_packet_timout(struct k_work *item) +{ + struct zmk_mouse_ps2_data *data = &zmk_mouse_ps2_data; + + LOG_DBG("Mouse movement cmd timed out on idx=%d", data->packet_idx); + + // Reset the cmd buffer in case we are out of alignment. + // This way if the mouse ever gets out of alignment, the user + // can reset it by just not moving it for a second. + zmk_mouse_ps2_activity_reset_packet_buffer(); +} + +void zmk_mouse_ps2_activity_reset_packet_buffer() +{ + struct zmk_mouse_ps2_data *data = &zmk_mouse_ps2_data; + + data->packet_idx = 0; + memset(data->packet_buffer, 0x0, sizeof(data->packet_buffer)); +} + +void zmk_mouse_ps2_activity_process_cmd( + zmk_mouse_ps2_packet_mode packet_mode, + uint8_t packet_state, + uint8_t packet_x, + uint8_t packet_y, + uint8_t packet_extra +) +{ + struct zmk_mouse_ps2_data *data = &zmk_mouse_ps2_data; + struct zmk_mouse_ps2_packet packet; + packet = zmk_mouse_ps2_activity_parse_packet_buffer( + packet_mode, + packet_state, packet_x, packet_y, packet_extra + ); + + int x_delta = abs(data->prev_packet.mov_x - packet.mov_x); + int y_delta = abs(data->prev_packet.mov_y - packet.mov_y); + + LOG_DBG( + "Got mouse activity cmd " + "(mov_x=%d, mov_y=%d, o_x=%d, o_y=%d, scroll=%d, " + "b_l=%d, b_m=%d, b_r=%d) and (" + "x_delta=%d, y_delta=%d)", + packet.mov_x, packet.mov_y, packet.overflow_x, packet.overflow_y, + packet.scroll, packet.button_l, packet.button_m, packet.button_r, + x_delta, y_delta + ); + + if(packet.overflow_x == 1 && packet.overflow_y == 1) { + LOG_WRN( + "Detected overflow in both x and y. " + "Probably mistransmission. Aborting..." + ); + + zmk_mouse_ps2_activity_abort_cmd(); + return; + } + + // If the mouse exceeds the allowed threshold of movement, it's probably + // a mistransmission or misalignment. + // But we only do this check if there was prior movement that wasn't + // reset in `zmk_mouse_ps2_activity_packet_timout`. + if((data->move_speed.x != 0 && data->move_speed.y != 0) && + (x_delta > 150 || y_delta > 150)) + { + LOG_WRN( + "Detected malformed packet with " + "(mov_x=%d, mov_y=%d, o_x=%d, o_y=%d, scroll=%d, " + "b_l=%d, b_m=%d, b_r=%d) and (" + "x_delta=%d, y_delta=%d)", + packet.mov_x, packet.mov_y, packet.overflow_x, packet.overflow_y, + packet.scroll, packet.button_l, packet.button_m, packet.button_r, + x_delta, y_delta + ); + zmk_mouse_ps2_activity_abort_cmd(); + return; + } + + zmk_mouse_ps2_activity_click_buttons( + packet.button_l, packet.button_m, packet.button_r + ); + zmk_mouse_ps2_activity_move_mouse(packet.mov_x, packet.mov_y); + zmk_mouse_ps2_activity_scroll(packet.scroll); + + data->prev_packet = packet; +} + +struct zmk_mouse_ps2_packet zmk_mouse_ps2_activity_parse_packet_buffer( + zmk_mouse_ps2_packet_mode packet_mode, + uint8_t packet_state, + uint8_t packet_x, + uint8_t packet_y, + uint8_t packet_extra +) +{ + struct zmk_mouse_ps2_packet packet; + + packet.button_l = MOUSE_PS2_GET_BIT(packet_state, 0); + packet.button_r = MOUSE_PS2_GET_BIT(packet_state, 1); + packet.button_m = MOUSE_PS2_GET_BIT(packet_state, 2); + packet.overflow_x = MOUSE_PS2_GET_BIT(packet_state, 6); + packet.overflow_y = MOUSE_PS2_GET_BIT(packet_state, 7); + packet.scroll = 0; + + // The coordinates are delivered as a signed 9bit integers. + // But a PS/2 packet is only 8 bits, so the most significant + // bit with the sign is stored inside the state packet. + // + // Since we are converting the uint8_t into a int16_t + // we must pad the unused most significant bits with + // the sign bit. + // + // Example: + // ↓ x sign bit + // - State: 0x18 ( 0001 1000) + // ↑ y sign bit + // - X: 0xfd ( 1111 1101) / decimal 253 + // - New X: (1111 1111 1111 1101) / decimal -3 + // + // - Y: 0x02 ( 0000 0010) / decimal 2 + // - New Y: (0000 0000 0000 0010) / decimal 2 + // + // The code below creates a signed int and is from... + // https://wiki.osdev.org/PS/2_Mouse + packet.mov_x = packet_x - ((packet_state << 4) & 0x100); + packet.mov_y = packet_y - ((packet_state << 3) & 0x100); + + // If packet mode scroll or scroll+5 buttons is used, + // then the first 4 bit of the extra byte are used for the + // scroll wheel. It is a signed number with the rango of + // -8 to +7. + if(packet_mode == MOUSE_PS2_PACKET_MODE_SCROLL) { + MOUSE_PS2_SET_BIT( + packet.scroll, + MOUSE_PS2_GET_BIT(packet_extra, 0), + 0 + ); + MOUSE_PS2_SET_BIT( + packet.scroll, + MOUSE_PS2_GET_BIT(packet_extra, 1), + 1 + ); + MOUSE_PS2_SET_BIT( + packet.scroll, + MOUSE_PS2_GET_BIT(packet_extra, 2), + 2 + ); + packet.scroll = packet_extra - ((packet.scroll << 3) & 0x100); + } + + return packet; +} + + +/* + * Mouse Moving and Clicking + */ + +// We don't send the mouse move over BT every time we get a PS2 packet, +// because it can send too fast. +// Here we just add up the coordinates and then we use a timer to actually +// send the movement once every CONFIG_ZMK_MOUSE_TICK_DURATION ms in +// zmk_mouse_ps2_tick_timer_handler. +void zmk_mouse_ps2_activity_move_mouse(int16_t mov_x, int16_t mov_y) +{ + struct zmk_mouse_ps2_data *data = &zmk_mouse_ps2_data; + + data->move_speed.x += mov_x; + data->move_speed.y += mov_y; +} + +void zmk_mouse_ps2_activity_scroll(int8_t scroll_y) +{ + if(scroll_y > 0) { + LOG_INF("Mouse scrolled by: %d", scroll_y); + + struct zmk_mouse_ps2_data *data = &zmk_mouse_ps2_data; + data->scroll_speed.y += scroll_y; + } +} + +// Called using k_timer data->mouse_timer every x ms as configured with +// CONFIG_ZMK_MOUSE_TICK_DURATION +void zmk_mouse_ps2_tick_timer_cb(struct k_timer *dummy) { + struct zmk_mouse_ps2_data *data = &zmk_mouse_ps2_data; + + // LOG_DBG("Submitting mouse work to queue"); + // Calls zmk_mouse_ps2_tick_timer_handler on our work queue + k_work_submit_to_queue(zmk_mouse_work_q(), &data->mouse_tick); +} + +// Here is where we actually ask zmk to send the mouse movement to +// the OS. +static void zmk_mouse_ps2_tick_timer_handler(struct k_work *work) +{ + struct zmk_mouse_ps2_data *data = &zmk_mouse_ps2_data; + + // LOG_DBG("Raising mouse tick event"); + + if(data->move_speed.x == 0 && data->move_speed.y == 0 && + data->scroll_speed.x == 0 && data->scroll_speed.y == 0) { + // LOG_DBG("Not raising mouse tick event as the mouse hasn't moved."); + return; + } + + struct vector2d mouse_move = data->move_speed; + #if IS_ENABLED(CONFIG_ZMK_MOUSE_PS2_SWAP_XY) + mouse_move.x = data->move_speed.y; + mouse_move.y = data->move_speed.x; + #endif /* IS_ENABLED(CONFIG_ZMK_MOUSE_PS2_SWAP_XY) */ + + #if IS_ENABLED(CONFIG_ZMK_MOUSE_PS2_INVERT_X) + mouse_move.x = -mouse_move.x; + #endif /* IS_ENABLED(ZMK_MOUSE_PS2_INVERT_X) */ + + #if IS_ENABLED(CONFIG_ZMK_MOUSE_PS2_INVERT_Y) + mouse_move.y = -mouse_move.y; + #endif /* IS_ENABLED(ZMK_MOUSE_PS2_INVERT_Y) */ + + zmk_hid_mouse_movement_set(0, 0); + + // ZMK expects up movement to be negative, but PS2 sends it as positive + zmk_hid_mouse_movement_update(mouse_move.x, -mouse_move.y); + + zmk_hid_mouse_scroll_set(0, 0); + zmk_hid_mouse_scroll_update(data->scroll_speed.x, data->scroll_speed.y); + + zmk_endpoints_send_mouse_report(); + + data->move_speed.x = 0; + data->move_speed.y = 0; + + data->scroll_speed.x = 0; + data->scroll_speed.y = 0; +} + +void zmk_mouse_ps2_activity_click_buttons(bool button_l, + bool button_m, + bool button_r) +{ + struct zmk_mouse_ps2_data *data = &zmk_mouse_ps2_data; + + // TODO: Integrate this with the proper button mask instead + // of hardcoding the mouse button indeces. + // Check hid.c and zmk_hid_mouse_buttons_press() for more info. + + int buttons_pressed = 0; + int buttons_released = 0; + + // First we check which mouse button press states have changed + bool button_l_pressed = false; + bool button_l_released = false; + if(button_l == true && data->button_l_is_held == false) { + LOG_INF("Pressed button_l"); + + button_l_pressed = true; + buttons_pressed++; + } else if(button_l == false && data->button_l_is_held == true) { + LOG_INF("Releasing button_l"); + + button_l_released = true; + buttons_released++; + } + + bool button_m_released = false; + bool button_m_pressed = false; + if(button_m == true && data->button_m_is_held == false) { + LOG_INF("Pressing button_m"); + + button_m_pressed = true; + buttons_pressed++; + } else if(button_m == false && data->button_m_is_held == true) { + LOG_INF("Releasing button_m"); + + button_m_released = true; + buttons_released++; + } + + bool button_r_released = false; + bool button_r_pressed = false; + if(button_r == true && data->button_r_is_held == false) { + LOG_INF("Pressing button_r"); + + button_r_pressed = true; + buttons_pressed++; + } else if(button_r == false && data->button_r_is_held == true) { + LOG_INF("Releasing button_r"); + + button_r_released = true; + buttons_released++; + } + + // Then we check if this is likely a transmission error + if(buttons_pressed > 1 || buttons_released > 1) { + LOG_WRN( + "Ignoring button presses: Received %d button presses " + "and %d button releases in one packet. " + "Probably tranmission error.", + buttons_pressed, buttons_released + ); + + zmk_mouse_ps2_activity_abort_cmd(); + return; + } + + #if IS_ENABLED(CONFIG_ZMK_MOUSE_PS2_ENABLE_CLICKING) + + // If it wasn't, we actually send the events. + if(buttons_pressed > 0 || buttons_released > 0) { + + // Left button + if(button_l_pressed) { + + zmk_hid_mouse_button_press(MOUSE_PS2_BUTTON_L_IDX); + data->button_l_is_held = true; + } else if(button_l_released) { + + zmk_hid_mouse_button_release(MOUSE_PS2_BUTTON_L_IDX); + data->button_l_is_held = false; + } + + // Middle Button + if(button_m_pressed) { + + zmk_hid_mouse_button_press(MOUSE_PS2_BUTTON_M_IDX); + data->button_m_is_held = true; + } else if(button_m_released) { + + zmk_hid_mouse_button_release(MOUSE_PS2_BUTTON_M_IDX); + data->button_m_is_held = false; + } + + // Right button + if(button_r_pressed) { + + zmk_hid_mouse_button_press(MOUSE_PS2_BUTTON_R_IDX); + data->button_r_is_held = true; + } else if(button_r_released) { + + zmk_hid_mouse_button_release(MOUSE_PS2_BUTTON_R_IDX); + data->button_r_is_held = false; + } + + // Since mouse clicks generate far few events than movement, + // we send them right away instead of using the timer. + zmk_endpoints_send_mouse_report(); + } + #endif /* IS_ENABLED(CONFIG_ZMK_MOUSE_PS2_ENABLE_CLICKING) */ +} + + +/* + * PS/2 Command Sending Wrapper + */ +int zmk_mouse_ps2_activity_reporting_enable(); +int zmk_mouse_ps2_activity_reporting_disable(); + + +struct zmk_mouse_ps2_send_cmd_resp { + int err; + char err_msg[50]; + uint8_t resp_buffer[8]; + int resp_len; +}; + +struct zmk_mouse_ps2_send_cmd_resp zmk_mouse_ps2_send_cmd( + char *cmd, + int cmd_len, + uint8_t *arg, + int resp_len, + bool pause_reporting +) +{ + struct zmk_mouse_ps2_data *data = &zmk_mouse_ps2_data; + const struct zmk_mouse_ps2_config *config = &zmk_mouse_ps2_config; + const struct device *ps2_device = config->ps2_device; + int err = 0; + bool prev_activity_reporting_on = data->activity_reporting_on; + + struct zmk_mouse_ps2_send_cmd_resp resp = { + .err = 0, + .err_msg = "", + .resp_len = 0, + }; + memset(resp.resp_buffer, 0x0, sizeof(resp.resp_buffer)); + + // Don't send the string termination NULL byte + int cmd_bytes = cmd_len - 1; + if(cmd_bytes < 1) { + err = 1; + snprintf( + resp.err_msg, sizeof(resp.err_msg), + "Cannot send cmd with less than 1 byte length" + ); + + return resp; + } + + if(resp_len > sizeof(resp.resp_buffer)) { + err = 2; + snprintf( + resp.err_msg, sizeof(resp.err_msg), + "Response can't be longer than the resp_buffer (%d)", + sizeof(resp.err_msg) + ); + + return resp; + } + + if(pause_reporting == true && data->activity_reporting_on == true) { + LOG_DBG("Disabling mouse activity reporting..."); + + err = zmk_mouse_ps2_activity_reporting_disable(); + if(err) { + resp.err = err; + snprintf( + resp.err_msg, sizeof(resp.err_msg), + "Could not disable data reporting (%d)", err + ); + } + } + + if(resp.err == 0) { + LOG_DBG("Sending cmd..."); + + for(int i = 0; i < cmd_bytes; i++) { + err = ps2_write(ps2_device, cmd[i]); + if(err) { + resp.err = err; + snprintf( + resp.err_msg, sizeof(resp.err_msg), + "Could not send cmd byte %d/%d (%d)", i+1, cmd_bytes, err + ); + break; + } + } + } + + if(resp.err == 0 && arg != NULL) { + LOG_DBG("Sending arg..."); + err = ps2_write(ps2_device, *arg); + if(err) { + resp.err = err; + snprintf( + resp.err_msg, sizeof(resp.err_msg), + "Could not send arg (%d)", err + ); + } + } + + if(resp.err == 0 && resp_len > 0) { + LOG_DBG("Reading response..."); + for(int i = 0; i < resp_len; i++) { + err = ps2_read(ps2_device, &resp.resp_buffer[i]); + if(err) { + resp.err = err; + snprintf( + resp.err_msg, sizeof(resp.err_msg), + "Could not read response cmd byte %d/%d (%d)", + i+1, resp_len, err + ); + break; + } + } + } + + if(pause_reporting == true && prev_activity_reporting_on == true) { + LOG_DBG("Enabling mouse activity reporting..."); + + err = zmk_mouse_ps2_activity_reporting_enable(); + if(err) { + // Don' overwrite existing error + if(resp.err == 0) { + resp.err = err; + snprintf( + resp.err_msg, sizeof(resp.err_msg), + "Could not re-enable data reporting (%d)", err + ); + } + } + } + + return resp; +} + + +int zmk_mouse_ps2_activity_reporting_enable() +{ + struct zmk_mouse_ps2_data *data = &zmk_mouse_ps2_data; + const struct zmk_mouse_ps2_config *config = &zmk_mouse_ps2_config; + const struct device *ps2_device = config->ps2_device; + + if(data->activity_reporting_on == true) { + return 0; + } + + uint8_t cmd = MOUSE_PS2_CMD_ENABLE_REPORTING[0]; + int err = ps2_write(ps2_device, cmd); + if(err) { + LOG_ERR("Could not enable data reporting: %d", err); + return err; + } + + err = ps2_enable_callback(ps2_device); + if(err) { + LOG_ERR("Could not enable ps2 callback: %d", err); + return err; + } + + data->activity_reporting_on = true; + + return 0; +} + +int zmk_mouse_ps2_activity_reporting_disable() +{ + struct zmk_mouse_ps2_data *data = &zmk_mouse_ps2_data; + const struct zmk_mouse_ps2_config *config = &zmk_mouse_ps2_config; + const struct device *ps2_device = config->ps2_device; + + if(data->activity_reporting_on == false) { + return 0; + } + + uint8_t cmd = MOUSE_PS2_CMD_DISABLE_REPORTING[0]; + int err = ps2_write(ps2_device, cmd); + if(err) { + LOG_ERR("Could not disable data reporting: %d", err); + return err; + } + + err = ps2_disable_callback(ps2_device); + if(err) { + LOG_ERR("Could not disable ps2 callback: %d", err); + return err; + } + + data->activity_reporting_on = false; + + return 0; +} + + +/* + * PS/2 Command Helpers + */ + +int zmk_mouse_ps2_array_get_elem_index(int elem, + int *array, + size_t array_size) +{ + int elem_index = -1; + for(int i = 0; i < array_size; i++) { + if(array[i] == elem) { + elem_index = i; + break; + } + } + + return elem_index; +} + +int zmk_mouse_ps2_array_get_next_elem(int elem, + int *array, + size_t array_size) +{ + int elem_index = zmk_mouse_ps2_array_get_elem_index( + elem, array, array_size + ); + if(elem_index == -1) { + return -1; + } + + int next_index = elem_index + 1; + if(next_index >= array_size) { + return -1; + } + + return array[next_index]; +} + +int zmk_mouse_ps2_array_get_prev_elem(int elem, + int *array, + size_t array_size) +{ + int elem_index = zmk_mouse_ps2_array_get_elem_index( + elem, array, array_size + ); + if(elem_index == -1) { + return -1; + } + + int prev_index = elem_index - 1; + if(prev_index < 0 || prev_index >= array_size) { + return -1; + } + + return array[prev_index]; +} + + +/* + * PS/2 Commands + */ + +int zmk_mouse_ps2_reset(const struct device *ps2_device) { + struct zmk_mouse_ps2_send_cmd_resp resp = zmk_mouse_ps2_send_cmd( + MOUSE_PS2_CMD_RESET, + sizeof(MOUSE_PS2_CMD_RESET), + NULL, + MOUSE_PS2_CMD_RESET_RESP_LEN, + false + ); + if(resp.err) { + LOG_ERR( + "Could not send reset cmd" + ); + } + + return resp.err; +} + +int zmk_mouse_ps2_get_secondary_id(uint8_t *resp_byte_1, + uint8_t *resp_byte_2) +{ + struct zmk_mouse_ps2_send_cmd_resp resp = zmk_mouse_ps2_send_cmd( + MOUSE_PS2_CMD_GET_SECONDARY_ID, + sizeof(MOUSE_PS2_CMD_GET_SECONDARY_ID), + NULL, + MOUSE_PS2_CMD_GET_SECONDARY_ID_RESP_LEN, + true + ); + if(resp.err) { + LOG_ERR( + "Could not get secondary id" + ); + return resp.err; + } + + *resp_byte_1 = resp.resp_buffer[0]; + *resp_byte_2 = resp.resp_buffer[1]; + + return 0; +} + +int zmk_mouse_ps2_set_sampling_rate(uint8_t sampling_rate) +{ + struct zmk_mouse_ps2_data *data = &zmk_mouse_ps2_data; + + int rate_idx = zmk_mouse_ps2_array_get_elem_index( + sampling_rate, + allowed_sampling_rates, sizeof(allowed_sampling_rates) + ); + if(rate_idx == -1) { + LOG_ERR("Requested to set illegal sampling rate: %d", sampling_rate); + return -1; + } + + struct zmk_mouse_ps2_send_cmd_resp resp = zmk_mouse_ps2_send_cmd( + MOUSE_PS2_CMD_SET_SAMPLING_RATE, + sizeof(MOUSE_PS2_CMD_SET_SAMPLING_RATE), + &sampling_rate, + MOUSE_PS2_CMD_SET_SAMPLING_RATE_RESP_LEN, + true + ); + if(resp.err) { + LOG_ERR( + "Could not set sample rate to %d", sampling_rate + ); + return resp.err; + } + + data->sampling_rate = sampling_rate; + + return resp.err; +} + +int zmk_mouse_ps2_get_device_id(uint8_t *device_id) +{ + + struct zmk_mouse_ps2_send_cmd_resp resp = zmk_mouse_ps2_send_cmd( + MOUSE_PS2_CMD_GET_DEVICE_ID, + sizeof(MOUSE_PS2_CMD_GET_DEVICE_ID), + NULL, + 1, + true + ); + if(resp.err) { + LOG_ERR( + "Could not get device id" + ); + return resp.err; + } + + *device_id = resp.resp_buffer[0]; + + return 0; +} + +int zmk_mouse_ps2_set_packet_mode(zmk_mouse_ps2_packet_mode mode) +{ + struct zmk_mouse_ps2_data *data = &zmk_mouse_ps2_data; + + if(mode == MOUSE_PS2_PACKET_MODE_PS2_DEFAULT) { + // Do nothing. Mouse devices enable this by + // default. + return 0; + } + + bool prev_activity_reporting_on = data->activity_reporting_on; + zmk_mouse_ps2_activity_reporting_disable(); + + // Setting a mouse mode is a bit like using a cheat code + // in a video game. + // You have to send a specific sequence of sampling rates. + if(mode == MOUSE_PS2_PACKET_MODE_SCROLL) { + + zmk_mouse_ps2_set_sampling_rate(200); + zmk_mouse_ps2_set_sampling_rate(100); + zmk_mouse_ps2_set_sampling_rate(80); + } + + // Scroll mouse + 5 buttons mode can be enabled with the + // following sequence, but since I don't have a mouse to + // test it, I am commenting it out for now. + // else if(mode == MOUSE_PS2_PACKET_MODE_SCROLL_5_BUTTONS) { + + // zmk_mouse_ps2_set_sampling_rate(200); + // zmk_mouse_ps2_set_sampling_rate(200); + // zmk_mouse_ps2_set_sampling_rate(80); + // } + + uint8_t device_id; + int err = zmk_mouse_ps2_get_device_id(&device_id); + if(err) { + LOG_ERR( + "Could not enable packet mode %d. Failed to get device id with " + "error %d", mode, err + ); + } else { + if(device_id == 0x00) { + LOG_ERR( + "Could not enable packet mode %d. The device does not " + "support it", mode + ); + + data->packet_mode = MOUSE_PS2_PACKET_MODE_PS2_DEFAULT; + err = 1; + } else if(device_id == 0x03 || device_id == 0x04) { + LOG_INF( + "Successfully activated packet mode %d. Mouse returned " + "device id: %d", mode, device_id + ); + + data->packet_mode = MOUSE_PS2_PACKET_MODE_SCROLL; + err = 0; + } + // else if(device_id == 0x04) { + // LOG_INF( + // "Successfully activated packet mode %d. Mouse returned device " + // "id: %d", mode, device_id + // ); + + // data->packet_mode = MOUSE_PS2_PACKET_MODE_SCROLL_5_BUTTONS; + // err = 0; + // } + else { + LOG_ERR( + "Could not enable packet mode %d. Received an invalid " + "device id: %d", mode, device_id + ); + + data->packet_mode = MOUSE_PS2_PACKET_MODE_PS2_DEFAULT; + err = 1; + } + } + + // Restore sampling rate to prev value + zmk_mouse_ps2_set_sampling_rate(data->sampling_rate); + + if(prev_activity_reporting_on == true) { + zmk_mouse_ps2_activity_reporting_enable(); + } + + return err; +} + +/* + * Trackpoint Commands + */ + +bool zmk_mouse_ps2_is_device_trackpoint() +{ + bool ret = false; + + uint8_t second_id_1, second_id_2; + int err = zmk_mouse_ps2_get_secondary_id( + &second_id_1, &second_id_2 + ); + if(err) { + // Not all devices implement this command. + ret = false; + } else { + if(second_id_1 == 0x1) { + ret = true; + } + } + + LOG_DBG("Connected device is a trackpoint: %d", ret); + + return ret; +} + +int zmk_mouse_ps2_tp_get_config_byte(uint8_t *config_byte) +{ + struct zmk_mouse_ps2_send_cmd_resp resp = zmk_mouse_ps2_send_cmd( + MOUSE_PS2_CMD_TP_GET_CONFIG_BYTE, + sizeof(MOUSE_PS2_CMD_TP_GET_CONFIG_BYTE), + NULL, + MOUSE_PS2_CMD_TP_GET_CONFIG_BYTE_RESP_LEN, + true + ); + if(resp.err) { + LOG_ERR( + "Could not read trackpoint config byte" + ); + return resp.err; + } + + *config_byte = resp.resp_buffer[0]; + + return 0; +} + +int zmk_mouse_ps2_tp_set_config_option(int config_bit, + bool enabled, + char *descr) +{ + uint8_t config_byte; + int err = zmk_mouse_ps2_tp_get_config_byte( + &config_byte + ); + if(err) { + return err; + } + + bool is_enabled = MOUSE_PS2_GET_BIT( + config_byte, + config_bit + ); + + if(is_enabled == enabled) { + LOG_DBG( + "Trackpoint %s was already %s... not doing anything.", + descr, is_enabled ? "enabled" : "disabled" + ); + return 0; + } + + LOG_DBG( + "Setting trackpoint %s: %s", + descr, enabled ? "enabled" : "disabled" + ); + + MOUSE_PS2_SET_BIT(config_byte, enabled, config_bit); + + struct zmk_mouse_ps2_send_cmd_resp resp = zmk_mouse_ps2_send_cmd( + MOUSE_PS2_CMD_TP_SET_CONFIG_BYTE, + sizeof(MOUSE_PS2_CMD_TP_SET_CONFIG_BYTE), + &config_byte, + MOUSE_PS2_CMD_TP_SET_CONFIG_BYTE_RESP_LEN, + true + ); + if(resp.err) { + LOG_ERR( + "Could not set trackpoint %s to %s", + descr, enabled ? "enabled" : "disabled" + ); + return resp.err; + } + + return 0; +} + +int zmk_mouse_ps2_tp_press_to_select_set(bool enabled) +{ + int err = zmk_mouse_ps2_tp_set_config_option( + MOUSE_PS2_TP_CONFIG_BIT_PRESS_TO_SELECT, + enabled, + "Press To Select" + ); + + return err; +} + +int zmk_mouse_ps2_tp_invert_x_set(bool enabled) +{ + int err = zmk_mouse_ps2_tp_set_config_option( + MOUSE_PS2_TP_CONFIG_BIT_INVERT_X, + enabled, + "Invert X" + ); + + return err; +} + +int zmk_mouse_ps2_tp_invert_y_set(bool enabled) +{ + int err = zmk_mouse_ps2_tp_set_config_option( + MOUSE_PS2_TP_CONFIG_BIT_INVERT_Y, + enabled, + "Invert Y" + ); + + return err; +} + +int zmk_mouse_ps2_tp_swap_xy_set(bool enabled) +{ + int err = zmk_mouse_ps2_tp_set_config_option( + MOUSE_PS2_TP_CONFIG_BIT_SWAP_XY, + enabled, + "Swap XY" + ); + + return err; +} + +int zmk_mouse_ps2_tp_sensitivity_get(uint8_t *sensitivity) +{ + struct zmk_mouse_ps2_send_cmd_resp resp = zmk_mouse_ps2_send_cmd( + MOUSE_PS2_CMD_TP_GET_SENSITIVITY, + sizeof(MOUSE_PS2_CMD_TP_GET_SENSITIVITY), + NULL, + MOUSE_PS2_CMD_TP_GET_SENSITIVITY_RESP_LEN, + true + ); + if(resp.err) { + LOG_ERR( + "Could not get trackpoint sensitivity" + ); + return resp.err; + } + + // Convert uint8_t to float + // 0x80 (128) represents 1.0 + uint8_t sensitivity_int = resp.resp_buffer[0]; + *sensitivity = sensitivity_int; + + LOG_DBG("Trackpoint sensitivity is %d", sensitivity_int); + + return 0; +} + +int zmk_mouse_ps2_tp_sensitivity_set(int sensitivity) +{ + struct zmk_mouse_ps2_data *data = &zmk_mouse_ps2_data; + + if(sensitivity < MOUSE_PS2_CMD_TP_SET_SENSITIVITY_MIN || + sensitivity > MOUSE_PS2_CMD_TP_SET_SENSITIVITY_MAX) + { + LOG_ERR( + "Invalid sensitivity value %d. Min: %d; Max: %d", + sensitivity, + MOUSE_PS2_CMD_TP_SET_SENSITIVITY_MIN, + MOUSE_PS2_CMD_TP_SET_SENSITIVITY_MAX + ); + return 1; + } + + uint8_t arg = sensitivity; + + struct zmk_mouse_ps2_send_cmd_resp resp = zmk_mouse_ps2_send_cmd( + MOUSE_PS2_CMD_TP_SET_SENSITIVITY, + sizeof(MOUSE_PS2_CMD_TP_SET_SENSITIVITY), + &arg, + MOUSE_PS2_CMD_TP_SET_SENSITIVITY_RESP_LEN, + true + ); + if(resp.err) { + LOG_ERR( + "Could not set sensitivity to %d", sensitivity + ); + return resp.err; + } + + data->tp_sensitivity = sensitivity; + + return 0; +} + +int zmk_mouse_ps2_tp_sensitivity_change(int amount) +{ + struct zmk_mouse_ps2_data *data = &zmk_mouse_ps2_data; + + int new_val = data->tp_sensitivity + amount; + + LOG_INF("Setting trackpoint sensitivity to %d", new_val); + int err = zmk_mouse_ps2_tp_sensitivity_set(new_val); + if(err == 0) { + + zmk_mouse_ps2_settings_save(); + } + + return err; +} + +int zmk_mouse_ps2_tp_negative_inertia_get(uint8_t *neg_inertia) +{ + struct zmk_mouse_ps2_send_cmd_resp resp = zmk_mouse_ps2_send_cmd( + MOUSE_PS2_CMD_TP_GET_NEG_INERTIA, + sizeof(MOUSE_PS2_CMD_TP_GET_NEG_INERTIA), + NULL, + MOUSE_PS2_CMD_TP_GET_NEG_INERTIA_RESP_LEN, + true + ); + if(resp.err) { + LOG_ERR( + "Could not get trackpoint negative inertia" + ); + return resp.err; + } + + uint8_t neg_inertia_int = resp.resp_buffer[0]; + *neg_inertia = neg_inertia_int; + + LOG_DBG("Trackpoint negative inertia is %d", neg_inertia_int); + + return 0; +} + +int zmk_mouse_ps2_tp_neg_inertia_set(int neg_inertia) +{ + struct zmk_mouse_ps2_data *data = &zmk_mouse_ps2_data; + + if(neg_inertia < MOUSE_PS2_CMD_TP_SET_NEG_INERTIA_MIN || + neg_inertia > MOUSE_PS2_CMD_TP_SET_NEG_INERTIA_MAX) + { + LOG_ERR( + "Invalid negative inertia value %d. Min: %d; Max: %d", + neg_inertia, + MOUSE_PS2_CMD_TP_SET_NEG_INERTIA_MIN, + MOUSE_PS2_CMD_TP_SET_NEG_INERTIA_MAX + ); + return 1; + } + + uint8_t arg = neg_inertia; + + struct zmk_mouse_ps2_send_cmd_resp resp = zmk_mouse_ps2_send_cmd( + MOUSE_PS2_CMD_TP_SET_NEG_INERTIA, + sizeof(MOUSE_PS2_CMD_TP_SET_NEG_INERTIA), + &arg, + MOUSE_PS2_CMD_TP_SET_NEG_INERTIA_RESP_LEN, + true + ); + if(resp.err) { + LOG_ERR( + "Could not set negative inertia to %d", + neg_inertia + ); + return resp.err; + } + + data->tp_neg_inertia = neg_inertia; + + return 0; +} + +int zmk_mouse_ps2_tp_neg_inertia_change(int amount) +{ + struct zmk_mouse_ps2_data *data = &zmk_mouse_ps2_data; + + int new_val = data->tp_neg_inertia + amount; + + LOG_INF("Setting negative inertia to %d", new_val); + int err = zmk_mouse_ps2_tp_neg_inertia_set(new_val); + if(err == 0) { + + zmk_mouse_ps2_settings_save(); + } + + return err; +} + +int zmk_mouse_ps2_tp_value6_upper_plateau_speed_get(uint8_t *value6) +{ + struct zmk_mouse_ps2_send_cmd_resp resp = zmk_mouse_ps2_send_cmd( + MOUSE_PS2_CMD_TP_GET_VALUE6_UPPER_PLATEAU_SPEED, + sizeof(MOUSE_PS2_CMD_TP_GET_VALUE6_UPPER_PLATEAU_SPEED), + NULL, + MOUSE_PS2_CMD_TP_GET_VALUE6_UPPER_PLATEAU_SPEED_RESP_LEN, + true + ); + if(resp.err) { + LOG_ERR( + "Could not get trackpoint value6 upper plateau speed" + ); + return resp.err; + } + + uint8_t value6_int = resp.resp_buffer[0]; + *value6 = value6_int; + + LOG_DBG("Trackpoint value6 upper plateau speed is %d", value6_int); + + return 0; +} + +int zmk_mouse_ps2_tp_value6_upper_plateau_speed_set(int value6) +{ + struct zmk_mouse_ps2_data *data = &zmk_mouse_ps2_data; + + if(value6 < MOUSE_PS2_CMD_TP_SET_VALUE6_UPPER_PLATEAU_SPEED_MIN || + value6 > MOUSE_PS2_CMD_TP_SET_VALUE6_UPPER_PLATEAU_SPEED_MAX) + { + LOG_ERR( + "Invalid value6 upper plateau speed value %d. Min: %d; Max: %d", + value6, + MOUSE_PS2_CMD_TP_SET_VALUE6_UPPER_PLATEAU_SPEED_MIN, + MOUSE_PS2_CMD_TP_SET_VALUE6_UPPER_PLATEAU_SPEED_MAX + ); + return 1; + } + + uint8_t arg = value6; + + struct zmk_mouse_ps2_send_cmd_resp resp = zmk_mouse_ps2_send_cmd( + MOUSE_PS2_CMD_TP_SET_VALUE6_UPPER_PLATEAU_SPEED, + sizeof(MOUSE_PS2_CMD_TP_SET_VALUE6_UPPER_PLATEAU_SPEED), + &arg, + MOUSE_PS2_CMD_TP_SET_VALUE6_UPPER_PLATEAU_SPEED_RESP_LEN, + true + ); + if(resp.err) { + LOG_ERR( + "Could not set value6 upper plateau speed to %d", + value6 + ); + return resp.err; + } + + data->tp_value6 = value6; + + return 0; +} + +int zmk_mouse_ps2_tp_value6_upper_plateau_speed_change(int amount) +{ + struct zmk_mouse_ps2_data *data = &zmk_mouse_ps2_data; + + int new_val = data->tp_value6 + amount; + + LOG_INF("Setting value6 upper plateau speed to %d", new_val); + int err = zmk_mouse_ps2_tp_value6_upper_plateau_speed_set(new_val); + if(err == 0) { + + zmk_mouse_ps2_settings_save(); + } + + return err; +} + +int zmk_mouse_ps2_tp_pts_threshold_get(uint8_t *pts_threshold) +{ + struct zmk_mouse_ps2_send_cmd_resp resp = zmk_mouse_ps2_send_cmd( + MOUSE_PS2_CMD_TP_GET_PTS_THRESHOLD, + sizeof(MOUSE_PS2_CMD_TP_GET_PTS_THRESHOLD), + NULL, + MOUSE_PS2_CMD_TP_GET_PTS_THRESHOLD_RESP_LEN, + true + ); + if(resp.err) { + LOG_ERR( + "Could not get trackpoint press-to-select threshold" + ); + return resp.err; + } + + uint8_t pts_threshold_int = resp.resp_buffer[0]; + *pts_threshold = pts_threshold_int; + + LOG_DBG( + "Trackpoint press-to-select threshold is %d", pts_threshold_int + ); + + return 0; +} + +int zmk_mouse_ps2_tp_pts_threshold_set(int pts_threshold) +{ + struct zmk_mouse_ps2_data *data = &zmk_mouse_ps2_data; + + if(pts_threshold < MOUSE_PS2_CMD_TP_SET_PTS_THRESHOLD_MIN || + pts_threshold > MOUSE_PS2_CMD_TP_SET_PTS_THRESHOLD_MAX) + { + LOG_ERR( + "Invalid press-to-select threshold value %d. Min: %d; Max: %d", + pts_threshold, + MOUSE_PS2_CMD_TP_SET_PTS_THRESHOLD_MIN, + MOUSE_PS2_CMD_TP_SET_PTS_THRESHOLD_MAX + ); + return 1; + } + + uint8_t arg = pts_threshold; + + struct zmk_mouse_ps2_send_cmd_resp resp = zmk_mouse_ps2_send_cmd( + MOUSE_PS2_CMD_TP_SET_PTS_THRESHOLD, + sizeof(MOUSE_PS2_CMD_TP_SET_PTS_THRESHOLD), + &arg, + MOUSE_PS2_CMD_TP_SET_PTS_THRESHOLD_RESP_LEN, + true + ); + if(resp.err) { + LOG_ERR( + "Could not set press-to-select threshold to %d", + pts_threshold + ); + return resp.err; + } + + data->tp_pts_threshold = pts_threshold; + + return 0; +} + +int zmk_mouse_ps2_tp_pts_threshold_change(int amount) +{ + struct zmk_mouse_ps2_data *data = &zmk_mouse_ps2_data; + + int new_val = data->tp_pts_threshold + amount; + + LOG_INF("Setting press-to-select threshold to %d", new_val); + int err = zmk_mouse_ps2_tp_pts_threshold_set(new_val); + if(err == 0) { + + zmk_mouse_ps2_settings_save(); + } + + return err; +} + + +/* + * State Saving + */ + +#if IS_ENABLED(CONFIG_SETTINGS) + +struct k_work_delayable zmk_mouse_ps2_save_work; + +int zmk_mouse_ps2_settings_save_setting(char *setting_name, + const void *value, + size_t val_len) +{ + char setting_path[40]; + snprintf( + setting_path, + sizeof(setting_path), + "%s/%s", + MOUSE_PS2_SETTINGS_SUBTREE, + setting_name + ); + + LOG_DBG("Saving setting to `%s`", setting_path); + int err = settings_save_one(setting_path, value, val_len); + if(err) { + LOG_ERR("Could not save setting to `%s`: %d", setting_path, err); + } + + return err; +} + +static void zmk_mouse_ps2_settings_save_work(struct k_work *work) +{ + struct zmk_mouse_ps2_data *data = &zmk_mouse_ps2_data; + + LOG_DBG(""); + + zmk_mouse_ps2_settings_save_setting( + MOUSE_PS2_ST_TP_SENSITIVITY, + &data->tp_sensitivity, + sizeof(data->tp_sensitivity) + ); + zmk_mouse_ps2_settings_save_setting( + MOUSE_PS2_ST_TP_NEG_INERTIA, + &data->tp_neg_inertia, + sizeof(data->tp_neg_inertia) + ); + zmk_mouse_ps2_settings_save_setting( + MOUSE_PS2_ST_TP_VALUE6, + &data->tp_value6, + sizeof(data->tp_value6) + ); + zmk_mouse_ps2_settings_save_setting( + MOUSE_PS2_ST_TP_PTS_THRESHOLD, + &data->tp_pts_threshold, + sizeof(data->tp_pts_threshold) + ); +} +#endif + +int zmk_mouse_ps2_settings_save() { + LOG_DBG(""); + +#if IS_ENABLED(CONFIG_SETTINGS) + int ret = k_work_reschedule( + &zmk_mouse_ps2_save_work, + K_MSEC(CONFIG_ZMK_SETTINGS_SAVE_DEBOUNCE) + ); + return MIN(ret, 0); +#else + return 0; +#endif +} + +// This function is called when settings are loaded from flash by +// `settings_load_subtree`. +// It's called once for each PS/2 mouse setting that has been stored. +static int zmk_mouse_ps2_settings_restore(const char *name, + size_t len, + settings_read_cb read_cb, + void *cb_arg) +{ + struct zmk_mouse_ps2_data *data = &zmk_mouse_ps2_data; + uint8_t setting_val; + + if (len != sizeof(setting_val)) { + LOG_ERR( + "Could not restore settings %s: Len mismatch", + name + ); + + return -EINVAL; + } + + int rc = read_cb(cb_arg, &setting_val, sizeof(setting_val)); + if (rc <= 0) { + LOG_ERR("Could not restore setting %s: %d", name, rc); + return -EINVAL; + } + + if(data->is_trackpoint == false) { + LOG_INF( + "Mouse device is not a trackpoint. Not restoring setting %s.", + name + ); + + return 0; + } + + LOG_INF("Restoring setting %s with value: %d", name, setting_val); + + if(strcmp(name, MOUSE_PS2_ST_TP_SENSITIVITY) == 0) { + + return zmk_mouse_ps2_tp_sensitivity_set(setting_val); + } else if(strcmp(name, MOUSE_PS2_ST_TP_NEG_INERTIA) == 0) { + + return zmk_mouse_ps2_tp_neg_inertia_set(setting_val); + } else if(strcmp(name, MOUSE_PS2_ST_TP_VALUE6) == 0) { + + return zmk_mouse_ps2_tp_value6_upper_plateau_speed_set(setting_val); + } else if(strcmp(name, MOUSE_PS2_ST_TP_PTS_THRESHOLD) == 0) { + + return zmk_mouse_ps2_tp_pts_threshold_set(setting_val); + } + + return -EINVAL; +} + + +struct settings_handler zmk_mouse_ps2_settings_conf = { + .name = MOUSE_PS2_SETTINGS_SUBTREE, + .h_set = zmk_mouse_ps2_settings_restore, +}; + +int zmk_mouse_ps2_settings_init() { +#if IS_ENABLED(CONFIG_SETTINGS) + LOG_DBG(""); + + settings_subsys_init(); + + int err = settings_register(&zmk_mouse_ps2_settings_conf); + if (err) { + LOG_ERR( + "Failed to register the PS/2 mouse settings handler (err %d)", + err + ); + return err; + } + + k_work_init_delayable( + &zmk_mouse_ps2_save_work, + zmk_mouse_ps2_settings_save_work + ); + + // This will load the settings and then call + // `zmk_mouse_ps2_settings_restore`, which will set the settings + settings_load_subtree(MOUSE_PS2_SETTINGS_SUBTREE); +#endif + + return 0; +} + +/* + * Init + */ + +static void zmk_mouse_ps2_init_thread(int dev_ptr, int unused); +int zmk_mouse_ps2_init_power_on_reset(); +int zmk_mouse_ps2_init_wait_for_mouse(const struct device *dev); + +static int zmk_mouse_ps2_init(const struct device *dev) +{ + LOG_DBG("Inside zmk_mouse_ps2_init"); + + LOG_DBG("Creating mouse_ps2 init thread."); + k_thread_create( + &zmk_mouse_ps2_data.thread, + zmk_mouse_ps2_data.thread_stack, + MOUSE_PS2_THREAD_STACK_SIZE, + (k_thread_entry_t)zmk_mouse_ps2_init_thread, + (struct device *)dev, 0, NULL, + K_PRIO_COOP(MOUSE_PS2_THREAD_PRIORITY), 0, K_NO_WAIT + ); + + return 0; +} + +static void zmk_mouse_ps2_init_thread(int dev_ptr, int unused) { + const struct device *dev = INT_TO_POINTER(dev_ptr); + struct zmk_mouse_ps2_data *data = &zmk_mouse_ps2_data; + const struct zmk_mouse_ps2_config *config = dev->config; + int err; + + zmk_mouse_ps2_init_power_on_reset(); + + LOG_INF("Waiting for mouse to connect..."); + err = zmk_mouse_ps2_init_wait_for_mouse(dev); + if(err) { + LOG_ERR( + "Could not init a mouse in %d attempts. Giving up. " + "Power cycle the mouse and reset zmk to try again.", + MOUSE_PS2_INIT_ATTEMPTS + ); + return; + } + + #if CONFIG_ZMK_MOUSE_PS2_SAMPLING_RATE != \ + MOUSE_PS2_CMD_SET_SAMPLING_RATE_DEFAULT + LOG_INF( + "Setting sample rate to %d...", + CONFIG_ZMK_MOUSE_PS2_SAMPLING_RATE + ); + zmk_mouse_ps2_set_sampling_rate(CONFIG_ZMK_MOUSE_PS2_SAMPLING_RATE); + if(err) { + LOG_ERR( + "Could not set sampling rate to %d: %d", + CONFIG_ZMK_MOUSE_PS2_SAMPLING_RATE, + err + ); + return; + } + #endif /* CONFIG_ZMK_MOUSE_PS2_SAMPLING_RATE */ + + if(zmk_mouse_ps2_is_device_trackpoint() == true) { + LOG_INF("Device is a trackpoint"); + data->is_trackpoint = true; + + #if IS_ENABLED(CONFIG_ZMK_MOUSE_PS2_TP_TAP_TO_SELECT) + LOG_INF("Enabling trackpoint press to select..."); + zmk_mouse_ps2_tp_press_to_select_set(true); + #endif /* IS_ENABLED(CONFIG_ZMK_MOUSE_PS2_TP_TAP_TO_SELECT) */ + + #if IS_ENABLED(CONFIG_ZMK_MOUSE_PS2_TP_INVERT_X) + LOG_INF("Inverting trackpoint x axis."); + zmk_mouse_ps2_tp_invert_x_set(true); + #endif /* IS_ENABLED(CONFIG_ZMK_MOUSE_PS2_TP_INVERT_X) */ + + #if IS_ENABLED(CONFIG_ZMK_MOUSE_PS2_TP_INVERT_Y) + LOG_INF("Inverting trackpoint y axis."); + zmk_mouse_ps2_tp_invert_y_set(true); + #endif /* IS_ENABLED(CONFIG_ZMK_MOUSE_PS2_TP_INVERT_Y) */ + + #if IS_ENABLED(CONFIG_ZMK_MOUSE_PS2_TP_SWAP_XY) + LOG_INF("Swapping trackpoint x and y axis."); + zmk_mouse_ps2_tp_swap_xy_set(true); + #endif /* IS_ENABLED(CONFIG_ZMK_MOUSE_PS2_TP_SWAP_XY) */ + + } + + #if IS_ENABLED(CONFIG_ZMK_MOUSE_PS2_SCROLL) + LOG_INF("Enabling scroll mode."); + zmk_mouse_ps2_set_packet_mode(MOUSE_PS2_PACKET_MODE_SCROLL); + #endif /* IS_ENABLED(CONFIG_ZMK_MOUSE_PS2_SCROLL) */ + + zmk_mouse_ps2_settings_init(); + + // Configure read callback + LOG_DBG("Configuring ps2 callback..."); + err = ps2_config( + config->ps2_device, + &zmk_mouse_ps2_activity_callback, + &zmk_mouse_ps2_activity_resend_callback + ); + if(err) { + LOG_ERR("Could not configure ps2 interface: %d", err); + return ; + } + + LOG_INF("Enabling data reporting and ps2 callback..."); + err = zmk_mouse_ps2_activity_reporting_enable(); + if(err) { + LOG_ERR("Could not activate ps2 callback: %d", err); + } else { + LOG_DBG("Successfully activated ps2 callback"); + } + + k_timer_init(&data->mouse_timer, zmk_mouse_ps2_tick_timer_cb, NULL); + k_timer_start( + &data->mouse_timer, K_NO_WAIT, K_MSEC(CONFIG_ZMK_MOUSE_TICK_DURATION) + ); + k_work_init(&data->mouse_tick, zmk_mouse_ps2_tick_timer_handler); + k_work_init_delayable( + &data->packet_buffer_timeout, zmk_mouse_ps2_activity_packet_timout + ); + + return; +} + +// Power-On-Reset for trackpoints (and possibly other devices). +// From the `IBM TrackPoint System Version 4.0 Engineering +// Specification`... +// "The TrackPoint logic shall execute a Power On Reset (POR) when power is +// applied to the device. The POR shall be timed to occur 600 ms ± 20 % from +// the time power is applied to the TrackPoint controller. Activity on the +// clock and data lines is ignored prior to the completion of the diagnostic +// sequence. (See RESET mode of operation.)" +int zmk_mouse_ps2_init_power_on_reset() +{ + struct zmk_mouse_ps2_data *data = &zmk_mouse_ps2_data; + const struct zmk_mouse_ps2_config *config = &zmk_mouse_ps2_config; + + // Check if the optional rst-gpios setting was set + if(config->rst_gpio.port == NULL) { + return 0; + } + + LOG_INF("Performing Power-On-Reset..."); + + if(data->rst_gpio.port == NULL) { + data->rst_gpio = config->rst_gpio; + + // Overwrite any user-provided flags from the devicetree + data->rst_gpio.dt_flags = 0; + } + + // Set reset pin low... + int err = gpio_pin_configure_dt( + &data->rst_gpio, + (GPIO_OUTPUT_HIGH) + ); + if (err) { + LOG_ERR( + "Failed Power-On-Reset: Failed to configure RST GPIO pin to " + "output low (err %d)", err + ); + return err; + } + + // Wait 600ms + k_sleep(MOUSE_PS2_POWER_ON_RESET_TIME); + + // Set pin high + err = gpio_pin_set_dt(&data->rst_gpio, 0); + if (err) { + LOG_ERR( + "Failed Power-On-Reset: Failed to set RST GPIO pin to " + "low (err %d)", err + ); + return err; + } + + LOG_DBG("Finished Power-On-Reset successfully..."); + + return 0; +} + +int zmk_mouse_ps2_init_wait_for_mouse(const struct device *dev) +{ + const struct zmk_mouse_ps2_config *config = dev->config; + int err; + + uint8_t read_val; + + for(int i = 0; i < MOUSE_PS2_INIT_ATTEMPTS; i++) { + + LOG_INF( + "Trying to initialize mouse device (attempt %d / %d)", + i+1, MOUSE_PS2_INIT_ATTEMPTS + ); + + // PS/2 Devices do a self-test and send the result when they power up. + + err = ps2_read(config->ps2_device, &read_val); + if(err == 0) { + if(read_val != MOUSE_PS2_RESP_SELF_TEST_PASS) { + LOG_WRN("Got invalid PS/2 self-test result: 0x%x", read_val); + continue; + } + + LOG_INF("PS/2 Device passed self-test: 0x%x", read_val); + + // Read device id + LOG_INF("Reading PS/2 device id..."); + err = ps2_read(config->ps2_device, &read_val); + if(err) { + LOG_WRN("Could not read PS/2 device id: %d", err); + } else { + if(read_val == 0) { + LOG_INF("Connected PS/2 device is a mouse..."); + return 0; + } else { + LOG_WRN("PS/2 device is not a mouse: 0x%x", read_val); + return 1; + } + } + } else { + LOG_DBG( + "Could not read PS/2 device self-test result: %d. ", err + ); + } + + // But when a zmk device is reset, it doesn't cut the power to external + // devices. So the device acts as if it was never disconnected. + // So we try sending the reset command. + if(i % 2 == 0) { + LOG_INF("Trying to reset PS2 device..."); + zmk_mouse_ps2_reset(config->ps2_device); + continue; + } + + k_sleep(K_SECONDS(5)); + } + + return 1; +} + + +DEVICE_DT_INST_DEFINE( + 0, + &zmk_mouse_ps2_init, + NULL, + &zmk_mouse_ps2_data, &zmk_mouse_ps2_config, + POST_KERNEL, 41, + NULL +);