blob: c02072d3f5df9aa2e1d5fe7276fac5ba5c3b8cde [file] [log] [blame]
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <sample_usbd.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/input/input.h>
#include <zephyr/usb/usbd.h>
#include <zephyr/usb/class/usbd_hid.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(main, LOG_LEVEL_INF);
static const uint8_t hid_report_desc[] = HID_KEYBOARD_REPORT_DESC();
enum kb_leds_idx {
KB_LED_NUMLOCK = 0,
KB_LED_CAPSLOCK,
KB_LED_SCROLLLOCK,
KB_LED_COUNT,
};
static const struct gpio_dt_spec kb_leds[KB_LED_COUNT] = {
GPIO_DT_SPEC_GET_OR(DT_ALIAS(led0), gpios, {0}),
GPIO_DT_SPEC_GET_OR(DT_ALIAS(led1), gpios, {0}),
GPIO_DT_SPEC_GET_OR(DT_ALIAS(led2), gpios, {0}),
};
enum kb_report_idx {
KB_MOD_KEY = 0,
KB_RESERVED,
KB_KEY_CODE1,
KB_KEY_CODE2,
KB_KEY_CODE3,
KB_KEY_CODE4,
KB_KEY_CODE5,
KB_KEY_CODE6,
KB_REPORT_COUNT,
};
struct kb_event {
uint16_t code;
int32_t value;
};
K_MSGQ_DEFINE(kb_msgq, sizeof(struct kb_event), 2, 1);
UDC_STATIC_BUF_DEFINE(report, KB_REPORT_COUNT);
static uint32_t kb_duration;
static bool kb_ready;
static void input_cb(struct input_event *evt, void *user_data)
{
struct kb_event kb_evt;
ARG_UNUSED(user_data);
kb_evt.code = evt->code;
kb_evt.value = evt->value;
if (k_msgq_put(&kb_msgq, &kb_evt, K_NO_WAIT) != 0) {
LOG_ERR("Failed to put new input event");
}
}
INPUT_CALLBACK_DEFINE(NULL, input_cb, NULL);
static void kb_iface_ready(const struct device *dev, const bool ready)
{
LOG_INF("HID device %s interface is %s",
dev->name, ready ? "ready" : "not ready");
kb_ready = ready;
}
static int kb_get_report(const struct device *dev,
const uint8_t type, const uint8_t id, const uint16_t len,
uint8_t *const buf)
{
LOG_WRN("Get Report not implemented, Type %u ID %u", type, id);
return 0;
}
static int kb_set_report(const struct device *dev,
const uint8_t type, const uint8_t id, const uint16_t len,
const uint8_t *const buf)
{
if (type != HID_REPORT_TYPE_OUTPUT) {
LOG_WRN("Unsupported report type");
return -ENOTSUP;
}
for (unsigned int i = 0; i < ARRAY_SIZE(kb_leds); i++) {
if (kb_leds[i].port == NULL) {
continue;
}
(void)gpio_pin_set_dt(&kb_leds[i], buf[0] & BIT(i));
}
return 0;
}
/* Idle duration is stored but not used to calculate idle reports. */
static void kb_set_idle(const struct device *dev,
const uint8_t id, const uint32_t duration)
{
LOG_INF("Set Idle %u to %u", id, duration);
kb_duration = duration;
}
static uint32_t kb_get_idle(const struct device *dev, const uint8_t id)
{
LOG_INF("Get Idle %u to %u", id, kb_duration);
return kb_duration;
}
static void kb_set_protocol(const struct device *dev, const uint8_t proto)
{
LOG_INF("Protocol changed to %s",
proto == 0U ? "Boot Protocol" : "Report Protocol");
}
static void kb_output_report(const struct device *dev, const uint16_t len,
const uint8_t *const buf)
{
LOG_HEXDUMP_DBG(buf, len, "o.r.");
kb_set_report(dev, HID_REPORT_TYPE_OUTPUT, 0U, len, buf);
}
struct hid_device_ops kb_ops = {
.iface_ready = kb_iface_ready,
.get_report = kb_get_report,
.set_report = kb_set_report,
.set_idle = kb_set_idle,
.get_idle = kb_get_idle,
.set_protocol = kb_set_protocol,
.output_report = kb_output_report,
};
/* doc device msg-cb start */
static void msg_cb(struct usbd_context *const usbd_ctx,
const struct usbd_msg *const msg)
{
LOG_INF("USBD message: %s", usbd_msg_type_string(msg->type));
if (msg->type == USBD_MSG_CONFIGURATION) {
LOG_INF("\tConfiguration value %d", msg->status);
}
if (usbd_can_detect_vbus(usbd_ctx)) {
if (msg->type == USBD_MSG_VBUS_READY) {
if (usbd_enable(usbd_ctx)) {
LOG_ERR("Failed to enable device support");
}
}
if (msg->type == USBD_MSG_VBUS_REMOVED) {
if (usbd_disable(usbd_ctx)) {
LOG_ERR("Failed to disable device support");
}
}
}
}
/* doc device msg-cb end */
int main(void)
{
struct usbd_context *sample_usbd;
const struct device *hid_dev;
int ret;
for (unsigned int i = 0; i < ARRAY_SIZE(kb_leds); i++) {
if (kb_leds[i].port == NULL) {
continue;
}
if (!gpio_is_ready_dt(&kb_leds[i])) {
LOG_ERR("LED device %s is not ready", kb_leds[i].port->name);
return -EIO;
}
ret = gpio_pin_configure_dt(&kb_leds[i], GPIO_OUTPUT_INACTIVE);
if (ret != 0) {
LOG_ERR("Failed to configure the LED pin, %d", ret);
return -EIO;
}
}
hid_dev = DEVICE_DT_GET_ONE(zephyr_hid_device);
if (!device_is_ready(hid_dev)) {
LOG_ERR("HID Device is not ready");
return -EIO;
}
ret = hid_device_register(hid_dev,
hid_report_desc, sizeof(hid_report_desc),
&kb_ops);
if (ret != 0) {
LOG_ERR("Failed to register HID Device, %d", ret);
return ret;
}
sample_usbd = sample_usbd_init_device(msg_cb);
if (sample_usbd == NULL) {
LOG_ERR("Failed to initialize USB device");
return -ENODEV;
}
if (!usbd_can_detect_vbus(sample_usbd)) {
/* doc device enable start */
ret = usbd_enable(sample_usbd);
if (ret) {
LOG_ERR("Failed to enable device support");
return ret;
}
/* doc device enable end */
}
LOG_INF("HID keyboard sample is initialized");
while (true) {
struct kb_event kb_evt;
k_msgq_get(&kb_msgq, &kb_evt, K_FOREVER);
switch (kb_evt.code) {
case INPUT_KEY_0:
if (kb_evt.value) {
report[KB_KEY_CODE1] = HID_KEY_NUMLOCK;
} else {
report[KB_KEY_CODE1] = 0;
}
break;
case INPUT_KEY_1:
if (kb_evt.value) {
report[KB_KEY_CODE2] = HID_KEY_CAPSLOCK;
} else {
report[KB_KEY_CODE2] = 0;
}
break;
case INPUT_KEY_2:
if (kb_evt.value) {
report[KB_KEY_CODE3] = HID_KEY_SCROLLLOCK;
} else {
report[KB_KEY_CODE3] = 0;
}
break;
case INPUT_KEY_3:
if (kb_evt.value) {
report[KB_MOD_KEY] = HID_KBD_MODIFIER_RIGHT_ALT;
report[KB_KEY_CODE4] = HID_KEY_1;
report[KB_KEY_CODE5] = HID_KEY_2;
report[KB_KEY_CODE6] = HID_KEY_3;
} else {
report[KB_MOD_KEY] = HID_KBD_MODIFIER_NONE;
report[KB_KEY_CODE4] = 0;
report[KB_KEY_CODE5] = 0;
report[KB_KEY_CODE6] = 0;
}
break;
default:
LOG_INF("Unrecognized input code %u value %d",
kb_evt.code, kb_evt.value);
continue;
}
if (!kb_ready) {
LOG_INF("USB HID device is not ready");
continue;
}
if (usbd_is_suspended(sample_usbd)) {
/* on a press of any button, send wakeup request */
if (kb_evt.value) {
ret = usbd_wakeup_request(sample_usbd);
if (ret) {
LOG_ERR("Remote wakeup error, %d", ret);
}
}
continue;
}
ret = hid_device_submit_report(hid_dev, KB_REPORT_COUNT, report);
if (ret) {
LOG_ERR("HID submit report error, %d", ret);
}
}
return 0;
}