OpenCloudOS-Kernel/drivers/usb/phytium/host.c

2642 lines
68 KiB
C

// SPDX-License-Identifier: GPL-2.0
#include <linux/dma-mapping.h>
#include <linux/usb.h>
#include<linux/usb/hcd.h>
#include <linux/interrupt.h>
//#include "list.h"
#include "core.h"
#include "dma.h"
#include "hw-regs.h"
#define DRV_NAME "phytium_usb"
#define HOST_GENERIC_EP_CONTROLL 0x00
#define HOST_GENERIC_EP_ISOC 0x01
#define HOST_GENERIC_EP_BULK 0x02
#define HOST_GENERIC_EP_INT 0x03
#define HOST_ESTALL 1
#define HOST_EUNHANDLED 2
#define HOST_EAUTOACK 3
#define HOST_ESHUTDOWN 4
#define HOST_EP_NUM 16
static inline struct HOST_REQ *getUsbRequestEntry(struct list_head *list)
{
return (struct HOST_REQ *)((uintptr_t)list - (uintptr_t)&(((struct HOST_REQ *)0)->list));
}
static inline struct HOST_EP_PRIV *getUsbHEpPrivEntry(struct list_head *list)
{
struct HOST_EP_PRIV *hostEpPriv;
if (list_empty(list))
return NULL;
hostEpPriv = (struct HOST_EP_PRIV *)((uintptr_t)list -
(uintptr_t)&(((struct HOST_EP_PRIV *)0)->node));
return hostEpPriv;
}
static struct HOST_REQ *getNextReq(struct HOST_EP *usbEp)
{
struct list_head *queue;
if (!usbEp)
return NULL;
queue = &usbEp->reqList;
if (list_empty(queue))
return NULL;
return getUsbRequestEntry(queue->next);
}
static void host_SetVbus(struct HOST_CTRL *priv, uint8_t isOn)
{
uint8_t otgctrl = phytium_read8(&priv->regs->otgctrl);
if (isOn) {
if (!(otgctrl & OTGCTRL_BUSREQ) || (otgctrl & OTGCTRL_ABUSDROP)) {
otgctrl &= ~OTGCTRL_ABUSDROP;
otgctrl |= OTGCTRL_BUSREQ;
phytium_write8(&priv->regs->otgctrl, otgctrl);
}
priv->otgState = HOST_OTG_STATE_A_WAIT_BCON;
} else {
if ((otgctrl & OTGCTRL_BUSREQ) || (otgctrl & OTGCTRL_ABUSDROP)) {
otgctrl |= OTGCTRL_ABUSDROP;
otgctrl &= ~OTGCTRL_BUSREQ;
phytium_write8(&priv->regs->otgctrl, otgctrl);
}
priv->otgState = HOST_OTG_STATE_A_IDLE;
}
}
static inline void disconnectHostDetect(struct HOST_CTRL *priv)
{
uint8_t otgctrl, otgstate;
uint32_t gen_cfg;
if (!priv)
return;
otgctrl = phytium_read8(&priv->regs->otgctrl);
if ((otgctrl & OTGCTRL_ASETBHNPEN) && priv->otgState == HOST_OTG_STATE_A_SUSPEND)
pr_info("Device no Response\n");
phytium_write8(&priv->regs->otgirq, OTGIRQ_CONIRQ);
retry:
otgstate = phytium_read8(&priv->regs->otgstate);
if ((otgstate == HOST_OTG_STATE_A_HOST || otgstate == HOST_OTG_STATE_B_HOST)) {
pr_info("IRQ OTG: DisconnIrq Babble\n");
goto retry;
}
phytium_write8(&priv->regs->endprst, ENDPRST_IO_TX);
phytium_write8(&priv->regs->endprst, ENDPRST_FIFORST | ENDPRST_TOGRST | ENDPRST_IO_TX);
phytium_write8(&priv->regs->endprst, ENDPRST_FIFORST | ENDPRST_TOGRST);
phytium_write8(&priv->regs->ep0fifoctrl, FIFOCTRL_FIFOAUTO | 0 | 0x04);
phytium_write8(&priv->regs->ep0fifoctrl, FIFOCTRL_FIFOAUTO | FIFOCTRL_IO_TX | 0 | 0x04);
priv->portStatus = USB_PORT_STAT_POWER;
priv->portStatus |= USB_PORT_STAT_C_CONNECTION << 16;
if (priv->hostCallbacks.portStatusChange)
priv->hostCallbacks.portStatusChange(priv);
if (priv->otgState == HOST_OTG_STATE_A_SUSPEND)
host_SetVbus(priv, 1);
priv->otgState = HOST_OTG_STATE_A_IDLE;
if (priv->custom_regs) {
phytium_write32(&priv->custom_regs->wakeup, 1);
} else {
gen_cfg = phytium_read32(&priv->vhub_regs->gen_cfg);
gen_cfg |= BIT(1);
phytium_write32(&priv->vhub_regs->gen_cfg, gen_cfg);
}
}
static inline void A_IdleDetect(struct HOST_CTRL *priv, uint8_t otgstate)
{
uint8_t otgctrl;
if (!priv)
return;
phytium_write8(&priv->regs->otgirq, OTGIRQ_IDLEIRQ);
if (otgstate != HOST_OTG_STATE_A_IDLE) {
pr_info("IRQ OTG: A_IDLE Babble\n");
return;
}
priv->portStatus = 0;
otgctrl = phytium_read8(&priv->regs->otgctrl);
otgctrl &= ~OTGCTRL_ASETBHNPEN;
phytium_write8(&priv->regs->otgctrl, otgctrl);
host_SetVbus(priv, 1);
priv->otgState = HOST_OTG_STATE_A_IDLE;
}
static inline void B_IdleDetect(struct HOST_CTRL *priv, uint8_t otgstate)
{
uint8_t otgctrl, usbcs;
if (!priv)
return;
phytium_write8(&priv->regs->otgirq, OTGIRQ_IDLEIRQ);
if (otgstate != HOST_OTG_STATE_B_IDLE) {
pr_info("IRQ OTG: B_IDLE Babble\n");
return;
}
otgctrl = phytium_read8(&priv->regs->otgctrl);
otgctrl &= ~OTGCTRL_ASETBHNPEN;
phytium_write8(&priv->regs->otgctrl, otgctrl);
host_SetVbus(priv, 0);
priv->otgState = HOST_OTG_STATE_B_IDLE;
usbcs = phytium_read8(&priv->regs->usbcs);
usbcs &= ~USBCS_DISCON;
phytium_write8(&priv->regs->usbcs, usbcs);
}
static uint32_t waitForBusyBit(struct HOST_CTRL *priv, struct HostEp *hwEp)
{
uint8_t *csReg;
uint8_t flag = CS_BUSY;
uint8_t buf = 0;
uint8_t val = CS_BUSY;
uint8_t otgstate;
uint8_t bufflag = 0;
if (!priv || !hwEp)
return 0;
if (hwEp->isInEp)
return 0;
if (hwEp->hwEpNum == 0) {
csReg = &priv->regs->ep0cs;
flag = EP0CS_TXBUSY_MASK;
buf = 0;
} else {
csReg = &priv->regs->ep[hwEp->hwEpNum - 1].txcs;
buf = phytium_read8(&priv->regs->ep[hwEp->hwEpNum - 1].txcon) & CON_BUF;
}
while ((val & flag) || bufflag == 0) {
otgstate = phytium_read8(&priv->regs->otgstate);
if (otgstate != HOST_OTG_STATE_B_HOST && otgstate != HOST_OTG_STATE_A_HOST) {
priv->ep0State = HOST_EP0_STAGE_IDLE;
return HOST_ESHUTDOWN;
}
val = phytium_read8(csReg);
if (((val & CS_NPAK) >> CS_NPAK_OFFSET) == buf || buf == 0)
bufflag = 1;
else
bufflag = 0;
}
return 0;
}
static inline void connectHostDetect(struct HOST_CTRL *priv, uint8_t otgState)
{
uint32_t gen_cfg;
if (!priv)
return;
pr_debug("otgState:0x%x pirv->otgState:0x%x\n", otgState, priv->otgState);
if (priv->custom_regs) {
phytium_write32(&priv->custom_regs->wakeup, 0);
} else {
gen_cfg = phytium_read32(&priv->vhub_regs->gen_cfg);
gen_cfg &= ~BIT(1);
phytium_write32(&priv->vhub_regs->gen_cfg, gen_cfg);
}
phytium_write8(&priv->regs->otgirq, OTGIRQ_CONIRQ);
if ((otgState != HOST_OTG_STATE_A_HOST) && (otgState != HOST_OTG_STATE_B_HOST))
return;
if ((priv->otgState == HOST_OTG_STATE_A_PERIPHERAL)
|| (priv->otgState == HOST_OTG_STATE_B_PERIPHERAL))
priv->otgState = otgState;
priv->ep0State = HOST_EP0_STAGE_IDLE;
priv->portStatus &= ~(USB_PORT_STAT_LOW_SPEED | USB_PORT_STAT_HIGH_SPEED |
USB_PORT_STAT_ENABLE);
priv->portStatus |= USB_PORT_STAT_C_CONNECTION | (USB_PORT_STAT_C_CONNECTION << 16);
priv->dmaDrv->dma_controllerReset(priv->dmaController);
priv->port_resetting = 1;
host_SetVbus(priv, 1);
switch (phytium_read8(&priv->regs->speedctrl)) {
case SPEEDCTRL_HS:
priv->portStatus |= USB_PORT_STAT_HIGH_SPEED;
pr_debug("detect High speed device\n");
break;
case SPEEDCTRL_FS:
priv->portStatus &= ~(USB_PORT_STAT_HIGH_SPEED | USB_PORT_STAT_LOW_SPEED);
pr_debug("detect Full speed device\n");
break;
case SPEEDCTRL_LS:
priv->portStatus |= USB_PORT_STAT_LOW_SPEED;
pr_debug("detect Low speed device\n");
break;
}
priv->vBusErrCnt = 0;
priv->dmaDrv->dma_setHostMode(priv->dmaController);
if (priv->hostCallbacks.portStatusChange)
priv->hostCallbacks.portStatusChange(priv);
priv->otgState = otgState;
}
static void hostOtgIrq(struct HOST_CTRL *priv)
{
uint8_t otgirq, otgien;
uint8_t otgstatus, otgstate;
uint8_t otgctrl;
if (!priv)
return;
otgirq = phytium_read8(&priv->regs->otgirq);
otgien = phytium_read8(&priv->regs->otgien);
otgstatus = phytium_read8(&priv->regs->otgstatus);
otgstate = phytium_read8(&priv->regs->otgstate);
otgirq &= otgien;
if (!otgirq)
return;
if (otgirq & OTGIRQ_BSE0SRPIRQ) {
otgirq &= ~OTGIRQ_BSE0SRPIRQ;
phytium_write8(&priv->regs->otgirq, OTGIRQ_BSE0SRPIRQ);
otgctrl = phytium_read8(&priv->regs->otgctrl);
otgctrl &= ~OTGIRQ_BSE0SRPIRQ;
phytium_write8(&priv->regs->otgctrl, otgctrl);
}
if (otgirq & OTGIRQ_SRPDETIRQ) {
otgirq &= ~OTGIRQ_SRPDETIRQ;
phytium_write8(&priv->regs->otgirq, OTGIRQ_SRPDETIRQ);
otgctrl = phytium_read8(&priv->regs->otgctrl);
otgctrl &= ~OTGIRQ_SRPDETIRQ;
phytium_write8(&priv->regs->otgctrl, otgctrl);
}
if (otgirq & OTGIRQ_VBUSERRIRQ) {
otgirq &= ~OTGIRQ_VBUSERRIRQ;
phytium_write8(&priv->regs->otgirq, OTGIRQ_VBUSERRIRQ);
if (otgstate != HOST_OTG_STATE_A_VBUS_ERR) {
pr_info("IRQ OTG: VBUS ERROR Babble\n");
return;
}
host_SetVbus(priv, 0);
priv->otgState = HOST_OTG_STATE_A_VBUS_ERR;
if (priv->portStatus & USB_PORT_STAT_CONNECTION) {
priv->portStatus = USB_PORT_STAT_POWER | (USB_PORT_STAT_C_CONNECTION << 16);
if (priv->hostCallbacks.portStatusChange)
priv->hostCallbacks.portStatusChange(priv);
return;
}
if (priv->vBusErrCnt >= 3) {
priv->vBusErrCnt = 0;
pr_info("%s %d VBUS OVER CURRENT\n", __func__, __LINE__);
priv->portStatus |= USB_PORT_STAT_OVERCURRENT |
(USB_PORT_STAT_C_OVERCURRENT << 16);
phytium_write8(&priv->regs->otgirq, OTGIRQ_IDLEIRQ);
} else {
priv->vBusErrCnt++;
host_SetVbus(priv, 1);
phytium_write8(&priv->regs->otgirq, OTGIRQ_IDLEIRQ);
}
}
if (otgirq & OTGIRQ_CONIRQ) {
if (priv->otgState == HOST_OTG_STATE_A_HOST ||
priv->otgState == HOST_OTG_STATE_B_HOST ||
priv->otgState == HOST_OTG_STATE_A_SUSPEND) {
if (otgstate == HOST_OTG_STATE_A_WAIT_VFALL ||
otgstate == HOST_OTG_STATE_A_WAIT_BCON ||
otgstate == HOST_OTG_STATE_A_SUSPEND)
disconnectHostDetect(priv);
} else if (priv->otgState != HOST_OTG_STATE_A_HOST &&
priv->otgState != HOST_OTG_STATE_B_HOST &&
priv->otgState != HOST_OTG_STATE_A_SUSPEND)
connectHostDetect(priv, otgstate);
phytium_write8(&priv->regs->otgirq, OTGIRQ_CONIRQ);
}
if (otgirq & OTGIRQ_IDLEIRQ) {
if (!(otgstatus & OTGSTATUS_ID))
A_IdleDetect(priv, otgstate);
else
B_IdleDetect(priv, otgstate);
}
phytium_write8(&priv->regs->otgirq, OTGIRQ_IDCHANGEIRQ |
OTGIRQ_SRPDETIRQ);
}
static void hostErrorIrq(struct HOST_CTRL *priv)
{
uint16_t txerrirq, txerrien;
uint16_t rxerrirq, rxerrien;
uint16_t i, mask;
if (!priv)
return;
txerrirq = phytium_read16(&priv->regs->txerrirq);
txerrien = phytium_read16(&priv->regs->txerrien);
txerrirq &= txerrien;
rxerrirq = phytium_read16(&priv->regs->rxerrirq);
rxerrien = phytium_read16(&priv->regs->rxerrien);
rxerrirq &= rxerrirq;
if (!txerrirq && !rxerrirq)
return;
for (i = 0; i < HOST_EP_NUM; i++) {
mask = 1 << i;
if (rxerrirq & mask) {
phytium_write16(&priv->regs->rxerrirq, mask);
rxerrien &= ~mask;
phytium_write16(&priv->regs->rxerrien, rxerrien);
priv->dmaDrv->dma_errIsr(priv->dmaController, i, 0);
}
if (txerrirq & mask) {
phytium_write16(&priv->regs->txerrirq, mask);
txerrien &= ~mask;
phytium_write16(&priv->regs->txerrien, txerrien);
priv->dmaDrv->dma_errIsr(priv->dmaController, i, 1);
}
}
}
static uint32_t decodeErrorCode(uint8_t code)
{
uint32_t status = 0;
switch (code) {
case ERR_NONE:
status = 0;
break;
case ERR_CRC:
pr_info("CRC Error\n");
status = HOST_ESHUTDOWN;
break;
case ERR_DATA_TOGGLE_MISMATCH:
pr_info("Toggle MisMatch Error\n");
status = HOST_ESHUTDOWN;
break;
case ERR_STALL:
pr_debug("Stall Error\n");
status = HOST_ESTALL;
break;
case ERR_TIMEOUT:
pr_debug("Timeout Error\n");
status = HOST_ESHUTDOWN;
break;
case ERR_PID:
pr_info("PID Error\n");
status = HOST_ESHUTDOWN;
break;
case ERR_TOO_LONG_PACKET:
pr_info("TOO_LONG_PACKET Error\n");
status = HOST_ESHUTDOWN;
break;
case ERR_DATA_UNDERRUN:
pr_info("UNDERRUN Error\n");
status = HOST_ESHUTDOWN;
break;
}
return status;
}
static struct HOST_EP_PRIV *getIntTransfer(struct list_head *head)
{
struct list_head *listEntry = NULL;
struct HOST_EP_PRIV *usbHEpPriv = NULL;
struct HOST_EP_PRIV *usbHEpPrivActual = NULL;
list_for_each(listEntry, head) {
usbHEpPriv = getUsbHEpPrivEntry(listEntry);
if (!usbHEpPrivActual)
usbHEpPrivActual = usbHEpPriv;
if (usbHEpPriv->frame < usbHEpPrivActual->frame)
usbHEpPrivActual = usbHEpPriv;
}
return usbHEpPrivActual;
}
static void givebackRequest(struct HOST_CTRL *priv, struct HOST_REQ *usbReq, uint32_t status)
{
if (!priv || !usbReq)
return;
list_del(&usbReq->list);
if (usbReq->status == EINPROGRESS)
usbReq->status = status;
if (priv->hostCallbacks.givebackRequest)
priv->hostCallbacks.givebackRequest(priv, usbReq, status);
}
static void hostEpProgram(struct HOST_CTRL *priv, struct HostEp *hwEp,
struct HOST_REQ *usbReq, uintptr_t dmaBuff, uint32_t length)
{
struct HOST_EP *usbHEp;
struct HOST_EP_PRIV *usbEpPriv;
uint32_t chMaxLen;
uint8_t regCon = 0;
uint8_t ep0cs;
uint16_t txerrien = 0;
uint16_t rxerrien = 0;
uint32_t result;
uint8_t txsoftimer, rxsoftimer;
u8 retval = 0;
if (!priv || !hwEp || !usbReq)
return;
usbHEp = hwEp->scheduledUsbHEp;
usbEpPriv = (struct HOST_EP_PRIV *)usbHEp->hcPriv;
if (!hwEp->channel) {
if (usbEpPriv->type == USB_ENDPOINT_XFER_ISOC)
hwEp->channel = priv->dmaDrv->dma_channelAlloc(priv->dmaController,
!hwEp->isInEp, hwEp->hwEpNum, 1);
else
hwEp->channel = priv->dmaDrv->dma_channelAlloc(priv->dmaController,
!hwEp->isInEp, hwEp->hwEpNum, 0);
}
chMaxLen = priv->dmaDrv->dma_getMaxLength(priv->dmaController, hwEp->channel);
pr_debug("chMaxLen:0x%x buffLength:0x%x\n", chMaxLen, usbReq->buffLength);
if (usbReq->buffLength > chMaxLen)
length = chMaxLen;
switch (usbEpPriv->type) {
case USB_ENDPOINT_XFER_CONTROL:
regCon = CON_TYPE_CONTROL;
break;
case USB_ENDPOINT_XFER_BULK:
regCon = CON_TYPE_BULK;
break;
case USB_ENDPOINT_XFER_INT:
regCon = CON_TYPE_INT;
break;
case USB_ENDPOINT_XFER_ISOC:
if (usbEpPriv->isocEpConfigured)
goto dma_program;
usbEpPriv->isocEpConfigured = 1;
regCon = CON_TYPE_ISOC;
switch (usbHEp->desc.wMaxPacketSize >> 11) {
case 0:
regCon |= CON_TYPE_ISOC_1_ISOD;
priv->dmaDrv->dma_setMaxLength(priv->dmaController,
hwEp->channel, usbEpPriv->maxPacketSize);
break;
case 1:
regCon |= CON_TYPE_ISOC_2_ISOD;
priv->dmaDrv->dma_setMaxLength(priv->dmaController,
hwEp->channel, 2 * 1024);
break;
case 2:
priv->dmaDrv->dma_setMaxLength(priv->dmaController,
hwEp->channel, 3 * 1024);
regCon |= CON_TYPE_ISOC_3_ISOD;
break;
}
break;
}
if (usbEpPriv->type != USB_ENDPOINT_XFER_ISOC) {
if (!hwEp->hwEpNum) {
if (phytium_read8(&priv->regs->ep0cs) & 0x4) {
phytium_write8(&priv->regs->ep0fifoctrl, FIFOCTRL_FIFOAUTO |
0 | 0x4);
phytium_write8(&priv->regs->ep0fifoctrl, FIFOCTRL_FIFOAUTO |
FIFOCTRL_IO_TX | 0 | 0x4);
}
}
if (waitForBusyBit(priv, hwEp) > 0) {
usbReq->status = HOST_ESHUTDOWN;
givebackRequest(priv, usbReq, HOST_ESHUTDOWN);
pr_info("something error happen\n");
return;
}
}
if (!hwEp->isInEp) {
if (hwEp->hwEpNum) {
regCon |= hwEp->hwBuffers - 1;
phytium_write8(&priv->regs->ep[hwEp->hwEpNum - 1].txcon, regCon);
if (usbEpPriv->type != USB_ENDPOINT_XFER_ISOC) {
retval = priv->hostCallbacks.getEpToggle(priv,
usbReq->usbDev, usbEpPriv->epNum, 0);
if (retval) {
phytium_write8(&priv->regs->endprst, hwEp->hwEpNum |
ENDPRST_IO_TX);
phytium_write8(&priv->regs->endprst, hwEp->hwEpNum |
ENDPRST_TOGSETQ | ENDPRST_IO_TX | ENDPRST_FIFORST);
} else {
phytium_write8(&priv->regs->endprst, hwEp->hwEpNum |
ENDPRST_IO_TX);
phytium_write8(&priv->regs->endprst, hwEp->hwEpNum |
ENDPRST_TOGRST | ENDPRST_IO_TX | ENDPRST_FIFORST);
}
}
phytium_write8(&priv->regs->ep[hwEp->hwEpNum - 1].txcon, regCon | CON_VAL);
phytium_write16(&priv->regs->txmaxpack[hwEp->hwEpNum - 1],
usbEpPriv->maxPacketSize);
phytium_write8(&priv->regs->epExt[hwEp->hwEpNum - 1].txctrl,
usbEpPriv->epNum);
phytium_write8(&priv->regs->fnaddr, usbEpPriv->faddress);
// if (usbEpPriv->type == USB_ENDPOINT_XFER_INT)
txsoftimer = phytium_read8(&priv->regs->txsoftimer[hwEp->hwEpNum].ctrl);
txsoftimer = txsoftimer | BIT(1);
phytium_write8(&priv->regs->txsoftimer[hwEp->hwEpNum].ctrl, txsoftimer);
phytium_write16(&priv->regs->txsoftimer[hwEp->hwEpNum].timer,
usbEpPriv->frame);
phytium_write8(&priv->regs->txsoftimer[hwEp->hwEpNum].ctrl, 0x83);
} else {
phytium_write8(&priv->regs->fnaddr, usbEpPriv->faddress);
phytium_write8(&priv->regs->ep0maxpack, usbEpPriv->maxPacketSize);
phytium_write8(&priv->regs->ep0ctrl, usbEpPriv->epNum);
if (priv->ep0State == HOST_EP0_STAGE_SETUP) {
ep0cs = phytium_read8(&priv->regs->ep0cs);
ep0cs |= EP0CS_HCSET;
phytium_write8(&priv->regs->ep0cs, ep0cs);
}
}
phytium_write16(&priv->regs->txerrirq, 1 << hwEp->hwEpNum);
txerrien = phytium_read16(&priv->regs->txerrien);
txerrien |= 1 << hwEp->hwEpNum;
phytium_write16(&priv->regs->txerrien, txerrien);
} else {
if (hwEp->hwEpNum) {
regCon |= hwEp->hwBuffers - 1;
phytium_write8(&priv->regs->ep[hwEp->hwEpNum - 1].rxcon, regCon);
if (usbEpPriv->type != USB_ENDPOINT_XFER_ISOC) {
if (priv->hostCallbacks.getEpToggle) {
retval = priv->hostCallbacks.getEpToggle(priv,
usbReq->usbDev, usbEpPriv->epNum, 1);
if (retval) {
phytium_write8(&priv->regs->endprst, hwEp->hwEpNum);
phytium_write8(&priv->regs->endprst, hwEp->hwEpNum |
ENDPRST_TOGSETQ | ENDPRST_FIFORST);
} else {
phytium_write8(&priv->regs->endprst, hwEp->hwEpNum);
phytium_write8(&priv->regs->endprst, hwEp->hwEpNum |
ENDPRST_TOGRST | ENDPRST_FIFORST);
}
}
}
phytium_write16(&priv->regs->rxmaxpack[hwEp->hwEpNum - 1],
usbEpPriv->maxPacketSize);
phytium_write8(&priv->regs->epExt[hwEp->hwEpNum - 1].rxctrl,
usbEpPriv->epNum);
phytium_write8(&priv->regs->fnaddr, usbEpPriv->faddress);
phytium_write8(&priv->regs->ep[hwEp->hwEpNum - 1].rxcs, 1);
phytium_write8(&priv->regs->ep[hwEp->hwEpNum - 1].rxcs, 1);
phytium_write8(&priv->regs->ep[hwEp->hwEpNum - 1].rxcs, 1);
phytium_write8(&priv->regs->ep[hwEp->hwEpNum - 1].rxcs, 1);
phytium_write8(&priv->regs->ep[hwEp->hwEpNum - 1].rxcon, regCon | CON_VAL);
rxsoftimer = phytium_read8(&priv->regs->rxsoftimer[hwEp->hwEpNum].ctrl);
rxsoftimer = rxsoftimer | BIT(1);
phytium_write8(&priv->regs->rxsoftimer[hwEp->hwEpNum].ctrl, rxsoftimer);
phytium_write16(&priv->regs->rxsoftimer[hwEp->hwEpNum].timer,
usbEpPriv->frame);
phytium_write8(&priv->regs->rxsoftimer[hwEp->hwEpNum].ctrl, 0x83);
} else {
phytium_write8(&priv->regs->fnaddr, usbEpPriv->faddress);
phytium_write8(&priv->regs->ep0maxpack, usbEpPriv->maxPacketSize);
phytium_write8(&priv->regs->ep0ctrl, usbEpPriv->epNum);
if (priv->ep0State == HOST_EP0_STAGE_IN
|| priv->ep0State == HOST_EP0_STAGE_STATUSIN)
phytium_write8(&priv->regs->ep0cs, EP0CS_HCSETTOGGLE);
}
phytium_write16(&priv->regs->rxerrirq, 1 << hwEp->hwEpNum);
rxerrien = phytium_read16(&priv->regs->rxerrien);
rxerrien |= 1 << hwEp->hwEpNum;
phytium_write16(&priv->regs->rxerrien, rxerrien);
}
dma_program:
result = priv->dmaDrv->dma_channelProgram(priv->dmaController, hwEp->channel,
usbEpPriv->maxPacketSize, dmaBuff, length,
(void *)usbReq->isoFramesDesc, usbReq->isoFramesNumber);
if (result) {
if (!hwEp->isInEp) {
txerrien &= ~(1 << hwEp->hwEpNum);
if (hwEp->hwEpNum)
phytium_write8(&priv->regs->ep[hwEp->hwEpNum - 1].txcon, 0x08);
phytium_write16(&priv->regs->txerrien, txerrien);
} else {
rxerrien &= ~(1 << hwEp->hwEpNum);
if (hwEp->hwEpNum)
phytium_write8(&priv->regs->ep[hwEp->hwEpNum - 1].rxcon, 0x08);
phytium_write16(&priv->regs->rxerrien, rxerrien);
}
priv->dmaDrv->dma_channelRelease(priv->dmaController, hwEp->channel);
hwEp->channel = NULL;
}
}
static void hostStartReq(struct HOST_CTRL *priv, struct HOST_REQ *req)
{
uintptr_t dmaBuff;
uint32_t length;
struct HOST_EP *hostEp;
struct HOST_EP_PRIV *hostEpPriv;
struct HOST_EP_PRIV *hostNewEpPriv;
struct HOST_REQ *usbReq = NULL;
struct HostEp *hwEp = NULL;
int num;
if (!priv || !req)
return;
hostEp = req->hcPriv;
if (hostEp) {
hostEpPriv = (struct HOST_EP_PRIV *)hostEp->hcPriv;
if (hostEpPriv) {
hostEpPriv->genericHwEp->state = HOST_EP_BUSY;
switch (hostEpPriv->type) {
case USB_ENDPOINT_XFER_CONTROL:
usbReq = getNextReq(hostEp);
priv->in[HOST_GENERIC_EP_CONTROLL].scheduledUsbHEp = hostEp;
priv->ep0State = HOST_EP0_STAGE_SETUP;
hostEpPriv->currentHwEp = hostEpPriv->genericHwEp;
hostEpPriv->genericHwEp->scheduledUsbHEp = hostEp;
dmaBuff = usbReq->setupDma;
length = 8;
pr_debug("packet info: %02x %02x %04x %04x %04x\n",
usbReq->setup->bRequestType,
usbReq->setup->bRequest,
usbReq->setup->wValue,
usbReq->setup->wIndex,
usbReq->setup->wLength);
hostEpProgram(priv, hostEpPriv->genericHwEp,
usbReq, dmaBuff, length);
break;
case USB_ENDPOINT_XFER_BULK:
hwEp = hostEpPriv->genericHwEp;
usbReq = getNextReq(hostEp);
hostEpPriv->currentHwEp = hwEp;
hwEp->scheduledUsbHEp = hostEp;
dmaBuff = usbReq->buffDma + usbReq->actualLength;
length = usbReq->buffLength - usbReq->actualLength;
hostEpProgram(priv, hwEp, usbReq, dmaBuff, length);
break;
case USB_ENDPOINT_XFER_INT:
if (hostEpPriv->genericHwEp->scheduledUsbHEp)
return;
num = req->epNum - 1;
if (hostEpPriv->isIn)
hostNewEpPriv = getIntTransfer(&priv->intInHEpQueue[num]);
else
hostNewEpPriv = getIntTransfer(&priv->intOutHEpQueue[num]);
hostNewEpPriv->currentHwEp = hostEpPriv->genericHwEp;
hostEpPriv->genericHwEp->scheduledUsbHEp = hostNewEpPriv->usbHEp;
usbReq = getNextReq(hostEp);
dmaBuff = usbReq->buffDma + usbReq->actualLength;
length = usbReq->buffLength - usbReq->actualLength;
hostEpProgram(priv, hostEpPriv->genericHwEp,
usbReq, dmaBuff, length);
break;
case USB_ENDPOINT_XFER_ISOC:
hostEpPriv->currentHwEp = hostEpPriv->genericHwEp;
hostEpPriv->genericHwEp->scheduledUsbHEp = hostEp;
dmaBuff = req->buffDma + req->actualLength;
length = req->buffLength - req->actualLength;
hostEpProgram(priv, hostEpPriv->genericHwEp, req, dmaBuff, length);
break;
}
}
}
}
static void scheduleNextTransfer(struct HOST_CTRL *priv,
struct HOST_REQ *usbReq, struct HostEp *hwEp)
{
struct HOST_EP *usbEp;
struct HOST_EP_PRIV *usbHEpPriv;
uint8_t endprst;
uint32_t status;
struct HOST_REQ *usbNextReq = NULL;
if (!priv || !usbReq || !hwEp)
return;
usbEp = hwEp->scheduledUsbHEp;
usbHEpPriv = (struct HOST_EP_PRIV *)usbEp->hcPriv;
if (!usbHEpPriv)
return;
status = (usbReq->status == EINPROGRESS) ? 0 : usbReq->status;
switch (usbHEpPriv->type) {
case USB_ENDPOINT_XFER_BULK:
case USB_ENDPOINT_XFER_INT:
if (hwEp->isInEp) {
phytium_write8(&priv->regs->endprst, hwEp->hwEpNum);
endprst = (phytium_read8(&priv->regs->endprst) & ENDPRST_TOGSETQ) ? 1 : 0;
if (priv->hostCallbacks.setEpToggle)
priv->hostCallbacks.setEpToggle(priv, usbReq->usbDev,
usbHEpPriv->epNum, usbHEpPriv->isIn, endprst);
} else {
if (waitForBusyBit(priv, hwEp) > 0) {
usbReq->status = HOST_ESHUTDOWN;
givebackRequest(priv, usbReq, HOST_ESHUTDOWN);
return;
}
phytium_write8(&priv->regs->endprst, hwEp->hwEpNum | ENDPRST_IO_TX);
endprst = (phytium_read8(&priv->regs->endprst) & ENDPRST_TOGSETQ) ? 1 : 0;
if (priv->hostCallbacks.setEpToggle)
priv->hostCallbacks.setEpToggle(priv, usbReq->usbDev,
usbHEpPriv->epNum, usbHEpPriv->isIn, endprst);
}
break;
}
if (usbHEpPriv->transferFinished)
givebackRequest(priv, usbReq, status);
if (list_empty(&usbEp->reqList)) {
if (usbHEpPriv->type == USB_ENDPOINT_XFER_CONTROL)
hwEp->state = HOST_EP_ALLOCATED;
else {
if (usbHEpPriv->genericHwEp == hwEp)
hwEp->state = HOST_EP_ALLOCATED;
else
hwEp->state = HOST_EP_FREE;
}
usbHEpPriv->epIsReady = 0;
usbHEpPriv->currentHwEp = NULL;
hwEp->scheduledUsbHEp = NULL;
if (hwEp->channel) {
priv->dmaDrv->dma_channelRelease(priv->dmaController, hwEp->channel);
hwEp->channel = NULL;
}
if (usb_endpoint_xfer_int(&usbEp->desc))
list_del(&usbHEpPriv->node);
usbHEpPriv = NULL;
} else {
if (usbHEpPriv->type == USB_ENDPOINT_XFER_INT) {
usbHEpPriv->currentHwEp = NULL;
hwEp->scheduledUsbHEp = NULL;
}
if (usbHEpPriv->type == USB_ENDPOINT_XFER_ISOC)
return;
}
if (usbHEpPriv) {
usbNextReq = getNextReq(usbHEpPriv->usbHEp);
hostStartReq(priv, usbNextReq);
}
}
static int32_t hostEp0Irq(struct HOST_CTRL *priv, uint8_t isIn)
{
struct HostEp *hwEp;
struct HOST_EP *hostEp;
struct HOST_REQ *usbReq = NULL;
struct HOST_EP_PRIV *usbHEpPriv;
uint32_t status, length;
uint8_t usbError;
uint8_t nextStage = 0;
if (!priv)
return -EINVAL;
hwEp = isIn ? &priv->in[HOST_GENERIC_EP_CONTROLL] : &priv->out[HOST_GENERIC_EP_CONTROLL];
hostEp = hwEp->scheduledUsbHEp;
usbHEpPriv = (struct HOST_EP_PRIV *)hostEp->hcPriv;
usbReq = getNextReq(hostEp);
if (!usbReq)
return 0;
usbError = isIn ? phytium_read8(&priv->regs->rx0err) : phytium_read8(&priv->regs->tx0err);
usbError = (usbError & ERR_TYPE) >> 2;
status = decodeErrorCode(usbError);
if (status) {
usbReq->status = status;
if (status == HOST_ESTALL)
priv->dmaDrv->dma_controllerReset(priv->dmaController);
phytium_write16(&priv->regs->rxerrirq, 1 << hwEp->hwEpNum);
phytium_write16(&priv->regs->txerrirq, 1 << hwEp->hwEpNum);
phytium_write8(&priv->regs->ep0fifoctrl, FIFOCTRL_FIFOAUTO | 0 | 0x4);
phytium_write8(&priv->regs->ep0fifoctrl, FIFOCTRL_FIFOAUTO |
FIFOCTRL_IO_TX | 0 | 0x4);
}
length = priv->dmaDrv->dma_getActualLength(priv->dmaController, hwEp->channel);
priv->dmaDrv->dma_channelRelease(priv->dmaController, hwEp->channel);
hwEp->channel = NULL;
if (usbReq->status == EINPROGRESS && priv->ep0State < HOST_EP0_STAGE_STATUSIN) {
nextStage = 0;
switch (priv->ep0State) {
case HOST_EP0_STAGE_IN:
pr_debug("Ep0 Data IN\n");
usbHEpPriv->currentHwEp = &priv->out[HOST_GENERIC_EP_CONTROLL];
usbReq->actualLength = length;
priv->ep0State = HOST_EP0_STAGE_STATUSOUT;
break;
case HOST_EP0_STAGE_OUT:
pr_debug("Ep0 Data OUT\n");
usbHEpPriv->currentHwEp = &priv->in[HOST_GENERIC_EP_CONTROLL];
usbReq->actualLength = length;
priv->ep0State = HOST_EP0_STAGE_STATUSIN;
break;
case HOST_EP0_STAGE_SETUP:
pr_debug("Ep0 Stage Setup\n");
if (!usbReq->setup->wLength) {
pr_debug("EP0_STAGE_STATUSIN\n");
priv->ep0State = HOST_EP0_STAGE_STATUSIN;
usbHEpPriv->currentHwEp = &priv->in[HOST_GENERIC_EP_CONTROLL];
break;
} else if (usbReq->setup->bRequestType & USB_DIR_IN) {
pr_debug("EP0_STAGE_STAGE_IN\n");
priv->ep0State = HOST_EP0_STAGE_IN;
usbHEpPriv->currentHwEp = &priv->in[HOST_GENERIC_EP_CONTROLL];
nextStage = 1;
break;
}
priv->ep0State = HOST_EP0_STAGE_OUT;
nextStage = 1;
break;
case HOST_EP0_STAGE_STATUSIN:
case HOST_EP0_STAGE_STATUSOUT:
case HOST_EP0_STAGE_ACK:
case HOST_EP0_STAGE_IDLE:
default:
pr_debug("EP0 STAGE is %d\n", priv->ep0State);
break;
}
if (nextStage) {
length = usbReq->buffLength;
hostEpProgram(priv, usbHEpPriv->currentHwEp, usbReq,
usbReq->buffDma, length);
} else
hostEpProgram(priv, usbHEpPriv->currentHwEp, usbReq, usbReq->setupDma, 0);
} else
priv->ep0State = HOST_EP0_STAGE_IDLE;
if (priv->ep0State == HOST_EP0_STAGE_IDLE || usbReq->status != EINPROGRESS) {
usbHEpPriv->transferFinished = 1;
scheduleNextTransfer(priv, usbReq, hwEp);
}
return 0;
}
static void updateTimeIntTransfer(struct list_head *head, struct HOST_EP_PRIV *lastFinished)
{
struct list_head *listEntry = NULL;
struct HOST_EP_PRIV *usbHEpPriv = NULL;
uint16_t time = lastFinished->frame;
list_for_each(listEntry, head) {
usbHEpPriv = getUsbHEpPrivEntry(listEntry);
if (usbHEpPriv == lastFinished) {
lastFinished->frame = lastFinished->interval;
continue;
}
if (usbHEpPriv->frame < time)
usbHEpPriv->frame = 0;
else
lastFinished->interval = usbHEpPriv->frame;
}
}
static int32_t hostEpXIrq(struct HOST_CTRL *priv, uint8_t hwEpNum, uint8_t isIn, bool resubmit)
{
struct HostEp *hwEp;
struct HOST_EP *hostEp;
struct HOST_EP_PRIV *usbHEpPriv;
struct HOST_REQ *usbReq;
uint8_t usbError, rxcon, txcon;
uint32_t status, length, chMaxLen;
if (!priv)
return -EINVAL;
hwEp = isIn ? &priv->in[hwEpNum] : &priv->out[hwEpNum];
hostEp = hwEp->scheduledUsbHEp;
if (!hostEp)
return -EINVAL;
usbHEpPriv = (struct HOST_EP_PRIV *)hostEp->hcPriv;
usbReq = getNextReq(hostEp);
if (!usbReq)
return 0;
if (isIn)
usbError = phytium_read8(&priv->regs->epExt[hwEpNum - 1].rxerr);
else
usbError = phytium_read8(&priv->regs->epExt[hwEpNum - 1].txerr);
usbError = (usbError & ERR_TYPE) >> 2;
status = decodeErrorCode(usbError);
if (status) {
pr_debug("%s %d Aborting\n", __func__, __LINE__);
if (isIn)
phytium_write16(&priv->regs->rxerrirq, 1 << hwEpNum);
else
phytium_write16(&priv->regs->txerrirq, 1 << hwEpNum);
priv->dmaDrv->dma_channelAbort(priv->dmaController, hwEp->channel);
}
length = priv->dmaDrv->dma_getActualLength(priv->dmaController, hwEp->channel);
chMaxLen = priv->dmaDrv->dma_getMaxLength(priv->dmaController, hwEp->channel);
if (status != 0)
usbReq->status = status;
usbReq->actualLength += length;
if (!resubmit)
usbHEpPriv->transferFinished = 1;
if (length == chMaxLen) {
if ((usbReq->buffLength - usbReq->actualLength) == 0)
usbHEpPriv->transferFinished = 1;
else
usbHEpPriv->transferFinished = 0;
}
if (usbHEpPriv->type != USB_ENDPOINT_XFER_ISOC) {
if (!hwEp->isInEp) {
txcon = phytium_read8(&priv->regs->ep[hwEp->hwEpNum - 1].txcon);
txcon &= ~CON_VAL;
phytium_write8(&priv->regs->ep[hwEp->hwEpNum - 1].txcon, txcon);
} else {
rxcon = phytium_read8(&priv->regs->ep[hwEp->hwEpNum - 1].rxcon);
rxcon &= ~CON_VAL;
phytium_write8(&priv->regs->ep[hwEp->hwEpNum - 1].rxcon, rxcon);
}
priv->dmaDrv->dma_channelRelease(priv->dmaController, hwEp->channel);
hwEp->channel = NULL;
}
if (usbHEpPriv->type == USB_ENDPOINT_XFER_INT) {
if (usbHEpPriv->isIn)
updateTimeIntTransfer(&priv->intInHEpQueue[hwEp->hwEpNum - 1], usbHEpPriv);
else
updateTimeIntTransfer(&priv->intOutHEpQueue[hwEp->hwEpNum - 1], usbHEpPriv);
}
scheduleNextTransfer(priv, usbReq, hwEp);
return 0;
}
static void host_CallbackTransfer(void *priv, uint8_t epNum, uint8_t isDirTx, bool resubmit)
{
struct HOST_CTRL *host_priv = (struct HOST_CTRL *)priv;
int i;
if (!epNum) {
hostEp0Irq(host_priv, !isDirTx);
if (resubmit) {
for (i = 1; i < HOST_EP_NUM; i++) {
hostEpXIrq(host_priv, i, !isDirTx, true);
hostEpXIrq(host_priv, i, isDirTx, true);
}
}
} else
hostEpXIrq(host_priv, epNum, !isDirTx, false);
}
static int hc_reset(struct usb_hcd *hcd)
{
hcd->speed = HCD_USB2;
hcd->self.root_hub->speed = USB_SPEED_HIGH;
return 0;
}
static int hc_start(struct usb_hcd *hcd)
{
hcd->state = HC_STATE_RUNNING;
return 0;
}
static void hc_stop(struct usb_hcd *hcd)
{
struct phytium_cusb *config = *(struct phytium_cusb **)hcd->hcd_priv;
if (config)
config->host_obj->host_stop(config->host_priv);
if (config->host_cfg.trbAddr) {
dma_free_coherent(config->dev, config->host_sysreq.trbMemSize,
config->host_cfg.trbAddr, config->host_cfg.trbDmaAddr);
config->host_cfg.trbAddr = NULL;
}
if (config->host_priv) {
devm_kfree(config->dev, config->host_priv);
config->host_priv = NULL;
}
hcd->state = HC_STATE_HALT;
}
static void hc_shutdown(struct usb_hcd *hcd)
{
}
static void host_endpoint_update(struct phytium_cusb *config,
struct HOST_USB_DEVICE *udev, struct usb_host_endpoint *ep)
{
int epnum;
struct HOST_EP *hostEp;
if (!config || !udev || !ep)
return;
epnum = usb_endpoint_num(&ep->desc);
if (epnum > MAX_INSTANCE_EP_NUM)
epnum = MAX_INSTANCE_EP_NUM;
if (usb_endpoint_dir_out(&ep->desc)) {
if (udev->out_ep[epnum] == NULL) {
hostEp = kzalloc(sizeof(struct HOST_EP) +
config->host_obj->host_epGetPrivateDataSize(config->host_priv),
GFP_ATOMIC);
udev->out_ep[epnum] = hostEp;
} else
hostEp = udev->out_ep[epnum];
} else {
if (udev->in_ep[epnum] == NULL) {
hostEp = kzalloc(sizeof(struct HOST_EP) +
config->host_obj->host_epGetPrivateDataSize(config->host_priv),
GFP_ATOMIC);
udev->in_ep[epnum] = hostEp;
} else
hostEp = udev->in_ep[epnum];
}
hostEp->desc = *(struct usb_endpoint_descriptor *)(&ep->desc);
hostEp->userExt = (void *)ep;
INIT_LIST_HEAD(&hostEp->reqList);
hostEp->hcPriv = &((uint8_t *)hostEp)[sizeof(struct HOST_EP)];
}
static int hc_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags)
{
unsigned long flags;
u32 isoFrameSize;
int retval;
int index;
struct HOST_REQ *req;
struct HOST_CTRL *priv;
struct HOST_USB_DEVICE *usbDev;
struct usb_host_endpoint *host_ep;
struct usb_endpoint_descriptor *host_ep_desc;
struct phytium_cusb *config = *(struct phytium_cusb **)hcd->hcd_priv;
if (!config)
return -ENODEV;
priv = config->host_priv;
if (!priv)
return -ENODEV;
usbDev = priv->host_devices_table[urb->dev->slot_id];
if (!usbDev)
return -ENODEV;
host_ep = urb->ep;
host_ep_desc = &host_ep->desc;
spin_lock_irqsave(&config->lock, flags);
retval = usb_hcd_link_urb_to_ep(hcd, urb);
if (retval < 0) {
spin_unlock_irqrestore(&config->lock, flags);
return retval;
}
spin_unlock_irqrestore(&config->lock, flags);
isoFrameSize = urb->number_of_packets * sizeof(struct HOST_ISOFRAMESDESC);
req = kzalloc((sizeof(struct HOST_REQ) +
isoFrameSize), mem_flags);
if (!req) {
spin_lock_irqsave(&config->lock, flags);
usb_hcd_unlink_urb_from_ep(hcd, urb);
spin_unlock_irqrestore(&config->lock, flags);
return -ENOMEM;
}
req->isoFramesDesc = NULL;
req->isoFramesNumber = urb->number_of_packets;
req->epNum = usb_endpoint_num(host_ep_desc);
if (req->epNum > MAX_INSTANCE_EP_NUM)
req->epNum = MAX_INSTANCE_EP_NUM;
if (usb_endpoint_dir_in(host_ep_desc)) {
if (!usbDev->in_ep[req->epNum])
host_endpoint_update(config, usbDev, host_ep);
} else {
if (!usbDev->out_ep[req->epNum])
host_endpoint_update(config, usbDev, host_ep);
}
if (usb_endpoint_xfer_isoc(host_ep_desc)) {
req->isoFramesDesc = (struct HOST_ISOFRAMESDESC *)(&req[1]);
for (index = 0; index < urb->number_of_packets; index++) {
req->isoFramesDesc[index].length = urb->iso_frame_desc[index].length;
req->isoFramesDesc[index].offset = urb->iso_frame_desc[index].offset;
}
}
spin_lock_irqsave(&config->lock, flags);
INIT_LIST_HEAD(&req->list);
req->userExt = (void *)urb;
req->actualLength = urb->actual_length;
req->bufAddress = urb->transfer_buffer;
req->buffDma = urb->transfer_dma;
req->buffLength = urb->transfer_buffer_length;
req->epIsIn = usb_endpoint_dir_in(host_ep_desc);
req->eptype = usb_endpoint_type(host_ep_desc);
req->faddress = usb_pipedevice(urb->pipe);
req->interval = urb->interval;
req->reqUnlinked = 0;
req->setup = (struct usb_ctrlrequest *)urb->setup_packet;
req->setupDma = urb->setup_dma;
req->status = EINPROGRESS;
req->usbDev = &usbDev->udev;
req->usbEp = req->epIsIn ? usbDev->in_ep[req->epNum] : usbDev->out_ep[req->epNum];
if (!req->epNum)
usbDev->ep0_hep.desc.wMaxPacketSize = urb->dev->ep0.desc.wMaxPacketSize;
req->hcPriv = (void *)req->usbEp;
urb->hcpriv = req;
host_ep->hcpriv = (void *)usbDev;
retval = config->host_obj->host_reqQueue(config->host_priv, req);
if (retval) {
usb_hcd_unlink_urb_from_ep(hcd, urb);
urb->hcpriv = NULL;
spin_unlock_irqrestore(&config->lock, flags);
kfree(req);
return -retval;
}
spin_unlock_irqrestore(&config->lock, flags);
return 0;
}
static int hc_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
{
unsigned long flags;
struct HOST_CTRL *priv;
int ret = 0;
struct phytium_cusb *config = *(struct phytium_cusb **)hcd->hcd_priv;
if (!config)
return -ENODEV;
priv = config->host_priv;
if (!priv->host_devices_table[urb->dev->slot_id])
return -ENODEV;
spin_lock_irqsave(&config->lock, flags);
if (usb_hcd_check_unlink_urb(hcd, urb, status))
goto done;
if (!urb->hcpriv)
goto err_giveback;
ret = config->host_obj->host_reqDequeue(priv, urb->hcpriv, status);
kfree(urb->hcpriv);
urb->hcpriv = NULL;
done:
spin_unlock_irqrestore(&config->lock, flags);
return ret;
err_giveback:
kfree(urb->hcpriv);
usb_hcd_unlink_urb_from_ep(hcd, urb);
spin_unlock_irqrestore(&config->lock, flags);
usb_hcd_giveback_urb(hcd, urb, -ESHUTDOWN);
return ret;
}
static void hc_endpoint_disable(struct usb_hcd *hcd, struct usb_host_endpoint *ld_ep)
{
struct HOST_USB_DEVICE *usbDev;
int ep_num = usb_endpoint_num(&ld_ep->desc);
if (ep_num > MAX_INSTANCE_EP_NUM)
ep_num = MAX_INSTANCE_EP_NUM;
usbDev = (struct HOST_USB_DEVICE *)ld_ep->hcpriv;
if (!usbDev)
return;
if (ld_ep->desc.bEndpointAddress) {
if (usb_endpoint_dir_in(&ld_ep->desc)) {
if (!usbDev->in_ep[ep_num]) {
usbDev->in_ep[ep_num]->userExt = NULL;
INIT_LIST_HEAD(&usbDev->in_ep[ep_num]->reqList);
kfree(usbDev->in_ep[ep_num]);
usbDev->in_ep[ep_num] = NULL;
}
} else {
if (!usbDev->out_ep[ep_num]) {
usbDev->out_ep[ep_num]->userExt = NULL;
INIT_LIST_HEAD(&usbDev->out_ep[ep_num]->reqList);
kfree(usbDev->out_ep[ep_num]);
usbDev->out_ep[ep_num] = NULL;
}
}
}
}
static int hc_get_frame(struct usb_hcd *hcd)
{
return 0;
}
static int hc_alloc_dev(struct usb_hcd *hcd, struct usb_device *udev)
{
struct HOST_USB_DEVICE *usbDev;
struct phytium_cusb *config;
struct HOST_CTRL *priv;
unsigned long flags = 0;
int index;
int slot = -EINVAL;
if (!hcd || !udev)
return -EINVAL;
config = *(struct phytium_cusb **)hcd->hcd_priv;
if (!config)
return 0;
spin_lock_irqsave(&config->lock, flags);
priv = config->host_priv;
for (index = 0; index < MAX_SUPPORTED_DEVICES; index++) {
if (priv->host_devices_table[index] == NULL) {
slot = index;
break;
}
}
spin_unlock_irqrestore(&config->lock, flags);
if (slot < 0)
return -ENOMEM;
usbDev = kzalloc((sizeof(struct HOST_USB_DEVICE) +
config->host_obj->host_epGetPrivateDataSize(priv)), GFP_KERNEL);
if (!usbDev)
return -ENOMEM;
usbDev->ep0_hep.hcPriv = &((uint8_t *)usbDev)[sizeof(struct HOST_USB_DEVICE)];
usbDev->udev.userExt = (void *)usbDev;
usbDev->ld_udev = udev;
usbDev->ld_udev->slot_id = slot;
usbDev->ep0_hep.desc.bLength = 7;
usbDev->ep0_hep.desc.bDescriptorType = USB_DT_ENDPOINT;
usbDev->in_ep[0] = &usbDev->ep0_hep;
usbDev->out_ep[0] = &usbDev->ep0_hep;
INIT_LIST_HEAD(&usbDev->ep0_hep.reqList);
priv->host_devices_table[slot] = usbDev;
return 1;
}
static void hc_free_dev(struct usb_hcd *hcd, struct usb_device *udev)
{
struct HOST_CTRL *priv;
struct HOST_USB_DEVICE *usbDev;
struct phytium_cusb *config;
int i;
if (!hcd || !udev)
return;
config = *(struct phytium_cusb **)(hcd->hcd_priv);
if (!config)
return;
priv = (struct HOST_CTRL *)config->host_priv;
usbDev = priv->host_devices_table[udev->slot_id];
if (!usbDev)
return;
usbDev->in_ep[0] = NULL;
usbDev->out_ep[0] = NULL;
for (i = 1; i < HOST_EP_NUM; i++) {
if (usbDev->in_ep[i])
hc_endpoint_disable(hcd,
(struct usb_host_endpoint *)usbDev->in_ep[i]->userExt);
if (usbDev->out_ep[i])
hc_endpoint_disable(hcd,
(struct usb_host_endpoint *)usbDev->out_ep[i]->userExt);
}
priv->host_devices_table[udev->slot_id] = NULL;
usbDev->ld_udev->slot_id = 0;
usbDev->udev.userExt = (void *)NULL;
kfree(usbDev);
}
static int hc_reset_device(struct usb_hcd *hcd, struct usb_device *udev)
{
struct HOST_CTRL *priv;
struct HOST_USB_DEVICE *usb_device;
struct phytium_cusb *config;
if (!hcd || !udev)
return -EINVAL;
config = *(struct phytium_cusb **)hcd->hcd_priv;
priv = (struct HOST_CTRL *)config->host_priv;
usb_device = priv->host_devices_table[udev->slot_id];
if (!usb_device)
return -ENODEV;
usb_device->udev.speed = usb_device->ld_udev->speed;
usb_device->udev.devnum = usb_device->ld_udev->devnum;
if (USB_SPEED_HIGH == usb_device->udev.speed || USB_SPEED_FULL == usb_device->udev.speed)
usb_device->ep0_hep.desc.wMaxPacketSize = 64;
else
usb_device->ep0_hep.desc.wMaxPacketSize = 8;
pr_debug("speed:%d ep0 wMaxPacketSize:%d\n", usb_device->udev.speed,
usb_device->ep0_hep.desc.wMaxPacketSize);
return 0;
}
static int hc_update_device(struct usb_hcd *hcd, struct usb_device *udev)
{
struct HOST_CTRL *priv;
struct HOST_USB_DEVICE *usb_device;
struct phytium_cusb *config;
if (!hcd || !udev)
return -EINVAL;
config = *(struct phytium_cusb **)(hcd->hcd_priv);
priv = (struct HOST_CTRL *)config->host_priv;
usb_device = priv->host_devices_table[udev->slot_id];
if (!usb_device)
return -ENODEV;
usb_device->udev.devnum = udev->devnum;
return 0;
}
static int hc_add_endpoint(struct usb_hcd *hcd, struct usb_device *udev,
struct usb_host_endpoint *ep)
{
return 0;
}
static int hc_drop_endpoint(struct usb_hcd *hcd, struct usb_device *udev,
struct usb_host_endpoint *ep)
{
return 0;
}
static int hc_hub_status_data(struct usb_hcd *hcd, char *buf)
{
struct HOST_CTRL *priv;
struct phytium_cusb *config = *(struct phytium_cusb **)hcd->hcd_priv;
if (!config)
return 0;
priv = (struct HOST_CTRL *)config->host_priv;
if (!priv)
return 0;
if (priv->portStatus & 0xffff0000) {
*buf = 0x02;
return 1;
}
return 0;
}
#ifdef CONFIG_PM
static int hc_bus_suspend(struct usb_hcd *hcd)
{
unsigned long flags;
struct phytium_cusb *config = *(struct phytium_cusb **)(hcd->hcd_priv);
if (!config)
return 0;
spin_lock_irqsave(&config->lock, flags);
hcd->state = HC_STATE_SUSPENDED;
spin_unlock_irqrestore(&config->lock, flags);
return 0;
}
static int hc_bus_resume(struct usb_hcd *hcd)
{
unsigned long flags;
struct phytium_cusb *config = *(struct phytium_cusb **)(hcd->hcd_priv);
if (!config)
return 0;
spin_lock_irqsave(&config->lock, flags);
hcd->state = HC_STATE_RESUMING;
spin_unlock_irqrestore(&config->lock, flags);
return 0;
}
#endif
static void host_giveback_request(struct HOST_CTRL *priv,
struct HOST_REQ *req, uint32_t status)
{
struct urb *urb_req;
int urb_status = 0;
int i = 0;
struct phytium_cusb *config;
if (!priv || !req)
return;
urb_req = req->userExt;
urb_req->actual_length = req->actualLength;
switch (status) {
case HOST_ESTALL:
urb_status = -EPIPE;
break;
case HOST_EUNHANDLED:
urb_status = -EPROTO;
break;
case HOST_ESHUTDOWN:
urb_status = -ESHUTDOWN;
break;
default:
urb_status = status;
}
pr_debug("complete %p %pS (%d) dev%d ep%d%s %d/%d\n",
urb_req, urb_req->complete, urb_status,
usb_pipedevice(urb_req->pipe),
usb_pipeendpoint(urb_req->pipe),
usb_pipein(urb_req->pipe) ? "in" : "out",
urb_req->actual_length,
urb_req->transfer_buffer_length);
if (usb_endpoint_xfer_isoc(&urb_req->ep->desc)) {
for (i = 0; i < urb_req->number_of_packets; i++) {
urb_req->iso_frame_desc[i].status = 0;
urb_req->iso_frame_desc[i].actual_length = req->isoFramesDesc[i].length;
}
}
config = dev_get_drvdata(priv->dev);
usb_hcd_unlink_urb_from_ep(config->hcd, urb_req);
spin_unlock(&config->lock);
usb_hcd_giveback_urb(config->hcd, urb_req, urb_status);
kfree(req);
spin_lock(&config->lock);
}
static void host_rh_port_status_change(struct HOST_CTRL *priv)
{
uint32_t statusHub;
char *status;
uint32_t retval;
struct usb_ctrlrequest setup;
struct phytium_cusb *config;
if (!priv)
return;
config = dev_get_drvdata(priv->dev);
if (!config)
return;
status = (char *)&statusHub;
hc_hub_status_data(config->hcd, status);
if (status) {
setup.bRequestType = USB_TYPE_CLASS | USB_RECIP_OTHER | USB_DIR_IN;//to host
setup.bRequest = USB_REQ_GET_STATUS;
setup.wValue = 0;
setup.wIndex = 1;
setup.wLength = 4;
retval = config->host_obj->host_vhubControl(priv, &setup, (uint8_t *)&statusHub);
if (retval)
return;
if (status[1] & USB_PORT_STAT_C_CONNECTION) {
if (status[0] & USB_PORT_STAT_CONNECTION) {
if (config->hcd->status_urb)
usb_hcd_poll_rh_status(config->hcd);
else
usb_hcd_resume_root_hub(config->hcd);
} else {
usb_hcd_resume_root_hub(config->hcd);
usb_hcd_poll_rh_status(config->hcd);
}
}
} else
usb_hcd_resume_root_hub(config->hcd);
}
static void host_set_ep_toggle(struct HOST_CTRL *priv,
struct HOST_DEVICE *udev, u8 ep_num, u8 is_in, u8 toggle)
{
struct HOST_USB_DEVICE *device;
if (!priv || !udev)
return;
device = (struct HOST_USB_DEVICE *)udev->userExt;
usb_settoggle(device->ld_udev, ep_num, !is_in, toggle);
}
static u8 host_get_ep_toggle(struct HOST_CTRL *priv,
struct HOST_DEVICE *udev, u8 ep_num, u8 is_in)
{
struct HOST_USB_DEVICE *device;
if (!priv || !udev)
return 0;
device = (struct HOST_USB_DEVICE *)udev->userExt;
return usb_gettoggle(device->ld_udev, ep_num, !is_in);
}
static uint32_t initEndpoints(struct HOST_CTRL *priv)
{
int epNum;
if (!priv)
return 0;
priv->hwEpInCount = 0;
priv->hwEpOutCount = 0;
phytium_write8(&priv->regs->ep0fifoctrl, FIFOCTRL_FIFOAUTO | 0);
phytium_write8(&priv->regs->ep0fifoctrl, FIFOCTRL_FIFOAUTO | FIFOCTRL_IO_TX | 0);
for (epNum = 0; epNum < HOST_EP_NUM; epNum++) {
priv->in[epNum].isInEp = 1;
priv->in[epNum].hwEpNum = epNum;
if (priv->hostCfg.epIN[epNum].bufferingValue) {
priv->in[epNum].hwMaxPacketSize = priv->hostCfg.epIN[epNum].maxPacketSize;
priv->in[epNum].hwBuffers = priv->hostCfg.epIN[epNum].bufferingValue;
priv->in[epNum].state = HOST_EP_FREE;
priv->in[epNum].channel = NULL;
priv->hwEpInCount++;
if (epNum) {
phytium_write16(&priv->regs->rxstaddr[epNum - 1].addr,
priv->hostCfg.epIN[epNum].startBuf);
phytium_write8(&priv->regs->ep[epNum - 1].rxcon, 0x08);
phytium_write8(&priv->regs->fifoctrl, FIFOCTRL_FIFOAUTO | epNum);
phytium_write8(&priv->regs->irqmode[epNum - 1].inirqmode, 0x80);
}
} else
priv->in[epNum].state = HOST_EP_NOT_IMPLEMENTED;
priv->out[epNum].isInEp = 0;
priv->out[epNum].hwEpNum = epNum;
if (priv->hostCfg.epOUT[epNum].bufferingValue) {
priv->out[epNum].hwMaxPacketSize = priv->hostCfg.epOUT[epNum].maxPacketSize;
priv->out[epNum].hwBuffers = priv->hostCfg.epOUT[epNum].bufferingValue;
priv->out[epNum].state = HOST_EP_FREE;
priv->out[epNum].channel = NULL;
priv->hwEpInCount++;
if (epNum) {
phytium_write16(&priv->regs->txstaddr[epNum - 1].addr,
priv->hostCfg.epOUT[epNum].startBuf);
phytium_write8(&priv->regs->ep[epNum - 1].txcon, 0x08);
phytium_write8(&priv->regs->fifoctrl,
FIFOCTRL_FIFOAUTO | FIFOCTRL_IO_TX | epNum);
phytium_write8(&priv->regs->irqmode[epNum - 1].outirqmode, 0x80);
}
} else
priv->out[epNum].state = HOST_EP_NOT_IMPLEMENTED;
}
return 0;
}
int32_t hostInit(struct HOST_CTRL *priv, struct HOST_CFG *config,
struct HOST_CALLBACKS *callbacks, struct device *pdev, bool isVhubHost)
{
int index;
if (!config || !priv || !callbacks || !pdev)
return -EINVAL;
priv->dev = pdev;
priv->hostCallbacks = *callbacks;
priv->hostCfg = *config;
priv->regs = (struct HW_REGS *)config->regBase;
priv->hostDrv = HOST_GetInstance();
priv->dmaDrv = DMA_GetInstance();
priv->dmaCfg.dmaModeRx = 0xFFFF;
priv->dmaCfg.dmaModeTx = 0xFFFF;
priv->dmaCfg.regBase = config->regBase + 0x400;
priv->dmaCfg.trbAddr = config->trbAddr;
priv->dmaCfg.trbDmaAddr = config->trbDmaAddr;
priv->dmaCallback.complete = host_CallbackTransfer;
priv->dmaController = (void *)(priv + 1);
priv->dmaDrv->dma_init(priv->dmaController, &priv->dmaCfg, &priv->dmaCallback);
priv->dmaDrv->dma_setParentPriv(priv->dmaController, priv);
INIT_LIST_HEAD(&priv->ctrlHEpQueue);
for (index = 0; index < MAX_INSTANCE_EP_NUM; index++) {
INIT_LIST_HEAD(&priv->isoInHEpQueue[index]);
INIT_LIST_HEAD(&priv->isoOutHEpQueue[index]);
INIT_LIST_HEAD(&priv->intInHEpQueue[index]);
INIT_LIST_HEAD(&priv->intOutHEpQueue[index]);
INIT_LIST_HEAD(&priv->bulkInHEpQueue[index]);
INIT_LIST_HEAD(&priv->bulkOutHEpQueue[index]);
}
phytium_write8(&priv->regs->cpuctrl, BIT(1));
phytium_write8(&priv->regs->otgctrl, OTGCTRL_ABUSDROP);
phytium_write8(&priv->regs->ep0maxpack, 0x40);
//disable interrupts
phytium_write8(&priv->regs->otgien, 0x0);
phytium_write8(&priv->regs->usbien, 0x0);
phytium_write16(&priv->regs->txerrien, 0x0);
phytium_write16(&priv->regs->rxerrien, 0x0);
//clear all interrupt except otg idle irq
phytium_write8(&priv->regs->otgirq, 0xFE);
phytium_write8(&priv->regs->usbirq, 0xFF);
phytium_write16(&priv->regs->txerrirq, 0xFF);
phytium_write16(&priv->regs->rxerrirq, 0xFF);
phytium_write8(&priv->regs->tawaitbcon, 0x80);
initEndpoints(priv);
priv->otgState = HOST_OTG_STATE_B_IDLE;
//reset all endpoint
phytium_write8(&priv->regs->endprst, ENDPRST_IO_TX);
phytium_write8(&priv->regs->endprst, ENDPRST_FIFORST | ENDPRST_TOGRST | ENDPRST_IO_TX);
phytium_write8(&priv->regs->endprst, ENDPRST_FIFORST | ENDPRST_TOGRST);
if (isVhubHost)
priv->vhub_regs = (struct VHUB_REGS *)(config->phy_regBase);
else
priv->custom_regs = (struct CUSTOM_REGS *)(config->phy_regBase);
return 0;
}
void hostDestroy(struct HOST_CTRL *priv)
{
}
void hostStart(struct HOST_CTRL *priv)
{
uint8_t otgstate, usbien;
if (!priv)
return;
priv->dmaDrv->dma_start(priv->dmaController);
usbien = phytium_read8(&priv->regs->usbien);
usbien = usbien | USBIR_URES | USBIR_SUSP;
phytium_write8(&priv->regs->usbien, usbien);
retry:
otgstate = phytium_read8(&priv->regs->otgstate);
switch (otgstate) {
case HOST_OTG_STATE_A_IDLE:
priv->ep0State = HOST_EP0_STAGE_IDLE;
priv->otgState = HOST_OTG_STATE_A_IDLE;
phytium_write8(&priv->regs->otgirq, OTGIRQ_IDLEIRQ);
host_SetVbus(priv, 1);
break;
case HOST_OTG_STATE_B_IDLE:
host_SetVbus(priv, 1);
break;
case HOST_OTG_STATE_A_WAIT_VFALL:
goto retry;
}
phytium_write8(&priv->regs->otgien, OTGIRQ_CONIRQ |
OTGIRQ_VBUSERRIRQ | OTGIRQ_SRPDETIRQ);
}
void hostStop(struct HOST_CTRL *priv)
{
if (!priv)
return;
phytium_write8(&priv->regs->otgien, 0x0);
phytium_write8(&priv->regs->usbien, 0x0);
phytium_write16(&priv->regs->txerrien, 0x0);
phytium_write16(&priv->regs->rxerrien, 0x0);
phytium_write8(&priv->regs->otgirq, 0xFE);
phytium_write8(&priv->regs->usbirq, 0xFF);
phytium_write16(&priv->regs->txerrirq, 0xFF);
phytium_write16(&priv->regs->rxerrirq, 0xFF);
host_SetVbus(priv, 0);
priv->dmaDrv->dma_stop(priv->dmaController);
}
static void handleReset(struct HOST_CTRL *priv)
{
if (!priv)
return;
if (priv->otgState == HOST_OTG_STATE_A_WAIT_BCON
|| priv->otgState == HOST_OTG_STATE_B_WAIT_ACON) {
switch (phytium_read8(&priv->regs->speedctrl)) {
case SPEEDCTRL_HS:
priv->portStatus |= USB_PORT_STAT_HIGH_SPEED;
break;
case SPEEDCTRL_FS:
priv->portStatus &= ~(USB_PORT_STAT_HIGH_SPEED | USB_PORT_STAT_LOW_SPEED);
break;
case SPEEDCTRL_LS:
priv->portStatus |= USB_PORT_STAT_LOW_SPEED;
break;
}
priv->dmaDrv->dma_setHostMode(priv->dmaController);
if (priv->hostCallbacks.portStatusChange)
priv->hostCallbacks.portStatusChange(priv);
switch (phytium_read8(&priv->regs->otgstate)) {
case HOST_OTG_STATE_B_HOST:
priv->otgState = HOST_OTG_STATE_B_HOST;
break;
case HOST_OTG_STATE_A_HOST:
break;
default:
priv->otgState = HOST_OTG_STATE_A_HOST;
break;
}
}
}
void hostIsr(struct HOST_CTRL *priv)
{
uint8_t usbirq, usbien;
if (!priv)
return;
usbirq = phytium_read8(&priv->regs->usbirq);
usbien = phytium_read8(&priv->regs->usbien);
pr_debug("raw usbirq:0x%x usbien:0x%x\n", usbirq, usbien);
usbirq = usbirq & usbien;
hostErrorIrq(priv);
hostOtgIrq(priv);
if (!usbirq)
goto DMA_IRQ;
if (usbirq & USBIR_URES) {
phytium_write8(&priv->regs->usbirq, USBIR_URES);
priv->port_resetting = 0;
handleReset(priv);
}
if (usbirq & USBIR_SOF)
phytium_write8(&priv->regs->usbirq, USBIR_SOF);
if (usbirq & USBIR_SUSP) {
pr_debug("clear suspend irq\n");
phytium_write8(&priv->regs->usbirq, USBIR_SUSP);
phytium_write8(&priv->regs->clkgate, 0x7);
}
return;
DMA_IRQ:
priv->dmaDrv->dma_isr(priv->dmaController);
}
int32_t hostEpDisable(struct HOST_CTRL *priv, struct HOST_EP *ep)
{
return 0;
}
int32_t hostReqQueue(struct HOST_CTRL *priv, struct HOST_REQ *req)
{
struct HOST_EP_PRIV *hostEpPriv;
struct list_head *hEpQueue = NULL;
uint32_t interval = 0;
uint8_t idleQueue = 0;
if (!priv || !req)
return -EINVAL;
list_add_tail((struct list_head *)&req->list, (struct list_head *)&req->usbEp->reqList);
hostEpPriv = (struct HOST_EP_PRIV *)req->usbEp->hcPriv;
if (hostEpPriv->epIsReady) {
if (usb_endpoint_xfer_isoc(&req->usbEp->desc)) {
hostEpPriv->isocEpConfigured = 1;
hostStartReq(priv, req);
}
return 0;
}
hostEpPriv->epIsReady = 1;
hostEpPriv->maxPacketSize = req->usbEp->desc.wMaxPacketSize;
hostEpPriv->interval = req->interval;
hostEpPriv->epNum = req->usbEp->desc.bEndpointAddress & 0xf;
hostEpPriv->faddress = req->faddress;
hostEpPriv->usbHEp = req->usbEp;
hostEpPriv->isIn = req->epIsIn;
switch (usb_endpoint_type(&req->usbEp->desc)) {
case USB_ENDPOINT_XFER_CONTROL:
hostEpPriv->isIn = 0;
hEpQueue = &priv->ctrlHEpQueue;
hostEpPriv->type = USB_ENDPOINT_XFER_CONTROL;
break;
case USB_ENDPOINT_XFER_BULK:
hEpQueue = hostEpPriv->isIn ? &priv->bulkInHEpQueue[req->epNum - 1] :
&priv->bulkOutHEpQueue[req->epNum - 1];
hostEpPriv->type = USB_ENDPOINT_XFER_BULK;
break;
case USB_ENDPOINT_XFER_INT:
if (req->usbDev->speed <= USB_SPEED_FULL)
interval = (req->usbEp->desc.bInterval < 1) ?
1 : req->usbEp->desc.bInterval;
else {
if (req->usbEp->desc.bInterval < 1)
interval = 1;
else if (req->usbEp->desc.bInterval > 16)
interval = 16;
else
interval = req->usbEp->desc.bInterval;
interval = 1 << (interval - 1);
}
hEpQueue = hostEpPriv->isIn ? &priv->intInHEpQueue[req->epNum - 1] :
&priv->intOutHEpQueue[req->epNum - 1];
hostEpPriv->type = USB_ENDPOINT_XFER_INT;
hostEpPriv->frame = interval;
hostEpPriv->interval = interval;
break;
case USB_ENDPOINT_XFER_ISOC:
if (req->usbEp->desc.bInterval < 1)
interval = 1;
else if (req->usbEp->desc.bInterval > 16)
interval = 16;
else
interval = req->usbEp->desc.bInterval;
interval = 1 << (interval - 1);
hEpQueue = hostEpPriv->isIn ? &priv->isoInHEpQueue[req->epNum - 1] :
&priv->isoOutHEpQueue[req->epNum - 1];
hostEpPriv->type = USB_ENDPOINT_XFER_ISOC;
hostEpPriv->frame = interval;
hostEpPriv->interval = interval;
break;
default:
break;
}
if (hostEpPriv->isIn)
hostEpPriv->genericHwEp = &priv->in[req->epNum];
else
hostEpPriv->genericHwEp = &priv->out[req->epNum];
hostEpPriv->genericHwEp->state = HOST_EP_ALLOCATED;
hostEpPriv->genericHwEp->refCount++;
if (list_empty(hEpQueue)) {
if (hostEpPriv->genericHwEp->state == HOST_EP_BUSY) {
pr_err("Error:Hardware endpoint %d is busy\n",
hostEpPriv->genericHwEp->hwEpNum);
return -EINVAL;
}
idleQueue = 1;
}
if (usb_endpoint_xfer_int(&req->usbEp->desc))
list_add_tail(&hostEpPriv->node, hEpQueue);
if (idleQueue)
hostStartReq(priv, req);
return 0;
}
static int abortActuallyUsbRequest(struct HOST_CTRL *priv,
struct HOST_REQ *req, struct HOST_EP *usbEp)
{
struct HOST_EP_PRIV *usbEpPriv;
struct HostEp *hostEp;
uint16_t rxerrien = 0;
uint16_t txerrien = 0;
uint8_t rxcon, txcon;
if (!priv || !req || !usbEp)
return -EINVAL;
usbEpPriv = (struct HOST_EP_PRIV *)usbEp->hcPriv;
hostEp = usbEpPriv->currentHwEp;
usbEpPriv->transferFinished = 1;
if (hostEp->isInEp) {
if (hostEp->hwEpNum) {
rxcon = phytium_read8(&priv->regs->ep[hostEp->hwEpNum - 1].rxcon);
rxcon = rxcon & (~BIT(7));
phytium_write8(&priv->regs->ep[hostEp->hwEpNum - 1].rxcon, rxcon);
}
rxerrien = phytium_read16(&priv->regs->rxerrien);
rxerrien &= ~(1 << hostEp->hwEpNum);
phytium_write16(&priv->regs->rxerrien, rxerrien);
phytium_write8(&priv->regs->endprst, ENDPRST_FIFORST |
ENDPRST_IO_TX | hostEp->hwEpNum);
} else {
if (hostEp->hwEpNum) {
txcon = phytium_read8(&priv->regs->ep[hostEp->hwEpNum - 1].txcon);
txcon = txcon & (~BIT(7));
phytium_write8(&priv->regs->ep[hostEp->hwEpNum - 1].txcon, txcon);
}
txerrien = phytium_read16(&priv->regs->txerrien);
txerrien &= ~(1 << hostEp->hwEpNum);
phytium_write16(&priv->regs->txerrien, txerrien);
phytium_write8(&priv->regs->endprst, ENDPRST_FIFORST | hostEp->hwEpNum);
}
scheduleNextTransfer(priv, req, hostEp);
return 0;
}
int32_t hostReqDequeue(struct HOST_CTRL *priv, struct HOST_REQ *req, uint32_t status)
{
struct HOST_EP *usbEp;
struct HOST_EP_PRIV *usbEpPriv;
int ret = 0;
if (!priv || !req)
return -EINVAL;
usbEp = req->usbEp;
usbEpPriv = (struct HOST_EP_PRIV *)usbEp->hcPriv;
pr_debug("Dequeue usbReq:%p dev%d usbEp%d%s\n", req, req->faddress, req->epNum,
req->epIsIn ? "in" : "out");
if (!usbEpPriv->epIsReady)
return 0;
if (!usbEpPriv->currentHwEp || req->list.prev != &usbEp->reqList) {
givebackRequest(priv, req, status);
if (list_empty(&usbEp->reqList)) {
usbEpPriv->epIsReady = 0;
usbEpPriv->currentHwEp = NULL;
if (usb_endpoint_xfer_int(&usbEp->desc))
list_del(&usbEpPriv->node);
}
} else
ret = abortActuallyUsbRequest(priv, req, usbEp);
if (usbEpPriv->isocEpConfigured)
usbEpPriv->isocEpConfigured = 0;
return ret;
}
int32_t hostVHubStatusData(struct HOST_CTRL *priv, uint8_t *status)
{
return 0;
}
int32_t hostGetDevicePD(struct HOST_CTRL *priv)
{
return 0;
}
int32_t hostGetPrivateDataSize(struct HOST_CTRL *priv)
{
if (!priv)
return 0;
return sizeof(struct HOST_EP_PRIV);
}
static int hub_descriptor(struct usb_hub_descriptor *buf)
{
buf->bDescLength = 9;
buf->bDescriptorType = 0x29;
buf->bNbrPorts = 1;
buf->wHubCharacteristics = 0x11;
buf->bPwrOn2PwrGood = 5;
buf->bHubContrCurrent = 0;
buf->u.hs.DeviceRemovable[0] = 0x2;
return 0;
}
static int HubPortSuspend(struct HOST_CTRL *priv, u16 on)
{
uint8_t otgctrl;
if (!priv)
return 0;
otgctrl = phytium_read8(&priv->regs->otgctrl);
if (on) {
otgctrl &= ~OTGCTRL_BUSREQ;
otgctrl &= ~OTGCTRL_BHNPEN;
priv->portStatus |= USB_PORT_STAT_SUSPEND;
switch (phytium_read8(&priv->regs->otgstate)) {
case HOST_OTG_STATE_A_HOST:
priv->otgState = HOST_OTG_STATE_A_SUSPEND;
otgctrl |= OTGCTRL_ASETBHNPEN;
break;
case HOST_OTG_STATE_B_HOST:
priv->otgState = HOST_OTG_STATE_B_HOST_2;
break;
default:
break;
}
phytium_write8(&priv->regs->otgctrl, otgctrl);
} else {
otgctrl |= OTGCTRL_BUSREQ;
otgctrl &= ~OTGCTRL_ASETBHNPEN;
phytium_write8(&priv->regs->otgctrl, otgctrl);
priv->portStatus |= USB_PORT_STAT_RESUME;
}
return 0;
}
static void HubPortReset(struct HOST_CTRL *priv, uint8_t on)
{
uint8_t speed;
if (!priv)
return;
if (on) {
phytium_write16(&priv->regs->txerrirq, 0xFFFF);
phytium_write16(&priv->regs->txirq, 0xFFFF);
phytium_write16(&priv->regs->rxerrirq, 0xFFFF);
phytium_write16(&priv->regs->rxirq, 0xFFFF);
phytium_write8(&priv->regs->endprst, ENDPRST_IO_TX);
phytium_write8(&priv->regs->endprst, ENDPRST_FIFORST |
ENDPRST_TOGRST | ENDPRST_IO_TX);
phytium_write8(&priv->regs->endprst, ENDPRST_FIFORST | ENDPRST_TOGRST);
phytium_write8(&priv->regs->ep0fifoctrl, FIFOCTRL_FIFOAUTO | 0 | 0x04);
phytium_write8(&priv->regs->ep0fifoctrl, FIFOCTRL_FIFOAUTO |
FIFOCTRL_IO_TX | 0 | 0x04);
priv->portStatus |= USB_PORT_STAT_RESET;
priv->portStatus &= ~USB_PORT_STAT_ENABLE;
priv->port_resetting = 0;
} else {
speed = phytium_read8(&priv->regs->speedctrl);
if (speed == SPEEDCTRL_HS)
priv->portStatus |= USB_PORT_STAT_HIGH_SPEED;
else if (speed == SPEEDCTRL_FS)
priv->portStatus &= ~(USB_PORT_STAT_HIGH_SPEED | USB_PORT_STAT_LOW_SPEED);
else
priv->portStatus |= USB_PORT_STAT_LOW_SPEED;
priv->portStatus &= ~USB_PORT_STAT_RESET;
priv->portStatus |= USB_PORT_STAT_ENABLE | (USB_PORT_STAT_C_RESET << 16)
| (USB_PORT_STAT_C_ENABLE << 16);
if (priv->hostCallbacks.portStatusChange)
priv->hostCallbacks.portStatusChange(priv);
}
}
static int get_PortStatus(struct HOST_CTRL *priv, u16 wIndex, uint8_t *buff)
{
uint32_t temp = 0;
if (!priv || !buff)
return 0;
if ((wIndex & 0xff) != 1)
return 1;
if (priv->portStatus & USB_PORT_STAT_RESET) {
if (!priv->port_resetting)
HubPortReset(priv, 0);
}
if (priv->portStatus & USB_PORT_STAT_RESUME) {
priv->portStatus &= ~(USB_PORT_STAT_SUSPEND | USB_PORT_STAT_RESUME);
priv->portStatus |= USB_PORT_STAT_C_SUSPEND << 16;
if (priv->hostCallbacks.portStatusChange)
priv->hostCallbacks.portStatusChange(priv);
}
temp = priv->portStatus & (~USB_PORT_STAT_RESUME);
buff[0] = temp;
buff[1] = temp >> 8;
buff[2] = temp >> 16;
buff[3] = temp >> 24;
return 0;
}
static int set_PortFeature(struct HOST_CTRL *priv, u16 wValue, u16 wIndex)
{
// struct HW_Regs *regs = priv->regs;
if ((wIndex & 0xff) != 1)
return 1;
switch (wValue) {
case USB_PORT_FEAT_CONNECTION:
break;
case USB_PORT_FEAT_ENABLE:
break;
case USB_PORT_FEAT_SUSPEND:
HubPortSuspend(priv, 1);
break;
case USB_PORT_FEAT_OVER_CURRENT:
break;
case USB_PORT_FEAT_RESET:
HubPortReset(priv, 1);
break;
case USB_PORT_FEAT_L1:
break;
case USB_PORT_FEAT_POWER:
hostStart(priv);
break;
case USB_PORT_FEAT_LOWSPEED:
break;
case USB_PORT_FEAT_C_CONNECTION:
break;
case USB_PORT_FEAT_C_ENABLE:
break;
case USB_PORT_FEAT_C_SUSPEND:
break;
case USB_PORT_FEAT_C_OVER_CURRENT:
break;
case USB_PORT_FEAT_INDICATOR:
break;
case USB_PORT_FEAT_C_PORT_L1:
break;
default:
break;
}
priv->portStatus |= 1 << wValue;
return 0;
}
static int Clear_PortFeature(struct HOST_CTRL *priv, u16 wValue, u16 wIndex)
{
if ((wIndex & 0xff) != 1)
return 1;
switch (wValue) {
case USB_PORT_FEAT_CONNECTION:
break;
case USB_PORT_FEAT_ENABLE:
break;
case USB_PORT_FEAT_SUSPEND:
HubPortSuspend(priv, 0);
break;
case USB_PORT_FEAT_OVER_CURRENT:
break;
case USB_PORT_FEAT_RESET:
break;
case USB_PORT_FEAT_L1:
break;
case USB_PORT_FEAT_POWER:
break;
case USB_PORT_FEAT_LOWSPEED:
break;
case USB_PORT_FEAT_C_CONNECTION:
break;
case USB_PORT_FEAT_C_ENABLE:
break;
case USB_PORT_FEAT_C_SUSPEND:
break;
case USB_PORT_FEAT_C_OVER_CURRENT:
break;
case USB_PORT_FEAT_INDICATOR:
break;
case USB_PORT_FEAT_C_PORT_L1:
break;
default:
break;
}
priv->portStatus &= ~(1 << wValue);
return 0;
}
static int hc_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
u16 wIndex, char *buf, u16 wLength)
{
unsigned long flags = 0;
int retval = 0;
struct HOST_CTRL *priv;
struct phytium_cusb *config = *(struct phytium_cusb **)hcd->hcd_priv;
if (!config)
return -EINVAL;
if (!buf)
return -EINVAL;
if (unlikely(!HCD_HW_ACCESSIBLE(hcd))) {
spin_unlock_irqrestore(&config->lock, flags);
return -ESHUTDOWN;
}
priv = (struct HOST_CTRL *)config->host_priv;
if (!priv)
return -EINVAL;
spin_lock_irqsave(&config->lock, flags);
switch (typeReq) {
case GetHubStatus:
break;
case GetPortStatus:
get_PortStatus(priv, wIndex, (uint8_t *)buf);
break;
case GetHubDescriptor:
hub_descriptor((struct usb_hub_descriptor *)buf);
break;
case SetPortFeature:
set_PortFeature(priv, wValue, wIndex);
break;
case ClearPortFeature:
retval = Clear_PortFeature(priv, wValue, wIndex);
break;
case SetHubFeature:
break;
case ClearHubFeature:
break;
default:
break;
}
spin_unlock_irqrestore(&config->lock, flags);
return retval;
}
int32_t hostVHubControl(struct HOST_CTRL *priv, struct usb_ctrlrequest *setup, uint8_t *buff)
{
uint16_t request;
uint32_t retval = 0;
if (!priv || !setup || !buff)
return -EINVAL;
request = setup->bRequestType << 0x08 | setup->bRequest;
switch (request) {
case ClearHubFeature:
break;
case SetHubFeature:
break;
case ClearPortFeature:
retval = Clear_PortFeature(priv, setup->wValue, setup->wIndex);
break;
case SetPortFeature:
retval = set_PortFeature(priv, setup->wValue, setup->wIndex);
break;
case GetHubDescriptor:
retval = hub_descriptor((struct usb_hub_descriptor *)buff);
break;
case GetHubStatus:
break;
case GetPortStatus:
retval = get_PortStatus(priv, setup->wIndex, (uint8_t *)buff);
break;
default:
retval = EOPNOTSUPP;
break;
}
return retval;
}
struct HOST_OBJ hostDrv = {
.host_init = hostInit,
.host_destroy = hostDestroy,
.host_start = hostStart,
.host_stop = hostStop,
.host_isr = hostIsr,
//endpoint operation
.host_epDisable = hostEpDisable,
.host_reqQueue = hostReqQueue,
.host_reqDequeue = hostReqDequeue,
.host_vhubStatusData = hostVHubStatusData,
.host_vhubControl = hostVHubControl,
.host_getDevicePD = hostGetDevicePD,
.host_epGetPrivateDataSize = hostGetPrivateDataSize,
};
static struct hc_driver host_driver = {
.description = "phytium-hcd",
.product_desc = "Phytium Host USB Driver",
.hcd_priv_size = sizeof(struct phytium_cusb *),
.flags = HCD_MEMORY | HCD_USB2,
.reset = hc_reset,
.start = hc_start,
.stop = hc_stop,
.shutdown = hc_shutdown,
.urb_enqueue = hc_urb_enqueue,
.urb_dequeue = hc_urb_dequeue,
.endpoint_disable = hc_endpoint_disable,
.get_frame_number = hc_get_frame,
.alloc_dev = hc_alloc_dev,
.free_dev = hc_free_dev,
.reset_device = hc_reset_device,
.update_device = hc_update_device,
.add_endpoint = hc_add_endpoint,
.drop_endpoint = hc_drop_endpoint,
.hub_status_data = hc_hub_status_data,
.hub_control = hc_hub_control,
#ifdef CONFIG_PM
.bus_suspend = hc_bus_suspend,
.bus_resume = hc_bus_resume,
#endif
};
static int phytium_host_set_default_cfg(struct phytium_cusb *config)
{
int index;
config->host_cfg.regBase = (uintptr_t)config->regs;
config->host_cfg.phy_regBase = (uintptr_t)config->phy_regs;
config->host_cfg.dmultEnabled = 1;
config->host_cfg.memoryAlignment = 0;
config->host_cfg.dmaSupport = 1;
config->host_cfg.isEmbeddedHost = 1;
for (index = 0; index < HOST_EP_NUM; index++) {
if (index == 0) {
config->host_cfg.epIN[index].bufferingValue = 1;
config->host_cfg.epIN[index].maxPacketSize = 64;
config->host_cfg.epIN[index].startBuf = 0;
config->host_cfg.epOUT[index].bufferingValue = 1;
config->host_cfg.epOUT[index].maxPacketSize = 64;
config->host_cfg.epOUT[index].startBuf = 0;
} else {
config->host_cfg.epIN[index].bufferingValue = 2;
config->host_cfg.epIN[index].maxPacketSize = 1024;
config->host_cfg.epIN[index].startBuf = 64 + 2 * 1024 * (index - 1);
config->host_cfg.epOUT[index].bufferingValue = 2;
config->host_cfg.epOUT[index].maxPacketSize = 1024;
config->host_cfg.epOUT[index].startBuf = 64 + 2 * 1024 * (index - 1);
}
}
return 0;
}
static int phytium_host_reinit(struct phytium_cusb *config)
{
struct HOST_CTRL *ctrl;
if (!config || !config->host_priv)
return 0;
ctrl = (struct HOST_CTRL *)config->host_priv;
usb_root_hub_lost_power(config->hcd->self.root_hub);
hostStop(ctrl);
ctrl->portStatus = 0;
config->host_obj->host_init(config->host_priv, &config->host_cfg,
&config->host_callbacks, config->dev, config->isVhubHost);
return 0;
}
int phytium_host_init(struct phytium_cusb *config)
{
int ret;
if (!config)
return 0;
phytium_host_set_default_cfg(config);
config->host_obj = HOST_GetInstance();
config->dma_cfg.regBase = config->host_cfg.regBase + 0x400;
config->dma_obj = DMA_GetInstance();
config->dma_obj->dma_probe(&config->dma_cfg, &config->dma_sysreq);
config->host_sysreq.privDataSize = sizeof(struct HOST_CTRL);
config->host_sysreq.trbMemSize = config->dma_sysreq.trbMemSize;
config->host_sysreq.privDataSize += config->dma_sysreq.privDataSize;
config->host_priv = devm_kzalloc(config->dev, config->host_sysreq.privDataSize, GFP_KERNEL);
if (!config->host_priv) {
ret = -ENOMEM;
goto err_probe;
}
config->host_cfg.trbAddr = dma_alloc_coherent(config->dev, config->host_sysreq.trbMemSize,
(dma_addr_t *)&config->host_cfg.trbDmaAddr, GFP_KERNEL);
if (!config->host_cfg.trbAddr) {
ret = -ENOMEM;
goto err_dma_coherent;
}
config->host_callbacks.portStatusChange = host_rh_port_status_change;
config->host_callbacks.getEpToggle = host_get_ep_toggle;
config->host_callbacks.setEpToggle = host_set_ep_toggle;
config->host_callbacks.givebackRequest = host_giveback_request;
config->host_obj->host_init(config->host_priv, &config->host_cfg,
&config->host_callbacks, config->dev, config->isVhubHost);
config->hcd = usb_create_hcd(&host_driver, config->dev, dev_name(config->dev));
if (!config->hcd) {
ret = -ENODEV;
goto err_host;
}
*config->hcd->hcd_priv = (unsigned long)config;
config->hcd->self.uses_pio_for_control = 0;
config->hcd->uses_new_polling = 1;
config->hcd->has_tt = 1;
dev_set_drvdata(config->dev, config);
config->hcd->self.otg_port = 1;
config->hcd->power_budget = 500;
ret = usb_add_hcd(config->hcd, 0, 0);
if (ret < 0)
goto err_setup;
return 0;
err_setup:
usb_put_hcd(config->hcd);
err_host:
config->host_obj->host_destroy(config->host_priv);
dma_free_coherent(config->dev, config->host_sysreq.trbMemSize,
config->host_cfg.trbAddr, config->host_cfg.trbDmaAddr);
err_dma_coherent:
err_probe:
dev_set_drvdata(config->dev, NULL);
return ret;
}
int phytium_host_uninit(struct phytium_cusb *config)
{
if (!config)
return 0;
if (config->hcd) {
usb_remove_hcd(config->hcd);
usb_put_hcd(config->hcd);
config->hcd = NULL;
}
return 0;
}
#ifdef CONFIG_PM
int phytium_host_resume(void *priv)
{
int otgctrl;
struct phytium_cusb *config = (struct phytium_cusb *)priv;
struct HOST_CTRL *ctrl = (struct HOST_CTRL *)config->host_priv;
if (!ctrl)
return -EINVAL;
otgctrl = phytium_read8(&ctrl->regs->otgctrl);
otgctrl |= 1;
phytium_write8(&ctrl->regs->otgctrl, otgctrl);
phytium_host_reinit(config);
return 0;
}
int phytium_host_suspend(void *priv)
{
int otgctrl;
struct phytium_cusb *config = (struct phytium_cusb *)priv;
struct HOST_CTRL *ctrl = (struct HOST_CTRL *)config->host_priv;
if (!ctrl)
return -EINVAL;
otgctrl = phytium_read8(&ctrl->regs->otgctrl);
otgctrl = otgctrl & (~1);
phytium_write8(&ctrl->regs->otgctrl, otgctrl);
return 0;
}
#endif
struct HOST_OBJ *HOST_GetInstance(void)
{
return &hostDrv;
}