blob: 3de0689de9e273fc778d467e0fb8d72d0e18deb7 [file] [log] [blame]
/*
* Copyright (c) 2021-2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief New USB device controller (UDC) driver API
*/
#ifndef ZEPHYR_INCLUDE_UDC_H
#define ZEPHYR_INCLUDE_UDC_H
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/net/buf.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/usb/usb_ch9.h>
/**
* USB device controller capabilities
*
* This structure is mainly intended for the USB device stack.
*/
struct udc_device_caps {
/** USB high speed capable controller */
uint32_t hs : 1;
/** Controller supports USB remote wakeup */
uint32_t rwup : 1;
/** Controller performs status OUT stage automatically */
uint32_t out_ack : 1;
};
/**
* @brief USB device actual speed
*/
enum udc_bus_speed {
/** Device is probably not connected */
UDC_BUS_UNKNOWN,
/** Device is connected to a full speed bus */
UDC_BUS_SPEED_FS,
/** Device is connected to a high speed bus */
UDC_BUS_SPEED_HS,
/** Device is connected to a super speed bus */
UDC_BUS_SPEED_SS,
};
/**
* USB device controller endpoint capabilities
*/
struct udc_ep_caps {
/** Maximum packet size of the endpoint buffer */
uint32_t mps : 16;
/** Control transfer capable endpoint (for completeness) */
uint32_t control : 1;
/** Interrupt transfer capable endpoint */
uint32_t interrupt : 1;
/** Bulk transfer capable endpoint */
uint32_t bulk : 1;
/** ISO transfer capable endpoint */
uint32_t iso : 1;
/** IN transfer capable endpoint */
uint32_t in : 1;
/** OUT transfer capable endpoint */
uint32_t out : 1;
};
/**
* USB device controller endpoint status
*/
struct udc_ep_stat {
/** Endpoint is enabled */
uint32_t enabled : 1;
/** Endpoint is halted (returning STALL PID) */
uint32_t halted : 1;
/** Last submitted PID is DATA1 */
uint32_t data1 : 1;
/** If double buffering is supported, last used buffer is odd */
uint32_t odd : 1;
/** Endpoint is busy */
uint32_t busy : 1;
};
/**
* USB device controller endpoint configuration
*
* This structure is mandatory for configuration and management of endpoints.
* It is not exposed to higher layer and is used only by internal part
* of UDC API and driver.
*/
struct udc_ep_config {
/** Endpoint requests FIFO */
struct k_fifo fifo;
/** Endpoint capabilities */
struct udc_ep_caps caps;
/** Endpoint status */
struct udc_ep_stat stat;
/** Endpoint address */
uint8_t addr;
/** Endpoint attributes */
uint8_t attributes;
/** Maximum packet size */
uint16_t mps;
/** Polling interval */
uint8_t interval;
};
/**
* @brief USB device controller event types
*/
enum udc_event_type {
/** VBUS ready event. Signals that VBUS is in stable condition. */
UDC_EVT_VBUS_READY,
/** VBUS removed event. Signals that VBUS is below the valid range. */
UDC_EVT_VBUS_REMOVED,
/** Device resume event */
UDC_EVT_RESUME,
/** Device suspended event */
UDC_EVT_SUSPEND,
/** Port reset detected */
UDC_EVT_RESET,
/** Start of Frame event */
UDC_EVT_SOF,
/** Endpoint request result event */
UDC_EVT_EP_REQUEST,
/**
* Non-correctable error event, requires attention from higher
* levels or application.
*/
UDC_EVT_ERROR,
};
/**
* USB device controller event
*
* Common structure for all events that originate from
* the UDC driver and are passed to higher layer using
* message queue and a callback (udc_event_cb_t) provided
* by higher layer during controller initialization (udc_init).
*/
struct udc_event {
/** Event type */
enum udc_event_type type;
union {
/** Event value */
uint32_t value;
/** Pointer to request used only for UDC_EVT_EP_REQUEST */
struct net_buf *buf;
};
/** Event status, 0 on success, other values on error */
int status;
/** Pointer to device struct */
const struct device *dev;
};
/**
* UDC endpoint buffer info
*
* This structure is mandatory for all UDC request.
* It contains the meta data about the request and is stored in
* user_data array of net_buf structure for each request.
*/
struct udc_buf_info {
/** Endpoint to which request is associated */
uint8_t ep;
/** Flag marks setup transfer */
unsigned int setup : 1;
/** Flag marks data stage of setup transfer */
unsigned int data : 1;
/** Flag marks status stage of setup transfer */
unsigned int status : 1;
/** Flag marks ZLP at the end of a transfer */
unsigned int zlp : 1;
/** Flag marks request buffer claimed by the controller (TBD) */
unsigned int claimed : 1;
/** Flag marks request buffer is queued (TBD) */
unsigned int queued : 1;
/** Transfer owner (usually pointer to a class instance) */
void *owner;
} __packed;
/**
* @typedef udc_event_cb_t
* @brief Callback to submit UDC event to higher layer.
*
* At the higher level, the event is to be inserted into a message queue.
* (TBD) Maybe it is better to provide a pointer to k_msgq passed during
* initialization.
*
* @param[in] dev Pointer to device struct of the driver instance
* @param[in] event Point to event structure
*
* @return 0 on success, all other values should be treated as error.
*/
typedef int (*udc_event_cb_t)(const struct device *dev,
const struct udc_event *const event);
/**
* @brief UDC driver API
* This is the mandatory API any USB device controller driver needs to expose
* with exception of:
* device_speed() used by udc_device_speed(), not required for FS only devices
*/
struct udc_api {
enum udc_bus_speed (*device_speed)(const struct device *dev);
int (*ep_enqueue)(const struct device *dev,
struct udc_ep_config *const cfg,
struct net_buf *const buf);
int (*ep_dequeue)(const struct device *dev,
struct udc_ep_config *const cfg);
int (*ep_flush)(const struct device *dev,
struct udc_ep_config *const cfg);
int (*ep_set_halt)(const struct device *dev,
struct udc_ep_config *const cfg);
int (*ep_clear_halt)(const struct device *dev,
struct udc_ep_config *const cfg);
int (*ep_try_config)(const struct device *dev,
struct udc_ep_config *const cfg);
int (*ep_enable)(const struct device *dev,
struct udc_ep_config *const cfg);
int (*ep_disable)(const struct device *dev,
struct udc_ep_config *const cfg);
int (*host_wakeup)(const struct device *dev);
int (*set_address)(const struct device *dev,
const uint8_t addr);
int (*enable)(const struct device *dev);
int (*disable)(const struct device *dev);
int (*init)(const struct device *dev);
int (*shutdown)(const struct device *dev);
int (*lock)(const struct device *dev);
int (*unlock)(const struct device *dev);
};
/**
* Controller is initialized by udc_init() and can generate the VBUS events,
* if capable, but shall not be recognizable by host.
*/
#define UDC_STATUS_INITIALIZED 0
/**
* Controller is enabled and all API functions are available,
* controller is recognizable by host.
*/
#define UDC_STATUS_ENABLED 1
/** Controller is suspended by the host */
#define UDC_STATUS_SUSPENDED 2
/**
* Common UDC driver data structure
*
* Mandatory structure for each UDC controller driver.
* To be implemented as device's private data (device->data).
*/
struct udc_data {
/** LUT for endpoint management */
struct udc_ep_config *ep_lut[32];
/** Controller capabilities */
struct udc_device_caps caps;
/** Driver access mutex */
struct k_mutex mutex;
/** Callback to submit an UDC event to upper layer */
udc_event_cb_t event_cb;
/** USB device controller status */
atomic_t status;
/** Internal used Control Sequence Stage */
int stage;
/** Pointer to buffer containing setup packet */
struct net_buf *setup;
/** Driver private data */
void *priv;
};
/**
* @brief New USB device controller (UDC) driver API
* @defgroup udc_api USB device controller driver API
* @ingroup io_interfaces
* @{
*/
/**
* @brief Checks whether the controller is initialized.
*
* @param[in] dev Pointer to device struct of the driver instance
*
* @return true if controller is initialized, false otherwise
*/
static inline bool udc_is_initialized(const struct device *dev)
{
struct udc_data *data = dev->data;
return atomic_test_bit(&data->status, UDC_STATUS_INITIALIZED);
}
/**
* @brief Checks whether the controller is enabled.
*
* @param[in] dev Pointer to device struct of the driver instance
*
* @return true if controller is enabled, false otherwise
*/
static inline bool udc_is_enabled(const struct device *dev)
{
struct udc_data *data = dev->data;
return atomic_test_bit(&data->status, UDC_STATUS_ENABLED);
}
/**
* @brief Checks whether the controller is suspended.
*
* @param[in] dev Pointer to device struct of the driver instance
*
* @return true if controller is suspended, false otherwise
*/
static inline bool udc_is_suspended(const struct device *dev)
{
struct udc_data *data = dev->data;
return atomic_test_bit(&data->status, UDC_STATUS_SUSPENDED);
}
/**
* @brief Initialize USB device controller
*
* Initialize USB device controller and control IN/OUT endpoint.
* After initialization controller driver should be able to detect
* power state of the bus and signal power state changes.
*
* @param[in] dev Pointer to device struct of the driver instance
* @param[in] event_cb Event callback from the higher layer (USB device stack)
*
* @return 0 on success, all other values should be treated as error.
* @retval -EINVAL on parameter error (no callback is passed)
* @retval -EALREADY already initialized
*/
int udc_init(const struct device *dev, udc_event_cb_t event_cb);
/**
* @brief Enable USB device controller
*
* Enable powered USB device controller and allow host to
* recognize and enumerate the device.
*
* @param[in] dev Pointer to device struct of the driver instance
*
* @return 0 on success, all other values should be treated as error.
* @retval -EPERM controller is not initialized
* @retval -EALREADY already enabled
*/
int udc_enable(const struct device *dev);
/**
* @brief Disable USB device controller
*
* Disable enabled USB device controller.
* The driver should continue to detect power state changes.
*
* @param[in] dev Pointer to device struct of the driver instance
*
* @return 0 on success, all other values should be treated as error.
* @retval -EALREADY already disabled
*/
int udc_disable(const struct device *dev);
/**
* @brief Poweroff USB device controller
*
* Shut down the controller completely to reduce energy consumption
* or to change the role of the controller.
*
* @param[in] dev Pointer to device struct of the driver instance
*
* @return 0 on success, all other values should be treated as error.
* @retval -EALREADY controller is not initialized
*/
int udc_shutdown(const struct device *dev);
/**
* @brief Get USB device controller capabilities
*
* Obtain the capabilities of the controller
* such as full speed (FS), high speed (HS), and more.
*
* @param[in] dev Pointer to device struct of the driver instance
*
* @return USB device controller capabilities.
*/
static inline struct udc_device_caps udc_caps(const struct device *dev)
{
struct udc_data *data = dev->data;
return data->caps;
}
/**
* @brief Get actual USB device speed
*
* The function should be called after the reset event to determine
* the actual bus speed.
*
* @param[in] dev Pointer to device struct of the driver instance
*
* @return USB device controller capabilities.
*/
enum udc_bus_speed udc_device_speed(const struct device *dev);
/**
* @brief Set USB device address.
*
* Set address of enabled USB device.
*
* @param[in] dev Pointer to device struct of the driver instance
* @param[in] addr USB device address
*
* @return 0 on success, all other values should be treated as error.
* @retval -EPERM controller is not enabled (or not initialized)
*/
static inline int udc_set_address(const struct device *dev, const uint8_t addr)
{
const struct udc_api *api = dev->api;
int ret;
if (!udc_is_enabled(dev)) {
return -EPERM;
}
api->lock(dev);
ret = api->set_address(dev, addr);
api->unlock(dev);
return ret;
}
/**
* @brief Initiate host wakeup procedure.
*
* Initiate host wakeup. Only possible when the bus is suspended.
*
* @param[in] dev Pointer to device struct of the driver instance
*
* @return 0 on success, all other values should be treated as error.
* @retval -EPERM controller is not enabled (or not initialized)
*/
static inline int udc_host_wakeup(const struct device *dev)
{
const struct udc_api *api = dev->api;
int ret;
if (!udc_is_enabled(dev)) {
return -EPERM;
}
api->lock(dev);
ret = api->host_wakeup(dev);
api->unlock(dev);
return ret;
}
/**
* @brief Try an endpoint configuration.
*
* Try an endpoint configuration based on endpoint descriptor.
* This function may modify wMaxPacketSize descriptor fields
* of the endpoint. All properties of the descriptor,
* such as direction, and transfer type, should be set correctly.
* If wMaxPacketSize value is zero, it will be
* updated to maximum buffer size of the enpoint.
*
* @param[in] dev Pointer to device struct of the driver instance
* @param[in] ep Endpoint address (same as bEndpointAddress)
* @param[in] attributes Endpoint attributes (same as bmAttributes)
* @param[in] mps Maximum packet size (same as wMaxPacketSize)
* @param[in] interval Polling interval (same as bInterval)
*
* @return 0 on success, all other values should be treated as error.
* @retval -EINVAL on wrong parameter
* @retval -ENOTSUP endpoint configuration not supported
* @retval -ENODEV no endpoints available
*/
int udc_ep_try_config(const struct device *dev,
const uint8_t ep,
const uint8_t attributes,
uint16_t *const mps,
const uint8_t interval);
/**
* @brief Configure and enable endpoint.
*
* Configure and make an endpoint ready for use.
* Valid for all endpoints except control IN/OUT.
*
* @param[in] dev Pointer to device struct of the driver instance
* @param[in] ep Endpoint address (same as bEndpointAddress)
* @param[in] attributes Endpoint attributes (same as bmAttributes)
* @param[in] mps Maximum packet size (same as wMaxPacketSize)
* @param[in] interval Polling interval (same as bInterval)
*
* @return 0 on success, all other values should be treated as error.
* @retval -EINVAL on wrong parameter (control IN/OUT endpoint)
* @retval -EPERM controller is not initialized
* @retval -ENODEV endpoint configuration not found
* @retval -EALREADY endpoint is already enabled
*/
int udc_ep_enable(const struct device *dev,
const uint8_t ep,
const uint8_t attributes,
const uint16_t mps,
const uint8_t interval);
/**
* @brief Disable endpoint.
*
* Valid for all endpoints except control IN/OUT.
*
* @param[in] dev Pointer to device struct of the driver instance
* @param[in] ep Endpoint address
*
* @return 0 on success, all other values should be treated as error.
* @retval -EINVAL on wrong parameter (control IN/OUT endpoint)
* @retval -ENODEV endpoint configuration not found
* @retval -EALREADY endpoint is already disabled
* @retval -EPERM controller is not initialized
*/
int udc_ep_disable(const struct device *dev, const uint8_t ep);
/**
* @brief Halt endpoint
*
* Valid for all endpoints.
*
* @param[in] dev Pointer to device struct of the driver instance
* @param[in] ep Endpoint address
*
* @return 0 on success, all other values should be treated as error.
* @retval -ENODEV endpoint configuration not found
* @retval -ENOTSUP not supported (e.g. isochronous endpoint)
* @retval -EPERM controller is not enabled
*/
int udc_ep_set_halt(const struct device *dev, const uint8_t ep);
/**
* @brief Clear endpoint halt
*
* Valid for all endpoints.
*
* @param[in] dev Pointer to device struct of the driver instance
* @param[in] ep Endpoint address
*
* @return 0 on success, all other values should be treated as error.
* @retval -ENODEV endpoint configuration not found
* @retval -ENOTSUP not supported (e.g. isochronous endpoint)
* @retval -EPERM controller is not enabled
*/
int udc_ep_clear_halt(const struct device *dev, const uint8_t ep);
/**
* @brief Flush endpoint buffer (TBD)
*
* @param[in] dev Pointer to device struct of the driver instance
* @param[in] ep Endpoint address
*
* @return 0 on success, all other values should be treated as error.
* @retval -ENODEV endpoint configuration not found
* @retval -EPERM controller is not initialized
*/
int udc_ep_flush(const struct device *dev, const uint8_t ep);
/**
* @brief Queue USB device controller request
*
* Add request to the queue. If the queue is empty, the request
* buffer can be claimed by the controller immediately.
*
* @param[in] dev Pointer to device struct of the driver instance
* @param[in] buf Pointer to UDC request buffer
*
* @return 0 on success, all other values should be treated as error.
* @retval -ENODEV endpoint configuration not found
* @retval -EACCES endpoint is not enabled (TBD)
* @retval -EBUSY request can not be queued
* @retval -EPERM controller is not initialized
*/
int udc_ep_enqueue(const struct device *dev, struct net_buf *const buf);
/**
* @brief Remove all USB device controller requests from endpoint queue
*
* UDC_EVT_EP_REQUEST event will be generated when the driver
* releases claimed buffer, no new requests will be claimed,
* all requests in the queue will passed as chained list of
* the event variable buf. The endpoint queue is empty after that.
*
* @param[in] dev Pointer to device struct of the driver instance
* @param[in] ep Endpoint address
*
* @return 0 on success, all other values should be treated as error.
* @retval -ENODEV endpoint configuration not found
* @retval -EACCES endpoint is not disabled
* @retval -EPERM controller is not initialized
*/
int udc_ep_dequeue(const struct device *dev, const uint8_t ep);
/**
* @brief Allocate UDC request buffer
*
* Allocate a new buffer from common request buffer pool.
*
* @param[in] dev Pointer to device struct of the driver instance
* @param[in] ep Endpoint address
* @param[in] size Size of the request buffer
*
* @return pointer to allocated request or NULL on error.
*/
struct net_buf *udc_ep_buf_alloc(const struct device *dev,
const uint8_t ep,
const size_t size);
/**
* @brief Free UDC request buffer
*
* Put the buffer back into the request buffer pool.
*
* @param[in] dev Pointer to device struct of the driver instance
* @param[in] buf Pointer to UDC request buffer
*
* @return 0 on success, all other values should be treated as error.
*/
int udc_ep_buf_free(const struct device *dev, struct net_buf *const buf);
/**
* @brief Set ZLP flag in requests metadata.
*
* The controller should send a ZLP at the end of the transfer.
*
* @param[in] buf Pointer to UDC request buffer
*/
static inline void udc_ep_buf_set_zlp(struct net_buf *const buf)
{
struct udc_buf_info *bi;
__ASSERT_NO_MSG(buf);
bi = (struct udc_buf_info *)net_buf_user_data(buf);
if (USB_EP_DIR_IS_IN(bi->ep)) {
bi->zlp = 1;
}
}
/**
* @brief Get requests metadata.
*
* @param[in] buf Pointer to UDC request buffer
*
* @return pointer to metadata structure.
*/
static inline struct udc_buf_info *udc_get_buf_info(const struct net_buf *const buf)
{
__ASSERT_NO_MSG(buf);
return (struct udc_buf_info *)net_buf_user_data(buf);
}
/**
* @}
*/
#endif /* ZEPHYR_INCLUDE_UDC_H */