718 lines
23 KiB
C
718 lines
23 KiB
C
/*
|
|
* Copyright (c) 2021-2024 HPMicro
|
|
*
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*
|
|
*/
|
|
/*
|
|
* Copyright (c) 2024, sakumisu
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
#include "usbd_core.h"
|
|
#include "usb_chipidea_reg.h"
|
|
|
|
#define USB_OTG_DEV ((CHIPIDEA_TypeDef *)g_usbdev_bus[busid].reg_base)
|
|
|
|
#define CHIPIDEA_BITSMASK(val, offset) ((uint32_t)(val) << (offset))
|
|
#define QTD_COUNT_EACH_ENDPOINT (8U)
|
|
|
|
/* ENDPTCTRL */
|
|
enum {
|
|
ENDPTCTRL_STALL = CHIPIDEA_BITSMASK(1, 0),
|
|
ENDPTCTRL_TYPE = CHIPIDEA_BITSMASK(3, 2),
|
|
ENDPTCTRL_TOGGLE_INHIBIT = CHIPIDEA_BITSMASK(1, 5),
|
|
ENDPTCTRL_TOGGLE_RESET = CHIPIDEA_BITSMASK(1, 6),
|
|
ENDPTCTRL_ENABLE = CHIPIDEA_BITSMASK(1, 7),
|
|
};
|
|
|
|
/* USBSTS, USBINTR */
|
|
enum {
|
|
intr_usb = CHIPIDEA_BITSMASK(1, 0),
|
|
intr_error = CHIPIDEA_BITSMASK(1, 1),
|
|
intr_port_change = CHIPIDEA_BITSMASK(1, 2),
|
|
intr_reset = CHIPIDEA_BITSMASK(1, 6),
|
|
intr_sof = CHIPIDEA_BITSMASK(1, 7),
|
|
intr_suspend = CHIPIDEA_BITSMASK(1, 8),
|
|
intr_nak = CHIPIDEA_BITSMASK(1, 16)
|
|
};
|
|
|
|
/* Queue Transfer Descriptor */
|
|
typedef struct {
|
|
/* Word 0: Next QTD Pointer */
|
|
volatile uint32_t next; /* Next link pointer This field contains the physical memory address of the next dTD to be processed */
|
|
|
|
/* Word 1: qTQ Token */
|
|
volatile uint32_t : 3;
|
|
volatile uint32_t xact_err : 1;
|
|
volatile uint32_t : 1;
|
|
volatile uint32_t buffer_err : 1;
|
|
volatile uint32_t halted : 1;
|
|
volatile uint32_t active : 1;
|
|
volatile uint32_t : 2;
|
|
volatile uint32_t iso_mult_override : 2; /* This field can be used for transmit ISOs to override the MULT field in the dQH. This field must be zero for all packet types that are not transmit-ISO. */
|
|
volatile uint32_t : 3;
|
|
volatile uint32_t int_on_complete : 1;
|
|
volatile uint32_t total_bytes : 15;
|
|
volatile uint32_t : 0;
|
|
|
|
/* Word 2-6: Buffer Page Pointer List, Each element in the list is a 4K page aligned, physical memory address. The lower 12 bits in each pointer are reserved (except for the first one) as each memory pointer must reference the start of a 4K page */
|
|
volatile uint32_t buffer[5];
|
|
|
|
/*------------- DCD Area -------------*/
|
|
volatile uint16_t expected_bytes;
|
|
volatile uint8_t reserved[2];
|
|
} dcd_qtd_t;
|
|
|
|
/* Queue Head */
|
|
typedef struct {
|
|
/* Word 0: Capabilities and Characteristics */
|
|
|
|
volatile uint32_t : 15; /* Number of packets executed per transaction descriptor 00 - Execute N transactions as demonstrated by the USB variable length protocol where N is computed using Max_packet_length and the Total_bytes field in the dTD. 01 - Execute one transaction 10 - Execute two transactions 11 - Execute three transactions Remark: Non-isochronous endpoints must set MULT = 00. Remark: Isochronous endpoints must set MULT = 01, 10, or 11 as needed. */
|
|
volatile uint32_t int_on_setup : 1; /* Interrupt on setup This bit is used on control type endpoints to indicate if USBINT is set in response to a setup being received. */
|
|
volatile uint32_t max_packet_size : 11; /* This directly corresponds to the maximum packet size of the associated endpoint (wMaxPacketSize) */
|
|
volatile uint32_t : 2;
|
|
volatile uint32_t zero_length_termination : 1; /* This bit is used for non-isochronous endpoints to indicate when a zero-length packet is received to terminate transfers in case the total transfer length is “multiple”. 0 - Enable zero-length packet to terminate transfers equal to a multiple of Max_packet_length (default). 1 - Disable zero-length packet on transfers that are equal in length to a multiple Max_packet_length. */
|
|
volatile uint32_t iso_mult : 2;
|
|
volatile uint32_t : 0;
|
|
|
|
/* Word 1: Current qTD Pointer */
|
|
volatile uint32_t qtd_addr;
|
|
|
|
/* Word 2-9: Transfer Overlay */
|
|
volatile dcd_qtd_t qtd_overlay;
|
|
|
|
/* Word 10-11: Setup request (control OUT only) */
|
|
volatile struct usb_setup_packet setup_request;
|
|
|
|
/*--------------------------------------------------------------------
|
|
* Due to the fact QHD is 64 bytes aligned but occupies only 48 bytes
|
|
* thus there are 16 bytes padding free that we can make use of.
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
volatile uint8_t reserved[16];
|
|
} dcd_qhd_t;
|
|
|
|
typedef struct {
|
|
dcd_qhd_t qhd[CONFIG_USBDEV_EP_NUM * 2];
|
|
dcd_qtd_t qtd[CONFIG_USBDEV_EP_NUM * 2 * QTD_COUNT_EACH_ENDPOINT];
|
|
} dcd_data_t;
|
|
|
|
/* Endpoint state */
|
|
struct chipidea_ep_state {
|
|
uint16_t ep_mps; /* Endpoint max packet size */
|
|
uint8_t ep_type; /* Endpoint type */
|
|
uint8_t ep_stalled; /* Endpoint stall flag */
|
|
uint8_t ep_enable; /* Endpoint enable */
|
|
uint8_t *xfer_buf;
|
|
uint32_t xfer_len;
|
|
uint32_t actual_xfer_len;
|
|
};
|
|
|
|
/* Driver state */
|
|
struct chipidea_udc {
|
|
dcd_data_t *dcd_data;
|
|
bool is_suspend;
|
|
struct chipidea_ep_state in_ep[CONFIG_USBDEV_EP_NUM]; /*!< IN endpoint parameters*/
|
|
struct chipidea_ep_state out_ep[CONFIG_USBDEV_EP_NUM]; /*!< OUT endpoint parameters */
|
|
} g_chipidea_udc[CONFIG_USBDEV_MAX_BUS];
|
|
|
|
static USB_NOCACHE_RAM_SECTION __attribute__((aligned(2048))) dcd_data_t _dcd_data0;
|
|
#if CONFIG_USBDEV_MAX_BUS == 2
|
|
static USB_NOCACHE_RAM_SECTION __attribute__((aligned(2048))) dcd_data_t _dcd_data1;
|
|
#endif
|
|
|
|
static dcd_data_t *g_dcd_data[CONFIG_USBDEV_MAX_BUS] = {
|
|
&_dcd_data0,
|
|
#if CONFIG_USBDEV_MAX_BUS == 2
|
|
&_dcd_data1
|
|
#endif
|
|
};
|
|
|
|
/* Index to bit position in register */
|
|
static inline uint8_t ep_idx2bit(uint8_t ep_idx)
|
|
{
|
|
return ep_idx / 2 + ((ep_idx % 2) ? 16 : 0);
|
|
}
|
|
|
|
static void __chipidea_bus_reset(CHIPIDEA_TypeDef *ptr)
|
|
{
|
|
/* The reset value for all endpoint types is the control endpoint. If one endpoint
|
|
* direction is enabled and the paired endpoint of opposite direction is disabled, then the
|
|
* endpoint type of the unused direction must be changed from the control type to any other
|
|
* type (e.g. bulk). Leaving an un-configured endpoint control will cause undefined behavior
|
|
* for the data PID tracking on the active endpoint.
|
|
*/
|
|
|
|
for (uint32_t i = 1; i < CONFIG_USBDEV_EP_NUM; i++) {
|
|
ptr->ENDPTCTRL[i] = USB_ENDPTCTRL_TXT_SET(USB_ENDPOINT_TYPE_BULK) | USB_ENDPTCTRL_RXT_SET(USB_ENDPOINT_TYPE_BULK);
|
|
}
|
|
|
|
/* Clear All Registers */
|
|
ptr->ENDPTNAK = ptr->ENDPTNAK;
|
|
ptr->ENDPTNAKEN = 0;
|
|
ptr->USBSTS = ptr->USBSTS;
|
|
ptr->ENDPTSETUPSTAT = ptr->ENDPTSETUPSTAT;
|
|
ptr->ENDPTCOMPLETE = ptr->ENDPTCOMPLETE;
|
|
|
|
while (ptr->ENDPTPRIME) {
|
|
}
|
|
ptr->ENDPTFLUSH = 0xFFFFFFFF;
|
|
while (ptr->ENDPTFLUSH) {
|
|
}
|
|
}
|
|
|
|
static void chipidea_init(CHIPIDEA_TypeDef *ptr)
|
|
{
|
|
/* Reset controller */
|
|
ptr->USBCMD |= USB_USBCMD_RST_MASK;
|
|
while (USB_USBCMD_RST_GET(ptr->USBCMD)) {
|
|
}
|
|
|
|
/* Set mode to device, must be set immediately after reset */
|
|
ptr->USBMODE &= ~USB_USBMODE_CM_MASK;
|
|
ptr->USBMODE |= USB_USBMODE_CM_SET(2);
|
|
|
|
/* Disable setup lockout, please refer to "Control Endpoint Operation" section in RM. */
|
|
ptr->USBMODE &= ~USB_USBMODE_SLOM_MASK;
|
|
|
|
/* Set the endian */
|
|
ptr->USBMODE &= ~USB_USBMODE_ES_MASK;
|
|
|
|
/* Set parallel interface signal */
|
|
ptr->PORTSC1 &= ~USB_PORTSC1_STS_MASK;
|
|
|
|
/* Set parallel transceiver width */
|
|
ptr->PORTSC1 &= ~USB_PORTSC1_PTW_MASK;
|
|
|
|
/* Set usb forced to full speed mode */
|
|
//ptr->PORTSC1 |= USB_PORTSC1_PFSC_MASK;
|
|
|
|
/* Not use interrupt threshold. */
|
|
ptr->USBCMD &= ~USB_USBCMD_ITC_MASK;
|
|
|
|
/* Enable VBUS discharge */
|
|
ptr->OTGSC |= USB_OTGSC_VD_MASK;
|
|
}
|
|
|
|
static void chipidea_deinit(CHIPIDEA_TypeDef *ptr)
|
|
{
|
|
/* Stop */
|
|
ptr->USBCMD &= ~USB_USBCMD_RS_MASK;
|
|
|
|
/* Reset controller */
|
|
ptr->USBCMD |= USB_USBCMD_RST_MASK;
|
|
while (USB_USBCMD_RST_GET(ptr->USBCMD)) {
|
|
}
|
|
|
|
/* Reset endpoint list address register */
|
|
ptr->ENDPTLISTADDR = 0;
|
|
|
|
/* Reset status register */
|
|
ptr->USBSTS = ptr->USBSTS;
|
|
|
|
/* Reset interrupt enable register */
|
|
ptr->USBINTR = 0;
|
|
}
|
|
|
|
/*---------------------------------------------------------------------
|
|
* Endpoint API
|
|
*---------------------------------------------------------------------
|
|
*/
|
|
static void __chipidea_edpt_open(CHIPIDEA_TypeDef *ptr, uint8_t ep_addr, uint8_t ep_type)
|
|
{
|
|
uint8_t const epnum = ep_addr & 0x0f;
|
|
uint8_t const dir = (ep_addr & 0x80) >> 7;
|
|
|
|
/* Enable EP Control */
|
|
uint32_t temp = ptr->ENDPTCTRL[epnum];
|
|
temp &= ~((0x03 << 2) << (dir ? 16 : 0));
|
|
temp |= ((ep_type << 2) | ENDPTCTRL_ENABLE | ENDPTCTRL_TOGGLE_RESET) << (dir ? 16 : 0);
|
|
ptr->ENDPTCTRL[epnum] = temp;
|
|
}
|
|
|
|
static void chipidea_edpt_xfer(CHIPIDEA_TypeDef *ptr, uint8_t ep_idx)
|
|
{
|
|
uint32_t offset = ep_idx / 2 + ((ep_idx % 2) ? 16 : 0);
|
|
|
|
/* Start transfer */
|
|
ptr->ENDPTPRIME = 1 << offset;
|
|
}
|
|
|
|
static void chipidea_edpt_stall(CHIPIDEA_TypeDef *ptr, uint8_t ep_addr)
|
|
{
|
|
uint8_t const epnum = ep_addr & 0x0f;
|
|
uint8_t const dir = (ep_addr & 0x80) >> 7;
|
|
|
|
ptr->ENDPTCTRL[epnum] |= ENDPTCTRL_STALL << (dir ? 16 : 0);
|
|
}
|
|
|
|
static void chipidea_edpt_clear_stall(CHIPIDEA_TypeDef *ptr, uint8_t ep_addr)
|
|
{
|
|
uint8_t const epnum = ep_addr & 0x0f;
|
|
uint8_t const dir = (ep_addr & 0x80) >> 7;
|
|
|
|
/* data toggle also need to be reset */
|
|
ptr->ENDPTCTRL[epnum] |= ENDPTCTRL_TOGGLE_RESET << (dir ? 16 : 0);
|
|
ptr->ENDPTCTRL[epnum] &= ~(ENDPTCTRL_STALL << (dir ? 16 : 0));
|
|
}
|
|
|
|
static bool chipidea_edpt_check_stall(CHIPIDEA_TypeDef *ptr, uint8_t ep_addr)
|
|
{
|
|
uint8_t const epnum = ep_addr & 0x0f;
|
|
uint8_t const dir = (ep_addr & 0x80) >> 7;
|
|
|
|
return (ptr->ENDPTCTRL[epnum] & (ENDPTCTRL_STALL << (dir ? 16 : 0))) ? true : false;
|
|
}
|
|
|
|
static void chipidea_edpt_close(CHIPIDEA_TypeDef *ptr, uint8_t ep_addr)
|
|
{
|
|
uint8_t const epnum = ep_addr & 0x0f;
|
|
uint8_t const dir = (ep_addr & 0x80) >> 7;
|
|
|
|
uint32_t primebit = CHIPIDEA_BITSMASK(1, epnum) << (dir ? 16 : 0);
|
|
|
|
/* Flush the endpoint to stop a transfer. */
|
|
do {
|
|
/* Set the corresponding bit(s) in the ENDPTFLUSH register */
|
|
ptr->ENDPTFLUSH |= primebit;
|
|
|
|
/* Wait until all bits in the ENDPTFLUSH register are cleared. */
|
|
while (0U != (ptr->ENDPTFLUSH & primebit)) {
|
|
}
|
|
/*
|
|
* Read the ENDPTSTAT register to ensure that for all endpoints
|
|
* commanded to be flushed, that the corresponding bits
|
|
* are now cleared.
|
|
*/
|
|
} while (0U != (ptr->ENDPTSTAT & primebit));
|
|
|
|
/* Disable the endpoint */
|
|
ptr->ENDPTCTRL[epnum] &= ~((ENDPTCTRL_TYPE | ENDPTCTRL_ENABLE | ENDPTCTRL_STALL) << (dir ? 16 : 0));
|
|
ptr->ENDPTCTRL[epnum] |= (USB_ENDPOINT_TYPE_BULK << 2) << (dir ? 16 : 0);
|
|
}
|
|
|
|
/* Initialize qtd */
|
|
static void usb_qtd_init(dcd_qtd_t *p_qtd, void *data_ptr, uint16_t total_bytes)
|
|
{
|
|
memset(p_qtd, 0, sizeof(dcd_qtd_t));
|
|
|
|
p_qtd->next = 1;
|
|
p_qtd->active = 1;
|
|
p_qtd->total_bytes = p_qtd->expected_bytes = total_bytes;
|
|
|
|
if (data_ptr != NULL) {
|
|
p_qtd->buffer[0] = (uint32_t)data_ptr;
|
|
for (uint8_t i = 1; i < 5; i++) {
|
|
p_qtd->buffer[i] |= ((p_qtd->buffer[i - 1]) & 0xFFFFF000UL) + 4096U;
|
|
}
|
|
}
|
|
}
|
|
|
|
static dcd_qhd_t *chipidea_qhd_get(uint8_t busid, uint8_t ep_idx)
|
|
{
|
|
dcd_data_t *dcd_data;
|
|
|
|
dcd_data = g_chipidea_udc[busid].dcd_data;
|
|
return &dcd_data->qhd[ep_idx];
|
|
}
|
|
|
|
static dcd_qtd_t *chipidea_qtd_get(uint8_t busid, uint8_t ep_idx)
|
|
{
|
|
dcd_data_t *dcd_data;
|
|
|
|
dcd_data = g_chipidea_udc[busid].dcd_data;
|
|
return &dcd_data->qtd[ep_idx * QTD_COUNT_EACH_ENDPOINT];
|
|
}
|
|
|
|
static void chipidea_bus_reset(uint8_t busid, uint16_t ep0_max_packet_size)
|
|
{
|
|
dcd_data_t *dcd_data;
|
|
|
|
dcd_data = g_chipidea_udc[busid].dcd_data;
|
|
__chipidea_bus_reset(USB_OTG_DEV);
|
|
|
|
/* Queue Head & Queue TD */
|
|
memset(dcd_data, 0, sizeof(dcd_data_t));
|
|
|
|
/* Set up Control Endpoints (0 OUT, 1 IN) */
|
|
dcd_data->qhd[0].zero_length_termination = dcd_data->qhd[1].zero_length_termination = 1;
|
|
dcd_data->qhd[0].max_packet_size = dcd_data->qhd[1].max_packet_size = ep0_max_packet_size;
|
|
dcd_data->qhd[0].qtd_overlay.next = dcd_data->qhd[1].qtd_overlay.next = 1;
|
|
|
|
/* OUT only */
|
|
dcd_data->qhd[0].int_on_setup = 1;
|
|
}
|
|
|
|
static void chipidea_edpt_open(uint8_t busid, uint8_t ep_addr, uint8_t ep_type, uint16_t ep_mps)
|
|
{
|
|
uint8_t const epnum = ep_addr & 0x0f;
|
|
uint8_t const dir = (ep_addr & 0x80) >> 7;
|
|
uint8_t const ep_idx = 2 * epnum + dir;
|
|
dcd_data_t *dcd_data;
|
|
dcd_qhd_t *p_qhd;
|
|
|
|
/* Prepare Queue Head */
|
|
dcd_data = g_chipidea_udc[busid].dcd_data;
|
|
p_qhd = &dcd_data->qhd[ep_idx];
|
|
memset(p_qhd, 0, sizeof(dcd_qhd_t));
|
|
|
|
p_qhd->zero_length_termination = 1;
|
|
p_qhd->max_packet_size = ep_mps & 0x7FFu;
|
|
p_qhd->qtd_overlay.next = 1;
|
|
if (ep_type == USB_ENDPOINT_TYPE_ISOCHRONOUS) {
|
|
p_qhd->iso_mult = ((ep_mps >> 11u) & 0x3u) + 1u;
|
|
}
|
|
|
|
__chipidea_edpt_open(USB_OTG_DEV, ep_addr, ep_type);
|
|
}
|
|
|
|
static bool chipidea_start_xfer(uint8_t busid, uint8_t ep_addr, uint8_t *buffer, uint32_t total_bytes)
|
|
{
|
|
uint8_t const epnum = ep_addr & 0x0f;
|
|
uint8_t const dir = (ep_addr & 0x80) >> 7;
|
|
uint8_t const ep_idx = 2 * epnum + dir;
|
|
uint8_t qtd_num;
|
|
uint8_t i;
|
|
uint32_t xfer_len;
|
|
dcd_qhd_t *p_qhd;
|
|
dcd_qtd_t *p_qtd;
|
|
dcd_qtd_t *first_p_qtd = NULL;
|
|
dcd_qtd_t *prev_p_qtd = NULL;
|
|
dcd_data_t *dcd_data;
|
|
|
|
dcd_data = g_chipidea_udc[busid].dcd_data;
|
|
|
|
if (epnum == 0) {
|
|
/* follows UM Setup packet handling using setup lockout mechanism
|
|
* wait until ENDPTSETUPSTAT before priming data/status in response TODO add time out
|
|
*/
|
|
while (USB_OTG_DEV->ENDPTSETUPSTAT & CHIPIDEA_BITSMASK(1, 0)) {
|
|
}
|
|
}
|
|
|
|
qtd_num = (total_bytes + 0x3fff) / 0x4000;
|
|
if (qtd_num > QTD_COUNT_EACH_ENDPOINT) {
|
|
return false;
|
|
}
|
|
|
|
if (buffer != NULL) {
|
|
buffer = (uint8_t *)buffer;
|
|
}
|
|
p_qhd = &dcd_data->qhd[ep_idx];
|
|
i = 0;
|
|
do {
|
|
p_qtd = &dcd_data->qtd[ep_idx * QTD_COUNT_EACH_ENDPOINT + i];
|
|
i++;
|
|
|
|
if (total_bytes > 0x4000) {
|
|
xfer_len = 0x4000;
|
|
total_bytes -= 0x4000;
|
|
} else {
|
|
xfer_len = total_bytes;
|
|
total_bytes = 0;
|
|
}
|
|
|
|
usb_qtd_init(p_qtd, (void *)buffer, xfer_len);
|
|
if (total_bytes == 0) {
|
|
p_qtd->int_on_complete = true;
|
|
}
|
|
buffer += xfer_len;
|
|
|
|
if (prev_p_qtd) {
|
|
prev_p_qtd->next = (uint32_t)p_qtd;
|
|
} else {
|
|
first_p_qtd = p_qtd;
|
|
}
|
|
prev_p_qtd = p_qtd;
|
|
} while (total_bytes > 0);
|
|
|
|
p_qhd->qtd_overlay.next = (uint32_t)first_p_qtd; /* link qtd to qhd */
|
|
|
|
chipidea_edpt_xfer(USB_OTG_DEV, ep_idx);
|
|
|
|
return true;
|
|
}
|
|
|
|
__WEAK void usb_dc_low_level_init(uint8_t busid)
|
|
{
|
|
}
|
|
|
|
__WEAK void usb_dc_low_level_deinit(uint8_t busid)
|
|
{
|
|
}
|
|
|
|
int usb_dc_init(uint8_t busid)
|
|
{
|
|
uint32_t int_mask;
|
|
int_mask = (USB_USBINTR_UE_MASK | USB_USBINTR_UEE_MASK | USB_USBINTR_SLE_MASK |
|
|
USB_USBINTR_PCE_MASK | USB_USBINTR_URE_MASK);
|
|
|
|
usb_dc_low_level_init(busid);
|
|
|
|
memset(&g_chipidea_udc[busid], 0, sizeof(struct chipidea_udc));
|
|
g_chipidea_udc[busid].dcd_data = g_dcd_data[busid];
|
|
memset(g_chipidea_udc[busid].dcd_data, 0, sizeof(dcd_data_t));
|
|
|
|
chipidea_init(USB_OTG_DEV);
|
|
|
|
/* Set endpoint list address */
|
|
USB_OTG_DEV->ENDPTLISTADDR = ((uint32_t)g_chipidea_udc[busid].dcd_data->qhd) & USB_ENDPTLISTADDR_EPBASE_MASK;
|
|
|
|
/* Clear status */
|
|
USB_OTG_DEV->USBSTS = USB_OTG_DEV->USBSTS;
|
|
|
|
/* Enable interrupt mask */
|
|
USB_OTG_DEV->USBINTR |= int_mask;
|
|
|
|
/* Connect by enabling internal pull-up resistor on D+/D- */
|
|
USB_OTG_DEV->USBCMD |= USB_USBCMD_RS_MASK;
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_deinit(uint8_t busid)
|
|
{
|
|
chipidea_deinit(USB_OTG_DEV);
|
|
|
|
for (uint32_t i = 0; i < CONFIG_USBDEV_EP_NUM; i++) {
|
|
chipidea_edpt_close(USB_OTG_DEV, (i | 0x80));
|
|
chipidea_edpt_close(USB_OTG_DEV, (i | 0x00));
|
|
}
|
|
|
|
usb_dc_low_level_deinit(busid);
|
|
return 0;
|
|
}
|
|
|
|
int usbd_set_address(uint8_t busid, const uint8_t addr)
|
|
{
|
|
USB_OTG_DEV->DEVICEADDR = USB_DEVICEADDR_USBADR_SET(addr) | USB_DEVICEADDR_USBADRA_MASK;
|
|
return 0;
|
|
}
|
|
|
|
int usbd_set_remote_wakeup(uint8_t busid)
|
|
{
|
|
if (!USB_PORTSC1_SUSP_GET(USB_OTG_DEV->PORTSC1)) {
|
|
return -1;
|
|
}
|
|
|
|
USB_OTG_DEV->PORTSC1 |= USB_PORTSC1_FPR_MASK;
|
|
while (USB_OTG_DEV->PORTSC1 & USB_PORTSC1_FPR_MASK) {
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint8_t usbd_get_port_speed(uint8_t busid)
|
|
{
|
|
uint8_t speed;
|
|
|
|
speed = USB_PORTSC1_PSPD_GET(USB_OTG_DEV->PORTSC1);
|
|
|
|
if (speed == 0x00) {
|
|
return USB_SPEED_FULL;
|
|
}
|
|
if (speed == 0x01) {
|
|
return USB_SPEED_LOW;
|
|
}
|
|
if (speed == 0x02) {
|
|
return USB_SPEED_HIGH;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usbd_ep_open(uint8_t busid, const struct usb_endpoint_descriptor *ep)
|
|
{
|
|
uint8_t ep_idx = USB_EP_GET_IDX(ep->bEndpointAddress);
|
|
|
|
/* Must not exceed max endpoint number */
|
|
if (ep_idx >= CONFIG_USBDEV_EP_NUM) {
|
|
return -1;
|
|
}
|
|
|
|
chipidea_edpt_open(busid, ep->bEndpointAddress, USB_GET_ENDPOINT_TYPE(ep->bmAttributes), ep->wMaxPacketSize);
|
|
|
|
if (USB_EP_DIR_IS_OUT(ep->bEndpointAddress)) {
|
|
g_chipidea_udc[busid].out_ep[ep_idx].ep_mps = USB_GET_MAXPACKETSIZE(ep->wMaxPacketSize);
|
|
g_chipidea_udc[busid].out_ep[ep_idx].ep_type = USB_GET_ENDPOINT_TYPE(ep->bmAttributes);
|
|
g_chipidea_udc[busid].out_ep[ep_idx].ep_enable = true;
|
|
} else {
|
|
g_chipidea_udc[busid].in_ep[ep_idx].ep_mps = USB_GET_MAXPACKETSIZE(ep->wMaxPacketSize);
|
|
g_chipidea_udc[busid].in_ep[ep_idx].ep_type = USB_GET_ENDPOINT_TYPE(ep->bmAttributes);
|
|
g_chipidea_udc[busid].in_ep[ep_idx].ep_enable = true;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usbd_ep_close(uint8_t busid, const uint8_t ep)
|
|
{
|
|
uint8_t ep_idx = USB_EP_GET_IDX(ep);
|
|
|
|
if (USB_EP_DIR_IS_OUT(ep)) {
|
|
g_chipidea_udc[busid].out_ep[ep_idx].ep_enable = false;
|
|
} else {
|
|
g_chipidea_udc[busid].in_ep[ep_idx].ep_enable = false;
|
|
}
|
|
|
|
chipidea_edpt_close(USB_OTG_DEV, ep);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usbd_ep_set_stall(uint8_t busid, const uint8_t ep)
|
|
{
|
|
chipidea_edpt_stall(USB_OTG_DEV, ep);
|
|
return 0;
|
|
}
|
|
|
|
int usbd_ep_clear_stall(uint8_t busid, const uint8_t ep)
|
|
{
|
|
chipidea_edpt_clear_stall(USB_OTG_DEV, ep);
|
|
return 0;
|
|
}
|
|
|
|
int usbd_ep_is_stalled(uint8_t busid, const uint8_t ep, uint8_t *stalled)
|
|
{
|
|
*stalled = chipidea_edpt_check_stall(USB_OTG_DEV, ep);
|
|
return 0;
|
|
}
|
|
|
|
int usbd_ep_start_write(uint8_t busid, const uint8_t ep, const uint8_t *data, uint32_t data_len)
|
|
{
|
|
uint8_t ep_idx = USB_EP_GET_IDX(ep);
|
|
|
|
if (!data && data_len) {
|
|
return -1;
|
|
}
|
|
if (!g_chipidea_udc[busid].in_ep[ep_idx].ep_enable) {
|
|
return -2;
|
|
}
|
|
|
|
g_chipidea_udc[busid].in_ep[ep_idx].xfer_buf = (uint8_t *)data;
|
|
g_chipidea_udc[busid].in_ep[ep_idx].xfer_len = data_len;
|
|
g_chipidea_udc[busid].in_ep[ep_idx].actual_xfer_len = 0;
|
|
|
|
chipidea_start_xfer(busid, ep, (uint8_t *)data, data_len);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usbd_ep_start_read(uint8_t busid, const uint8_t ep, uint8_t *data, uint32_t data_len)
|
|
{
|
|
uint8_t ep_idx = USB_EP_GET_IDX(ep);
|
|
|
|
if (!data && data_len) {
|
|
return -1;
|
|
}
|
|
if (!g_chipidea_udc[busid].out_ep[ep_idx].ep_enable) {
|
|
return -2;
|
|
}
|
|
|
|
g_chipidea_udc[busid].out_ep[ep_idx].xfer_buf = (uint8_t *)data;
|
|
g_chipidea_udc[busid].out_ep[ep_idx].xfer_len = data_len;
|
|
g_chipidea_udc[busid].out_ep[ep_idx].actual_xfer_len = 0;
|
|
|
|
chipidea_start_xfer(busid, ep, data, data_len);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void USBD_IRQHandler(uint8_t busid)
|
|
{
|
|
uint32_t int_status;
|
|
uint32_t transfer_len;
|
|
bool ep_cb_req;
|
|
|
|
/* Acknowledge handled interrupt */
|
|
int_status = USB_OTG_DEV->USBSTS;
|
|
int_status &= USB_OTG_DEV->USBINTR;
|
|
USB_OTG_DEV->USBSTS = int_status;
|
|
|
|
if (int_status & intr_error) {
|
|
USB_LOG_ERR("usbd intr error!\r\n");
|
|
}
|
|
|
|
if (int_status & intr_reset) {
|
|
g_chipidea_udc[busid].is_suspend = false;
|
|
memset(g_chipidea_udc[busid].in_ep, 0, sizeof(struct chipidea_ep_state) * CONFIG_USBDEV_EP_NUM);
|
|
memset(g_chipidea_udc[busid].out_ep, 0, sizeof(struct chipidea_ep_state) * CONFIG_USBDEV_EP_NUM);
|
|
usbd_event_reset_handler(busid);
|
|
chipidea_bus_reset(busid, 64);
|
|
}
|
|
|
|
if (int_status & intr_suspend) {
|
|
if (USB_PORTSC1_SUSP_GET(USB_OTG_DEV->PORTSC1)) {
|
|
/* Note: Host may delay more than 3 ms before and/or after bus reset before doing enumeration. */
|
|
if (USB_DEVICEADDR_USBADR_GET(USB_OTG_DEV->DEVICEADDR)) {
|
|
g_chipidea_udc[busid].is_suspend = true;
|
|
usbd_event_suspend_handler(busid);
|
|
}
|
|
} else {
|
|
}
|
|
}
|
|
|
|
if (int_status & intr_port_change) {
|
|
if (!USB_PORTSC1_CCS_GET(USB_OTG_DEV->PORTSC1)) {
|
|
usbd_event_disconnect_handler(busid);
|
|
} else {
|
|
if (g_chipidea_udc[busid].is_suspend) {
|
|
g_chipidea_udc[busid].is_suspend = false;
|
|
usbd_event_resume_handler(busid);
|
|
}
|
|
usbd_event_connect_handler(busid);
|
|
}
|
|
}
|
|
|
|
if (int_status & intr_usb) {
|
|
uint32_t const edpt_complete = USB_OTG_DEV->ENDPTCOMPLETE;
|
|
USB_OTG_DEV->ENDPTCOMPLETE = edpt_complete;
|
|
uint32_t edpt_setup_status = USB_OTG_DEV->ENDPTSETUPSTAT;
|
|
|
|
if (edpt_setup_status) {
|
|
/*------------- Set up Received -------------*/
|
|
USB_OTG_DEV->ENDPTSETUPSTAT = edpt_setup_status;
|
|
dcd_qhd_t *qhd0 = chipidea_qhd_get(busid, 0);
|
|
usbd_event_ep0_setup_complete_handler(busid, (uint8_t *)&qhd0->setup_request);
|
|
}
|
|
|
|
if (edpt_complete) {
|
|
for (uint8_t ep_idx = 0; ep_idx < (CONFIG_USBDEV_EP_NUM * 2); ep_idx++) {
|
|
if (edpt_complete & (1 << ep_idx2bit(ep_idx))) {
|
|
transfer_len = 0;
|
|
ep_cb_req = true;
|
|
|
|
/* Failed QTD also get ENDPTCOMPLETE set */
|
|
dcd_qtd_t *p_qtd = chipidea_qtd_get(busid, ep_idx);
|
|
while (1) {
|
|
if (p_qtd->halted || p_qtd->xact_err || p_qtd->buffer_err) {
|
|
USB_LOG_ERR("usbd transfer error!\r\n");
|
|
ep_cb_req = false;
|
|
break;
|
|
} else if (p_qtd->active) {
|
|
ep_cb_req = false;
|
|
break;
|
|
} else {
|
|
transfer_len += p_qtd->expected_bytes - p_qtd->total_bytes;
|
|
}
|
|
|
|
if (p_qtd->next == 1) {
|
|
break;
|
|
} else {
|
|
p_qtd = (dcd_qtd_t *)p_qtd->next;
|
|
}
|
|
}
|
|
|
|
if (ep_cb_req) {
|
|
uint8_t const ep_addr = (ep_idx / 2) | ((ep_idx & 0x01) ? 0x80 : 0);
|
|
if (ep_addr & 0x80) {
|
|
usbd_event_ep_in_complete_handler(busid, ep_addr, transfer_len);
|
|
} else {
|
|
usbd_event_ep_out_complete_handler(busid, ep_addr, transfer_len);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |