9
0
Fork 0

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:
Peter Mamonov 2015-09-24 19:20:25 +03:00 committed by Sascha Hauer
parent 8165f3131c
commit 4350744bf5
2 changed files with 415 additions and 2 deletions

View File

@ -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);

View File

@ -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 */