5144d0f65f
USB High-Speed devices must be able to operate at both High-Speed and Full-Speed. The USB specification allows the device to have different configurations depending on connection speed. Modify the API to reflect USB Specification requirements on what can (e.g. configurations) and what cannot (e.g. VID, PID) be speed dependent. While the class configurations for different speeds are completely independent, the actual class instances are shared between operating speeds (because only one speed can be active at a time). Classes are free to provide different number of interfaces and/or endpoints for different speeds. The endpoints are assigned for all operating speeds during initialization. Signed-off-by: Tomasz Moń <tomasz.mon@nordicsemi.no>
299 lines
5.8 KiB
C
299 lines
5.8 KiB
C
/*
|
|
* Copyright (c) 2022 Nordic Semiconductor ASA
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/drivers/usb/udc.h>
|
|
#include <zephyr/usb/usbd.h>
|
|
|
|
#include "usbd_device.h"
|
|
#include "usbd_config.h"
|
|
#include "usbd_class.h"
|
|
#include "usbd_ch9.h"
|
|
#include "usbd_desc.h"
|
|
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(usbd_dev, CONFIG_USBD_LOG_LEVEL);
|
|
|
|
/*
|
|
* All the functions below are part of public USB device support API.
|
|
*/
|
|
|
|
enum usbd_speed usbd_bus_speed(const struct usbd_contex *const uds_ctx)
|
|
{
|
|
return uds_ctx->status.speed;
|
|
}
|
|
|
|
enum usbd_speed usbd_caps_speed(const struct usbd_contex *const uds_ctx)
|
|
{
|
|
struct udc_device_caps caps = udc_caps(uds_ctx->dev);
|
|
|
|
/* For now, either high speed is supported or not. */
|
|
if (caps.hs) {
|
|
return USBD_SPEED_HS;
|
|
}
|
|
|
|
return USBD_SPEED_FS;
|
|
}
|
|
|
|
static struct usb_device_descriptor *
|
|
get_device_descriptor(struct usbd_contex *const uds_ctx,
|
|
const enum usbd_speed speed)
|
|
{
|
|
switch (speed) {
|
|
case USBD_SPEED_FS:
|
|
return uds_ctx->fs_desc;
|
|
case USBD_SPEED_HS:
|
|
return uds_ctx->hs_desc;
|
|
default:
|
|
__ASSERT(false, "Not supported speed");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
int usbd_device_set_bcd(struct usbd_contex *const uds_ctx,
|
|
const enum usbd_speed speed, const uint16_t bcd)
|
|
{
|
|
struct usb_device_descriptor *desc;
|
|
int ret = 0;
|
|
|
|
usbd_device_lock(uds_ctx);
|
|
|
|
if (usbd_is_enabled(uds_ctx)) {
|
|
ret = -EALREADY;
|
|
goto set_bcd_exit;
|
|
}
|
|
|
|
desc = get_device_descriptor(uds_ctx, speed);
|
|
desc->bcdUSB = sys_cpu_to_le16(bcd);
|
|
|
|
set_bcd_exit:
|
|
usbd_device_unlock(uds_ctx);
|
|
return ret;
|
|
}
|
|
|
|
int usbd_device_set_vid(struct usbd_contex *const uds_ctx,
|
|
const uint16_t vid)
|
|
{
|
|
struct usb_device_descriptor *fs_desc, *hs_desc;
|
|
int ret = 0;
|
|
|
|
usbd_device_lock(uds_ctx);
|
|
|
|
if (usbd_is_enabled(uds_ctx)) {
|
|
ret = -EALREADY;
|
|
goto set_vid_exit;
|
|
}
|
|
|
|
fs_desc = get_device_descriptor(uds_ctx, USBD_SPEED_FS);
|
|
fs_desc->idVendor = sys_cpu_to_le16(vid);
|
|
|
|
hs_desc = get_device_descriptor(uds_ctx, USBD_SPEED_HS);
|
|
hs_desc->idVendor = sys_cpu_to_le16(vid);
|
|
|
|
set_vid_exit:
|
|
usbd_device_unlock(uds_ctx);
|
|
return ret;
|
|
}
|
|
|
|
int usbd_device_set_pid(struct usbd_contex *const uds_ctx,
|
|
const uint16_t pid)
|
|
{
|
|
struct usb_device_descriptor *fs_desc, *hs_desc;
|
|
int ret = 0;
|
|
|
|
usbd_device_lock(uds_ctx);
|
|
|
|
if (usbd_is_enabled(uds_ctx)) {
|
|
ret = -EALREADY;
|
|
goto set_pid_exit;
|
|
}
|
|
|
|
fs_desc = get_device_descriptor(uds_ctx, USBD_SPEED_FS);
|
|
fs_desc->idProduct = sys_cpu_to_le16(pid);
|
|
|
|
hs_desc = get_device_descriptor(uds_ctx, USBD_SPEED_HS);
|
|
hs_desc->idProduct = sys_cpu_to_le16(pid);
|
|
|
|
set_pid_exit:
|
|
usbd_device_unlock(uds_ctx);
|
|
return ret;
|
|
}
|
|
|
|
int usbd_device_set_code_triple(struct usbd_contex *const uds_ctx,
|
|
const enum usbd_speed speed,
|
|
const uint8_t base_class,
|
|
const uint8_t subclass, const uint8_t protocol)
|
|
{
|
|
struct usb_device_descriptor *desc;
|
|
int ret = 0;
|
|
|
|
usbd_device_lock(uds_ctx);
|
|
|
|
if (usbd_is_enabled(uds_ctx)) {
|
|
ret = -EALREADY;
|
|
goto set_code_triple_exit;
|
|
}
|
|
|
|
desc = get_device_descriptor(uds_ctx, speed);
|
|
desc->bDeviceClass = base_class;
|
|
desc->bDeviceSubClass = subclass;
|
|
desc->bDeviceProtocol = protocol;
|
|
|
|
set_code_triple_exit:
|
|
usbd_device_unlock(uds_ctx);
|
|
return ret;
|
|
}
|
|
|
|
int usbd_wakeup_request(struct usbd_contex *const uds_ctx)
|
|
{
|
|
struct udc_device_caps caps = udc_caps(uds_ctx->dev);
|
|
int ret = 0;
|
|
|
|
usbd_device_lock(uds_ctx);
|
|
|
|
if (!caps.rwup) {
|
|
LOG_ERR("Remote wakeup feature not supported");
|
|
ret = -ENOTSUP;
|
|
goto wakeup_request_error;
|
|
}
|
|
|
|
if (!uds_ctx->status.rwup || !usbd_is_suspended(uds_ctx)) {
|
|
LOG_ERR("Remote wakeup feature not enabled or not suspended");
|
|
ret = -EACCES;
|
|
goto wakeup_request_error;
|
|
}
|
|
|
|
ret = udc_host_wakeup(uds_ctx->dev);
|
|
|
|
wakeup_request_error:
|
|
usbd_device_unlock(uds_ctx);
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool usbd_is_suspended(struct usbd_contex *uds_ctx)
|
|
{
|
|
return uds_ctx->status.suspended;
|
|
}
|
|
|
|
int usbd_init(struct usbd_contex *const uds_ctx)
|
|
{
|
|
int ret;
|
|
|
|
usbd_device_lock(uds_ctx);
|
|
|
|
if (uds_ctx->dev == NULL) {
|
|
ret = -ENODEV;
|
|
goto init_exit;
|
|
}
|
|
|
|
if (usbd_is_initialized(uds_ctx)) {
|
|
LOG_WRN("USB device support is already initialized");
|
|
ret = -EALREADY;
|
|
goto init_exit;
|
|
}
|
|
|
|
if (!device_is_ready(uds_ctx->dev)) {
|
|
LOG_ERR("USB device controller is not ready");
|
|
ret = -ENODEV;
|
|
goto init_exit;
|
|
}
|
|
|
|
ret = usbd_device_init_core(uds_ctx);
|
|
if (ret) {
|
|
goto init_exit;
|
|
}
|
|
|
|
memset(&uds_ctx->ch9_data, 0, sizeof(struct usbd_ch9_data));
|
|
uds_ctx->status.initialized = true;
|
|
|
|
init_exit:
|
|
usbd_device_unlock(uds_ctx);
|
|
return ret;
|
|
}
|
|
|
|
int usbd_enable(struct usbd_contex *const uds_ctx)
|
|
{
|
|
int ret;
|
|
|
|
usbd_device_lock(uds_ctx);
|
|
|
|
if (!usbd_is_initialized(uds_ctx)) {
|
|
LOG_WRN("USB device support is not initialized");
|
|
ret = -EPERM;
|
|
goto enable_exit;
|
|
}
|
|
|
|
if (usbd_is_enabled(uds_ctx)) {
|
|
LOG_WRN("USB device support is already enabled");
|
|
ret = -EALREADY;
|
|
goto enable_exit;
|
|
}
|
|
|
|
ret = udc_enable(uds_ctx->dev);
|
|
if (ret != 0) {
|
|
LOG_ERR("Failed to enable controller");
|
|
goto enable_exit;
|
|
}
|
|
|
|
ret = usbd_init_control_pipe(uds_ctx);
|
|
if (ret != 0) {
|
|
udc_disable(uds_ctx->dev);
|
|
goto enable_exit;
|
|
}
|
|
|
|
uds_ctx->status.enabled = true;
|
|
|
|
enable_exit:
|
|
usbd_device_unlock(uds_ctx);
|
|
return ret;
|
|
}
|
|
|
|
int usbd_disable(struct usbd_contex *const uds_ctx)
|
|
{
|
|
int ret;
|
|
|
|
if (!usbd_is_enabled(uds_ctx)) {
|
|
LOG_WRN("USB device support is already disabled");
|
|
return -EALREADY;
|
|
}
|
|
|
|
usbd_device_lock(uds_ctx);
|
|
|
|
ret = usbd_config_set(uds_ctx, 0);
|
|
if (ret) {
|
|
LOG_ERR("Failed to reset configuration");
|
|
}
|
|
|
|
ret = udc_disable(uds_ctx->dev);
|
|
if (ret) {
|
|
LOG_ERR("Failed to disable USB device");
|
|
}
|
|
|
|
uds_ctx->status.enabled = false;
|
|
|
|
usbd_device_unlock(uds_ctx);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int usbd_shutdown(struct usbd_contex *const uds_ctx)
|
|
{
|
|
int ret;
|
|
|
|
usbd_device_lock(uds_ctx);
|
|
|
|
/* TODO: control request dequeue ? */
|
|
ret = usbd_device_shutdown_core(uds_ctx);
|
|
if (ret) {
|
|
LOG_ERR("Failed to shutdown USB device");
|
|
}
|
|
|
|
uds_ctx->status.initialized = false;
|
|
usbd_device_unlock(uds_ctx);
|
|
|
|
return 0;
|
|
}
|