libpayload: usb: dwc2: support split transaction
With split transaction, dwc2 host controller can handle full- and low-speed devices on hub in high-speed mode. This commit adds support for split control and interrupt transfers BUG=None TEST=Connect usb keyboard through hub, usb keyboard can work BRANCH=None Change-Id: If7a00db21c8ad4c635f39581382b877603075d1a Signed-off-by: Patrick Georgi <pgeorgi@chromium.org> Original-Commit-Id: 4fb514b7f7f7e414fa94bfce05420957b1c57019 Original-Change-Id: I07e64064c6182d33905ae4efb13712645de7cf93 Original-Signed-off-by: Yunzhi Li <lyz@rock-chips.com> Original-Reviewed-on: https://chromium-review.googlesource.com/283282 Original-Tested-by: Lin Huang <hl@rock-chips.com> Original-Commit-Queue: Lin Huang <hl@rock-chips.com> Original-Reviewed-by: Julius Werner <jwerner@chromium.org> Reviewed-on: http://review.coreboot.org/10956 Tested-by: build bot (Jenkins) Reviewed-by: Stefan Reinauer <stefan.reinauer@coreboot.org>
This commit is contained in:
parent
ae3d71a4d1
commit
ebd3da7dba
|
@ -164,16 +164,20 @@ wait_for_complete(endpoint_t *ep, uint32_t ch_num)
|
||||||
|
|
||||||
if (hcint.chhltd) {
|
if (hcint.chhltd) {
|
||||||
writel(hcint.d32, ®->host.hchn[ch_num].hcintn);
|
writel(hcint.d32, ®->host.hchn[ch_num].hcintn);
|
||||||
if (hcint.xfercomp)
|
if (hcint.xfercomp || hcint.ack)
|
||||||
return hctsiz.xfersize;
|
return hctsiz.xfersize;
|
||||||
else if (hcint.nak || hcint.frmovrun)
|
else if (hcint.nak || hcint.frmovrun)
|
||||||
return hctsiz.xfersize;
|
return -HCSTAT_NAK;
|
||||||
else if (hcint.xacterr)
|
else if (hcint.xacterr)
|
||||||
return -HCSTAT_XFERERR;
|
return -HCSTAT_XFERERR;
|
||||||
else if (hcint.bblerr)
|
else if (hcint.bblerr)
|
||||||
return -HCSTAT_BABBLE;
|
return -HCSTAT_BABBLE;
|
||||||
else if (hcint.stall)
|
else if (hcint.stall)
|
||||||
return -HCSTAT_STALL;
|
return -HCSTAT_STALL;
|
||||||
|
else if (hcint.ack)
|
||||||
|
return -HCSTAT_ACK;
|
||||||
|
else if (hcint.nyet)
|
||||||
|
return -HCSTAT_NYET;
|
||||||
else
|
else
|
||||||
return -HCSTAT_UNKNOW;
|
return -HCSTAT_UNKNOW;
|
||||||
}
|
}
|
||||||
|
@ -204,7 +208,7 @@ wait_for_complete(endpoint_t *ep, uint32_t ch_num)
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
dwc2_transfer(endpoint_t *ep, int size, int pid, ep_dir_t dir,
|
dwc2_do_xfer(endpoint_t *ep, int size, int pid, ep_dir_t dir,
|
||||||
uint32_t ch_num, u8 *data_buf)
|
uint32_t ch_num, u8 *data_buf)
|
||||||
{
|
{
|
||||||
uint32_t do_copy;
|
uint32_t do_copy;
|
||||||
|
@ -241,6 +245,8 @@ dwc2_transfer(endpoint_t *ep, int size, int pid, ep_dir_t dir,
|
||||||
hcchar.devaddr = ep->dev->address;
|
hcchar.devaddr = ep->dev->address;
|
||||||
hcchar.chdis = 0;
|
hcchar.chdis = 0;
|
||||||
hcchar.chen = 1;
|
hcchar.chen = 1;
|
||||||
|
if (ep->dev->speed == LOW_SPEED)
|
||||||
|
hcchar.lspddev = 1;
|
||||||
|
|
||||||
if (size > DMA_SIZE) {
|
if (size > DMA_SIZE) {
|
||||||
usb_debug("Transfer too large: %d\n", size);
|
usb_debug("Transfer too large: %d\n", size);
|
||||||
|
@ -284,6 +290,110 @@ dwc2_transfer(endpoint_t *ep, int size, int pid, ep_dir_t dir,
|
||||||
return transferred;
|
return transferred;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
dwc2_split_transfer(endpoint_t *ep, int size, int pid, ep_dir_t dir,
|
||||||
|
uint32_t ch_num, u8 *data_buf, split_info_t *split)
|
||||||
|
{
|
||||||
|
dwc2_reg_t *reg = DWC2_REG(ep->dev->controller);
|
||||||
|
hfnum_t hfnum;
|
||||||
|
hcsplit_t hcsplit = { .d32 = 0 };
|
||||||
|
int ret, transferred = 0;
|
||||||
|
|
||||||
|
hcsplit.hubaddr = split->hubaddr;
|
||||||
|
hcsplit.prtaddr = split->hubport;
|
||||||
|
hcsplit.spltena = 1;
|
||||||
|
writel(hcsplit.d32, ®->host.hchn[ch_num].hcspltn);
|
||||||
|
|
||||||
|
/* Wait for next frame boundary */
|
||||||
|
do {
|
||||||
|
hfnum.d32 = readl(®->host.hfnum);
|
||||||
|
} while (hfnum.frnum % 8 != 0);
|
||||||
|
|
||||||
|
/* Handle Start-Split */
|
||||||
|
ret = dwc2_do_xfer(ep, dir == EPDIR_IN ? 0 : size, pid, dir, ch_num,
|
||||||
|
data_buf);
|
||||||
|
if (ret < 0)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
hcsplit.spltena = 1;
|
||||||
|
hcsplit.compsplt = 1;
|
||||||
|
writel(hcsplit.d32, ®->host.hchn[ch_num].hcspltn);
|
||||||
|
ep->toggle = pid;
|
||||||
|
|
||||||
|
if (dir == EPDIR_OUT)
|
||||||
|
transferred += ret;
|
||||||
|
|
||||||
|
/* Handle Complete-Split */
|
||||||
|
do {
|
||||||
|
ret = dwc2_do_xfer(ep, dir == EPDIR_OUT ? 0 : size, ep->toggle,
|
||||||
|
dir, ch_num, data_buf);
|
||||||
|
} while (ret == -HCSTAT_NYET);
|
||||||
|
|
||||||
|
if (dir == EPDIR_IN)
|
||||||
|
transferred += ret;
|
||||||
|
|
||||||
|
out:
|
||||||
|
/* Clear hcsplit reg */
|
||||||
|
hcsplit.spltena = 0;
|
||||||
|
hcsplit.compsplt = 0;
|
||||||
|
writel(hcsplit.d32, ®->host.hchn[ch_num].hcspltn);
|
||||||
|
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
return transferred;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int dwc2_need_split(usbdev_t *dev, split_info_t *split)
|
||||||
|
{
|
||||||
|
if (dev->speed == HIGH_SPEED)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (closest_usb2_hub(dev, &split->hubaddr, &split->hubport))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
dwc2_transfer(endpoint_t *ep, int size, int pid, ep_dir_t dir, uint32_t ch_num,
|
||||||
|
u8 *src, uint8_t skip_nak)
|
||||||
|
{
|
||||||
|
split_info_t split;
|
||||||
|
int ret, transferred = 0, timeout = 3000;
|
||||||
|
|
||||||
|
ep->toggle = pid;
|
||||||
|
|
||||||
|
do {
|
||||||
|
if (dwc2_need_split(ep->dev, &split)) {
|
||||||
|
nak_retry:
|
||||||
|
ret = dwc2_split_transfer(ep, size, ep->toggle, dir, 0,
|
||||||
|
src, &split);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* dwc2_split_transfer() waits for the next FullSpeed
|
||||||
|
* frame boundary, so we have one try per millisecond.
|
||||||
|
* It's 3s timeout for each split transfer.
|
||||||
|
*/
|
||||||
|
if (ret == -HCSTAT_NAK && !skip_nak && --timeout) {
|
||||||
|
udelay(500);
|
||||||
|
goto nak_retry;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ret = dwc2_do_xfer(ep, size, pid, dir, 0, src);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
size -= ret;
|
||||||
|
src += ret;
|
||||||
|
transferred += ret;
|
||||||
|
} while (size > 0);
|
||||||
|
|
||||||
|
return transferred;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
dwc2_bulk(endpoint_t *ep, int size, u8 *src, int finalize)
|
dwc2_bulk(endpoint_t *ep, int size, u8 *src, int finalize)
|
||||||
{
|
{
|
||||||
|
@ -296,7 +406,7 @@ dwc2_bulk(endpoint_t *ep, int size, u8 *src, int finalize)
|
||||||
else
|
else
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
return dwc2_transfer(ep, size, ep->toggle, data_dir, 0, src);
|
return dwc2_transfer(ep, size, ep->toggle, data_dir, 0, src, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
|
@ -304,8 +414,8 @@ dwc2_control(usbdev_t *dev, direction_t dir, int drlen, void *setup,
|
||||||
int dalen, u8 *src)
|
int dalen, u8 *src)
|
||||||
{
|
{
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
ep_dir_t data_dir;
|
ep_dir_t data_dir;
|
||||||
|
endpoint_t *ep = &dev->endpoints[0];
|
||||||
|
|
||||||
if (dir == IN)
|
if (dir == IN)
|
||||||
data_dir = EPDIR_IN;
|
data_dir = EPDIR_IN;
|
||||||
|
@ -315,19 +425,19 @@ dwc2_control(usbdev_t *dev, direction_t dir, int drlen, void *setup,
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
/* Setup Phase */
|
/* Setup Phase */
|
||||||
if (dwc2_transfer(&dev->endpoints[0], drlen, PID_SETUP, EPDIR_OUT, 0,
|
if (dwc2_transfer(ep, drlen, PID_SETUP, EPDIR_OUT, 0, setup, 0) < 0)
|
||||||
setup) < 0)
|
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
/* Data Phase */
|
/* Data Phase */
|
||||||
|
ep->toggle = PID_DATA1;
|
||||||
if (dalen > 0) {
|
if (dalen > 0) {
|
||||||
ret = dwc2_transfer(&dev->endpoints[0], dalen, PID_DATA1,
|
ret = dwc2_transfer(ep, dalen, ep->toggle, data_dir, 0, src, 0);
|
||||||
data_dir, 0, src);
|
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Status Phase */
|
/* Status Phase */
|
||||||
if (dwc2_transfer(&dev->endpoints[0], 0, PID_DATA1, !data_dir, 0,
|
if (dwc2_transfer(ep, 0, PID_DATA1, !data_dir, 0, NULL, 0) < 0)
|
||||||
NULL) < 0)
|
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -345,7 +455,7 @@ dwc2_intr(endpoint_t *ep, int size, u8 *src)
|
||||||
else
|
else
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
return dwc2_transfer(ep, size, ep->toggle, data_dir, 0, src);
|
return dwc2_transfer(ep, size, ep->toggle, data_dir, 0, src, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
static u32 dwc2_intr_get_timestamp(intr_queue_t *q)
|
static u32 dwc2_intr_get_timestamp(intr_queue_t *q)
|
||||||
|
|
|
@ -36,6 +36,11 @@ typedef struct {
|
||||||
u32 timestamp;
|
u32 timestamp;
|
||||||
} intr_queue_t;
|
} intr_queue_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int hubaddr;
|
||||||
|
int hubport;
|
||||||
|
} split_info_t;
|
||||||
|
|
||||||
#define DWC2_INST(controller) ((dwc_ctrl_t *)((controller)->instance))
|
#define DWC2_INST(controller) ((dwc_ctrl_t *)((controller)->instance))
|
||||||
#define DWC2_REG(controller) ((dwc2_reg_t *)((controller)->reg_base))
|
#define DWC2_REG(controller) ((dwc2_reg_t *)((controller)->reg_base))
|
||||||
|
|
||||||
|
@ -44,6 +49,9 @@ typedef enum {
|
||||||
HCSTAT_XFERERR,
|
HCSTAT_XFERERR,
|
||||||
HCSTAT_BABBLE,
|
HCSTAT_BABBLE,
|
||||||
HCSTAT_STALL,
|
HCSTAT_STALL,
|
||||||
|
HCSTAT_ACK,
|
||||||
|
HCSTAT_NAK,
|
||||||
|
HCSTAT_NYET,
|
||||||
HCSTAT_UNKNOW,
|
HCSTAT_UNKNOW,
|
||||||
HCSTAT_TIMEOUT,
|
HCSTAT_TIMEOUT,
|
||||||
} hcstat_t;
|
} hcstat_t;
|
||||||
|
|
|
@ -194,30 +194,6 @@ static void ehci_shutdown (hci_t *controller)
|
||||||
|
|
||||||
enum { EHCI_OUT=0, EHCI_IN=1, EHCI_SETUP=2 };
|
enum { EHCI_OUT=0, EHCI_IN=1, EHCI_SETUP=2 };
|
||||||
|
|
||||||
/*
|
|
||||||
* returns the address of the closest USB2.0 hub, which is responsible for
|
|
||||||
* split transactions, along with the number of the used downstream port
|
|
||||||
*/
|
|
||||||
static int closest_usb2_hub(const usbdev_t *dev, int *const addr, int *const port)
|
|
||||||
{
|
|
||||||
const usbdev_t *usb1dev;
|
|
||||||
do {
|
|
||||||
usb1dev = dev;
|
|
||||||
if ((dev->hub >= 0) && (dev->hub < 128))
|
|
||||||
dev = dev->controller->devices[dev->hub];
|
|
||||||
else
|
|
||||||
dev = NULL;
|
|
||||||
} while (dev && (dev->speed < 2));
|
|
||||||
if (dev) {
|
|
||||||
*addr = usb1dev->hub;
|
|
||||||
*port = usb1dev->port;
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
usb_debug("ehci: Couldn't find closest USB2.0 hub.\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* returns handled bytes. assumes that the fields it writes are empty on entry */
|
/* returns handled bytes. assumes that the fields it writes are empty on entry */
|
||||||
static int fill_td(qtd_t *td, void* data, int datalen)
|
static int fill_td(qtd_t *td, void* data, int datalen)
|
||||||
{
|
{
|
||||||
|
|
|
@ -653,3 +653,29 @@ usb_generic_init (usbdev_t *dev)
|
||||||
usb_detach_device(dev->controller, dev->address);
|
usb_detach_device(dev->controller, dev->address);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* returns the address of the closest USB2.0 hub, which is responsible for
|
||||||
|
* split transactions, along with the number of the used downstream port
|
||||||
|
*/
|
||||||
|
int closest_usb2_hub(const usbdev_t *dev, int *const addr, int *const port)
|
||||||
|
{
|
||||||
|
const usbdev_t *usb1dev;
|
||||||
|
|
||||||
|
do {
|
||||||
|
usb1dev = dev;
|
||||||
|
if ((dev->hub >= 0) && (dev->hub < 128))
|
||||||
|
dev = dev->controller->devices[dev->hub];
|
||||||
|
else
|
||||||
|
dev = NULL;
|
||||||
|
} while (dev && (dev->speed < 2));
|
||||||
|
|
||||||
|
if (dev) {
|
||||||
|
*addr = usb1dev->hub;
|
||||||
|
*port = usb1dev->port;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
usb_debug("Couldn't find closest USB2.0 hub.\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
|
@ -597,6 +597,30 @@ typedef union {
|
||||||
};
|
};
|
||||||
} hcchar_t;
|
} hcchar_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This union represents the bit fields in the Host Channel-n Split Control
|
||||||
|
* Register.
|
||||||
|
*/
|
||||||
|
typedef union {
|
||||||
|
/* raw register data */
|
||||||
|
uint32_t d32;
|
||||||
|
|
||||||
|
/* register bits */
|
||||||
|
struct {
|
||||||
|
/** Port Address */
|
||||||
|
unsigned prtaddr:7;
|
||||||
|
/** Hub Address */
|
||||||
|
unsigned hubaddr:7;
|
||||||
|
/** Transaction Position */
|
||||||
|
unsigned xactpos:2;
|
||||||
|
/** Do Complete Split */
|
||||||
|
unsigned compsplt:1;
|
||||||
|
unsigned reserved:14;
|
||||||
|
/** Split Enable */
|
||||||
|
unsigned spltena:1;
|
||||||
|
};
|
||||||
|
} hcsplit_t;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
EPDIR_OUT = 0,
|
EPDIR_OUT = 0,
|
||||||
EPDIR_IN,
|
EPDIR_IN,
|
||||||
|
|
|
@ -277,6 +277,8 @@ void usb_hid_init (usbdev_t *dev);
|
||||||
void usb_msc_init (usbdev_t *dev);
|
void usb_msc_init (usbdev_t *dev);
|
||||||
void usb_generic_init (usbdev_t *dev);
|
void usb_generic_init (usbdev_t *dev);
|
||||||
|
|
||||||
|
int closest_usb2_hub(const usbdev_t *dev, int *const addr, int *const port);
|
||||||
|
|
||||||
static inline unsigned char
|
static inline unsigned char
|
||||||
gen_bmRequestType (dev_req_dir dir, dev_req_type type, dev_req_recp recp)
|
gen_bmRequestType (dev_req_dir dir, dev_req_type type, dev_req_recp recp)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue