diff options
Diffstat (limited to 'drivers/usb/host/ehci-hcd.c')
| -rw-r--r-- | drivers/usb/host/ehci-hcd.c | 318 | 
1 files changed, 314 insertions, 4 deletions
| diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 7f98a6354..c81687820 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -21,12 +21,14 @@   * MA 02111-1307 USA   */  #include <common.h> +#include <errno.h>  #include <asm/byteorder.h>  #include <asm/unaligned.h>  #include <usb.h>  #include <asm/io.h>  #include <malloc.h>  #include <watchdog.h> +#include <linux/compiler.h>  #include "ehci.h" @@ -39,7 +41,10 @@ static struct ehci_ctrl {  	struct ehci_hcor *hcor;  	int rootdev;  	uint16_t portreset; -	struct QH qh_list __attribute__((aligned(USB_DMA_MINALIGN))); +	struct QH qh_list __aligned(USB_DMA_MINALIGN); +	struct QH periodic_queue __aligned(USB_DMA_MINALIGN); +	uint32_t *periodic_list; +	int ntds;  } ehcic[CONFIG_USB_MAX_CONTROLLER_COUNT];  #define ALIGN_END_ADDR(type, ptr, size)			\ @@ -858,6 +863,8 @@ int usb_lowlevel_init(int index, void **controller)  	uint32_t reg;  	uint32_t cmd;  	struct QH *qh_list; +	struct QH *periodic; +	int i;  	if (ehci_hcd_init(index, &ehcic[index].hccr, &ehcic[index].hcor))  		return -1; @@ -870,6 +877,9 @@ int usb_lowlevel_init(int index, void **controller)  	if (ehci_hcd_init(index, &ehcic[index].hccr, &ehcic[index].hcor))  		return -1;  #endif +	/* Set the high address word (aka segment) for 64-bit controller */ +	if (ehci_readl(&ehcic[index].hccr->cr_hccparams) & 1) +		ehci_writel(ehcic[index].hcor->or_ctrldssegment, 0);  	qh_list = &ehcic[index].qh_list; @@ -884,6 +894,40 @@ int usb_lowlevel_init(int index, void **controller)  	qh_list->qh_overlay.qt_token =  			cpu_to_hc32(QT_TOKEN_STATUS(QT_TOKEN_STATUS_HALTED)); +	/* Set async. queue head pointer. */ +	ehci_writel(&ehcic[index].hcor->or_asynclistaddr, (uint32_t)qh_list); + +	/* +	 * Set up periodic list +	 * Step 1: Parent QH for all periodic transfers. +	 */ +	periodic = &ehcic[index].periodic_queue; +	memset(periodic, 0, sizeof(*periodic)); +	periodic->qh_link = cpu_to_hc32(QH_LINK_TERMINATE); +	periodic->qh_overlay.qt_next = cpu_to_hc32(QT_NEXT_TERMINATE); +	periodic->qh_overlay.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. +	 */ +	ehcic[index].periodic_list = memalign(4096, 1024*4); +	if (!ehcic[index].periodic_list) +		return -ENOMEM; +	for (i = 0; i < 1024; i++) { +		ehcic[index].periodic_list[i] = (uint32_t)periodic +						| QH_LINK_TYPE_QH; +	} + +	/* Set periodic list base address */ +	ehci_writel(&ehcic[index].hcor->or_periodiclistbase, +		(uint32_t)ehcic[index].periodic_list); +  	reg = ehci_readl(&ehcic[index].hccr->cr_hcsparams);  	descriptor.hub.bNbrPorts = HCS_N_PORTS(reg);  	debug("Register %x NbrPorts %d\n", reg, descriptor.hub.bNbrPorts); @@ -953,10 +997,254 @@ submit_control_msg(struct usb_device *dev, unsigned long pipe, void *buffer,  	return ehci_submit_async(dev, pipe, buffer, length, setup);  } +struct int_queue { +	struct QH *first; +	struct QH *current; +	struct QH *last; +	struct qTD *tds; +}; + +#define NEXT_QH(qh) (struct QH *)((qh)->qh_link & ~0x1f) + +static int +enable_periodic(struct ehci_ctrl *ctrl) +{ +	uint32_t cmd; +	struct ehci_hcor *hcor = ctrl->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; +	} +	udelay(1000); +	return 0; +} + +static int +disable_periodic(struct ehci_ctrl *ctrl) +{ +	uint32_t cmd; +	struct ehci_hcor *hcor = ctrl->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; +} + +static int periodic_schedules; + +struct int_queue * +create_int_queue(struct usb_device *dev, unsigned long pipe, int queuesize, +		 int elementsize, void *buffer) +{ +	struct ehci_ctrl *ctrl = dev->controller; +	struct int_queue *result = NULL; +	int i; + +	debug("Enter create_int_queue\n"); +	if (usb_pipetype(pipe) != PIPE_INTERRUPT) { +		debug("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) { +		debug("too large elements for interrupt transfers\n"); +		return NULL; +	} + +	result = malloc(sizeof(*result)); +	if (!result) { +		debug("ehci intr queue: out of memory\n"); +		goto fail1; +	} +	result->first = memalign(32, sizeof(struct QH) * queuesize); +	if (!result->first) { +		debug("ehci intr queue: out of memory\n"); +		goto fail2; +	} +	result->current = result->first; +	result->last = result->first + queuesize - 1; +	result->tds = memalign(32, sizeof(struct qTD) * queuesize); +	if (!result->tds) { +		debug("ehci intr queue: out of memory\n"); +		goto fail3; +	} +	memset(result->first, 0, sizeof(struct QH) * queuesize); +	memset(result->tds, 0, sizeof(struct qTD) * queuesize); + +	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 = (uint32_t)(qh+1) | QH_LINK_TYPE_QH; +		if (i == queuesize - 1) +			qh->qh_link = QH_LINK_TERMINATE; + +		qh->qh_overlay.qt_next = (uint32_t)td; +		qh->qh_endpt1 = (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 = (1 << 30) | /* 1 Tx per mframe */ +			(1 << 0); /* S-mask: microframe 0 */ +		if (dev->speed == USB_SPEED_LOW || +				dev->speed == USB_SPEED_FULL) { +			debug("TT: port: %d, hub address: %d\n", +				dev->portnr, dev->parent->devnum); +			qh->qh_endpt2 |= (dev->portnr << 23) | +				(dev->parent->devnum << 16) | +				(0x1c << 8); /* C-mask: microframes 2-4 */ +		} + +		td->qt_next = QT_NEXT_TERMINATE; +		td->qt_altnext = QT_NEXT_TERMINATE; +		debug("communication direction is '%s'\n", +		      usb_pipein(pipe) ? "in" : "out"); +		td->qt_token = (elementsize << 16) | +			((usb_pipein(pipe) ? 1 : 0) << 8) | /* IN/OUT token */ +			0x80; /* active */ +		td->qt_buffer[0] = (uint32_t)buffer + i * elementsize; +		td->qt_buffer[1] = (td->qt_buffer[0] + 0x1000) & ~0xfff; +		td->qt_buffer[2] = (td->qt_buffer[0] + 0x2000) & ~0xfff; +		td->qt_buffer[3] = (td->qt_buffer[0] + 0x3000) & ~0xfff; +		td->qt_buffer[4] = (td->qt_buffer[0] + 0x4000) & ~0xfff; + +		*buf = buffer + i * elementsize; +	} + +	if (disable_periodic(ctrl) < 0) { +		debug("FATAL: periodic should never fail, but did"); +		goto fail3; +	} + +	/* hook up to periodic list */ +	struct QH *list = &ctrl->periodic_queue; +	result->last->qh_link = list->qh_link; +	list->qh_link = (uint32_t)result->first | QH_LINK_TYPE_QH; + +	if (enable_periodic(ctrl) < 0) { +		debug("FATAL: periodic should never fail, but did"); +		goto fail3; +	} +	periodic_schedules++; + +	debug("Exit create_int_queue\n"); +	return result; +fail3: +	if (result->tds) +		free(result->tds); +fail2: +	if (result->first) +		free(result->first); +	if (result) +		free(result); +fail1: +	return NULL; +} + +void *poll_int_queue(struct usb_device *dev, struct int_queue *queue) +{ +	struct QH *cur = queue->current; + +	/* depleted queue */ +	if (cur == NULL) { +		debug("Exit poll_int_queue with completed queue\n"); +		return NULL; +	} +	/* still active */ +	if (cur->qh_overlay.qt_token & 0x80) { +		debug("Exit poll_int_queue with no completed intr transfer. " +		      "token is %x\n", cur->qh_overlay.qt_token); +		return NULL; +	} +	if (!(cur->qh_link & QH_LINK_TERMINATE)) +		queue->current++; +	else +		queue->current = NULL; +	debug("Exit poll_int_queue with completed intr transfer. " +	      "token is %x at %p (first at %p)\n", cur->qh_overlay.qt_token, +	      &cur->qh_overlay.qt_token, queue->first); +	return cur->buffer; +} + +/* Do not free buffers associated with QHs, they're owned by someone else */ +int +destroy_int_queue(struct usb_device *dev, struct int_queue *queue) +{ +	struct ehci_ctrl *ctrl = dev->controller; +	int result = -1; +	unsigned long timeout; + +	if (disable_periodic(ctrl) < 0) { +		debug("FATAL: periodic should never fail, but did"); +		goto out; +	} +	periodic_schedules--; + +	struct QH *cur = &ctrl->periodic_queue; +	timeout = get_timer(0) + 500; /* abort after 500ms */ +	while (!(cur->qh_link & QH_LINK_TERMINATE)) { +		debug("considering %p, with qh_link %x\n", cur, cur->qh_link); +		if (NEXT_QH(cur) == queue->first) { +			debug("found candidate. removing from chain\n"); +			cur->qh_link = queue->last->qh_link; +			result = 0; +			break; +		} +		cur = NEXT_QH(cur); +		if (get_timer(0) > timeout) { +			printf("Timeout destroying interrupt endpoint queue\n"); +			result = -1; +			goto out; +		} +	} + +	if (periodic_schedules > 0) { +		result = enable_periodic(ctrl); +		if (result < 0) +			debug("FATAL: periodic should never fail, but did"); +	} + +out: +	free(queue->tds); +	free(queue->first); +	free(queue); + +	return result; +} +  int  submit_int_msg(struct usb_device *dev, unsigned long pipe, void *buffer,  	       int length, int interval)  { +	void *backbuffer; +	struct int_queue *queue; +	unsigned long timeout; +	int result = 0, ret; +  	debug("dev=%p, pipe=%lu, buffer=%p, length=%d, interval=%d",  	      dev, pipe, buffer, length, interval); @@ -972,9 +1260,31 @@ submit_int_msg(struct usb_device *dev, unsigned long pipe, void *buffer,  	 * not require more than a single qTD.  	 */  	if (length > usb_maxpacket(dev, pipe)) { -		printf("%s: Interrupt transfers requiring several transactions " -			"are not supported.\n", __func__); +		printf("%s: Interrupt transfers requiring several " +			"transactions are not supported.\n", __func__);  		return -1;  	} -	return ehci_submit_async(dev, pipe, buffer, length, NULL); + +	queue = create_int_queue(dev, pipe, 1, length, buffer); + +	timeout = get_timer(0) + USB_TIMEOUT_MS(pipe); +	while ((backbuffer = poll_int_queue(dev, queue)) == NULL) +		if (get_timer(0) > timeout) { +			printf("Timeout poll on interrupt endpoint\n"); +			result = -ETIMEDOUT; +			break; +		} + +	if (backbuffer != buffer) { +		debug("got wrong buffer back (%x instead of %x)\n", +		      (uint32_t)backbuffer, (uint32_t)buffer); +		return -EINVAL; +	} + +	ret = destroy_int_queue(dev, queue); +	if (ret < 0) +		return ret; + +	/* everything worked out fine */ +	return result;  } |