/* * Copyright (c) 2023 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ /* * USB device controller (UDC) driver skeleton * * This is a skeleton for a device controller driver using the UDC API. * Please use it as a starting point for a driver implementation for your * USB device controller. Maintaining a common style, terminology and * abbreviations will allow us to speed up reviews and reduce maintenance. * Copy UDC driver skeleton, remove all unrelated comments and replace the * copyright notice with your own. * * Typically, a driver implementation contains only a single source file, * but the large list of e.g. register definitions should be in a separate * .h file. * * If you want to define a helper macro, check if there is something similar * in include/zephyr/sys/util.h or include/zephyr/usb/usb_ch9.h that you can use. * Please keep all identifiers and logging messages concise and clear. */ #include "udc_common.h" #include #include #include #include #include LOG_MODULE_REGISTER(udc_skeleton, CONFIG_UDC_DRIVER_LOG_LEVEL); /* * Structure for holding controller configuration items that can remain in * non-volatile memory. This is usually accessed as * const struct udc_skeleton_config *config = dev->config; */ struct udc_skeleton_config { size_t num_of_eps; struct udc_ep_config *ep_cfg_in; struct udc_ep_config *ep_cfg_out; void (*make_thread)(const struct device *dev); int speed_idx; }; /* * Structure to hold driver private data. * Note that this is not accessible via dev->data, but as * struct udc_skeleton_data *priv = udc_get_private(dev); */ struct udc_skeleton_data { struct k_thread thread_data; }; /* * You can use one thread per driver instance model or UDC driver workqueue, * whichever model suits your needs best. If you decide to use the UDC workqueue, * enable Kconfig option UDC_WORKQUEUE and remove the handler below and * caller from the UDC_SKELETON_DEVICE_DEFINE macro. */ static ALWAYS_INLINE void skeleton_thread_handler(void *const arg) { const struct device *dev = (const struct device *)arg; LOG_DBG("Driver %p thread started", dev); while (true) { k_msleep(1000); } } /* * This is called in the context of udc_ep_enqueue() and must * not block. The driver can immediately claim the buffer if the queue is empty, * but usually it is offloaded to a thread or workqueue to handle transfers * in a single location. Please refer to existing driver implementations * for examples. */ static int udc_skeleton_ep_enqueue(const struct device *dev, struct udc_ep_config *const cfg, struct net_buf *buf) { LOG_DBG("%p enqueue %p", dev, buf); udc_buf_put(cfg, buf); if (cfg->stat.halted) { /* * It is fine to enqueue a transfer for a halted endpoint, * you need to make sure that transfers are retriggered when * the halt is cleared. * * Always use the abbreviation 'ep' for the endpoint address * and 'ep_idx' or 'ep_num' for the endpoint number identifiers. * Although struct udc_ep_config uses address to be unambiguous * in its context. */ LOG_DBG("ep 0x%02x halted", cfg->addr); return 0; } return 0; } /* * This is called in the context of udc_ep_dequeue() * and must remove all requests from an endpoint queue * Successful removal should be reported to the higher level with * ECONNABORTED as the request result. * It is up to the request owner to clean up or reuse the buffer. */ static int udc_skeleton_ep_dequeue(const struct device *dev, struct udc_ep_config *const cfg) { unsigned int lock_key; struct net_buf *buf; lock_key = irq_lock(); buf = udc_buf_get_all(dev, cfg->addr); if (buf) { udc_submit_ep_event(dev, buf, -ECONNABORTED); } irq_unlock(lock_key); return 0; } /* * Configure and make an endpoint ready for use. * This is called in the context of udc_ep_enable() or udc_ep_enable_internal(), * the latter of which may be used by the driver to enable control endpoints. */ static int udc_skeleton_ep_enable(const struct device *dev, struct udc_ep_config *const cfg) { LOG_DBG("Enable ep 0x%02x", cfg->addr); return 0; } /* * Opposite function to udc_skeleton_ep_enable(). udc_ep_disable_internal() * may be used by the driver to disable control endpoints. */ static int udc_skeleton_ep_disable(const struct device *dev, struct udc_ep_config *const cfg) { LOG_DBG("Disable ep 0x%02x", cfg->addr); return 0; } /* Halt endpoint. Halted endpoint should respond with a STALL handshake. */ static int udc_skeleton_ep_set_halt(const struct device *dev, struct udc_ep_config *const cfg) { LOG_DBG("Set halt ep 0x%02x", cfg->addr); cfg->stat.halted = true; return 0; } /* * Opposite to halt endpoint. If there are requests in the endpoint queue, * the next transfer should be prepared. */ static int udc_skeleton_ep_clear_halt(const struct device *dev, struct udc_ep_config *const cfg) { LOG_DBG("Clear halt ep 0x%02x", cfg->addr); cfg->stat.halted = false; return 0; } static int udc_skeleton_set_address(const struct device *dev, const uint8_t addr) { LOG_DBG("Set new address %u for %p", addr, dev); return 0; } static int udc_skeleton_host_wakeup(const struct device *dev) { LOG_DBG("Remote wakeup from %p", dev); return 0; } /* Return actual USB device speed */ static enum udc_bus_speed udc_skeleton_device_speed(const struct device *dev) { struct udc_data *data = dev->data; return data->caps.hs ? UDC_BUS_SPEED_HS : UDC_BUS_SPEED_FS; } static int udc_skeleton_enable(const struct device *dev) { LOG_DBG("Enable device %p", dev); return 0; } static int udc_skeleton_disable(const struct device *dev) { LOG_DBG("Enable device %p", dev); return 0; } /* * Prepare and configure most of the parts, if the controller has a way * of detecting VBUS activity it should be enabled here. * Only udc_skeleton_enable() makes device visible to the host. */ static int udc_skeleton_init(const struct device *dev) { if (udc_ep_enable_internal(dev, USB_CONTROL_EP_OUT, USB_EP_TYPE_CONTROL, 64, 0)) { LOG_ERR("Failed to enable control endpoint"); return -EIO; } if (udc_ep_enable_internal(dev, USB_CONTROL_EP_IN, USB_EP_TYPE_CONTROL, 64, 0)) { LOG_ERR("Failed to enable control endpoint"); return -EIO; } return 0; } /* Shut down the controller completely */ static int udc_skeleton_shutdown(const struct device *dev) { if (udc_ep_disable_internal(dev, USB_CONTROL_EP_OUT)) { LOG_ERR("Failed to disable control endpoint"); return -EIO; } if (udc_ep_disable_internal(dev, USB_CONTROL_EP_IN)) { LOG_ERR("Failed to disable control endpoint"); return -EIO; } return 0; } /* * This is called once to initialize the controller and endpoints * capabilities, and register endpoint structures. */ static int udc_skeleton_driver_preinit(const struct device *dev) { const struct udc_skeleton_config *config = dev->config; struct udc_data *data = dev->data; uint16_t mps = 1023; int err; /* * You do not need to initialize it if your driver does not use * udc_lock_internal() / udc_unlock_internal(), but implements its * own mechanism. */ k_mutex_init(&data->mutex); data->caps.rwup = true; data->caps.mps0 = UDC_MPS0_64; if (config->speed_idx == 2) { data->caps.hs = true; mps = 1024; } for (int i = 0; i < config->num_of_eps; i++) { config->ep_cfg_out[i].caps.out = 1; if (i == 0) { config->ep_cfg_out[i].caps.control = 1; config->ep_cfg_out[i].caps.mps = 64; } else { config->ep_cfg_out[i].caps.bulk = 1; config->ep_cfg_out[i].caps.interrupt = 1; config->ep_cfg_out[i].caps.iso = 1; config->ep_cfg_out[i].caps.mps = mps; } config->ep_cfg_out[i].addr = USB_EP_DIR_OUT | i; err = udc_register_ep(dev, &config->ep_cfg_out[i]); if (err != 0) { LOG_ERR("Failed to register endpoint"); return err; } } for (int i = 0; i < config->num_of_eps; i++) { config->ep_cfg_in[i].caps.in = 1; if (i == 0) { config->ep_cfg_in[i].caps.control = 1; config->ep_cfg_in[i].caps.mps = 64; } else { config->ep_cfg_in[i].caps.bulk = 1; config->ep_cfg_in[i].caps.interrupt = 1; config->ep_cfg_in[i].caps.iso = 1; config->ep_cfg_in[i].caps.mps = mps; } config->ep_cfg_in[i].addr = USB_EP_DIR_IN | i; err = udc_register_ep(dev, &config->ep_cfg_in[i]); if (err != 0) { LOG_ERR("Failed to register endpoint"); return err; } } config->make_thread(dev); LOG_INF("Device %p (max. speed %d)", dev, config->speed_idx); return 0; } static int udc_skeleton_lock(const struct device *dev) { return udc_lock_internal(dev, K_FOREVER); } static int udc_skeleton_unlock(const struct device *dev) { return udc_unlock_internal(dev); } /* * UDC API structure. * Note, you do not need to implement basic checks, these are done by * the UDC common layer udc_common.c */ static const struct udc_api udc_skeleton_api = { .lock = udc_skeleton_lock, .unlock = udc_skeleton_unlock, .device_speed = udc_skeleton_device_speed, .init = udc_skeleton_init, .enable = udc_skeleton_enable, .disable = udc_skeleton_disable, .shutdown = udc_skeleton_shutdown, .set_address = udc_skeleton_set_address, .host_wakeup = udc_skeleton_host_wakeup, .ep_enable = udc_skeleton_ep_enable, .ep_disable = udc_skeleton_ep_disable, .ep_set_halt = udc_skeleton_ep_set_halt, .ep_clear_halt = udc_skeleton_ep_clear_halt, .ep_enqueue = udc_skeleton_ep_enqueue, .ep_dequeue = udc_skeleton_ep_dequeue, }; #define DT_DRV_COMPAT zephyr_udc_skeleton /* * A UDC driver should always be implemented as a multi-instance * driver, even if your platform does not require it. */ #define UDC_SKELETON_DEVICE_DEFINE(n) \ K_THREAD_STACK_DEFINE(udc_skeleton_stack_##n, CONFIG_UDC_SKELETON); \ \ static void udc_skeleton_thread_##n(void *dev, void *arg1, void *arg2) \ { \ skeleton_thread_handler(dev); \ } \ \ static void udc_skeleton_make_thread_##n(const struct device *dev) \ { \ struct udc_skeleton_data *priv = udc_get_private(dev); \ \ k_thread_create(&priv->thread_data, \ udc_skeleton_stack_##n, \ K_THREAD_STACK_SIZEOF(udc_skeleton_stack_##n), \ udc_skeleton_thread_##n, \ (void *)dev, NULL, NULL, \ K_PRIO_COOP(CONFIG_UDC_SKELETON_THREAD_PRIORITY),\ K_ESSENTIAL, \ K_NO_WAIT); \ k_thread_name_set(&priv->thread_data, dev->name); \ } \ \ static struct udc_ep_config \ ep_cfg_out[DT_INST_PROP(n, num_bidir_endpoints)]; \ static struct udc_ep_config \ ep_cfg_in[DT_INST_PROP(n, num_bidir_endpoints)]; \ \ static const struct udc_skeleton_config udc_skeleton_config_##n = { \ .num_of_eps = DT_INST_PROP(n, num_bidir_endpoints), \ .ep_cfg_in = ep_cfg_out, \ .ep_cfg_out = ep_cfg_in, \ .make_thread = udc_skeleton_make_thread_##n, \ .speed_idx = DT_ENUM_IDX(DT_DRV_INST(n), maximum_speed), \ }; \ \ static struct udc_skeleton_data udc_priv_##n = { \ }; \ \ static struct udc_data udc_data_##n = { \ .mutex = Z_MUTEX_INITIALIZER(udc_data_##n.mutex), \ .priv = &udc_priv_##n, \ }; \ \ DEVICE_DT_INST_DEFINE(n, udc_skeleton_driver_preinit, NULL, \ &udc_data_##n, &udc_skeleton_config_##n, \ POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \ &udc_skeleton_api); DT_INST_FOREACH_STATUS_OKAY(UDC_SKELETON_DEVICE_DEFINE)