Files
luban-lite-t3e-pro/bsp/artinchip/drv_bare/boot_usb/udc.c
刘可亮 aaa66c7b20 V1.0.1
2023-11-09 20:19:51 +08:00

688 lines
18 KiB
C

/*
* Copyright (c) 2023, Artinchip Technology Co., Ltd
*
* SPDX-License-Identifier: Apache-2.0
*
* Wu Dehuang <dehuang.wu@artinchip.com>
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <driver.h>
#include <aic_core.h>
#include <usb_drv.h>
#include "udc.h"
#include "usbc.h"
#include "usbdevice.h"
#include "usb_defs.h"
#include "usb_reg.h"
static u8 rx_base_buffer[BULK_EP_HS_MPS];
static struct aic_udc aic_udc;
s32 aic_udc_init(struct usb_device *usbdev)
{
struct aic_udc *udc = &aic_udc;
if (usbdev == NULL)
return -1;
memset(udc, 0, sizeof(struct aic_udc));
udc->gadget = usbdev;
if (udc->gadget->state_init()) {
pr_err("usb err: fail to init usb device\n");
return -1;
}
usbc_init();
usbc_soft_disconnect();
usbc_clock_gating_disable();
memset(&udc->buf, 0, sizeof(struct aic_ubuf));
udc->buf.rx_buf = (u8 *)rx_base_buffer;
usbc_clock_gating_enable();
usbc_soft_connect();
pr_debug("%s done\n", __func__);
return 0;
}
s32 aic_udc_exit(void)
{
return 0;
}
static void reset_connection(void)
{
usbc_soft_disconnect();
aicos_mdelay(10);
usbc_soft_connect();
aic_udc.reset_conn = 0;
}
void aic_udc_bulk_ep_reset(void)
{
usbc_in_bulk_ep_activate(BULK_IN_EP_INDEX);
usbc_out_bulk_ep_activate(BULK_OUT_EP_INDEX);
usbc_flush_np_txfifo();
}
/*
* USB Reset from HOST.
* This is the first interrupt when device connect to host.
*/
void reset_intr_proc(void)
{
struct aic_udc *udc = &aic_udc;
u32 i, setup_pkt_max, pkt_cnt, xfersiz;
pr_debug("\n\n%s\n\n", __func__);
usbc_intr_clear_gintsts_pending(USB_DEV_GINTSTS_USBRST);
usbc_flush_all_txfifo();
for (i = 0; i < USB_DEV_EP_CNT; i++) {
usbc_in_ep_reset(i);
usbc_out_ep_reset(i);
}
usbc_intr_reset();
usbc_set_address(0);
/*
* Transfer configuration
* Setup HW to recieve 1 SETUP packet after reset
*/
setup_pkt_max = 3;
pkt_cnt = 1;
xfersiz = setup_pkt_max * 8;
usbc_out_ctrl_ep_xfer_cfg(setup_pkt_max, pkt_cnt, xfersiz);
udc->gadget->state_reset();
}
/*
* After reset interrupt, host will enumuerate device speed.
*/
static void speed_enum_done_intr_proc(void)
{
u8 speed;
pr_debug("%s\n", __func__);
usbc_intr_clear_gintsts_pending(USB_DEV_GINTSTS_ENUMDNE);
speed = usbc_get_dev_speed();
switch (speed) {
case USB_DEV_SPEED_HIGH:
pr_debug("HS, phy clock 30MHz or 60MHz\n");
break;
case USB_DEV_SPEED_FULL2:
pr_debug("FS, phy clock 30MHz or 60MHz\n");
break;
case USB_DEV_SPEED_FULL1:
pr_debug("FS, phy clock 48MHz\n");
break;
case USB_DEV_SPEED_LOW:
pr_debug("LS, phy clock 6MHz\n");
break;
}
/*
* Speed enumerate finish, going to perform Ctrl EP communication
*/
usbc_set_ctrl_ep_mps(DIEPCTL0_MPS_64B);
usbc_in_ctrl_ep_activate();
usbc_out_ctrl_ep_activate();
}
static u32 rxfifo_data_intr_proc(u8 *buf, u32 *ep_id)
{
u32 status_pop, data_len = 0, pktsts;
usbc_intr_gintmsk_disable(USB_DEV_GINTMSK_RXFLVL);
status_pop = usbc_get_status_pop();
*ep_id = (status_pop & USB_DEV_GRXSTSP_EPNUM) >> 0;
data_len = (status_pop & USB_DEV_GRXSTSP_BCNT) >> 4;
pktsts = (status_pop & USB_DEV_GRXSTSP_PKTSTS) >> 17;
if (pktsts == GRXSTSP_PKTSTS_SETUP_RECV)
pr_debug("%s, GRXSTSP_PKTSTS_SETUP_RECV\n", __func__);
if (pktsts == GRXSTSP_PKTSTS_OUTDATA_RECV)
pr_debug("%s, GRXSTSP_PKTSTS_OUTDATA_RECV\n", __func__);
if (pktsts == GRXSTSP_PKTSTS_OUT_NAK)
pr_debug("%s, GRXSTSP_PKTSTS_OUT_NAK\n", __func__);
if (pktsts == GRXSTSP_PKTSTS_OUTXFER_COMPLETED)
pr_debug("%s, GRXSTSP_PKTSTS_OUTXFER_COMPLETED\n", __func__);
if (pktsts == GRXSTSP_PKTSTS_SETUP_COMPLETED)
pr_debug("%s, GRXSTSP_PKTSTS_SETUP_COMPLETED\n", __func__);
pr_debug("%s, datalen 0x%X\n", __func__, data_len);
switch (pktsts) {
case GRXSTSP_PKTSTS_SETUP_RECV:
case GRXSTSP_PKTSTS_OUTDATA_RECV:
/*
* Read SETUP/OUT transaction's data
* Two types data will be read here:
* - SETUP Request
* - CBW
*/
usbc_ep_recv_data_cpu(buf, data_len);
break;
case GRXSTSP_PKTSTS_OUT_NAK:
break;
case GRXSTSP_PKTSTS_OUTXFER_COMPLETED:
break;
case GRXSTSP_PKTSTS_SETUP_COMPLETED:
break;
}
usbc_intr_gintmsk_enable(USB_DEV_GINTMSK_RXFLVL);
return data_len;
}
static void ctrl_ep_income_req_proc(u8 *buf, u32 len)
{
s32 ret = 0;
struct aic_udc *udc = &aic_udc;
struct usb_device_request *req = &udc->std_req;
pr_debug("%s\n", __func__);
if (len != sizeof(struct usb_device_request)) {
pr_err("Error, EP0 recv data should be usb_device_request\n");
ret = -1;
goto out;
}
if (udc->gadget == NULL) {
pr_err("Error, no active device\n");
ret = -1;
goto out;
}
memcpy(req, buf, len);
if (USB_REQ_TYPE_STANDARD == (req->bmRequestType & USB_REQ_TYPE_MASK))
ret = udc->gadget->standard_req_proc(req);
else
ret = udc->gadget->nonstandard_req_proc(req);
out:
if (ret != 0) {
/*
* Control EP, not supported request, should return STALL
* to host. Set STALL on control EP here, and set ready to
* receive next SETUP packet, STALL on control EP will be clear
* by hardware when receive SETUP PID.
*/
usbc_out_ctrl_ep_set_stall();
usbc_in_ctrl_ep_set_stall();
usbc_out_ctrl_ep_xfer_cfg(3, 1, 24);
}
}
/*
* Interrupt from OUT EP
*/
static void out_ep_intr_proc(struct aic_ubuf *buf)
{
struct aic_udc *udc = &aic_udc;
u32 ep, intr_overview, ep_intr;
pr_debug("%s\n", __func__);
usbc_intr_clear_gintsts_pending(USB_DEV_GINTSTS_OEPINT);
intr_overview = usbc_get_daint_out();
/*
* Check which EP has pending interrupt
*/
for (ep = 0; ep < USB_DEV_EP_CNT; ep++) {
if ((intr_overview & (0x1 << ep)) == 0)
continue;
/*
* Get OUT EP's detail interrupt information
*/
ep_intr = usbc_intr_get_doepint(ep);
if ((ep == CTRL_EP_INDEX) && (ep_intr & USB_DEV_DOEPINT_XFRC)) {
pr_debug("ep0 USB_DEV_DOEPINT_XFRC\n");
usbc_intr_clear_doepint(ep, USB_DEV_DOEPINT_XFRC);
/*
* Going to receive next SETUP packet from
* OUT EP0
*/
usbc_out_ctrl_ep_xfer_cfg(3, 1, 24);
}
if ((ep == CTRL_EP_INDEX) && (ep_intr & USB_DEV_DOEPINT_STUP)) {
pr_debug("USB_DEV_DOEPINT_STUP\n");
usbc_intr_clear_doepint(ep, USB_DEV_DOEPINT_STUP);
/*
* Going to receive next SETUP packet from EP0
*
* Since USB_DEV_DOEPINT_XFRC not occur every time,
* set it again.
*/
usbc_out_ctrl_ep_xfer_cfg(3, 1, 24);
/*
* Receive SETUP request from OUT EP0 done
*/
ctrl_ep_income_req_proc(buf->rx_buf, buf->rx_len);
memset(buf->rx_buf, 0, buf->rx_len);
buf->rx_len = 0;
}
if ((ep == BULK_OUT_EP_INDEX) && (ep_intr & USB_DEV_DOEPINT_XFRC)) {
pr_debug("ep2 USB_DEV_DOEPINT_XFRC\n");
usbc_intr_clear_doepint(ep, USB_DEV_DOEPINT_XFRC);
/*
* Two cases to issue this interrupt:
* - Receive CBW done
* - Receive data follows CBW. This data recv in CBW
* processing flow, but not wait and clear this intr.
*/
if (udc->gadget && buf->rx_len > 0) {
udc->gadget->state_cmd(buf->rx_buf, buf->rx_len);
memset(buf->rx_buf, 0, buf->rx_len);
buf->rx_len = 0;
}
/*
* Finish one CBW process, set bulk ep to receive next
* CBW
*/
usbc_out_bulk_ep_xfer_cfg(BULK_OUT_EP_INDEX, 1, BULK_EP_HS_MPS);
}
if (ep_intr & USB_DEV_DOEPINT_OTEPDIS) {
/*
* OUT Token Received when this EP was not yet enabled.
* (So we should enable this EP here?)
*/
pr_debug("ep = 0x%X USB_DEV_DOEPINT_OTEPDIS\n", ep);
usbc_intr_clear_doepint(ep, USB_DEV_DOEPINT_OTEPDIS);
}
if (ep_intr & USB_DEV_DOEPINT_OTEPSPR) {
pr_debug("ep = %X USB_DEV_DOEPINT_OTEPSPR\n", ep);
usbc_intr_clear_doepint(ep, USB_DEV_DOEPINT_OTEPSPR);
}
if (ep_intr & USB_DEV_DOEPINT_NAK) {
/*
* USB Core generate this intr when a NAK is sent
*/
pr_debug("ep = %X USB_DEV_DOEPINT_NAK\n", ep);
usbc_intr_clear_doepint(ep, USB_DEV_DOEPINT_NAK);
}
}
}
static void np_txfifo_empty_intr_proc(void)
{
usbc_intr_clear_gintsts_pending(USB_DEV_GINTSTS_NPTXFE);
pr_debug("%s\n", __func__);
/*
* Disable NPTXFE interrupt here, when to enable?
* - When application is going to send some data, after configure
* xfer size, can enable this interrupt to check if FIFO is ok to
* write data
*/
usbc_intr_gintmsk_disable(USB_DEV_GINTMSK_NPTXFE);
}
static void in_ep_intr_proc(void)
{
u32 ep, intr_overview, ep_intr;
pr_debug("%s\n", __func__);
usbc_intr_clear_gintsts_pending(USB_DEV_GINTSTS_IEPINT);
intr_overview = usbc_get_daint_in();
/*
* Check which EP has pending interrupt
*/
for (ep = 0; ep < USB_DEV_EP_CNT; ep++) {
if ((intr_overview & (0x1 << ep)) == 0)
continue;
/*
* Get IN EP's detail interrupt information
*/
ep_intr = usbc_intr_get_diepint(ep);
if ((ep == CTRL_EP_INDEX) && (ep_intr & USB_DEV_DIEPINT_XFRC)) {
pr_debug("ep0 USB_DEV_DIEPINT_XFRC\n");
usbc_intr_clear_diepint(ep, USB_DEV_DIEPINT_XFRC);
// usbc_out_ctrl_ep_xfer_cfg(3, 1, 24);
}
if ((ep == BULK_IN_EP_INDEX) && (ep_intr & USB_DEV_DIEPINT_XFRC)) {
pr_debug("ep1 USB_DEV_DIEPINT_XFRC\n");
usbc_intr_clear_diepint(ep, USB_DEV_DIEPINT_XFRC);
}
/*
* Timeout Condition
* HW detected a timeout condition on the USB for the last IN
* token on the EP.
*/
if (ep_intr & USB_DEV_DIEPINT_TOC) {
pr_debug("ep %X USB_DEV_DIEPINT_TOC\n", ep);
usbc_intr_clear_diepint(ep, USB_DEV_DIEPINT_TOC);
}
/*
* IN Token Rexeived when TXFIFO is Emptry
* Indicates that an IN token was received when the associated
* TxFIFO was empty.
*/
if (ep_intr & USB_DEV_DIEPINT_ITTXFE) {
pr_debug("ep %X USB_DEV_DIEPINT_TOC\n", ep);
usbc_intr_clear_diepint(ep, USB_DEV_DIEPINT_ITTXFE);
}
/*
* IN EP NAK Effective
* The interrupt indicates that the IN EP NAK bit set by the
* application has taken effect in the core.
*/
if (ep_intr & USB_DEV_DIEPINT_INEPNE) {
pr_debug("ep %X USB_DEV_DIEPINT_INEPNE\n", ep);
usbc_intr_clear_diepint(ep, USB_DEV_DIEPINT_INEPNE);
}
/*
* EP Disabled
* This bit indicates that the EP is disabled per the
* application's request.
*/
if (ep_intr & USB_DEV_DIEPINT_EPDISD) {
pr_debug("ep %X USB_DEV_DIEPINT_EPDISD\n", ep);
usbc_intr_clear_diepint(ep, USB_DEV_DIEPINT_EPDISD);
}
/*
* TXFIFO Empty
* Indicates that the TxFIFO for this EP is either half or
* completely empty.
*/
if (ep_intr & USB_DEV_DIEPINT_TXFE) {
pr_debug("ep %X USB_DEV_DIEPINT_TXFE\n", ep);
usbc_intr_clear_diepint(ep, USB_DEV_DIEPINT_TXFE);
}
}
}
void aic_udc_state_loop(void)
{
struct aic_udc *udc = &aic_udc;
u32 gintsts, rxlen, ep_id;
if (aic_udc.reset_conn) {
reset_connection();
return;
}
/* Read status registers */
gintsts = usbc_intr_get_gintsts();
if (gintsts & USB_DEV_GINTSTS_USBRST)
reset_intr_proc();
if (gintsts & USB_DEV_GINTSTS_ENUMDNE)
speed_enum_done_intr_proc();
if (gintsts & USB_DEV_GINTSTS_RXFLVL) {
rxlen = rxfifo_data_intr_proc(udc->buf.rx_buf, &ep_id);
/*
* Maybe is not data receive event, check here
*/
if (rxlen > 0) {
udc->buf.rx_len = rxlen;
udc->buf.ep_id = ep_id;
}
}
if (gintsts & USB_DEV_GINTSTS_NPTXFE)
np_txfifo_empty_intr_proc();
/* Read status again after tx/rx fifo proc */
gintsts = usbc_intr_get_gintsts();
if (gintsts & USB_DEV_GINTSTS_IEPINT)
in_ep_intr_proc();
if (gintsts & USB_DEV_GINTSTS_OEPINT)
out_ep_intr_proc(&udc->buf);
}
u32 aic_udc_ctrl_ep_send(u8 *buf, u32 len)
{
u32 pkt_siz, sndlen, all_todo = 0, pkt_todo = 0;
u32 maxtry = 100;
if (len == 0) {
/*
* Configure xfer pkt and size
*/
usbc_in_ctrl_ep_xfer_cfg(3, 0);
return 0;
}
all_todo = len;
while (all_todo > 0) {
/*
* One packet one time for EP0
*/
if (all_todo > CTL_EP0_MPS)
pkt_siz = CTL_EP0_MPS;
else
pkt_siz = all_todo;
usbc_in_ctrl_ep_xfer_cfg(1, pkt_siz);
pkt_todo = pkt_siz;
maxtry = 10000;
do {
sndlen = usbc_ctrl_ep_send_data(buf, pkt_todo);
if (sndlen == 0) {
aicos_udelay(1);
maxtry--;
continue;
}
pkt_todo -= sndlen;
buf += sndlen;
} while (pkt_todo > 0 && maxtry > 0);
all_todo -= (pkt_siz - pkt_todo);
if (pkt_todo > 0)
break; /* Not finished */
}
if (all_todo > 0) {
/* Send data timeout, Host encounter problem, just
* abort operation here.
*/
usbc_flush_all_txfifo();
}
return (len - all_todo);
}
s32 aic_udc_set_configuration(s32 config_param)
{
/*
* Send out one zero length packet as IN transaction
*/
aic_udc_ctrl_ep_send(NULL, 0);
/*
* When host set configuration, that means enumeration is done.
* Now set bulk ep to ready for CBW
*/
usbc_out_bulk_ep_xfer_cfg(BULK_OUT_EP_INDEX, 1, BULK_EP_HS_MPS);
return 0;
}
s32 aic_udc_set_address(u8 address)
{
usbc_set_address(address);
/*
* Send out one zero length packet after set new address
*/
aic_udc_ctrl_ep_send(NULL, 0);
return 0;
}
s32 aic_udc_bulk_send(u8 *buf, s32 len)
{
u32 pkt_cnt, sndlen, todo = 0, gintsts, maxtry = 100, ep;
ep = BULK_IN_EP_INDEX;
if (len == 0) {
/*
* Configure xfer pkt and size
*/
pkt_cnt = 1;
usbc_in_bulk_ep_xfer_cfg(ep, pkt_cnt, len);
return 0;
}
pkt_cnt = (len + BULK_EP_HS_MPS - 1) / BULK_EP_HS_MPS;
usbc_in_bulk_ep_xfer_cfg(ep, pkt_cnt, len);
/*
* Poll ITTXFE interrupt and send data to TX FIFO
*/
maxtry = 300000;
todo = len;
do {
sndlen = usbc_bulk_ep_send_data(ep, buf, todo);
if (sndlen == 0) {
aicos_udelay(1);
maxtry--;
continue;
}
todo -= sndlen;
buf += sndlen;
} while (todo > 0 && maxtry > 0);
if (maxtry == 0)
goto timeout;
maxtry = 30000;
do {
/*
* After write data, need to wait all data is sent out
*/
gintsts = usbc_intr_get_gintsts();
if (gintsts & USB_DEV_GINTSTS_NPTXFE) {
usbc_intr_clear_gintsts_pending(USB_DEV_GINTSTS_NPTXFE);
break;
}
aicos_udelay(1);
maxtry--;
} while (maxtry > 0);
if (maxtry == 0)
goto timeout;
return len;
timeout:
usbc_flush_all_txfifo();
return 0;
}
s32 aic_udc_bulk_recv_dma(u8 *dest, s32 len)
{
return aic_udc_bulk_recv(dest, len);
}
/*
* This API used in CBW bulk data transfer scenario, typical flow is:
* - Got CBW command, application know data size
* - Use this API to read data after CBW
*/
s32 aic_udc_bulk_recv(u8 *buf, s32 len)
{
u32 gintsts, pktcnt, todo, recvlen, ep_id, maxtry = 300000;
u32 xfersize;
if (len == 0)
return 0;
/*
* Set data pkt and size going to recv
*/
pktcnt = (len + BULK_EP_HS_MPS - 1) / BULK_EP_HS_MPS;
xfersize = len + BULK_EP_HS_MPS - (len % BULK_EP_HS_MPS);
usbc_out_bulk_ep_xfer_cfg(BULK_OUT_EP_INDEX, pktcnt, xfersize);
/*
* Wait data income interrupt
*/
todo = len;
do {
gintsts = usbc_intr_get_gintsts();
if ((gintsts & USB_DEV_GINTSTS_RXFLVL) == 0) {
aicos_udelay(1);
maxtry--;
continue;
}
recvlen = rxfifo_data_intr_proc(buf, &ep_id);
if (recvlen == 0)
continue;
/*
* If recv data not from BULK OUT EP, just drop it
*/
if (ep_id == BULK_OUT_EP_INDEX) {
todo -= recvlen;
buf += recvlen;
}
} while (todo > 0 && maxtry > 0);
return (len - todo);
}
/*
* Bulk EP stall indicates the EP is permanently halted or there is an error
* condition on the EP, and require USB system software intervention.
*/
void aic_udc_bulk_rx_stall(void)
{
usbc_out_ep_set_stall(BULK_OUT_EP_INDEX);
}
/*
* Bulk EP stall indicates the EP is permanently halted or there is an error
* condition on the EP, and require USB system software intervention.
*/
void aic_udc_bulk_tx_stall(void)
{
usbc_in_ep_set_stall(BULK_IN_EP_INDEX);
}
/*
* Send STALL to host to notify stop receiving data.
* And prepare to reset connection with host.
*/
void aic_udc_bulk_rx_fatal(void)
{
aic_udc_bulk_rx_stall();
aic_udc.reset_conn = 1;
}
/*
* Send STALL to host to notify it device stop sending data.
* And prepare to reset connection with host.
*/
void aic_udc_bulk_tx_fatal(void)
{
aic_udc_bulk_tx_stall();
aic_udc.reset_conn = 1;
}