1048 lines
24 KiB
C
1048 lines
24 KiB
C
/* usb_dc_dw.c - USB DesignWare device controller driver */
|
|
|
|
/*
|
|
* Copyright (c) 2016 Intel Corporation.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
/**
|
|
* @file
|
|
* @brief USB DesignWare device controller driver
|
|
*
|
|
* USB DesignWare device controller driver. The driver implements the low
|
|
* level control routines to deal directly with the hardware.
|
|
*/
|
|
|
|
#include <soc.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <misc/byteorder.h>
|
|
#include "usb_dc.h"
|
|
#include "usb_dw_registers.h"
|
|
#include "clk.h"
|
|
|
|
#ifndef CONFIG_USB_DW_DEBUG
|
|
#define DBG(...) { ; }
|
|
#else
|
|
#if defined(CONFIG_STDOUT_CONSOLE)
|
|
#include <stdio.h>
|
|
#define DBG printf
|
|
#else
|
|
#define DBG printk
|
|
#endif /* CONFIG_STDOUT_CONSOLE */
|
|
#endif /* CONFIG_USB_DW_DEBUG */
|
|
|
|
/* convert from endpoint address to hardware endpoint index */
|
|
#define USB_DW_EP_ADDR2IDX(ep) ((ep) & ~USB_EP_DIR_MASK)
|
|
/* get direction from endpoint address */
|
|
#define USB_DW_EP_ADDR2DIR(ep) ((ep) & USB_EP_DIR_MASK)
|
|
/* convert from hardware endpoint index and direction to endpoint address */
|
|
#define USB_DW_EP_IDX2ADDR(idx, dir) ((idx) | ((dir) & USB_EP_DIR_MASK))
|
|
|
|
/* Number of SETUP back-to-back packets */
|
|
#define USB_DW_SUP_CNT (1)
|
|
|
|
/*
|
|
* USB endpoint private structure.
|
|
*/
|
|
struct usb_ep_ctrl_prv {
|
|
uint8_t ep_ena;
|
|
uint16_t mps; /* Max ep pkt size */
|
|
usb_dc_ep_callback cb;/* Endpoint callback function */
|
|
uint32_t data_len;
|
|
};
|
|
|
|
/*
|
|
* USB controller private structure.
|
|
*/
|
|
struct usb_dw_ctrl_prv {
|
|
usb_dc_status_callback status_cb;
|
|
struct usb_ep_ctrl_prv in_ep_ctrl[USB_DW_IN_EP_NUM];
|
|
struct usb_ep_ctrl_prv out_ep_ctrl[USB_DW_OUT_EP_NUM];
|
|
uint8_t attached;
|
|
};
|
|
|
|
|
|
static struct usb_dw_ctrl_prv usb_dw_ctrl;
|
|
|
|
static inline void _usb_dw_int_unmask(void)
|
|
{
|
|
#if defined(CONFIG_SOC_QUARK_SE)
|
|
QM_SCSS_INT->int_usb_mask &= ~BIT(0);
|
|
#endif
|
|
}
|
|
|
|
#ifdef CONFIG_USB_DW_DEBUG
|
|
static void usb_dw_reg_dump(void)
|
|
{
|
|
uint8_t i;
|
|
|
|
DBG("USB registers:\n");
|
|
DBG(" GOTGCTL : 0x%x\n", USB_DW->gotgctl);
|
|
DBG(" GOTGINT : 0x%x\n", USB_DW->gotgint);
|
|
DBG(" GAHBCFG : 0x%x\n", USB_DW->gahbcfg);
|
|
DBG(" GUSBCFG : 0x%x\n", USB_DW->gusbcfg);
|
|
DBG(" GINTSTS : 0x%x\n", USB_DW->gintsts);
|
|
DBG(" GINTMSK : 0x%x\n", USB_DW->gintmsk);
|
|
DBG(" DCFG : 0x%x\n", USB_DW->dcfg);
|
|
DBG(" DCTL : 0x%x\n", USB_DW->dctl);
|
|
DBG(" DSTS : 0x%x\n", USB_DW->dsts);
|
|
DBG(" DIEPMSK : 0x%x\n", USB_DW->diepmsk);
|
|
DBG(" DOEPMSK : 0x%x\n", USB_DW->doepmsk);
|
|
DBG(" DAINT : 0x%x\n", USB_DW->daint);
|
|
DBG(" DAINTMSK: 0x%x\n", USB_DW->daintmsk);
|
|
DBG(" GHWCFG1 : 0x%x\n", USB_DW->ghwcfg1);
|
|
DBG(" GHWCFG2 : 0x%x\n", USB_DW->ghwcfg2);
|
|
DBG(" GHWCFG3 : 0x%x\n", USB_DW->ghwcfg3);
|
|
DBG(" GHWCFG4 : 0x%x\n", USB_DW->ghwcfg4);
|
|
|
|
for (i = 0; i < USB_DW_OUT_EP_NUM; i++) {
|
|
DBG("\n EP %d registers:\n", i);
|
|
DBG(" DIEPCTL : 0x%x\n",
|
|
USB_DW->in_ep_reg[i].diepctl);
|
|
DBG(" DIEPINT : 0x%x\n",
|
|
USB_DW->in_ep_reg[i].diepint);
|
|
DBG(" DIEPTSIZ: 0x%x\n",
|
|
USB_DW->in_ep_reg[i].dieptsiz);
|
|
DBG(" DIEPDMA : 0x%x\n",
|
|
USB_DW->in_ep_reg[i].diepdma);
|
|
DBG(" DOEPCTL : 0x%x\n",
|
|
USB_DW->out_ep_reg[i].doepctl);
|
|
DBG(" DOEPINT : 0x%x\n",
|
|
USB_DW->out_ep_reg[i].doepint);
|
|
DBG(" DOEPTSIZ: 0x%x\n",
|
|
USB_DW->out_ep_reg[i].doeptsiz);
|
|
DBG(" DOEPDMA : 0x%x\n",
|
|
USB_DW->out_ep_reg[i].doepdma);
|
|
}
|
|
}
|
|
#else
|
|
#define usb_dw_reg_dump()
|
|
#endif
|
|
|
|
static uint8_t usb_dw_ep_is_valid(uint8_t ep)
|
|
{
|
|
uint8_t ep_idx = USB_DW_EP_ADDR2IDX(ep);
|
|
|
|
/* Check if ep enabled */
|
|
if ((USB_DW_EP_ADDR2DIR(ep) == USB_EP_DIR_OUT) &&
|
|
ep_idx < USB_DW_OUT_EP_NUM) {
|
|
return 1;
|
|
} else if ((USB_DW_EP_ADDR2DIR(ep) == USB_EP_DIR_IN) &&
|
|
ep_idx < USB_DW_IN_EP_NUM) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static uint8_t usb_dw_ep_is_enabled(uint8_t ep)
|
|
{
|
|
uint8_t ep_idx = USB_DW_EP_ADDR2IDX(ep);
|
|
|
|
/* Check if ep enabled */
|
|
if ((USB_DW_EP_ADDR2DIR(ep) == USB_EP_DIR_OUT) &&
|
|
usb_dw_ctrl.out_ep_ctrl[ep_idx].ep_ena) {
|
|
return 1;
|
|
} else if ((USB_DW_EP_ADDR2DIR(ep) == USB_EP_DIR_IN) &&
|
|
usb_dw_ctrl.in_ep_ctrl[ep_idx].ep_ena) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline void usb_dw_udelay(uint32_t us)
|
|
{
|
|
sys_thread_busy_wait(us);
|
|
}
|
|
|
|
static int usb_dw_reset(void)
|
|
{
|
|
uint32_t cnt = 0;
|
|
|
|
/* Wait for AHB master idle state. */
|
|
while (!(USB_DW->grstctl & USB_DW_GRSTCTL_AHB_IDLE)) {
|
|
usb_dw_udelay(1);
|
|
if (++cnt > USB_DW_CORE_RST_TIMEOUT_US) {
|
|
DBG("USB reset HANG! AHB Idle GRSTCTL=0x%08x\n",
|
|
USB_DW->grstctl);
|
|
return -EIO;
|
|
}
|
|
}
|
|
|
|
/* Core Soft Reset */
|
|
cnt = 0;
|
|
USB_DW->grstctl |= USB_DW_GRSTCTL_C_SFT_RST;
|
|
do {
|
|
if (++cnt > USB_DW_CORE_RST_TIMEOUT_US) {
|
|
DBG("USB reset HANG! Soft Reset GRSTCTL=0x%08x\n",
|
|
USB_DW->grstctl);
|
|
return -EIO;
|
|
}
|
|
usb_dw_udelay(1);
|
|
} while (USB_DW->grstctl & USB_DW_GRSTCTL_C_SFT_RST);
|
|
|
|
/* Wait for 3 PHY Clocks */
|
|
usb_dw_udelay(100);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int usb_dw_clock_enable(void)
|
|
{
|
|
#if defined(CONFIG_SOC_QUARK_SE)
|
|
/* 7.2.7 USB Clock Operation */
|
|
clk_sys_set_mode(CLK_SYS_CRYSTAL_OSC, CLK_SYS_DIV_1);
|
|
|
|
/* Enable the USB Clock */
|
|
QM_SCSS_CCU->ccu_mlayer_ahb_ctl |= QM_CCU_USB_CLK_EN;
|
|
|
|
/* Set up the PLL. */
|
|
QM_USB_PLL_CFG0 = QM_USB_PLL_CFG0_DEFAULT | QM_USB_PLL_PDLD;
|
|
|
|
/* Wait for the PLL lock */
|
|
while (0 == (QM_USB_PLL_CFG0 & QM_USB_PLL_LOCK)) {
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static void usb_dw_clock_disable(void)
|
|
{
|
|
#if defined(CONFIG_SOC_QUARK_SE)
|
|
/* Disable the USB Clock */
|
|
QM_SCSS_CCU->ccu_mlayer_ahb_ctl &= ~QM_CCU_USB_CLK_EN;
|
|
|
|
/* Disable the PLL */
|
|
QM_USB_PLL_CFG0 &= ~QM_USB_PLL_PDLD;
|
|
#endif
|
|
}
|
|
|
|
static int usb_dw_ep_set(uint8_t ep,
|
|
uint32_t ep_mps, enum usb_dc_ep_type ep_type)
|
|
{
|
|
volatile uint32_t *p_depctl;
|
|
uint8_t ep_idx = USB_DW_EP_ADDR2IDX(ep);
|
|
|
|
DBG("usb_dw_ep_set ep %x, mps %d, type %d\n", ep, ep_mps, ep_type);
|
|
|
|
if (USB_DW_EP_ADDR2DIR(ep) == USB_EP_DIR_OUT) {
|
|
p_depctl = &USB_DW->out_ep_reg[ep_idx].doepctl;
|
|
usb_dw_ctrl.out_ep_ctrl[ep_idx].mps = ep_mps;
|
|
} else {
|
|
p_depctl = &USB_DW->in_ep_reg[ep_idx].diepctl;
|
|
usb_dw_ctrl.in_ep_ctrl[ep_idx].mps = ep_mps;
|
|
}
|
|
|
|
if (!ep_idx) {
|
|
/* Set max packet size for EP0 */
|
|
*p_depctl &= ~USB_DW_DEPCTL0_MSP_MASK;
|
|
switch (ep_mps) {
|
|
case 8:
|
|
*p_depctl |= USB_DW_DEPCTL0_MSP_8 <<
|
|
USB_DW_DEPCTL_MSP_OFFSET;
|
|
break;
|
|
case 16:
|
|
*p_depctl |= USB_DW_DEPCTL0_MSP_16 <<
|
|
USB_DW_DEPCTL_MSP_OFFSET;
|
|
break;
|
|
case 32:
|
|
*p_depctl |= USB_DW_DEPCTL0_MSP_32 <<
|
|
USB_DW_DEPCTL_MSP_OFFSET;
|
|
break;
|
|
case 64:
|
|
*p_depctl |= USB_DW_DEPCTL0_MSP_64 <<
|
|
USB_DW_DEPCTL_MSP_OFFSET;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
/* No need to set EP0 type */
|
|
} else {
|
|
/* Set max packet size for EP */
|
|
if (ep_mps > (USB_DW_DEPCTLn_MSP_MASK >>
|
|
USB_DW_DEPCTL_MSP_OFFSET)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
*p_depctl &= ~USB_DW_DEPCTLn_MSP_MASK;
|
|
*p_depctl |= ep_mps << USB_DW_DEPCTL_MSP_OFFSET;
|
|
|
|
/* Set endpoint type */
|
|
*p_depctl &= ~USB_DW_DEPCTL_EP_TYPE_MASK;
|
|
switch (ep_type) {
|
|
case USB_DC_EP_CONTROL:
|
|
*p_depctl |= USB_DW_DEPCTL_EP_TYPE_CONTROL <<
|
|
USB_DW_DEPCTL_EP_TYPE_OFFSET;
|
|
break;
|
|
case USB_DC_EP_BULK:
|
|
*p_depctl |= USB_DW_DEPCTL_EP_TYPE_BULK <<
|
|
USB_DW_DEPCTL_EP_TYPE_OFFSET;
|
|
break;
|
|
case USB_DC_EP_INTERRUPT:
|
|
*p_depctl |= USB_DW_DEPCTL_EP_TYPE_INTERRUPT <<
|
|
USB_DW_DEPCTL_EP_TYPE_OFFSET;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* sets the Endpoint Data PID to DATA0 */
|
|
*p_depctl |= USB_DW_DEPCTL_SETDOPID;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void usb_dw_prep_rx(const uint8_t ep, uint8_t setup)
|
|
{
|
|
enum usb_dw_out_ep_idx ep_idx = USB_DW_EP_ADDR2IDX(ep);
|
|
uint32_t ep_mps = usb_dw_ctrl.out_ep_ctrl[ep_idx].mps;
|
|
|
|
/* Set max RX size to EP mps so we get an interrupt
|
|
* each time a packet is received
|
|
*/
|
|
|
|
USB_DW->out_ep_reg[ep_idx].doeptsiz =
|
|
(USB_DW_SUP_CNT << USB_DW_DOEPTSIZ_SUP_CNT_OFFSET) |
|
|
(1 << USB_DW_DEPTSIZ_PKT_CNT_OFFSET) | ep_mps;
|
|
|
|
/* Clear NAK and enable ep */
|
|
if (!setup) {
|
|
USB_DW->out_ep_reg[ep_idx].doepctl |= USB_DW_DEPCTL_CNAK;
|
|
}
|
|
USB_DW->out_ep_reg[ep_idx].doepctl |= USB_DW_DEPCTL_EP_ENA;
|
|
|
|
DBG("USB OUT EP%d armed\n", ep_idx);
|
|
}
|
|
|
|
static int usb_dw_tx(uint8_t ep, const uint8_t *const data,
|
|
uint32_t data_len)
|
|
{
|
|
enum usb_dw_in_ep_idx ep_idx = USB_DW_EP_ADDR2IDX(ep);
|
|
uint32_t max_xfer_size, max_pkt_cnt, pkt_cnt, avail_space;
|
|
uint32_t ep_mps = usb_dw_ctrl.in_ep_ctrl[ep_idx].mps;
|
|
unsigned int key;
|
|
int i;
|
|
|
|
/* Check if FIFO space available */
|
|
avail_space = USB_DW->in_ep_reg[ep_idx].dtxfsts &
|
|
USB_DW_DTXFSTS_TXF_SPC_AVAIL_MASK;
|
|
avail_space *= 4;
|
|
if (!avail_space) {
|
|
DBG("USB IN EP%d no space available, DTXFSTS %x\n",
|
|
ep_idx, USB_DW->in_ep_reg[ep_idx].dtxfsts);
|
|
return -EAGAIN;
|
|
}
|
|
|
|
if (data_len > avail_space) {
|
|
data_len = avail_space;
|
|
}
|
|
|
|
if (data_len != 0) {
|
|
/* Get max packet size and packet count for ep */
|
|
if (ep_idx == USB_DW_IN_EP_0) {
|
|
max_pkt_cnt =
|
|
USB_DW_DIEPTSIZ0_PKT_CNT_MASK >>
|
|
USB_DW_DEPTSIZ_PKT_CNT_OFFSET;
|
|
max_xfer_size =
|
|
USB_DW_DEPTSIZ0_XFER_SIZE_MASK >>
|
|
USB_DW_DEPTSIZ_XFER_SIZE_OFFSET;
|
|
} else {
|
|
max_pkt_cnt =
|
|
USB_DW_DIEPTSIZn_PKT_CNT_MASK >>
|
|
USB_DW_DEPTSIZ_PKT_CNT_OFFSET;
|
|
max_xfer_size =
|
|
USB_DW_DEPTSIZn_XFER_SIZE_MASK >>
|
|
USB_DW_DEPTSIZ_XFER_SIZE_OFFSET;
|
|
}
|
|
|
|
/* Check if transfer len is too big */
|
|
if (data_len > max_xfer_size) {
|
|
DBG("USB IN EP%d len too big (%d->%d)\n", ep_idx,
|
|
data_len, max_xfer_size);
|
|
data_len = max_xfer_size;
|
|
}
|
|
|
|
/*
|
|
* Program the transfer size and packet count as follows:
|
|
*
|
|
* transfer size = N * ep_maxpacket + short_packet
|
|
* pktcnt = N + (short_packet exist ? 1 : 0)
|
|
*/
|
|
|
|
pkt_cnt = (data_len + ep_mps - 1) / ep_mps;
|
|
|
|
if (pkt_cnt > max_pkt_cnt) {
|
|
DBG("USB IN EP%d pkt count too big (%d->%d)\n",
|
|
ep_idx, pkt_cnt, pkt_cnt);
|
|
pkt_cnt = max_pkt_cnt;
|
|
data_len = pkt_cnt * ep_mps;
|
|
}
|
|
} else {
|
|
/* Zero length packet */
|
|
pkt_cnt = 1;
|
|
}
|
|
|
|
/* Set number of packets and transfer size */
|
|
USB_DW->in_ep_reg[ep_idx].dieptsiz =
|
|
(pkt_cnt << USB_DW_DEPTSIZ_PKT_CNT_OFFSET) | data_len;
|
|
|
|
/* Clear NAK and enable ep */
|
|
USB_DW->in_ep_reg[ep_idx].diepctl |= USB_DW_DEPCTL_CNAK;
|
|
USB_DW->in_ep_reg[ep_idx].diepctl |= USB_DW_DEPCTL_EP_ENA;
|
|
|
|
/*
|
|
* Write data to FIFO, make sure that we are protected against
|
|
* other USB register accesses. According to "DesignWare Cores
|
|
* USB 1.1/2.0 Device Subsystem-AHB/VCI Databook": "During FIFO
|
|
* access, the application must not access the UDC/Subsystem
|
|
* registers or vendor registers (for ULPI mode). After starting
|
|
* to access a FIFO, the application must complete the transaction
|
|
* before accessing the register."
|
|
*/
|
|
key = irq_lock();
|
|
for (i = 0; i < data_len; i += 4) {
|
|
USB_DW_EP_FIFO(ep_idx) = *(uint32_t *)(data + i);
|
|
}
|
|
irq_unlock(key);
|
|
|
|
DBG("USB IN EP%d write %x bytes\n", ep_idx, data_len);
|
|
|
|
return data_len;
|
|
}
|
|
|
|
static int usb_dw_init(void)
|
|
{
|
|
uint8_t ep;
|
|
int ret;
|
|
|
|
ret = usb_dw_reset();
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
/* Set device speed to FS */
|
|
USB_DW->dcfg |= USB_DW_DCFG_DEV_SPD_FS;
|
|
|
|
/* No FIFO setup needed, the default values are used */
|
|
|
|
/* Set NAK for all OUT EPs */
|
|
for (ep = 0; ep < USB_DW_OUT_EP_NUM; ep++) {
|
|
USB_DW->out_ep_reg[ep].doepctl = USB_DW_DEPCTL_SNAK;
|
|
}
|
|
|
|
/* Enable global interrupts */
|
|
USB_DW->gintmsk = USB_DW_GINTSTS_OEP_INT |
|
|
USB_DW_GINTSTS_IEP_INT |
|
|
USB_DW_GINTSTS_ENUM_DONE |
|
|
USB_DW_GINTSTS_USB_RST |
|
|
USB_DW_GINTSTS_WK_UP_INT |
|
|
USB_DW_GINTSTS_USB_SUSP;
|
|
|
|
/* Enable global interrupt */
|
|
USB_DW->gahbcfg |= USB_DW_GAHBCFG_GLB_INTR_MASK;
|
|
|
|
/* Disable soft disconnect */
|
|
USB_DW->dctl &= ~USB_DW_DCTL_SFT_DISCON;
|
|
|
|
usb_dw_reg_dump();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void usb_dw_handle_reset(void)
|
|
{
|
|
DBG("USB RESET event\n");
|
|
|
|
/* Inform upper layers */
|
|
if (usb_dw_ctrl.status_cb) {
|
|
usb_dw_ctrl.status_cb(USB_DC_RESET);
|
|
}
|
|
|
|
/* enable global EP interrupts */
|
|
USB_DW->doepmsk = 0;
|
|
USB_DW->gintmsk |= USB_DW_GINTSTS_RX_FLVL;
|
|
USB_DW->diepmsk |= USB_DW_DIEPINT_XFER_COMPL;
|
|
}
|
|
|
|
static void usb_dw_handle_enum_done(void)
|
|
{
|
|
uint32_t speed;
|
|
|
|
speed = (USB_DW->dsts & ~USB_DW_DSTS_ENUM_SPD_MASK) >>
|
|
USB_DW_DSTS_ENUM_SPD_OFFSET;
|
|
|
|
DBG("USB ENUM DONE event, %s speed detected\n",
|
|
speed == USB_DW_DSTS_ENUM_LS ? "Low" : "Full");
|
|
|
|
/* Inform upper layers */
|
|
if (usb_dw_ctrl.status_cb) {
|
|
usb_dw_ctrl.status_cb(USB_DC_CONNECTED);
|
|
}
|
|
}
|
|
|
|
/* USB ISR handler */
|
|
static void usb_dw_isr_handler(void)
|
|
{
|
|
uint32_t int_status, ep_int_status;
|
|
uint8_t ep_idx;
|
|
usb_dc_ep_callback ep_cb;
|
|
|
|
/* Read interrupt status */
|
|
while ((int_status = (USB_DW->gintsts & USB_DW->gintmsk))) {
|
|
|
|
DBG("USB GINTSTS 0x%x\n", int_status);
|
|
|
|
if (int_status & USB_DW_GINTSTS_USB_RST) {
|
|
/* Clear interrupt. */
|
|
USB_DW->gintsts = USB_DW_GINTSTS_USB_RST;
|
|
|
|
/* Reset detected */
|
|
usb_dw_handle_reset();
|
|
}
|
|
|
|
if (int_status & USB_DW_GINTSTS_ENUM_DONE) {
|
|
/* Clear interrupt. */
|
|
USB_DW->gintsts = USB_DW_GINTSTS_ENUM_DONE;
|
|
|
|
/* Enumeration done detected */
|
|
usb_dw_handle_enum_done();
|
|
}
|
|
|
|
if (int_status & USB_DW_GINTSTS_USB_SUSP) {
|
|
/* Clear interrupt. */
|
|
USB_DW->gintsts = USB_DW_GINTSTS_USB_SUSP;
|
|
|
|
if (usb_dw_ctrl.status_cb) {
|
|
usb_dw_ctrl.status_cb(USB_DC_SUSPEND);
|
|
}
|
|
}
|
|
|
|
if (int_status & USB_DW_GINTSTS_WK_UP_INT) {
|
|
/* Clear interrupt. */
|
|
USB_DW->gintsts = USB_DW_GINTSTS_WK_UP_INT;
|
|
|
|
if (usb_dw_ctrl.status_cb) {
|
|
usb_dw_ctrl.status_cb(USB_DC_RESUME);
|
|
}
|
|
}
|
|
|
|
if (int_status & USB_DW_GINTSTS_RX_FLVL) {
|
|
/* Packet in RX FIFO */
|
|
uint32_t status, xfer_size;
|
|
uint32_t grxstsp = USB_DW->grxstsp;
|
|
|
|
ep_idx = grxstsp & USB_DW_GRXSTSR_EP_NUM_MASK;
|
|
status = (grxstsp & USB_DW_GRXSTSR_PKT_STS_MASK) >>
|
|
USB_DW_GRXSTSR_PKT_STS_OFFSET;
|
|
xfer_size = (grxstsp & USB_DW_GRXSTSR_PKT_CNT_MASK) >>
|
|
USB_DW_GRXSTSR_PKT_CNT_OFFSET;
|
|
|
|
DBG("USB OUT EP%d: RX_FLVL status %d, size %d\n",
|
|
ep_idx, status, xfer_size);
|
|
usb_dw_ctrl.out_ep_ctrl[ep_idx].data_len = xfer_size;
|
|
ep_cb = usb_dw_ctrl.out_ep_ctrl[ep_idx].cb;
|
|
switch (status) {
|
|
case USB_DW_GRXSTSR_PKT_STS_SETUP:
|
|
/* Call the registered callback if any */
|
|
if (ep_cb) {
|
|
ep_cb(USB_DW_EP_IDX2ADDR(ep_idx,
|
|
USB_EP_DIR_OUT),
|
|
USB_DC_EP_SETUP);
|
|
}
|
|
break;
|
|
case USB_DW_GRXSTSR_PKT_STS_OUT_DATA:
|
|
if (ep_cb) {
|
|
ep_cb(USB_DW_EP_IDX2ADDR(ep_idx,
|
|
USB_EP_DIR_OUT),
|
|
USB_DC_EP_DATA_OUT);
|
|
}
|
|
break;
|
|
case USB_DW_GRXSTSR_PKT_STS_OUT_DATA_DONE:
|
|
case USB_DW_GRXSTSR_PKT_STS_SETUP_DONE:
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (int_status & USB_DW_GINTSTS_IEP_INT) {
|
|
/* IN EP interrupt */
|
|
for (ep_idx = 0; ep_idx < USB_DW_IN_EP_NUM; ep_idx++) {
|
|
if (USB_DW->daint &
|
|
USB_DW_DAINT_IN_EP_INT(ep_idx)) {
|
|
/* Read IN EP interrupt status */
|
|
ep_int_status =
|
|
USB_DW->in_ep_reg[ep_idx].diepint &
|
|
USB_DW->diepmsk;
|
|
|
|
/* Clear IN EP interrupts */
|
|
USB_DW->in_ep_reg[ep_idx].diepint =
|
|
ep_int_status;
|
|
|
|
DBG("USB IN EP%d interrupt status: "
|
|
"0x%x\n", ep_idx, ep_int_status);
|
|
|
|
ep_cb =
|
|
usb_dw_ctrl.in_ep_ctrl[ep_idx].cb;
|
|
|
|
if ((ep_int_status &
|
|
USB_DW_DIEPINT_XFER_COMPL) &&
|
|
ep_cb) {
|
|
|
|
/* Call the registered
|
|
* callback
|
|
*/
|
|
ep_cb(USB_DW_EP_IDX2ADDR(ep_idx,
|
|
USB_EP_DIR_IN),
|
|
USB_DC_EP_DATA_IN);
|
|
}
|
|
}
|
|
}
|
|
/* Clear interrupt. */
|
|
USB_DW->gintsts = USB_DW_GINTSTS_IEP_INT;
|
|
}
|
|
|
|
if (int_status & USB_DW_GINTSTS_OEP_INT) {
|
|
/* No OUT interrupt expected in FIFO mode,
|
|
* just clear interruot
|
|
*/
|
|
for (ep_idx = 0; ep_idx < USB_DW_OUT_EP_NUM; ep_idx++) {
|
|
if (USB_DW->daint &
|
|
USB_DW_DAINT_OUT_EP_INT(ep_idx)) {
|
|
/* Read OUT EP interrupt status */
|
|
ep_int_status =
|
|
USB_DW->out_ep_reg[ep_idx].doepint &
|
|
USB_DW->doepmsk;
|
|
|
|
/* Clear OUT EP interrupts */
|
|
USB_DW->out_ep_reg[ep_idx].doepint =
|
|
ep_int_status;
|
|
|
|
DBG("USB OUT EP%d interrupt status: "
|
|
"0x%x\n", ep_idx, ep_int_status);
|
|
}
|
|
}
|
|
/* Clear interrupt. */
|
|
USB_DW->gintsts = USB_DW_GINTSTS_OEP_INT;
|
|
}
|
|
}
|
|
}
|
|
|
|
int usb_dc_attach(void)
|
|
{
|
|
int ret;
|
|
|
|
if (usb_dw_ctrl.attached) {
|
|
return 0;
|
|
}
|
|
|
|
ret = usb_dw_clock_enable();
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
ret = usb_dw_init();
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
/* Connect and enable USB interrupt */
|
|
IRQ_CONNECT(USB_DW_IRQ, CONFIG_USB_DW_IRQ_PRI,
|
|
usb_dw_isr_handler, 0, IOAPIC_EDGE | IOAPIC_HIGH);
|
|
irq_enable(USB_DW_IRQ);
|
|
|
|
_usb_dw_int_unmask();
|
|
|
|
usb_dw_ctrl.attached = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_detach(void)
|
|
{
|
|
if (!usb_dw_ctrl.attached) {
|
|
return 0;
|
|
}
|
|
|
|
usb_dw_clock_disable();
|
|
|
|
irq_disable(USB_DW_IRQ);
|
|
|
|
/* Enable soft disconnect */
|
|
USB_DW->dctl |= USB_DW_DCTL_SFT_DISCON;
|
|
|
|
usb_dw_ctrl.attached = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_reset(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = usb_dw_reset();
|
|
|
|
/* Clear private data */
|
|
memset(&usb_dw_ctrl, 0, sizeof(usb_dw_ctrl));
|
|
|
|
return ret;
|
|
}
|
|
|
|
int usb_dc_set_address(const uint8_t addr)
|
|
{
|
|
if (addr > (USB_DW_DCFG_DEV_ADDR_MASK >> USB_DW_DCFG_DEV_ADDR_OFFSET)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
USB_DW->dcfg &= ~USB_DW_DCFG_DEV_ADDR_MASK;
|
|
USB_DW->dcfg |= addr << USB_DW_DCFG_DEV_ADDR_OFFSET;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_ep_configure(const struct usb_dc_ep_cfg_data * const ep_cfg)
|
|
{
|
|
if (!usb_dw_ctrl.attached && !usb_dw_ep_is_valid(ep_cfg->ep_addr)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
usb_dw_ep_set(ep_cfg->ep_addr, ep_cfg->ep_mps, ep_cfg->ep_type);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_ep_set_stall(const uint8_t ep)
|
|
{
|
|
uint8_t ep_idx = USB_DW_EP_ADDR2IDX(ep);
|
|
|
|
if (!usb_dw_ctrl.attached && !usb_dw_ep_is_valid(ep)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (USB_DW_EP_ADDR2DIR(ep) == USB_EP_DIR_OUT) {
|
|
USB_DW->out_ep_reg[ep_idx].doepctl |= USB_DW_DEPCTL_STALL;
|
|
} else {
|
|
USB_DW->in_ep_reg[ep_idx].diepctl |= USB_DW_DEPCTL_STALL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_ep_clear_stall(const uint8_t ep)
|
|
{
|
|
uint8_t ep_idx = USB_DW_EP_ADDR2IDX(ep);
|
|
|
|
if (!usb_dw_ctrl.attached && !usb_dw_ep_is_valid(ep)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!ep_idx) {
|
|
/* Not possible to clear stall for EP0 */
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (USB_DW_EP_ADDR2DIR(ep) == USB_EP_DIR_OUT) {
|
|
USB_DW->out_ep_reg[ep_idx].doepctl &= ~USB_DW_DEPCTL_STALL;
|
|
} else {
|
|
USB_DW->in_ep_reg[ep_idx].diepctl &= ~USB_DW_DEPCTL_STALL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_ep_halt(const uint8_t ep)
|
|
{
|
|
uint8_t ep_idx = USB_DW_EP_ADDR2IDX(ep);
|
|
volatile uint32_t *p_depctl;
|
|
|
|
|
|
if (!usb_dw_ctrl.attached && !usb_dw_ep_is_valid(ep)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!ep_idx) {
|
|
/* Cannot disable EP0, just set stall */
|
|
usb_dc_ep_set_stall(ep);
|
|
} else {
|
|
if (USB_DW_EP_ADDR2DIR(ep) == USB_EP_DIR_OUT) {
|
|
p_depctl = &USB_DW->out_ep_reg[ep_idx].doepctl;
|
|
} else {
|
|
p_depctl = &USB_DW->in_ep_reg[ep_idx].diepctl;
|
|
}
|
|
|
|
/* Set STALL and disable endpoint if enabled */
|
|
if (*p_depctl & USB_DW_DEPCTL_EP_ENA) {
|
|
*p_depctl |= USB_DW_DEPCTL_EP_DIS | USB_DW_DEPCTL_STALL;
|
|
} else {
|
|
*p_depctl |= USB_DW_DEPCTL_STALL;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_ep_is_stalled(const uint8_t ep, uint8_t *const stalled)
|
|
{
|
|
uint8_t ep_idx = USB_DW_EP_ADDR2IDX(ep);
|
|
|
|
if (!usb_dw_ctrl.attached && !usb_dw_ep_is_valid(ep)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!stalled) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
*stalled = 0;
|
|
if (USB_DW_EP_ADDR2DIR(ep) == USB_EP_DIR_OUT) {
|
|
if (USB_DW->out_ep_reg[ep_idx].doepctl & USB_DW_DEPCTL_STALL) {
|
|
*stalled = 1;
|
|
}
|
|
} else {
|
|
if (USB_DW->in_ep_reg[ep_idx].diepctl & USB_DW_DEPCTL_STALL) {
|
|
*stalled = 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_ep_enable(const uint8_t ep)
|
|
{
|
|
uint8_t ep_idx = USB_DW_EP_ADDR2IDX(ep);
|
|
|
|
if (!usb_dw_ctrl.attached && !usb_dw_ep_is_valid(ep)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* enable EP interrupts */
|
|
if (USB_DW_EP_ADDR2DIR(ep) == USB_EP_DIR_OUT) {
|
|
USB_DW->daintmsk |= USB_DW_DAINT_OUT_EP_INT(ep_idx);
|
|
} else {
|
|
USB_DW->daintmsk |= USB_DW_DAINT_IN_EP_INT(ep_idx);
|
|
}
|
|
|
|
/* Activate Ep */
|
|
if (USB_DW_EP_ADDR2DIR(ep) == USB_EP_DIR_OUT) {
|
|
USB_DW->out_ep_reg[ep_idx].doepctl |= USB_DW_DEPCTL_USB_ACT_EP;
|
|
usb_dw_ctrl.out_ep_ctrl[ep_idx].ep_ena = 1;
|
|
} else {
|
|
USB_DW->in_ep_reg[ep_idx].diepctl |= USB_DW_DEPCTL_USB_ACT_EP;
|
|
usb_dw_ctrl.in_ep_ctrl[ep_idx].ep_ena = 1;
|
|
}
|
|
|
|
if (USB_DW_EP_ADDR2DIR(ep) == USB_EP_DIR_OUT) {
|
|
/* Prepare EP for rx */
|
|
usb_dw_prep_rx(ep, 0);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_ep_disable(const uint8_t ep)
|
|
{
|
|
uint8_t ep_idx = USB_DW_EP_ADDR2IDX(ep);
|
|
|
|
/* Disable EP interrupts */
|
|
if (USB_DW_EP_ADDR2DIR(ep) == USB_EP_DIR_OUT) {
|
|
USB_DW->daintmsk &= ~USB_DW_DAINT_OUT_EP_INT(ep_idx);
|
|
USB_DW->doepmsk &= ~USB_DW_DOEPINT_SET_UP;
|
|
} else {
|
|
USB_DW->daintmsk &= ~USB_DW_DAINT_IN_EP_INT(ep_idx);
|
|
USB_DW->diepmsk &= ~USB_DW_DIEPINT_XFER_COMPL;
|
|
USB_DW->gintmsk &= ~USB_DW_GINTSTS_RX_FLVL;
|
|
}
|
|
|
|
/* De-activate, disable and set NAK for Ep */
|
|
if (USB_DW_EP_ADDR2DIR(ep) == USB_EP_DIR_OUT) {
|
|
USB_DW->out_ep_reg[ep_idx].doepctl &=
|
|
~(USB_DW_DEPCTL_USB_ACT_EP |
|
|
USB_DW_DEPCTL_EP_ENA |
|
|
USB_DW_DEPCTL_SNAK);
|
|
usb_dw_ctrl.out_ep_ctrl[ep_idx].ep_ena = 0;
|
|
} else {
|
|
USB_DW->in_ep_reg[ep_idx].diepctl &=
|
|
~(USB_DW_DEPCTL_USB_ACT_EP |
|
|
USB_DW_DEPCTL_EP_ENA |
|
|
USB_DW_DEPCTL_SNAK);
|
|
usb_dw_ctrl.in_ep_ctrl[ep_idx].ep_ena = 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_ep_flush(const uint8_t ep)
|
|
{
|
|
uint8_t ep_idx = USB_DW_EP_ADDR2IDX(ep);
|
|
uint32_t cnt;
|
|
|
|
if (!usb_dw_ctrl.attached && !usb_dw_ep_is_valid(ep)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (USB_DW_EP_ADDR2DIR(ep) == USB_EP_DIR_OUT) {
|
|
/* RX FIFO is global and cannot be flushed per EP */
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Each endpoint has dedicated Tx FIFO */
|
|
USB_DW->grstctl |= ep_idx << USB_DW_GRSTCTL_TX_FNUM_OFFSET;
|
|
USB_DW->grstctl |= USB_DW_GRSTCTL_TX_FFLSH;
|
|
|
|
cnt = 0;
|
|
do {
|
|
if (++cnt > USB_DW_CORE_RST_TIMEOUT_US) {
|
|
DBG("USB TX FIFO flush HANG!\n");
|
|
return -EIO;
|
|
}
|
|
usb_dw_udelay(1);
|
|
} while (USB_DW->grstctl & USB_DW_GRSTCTL_TX_FFLSH);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_ep_write(const uint8_t ep, const uint8_t *const data,
|
|
const uint32_t data_len, uint32_t * const ret_bytes)
|
|
{
|
|
int ret;
|
|
|
|
if (!usb_dw_ctrl.attached && !usb_dw_ep_is_valid(ep)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Check if IN ep */
|
|
if (USB_DW_EP_ADDR2DIR(ep) != USB_EP_DIR_IN) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Check if ep enabled */
|
|
if (!usb_dw_ep_is_enabled(ep)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = usb_dw_tx(ep, data, data_len);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
if (ret_bytes) {
|
|
*ret_bytes = ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_ep_read(const uint8_t ep, uint8_t *const data,
|
|
const uint32_t max_data_len, uint32_t * const read_bytes)
|
|
{
|
|
uint8_t ep_idx = USB_DW_EP_ADDR2IDX(ep);
|
|
uint32_t i, j, data_len, bytes_to_copy;
|
|
|
|
if (!usb_dw_ctrl.attached && !usb_dw_ep_is_valid(ep)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Check if OUT ep */
|
|
if (USB_DW_EP_ADDR2DIR(ep) != USB_EP_DIR_OUT) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Allow to read 0 bytes */
|
|
if (!data && max_data_len) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Check if ep enabled */
|
|
if (!usb_dw_ep_is_enabled(ep)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
data_len = usb_dw_ctrl.out_ep_ctrl[ep_idx].data_len;
|
|
|
|
if (!data && !max_data_len) {
|
|
/* When both buffer and max data to read are zero return
|
|
* the available data in buffer
|
|
*/
|
|
if (read_bytes) {
|
|
*read_bytes = data_len;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
if (data_len > max_data_len) {
|
|
DBG("Not enough room to copy all the rcvd data!\n");
|
|
bytes_to_copy = max_data_len;
|
|
} else {
|
|
bytes_to_copy = data_len;
|
|
}
|
|
|
|
DBG("Read EP%d, req %d, read %d bytes\n",
|
|
ep, max_data_len, bytes_to_copy);
|
|
|
|
/* Data in the FIFOs is always stored per 32-bit words */
|
|
for (i = 0; i < (bytes_to_copy & ~0x3); i += 4) {
|
|
*(uint32_t *)(data + i) = USB_DW_EP_FIFO(ep_idx);
|
|
}
|
|
if (bytes_to_copy & 0x3) {
|
|
/* Not multiple of 4 */
|
|
uint32_t last_dw = USB_DW_EP_FIFO(ep_idx);
|
|
|
|
for (j = 0; j < (bytes_to_copy & 0x3); j++) {
|
|
*(data + i + j) =
|
|
(sys_cpu_to_le32(last_dw) >> (8 * j)) & 0xFF;
|
|
}
|
|
}
|
|
|
|
usb_dw_ctrl.out_ep_ctrl[ep_idx].data_len -= bytes_to_copy;
|
|
|
|
if (read_bytes) {
|
|
*read_bytes = bytes_to_copy;
|
|
}
|
|
|
|
/* Prepare ep for rx if all the data where read */
|
|
if (!usb_dw_ctrl.out_ep_ctrl[ep_idx].data_len) {
|
|
usb_dw_prep_rx(ep_idx, 0);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_ep_set_callback(const uint8_t ep, const usb_dc_ep_callback cb)
|
|
{
|
|
uint8_t ep_idx = USB_DW_EP_ADDR2IDX(ep);
|
|
|
|
if (!usb_dw_ctrl.attached && !usb_dw_ep_is_valid(ep)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (USB_DW_EP_ADDR2DIR(ep) == USB_EP_DIR_IN) {
|
|
usb_dw_ctrl.in_ep_ctrl[ep_idx].cb = cb;
|
|
} else {
|
|
usb_dw_ctrl.out_ep_ctrl[ep_idx].cb = cb;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usb_dc_set_status_callback(const usb_dc_status_callback cb)
|
|
{
|
|
usb_dw_ctrl.status_cb = cb;
|
|
|
|
return 0;
|
|
}
|