mirror of
https://github.com/zmkfirmware/zmk.git
synced 2026-03-22 22:15:28 -05:00
feat(split): Runtime selection of split transport (#2886)
feat(split): Runtime selection of split transport Allow building multiple split transports, and select an active one based on the transport availability. Wired split availability depends on additional `detect-gpios` which must be a GPIO pin that goes active when a wired connection is present. feat(split): Suspend/resume wired UART devices. To better support runtime split support, suspend/resume the UART as necessary to save power when not using the UART. docs(split): Document adjusting nRF52 UART interrupt priorities For wired split on nRF52, you may need to adjust the priority for UART interrupts lower, to ensure the interrupts used for timing sensitive BT operations can run when needed, so document this in our pinctrl docs. refactor(split): Restore use of aync UART on nRF52. With fixes for Zephyr UART driver, re-enable using async API on nRF52. fix(split): Minor wired split fixes. Various minor fixes for wired split to avoid spurious TX in half duplex, etc. fix: Unconditionally define HID payloads to avoid error. Don't conditionally define HID indicator payload, to avoid compilation errors. docs(split): Expand on details of split transports. Expand the split keyboard documentation with a more fleshed out section on the available split trasnports, and what is and isn't supported by each, including the runtime selection functionality. --------- Co-authored-by: Nicolas Munnich <98408764+nmunnich@users.noreply.github.com>
This commit is contained in:
@@ -7,6 +7,8 @@
|
||||
#include <zephyr/types.h>
|
||||
#include <zephyr/init.h>
|
||||
|
||||
#include <zephyr/pm/device.h>
|
||||
#include <zephyr/pm/device_runtime.h>
|
||||
#include <zephyr/settings/settings.h>
|
||||
#include <zephyr/sys/crc.h>
|
||||
#include <zephyr/sys/ring_buffer.h>
|
||||
@@ -53,15 +55,24 @@ K_SEM_DEFINE(tx_sem, 0, 1);
|
||||
|
||||
#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
|
||||
|
||||
#define HAS_DIR_GPIO (DT_INST_NODE_HAS_PROP(0, dir_gpios))
|
||||
static const struct device *uart = DEVICE_DT_GET(DT_INST_PHANDLE(0, device));
|
||||
|
||||
#define HAS_DIR_GPIO (DT_INST_NODE_HAS_PROP(0, dir_gpios))
|
||||
|
||||
#if HAS_DIR_GPIO
|
||||
|
||||
static const struct gpio_dt_spec dir_gpio = GPIO_DT_SPEC_INST_GET(0, dir_gpios);
|
||||
|
||||
#endif
|
||||
|
||||
#define HAS_DETECT_GPIO DT_INST_NODE_HAS_PROP(0, detect_gpios)
|
||||
|
||||
#if HAS_DETECT_GPIO
|
||||
|
||||
static const struct gpio_dt_spec detect_gpio = GPIO_DT_SPEC_INST_GET(0, detect_gpios);
|
||||
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
#error \
|
||||
@@ -93,15 +104,43 @@ static struct zmk_split_wired_async_state async_state = {
|
||||
};
|
||||
|
||||
#endif
|
||||
#if HAS_DIR_GPIO
|
||||
|
||||
static void set_dir(uint8_t tx) { gpio_pin_set_dt(&dir_gpio, tx); }
|
||||
static void begin_rx(void) {
|
||||
#if IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)
|
||||
pm_device_runtime_get(uart);
|
||||
#elif IS_ENABLED(CONFIG_PM_DEVICE)
|
||||
pm_device_action_run(uart, PM_DEVICE_ACTION_RESUME);
|
||||
#endif // IS_ENABLED(CONFIG_PM_DEVICE)
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_INTERRUPT)
|
||||
uart_irq_rx_enable(uart);
|
||||
#elif IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_ASYNC)
|
||||
zmk_split_wired_async_rx(&async_state);
|
||||
#else
|
||||
|
||||
static inline void set_dir(uint8_t tx) {}
|
||||
|
||||
k_timer_start(&wired_central_read_timer, K_TICKS(CONFIG_ZMK_SPLIT_WIRED_POLLING_RX_PERIOD),
|
||||
K_TICKS(CONFIG_ZMK_SPLIT_WIRED_POLLING_RX_PERIOD));
|
||||
#endif
|
||||
}
|
||||
|
||||
#if HAS_DETECT_GPIO
|
||||
|
||||
static void stop_rx(void) {
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_INTERRUPT)
|
||||
uart_irq_rx_disable(uart);
|
||||
#elif IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_ASYNC)
|
||||
zmk_split_wired_async_rx_cancel(&async_state);
|
||||
#else
|
||||
k_timer_stop(&wired_central_read_timer);
|
||||
#endif
|
||||
|
||||
#if IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)
|
||||
pm_device_runtime_put(uart);
|
||||
#elif IS_ENABLED(CONFIG_PM_DEVICE)
|
||||
pm_device_action_run(uart, PM_DEVICE_ACTION_SUSPEND);
|
||||
#endif // IS_ENABLED(CONFIG_PM_DEVICE)
|
||||
}
|
||||
|
||||
#endif // HAS_DETECT_GPIO
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_INTERRUPT)
|
||||
|
||||
@@ -116,11 +155,15 @@ static void serial_cb(const struct device *dev, void *user_data) {
|
||||
uart_irq_tx_disable(dev);
|
||||
}
|
||||
|
||||
set_dir(0);
|
||||
#if HAS_DIR_GPIO
|
||||
gpio_pin_set_dt(&dir_gpio, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (uart_irq_tx_ready(dev)) {
|
||||
set_dir(1);
|
||||
#if HAS_DIR_GPIO
|
||||
gpio_pin_set_dt(&dir_gpio, 1);
|
||||
#endif
|
||||
zmk_split_wired_fifo_fill(dev, &chosen_tx_buf);
|
||||
}
|
||||
}
|
||||
@@ -142,11 +185,34 @@ static K_TIMER_DEFINE(wired_peripheral_read_timer, wired_peripheral_read_tick_cb
|
||||
|
||||
#endif
|
||||
|
||||
#if HAS_DETECT_GPIO
|
||||
|
||||
static void notify_transport_status(void);
|
||||
|
||||
static struct gpio_callback detect_callback;
|
||||
|
||||
static void notify_status_work_cb(struct k_work *_work) { notify_transport_status(); }
|
||||
|
||||
static K_WORK_DEFINE(notify_status_work, notify_status_work_cb);
|
||||
|
||||
static void detect_pin_irq_callback_handler(const struct device *port, struct gpio_callback *cb,
|
||||
const gpio_port_pins_t pin) {
|
||||
k_work_submit(¬ify_status_work);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static int zmk_split_wired_peripheral_init(void) {
|
||||
if (!device_is_ready(uart)) {
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)
|
||||
pm_device_runtime_put(uart);
|
||||
#elif IS_ENABLED(CONFIG_PM_DEVICE)
|
||||
pm_device_action_run(uart, PM_DEVICE_ACTION_SUSPEND);
|
||||
#endif // IS_ENABLED(CONFIG_PM_DEVICE)
|
||||
|
||||
#if HAS_DIR_GPIO
|
||||
gpio_pin_configure_dt(&dir_gpio, GPIO_OUTPUT_INACTIVE);
|
||||
#endif
|
||||
@@ -166,7 +232,6 @@ static int zmk_split_wired_peripheral_init(void) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
uart_irq_rx_enable(uart);
|
||||
#elif IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_ASYNC)
|
||||
async_state.uart = uart;
|
||||
int ret = zmk_split_wired_async_init(&async_state);
|
||||
@@ -174,11 +239,26 @@ static int zmk_split_wired_peripheral_init(void) {
|
||||
LOG_ERR("Failed to set up async wired split UART (%d)", ret);
|
||||
return ret;
|
||||
}
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_ASYNC)
|
||||
|
||||
#elif IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_POLLING)
|
||||
k_timer_start(&wired_peripheral_read_timer, K_TICKS(CONFIG_ZMK_SPLIT_WIRED_POLLING_RX_PERIOD),
|
||||
K_TICKS(CONFIG_ZMK_SPLIT_WIRED_POLLING_RX_PERIOD));
|
||||
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_WIRED_UART_MODE_INTERRUPT)
|
||||
#if HAS_DETECT_GPIO
|
||||
|
||||
gpio_pin_configure_dt(&detect_gpio, GPIO_INPUT);
|
||||
|
||||
gpio_init_callback(&detect_callback, detect_pin_irq_callback_handler, BIT(detect_gpio.pin));
|
||||
int err = gpio_add_callback(detect_gpio.port, &detect_callback);
|
||||
if (err) {
|
||||
LOG_ERR("Error adding the callback to the detect pin: %i", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = gpio_pin_interrupt_configure_dt(&detect_gpio, GPIO_INT_EDGE_BOTH);
|
||||
if (err < 0) {
|
||||
LOG_WRN("Failed to so configure interrupt for detection pin (%d)", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
#endif // HAS_DETECT_GPIO
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -260,11 +340,81 @@ split_peripheral_wired_report_event(const struct zmk_split_transport_peripheral_
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool is_enabled;
|
||||
|
||||
static int split_peripheral_wired_set_enabled(bool enabled) {
|
||||
if (is_enabled == enabled) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
is_enabled = enabled;
|
||||
|
||||
if (enabled) {
|
||||
begin_rx();
|
||||
return 0;
|
||||
#if HAS_DETECT_GPIO
|
||||
} else {
|
||||
stop_rx();
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
#if HAS_DETECT_GPIO
|
||||
|
||||
static zmk_split_transport_peripheral_status_changed_cb_t transport_status_cb;
|
||||
|
||||
static int
|
||||
split_peripheral_wired_set_status_callback(zmk_split_transport_peripheral_status_changed_cb_t cb) {
|
||||
transport_status_cb = cb;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct zmk_split_transport_status split_peripheral_wired_get_status() {
|
||||
int detected = gpio_pin_get_dt(&detect_gpio);
|
||||
if (detected > 0) {
|
||||
return (struct zmk_split_transport_status){
|
||||
.available = true,
|
||||
.enabled = true, // Track this
|
||||
.connections = ZMK_SPLIT_TRANSPORT_CONNECTIONS_STATUS_ALL_CONNECTED,
|
||||
|
||||
};
|
||||
} else {
|
||||
return (struct zmk_split_transport_status){
|
||||
.available = false,
|
||||
.enabled = true, // Track this
|
||||
.connections = ZMK_SPLIT_TRANSPORT_CONNECTIONS_STATUS_DISCONNECTED,
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif // HAS_DETECT_GPIO
|
||||
|
||||
static const struct zmk_split_transport_peripheral_api peripheral_api = {
|
||||
.report_event = split_peripheral_wired_report_event,
|
||||
.set_enabled = split_peripheral_wired_set_enabled,
|
||||
#if HAS_DETECT_GPIO
|
||||
.set_status_callback = split_peripheral_wired_set_status_callback,
|
||||
.get_status = split_peripheral_wired_get_status,
|
||||
#endif // HAS_DETECT_GPIO
|
||||
};
|
||||
|
||||
ZMK_SPLIT_TRANSPORT_PERIPHERAL_REGISTER(wired_peripheral, &peripheral_api);
|
||||
ZMK_SPLIT_TRANSPORT_PERIPHERAL_REGISTER(wired_peripheral, &peripheral_api,
|
||||
CONFIG_ZMK_SPLIT_WIRED_PRIORITY);
|
||||
|
||||
#if HAS_DETECT_GPIO
|
||||
|
||||
static void notify_transport_status(void) {
|
||||
if (transport_status_cb) {
|
||||
LOG_DBG("Invoking the status CB");
|
||||
transport_status_cb(&wired_peripheral, split_peripheral_wired_get_status());
|
||||
}
|
||||
}
|
||||
|
||||
#endif // HAS_DETECT_GPIO
|
||||
|
||||
static void process_tx_cb(void) {
|
||||
while (ring_buf_size_get(&chosen_rx_buf) > MSG_EXTRA_SIZE) {
|
||||
|
||||
Reference in New Issue
Block a user