rt-thread-official/bsp/wch/risc-v/Libraries/ch56x_drivers/ch56x_usbd.c

575 lines
17 KiB
C

/*
* Copyright (c) 2006-2023, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2022-08-22 Emuzit first version
*/
#include <rthw.h>
#include <rtdebug.h>
#include <drivers/usb_common.h>
#include <drivers/usb_device.h>
#include "ch56x_usbhs.h"
#include "ch56x_sys.h"
#include "isr_sp.h"
/*--------------------------------------------------------*/
/* Warning : Not fully tested, use at your own discretion */
/*--------------------------------------------------------*/
#ifdef SOC_SERIES_CH569
#define _attr_uepdma __attribute__((section(".dmadata"), aligned(16)))
#define _ep0_setup_dmabuf _dmadata_start
#else
#define _attr_uepdma __attribute__((aligned(4)))
#define _ep0_setup_dmabuf _dmadata_start
#define usbhs_irq_handler usb1_irq_handler
#define USBHS_IRQn USB1_IRQn
#define USBHS_REG_BASE USB1_REG_BASE
#define RAMX_BASE_ADDRESS RAMS_BASE_ADDRESS
#define UEP0_RT_DMA UEP_DMA[0]
#endif
#define UEP_MPS_64 64
#define UEP_MPS_512 512
#define _get_ep_idx(address) ((address) & USB_EPNO_MASK)
#define _get_ep_dir(address) ((address) & USB_DIR_MASK)
#define uep_dir_is_in(address) (_get_ep_dir(address) == USB_DIR_IN)
#define uep_dir_is_out(address) (_get_ep_dir(address) == USB_DIR_OUT)
extern uint32_t _dmadata_start[];
static uint32_t ep0_dmabuf[UEP_MPS_64 / 4] _attr_uepdma;
static uint32_t epx_dmabuf[UEP_ADDRESS_MAX][UEP_MPS_512 / 4] _attr_uepdma;
static struct ep_id usbhs_ep_pool[] =
{
{0x0, USB_EP_ATTR_CONTROL, USB_DIR_INOUT, 64, ID_ASSIGNED},
{0x1, USB_EP_ATTR_BULK, USB_DIR_IN, 512, ID_UNASSIGNED},
{0x1, USB_EP_ATTR_BULK, USB_DIR_OUT, 512, ID_UNASSIGNED},
{0x2, USB_EP_ATTR_INT, USB_DIR_IN, 512, ID_UNASSIGNED},
{0x2, USB_EP_ATTR_INT, USB_DIR_OUT, 512, ID_UNASSIGNED},
{0x3, USB_EP_ATTR_BULK, USB_DIR_IN, 512, ID_UNASSIGNED},
{0x3, USB_EP_ATTR_BULK, USB_DIR_OUT, 512, ID_UNASSIGNED},
#ifdef SOC_SERIES_CH569
/* FIXME: not sure how to deal with EP4, no UEP4_DMA register */
{0x4, USB_EP_ATTR_INT, USB_DIR_IN, 512, ID_UNASSIGNED},
{0x4, USB_EP_ATTR_INT, USB_DIR_OUT, 512, ID_UNASSIGNED},
{0x5, USB_EP_ATTR_BULK, USB_DIR_IN, 512, ID_UNASSIGNED},
{0x5, USB_EP_ATTR_BULK, USB_DIR_OUT, 512, ID_UNASSIGNED},
{0x6, USB_EP_ATTR_INT, USB_DIR_IN, 512, ID_UNASSIGNED},
{0x6, USB_EP_ATTR_INT, USB_DIR_OUT, 512, ID_UNASSIGNED},
{0x7, USB_EP_ATTR_BULK, USB_DIR_IN, 512, ID_UNASSIGNED},
{0x7, USB_EP_ATTR_BULK, USB_DIR_OUT, 512, ID_UNASSIGNED},
#endif
{0xff, USB_EP_ATTR_TYPE_MASK, USB_DIR_MASK, 0, ID_ASSIGNED},
};
static struct udcd udc_device;
static uint8_t setup_set_address;
static rt_err_t udc_set_address(uint8_t address)
{
/* DEV_AD should be updated after status stage IN token of SET_ADDRESS
* such that that IN token could still reach our device.
*/
setup_set_address = address | 0x80;
return RT_EOK;
}
static rt_err_t udc_set_config(uint8_t address)
{
return RT_EOK;
}
static rt_err_t udc_ep_set_stall(uint8_t address)
{
volatile struct usbhs_registers *usbhs = (void *)USBHS_REG_BASE;
uint8_t ep_idx = _get_ep_idx(address);
if (uep_dir_is_in(address))
usbhs->UEP_CTRL[ep_idx].TX_CTRL.res_mask = UEP_RES_STALL;
else
usbhs->UEP_CTRL[ep_idx].RX_CTRL.res_mask = UEP_RES_STALL;
return RT_EOK;
}
static rt_err_t udc_ep_clear_stall(uint8_t address)
{
volatile struct usbhs_registers *usbhs = (void *)USBHS_REG_BASE;
uint8_t ep_idx = _get_ep_idx(address);
if (uep_dir_is_in(address))
usbhs->UEP_CTRL[ep_idx].TX_CTRL.res_mask = UEP_RES_NAK;
else
usbhs->UEP_CTRL[ep_idx].RX_CTRL.res_mask = UEP_RES_NAK;
return RT_EOK;
}
static rt_err_t udc_ep_enable(struct uendpoint *ep)
{
volatile struct usbhs_registers *usbhs = (void *)USBHS_REG_BASE;
uint8_t ep_idx, address, mod;
RT_ASSERT(ep != RT_NULL);
RT_ASSERT(ep->ep_desc != RT_NULL);
address = EP_ADDRESS(ep);
ep_idx = _get_ep_idx(address);
if (ep_idx > 0 && ep_idx <= UEP_ADDRESS_MAX)
{
mod = uep_dir_is_in(address) ? RB_UEP_TX_EN : RB_UEP_RX_EN;
mod = _uep_mod_get(usbhs, ep_idx) | mod;
_uep_mod_set(usbhs, ep_idx, mod);
}
return RT_EOK;
}
static rt_err_t udc_ep_disable(struct uendpoint *ep)
{
volatile struct usbhs_registers *usbhs = (void *)USBHS_REG_BASE;
uint8_t ep_idx, address, mod;
RT_ASSERT(ep != RT_NULL);
RT_ASSERT(ep->ep_desc != RT_NULL);
address = EP_ADDRESS(ep);
ep_idx = _get_ep_idx(address);
if (ep_idx > 0 && ep_idx <= UEP_ADDRESS_MAX)
{
mod = uep_dir_is_in(address) ? RB_UEP_TX_EN : RB_UEP_RX_EN;
mod = _uep_mod_get(usbhs, ep_idx) & ~mod;
_uep_mod_set(usbhs, ep_idx, mod);
}
return RT_EOK;
}
static rt_ssize_t udc_ep_read_prepare(uint8_t address, void *buffer, rt_size_t size)
{
volatile struct usbhs_registers *usbhs = (void *)USBHS_REG_BASE;
uint8_t ep_idx = _get_ep_idx(address);
uint32_t dmabuf = (uint32_t)buffer;
if (uep_dir_is_in(address))
return 0;
if (size > (ep_idx ? UEP_MPS_512 : UEP_MPS_64))
size = (ep_idx ? UEP_MPS_512 : UEP_MPS_64);
/* need extra `buffer` copy if H/W requirement not met
* CH565/CH569 : DMA buffer resides in RAMX, 16-byte aligned
* CH567/CH568 : 4-byte aligned
*/
#ifdef SOC_SERIES_CH569
if (size > 0 && (dmabuf < RAMX_BASE_ADDRESS || (dmabuf & 0xf)))
{
dmabuf = (uint32_t)(ep_idx ? epx_dmabuf[ep_idx] : ep0_dmabuf);
}
/* Note : usbhs->UEP_RX_DMA[0] maps to usbhs->UEP0_RT_DMA actually */
usbhs->UEP_RX_DMA[ep_idx] = dmabuf & UEP_RT_DMA_MASK;
#else
if (size > 0 && (dmabuf & 3))
{
dmabuf = (uint32_t)(ep_idx ? epx_dmabuf[ep_idx] : ep0_dmabuf);
}
usbhs->UEP_DMA[ep_idx] = dmabuf & UEP_RT_DMA_MASK;
#endif
if (ep_idx == 0 && size == 0)
{
/* SETUP status stage, expect DATA1 */
usbhs->UEP_CTRL[0].RX_CTRL.reg = RB_UEP_RES_ACK | RB_UEP_TOG_DATA1;
}
else
{
/* keep TOG_MASK & AUTOTOG */
usbhs->UEP_CTRL[ep_idx].RX_CTRL.res_mask = UEP_RES_ACK;
}
return size;
}
static rt_ssize_t udc_ep_read(uint8_t address, void *buffer)
{
volatile struct usbhs_registers *usbhs = (void *)USBHS_REG_BASE;
uint8_t ep_idx = _get_ep_idx(address);
uint32_t dmabuf;
rt_size_t size;
if (uep_dir_is_in(address))
return 0;
#ifdef SOC_SERIES_CH569
/* Note : usbhs->UEP_RX_DMA[0] maps to usbhs->UEP0_RT_DMA actually */
dmabuf = usbhs->UEP_RX_DMA[ep_idx] & UEP_RT_DMA_MASK;
#else
dmabuf = usbhs->UEP_DMA[ep_idx] & UEP_RT_DMA_MASK;
#endif
size = usbhs->RX_LEN;
/* copy if proxy buffer */
if (size > 0 && ((uint32_t)buffer & UEP_RT_DMA_MASK) != dmabuf)
{
dmabuf |= RAMX_BASE_ADDRESS;
rt_memcpy(buffer, (void *)dmabuf, size);
}
return size;
}
static rt_ssize_t udc_ep_write(uint8_t address, void *buffer, rt_size_t size)
{
volatile struct usbhs_registers *usbhs = (void *)USBHS_REG_BASE;
uint8_t ep_idx = _get_ep_idx(address);
uint32_t dmabuf = (uint32_t)buffer;
union _uh_rt_ctrl ctrl;
if (uep_dir_is_out(address))
return 0;
if (size > (ep_idx ? UEP_MPS_512 : UEP_MPS_64))
size = (ep_idx ? UEP_MPS_512 : UEP_MPS_64);
/* need extra `buffer` copy if H/W requirement not met
* CH565/CH569 : DMA buffer resides in RAMX, 16-byte aligned
* CH567/CH568 : 4-byte aligned
*/
#ifdef SOC_SERIES_CH569
if (size > 0 && (dmabuf < RAMX_BASE_ADDRESS || (dmabuf & 0xf)))
{
dmabuf = (uint32_t)(ep_idx ? epx_dmabuf[ep_idx] : ep0_dmabuf);
rt_memcpy((void *)dmabuf, buffer, size);
}
if (ep_idx == 0)
usbhs->UEP0_RT_DMA = dmabuf & UEP_RT_DMA_MASK;
else
usbhs->UEP_TX_DMA[ep_idx] = dmabuf & UEP_RT_DMA_MASK;
#else
if (size > 0 && (dmabuf & 3))
{
dmabuf = (uint32_t)(ep_idx ? epx_dmabuf[ep_idx] : ep0_dmabuf);
rt_memcpy((void *)dmabuf, buffer, size);
}
usbhs->UEP_DMA[ep_idx] = dmabuf & UEP_RT_DMA_MASK;
#endif
usbhs->UEP_CTRL[ep_idx].t_len = size;
/* keep TOG_MASK & AUTOTOG */
usbhs->UEP_CTRL[ep_idx].TX_CTRL.res_mask = UEP_RES_ACK;
return size;
}
static rt_err_t udc_ep0_send_status(void)
{
volatile struct usbhs_registers *usbhs = (void *)USBHS_REG_BASE;
/* SETUP status stage : zero data length, always DATA1 */
usbhs->UEP_CTRL[0].t_len = 0;
/* This is the only case UEP0_RT_DMA is set to 0. */
usbhs->UEP0_RT_DMA = 0;
usbhs->UEP_CTRL[0].TX_CTRL.reg = RB_UEP_RES_ACK | RB_UEP_TOG_DATA1;
return RT_EOK;
}
static rt_err_t udc_suspend(void)
{
return RT_EOK;
}
static rt_err_t udc_wakeup(void)
{
return RT_EOK;
}
static const struct udcd_ops udcd_ops =
{
.set_address = udc_set_address,
.set_config = udc_set_config,
.ep_set_stall = udc_ep_set_stall,
.ep_clear_stall = udc_ep_clear_stall,
.ep_enable = udc_ep_enable,
.ep_disable = udc_ep_disable,
.ep_read_prepare = udc_ep_read_prepare,
.ep_read = udc_ep_read,
.ep_write = udc_ep_write,
.ep0_send_status = udc_ep0_send_status,
.suspend = udc_suspend,
.wakeup = udc_wakeup,
};
static void _hsbhs_device_mode_init(volatile struct usbhs_registers *usbhs)
{
uint8_t ep_idx;
/* disable all endpoints, use single buffer mode (BUF_MOD : 0) */
usbhs->UHOST_CTRL.reg = 0;
usbhs->SUSPEND.reg = 0;
usbhs->R32_UEP_MOD = 0;
usbhs->DEV_AD = 0;
usbhs->CTRL.reg = RB_USB_RESET_SIE | RB_USB_CLR_ALL;
usbhs->CTRL.reg = RB_USB_DEVICE_MODE |
RB_SPTP_HIGH_SPEED |
RB_DEV_PU_EN |
RB_USB_INT_BUSY |
RB_USB_DMA_EN;
usbhs->INT_EN.reg = RB_USB_IE_BUSRST |
RB_USB_IE_TRANS |
RB_USB_IE_FIFOOV |
RB_USB_IE_SETUPACT;
usbhs->UEP_MAX_LEN[0].reg = UEP_MPS_64;
/*
* It seems EP0 SETUP uses the first 8 bytes of RAMX as dmabuf and
* handles DATA0 transfer & ACK on its own. Here we still needs to
* RES_NAK TX/RX to block SETUP data stage till dma data is ready.
*/
usbhs->UEP_CTRL[0].TX_CTRL.reg = RB_UEP_RES_NAK | RB_UEP_TOG_DATA1;
usbhs->UEP_CTRL[0].RX_CTRL.reg = RB_UEP_RES_NAK | RB_UEP_TOG_DATA1;
for (ep_idx = 1; ep_idx <= UEP_ADDRESS_MAX; ep_idx++)
{
usbhs->UEP_MAX_LEN[ep_idx].reg = UEP_MPS_512;
/* set to DATA0, remains to be initialized (SET_CONFIGURATION...) */
usbhs->UEP_CTRL[ep_idx].TX_CTRL.reg = RB_UEP_RES_NAK | RB_UEP_AUTOTOG;
usbhs->UEP_CTRL[ep_idx].RX_CTRL.reg = RB_UEP_RES_NAK | RB_UEP_AUTOTOG;
}
}
static rt_err_t udc_device_init(struct rt_device *device)
{
volatile struct usbhs_registers *usbhs = device->user_data;
sys_clk_off_by_irqn(USBHS_IRQn, SYS_SLP_CLK_ON);
_hsbhs_device_mode_init(usbhs);
rt_hw_interrupt_umask(USBHS_IRQn);
return RT_EOK;
}
#ifdef RT_USING_DEVICE_OPS
static struct rt_device_ops device_ops;
#endif
static int rt_hw_usbd_init(void)
{
int ret;
udc_device.parent.type = RT_Device_Class_USBDevice;
#ifdef RT_USING_DEVICE_OPS
device_ops.init = udc_device_init;
udc_device.parent.ops = &device_ops;
#else
udc_device.parent.init = udc_device_init;
#endif
udc_device.parent.user_data = (void *)USBHS_REG_BASE;
udc_device.ops = &udcd_ops;
udc_device.ep_pool = usbhs_ep_pool;
udc_device.ep0.id = &usbhs_ep_pool[0];
udc_device.device_is_hs = RT_TRUE;
ret = rt_device_register(&udc_device.parent, "usbd", 0);
if (ret == RT_EOK)
ret = rt_usb_device_init();
return ret;
}
INIT_DEVICE_EXPORT(rt_hw_usbd_init);
rt_inline uint8_t _uep_tog_datax(uint8_t tog)
{
/* Note: treat tog as RB_UEP_TOG_DATA0 if not RB_UEP_TOG_DATA1 */
return (tog == RB_UEP_TOG_DATA1) ? RB_UEP_TOG_DATA0 : RB_UEP_TOG_DATA1;
}
static void _isr_ep_stall(volatile struct usbhs_registers *usbhs)
{
uint8_t ep_idx = usbhs->INT_ST.dev_endp_mask;
usbhs->UEP_CTRL[ep_idx].TX_CTRL.res_mask == UEP_RES_STALL;
usbhs->UEP_CTRL[ep_idx].RX_CTRL.res_mask == UEP_RES_STALL;
}
static void _isr_handle_setup(volatile struct usbhs_registers *usbhs)
{
struct urequest setup, *packet;
uint8_t ep_idx, xctrl, recipient;
/* RES_NAK to block data stage, will expect or response DATA1 */
usbhs->UEP_CTRL[0].TX_CTRL.reg = RB_UEP_RES_NAK | RB_UEP_TOG_DATA1;
usbhs->UEP_CTRL[0].RX_CTRL.reg = RB_UEP_RES_NAK | RB_UEP_TOG_DATA1;
packet = (struct urequest *)_ep0_setup_dmabuf;
setup.request_type = packet->request_type;
setup.bRequest = packet->bRequest;
setup.wValue = packet->wValue;
setup.wIndex = packet->wIndex;
setup.wLength = packet->wLength;
/* Init data toggle bit. Not sure if it has been done by h/w.*/
xctrl = RB_UEP_RES_NAK | RB_UEP_AUTOTOG | RB_UEP_TOG_DATA0;
recipient = setup.request_type & USB_REQ_TYPE_RECIPIENT_MASK;
if (recipient == USB_REQ_TYPE_DEVICE &&
setup.bRequest == USB_REQ_SET_CONFIGURATION)
{
for (ep_idx = 1; ep_idx <= UEP_ADDRESS_MAX; ep_idx++)
{
usbhs->UEP_CTRL[ep_idx].TX_CTRL.reg = xctrl;
usbhs->UEP_CTRL[ep_idx].RX_CTRL.reg = xctrl;
}
}
else if (recipient == USB_REQ_TYPE_ENDPOINT &&
setup.bRequest == USB_REQ_CLEAR_FEATURE &&
setup.wValue == USB_EP_HALT)
{
ep_idx = setup.wIndex;
if (ep_idx > 0 && ep_idx <= UEP_ADDRESS_MAX)
{
usbhs->UEP_CTRL[ep_idx].TX_CTRL.reg = xctrl;
usbhs->UEP_CTRL[ep_idx].RX_CTRL.reg = xctrl;
}
}
rt_usbd_ep0_setup_handler(&udc_device, &setup);
}
static void _isr_handle_transfer(volatile struct usbhs_registers *usbhs)
{
rt_size_t size;
uint8_t ep_idx, token, tog;
ep_idx = usbhs->INT_ST.dev_endp_mask;
token = usbhs->INT_ST.dev_token_mask;
if (ep_idx == 0)
{
if (token == DEV_TOKEN_IN)
{
/* UEP0 does not support AUTOTOG, generate DATAx manually */
tog = usbhs->UEP_CTRL[0].TX_CTRL.reg & RB_UEP_TOG_MASK;
tog = _uep_tog_datax(tog);
/* wait for udc_ep_write or udc_ep0_send_status to RES_ACK */
usbhs->UEP_CTRL[0].TX_CTRL.reg = RB_UEP_RES_NAK | tog;
if (setup_set_address != 0 && usbhs->UEP_CTRL[0].t_len == 0)
{
usbhs->DEV_AD = setup_set_address & 0x7f;
setup_set_address = 0;
}
/* don't call in_handler if send_status */
if (usbhs->UEP0_RT_DMA != 0)
{
rt_usbd_ep0_in_handler(&udc_device);
}
}
else if (token == DEV_TOKEN_OUT)
{
if (usbhs->INT_ST.st_togok)
{
/* UEP0 does not support AUTOTOG, generate DATAx manually */
tog = usbhs->UEP_CTRL[0].RX_CTRL.reg & RB_UEP_TOG_MASK;
tog = _uep_tog_datax(tog);
/* wait for udc_ep_read_prepare to RES_ACK */
usbhs->UEP_CTRL[0].RX_CTRL.reg = RB_UEP_RES_NAK | tog;
rt_usbd_ep0_out_handler(&udc_device, usbhs->RX_LEN);
}
else
{
/* Corrupted ACK Handshake => ignore data, keep sequence bit */
usbhs->UEP_CTRL[0].RX_CTRL.res_mask = UEP_RES_NAK;
}
}
}
else if (token == DEV_TOKEN_IN)
{
/* wait for udc_ep_write to RES_ACK */
usbhs->UEP_CTRL[ep_idx].TX_CTRL.res_mask = UEP_RES_NAK;
size = usbhs->UEP_CTRL[ep_idx].t_len;
rt_usbd_ep_in_handler(&udc_device, ep_idx | USB_DIR_IN, size);
}
else if (token == DEV_TOKEN_OUT)
{
/* wait for udc_ep_read_prepare to RES_ACK */
usbhs->UEP_CTRL[ep_idx].RX_CTRL.res_mask = UEP_RES_NAK;
/* ignore data if Corrupted ACK Handshake */
if (usbhs->INT_ST.st_togok)
{
/* size:0 to trigger dcd_ep_read() in _data_notify() */
rt_usbd_ep_out_handler(&udc_device, ep_idx | USB_DIR_OUT, 0);
}
}
}
/*
* CAVEAT: The usbd design of ch56x relies on instant isr to RES_NAK
* UEP_CTRL[n].TX_CTRL/RX_CTRL. A long tarried isr may leave RES_ACK
* in UEP_CTRL[n] and starts unintended DMA upon arrival of IN/OUT.
*/
void usbhs_irq_handler(void) __attribute__((interrupt()));
void usbhs_irq_handler(void)
{
volatile struct usbhs_registers *usbhs;
union _usb_int_fg intflag;
isr_sp_enter();
rt_interrupt_enter();
usbhs = (struct usbhs_registers *)USBHS_REG_BASE;
intflag.reg = usbhs->INT_FG.reg;
if (intflag.fifoov)
{
/* FIXME: fifo overflow */
_isr_ep_stall(usbhs);
}
else
{
if (intflag.transfer)
_isr_handle_transfer(usbhs);
if (intflag.setupact)
_isr_handle_setup(usbhs);
}
if (intflag.busrst)
{
_hsbhs_device_mode_init(usbhs);
rt_usbd_reset_handler(&udc_device);
}
/* clear all pending intflag (suspend, isoact & nak ignored) */
usbhs->INT_FG.reg = intflag.reg;
rt_interrupt_leave();
isr_sp_leave();
}