usb: ehci-hcd: port periodic transactions implementation from the u-boot
Signed-off-by: Peter Mamonov <pmamonov@gmail.com> Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
This commit is contained in:
parent
8165f3131c
commit
4350744bf5
|
@ -48,6 +48,19 @@ struct ehci_priv {
|
|||
int (*init)(void *drvdata);
|
||||
int (*post_init)(void *drvdata);
|
||||
void *drvdata;
|
||||
int periodic_schedules;
|
||||
struct QH *periodic_queue;
|
||||
uint32_t *periodic_list;
|
||||
};
|
||||
|
||||
struct int_queue {
|
||||
int elementsize;
|
||||
int queuesize;
|
||||
unsigned long pipe;
|
||||
struct QH *first;
|
||||
struct QH *current;
|
||||
struct QH *last;
|
||||
struct qTD *tds;
|
||||
};
|
||||
|
||||
#define to_ehci(ptr) container_of(ptr, struct ehci_priv, host)
|
||||
|
@ -768,6 +781,8 @@ static int ehci_init(struct usb_host *host)
|
|||
uint32_t reg;
|
||||
uint32_t cmd;
|
||||
int ret = 0;
|
||||
struct QH *periodic;
|
||||
int i;
|
||||
|
||||
ehci_halt(ehci);
|
||||
|
||||
|
@ -793,6 +808,44 @@ static int ehci_init(struct usb_host *host)
|
|||
/* Set async. queue head pointer. */
|
||||
ehci_writel(&ehci->hcor->or_asynclistaddr, (uint32_t)ehci->qh_list);
|
||||
|
||||
/*
|
||||
* Set up periodic list
|
||||
* Step 1: Parent QH for all periodic transfers.
|
||||
*/
|
||||
ehci->periodic_schedules = 0;
|
||||
periodic = ehci->periodic_queue;
|
||||
memset(periodic, 0, sizeof(*periodic));
|
||||
periodic->qh_link = cpu_to_hc32(QH_LINK_TERMINATE);
|
||||
periodic->qt_next = cpu_to_hc32(QT_NEXT_TERMINATE);
|
||||
periodic->qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE);
|
||||
|
||||
/*
|
||||
* Step 2: Setup frame-list: Every microframe, USB tries the same list.
|
||||
* In particular, device specifications on polling frequency
|
||||
* are disregarded. Keyboards seem to send NAK/NYet reliably
|
||||
* when polled with an empty buffer.
|
||||
*
|
||||
* Split Transactions will be spread across microframes using
|
||||
* S-mask and C-mask.
|
||||
*/
|
||||
if (ehci->periodic_list == NULL)
|
||||
/*
|
||||
* FIXME: this memory chunk have to be 4k aligned AND
|
||||
* reside in coherent memory. Current implementation of
|
||||
* dma_alloc_coherent() allocates PAGE_SIZE aligned memory chunks.
|
||||
* PAGE_SIZE less then 4k will break this code.
|
||||
*/
|
||||
ehci->periodic_list = dma_alloc_coherent(1024 * 4,
|
||||
DMA_ADDRESS_BROKEN);
|
||||
for (i = 0; i < 1024; i++) {
|
||||
ehci->periodic_list[i] = cpu_to_hc32((unsigned long)periodic
|
||||
| QH_LINK_TYPE_QH);
|
||||
}
|
||||
|
||||
/* Set periodic list base address */
|
||||
ehci_writel(&ehci->hcor->or_periodiclistbase,
|
||||
(unsigned long)ehci->periodic_list);
|
||||
|
||||
reg = ehci_readl(&ehci->hccr->cr_hcsparams);
|
||||
descriptor.hub.bNbrPorts = HCS_N_PORTS(reg);
|
||||
|
||||
|
@ -863,16 +916,361 @@ submit_control_msg(struct usb_device *dev, unsigned long pipe, void *buffer,
|
|||
return ehci_submit_async(dev, pipe, buffer, length, setup);
|
||||
}
|
||||
|
||||
static int
|
||||
disable_periodic(struct ehci_priv *ehci)
|
||||
{
|
||||
uint32_t cmd;
|
||||
struct ehci_hcor *hcor = ehci->hcor;
|
||||
int ret;
|
||||
|
||||
cmd = ehci_readl(&hcor->or_usbcmd);
|
||||
cmd &= ~CMD_PSE;
|
||||
ehci_writel(&hcor->or_usbcmd, cmd);
|
||||
|
||||
ret = handshake((uint32_t *)&hcor->or_usbsts,
|
||||
STS_PSS, 0, 100 * 1000);
|
||||
if (ret < 0) {
|
||||
printf("EHCI failed: timeout when disabling periodic list\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define NEXT_QH(qh) (struct QH *)((unsigned long)hc32_to_cpu((qh)->qh_link) & ~0x1f)
|
||||
|
||||
static int
|
||||
enable_periodic(struct ehci_priv *ehci)
|
||||
{
|
||||
uint32_t cmd;
|
||||
struct ehci_hcor *hcor = ehci->hcor;
|
||||
int ret;
|
||||
|
||||
cmd = ehci_readl(&hcor->or_usbcmd);
|
||||
cmd |= CMD_PSE;
|
||||
ehci_writel(&hcor->or_usbcmd, cmd);
|
||||
ret = handshake((uint32_t *)&hcor->or_usbsts,
|
||||
STS_PSS, STS_PSS, 100 * 1000);
|
||||
if (ret < 0) {
|
||||
printf("EHCI failed: timeout when enabling periodic list\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
mdelay_non_interruptible(1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline u8 ehci_encode_speed(enum usb_device_speed speed)
|
||||
{
|
||||
#define QH_HIGH_SPEED 2
|
||||
#define QH_FULL_SPEED 0
|
||||
#define QH_LOW_SPEED 1
|
||||
if (speed == USB_SPEED_HIGH)
|
||||
return QH_HIGH_SPEED;
|
||||
if (speed == USB_SPEED_LOW)
|
||||
return QH_LOW_SPEED;
|
||||
return QH_FULL_SPEED;
|
||||
}
|
||||
|
||||
static void ehci_update_endpt2_dev_n_port(struct usb_device *udev,
|
||||
struct QH *qh)
|
||||
{
|
||||
struct usb_device *ttdev;
|
||||
int parent_devnum;
|
||||
|
||||
if (udev->speed != USB_SPEED_LOW && udev->speed != USB_SPEED_FULL)
|
||||
return;
|
||||
|
||||
/*
|
||||
* For full / low speed devices we need to get the devnum and portnr of
|
||||
* the tt, so of the first upstream usb-2 hub, there may be usb-1 hubs
|
||||
* in the tree before that one!
|
||||
*/
|
||||
|
||||
ttdev = udev;
|
||||
while (ttdev->parent && ttdev->parent->speed != USB_SPEED_HIGH)
|
||||
ttdev = ttdev->parent;
|
||||
if (!ttdev->parent)
|
||||
return;
|
||||
parent_devnum = ttdev->parent->devnum;
|
||||
|
||||
qh->qh_endpt2 |= cpu_to_hc32(QH_ENDPT2_PORTNUM(ttdev->portnr) |
|
||||
QH_ENDPT2_HUBADDR(parent_devnum));
|
||||
}
|
||||
|
||||
static struct int_queue *ehci_create_int_queue(struct usb_device *dev,
|
||||
unsigned long pipe, int queuesize, int elementsize,
|
||||
void *buffer, int interval)
|
||||
{
|
||||
struct usb_host *host = dev->host;
|
||||
struct ehci_priv *ehci = to_ehci(host);
|
||||
struct int_queue *result = NULL;
|
||||
uint32_t i, toggle;
|
||||
struct QH *list = ehci->periodic_queue;
|
||||
|
||||
/*
|
||||
* Interrupt transfers requiring several transactions are not supported
|
||||
* because bInterval is ignored.
|
||||
*
|
||||
* Also, ehci_submit_async() relies on wMaxPacketSize being a power of 2
|
||||
* <= PKT_ALIGN if several qTDs are required, while the USB
|
||||
* specification does not constrain this for interrupt transfers. That
|
||||
* means that ehci_submit_async() would support interrupt transfers
|
||||
* requiring several transactions only as long as the transfer size does
|
||||
* not require more than a single qTD.
|
||||
*/
|
||||
if (elementsize > usb_maxpacket(dev, pipe)) {
|
||||
dev_err(&dev->dev,
|
||||
"%s: xfers requiring several transactions are not supported.\n",
|
||||
__func__);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
debug("Enter create_int_queue\n");
|
||||
if (usb_pipetype(pipe) != PIPE_INTERRUPT) {
|
||||
dev_dbg(&dev->dev,
|
||||
"non-interrupt pipe (type=%lu)",
|
||||
usb_pipetype(pipe));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* limit to 4 full pages worth of data -
|
||||
* we can safely fit them in a single TD,
|
||||
* no matter the alignment
|
||||
*/
|
||||
if (elementsize >= 16384) {
|
||||
dev_dbg(&dev->dev,
|
||||
"too large elements for interrupt transfers\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
result = xzalloc(sizeof(*result));
|
||||
result->elementsize = elementsize;
|
||||
result->queuesize = queuesize;
|
||||
result->pipe = pipe;
|
||||
result->first = dma_alloc_coherent(sizeof(struct QH) * queuesize,
|
||||
DMA_ADDRESS_BROKEN);
|
||||
result->current = result->first;
|
||||
result->last = result->first + queuesize - 1;
|
||||
result->tds = dma_alloc_coherent(sizeof(struct qTD) * queuesize,
|
||||
DMA_ADDRESS_BROKEN);
|
||||
memset(result->first, 0, sizeof(struct QH) * queuesize);
|
||||
memset(result->tds, 0, sizeof(struct qTD) * queuesize);
|
||||
|
||||
toggle = usb_gettoggle(dev, usb_pipeendpoint(pipe), usb_pipeout(pipe));
|
||||
|
||||
for (i = 0; i < queuesize; i++) {
|
||||
struct QH *qh = result->first + i;
|
||||
struct qTD *td = result->tds + i;
|
||||
void **buf = &qh->buffer;
|
||||
|
||||
qh->qh_link = cpu_to_hc32((unsigned long)(qh+1) | QH_LINK_TYPE_QH);
|
||||
if (i == queuesize - 1)
|
||||
qh->qh_link = cpu_to_hc32(QH_LINK_TERMINATE);
|
||||
|
||||
qh->qt_next = cpu_to_hc32((unsigned long)td);
|
||||
qh->qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE);
|
||||
qh->qh_endpt1 =
|
||||
cpu_to_hc32((0 << 28) | /* No NAK reload (ehci 4.9) */
|
||||
(usb_maxpacket(dev, pipe) << 16) | /* MPS */
|
||||
(1 << 14) |
|
||||
QH_ENDPT1_EPS(ehci_encode_speed(dev->speed)) |
|
||||
(usb_pipeendpoint(pipe) << 8) | /* Endpoint Number */
|
||||
(usb_pipedevice(pipe) << 0));
|
||||
qh->qh_endpt2 = cpu_to_hc32((1 << 30) | /* 1 Tx per mframe */
|
||||
(1 << 0)); /* S-mask: microframe 0 */
|
||||
if (dev->speed == USB_SPEED_LOW ||
|
||||
dev->speed == USB_SPEED_FULL) {
|
||||
/* C-mask: microframes 2-4 */
|
||||
qh->qh_endpt2 |= cpu_to_hc32((0x1c << 8));
|
||||
}
|
||||
ehci_update_endpt2_dev_n_port(dev, qh);
|
||||
|
||||
td->qt_next = cpu_to_hc32(QT_NEXT_TERMINATE);
|
||||
td->qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE);
|
||||
dev_dbg(&dev->dev,
|
||||
"communication direction is '%s'\n",
|
||||
usb_pipein(pipe) ? "in" : "out");
|
||||
td->qt_token = cpu_to_hc32(
|
||||
QT_TOKEN_DT(toggle) |
|
||||
(elementsize << 16) |
|
||||
((usb_pipein(pipe) ? 1 : 0) << 8) | /* IN/OUT token */
|
||||
0x80); /* active */
|
||||
td->qt_buffer[0] =
|
||||
cpu_to_hc32((unsigned long)buffer + i * elementsize);
|
||||
td->qt_buffer[1] =
|
||||
cpu_to_hc32((td->qt_buffer[0] + 0x1000) & ~0xfff);
|
||||
td->qt_buffer[2] =
|
||||
cpu_to_hc32((td->qt_buffer[0] + 0x2000) & ~0xfff);
|
||||
td->qt_buffer[3] =
|
||||
cpu_to_hc32((td->qt_buffer[0] + 0x3000) & ~0xfff);
|
||||
td->qt_buffer[4] =
|
||||
cpu_to_hc32((td->qt_buffer[0] + 0x4000) & ~0xfff);
|
||||
|
||||
*buf = buffer + i * elementsize;
|
||||
toggle ^= 1;
|
||||
}
|
||||
|
||||
if (ehci->periodic_schedules > 0) {
|
||||
if (disable_periodic(ehci) < 0) {
|
||||
dev_err(&dev->dev,
|
||||
"FATAL: periodic should never fail, but did");
|
||||
goto fail3;
|
||||
}
|
||||
}
|
||||
|
||||
/* hook up to periodic list */
|
||||
result->last->qh_link = list->qh_link;
|
||||
list->qh_link = cpu_to_hc32((unsigned long)result->first | QH_LINK_TYPE_QH);
|
||||
|
||||
if (enable_periodic(ehci) < 0) {
|
||||
dev_err(&dev->dev,
|
||||
"FATAL: periodic should never fail, but did");
|
||||
goto fail3;
|
||||
}
|
||||
ehci->periodic_schedules++;
|
||||
|
||||
dev_dbg(&dev->dev, "Exit create_int_queue\n");
|
||||
return result;
|
||||
fail3:
|
||||
dma_free_coherent(result->tds, 0, sizeof(struct qTD) * queuesize);
|
||||
dma_free_coherent(result->first, 0, sizeof(struct QH) * queuesize);
|
||||
free(result);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *ehci_poll_int_queue(struct usb_device *dev,
|
||||
struct int_queue *queue)
|
||||
{
|
||||
struct QH *cur = queue->current;
|
||||
struct qTD *cur_td;
|
||||
uint32_t token, toggle;
|
||||
unsigned long pipe = queue->pipe;
|
||||
|
||||
/* depleted queue */
|
||||
if (cur == NULL) {
|
||||
dev_dbg(&dev->dev, "Exit poll_int_queue with completed queue\n");
|
||||
return NULL;
|
||||
}
|
||||
/* still active */
|
||||
cur_td = &queue->tds[queue->current - queue->first];
|
||||
token = hc32_to_cpu(cur_td->qt_token);
|
||||
if (QT_TOKEN_GET_STATUS(token) & QT_TOKEN_STATUS_ACTIVE) {
|
||||
dev_dbg(&dev->dev,
|
||||
"Exit poll_int_queue with no completed intr transfer. token is %x\n",
|
||||
token);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
toggle = QT_TOKEN_GET_DT(token);
|
||||
usb_settoggle(dev, usb_pipeendpoint(pipe), usb_pipeout(pipe), toggle);
|
||||
|
||||
if (!(cur->qh_link & QH_LINK_TERMINATE))
|
||||
queue->current++;
|
||||
else
|
||||
queue->current = NULL;
|
||||
|
||||
dev_dbg(&dev->dev,
|
||||
"Exit poll_int_queue with completed intr transfer. token is %x at %p (first at %p)\n",
|
||||
token, cur, queue->first);
|
||||
return cur->buffer;
|
||||
}
|
||||
|
||||
static int ehci_destroy_int_queue(struct usb_device *dev,
|
||||
struct int_queue *queue)
|
||||
{
|
||||
int result = -EINVAL;
|
||||
struct usb_host *host = dev->host;
|
||||
struct ehci_priv *ehci = to_ehci(host);
|
||||
struct QH *cur = ehci->periodic_queue;
|
||||
uint64_t start;
|
||||
|
||||
if (disable_periodic(ehci) < 0) {
|
||||
dev_err(&dev->dev,
|
||||
"FATAL: periodic should never fail, but did\n");
|
||||
goto out;
|
||||
}
|
||||
ehci->periodic_schedules--;
|
||||
|
||||
start = get_time_ns();
|
||||
while (!(cur->qh_link & cpu_to_hc32(QH_LINK_TERMINATE))) {
|
||||
dev_dbg(&dev->dev,
|
||||
"considering %p, with qh_link %x\n",
|
||||
cur, cur->qh_link);
|
||||
if (NEXT_QH(cur) == queue->first) {
|
||||
dev_dbg(&dev->dev,
|
||||
"found candidate. removing from chain\n");
|
||||
cur->qh_link = queue->last->qh_link;
|
||||
result = 0;
|
||||
break;
|
||||
}
|
||||
cur = NEXT_QH(cur);
|
||||
if (is_timeout_non_interruptible(start, 500 * MSECOND)) {
|
||||
dev_err(&dev->dev,
|
||||
"Timeout destroying interrupt endpoint queue\n");
|
||||
result = -ETIMEDOUT;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if (ehci->periodic_schedules > 0) {
|
||||
result = enable_periodic(ehci);
|
||||
if (result < 0)
|
||||
dev_err(&dev->dev,
|
||||
"FATAL: periodic should never fail, but did");
|
||||
}
|
||||
|
||||
out:
|
||||
dma_free_coherent(queue->tds, 0, sizeof(struct qTD) * queue->queuesize);
|
||||
dma_free_coherent(queue->first, 0, sizeof(struct QH) * queue->queuesize);
|
||||
free(queue);
|
||||
return result;
|
||||
}
|
||||
|
||||
static int
|
||||
submit_int_msg(struct usb_device *dev, unsigned long pipe, void *buffer,
|
||||
int length, int interval)
|
||||
{
|
||||
struct usb_host *host = dev->host;
|
||||
struct ehci_priv *ehci = to_ehci(host);
|
||||
struct int_queue *queue;
|
||||
uint64_t start;
|
||||
void *backbuffer;
|
||||
int result = 0, ret;
|
||||
|
||||
dev_dbg(ehci->dev, "dev=%p, pipe=%lu, buffer=%p, length=%d, interval=%d",
|
||||
dev, pipe, buffer, length, interval);
|
||||
return -1;
|
||||
|
||||
dma_sync_single_for_device((unsigned long)buffer, length,
|
||||
DMA_BIDIRECTIONAL);
|
||||
|
||||
queue = ehci_create_int_queue(dev, pipe, 1, length, buffer, interval);
|
||||
if (!queue)
|
||||
return -EINVAL;
|
||||
|
||||
start = get_time_ns();
|
||||
while ((backbuffer = ehci_poll_int_queue(dev, queue)) == NULL)
|
||||
if (is_timeout_non_interruptible(start,
|
||||
USB_CNTL_TIMEOUT * MSECOND)) {
|
||||
dev_err(&dev->dev,
|
||||
"Timeout poll on interrupt endpoint\n");
|
||||
result = -ETIMEDOUT;
|
||||
break;
|
||||
}
|
||||
|
||||
if (backbuffer != buffer) {
|
||||
dev_err(&dev->dev,
|
||||
"got wrong buffer back (%p instead of %p)\n",
|
||||
backbuffer, buffer);
|
||||
if (!result)
|
||||
result = -EINVAL;
|
||||
}
|
||||
|
||||
dma_sync_single_for_cpu((unsigned long)buffer, length,
|
||||
DMA_BIDIRECTIONAL);
|
||||
|
||||
ret = ehci_destroy_int_queue(dev, queue);
|
||||
if (!result)
|
||||
result = ret;
|
||||
return result;
|
||||
}
|
||||
|
||||
static int ehci_detect(struct device_d *dev)
|
||||
|
@ -907,6 +1305,8 @@ int ehci_register(struct device_d *dev, struct ehci_data *data)
|
|||
|
||||
ehci->qh_list = dma_alloc_coherent(sizeof(struct QH) * NUM_TD,
|
||||
DMA_ADDRESS_BROKEN);
|
||||
ehci->periodic_queue = dma_alloc_coherent(sizeof(struct QH),
|
||||
DMA_ADDRESS_BROKEN);
|
||||
ehci->td = dma_alloc_coherent(sizeof(struct qTD) * NUM_TD,
|
||||
DMA_ADDRESS_BROKEN);
|
||||
|
||||
|
|
|
@ -20,6 +20,15 @@
|
|||
|
||||
#include <io.h>
|
||||
|
||||
#define QH_ENDPT1_EPS(x) (((x) & 0x3) << 12) /* Endpoint Speed */
|
||||
#define QH_ENDPT2_PORTNUM(x) (((x) & 0x7f) << 23) /* Port Number */
|
||||
#define QH_ENDPT2_HUBADDR(x) (((x) & 0x7f) << 16) /* Hub Address */
|
||||
|
||||
#define QT_TOKEN_DT(x) (((x) & 0x1) << 31) /* Data Toggle */
|
||||
#define QT_TOKEN_GET_STATUS(x) (((x) >> 0) & 0xff)
|
||||
#define QT_TOKEN_STATUS_ACTIVE 0x80
|
||||
#define QT_TOKEN_GET_DT(x) (((x) >> 31) & 0x1)
|
||||
|
||||
#if !defined(CONFIG_SYS_USB_EHCI_MAX_ROOT_PORTS)
|
||||
#define CONFIG_SYS_USB_EHCI_MAX_ROOT_PORTS 16
|
||||
#endif
|
||||
|
@ -51,6 +60,7 @@ struct ehci_hcor {
|
|||
#define CMD_RUN (1 << 0) /* start/stop HC */
|
||||
uint32_t or_usbsts;
|
||||
#define STD_ASS (1 << 15)
|
||||
#define STS_PSS (1 << 14)
|
||||
#define STS_HALT (1 << 12)
|
||||
uint32_t or_usbintr;
|
||||
uint32_t or_frindex;
|
||||
|
@ -148,7 +158,10 @@ struct QH {
|
|||
* Add dummy fill value to make the size of this struct
|
||||
* aligned to 32 bytes
|
||||
*/
|
||||
uint8_t fill[16];
|
||||
union {
|
||||
uint8_t fill[16];
|
||||
void* buffer;
|
||||
};
|
||||
};
|
||||
|
||||
#endif /* USB_EHCI_H */
|
||||
|
|
Loading…
Reference in New Issue