summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/zh/quick_start/opensource.rst4
-rw-r--r--platform/zmk/usb.c226
-rw-r--r--platform/zmk/usb_hid.c254
3 files changed, 484 insertions, 0 deletions
diff --git a/docs/zh/quick_start/opensource.rst b/docs/zh/quick_start/opensource.rst
index 24ffc0bb..ed730dbc 100644
--- a/docs/zh/quick_start/opensource.rst
+++ b/docs/zh/quick_start/opensource.rst
@@ -71,3 +71,7 @@
* - MuseCube
- dydcyy-gh
- https://github.com/dydcyy-gh/MuseCube
+ * - zmk
+ - OpenSiFli
+ - https://github.com/OpenSiFli/zmk
+
diff --git a/platform/zmk/usb.c b/platform/zmk/usb.c
new file mode 100644
index 00000000..40144d58
--- /dev/null
+++ b/platform/zmk/usb.c
@@ -0,0 +1,226 @@
+/*
+ * Copyright (c) 2020 The ZMK Contributors
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include <zephyr/device.h>
+#include <zephyr/init.h>
+#include <zephyr/kernel.h>
+#include <zephyr/logging/log.h>
+
+#include "usbd_core.h"
+#include "usbd_hid.h"
+
+#include <zmk/usb.h>
+#include <zmk/hid.h>
+#include <zmk/event_manager.h>
+#include <zmk/events/usb_conn_state_changed.h>
+
+LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
+
+#define CHERRYUSB_BUS_ID 0
+
+/* USB status tracking, mirroring the CherryUSB event states */
+enum cherryusb_status {
+ CUSB_STATUS_UNKNOWN,
+ CUSB_STATUS_CONNECTED,
+ CUSB_STATUS_DISCONNECTED,
+ CUSB_STATUS_CONFIGURED,
+ CUSB_STATUS_SUSPEND,
+ CUSB_STATUS_RESUME,
+ CUSB_STATUS_RESET,
+ CUSB_STATUS_ERROR,
+};
+
+static enum cherryusb_status usb_status = CUSB_STATUS_UNKNOWN;
+
+static void raise_usb_status_changed_event(struct k_work *_work) {
+ raise_zmk_usb_conn_state_changed(
+ (struct zmk_usb_conn_state_changed){.conn_state = zmk_usb_get_conn_state()});
+}
+
+K_WORK_DEFINE(usb_status_notifier_work, raise_usb_status_changed_event);
+
+enum zmk_usb_conn_state zmk_usb_get_conn_state(void) {
+ LOG_DBG("state: %d", usb_status);
+ switch (usb_status) {
+ case CUSB_STATUS_CONFIGURED:
+ case CUSB_STATUS_SUSPEND:
+ case CUSB_STATUS_RESUME:
+ return ZMK_USB_CONN_HID;
+
+ case CUSB_STATUS_DISCONNECTED:
+ case CUSB_STATUS_UNKNOWN:
+ return ZMK_USB_CONN_NONE;
+
+ default:
+ return ZMK_USB_CONN_POWERED;
+ }
+}
+
+bool zmk_usb_is_hid_ready(void) {
+ return usb_status == CUSB_STATUS_CONFIGURED || usb_status == CUSB_STATUS_RESUME;
+}
+
+/* ---- USB Descriptors ---- */
+
+#define USBD_VID CONFIG_USB_DEVICE_VID
+#define USBD_PID CONFIG_USB_DEVICE_PID
+#define USBD_MAX_POWER 100
+#define USBD_LANGID_STRING 1033
+
+#define HID_INT_EP 0x81
+#define HID_INT_EP_SIZE 64
+#define HID_INT_EP_INTERVAL 1
+
+#define USB_HID_CONFIG_DESC_SIZ (9 + HID_KEYBOARD_DESCRIPTOR_LEN)
+
+static const uint8_t device_descriptor[] = {
+ USB_DEVICE_DESCRIPTOR_INIT(USB_2_0, 0x00, 0x00, 0x00,
+ USBD_VID, USBD_PID, 0x0001, 0x01),
+};
+
+static const uint8_t config_descriptor[] = {
+ USB_CONFIG_DESCRIPTOR_INIT(USB_HID_CONFIG_DESC_SIZ, 0x01, 0x01,
+ USB_CONFIG_BUS_POWERED | USB_CONFIG_REMOTE_WAKEUP,
+ USBD_MAX_POWER),
+ HID_KEYBOARD_DESCRIPTOR_INIT(0x00,
+#if IS_ENABLED(CONFIG_ZMK_USB_BOOT)
+ 0x01, /* boot interface subclass */
+#else
+ 0x00, /* no boot */
+#endif
+ sizeof(zmk_hid_report_desc),
+ HID_INT_EP, HID_INT_EP_SIZE, HID_INT_EP_INTERVAL),
+};
+
+static const uint8_t device_quality_descriptor[] = {
+ 0x0a,
+ USB_DESCRIPTOR_TYPE_DEVICE_QUALIFIER,
+ 0x00, 0x02,
+ 0x00, 0x00, 0x00,
+ 0x40,
+ 0x00, 0x00,
+};
+
+#ifndef CONFIG_USB_DEVICE_MANUFACTURER
+#define CONFIG_USB_DEVICE_MANUFACTURER "ZMK Project"
+#endif
+
+static const char *string_descriptors[] = {
+ (const char[]){ 0x09, 0x04 }, /* Langid */
+ CONFIG_USB_DEVICE_MANUFACTURER, /* Manufacturer */
+ CONFIG_ZMK_KEYBOARD_NAME, /* Product */
+ "ZMK000000", /* Serial Number */
+};
+
+static const uint8_t *device_descriptor_callback(uint8_t speed) {
+ (void)speed;
+ return device_descriptor;
+}
+
+static const uint8_t *config_descriptor_callback(uint8_t speed) {
+ (void)speed;
+ return config_descriptor;
+}
+
+static const uint8_t *device_quality_descriptor_callback(uint8_t speed) {
+ (void)speed;
+ return device_quality_descriptor;
+}
+
+static const char *string_descriptor_callback(uint8_t speed, uint8_t index) {
+ (void)speed;
+ if (index >= ARRAY_SIZE(string_descriptors)) {
+ return NULL;
+ }
+ return string_descriptors[index];
+}
+
+const struct usb_descriptor zmk_cherryusb_descriptor = {
+ .device_descriptor_callback = device_descriptor_callback,
+ .config_descriptor_callback = config_descriptor_callback,
+ .device_quality_descriptor_callback = device_quality_descriptor_callback,
+ .string_descriptor_callback = string_descriptor_callback,
+};
+
+/* ---- CherryUSB event handler ---- */
+
+static void usbd_event_handler(uint8_t busid, uint8_t event) {
+ LOG_DBG("USB event: bus %d, event %d\n", busid, event);
+ switch (event) {
+ case USBD_EVENT_RESET:
+#if IS_ENABLED(CONFIG_ZMK_USB_BOOT)
+ extern void zmk_usb_hid_set_protocol(uint8_t protocol);
+ zmk_usb_hid_set_protocol(1); /* HID_PROTOCOL_REPORT */
+#endif
+ usb_status = CUSB_STATUS_RESET;
+ break;
+ case USBD_EVENT_CONNECTED:
+ usb_status = CUSB_STATUS_CONNECTED;
+ break;
+ case USBD_EVENT_DISCONNECTED:
+ usb_status = CUSB_STATUS_DISCONNECTED;
+ break;
+ case USBD_EVENT_CONFIGURED:
+ usb_status = CUSB_STATUS_CONFIGURED;
+ break;
+ case USBD_EVENT_SUSPEND:
+ usb_status = CUSB_STATUS_SUSPEND;
+ break;
+ case USBD_EVENT_RESUME:
+ usb_status = CUSB_STATUS_RESUME;
+ break;
+ case USBD_EVENT_ERROR:
+ usb_status = CUSB_STATUS_ERROR;
+ break;
+ default:
+ return;
+ }
+ k_work_submit(&usb_status_notifier_work);
+}
+
+/* ---- HID interface and endpoint (shared with usb_hid.c) ---- */
+
+struct usbd_interface zmk_cherryusb_hid_intf;
+
+static void hid_in_ep_callback(uint8_t busid, uint8_t ep, uint32_t nbytes);
+
+struct usbd_endpoint zmk_cherryusb_hid_in_ep = {
+ .ep_cb = hid_in_ep_callback,
+ .ep_addr = HID_INT_EP,
+};
+
+/* Semaphore for EP write completion, given from endpoint callback */
+K_SEM_DEFINE(zmk_cherryusb_hid_sem, 1, 1);
+
+static void hid_in_ep_callback(uint8_t busid, uint8_t ep, uint32_t nbytes) {
+ (void)busid;
+ (void)ep;
+ (void)nbytes;
+ k_sem_give(&zmk_cherryusb_hid_sem);
+}
+
+static int zmk_usb_init(void) {
+ usbd_desc_register(CHERRYUSB_BUS_ID, &zmk_cherryusb_descriptor);
+
+ usbd_add_interface(CHERRYUSB_BUS_ID,
+ usbd_hid_init_intf(CHERRYUSB_BUS_ID, &zmk_cherryusb_hid_intf,
+ zmk_hid_report_desc,
+ sizeof(zmk_hid_report_desc)));
+ usbd_add_endpoint(CHERRYUSB_BUS_ID, &zmk_cherryusb_hid_in_ep);
+
+ int ret = usbd_initialize(CHERRYUSB_BUS_ID,
+ (uintptr_t)CONFIG_CHERRYUSB_DEV_BASE_ADDR,
+ usbd_event_handler);
+ if (ret != 0) {
+ LOG_ERR("CherryUSB initialization failed (err %d)", ret);
+ return -EINVAL;
+ }
+
+ LOG_INF("CherryUSB initialized");
+ return 0;
+}
+
+SYS_INIT(zmk_usb_init, APPLICATION, CONFIG_ZMK_USB_INIT_PRIORITY); \ No newline at end of file
diff --git a/platform/zmk/usb_hid.c b/platform/zmk/usb_hid.c
new file mode 100644
index 00000000..81bc0d4f
--- /dev/null
+++ b/platform/zmk/usb_hid.c
@@ -0,0 +1,254 @@
+/*
+ * Copyright (c) 2020 The ZMK Contributors
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include <zephyr/device.h>
+#include <zephyr/init.h>
+#include <zephyr/kernel.h>
+#include <zephyr/logging/log.h>
+
+#include "usbd_core.h"
+#include "usbd_hid.h"
+
+#include <zmk/usb.h>
+#include <zmk/hid.h>
+#include <zmk/keymap.h>
+
+#if IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING)
+#include <zmk/pointing/resolution_multipliers.h>
+#endif
+
+#if IS_ENABLED(CONFIG_ZMK_HID_INDICATORS)
+#include <zmk/hid_indicators.h>
+#endif
+
+#include <zmk/event_manager.h>
+
+LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
+
+#define CHERRYUSB_BUS_ID 0
+#define HID_INT_EP 0x81
+
+/* Defined in cherryusb/usb.c */
+extern struct k_sem zmk_cherryusb_hid_sem;
+
+/* Write buffer must be USB-aligned and in non-cached memory */
+USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t hid_write_buffer[128];
+
+#if IS_ENABLED(CONFIG_ZMK_USB_BOOT)
+static uint8_t hid_protocol = 1; /* HID_PROTOCOL_REPORT */
+
+void zmk_usb_hid_set_protocol(uint8_t protocol) { hid_protocol = protocol; }
+#endif
+
+static uint8_t *get_keyboard_report(size_t *len) {
+#if IS_ENABLED(CONFIG_ZMK_USB_BOOT)
+ if (hid_protocol != 1) { /* boot protocol */
+ zmk_hid_boot_report_t *boot_report = zmk_hid_get_boot_report();
+ *len = sizeof(*boot_report);
+ return (uint8_t *)boot_report;
+ }
+#endif
+ struct zmk_hid_keyboard_report *report = zmk_hid_get_keyboard_report();
+ *len = sizeof(*report);
+ return (uint8_t *)report;
+}
+
+/* ---- CherryUSB HID class callbacks (weak overrides) ---- */
+
+void usbd_hid_get_report(uint8_t busid, uint8_t intf, uint8_t report_id,
+ uint8_t report_type, uint8_t **data, uint32_t *len) {
+ (void)busid;
+ (void)intf;
+
+ switch (report_type) {
+ case 3: /* Feature */
+ switch (report_id) {
+#if IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING)
+ case ZMK_HID_REPORT_ID_MOUSE: {
+ static struct zmk_hid_mouse_resolution_feature_report res_feature_report;
+ struct zmk_endpoint_instance endpoint = {
+ .transport = ZMK_TRANSPORT_USB,
+ };
+ *len = sizeof(struct zmk_hid_mouse_resolution_feature_report);
+ struct zmk_pointing_resolution_multipliers mult =
+ zmk_pointing_resolution_multipliers_get_profile(endpoint);
+ res_feature_report.body.wheel_res = mult.wheel;
+ res_feature_report.body.hwheel_res = mult.hor_wheel;
+ *data = (uint8_t *)&res_feature_report;
+ break;
+ }
+#endif
+ default:
+ *len = 0;
+ break;
+ }
+ break;
+
+ case 1: /* Input */
+ switch (report_id) {
+ case ZMK_HID_REPORT_ID_KEYBOARD: {
+ size_t size;
+ *data = get_keyboard_report(&size);
+ *len = (uint32_t)size;
+ break;
+ }
+ case ZMK_HID_REPORT_ID_CONSUMER: {
+ struct zmk_hid_consumer_report *report = zmk_hid_get_consumer_report();
+ *data = (uint8_t *)report;
+ *len = sizeof(*report);
+ break;
+ }
+ default:
+ LOG_ERR("Invalid report ID %d requested", report_id);
+ *len = 0;
+ break;
+ }
+ break;
+
+ default:
+ LOG_ERR("Unsupported report type %d requested", report_type);
+ *len = 0;
+ break;
+ }
+}
+
+void usbd_hid_set_report(uint8_t busid, uint8_t intf, uint8_t report_id,
+ uint8_t report_type, uint8_t *report, uint32_t report_len) {
+ (void)busid;
+ (void)intf;
+
+ switch (report_type) {
+ case 3: /* Feature */
+ switch (report_id) {
+#if IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING)
+ case ZMK_HID_REPORT_ID_MOUSE:
+ if (report_len != sizeof(struct zmk_hid_mouse_resolution_feature_report)) {
+ return;
+ }
+
+ struct zmk_hid_mouse_resolution_feature_report *feat =
+ (struct zmk_hid_mouse_resolution_feature_report *)report;
+ struct zmk_endpoint_instance endpoint = {
+ .transport = ZMK_TRANSPORT_USB,
+ };
+ zmk_pointing_resolution_multipliers_process_report(&feat->body, endpoint);
+ break;
+#endif
+ default:
+ break;
+ }
+ break;
+
+ case 2: /* Output */
+ switch (report_id) {
+#if IS_ENABLED(CONFIG_ZMK_HID_INDICATORS)
+ case ZMK_HID_REPORT_ID_LEDS:
+ if (report_len != sizeof(struct zmk_hid_led_report)) {
+ LOG_ERR("LED set report is malformed: length=%d", report_len);
+ return;
+ }
+ struct zmk_hid_led_report *led_report = (struct zmk_hid_led_report *)report;
+ struct zmk_endpoint_instance ep = {
+ .transport = ZMK_TRANSPORT_USB,
+ };
+ zmk_hid_indicators_process_report(&led_report->body, ep);
+ break;
+#endif
+ default:
+ LOG_ERR("Invalid report ID %d for output", report_id);
+ break;
+ }
+ break;
+
+ default:
+ LOG_ERR("Unsupported report type %d", report_type);
+ break;
+ }
+}
+
+void usbd_hid_set_idle(uint8_t busid, uint8_t intf, uint8_t report_id, uint8_t duration) {
+ (void)busid;
+ (void)intf;
+ (void)report_id;
+ (void)duration;
+}
+
+uint8_t usbd_hid_get_idle(uint8_t busid, uint8_t intf, uint8_t report_id) {
+ (void)busid;
+ (void)intf;
+ (void)report_id;
+ return 0;
+}
+
+uint8_t usbd_hid_get_protocol(uint8_t busid, uint8_t intf) {
+ (void)busid;
+ (void)intf;
+#if IS_ENABLED(CONFIG_ZMK_USB_BOOT)
+ return hid_protocol;
+#else
+ return 1; /* Report protocol */
+#endif
+}
+
+void usbd_hid_set_protocol(uint8_t busid, uint8_t intf, uint8_t protocol) {
+ (void)busid;
+ (void)intf;
+#if IS_ENABLED(CONFIG_ZMK_USB_BOOT)
+ hid_protocol = protocol;
+#endif
+}
+
+/* ---- Report sending ---- */
+
+static int zmk_usb_hid_send_report(const uint8_t *report, size_t len) {
+ if (!zmk_usb_is_hid_ready()) {
+ if (zmk_usb_get_conn_state() == ZMK_USB_CONN_HID) {
+ /* Suspended - try remote wakeup */
+ return usbd_send_remote_wakeup(CHERRYUSB_BUS_ID);
+ }
+ return -ENODEV;
+ }
+
+ if (len > sizeof(hid_write_buffer)) {
+ return -EINVAL;
+ }
+
+ k_sem_take(&zmk_cherryusb_hid_sem, K_MSEC(30));
+ memcpy(hid_write_buffer, report, len);
+ int err = usbd_ep_start_write(CHERRYUSB_BUS_ID, HID_INT_EP, hid_write_buffer, len);
+ if (err) {
+ k_sem_give(&zmk_cherryusb_hid_sem);
+ }
+ return err;
+}
+
+int zmk_usb_hid_send_keyboard_report(void) {
+ size_t len;
+ uint8_t *report = get_keyboard_report(&len);
+ return zmk_usb_hid_send_report(report, len);
+}
+
+int zmk_usb_hid_send_consumer_report(void) {
+#if IS_ENABLED(CONFIG_ZMK_USB_BOOT)
+ if (hid_protocol != 1) {
+ return -ENOTSUP;
+ }
+#endif
+ struct zmk_hid_consumer_report *report = zmk_hid_get_consumer_report();
+ return zmk_usb_hid_send_report((uint8_t *)report, sizeof(*report));
+}
+
+#if IS_ENABLED(CONFIG_ZMK_POINTING)
+int zmk_usb_hid_send_mouse_report(void) {
+#if IS_ENABLED(CONFIG_ZMK_USB_BOOT)
+ if (hid_protocol != 1) {
+ return -ENOTSUP;
+ }
+#endif
+ struct zmk_hid_mouse_report *report = zmk_hid_get_mouse_report();
+ return zmk_usb_hid_send_report((uint8_t *)report, sizeof(*report));
+}
+#endif /* CONFIG_ZMK_POINTING */ \ No newline at end of file