diff options
Diffstat (limited to 'drivers/usb/host')
| -rw-r--r-- | drivers/usb/host/Makefile | 59 | ||||
| -rw-r--r-- | drivers/usb/host/ehci-core.h | 29 | ||||
| -rw-r--r-- | drivers/usb/host/ehci-fsl.c | 100 | ||||
| -rw-r--r-- | drivers/usb/host/ehci-fsl.h | 86 | ||||
| -rw-r--r-- | drivers/usb/host/ehci-hcd.c | 883 | ||||
| -rw-r--r-- | drivers/usb/host/ehci-ixp4xx.c | 50 | ||||
| -rw-r--r-- | drivers/usb/host/ehci-pci.c | 65 | ||||
| -rw-r--r-- | drivers/usb/host/ehci-vct.c | 58 | ||||
| -rw-r--r-- | drivers/usb/host/ehci.h | 194 | ||||
| -rw-r--r-- | drivers/usb/host/isp116x-hcd.c | 1441 | ||||
| -rw-r--r-- | drivers/usb/host/isp116x.h | 489 | ||||
| -rw-r--r-- | drivers/usb/host/ohci-at91.c | 80 | ||||
| -rw-r--r-- | drivers/usb/host/ohci-hcd.c | 2016 | ||||
| -rw-r--r-- | drivers/usb/host/ohci.h | 483 | ||||
| -rw-r--r-- | drivers/usb/host/r8a66597-hcd.c | 945 | ||||
| -rw-r--r-- | drivers/usb/host/r8a66597.h | 659 | ||||
| -rw-r--r-- | drivers/usb/host/s3c64xx-hcd.c | 45 | ||||
| -rw-r--r-- | drivers/usb/host/sl811-hcd.c | 734 | ||||
| -rw-r--r-- | drivers/usb/host/sl811.h | 104 | 
19 files changed, 8520 insertions, 0 deletions
| diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile new file mode 100644 index 000000000..ec1d689b3 --- /dev/null +++ b/drivers/usb/host/Makefile @@ -0,0 +1,59 @@ +# +# (C) Copyright 2000-2007 +# Wolfgang Denk, DENX Software Engineering, wd@denx.de. +# +# See file CREDITS for list of people who contributed to this +# project. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, +# MA 02111-1307 USA +# + +include $(TOPDIR)/config.mk + +LIB	:= $(obj)libusb_host.a + +# ohci +COBJS-$(CONFIG_USB_OHCI_NEW) += ohci-hcd.o +COBJS-$(CONFIG_USB_ATMEL) += ohci-at91.o +COBJS-$(CONFIG_USB_ISP116X_HCD) += isp116x-hcd.o +COBJS-$(CONFIG_USB_R8A66597_HCD) += r8a66597-hcd.o +COBJS-$(CONFIG_USB_S3C64XX) += s3c64xx-hcd.o +COBJS-$(CONFIG_USB_SL811HS) += sl811-hcd.o + +# echi +COBJS-$(CONFIG_USB_EHCI) += ehci-hcd.o +COBJS-$(CONFIG_USB_EHCI_FSL) += ehci-fsl.o +COBJS-$(CONFIG_USB_EHCI_IXP4XX) += ehci-ixp.o +COBJS-$(CONFIG_USB_EHCI_PCI) += ehci-pci.o +COBJS-$(CONFIG_USB_EHCI_VCT) += ehci-vct.o + +COBJS	:= $(COBJS-y) +SRCS	:= $(COBJS:.o=.c) +OBJS	:= $(addprefix $(obj),$(COBJS)) + +all:	$(LIB) + +$(LIB):	$(obj).depend $(OBJS) +	$(AR) $(ARFLAGS) $@ $(OBJS) + +######################################################################### + +# defines $(obj).depend target +include $(SRCTREE)/rules.mk + +sinclude $(obj).depend + +######################################################################### diff --git a/drivers/usb/host/ehci-core.h b/drivers/usb/host/ehci-core.h new file mode 100644 index 000000000..39e5c5e58 --- /dev/null +++ b/drivers/usb/host/ehci-core.h @@ -0,0 +1,29 @@ +/*- + * Copyright (c) 2007-2008, Juniper Networks, Inc. + * Copyright (c) 2008, Excito Elektronik i Skåne AB + * All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2 of + * the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#ifndef USB_EHCI_CORE_H +#define USB_EHCI_CORE_H + +extern int rootdev; +extern struct ehci_hccr *hccr; +extern volatile struct ehci_hcor *hcor; + +#endif diff --git a/drivers/usb/host/ehci-fsl.c b/drivers/usb/host/ehci-fsl.c new file mode 100644 index 000000000..86ee1d571 --- /dev/null +++ b/drivers/usb/host/ehci-fsl.c @@ -0,0 +1,100 @@ +/* + * (C) Copyright 2008, Excito Elektronik i Sk=E5ne AB + * + * Author: Tor Krill tor@excito.com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <common.h> +#include <pci.h> +#include <usb.h> +#include <mpc83xx.h> +#include <asm/io.h> +#include <asm/bitops.h> + +#include "ehci.h" +#include "ehci-fsl.h" +#include "ehci-core.h" + +/* + * Create the appropriate control structures to manage + * a new EHCI host controller. + * + * Excerpts from linux ehci fsl driver. + */ +int ehci_hcd_init(void) +{ +	volatile immap_t *im = (immap_t *)CONFIG_SYS_IMMR; +	uint32_t addr, temp; + +	addr = (uint32_t)&(im->usb[0]); +	hccr = (struct ehci_hccr *)(addr + FSL_SKIP_PCI); +	hcor = (struct ehci_hcor *)((uint32_t) hccr + +			HC_LENGTH(ehci_readl(&hccr->cr_capbase))); + +	/* Configure clock */ +	clrsetbits_be32(&(im->clk.sccr), MPC83XX_SCCR_USB_MASK, +			MPC83XX_SCCR_USB_DRCM_11); + +	/* Confgure interface. */ +	temp = in_be32((void *)(addr + FSL_SOC_USB_CTRL)); +	out_be32((void *)(addr + FSL_SOC_USB_CTRL), temp +		 | REFSEL_16MHZ | UTMI_PHY_EN); + +	/* Wait for clock to stabilize */ +	do { +		temp = in_be32((void *)(addr + FSL_SOC_USB_CTRL)); +		udelay(1000); +	} while (!(temp & PHY_CLK_VALID)); + +	/* Set to Host mode */ +	temp = in_le32((void *)(addr + FSL_SOC_USB_USBMODE)); +	out_le32((void *)(addr + FSL_SOC_USB_USBMODE), temp | CM_HOST); + +	out_be32((void *)(addr + FSL_SOC_USB_SNOOP1), SNOOP_SIZE_2GB); +	out_be32((void *)(addr + FSL_SOC_USB_SNOOP2), +		 0x80000000 | SNOOP_SIZE_2GB); + +	/* Init phy */ +	/* TODO: handle different phys? */ +	out_le32(&(hcor->or_portsc[0]), PORT_PTS_UTMI); + +	/* Enable interface. */ +	temp = in_be32((void *)(addr + FSL_SOC_USB_CTRL)); +	out_be32((void *)(addr + FSL_SOC_USB_CTRL), temp | USB_EN); + +	out_be32((void *)(addr + FSL_SOC_USB_PRICTRL), 0x0000000c); +	out_be32((void *)(addr + FSL_SOC_USB_AGECNTTHRSH), 0x00000040); +	out_be32((void *)(addr + FSL_SOC_USB_SICTRL), 0x00000001); + +	/* Enable interface. */ +	temp = in_be32((void *)(addr + FSL_SOC_USB_CTRL)); +	out_be32((void *)(addr + FSL_SOC_USB_CTRL), temp | USB_EN); + +	temp = in_le32((void *)(addr + FSL_SOC_USB_USBMODE)); + +	return 0; +} + +/* + * Destroy the appropriate control structures corresponding + * the the EHCI host controller. + */ +int ehci_hcd_stop(void) +{ +	return 0; +} diff --git a/drivers/usb/host/ehci-fsl.h b/drivers/usb/host/ehci-fsl.h new file mode 100644 index 000000000..c429af125 --- /dev/null +++ b/drivers/usb/host/ehci-fsl.h @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2005 freescale semiconductor + * Copyright (c) 2005 MontaVista Software + * Copyright (c) 2008 Excito Elektronik i Sk=E5ne AB + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#ifndef _EHCI_FSL_H +#define _EHCI_FSL_H + +/* Global offsets */ +#define FSL_SKIP_PCI		0x100 + +/* offsets for the non-ehci registers in the FSL SOC USB controller */ +#define FSL_SOC_USB_ULPIVP	0x170 +#define FSL_SOC_USB_PORTSC1	0x184 +#define PORT_PTS_MSK		(3 << 30) +#define PORT_PTS_UTMI		(0 << 30) +#define PORT_PTS_ULPI		(2 << 30) +#define PORT_PTS_SERIAL		(3 << 30) +#define PORT_PTS_PTW		(1 << 28) + +/* USBMODE Register bits */ +#define CM_IDLE			(0 << 0) +#define CM_RESERVED		(1 << 0) +#define CM_DEVICE		(2 << 0) +#define CM_HOST			(3 << 0) +#define USBMODE_RESERVED_2	(0 << 2) +#define SLOM			(1 << 3) +#define SDIS			(1 << 4) + +/* CONTROL Register bits */ +#define ULPI_INT_EN		(1 << 0) +#define WU_INT_EN		(1 << 1) +#define USB_EN			(1 << 2) +#define LSF_EN			(1 << 3) +#define KEEP_OTG_ON		(1 << 4) +#define OTG_PORT		(1 << 5) +#define REFSEL_12MHZ		(0 << 6) +#define REFSEL_16MHZ		(1 << 6) +#define REFSEL_48MHZ		(2 << 6) +#define PLL_RESET		(1 << 8) +#define UTMI_PHY_EN		(1 << 9) +#define PHY_CLK_SEL_UTMI	(0 << 10) +#define PHY_CLK_SEL_ULPI	(1 << 10) +#define CLKIN_SEL_USB_CLK	(0 << 11) +#define CLKIN_SEL_USB_CLK2	(1 << 11) +#define CLKIN_SEL_SYS_CLK	(2 << 11) +#define CLKIN_SEL_SYS_CLK2	(3 << 11) +#define RESERVED_18		(0 << 13) +#define RESERVED_17		(0 << 14) +#define RESERVED_16		(0 << 15) +#define WU_INT			(1 << 16) +#define PHY_CLK_VALID		(1 << 17) + +#define FSL_SOC_USB_PORTSC2	0x188 +#define FSL_SOC_USB_USBMODE	0x1a8 +#define FSL_SOC_USB_SNOOP1	0x400	/* NOTE: big-endian */ +#define FSL_SOC_USB_SNOOP2	0x404	/* NOTE: big-endian */ +#define FSL_SOC_USB_AGECNTTHRSH	0x408	/* NOTE: big-endian */ +#define FSL_SOC_USB_PRICTRL	0x40c	/* NOTE: big-endian */ +#define FSL_SOC_USB_SICTRL	0x410	/* NOTE: big-endian */ +#define FSL_SOC_USB_CTRL	0x500	/* NOTE: big-endian */ +#define SNOOP_SIZE_2GB		0x1e + +/* System Clock Control Register */ +#define MPC83XX_SCCR_USB_MASK		0x00f00000 +#define MPC83XX_SCCR_USB_DRCM_11	0x00300000 +#define MPC83XX_SCCR_USB_DRCM_01	0x00100000 +#define MPC83XX_SCCR_USB_DRCM_10	0x00200000 + +#endif /* _EHCI_FSL_H */ diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c new file mode 100644 index 000000000..bbd547b3e --- /dev/null +++ b/drivers/usb/host/ehci-hcd.c @@ -0,0 +1,883 @@ +/*- + * Copyright (c) 2007-2008, Juniper Networks, Inc. + * Copyright (c) 2008, Excito Elektronik i Skåne AB + * Copyright (c) 2008, Michael Trimarchi <trimarchimichael@yahoo.it> + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2 of + * the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ +#include <common.h> +#include <asm/byteorder.h> +#include <usb.h> +#include <asm/io.h> +#include <malloc.h> + +#include "ehci.h" + +int rootdev; +struct ehci_hccr *hccr;	/* R/O registers, not need for volatile */ +volatile struct ehci_hcor *hcor; + +static uint16_t portreset; +static struct QH qh_list __attribute__((aligned(32))); + +static struct descriptor { +	struct usb_hub_descriptor hub; +	struct usb_device_descriptor device; +	struct usb_linux_config_descriptor config; +	struct usb_linux_interface_descriptor interface; +	struct usb_endpoint_descriptor endpoint; +}  __attribute__ ((packed)) descriptor = { +	{ +		0x8,		/* bDescLength */ +		0x29,		/* bDescriptorType: hub descriptor */ +		2,		/* bNrPorts -- runtime modified */ +		0,		/* wHubCharacteristics */ +		0xff,		/* bPwrOn2PwrGood */ +		0,		/* bHubCntrCurrent */ +		{},		/* Device removable */ +		{}		/* at most 7 ports! XXX */ +	}, +	{ +		0x12,		/* bLength */ +		1,		/* bDescriptorType: UDESC_DEVICE */ +		0x0002,		/* bcdUSB: v2.0 */ +		9,		/* bDeviceClass: UDCLASS_HUB */ +		0,		/* bDeviceSubClass: UDSUBCLASS_HUB */ +		1,		/* bDeviceProtocol: UDPROTO_HSHUBSTT */ +		64,		/* bMaxPacketSize: 64 bytes */ +		0x0000,		/* idVendor */ +		0x0000,		/* idProduct */ +		0x0001,		/* bcdDevice */ +		1,		/* iManufacturer */ +		2,		/* iProduct */ +		0,		/* iSerialNumber */ +		1		/* bNumConfigurations: 1 */ +	}, +	{ +		0x9, +		2,		/* bDescriptorType: UDESC_CONFIG */ +		cpu_to_le16(0x19), +		1,		/* bNumInterface */ +		1,		/* bConfigurationValue */ +		0,		/* iConfiguration */ +		0x40,		/* bmAttributes: UC_SELF_POWER */ +		0		/* bMaxPower */ +	}, +	{ +		0x9,		/* bLength */ +		4,		/* bDescriptorType: UDESC_INTERFACE */ +		0,		/* bInterfaceNumber */ +		0,		/* bAlternateSetting */ +		1,		/* bNumEndpoints */ +		9,		/* bInterfaceClass: UICLASS_HUB */ +		0,		/* bInterfaceSubClass: UISUBCLASS_HUB */ +		0,		/* bInterfaceProtocol: UIPROTO_HSHUBSTT */ +		0		/* iInterface */ +	}, +	{ +		0x7,		/* bLength */ +		5,		/* bDescriptorType: UDESC_ENDPOINT */ +		0x81,		/* bEndpointAddress: +				 * UE_DIR_IN | EHCI_INTR_ENDPT +				 */ +		3,		/* bmAttributes: UE_INTERRUPT */ +		8, 0,		/* wMaxPacketSize */ +		255		/* bInterval */ +	}, +}; + +#if defined(CONFIG_EHCI_IS_TDI) +#define ehci_is_TDI()	(1) +#else +#define ehci_is_TDI()	(0) +#endif + +#if defined(CONFIG_EHCI_DCACHE) +/* + * Routines to handle (flush/invalidate) the dcache for the QH and qTD + * structures and data buffers. This is needed on platforms using this + * EHCI support with dcache enabled. + */ +static void flush_invalidate(u32 addr, int size, int flush) +{ +	if (flush) +		flush_dcache_range(addr, addr + size); +	else +		invalidate_dcache_range(addr, addr + size); +} + +static void cache_qtd(struct qTD *qtd, int flush) +{ +	u32 *ptr = (u32 *)qtd->qt_buffer[0]; +	int len = (qtd->qt_token & 0x7fff0000) >> 16; + +	flush_invalidate((u32)qtd, sizeof(struct qTD), flush); +	if (ptr && len) +		flush_invalidate((u32)ptr, len, flush); +} + + +static inline struct QH *qh_addr(struct QH *qh) +{ +	return (struct QH *)((u32)qh & 0xffffffe0); +} + +static void cache_qh(struct QH *qh, int flush) +{ +	struct qTD *qtd; +	struct qTD *next; +	static struct qTD *first_qtd; + +	/* +	 * Walk the QH list and flush/invalidate all entries +	 */ +	while (1) { +		flush_invalidate((u32)qh_addr(qh), sizeof(struct QH), flush); +		if ((u32)qh & QH_LINK_TYPE_QH) +			break; +		qh = qh_addr(qh); +		qh = (struct QH *)qh->qh_link; +	} +	qh = qh_addr(qh); + +	/* +	 * Save first qTD pointer, needed for invalidating pass on this QH +	 */ +	if (flush) +		first_qtd = qtd = (struct qTD *)(*(u32 *)&qh->qh_overlay & +						 0xffffffe0); +	else +		qtd = first_qtd; + +	/* +	 * Walk the qTD list and flush/invalidate all entries +	 */ +	while (1) { +		if (qtd == NULL) +			break; +		cache_qtd(qtd, flush); +		next = (struct qTD *)((u32)qtd->qt_next & 0xffffffe0); +		if (next == qtd) +			break; +		qtd = next; +	} +} + +static inline void ehci_flush_dcache(struct QH *qh) +{ +	cache_qh(qh, 1); +} + +static inline void ehci_invalidate_dcache(struct QH *qh) +{ +	cache_qh(qh, 0); +} +#else /* CONFIG_EHCI_DCACHE */ +/* + * + */ +static inline void ehci_flush_dcache(struct QH *qh) +{ +} + +static inline void ehci_invalidate_dcache(struct QH *qh) +{ +} +#endif /* CONFIG_EHCI_DCACHE */ + +static int handshake(uint32_t *ptr, uint32_t mask, uint32_t done, int usec) +{ +	uint32_t result; +	do { +		result = ehci_readl(ptr); +		if (result == ~(uint32_t)0) +			return -1; +		result &= mask; +		if (result == done) +			return 0; +		udelay(1); +		usec--; +	} while (usec > 0); +	return -1; +} + +static void ehci_free(void *p, size_t sz) +{ + +} + +static int ehci_reset(void) +{ +	uint32_t cmd; +	uint32_t tmp; +	uint32_t *reg_ptr; +	int ret = 0; + +	cmd = ehci_readl(&hcor->or_usbcmd); +	cmd |= CMD_RESET; +	ehci_writel(&hcor->or_usbcmd, cmd); +	ret = handshake((uint32_t *)&hcor->or_usbcmd, CMD_RESET, 0, 250 * 1000); +	if (ret < 0) { +		printf("EHCI fail to reset\n"); +		goto out; +	} + +	if (ehci_is_TDI()) { +		reg_ptr = (uint32_t *)((u8 *)hcor + USBMODE); +		tmp = ehci_readl(reg_ptr); +		tmp |= USBMODE_CM_HC; +#if defined(CONFIG_EHCI_MMIO_BIG_ENDIAN) +		tmp |= USBMODE_BE; +#endif +		ehci_writel(reg_ptr, tmp); +	} +out: +	return ret; +} + +static void *ehci_alloc(size_t sz, size_t align) +{ +	static struct QH qh __attribute__((aligned(32))); +	static struct qTD td[3] __attribute__((aligned (32))); +	static int ntds; +	void *p; + +	switch (sz) { +	case sizeof(struct QH): +		p = &qh; +		ntds = 0; +		break; +	case sizeof(struct qTD): +		if (ntds == 3) { +			debug("out of TDs\n"); +			return NULL; +		} +		p = &td[ntds]; +		ntds++; +		break; +	default: +		debug("unknown allocation size\n"); +		return NULL; +	} + +	memset(p, sz, 0); +	return p; +} + +static int ehci_td_buffer(struct qTD *td, void *buf, size_t sz) +{ +	uint32_t addr, delta, next; +	int idx; + +	addr = (uint32_t) buf; +	idx = 0; +	while (idx < 5) { +		td->qt_buffer[idx] = cpu_to_hc32(addr); +		next = (addr + 4096) & ~4095; +		delta = next - addr; +		if (delta >= sz) +			break; +		sz -= delta; +		addr = next; +		idx++; +	} + +	if (idx == 5) { +		debug("out of buffer pointers (%u bytes left)\n", sz); +		return -1; +	} + +	return 0; +} + +static int +ehci_submit_async(struct usb_device *dev, unsigned long pipe, void *buffer, +		   int length, struct devrequest *req) +{ +	struct QH *qh; +	struct qTD *td; +	volatile struct qTD *vtd; +	unsigned long ts; +	uint32_t *tdp; +	uint32_t endpt, token, usbsts; +	uint32_t c, toggle; +	uint32_t cmd; +	int ret = 0; + +	debug("dev=%p, pipe=%lx, buffer=%p, length=%d, req=%p\n", dev, pipe, +	      buffer, length, req); +	if (req != NULL) +		debug("req=%u (%#x), type=%u (%#x), value=%u (%#x), index=%u\n", +		      req->request, req->request, +		      req->requesttype, req->requesttype, +		      le16_to_cpu(req->value), le16_to_cpu(req->value), +		      le16_to_cpu(req->index)); + +	qh = ehci_alloc(sizeof(struct QH), 32); +	if (qh == NULL) { +		debug("unable to allocate QH\n"); +		return -1; +	} +	qh->qh_link = cpu_to_hc32((uint32_t)&qh_list | QH_LINK_TYPE_QH); +	c = (usb_pipespeed(pipe) != USB_SPEED_HIGH && +	     usb_pipeendpoint(pipe) == 0) ? 1 : 0; +	endpt = (8 << 28) | +	    (c << 27) | +	    (usb_maxpacket(dev, pipe) << 16) | +	    (0 << 15) | +	    (1 << 14) | +	    (usb_pipespeed(pipe) << 12) | +	    (usb_pipeendpoint(pipe) << 8) | +	    (0 << 7) | (usb_pipedevice(pipe) << 0); +	qh->qh_endpt1 = cpu_to_hc32(endpt); +	endpt = (1 << 30) | +	    (dev->portnr << 23) | +	    (dev->parent->devnum << 16) | (0 << 8) | (0 << 0); +	qh->qh_endpt2 = cpu_to_hc32(endpt); +	qh->qh_overlay.qt_next = cpu_to_hc32(QT_NEXT_TERMINATE); +	qh->qh_overlay.qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE); + +	td = NULL; +	tdp = &qh->qh_overlay.qt_next; + +	toggle = +	    usb_gettoggle(dev, usb_pipeendpoint(pipe), usb_pipeout(pipe)); + +	if (req != NULL) { +		td = ehci_alloc(sizeof(struct qTD), 32); +		if (td == NULL) { +			debug("unable to allocate SETUP td\n"); +			goto fail; +		} +		td->qt_next = cpu_to_hc32(QT_NEXT_TERMINATE); +		td->qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE); +		token = (0 << 31) | +		    (sizeof(*req) << 16) | +		    (0 << 15) | (0 << 12) | (3 << 10) | (2 << 8) | (0x80 << 0); +		td->qt_token = cpu_to_hc32(token); +		if (ehci_td_buffer(td, req, sizeof(*req)) != 0) { +			debug("unable construct SETUP td\n"); +			ehci_free(td, sizeof(*td)); +			goto fail; +		} +		*tdp = cpu_to_hc32((uint32_t) td); +		tdp = &td->qt_next; +		toggle = 1; +	} + +	if (length > 0 || req == NULL) { +		td = ehci_alloc(sizeof(struct qTD), 32); +		if (td == NULL) { +			debug("unable to allocate DATA td\n"); +			goto fail; +		} +		td->qt_next = cpu_to_hc32(QT_NEXT_TERMINATE); +		td->qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE); +		token = (toggle << 31) | +		    (length << 16) | +		    ((req == NULL ? 1 : 0) << 15) | +		    (0 << 12) | +		    (3 << 10) | +		    ((usb_pipein(pipe) ? 1 : 0) << 8) | (0x80 << 0); +		td->qt_token = cpu_to_hc32(token); +		if (ehci_td_buffer(td, buffer, length) != 0) { +			debug("unable construct DATA td\n"); +			ehci_free(td, sizeof(*td)); +			goto fail; +		} +		*tdp = cpu_to_hc32((uint32_t) td); +		tdp = &td->qt_next; +	} + +	if (req != NULL) { +		td = ehci_alloc(sizeof(struct qTD), 32); +		if (td == NULL) { +			debug("unable to allocate ACK td\n"); +			goto fail; +		} +		td->qt_next = cpu_to_hc32(QT_NEXT_TERMINATE); +		td->qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE); +		token = (toggle << 31) | +		    (0 << 16) | +		    (1 << 15) | +		    (0 << 12) | +		    (3 << 10) | +		    ((usb_pipein(pipe) ? 0 : 1) << 8) | (0x80 << 0); +		td->qt_token = cpu_to_hc32(token); +		*tdp = cpu_to_hc32((uint32_t) td); +		tdp = &td->qt_next; +	} + +	qh_list.qh_link = cpu_to_hc32((uint32_t) qh | QH_LINK_TYPE_QH); + +	/* Flush dcache */ +	ehci_flush_dcache(&qh_list); + +	usbsts = ehci_readl(&hcor->or_usbsts); +	ehci_writel(&hcor->or_usbsts, (usbsts & 0x3f)); + +	/* Enable async. schedule. */ +	cmd = ehci_readl(&hcor->or_usbcmd); +	cmd |= CMD_ASE; +	ehci_writel(&hcor->or_usbcmd, cmd); + +	ret = handshake((uint32_t *)&hcor->or_usbsts, STD_ASS, STD_ASS, +			100 * 1000); +	if (ret < 0) { +		printf("EHCI fail timeout STD_ASS set\n"); +		goto fail; +	} + +	/* Wait for TDs to be processed. */ +	ts = get_timer(0); +	vtd = td; +	do { +		/* Invalidate dcache */ +		ehci_invalidate_dcache(&qh_list); +		token = hc32_to_cpu(vtd->qt_token); +		if (!(token & 0x80)) +			break; +	} while (get_timer(ts) < CONFIG_SYS_HZ); + +	/* Disable async schedule. */ +	cmd = ehci_readl(&hcor->or_usbcmd); +	cmd &= ~CMD_ASE; +	ehci_writel(&hcor->or_usbcmd, cmd); + +	ret = handshake((uint32_t *)&hcor->or_usbsts, STD_ASS, 0, +			100 * 1000); +	if (ret < 0) { +		printf("EHCI fail timeout STD_ASS reset\n"); +		goto fail; +	} + +	qh_list.qh_link = cpu_to_hc32((uint32_t)&qh_list | QH_LINK_TYPE_QH); + +	token = hc32_to_cpu(qh->qh_overlay.qt_token); +	if (!(token & 0x80)) { +		debug("TOKEN=%#x\n", token); +		switch (token & 0xfc) { +		case 0: +			toggle = token >> 31; +			usb_settoggle(dev, usb_pipeendpoint(pipe), +				       usb_pipeout(pipe), toggle); +			dev->status = 0; +			break; +		case 0x40: +			dev->status = USB_ST_STALLED; +			break; +		case 0xa0: +		case 0x20: +			dev->status = USB_ST_BUF_ERR; +			break; +		case 0x50: +		case 0x10: +			dev->status = USB_ST_BABBLE_DET; +			break; +		default: +			dev->status = USB_ST_CRC_ERR; +			break; +		} +		dev->act_len = length - ((token >> 16) & 0x7fff); +	} else { +		dev->act_len = 0; +		debug("dev=%u, usbsts=%#x, p[1]=%#x, p[2]=%#x\n", +		      dev->devnum, ehci_readl(&hcor->or_usbsts), +		      ehci_readl(&hcor->or_portsc[0]), +		      ehci_readl(&hcor->or_portsc[1])); +	} + +	return (dev->status != USB_ST_NOT_PROC) ? 0 : -1; + +fail: +	td = (void *)hc32_to_cpu(qh->qh_overlay.qt_next); +	while (td != (void *)QT_NEXT_TERMINATE) { +		qh->qh_overlay.qt_next = td->qt_next; +		ehci_free(td, sizeof(*td)); +		td = (void *)hc32_to_cpu(qh->qh_overlay.qt_next); +	} +	ehci_free(qh, sizeof(*qh)); +	return -1; +} + +static inline int min3(int a, int b, int c) +{ + +	if (b < a) +		a = b; +	if (c < a) +		a = c; +	return a; +} + +int +ehci_submit_root(struct usb_device *dev, unsigned long pipe, void *buffer, +		 int length, struct devrequest *req) +{ +	uint8_t tmpbuf[4]; +	u16 typeReq; +	void *srcptr = NULL; +	int len, srclen; +	uint32_t reg; +	uint32_t *status_reg; + +	if (le16_to_cpu(req->index) >= CONFIG_SYS_USB_EHCI_MAX_ROOT_PORTS) { +		printf("The request port(%d) is not configured\n", +			le16_to_cpu(req->index) - 1); +		return -1; +	} +	status_reg = (uint32_t *)&hcor->or_portsc[ +						le16_to_cpu(req->index) - 1]; +	srclen = 0; + +	debug("req=%u (%#x), type=%u (%#x), value=%u, index=%u\n", +	      req->request, req->request, +	      req->requesttype, req->requesttype, +	      le16_to_cpu(req->value), le16_to_cpu(req->index)); + +	typeReq = req->request << 8 | req->requesttype; + +	switch (le16_to_cpu(typeReq)) { +	case DeviceRequest | USB_REQ_GET_DESCRIPTOR: +		switch (le16_to_cpu(req->value) >> 8) { +		case USB_DT_DEVICE: +			debug("USB_DT_DEVICE request\n"); +			srcptr = &descriptor.device; +			srclen = 0x12; +			break; +		case USB_DT_CONFIG: +			debug("USB_DT_CONFIG config\n"); +			srcptr = &descriptor.config; +			srclen = 0x19; +			break; +		case USB_DT_STRING: +			debug("USB_DT_STRING config\n"); +			switch (le16_to_cpu(req->value) & 0xff) { +			case 0:	/* Language */ +				srcptr = "\4\3\1\0"; +				srclen = 4; +				break; +			case 1:	/* Vendor */ +				srcptr = "\16\3u\0-\0b\0o\0o\0t\0"; +				srclen = 14; +				break; +			case 2:	/* Product */ +				srcptr = "\52\3E\0H\0C\0I\0 " +					 "\0H\0o\0s\0t\0 " +					 "\0C\0o\0n\0t\0r\0o\0l\0l\0e\0r\0"; +				srclen = 42; +				break; +			default: +				debug("unknown value DT_STRING %x\n", +					le16_to_cpu(req->value)); +				goto unknown; +			} +			break; +		default: +			debug("unknown value %x\n", le16_to_cpu(req->value)); +			goto unknown; +		} +		break; +	case USB_REQ_GET_DESCRIPTOR | ((USB_DIR_IN | USB_RT_HUB) << 8): +		switch (le16_to_cpu(req->value) >> 8) { +		case USB_DT_HUB: +			debug("USB_DT_HUB config\n"); +			srcptr = &descriptor.hub; +			srclen = 0x8; +			break; +		default: +			debug("unknown value %x\n", le16_to_cpu(req->value)); +			goto unknown; +		} +		break; +	case USB_REQ_SET_ADDRESS | (USB_RECIP_DEVICE << 8): +		debug("USB_REQ_SET_ADDRESS\n"); +		rootdev = le16_to_cpu(req->value); +		break; +	case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: +		debug("USB_REQ_SET_CONFIGURATION\n"); +		/* Nothing to do */ +		break; +	case USB_REQ_GET_STATUS | ((USB_DIR_IN | USB_RT_HUB) << 8): +		tmpbuf[0] = 1;	/* USB_STATUS_SELFPOWERED */ +		tmpbuf[1] = 0; +		srcptr = tmpbuf; +		srclen = 2; +		break; +	case USB_REQ_GET_STATUS | ((USB_RT_PORT | USB_DIR_IN) << 8): +		memset(tmpbuf, 0, 4); +		reg = ehci_readl(status_reg); +		if (reg & EHCI_PS_CS) +			tmpbuf[0] |= USB_PORT_STAT_CONNECTION; +		if (reg & EHCI_PS_PE) +			tmpbuf[0] |= USB_PORT_STAT_ENABLE; +		if (reg & EHCI_PS_SUSP) +			tmpbuf[0] |= USB_PORT_STAT_SUSPEND; +		if (reg & EHCI_PS_OCA) +			tmpbuf[0] |= USB_PORT_STAT_OVERCURRENT; +		if (reg & EHCI_PS_PR && +		    (portreset & (1 << le16_to_cpu(req->index)))) { +			int ret; +			/* force reset to complete */ +			reg = reg & ~(EHCI_PS_PR | EHCI_PS_CLEAR); +			ehci_writel(status_reg, reg); +			ret = handshake(status_reg, EHCI_PS_PR, 0, 2 * 1000); +			if (!ret) +				tmpbuf[0] |= USB_PORT_STAT_RESET; +			else +				printf("port(%d) reset error\n", +					le16_to_cpu(req->index) - 1); +		} +		if (reg & EHCI_PS_PP) +			tmpbuf[1] |= USB_PORT_STAT_POWER >> 8; + +		if (ehci_is_TDI()) { +			switch ((reg >> 26) & 3) { +			case 0: +				break; +			case 1: +				tmpbuf[1] |= USB_PORT_STAT_LOW_SPEED >> 8; +				break; +			case 2: +			default: +				tmpbuf[1] |= USB_PORT_STAT_HIGH_SPEED >> 8; +				break; +			} +		} else { +			tmpbuf[1] |= USB_PORT_STAT_HIGH_SPEED >> 8; +		} + +		if (reg & EHCI_PS_CSC) +			tmpbuf[2] |= USB_PORT_STAT_C_CONNECTION; +		if (reg & EHCI_PS_PEC) +			tmpbuf[2] |= USB_PORT_STAT_C_ENABLE; +		if (reg & EHCI_PS_OCC) +			tmpbuf[2] |= USB_PORT_STAT_C_OVERCURRENT; +		if (portreset & (1 << le16_to_cpu(req->index))) +			tmpbuf[2] |= USB_PORT_STAT_C_RESET; + +		srcptr = tmpbuf; +		srclen = 4; +		break; +	case USB_REQ_SET_FEATURE | ((USB_DIR_OUT | USB_RT_PORT) << 8): +		reg = ehci_readl(status_reg); +		reg &= ~EHCI_PS_CLEAR; +		switch (le16_to_cpu(req->value)) { +		case USB_PORT_FEAT_ENABLE: +			reg |= EHCI_PS_PE; +			ehci_writel(status_reg, reg); +			break; +		case USB_PORT_FEAT_POWER: +			if (HCS_PPC(ehci_readl(&hccr->cr_hcsparams))) { +				reg |= EHCI_PS_PP; +				ehci_writel(status_reg, reg); +			} +			break; +		case USB_PORT_FEAT_RESET: +			if ((reg & (EHCI_PS_PE | EHCI_PS_CS)) == EHCI_PS_CS && +			    !ehci_is_TDI() && +			    EHCI_PS_IS_LOWSPEED(reg)) { +				/* Low speed device, give up ownership. */ +				debug("port %d low speed --> companion\n", +				      req->index - 1); +				reg |= EHCI_PS_PO; +				ehci_writel(status_reg, reg); +				break; +			} else { +				reg |= EHCI_PS_PR; +				reg &= ~EHCI_PS_PE; +				ehci_writel(status_reg, reg); +				/* +				 * caller must wait, then call GetPortStatus +				 * usb 2.0 specification say 50 ms resets on +				 * root +				 */ +				wait_ms(50); +				portreset |= 1 << le16_to_cpu(req->index); +			} +			break; +		default: +			debug("unknown feature %x\n", le16_to_cpu(req->value)); +			goto unknown; +		} +		/* unblock posted writes */ +		ehci_readl(&hcor->or_usbcmd); +		break; +	case USB_REQ_CLEAR_FEATURE | ((USB_DIR_OUT | USB_RT_PORT) << 8): +		reg = ehci_readl(status_reg); +		switch (le16_to_cpu(req->value)) { +		case USB_PORT_FEAT_ENABLE: +			reg &= ~EHCI_PS_PE; +			break; +		case USB_PORT_FEAT_C_ENABLE: +			reg = (reg & ~EHCI_PS_CLEAR) | EHCI_PS_PE; +			break; +		case USB_PORT_FEAT_POWER: +			if (HCS_PPC(ehci_readl(&hccr->cr_hcsparams))) +				reg = reg & ~(EHCI_PS_CLEAR | EHCI_PS_PP); +		case USB_PORT_FEAT_C_CONNECTION: +			reg = (reg & ~EHCI_PS_CLEAR) | EHCI_PS_CSC; +			break; +		case USB_PORT_FEAT_OVER_CURRENT: +			reg = (reg & ~EHCI_PS_CLEAR) | EHCI_PS_OCC; +			break; +		case USB_PORT_FEAT_C_RESET: +			portreset &= ~(1 << le16_to_cpu(req->index)); +			break; +		default: +			debug("unknown feature %x\n", le16_to_cpu(req->value)); +			goto unknown; +		} +		ehci_writel(status_reg, reg); +		/* unblock posted write */ +		ehci_readl(&hcor->or_usbcmd); +		break; +	default: +		debug("Unknown request\n"); +		goto unknown; +	} + +	wait_ms(1); +	len = min3(srclen, le16_to_cpu(req->length), length); +	if (srcptr != NULL && len > 0) +		memcpy(buffer, srcptr, len); +	else +		debug("Len is 0\n"); + +	dev->act_len = len; +	dev->status = 0; +	return 0; + +unknown: +	debug("requesttype=%x, request=%x, value=%x, index=%x, length=%x\n", +	      req->requesttype, req->request, le16_to_cpu(req->value), +	      le16_to_cpu(req->index), le16_to_cpu(req->length)); + +	dev->act_len = 0; +	dev->status = USB_ST_STALLED; +	return -1; +} + +int usb_lowlevel_stop(void) +{ +	return ehci_hcd_stop(); +} + +int usb_lowlevel_init(void) +{ +	uint32_t reg; +	uint32_t cmd; + +	if (ehci_hcd_init() != 0) +		return -1; + +	/* EHCI spec section 4.1 */ +	if (ehci_reset() != 0) +		return -1; + +#if defined(CONFIG_EHCI_HCD_INIT_AFTER_RESET) +	if (ehci_hcd_init() != 0) +		return -1; +#endif + +	/* Set head of reclaim list */ +	memset(&qh_list, 0, sizeof(qh_list)); +	qh_list.qh_link = cpu_to_hc32((uint32_t)&qh_list | QH_LINK_TYPE_QH); +	qh_list.qh_endpt1 = cpu_to_hc32((1 << 15) | (USB_SPEED_HIGH << 12)); +	qh_list.qh_curtd = cpu_to_hc32(QT_NEXT_TERMINATE); +	qh_list.qh_overlay.qt_next = cpu_to_hc32(QT_NEXT_TERMINATE); +	qh_list.qh_overlay.qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE); +	qh_list.qh_overlay.qt_token = cpu_to_hc32(0x40); + +	/* Set async. queue head pointer. */ +	ehci_writel(&hcor->or_asynclistaddr, (uint32_t)&qh_list); + +	reg = ehci_readl(&hccr->cr_hcsparams); +	descriptor.hub.bNbrPorts = HCS_N_PORTS(reg); +	printf("Register %x NbrPorts %d\n", reg, descriptor.hub.bNbrPorts); +	/* Port Indicators */ +	if (HCS_INDICATOR(reg)) +		descriptor.hub.wHubCharacteristics |= 0x80; +	/* Port Power Control */ +	if (HCS_PPC(reg)) +		descriptor.hub.wHubCharacteristics |= 0x01; + +	/* Start the host controller. */ +	cmd = ehci_readl(&hcor->or_usbcmd); +	/* +	 * Philips, Intel, and maybe others need CMD_RUN before the +	 * root hub will detect new devices (why?); NEC doesn't +	 */ +	cmd &= ~(CMD_LRESET|CMD_IAAD|CMD_PSE|CMD_ASE|CMD_RESET); +	cmd |= CMD_RUN; +	ehci_writel(&hcor->or_usbcmd, cmd); + +	/* take control over the ports */ +	cmd = ehci_readl(&hcor->or_configflag); +	cmd |= FLAG_CF; +	ehci_writel(&hcor->or_configflag, cmd); +	/* unblock posted write */ +	cmd = ehci_readl(&hcor->or_usbcmd); +	wait_ms(5); +	reg = HC_VERSION(ehci_readl(&hccr->cr_capbase)); +	printf("USB EHCI %x.%02x\n", reg >> 8, reg & 0xff); + +	rootdev = 0; + +	return 0; +} + +int +submit_bulk_msg(struct usb_device *dev, unsigned long pipe, void *buffer, +		int length) +{ + +	if (usb_pipetype(pipe) != PIPE_BULK) { +		debug("non-bulk pipe (type=%lu)", usb_pipetype(pipe)); +		return -1; +	} +	return ehci_submit_async(dev, pipe, buffer, length, NULL); +} + +int +submit_control_msg(struct usb_device *dev, unsigned long pipe, void *buffer, +		   int length, struct devrequest *setup) +{ + +	if (usb_pipetype(pipe) != PIPE_CONTROL) { +		debug("non-control pipe (type=%lu)", usb_pipetype(pipe)); +		return -1; +	} + +	if (usb_pipedevice(pipe) == rootdev) { +		if (rootdev == 0) +			dev->speed = USB_SPEED_HIGH; +		return ehci_submit_root(dev, pipe, buffer, length, setup); +	} +	return ehci_submit_async(dev, pipe, buffer, length, setup); +} + +int +submit_int_msg(struct usb_device *dev, unsigned long pipe, void *buffer, +	       int length, int interval) +{ + +	debug("dev=%p, pipe=%lu, buffer=%p, length=%d, interval=%d", +	      dev, pipe, buffer, length, interval); +	return -1; +} diff --git a/drivers/usb/host/ehci-ixp4xx.c b/drivers/usb/host/ehci-ixp4xx.c new file mode 100644 index 000000000..b8f15ae52 --- /dev/null +++ b/drivers/usb/host/ehci-ixp4xx.c @@ -0,0 +1,50 @@ +/* + * (C) Copyright 2008, Michael Trimarchi <trimarchimichael@yahoo.it> + * + * Author: Michael Trimarchi <trimarchimichael@yahoo.it> + * This code is based on ehci freescale driver + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ +#include <common.h> +#include <usb.h> + +#include "ehci.h" +#include "ehci-core.h" +/* + * Create the appropriate control structures to manage + * a new EHCI host controller. + */ +int ehci_hcd_init(void) +{ +	hccr = (struct ehci_hccr *)(0xcd000100); +	hcor = (struct ehci_hcor *)((uint32_t) hccr +			+ HC_LENGTH(ehci_readl(&hccr->cr_capbase))); + +	printf("IXP4XX init hccr %x and hcor %x hc_length %d\n", +		(uint32_t)hccr, (uint32_t)hcor, +		(uint32_t)HC_LENGTH(ehci_readl(&hccr->cr_capbase))); +	return 0; +} + +/* + * Destroy the appropriate control structures corresponding + * the the EHCI host controller. + */ +int ehci_hcd_stop(void) +{ +	return 0; +} diff --git a/drivers/usb/host/ehci-pci.c b/drivers/usb/host/ehci-pci.c new file mode 100644 index 000000000..441b1a271 --- /dev/null +++ b/drivers/usb/host/ehci-pci.c @@ -0,0 +1,65 @@ +/*- + * Copyright (c) 2007-2008, Juniper Networks, Inc. + * All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2 of + * the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <common.h> +#include <pci.h> +#include <usb.h> + +#include "ehci.h" +#include "ehci-core.h" + +#ifdef CONFIG_PCI_EHCI_DEVICE +static struct pci_device_id ehci_pci_ids[] = { +	/* Please add supported PCI EHCI controller ids here */ +	{0, 0} +}; +#endif + +/* + * Create the appropriate control structures to manage + * a new EHCI host controller. + */ +int ehci_hcd_init(void) +{ +	pci_dev_t pdev; +	uint32_t addr; + +	pdev = pci_find_devices(ehci_pci_ids, CONFIG_PCI_EHCI_DEVICE); +	if (pdev == -1) { +		printf("EHCI host controller not found\n"); +		return -1; +	} + +	pci_read_config_dword(pdev, PCI_BASE_ADDRESS_0, &addr); +	hccr = (struct ehci_hccr *)addr; +	hcor = (struct ehci_hcor *)((uint32_t) hccr + +			HC_LENGTH(ehci_readl(&hccr->cr_capbase))); + +	return 0; +} + +/* + * Destroy the appropriate control structures corresponding + * the the EHCI host controller. + */ +int ehci_hcd_stop(void) +{ +	return 0; +} diff --git a/drivers/usb/host/ehci-vct.c b/drivers/usb/host/ehci-vct.c new file mode 100644 index 000000000..3063dd172 --- /dev/null +++ b/drivers/usb/host/ehci-vct.c @@ -0,0 +1,58 @@ +/* + * (C) Copyright 2009 Stefan Roese <sr@denx.de>, DENX Software Engineering + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <common.h> +#include <usb.h> + +#include "ehci.h" +#include "ehci-core.h" + +int vct_ehci_hcd_init(u32 *hccr, u32 *hcor); + +/* + * Create the appropriate control structures to manage + * a new EHCI host controller. + */ +int ehci_hcd_init(void) +{ +	int ret; +	u32 vct_hccr; +	u32 vct_hcor; + +	/* +	 * Init VCT specific stuff +	 */ +	ret = vct_ehci_hcd_init(&vct_hccr, &vct_hcor); +	if (ret) +		return ret; + +	hccr = (struct ehci_hccr *)vct_hccr; +	hcor = (struct ehci_hcor *)vct_hcor; + +	return 0; +} + +/* + * Destroy the appropriate control structures corresponding + * the the EHCI host controller. + */ +int ehci_hcd_stop(void) +{ +	return 0; +} diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h new file mode 100644 index 000000000..b3c1d5d72 --- /dev/null +++ b/drivers/usb/host/ehci.h @@ -0,0 +1,194 @@ +/*- + * Copyright (c) 2007-2008, Juniper Networks, Inc. + * Copyright (c) 2008, Michael Trimarchi <trimarchimichael@yahoo.it> + * All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2 of + * the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#ifndef USB_EHCI_H +#define USB_EHCI_H + +#if !defined(CONFIG_SYS_USB_EHCI_MAX_ROOT_PORTS) +#define CONFIG_SYS_USB_EHCI_MAX_ROOT_PORTS	2 +#endif + +/* (shifted) direction/type/recipient from the USB 2.0 spec, table 9.2 */ +#define DeviceRequest \ +	((USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE) << 8) + +#define DeviceOutRequest \ +	((USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE) << 8) + +#define InterfaceRequest \ +	((USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_INTERFACE) << 8) + +#define EndpointRequest \ +	((USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_INTERFACE) << 8) + +#define EndpointOutRequest \ +	((USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_INTERFACE) << 8) + +/* + * Register Space. + */ +struct ehci_hccr { +	uint32_t cr_capbase; +#define HC_LENGTH(p)		(((p) >> 0) & 0x00ff) +#define HC_VERSION(p)		(((p) >> 16) & 0xffff) +	uint32_t cr_hcsparams; +#define HCS_PPC(p)		((p) & (1 << 4)) +#define HCS_INDICATOR(p)	((p) & (1 << 16)) /* Port indicators */ +#define HCS_N_PORTS(p)		(((p) >> 0) & 0xf) +	uint32_t cr_hccparams; +	uint8_t cr_hcsp_portrt[8]; +} __attribute__ ((packed)); + +struct ehci_hcor { +	uint32_t or_usbcmd; +#define CMD_PARK	(1 << 11)		/* enable "park" */ +#define CMD_PARK_CNT(c)	(((c) >> 8) & 3)	/* how many transfers to park */ +#define CMD_ASE		(1 << 5)		/* async schedule enable */ +#define CMD_LRESET	(1 << 7)		/* partial reset */ +#define CMD_IAAD	(1 << 5)		/* "doorbell" interrupt */ +#define CMD_PSE		(1 << 4)		/* periodic schedule enable */ +#define CMD_RESET	(1 << 1)		/* reset HC not bus */ +#define CMD_RUN		(1 << 0)		/* start/stop HC */ +	uint32_t or_usbsts; +#define	STD_ASS		(1 << 15) +#define STS_HALT	(1 << 12) +	uint32_t or_usbintr; +	uint32_t or_frindex; +	uint32_t or_ctrldssegment; +	uint32_t or_periodiclistbase; +	uint32_t or_asynclistaddr; +	uint32_t _reserved_[9]; +	uint32_t or_configflag; +#define FLAG_CF		(1 << 0)	/* true:  we'll support "high speed" */ +	uint32_t or_portsc[CONFIG_SYS_USB_EHCI_MAX_ROOT_PORTS]; +	uint32_t or_systune; +} __attribute__ ((packed)); + +#define USBMODE		0x68		/* USB Device mode */ +#define USBMODE_SDIS	(1 << 3)	/* Stream disable */ +#define USBMODE_BE	(1 << 2)	/* BE/LE endiannes select */ +#define USBMODE_CM_HC	(3 << 0)	/* host controller mode */ +#define USBMODE_CM_IDLE	(0 << 0)	/* idle state */ + +/* Interface descriptor */ +struct usb_linux_interface_descriptor { +	unsigned char	bLength; +	unsigned char	bDescriptorType; +	unsigned char	bInterfaceNumber; +	unsigned char	bAlternateSetting; +	unsigned char	bNumEndpoints; +	unsigned char	bInterfaceClass; +	unsigned char	bInterfaceSubClass; +	unsigned char	bInterfaceProtocol; +	unsigned char	iInterface; +} __attribute__ ((packed)); + +/* Configuration descriptor information.. */ +struct usb_linux_config_descriptor { +	unsigned char	bLength; +	unsigned char	bDescriptorType; +	unsigned short	wTotalLength; +	unsigned char	bNumInterfaces; +	unsigned char	bConfigurationValue; +	unsigned char	iConfiguration; +	unsigned char	bmAttributes; +	unsigned char	MaxPower; +} __attribute__ ((packed)); + +#if defined CONFIG_EHCI_DESC_BIG_ENDIAN +#define	ehci_readl(x)		(*((volatile u32 *)(x))) +#define ehci_writel(a, b)	(*((volatile u32 *)(a)) = ((volatile u32)b)) +#else +#define ehci_readl(x)		cpu_to_le32((*((volatile u32 *)(x)))) +#define ehci_writel(a, b)	(*((volatile u32 *)(a)) = \ +					cpu_to_le32(((volatile u32)b))) +#endif + +#if defined CONFIG_EHCI_MMIO_BIG_ENDIAN +#define hc32_to_cpu(x)		be32_to_cpu((x)) +#define cpu_to_hc32(x)		cpu_to_be32((x)) +#else +#define hc32_to_cpu(x)		le32_to_cpu((x)) +#define cpu_to_hc32(x)		cpu_to_le32((x)) +#endif + +#define EHCI_PS_WKOC_E		(1 << 22)	/* RW wake on over current */ +#define EHCI_PS_WKDSCNNT_E	(1 << 21)	/* RW wake on disconnect */ +#define EHCI_PS_WKCNNT_E	(1 << 20)	/* RW wake on connect */ +#define EHCI_PS_PO		(1 << 13)	/* RW port owner */ +#define EHCI_PS_PP		(1 << 12)	/* RW,RO port power */ +#define EHCI_PS_LS		(3 << 10)	/* RO line status */ +#define EHCI_PS_PR		(1 << 8)	/* RW port reset */ +#define EHCI_PS_SUSP		(1 << 7)	/* RW suspend */ +#define EHCI_PS_FPR		(1 << 6)	/* RW force port resume */ +#define EHCI_PS_OCC		(1 << 5)	/* RWC over current change */ +#define EHCI_PS_OCA		(1 << 4)	/* RO over current active */ +#define EHCI_PS_PEC		(1 << 3)	/* RWC port enable change */ +#define EHCI_PS_PE		(1 << 2)	/* RW port enable */ +#define EHCI_PS_CSC		(1 << 1)	/* RWC connect status change */ +#define EHCI_PS_CS		(1 << 0)	/* RO connect status */ +#define EHCI_PS_CLEAR		(EHCI_PS_OCC | EHCI_PS_PEC | EHCI_PS_CSC) + +#define EHCI_PS_IS_LOWSPEED(x)	(((x) & EHCI_PS_LS) == (1 << 10)) + +/* + * Schedule Interface Space. + * + * IMPORTANT: Software must ensure that no interface data structure + * reachable by the EHCI host controller spans a 4K page boundary! + * + * Periodic transfers (i.e. isochronous and interrupt transfers) are + * not supported. + */ + +/* Queue Element Transfer Descriptor (qTD). */ +struct qTD { +	uint32_t qt_next; +#define	QT_NEXT_TERMINATE	1 +	uint32_t qt_altnext; +	uint32_t qt_token; +	uint32_t qt_buffer[5]; +}; + +/* Queue Head (QH). */ +struct QH { +	uint32_t qh_link; +#define	QH_LINK_TERMINATE	1 +#define	QH_LINK_TYPE_ITD	0 +#define	QH_LINK_TYPE_QH		2 +#define	QH_LINK_TYPE_SITD	4 +#define	QH_LINK_TYPE_FSTN	6 +	uint32_t qh_endpt1; +	uint32_t qh_endpt2; +	uint32_t qh_curtd; +	struct qTD qh_overlay; +	/* +	 * Add dummy fill value to make the size of this struct +	 * aligned to 32 bytes +	 */ +	uint8_t fill[16]; +}; + +/* Low level init functions */ +int ehci_hcd_init(void); +int ehci_hcd_stop(void); + +#endif /* USB_EHCI_H */ diff --git a/drivers/usb/host/isp116x-hcd.c b/drivers/usb/host/isp116x-hcd.c new file mode 100644 index 000000000..348e404fe --- /dev/null +++ b/drivers/usb/host/isp116x-hcd.c @@ -0,0 +1,1441 @@ +/* + * ISP116x HCD (Host Controller Driver) for u-boot. + * + * Copyright (C) 2006-2007 Rodolfo Giometti <giometti@linux.it> + * Copyright (C) 2006-2007 Eurotech S.p.A. <info@eurotech.it> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + * + * Derived in part from the SL811 HCD driver "u-boot/drivers/usb/sl811_usb.c" + * (original copyright message follows): + * + *    (C) Copyright 2004 + *    Wolfgang Denk, DENX Software Engineering, wd@denx.de. + * + *    This code is based on linux driver for sl811hs chip, source at + *    drivers/usb/host/sl811.c: + * + *    SL811 Host Controller Interface driver for USB. + * + *    Copyright (c) 2003/06, Courage Co., Ltd. + * + *    Based on: + *         1.uhci.c by Linus Torvalds, Johannes Erdfelt, Randy Dunlap, + *           Georg Acher, Deti Fliegl, Thomas Sailer, Roman Weissgaerber, + *           Adam Richter, Gregory P. Smith; + *         2.Original SL811 driver (hc_sl811.o) by Pei Liu <pbl@cypress.com> + *         3.Rewrited as sl811.o by Yin Aihua <yinah:couragetech.com.cn> + * + *    [[GNU/GPL disclaimer]] + * + * and in part from AU1x00 OHCI HCD driver "u-boot/cpu/mips/au1x00_usb_ohci.c" + * (original copyright message follows): + * + *    URB OHCI HCD (Host Controller Driver) for USB on the AU1x00. + * + *    (C) Copyright 2003 + *    Gary Jennejohn, DENX Software Engineering <gj@denx.de> + * + *    [[GNU/GPL disclaimer]] + * + *    Note: Part of this code has been derived from linux + */ + +#include <common.h> +#include <asm/io.h> +#include <usb.h> +#include <malloc.h> +#include <linux/list.h> + +/* + * ISP116x chips require certain delays between accesses to its + * registers. The following timing options exist. + * + * 1. Configure your memory controller (the best) + * 2. Use ndelay (easiest, poorest). For that, enable the following macro. + * + * Value is in microseconds. + */ +#ifdef ISP116X_HCD_USE_UDELAY +#define UDELAY		1 +#endif + +/* + * On some (slowly?) machines an extra delay after data packing into + * controller's FIFOs is required, * otherwise you may get the following + * error: + * + *   uboot> usb start + *   (Re)start USB... + *   USB:   scanning bus for devices... isp116x: isp116x_submit_job: CTL:TIMEOUT + *   isp116x: isp116x_submit_job: ****** FIFO not ready! ****** + * + *         USB device not responding, giving up (status=4) + *         isp116x: isp116x_submit_job: ****** FIFO not empty! ****** + *         isp116x: isp116x_submit_job: ****** FIFO not empty! ****** + *         isp116x: isp116x_submit_job: ****** FIFO not empty! ****** + *         3 USB Device(s) found + *                scanning bus for storage devices... 0 Storage Device(s) found + * + * Value is in milliseconds. + */ +#ifdef ISP116X_HCD_USE_EXTRA_DELAY +#define EXTRA_DELAY	2 +#endif + +/* + * Enable the following defines if you wish enable debugging messages. + */ +#undef DEBUG			/* enable debugging messages */ +#undef TRACE			/* enable tracing code */ +#undef VERBOSE			/* verbose debugging messages */ + +#include "isp116x.h" + +#define DRIVER_VERSION	"08 Jan 2007" +static const char hcd_name[] = "isp116x-hcd"; + +struct isp116x isp116x_dev; +struct isp116x_platform_data isp116x_board; +static int got_rhsc;		/* root hub status change */ +struct usb_device *devgone;	/* device which was disconnected */ +static int rh_devnum;		/* address of Root Hub endpoint */ + +/* ------------------------------------------------------------------------- */ + +#define ALIGN(x,a)	(((x)+(a)-1UL)&~((a)-1UL)) +#define min_t(type,x,y)	\ +	({ type __x = (x); type __y = (y); __x < __y ? __x : __y; }) + +/* ------------------------------------------------------------------------- */ + +static int isp116x_reset(struct isp116x *isp116x); + +/* --- Debugging functions ------------------------------------------------- */ + +#define isp116x_show_reg(d, r) {				\ +	if ((r) < 0x20) {					\ +		DBG("%-12s[%02x]: %08x", #r,			\ +			r, isp116x_read_reg32(d, r));		\ +	} else {						\ +		DBG("%-12s[%02x]:     %04x", #r,		\ +			r, isp116x_read_reg16(d, r));		\ +	}							\ +} + +#define isp116x_show_regs(d) {					\ +	isp116x_show_reg(d, HCREVISION);			\ +	isp116x_show_reg(d, HCCONTROL);				\ +	isp116x_show_reg(d, HCCMDSTAT);				\ +	isp116x_show_reg(d, HCINTSTAT);				\ +	isp116x_show_reg(d, HCINTENB);				\ +	isp116x_show_reg(d, HCFMINTVL);				\ +	isp116x_show_reg(d, HCFMREM);				\ +	isp116x_show_reg(d, HCFMNUM);				\ +	isp116x_show_reg(d, HCLSTHRESH);			\ +	isp116x_show_reg(d, HCRHDESCA);				\ +	isp116x_show_reg(d, HCRHDESCB);				\ +	isp116x_show_reg(d, HCRHSTATUS);			\ +	isp116x_show_reg(d, HCRHPORT1);				\ +	isp116x_show_reg(d, HCRHPORT2);				\ +	isp116x_show_reg(d, HCHWCFG);				\ +	isp116x_show_reg(d, HCDMACFG);				\ +	isp116x_show_reg(d, HCXFERCTR);				\ +	isp116x_show_reg(d, HCuPINT);				\ +	isp116x_show_reg(d, HCuPINTENB);			\ +	isp116x_show_reg(d, HCCHIPID);				\ +	isp116x_show_reg(d, HCSCRATCH);				\ +	isp116x_show_reg(d, HCITLBUFLEN);			\ +	isp116x_show_reg(d, HCATLBUFLEN);			\ +	isp116x_show_reg(d, HCBUFSTAT);				\ +	isp116x_show_reg(d, HCRDITL0LEN);			\ +	isp116x_show_reg(d, HCRDITL1LEN);			\ +} + +#if defined(TRACE) + +static int isp116x_get_current_frame_number(struct usb_device *usb_dev) +{ +	struct isp116x *isp116x = &isp116x_dev; + +	return isp116x_read_reg32(isp116x, HCFMNUM); +} + +static void dump_msg(struct usb_device *dev, unsigned long pipe, void *buffer, +		     int len, char *str) +{ +#if defined(VERBOSE) +	int i; +#endif + +	DBG("%s URB:[%4x] dev:%2d,ep:%2d-%c,type:%s,len:%d stat:%#lx", +	    str, +	    isp116x_get_current_frame_number(dev), +	    usb_pipedevice(pipe), +	    usb_pipeendpoint(pipe), +	    usb_pipeout(pipe) ? 'O' : 'I', +	    usb_pipetype(pipe) < 2 ? +	    (usb_pipeint(pipe) ? +	     "INTR" : "ISOC") : +	    (usb_pipecontrol(pipe) ? "CTRL" : "BULK"), len, dev->status); +#if defined(VERBOSE) +	if (len > 0 && buffer) { +		printf(__FILE__ ": data(%d):", len); +		for (i = 0; i < 16 && i < len; i++) +			printf(" %02x", ((__u8 *) buffer)[i]); +		printf("%s\n", i < len ? "..." : ""); +	} +#endif +} + +#define PTD_DIR_STR(ptd)  ({char __c;		\ +	switch(PTD_GET_DIR(ptd)){		\ +	case 0:  __c = 's'; break;		\ +	case 1:  __c = 'o'; break;		\ +	default: __c = 'i'; break;		\ +	}; __c;}) + +/* +  Dump PTD info. The code documents the format +  perfectly, right :) +*/ +static inline void dump_ptd(struct ptd *ptd) +{ +#if defined(VERBOSE) +	int k; +#endif + +	DBG("PTD(ext) : cc:%x %d%c%d %d,%d,%d t:%x %x%x%x", +	    PTD_GET_CC(ptd), +	    PTD_GET_FA(ptd), PTD_DIR_STR(ptd), PTD_GET_EP(ptd), +	    PTD_GET_COUNT(ptd), PTD_GET_LEN(ptd), PTD_GET_MPS(ptd), +	    PTD_GET_TOGGLE(ptd), +	    PTD_GET_ACTIVE(ptd), PTD_GET_SPD(ptd), PTD_GET_LAST(ptd)); +#if defined(VERBOSE) +	printf("isp116x: %s: PTD(byte): ", __FUNCTION__); +	for (k = 0; k < sizeof(struct ptd); ++k) +		printf("%02x ", ((u8 *) ptd)[k]); +	printf("\n"); +#endif +} + +static inline void dump_ptd_data(struct ptd *ptd, u8 * buf, int type) +{ +#if defined(VERBOSE) +	int k; + +	if (type == 0 /* 0ut data */ ) { +		printf("isp116x: %s: out data: ", __FUNCTION__); +		for (k = 0; k < PTD_GET_LEN(ptd); ++k) +			printf("%02x ", ((u8 *) buf)[k]); +		printf("\n"); +	} +	if (type == 1 /* 1n data */ ) { +		printf("isp116x: %s: in data: ", __FUNCTION__); +		for (k = 0; k < PTD_GET_COUNT(ptd); ++k) +			printf("%02x ", ((u8 *) buf)[k]); +		printf("\n"); +	} + +	if (PTD_GET_LAST(ptd)) +		DBG("--- last PTD ---"); +#endif +} + +#else + +#define dump_msg(dev, pipe, buffer, len, str)			do { } while (0) +#define dump_pkt(dev, pipe, buffer, len, setup, str, small)	do {} while (0) + +#define dump_ptd(ptd)			do {} while (0) +#define dump_ptd_data(ptd, buf, type)	do {} while (0) + +#endif + +/* --- Virtual Root Hub ---------------------------------------------------- */ + +/* Device descriptor */ +static __u8 root_hub_dev_des[] = { +	0x12,			/*  __u8  bLength; */ +	0x01,			/*  __u8  bDescriptorType; Device */ +	0x10,			/*  __u16 bcdUSB; v1.1 */ +	0x01, +	0x09,			/*  __u8  bDeviceClass; HUB_CLASSCODE */ +	0x00,			/*  __u8  bDeviceSubClass; */ +	0x00,			/*  __u8  bDeviceProtocol; */ +	0x08,			/*  __u8  bMaxPacketSize0; 8 Bytes */ +	0x00,			/*  __u16 idVendor; */ +	0x00, +	0x00,			/*  __u16 idProduct; */ +	0x00, +	0x00,			/*  __u16 bcdDevice; */ +	0x00, +	0x00,			/*  __u8  iManufacturer; */ +	0x01,			/*  __u8  iProduct; */ +	0x00,			/*  __u8  iSerialNumber; */ +	0x01			/*  __u8  bNumConfigurations; */ +}; + +/* Configuration descriptor */ +static __u8 root_hub_config_des[] = { +	0x09,			/*  __u8  bLength; */ +	0x02,			/*  __u8  bDescriptorType; Configuration */ +	0x19,			/*  __u16 wTotalLength; */ +	0x00, +	0x01,			/*  __u8  bNumInterfaces; */ +	0x01,			/*  __u8  bConfigurationValue; */ +	0x00,			/*  __u8  iConfiguration; */ +	0x40,			/*  __u8  bmAttributes; +				   Bit 7: Bus-powered, 6: Self-powered, 5 Remote-wakwup, 4..0: resvd */ +	0x00,			/*  __u8  MaxPower; */ + +	/* interface */ +	0x09,			/*  __u8  if_bLength; */ +	0x04,			/*  __u8  if_bDescriptorType; Interface */ +	0x00,			/*  __u8  if_bInterfaceNumber; */ +	0x00,			/*  __u8  if_bAlternateSetting; */ +	0x01,			/*  __u8  if_bNumEndpoints; */ +	0x09,			/*  __u8  if_bInterfaceClass; HUB_CLASSCODE */ +	0x00,			/*  __u8  if_bInterfaceSubClass; */ +	0x00,			/*  __u8  if_bInterfaceProtocol; */ +	0x00,			/*  __u8  if_iInterface; */ + +	/* endpoint */ +	0x07,			/*  __u8  ep_bLength; */ +	0x05,			/*  __u8  ep_bDescriptorType; Endpoint */ +	0x81,			/*  __u8  ep_bEndpointAddress; IN Endpoint 1 */ +	0x03,			/*  __u8  ep_bmAttributes; Interrupt */ +	0x00,			/*  __u16 ep_wMaxPacketSize; ((MAX_ROOT_PORTS + 1) / 8 */ +	0x02, +	0xff			/*  __u8  ep_bInterval; 255 ms */ +}; + +static unsigned char root_hub_str_index0[] = { +	0x04,			/*  __u8  bLength; */ +	0x03,			/*  __u8  bDescriptorType; String-descriptor */ +	0x09,			/*  __u8  lang ID */ +	0x04,			/*  __u8  lang ID */ +}; + +static unsigned char root_hub_str_index1[] = { +	0x22,			/*  __u8  bLength; */ +	0x03,			/*  __u8  bDescriptorType; String-descriptor */ +	'I',			/*  __u8  Unicode */ +	0,			/*  __u8  Unicode */ +	'S',			/*  __u8  Unicode */ +	0,			/*  __u8  Unicode */ +	'P',			/*  __u8  Unicode */ +	0,			/*  __u8  Unicode */ +	'1',			/*  __u8  Unicode */ +	0,			/*  __u8  Unicode */ +	'1',			/*  __u8  Unicode */ +	0,			/*  __u8  Unicode */ +	'6',			/*  __u8  Unicode */ +	0,			/*  __u8  Unicode */ +	'x',			/*  __u8  Unicode */ +	0,			/*  __u8  Unicode */ +	' ',			/*  __u8  Unicode */ +	0,			/*  __u8  Unicode */ +	'R',			/*  __u8  Unicode */ +	0,			/*  __u8  Unicode */ +	'o',			/*  __u8  Unicode */ +	0,			/*  __u8  Unicode */ +	'o',			/*  __u8  Unicode */ +	0,			/*  __u8  Unicode */ +	't',			/*  __u8  Unicode */ +	0,			/*  __u8  Unicode */ +	' ',			/*  __u8  Unicode */ +	0,			/*  __u8  Unicode */ +	'H',			/*  __u8  Unicode */ +	0,			/*  __u8  Unicode */ +	'u',			/*  __u8  Unicode */ +	0,			/*  __u8  Unicode */ +	'b',			/*  __u8  Unicode */ +	0,			/*  __u8  Unicode */ +}; + +/* + * Hub class-specific descriptor is constructed dynamically + */ + +/* --- Virtual root hub management functions ------------------------------- */ + +static int rh_check_port_status(struct isp116x *isp116x) +{ +	u32 temp, ndp, i; +	int res; + +	res = -1; +	temp = isp116x_read_reg32(isp116x, HCRHSTATUS); +	ndp = (temp & RH_A_NDP); +	for (i = 0; i < ndp; i++) { +		temp = isp116x_read_reg32(isp116x, HCRHPORT1 + i); +		/* check for a device disconnect */ +		if (((temp & (RH_PS_PESC | RH_PS_CSC)) == +		     (RH_PS_PESC | RH_PS_CSC)) && ((temp & RH_PS_CCS) == 0)) { +			res = i; +			break; +		} +	} +	return res; +} + +/* --- HC management functions --------------------------------------------- */ + +/* Write len bytes to fifo, pad till 32-bit boundary + */ +static void write_ptddata_to_fifo(struct isp116x *isp116x, void *buf, int len) +{ +	u8 *dp = (u8 *) buf; +	u16 *dp2 = (u16 *) buf; +	u16 w; +	int quot = len % 4; + +	if ((unsigned long)dp2 & 1) { +		/* not aligned */ +		for (; len > 1; len -= 2) { +			w = *dp++; +			w |= *dp++ << 8; +			isp116x_raw_write_data16(isp116x, w); +		} +		if (len) +			isp116x_write_data16(isp116x, (u16) * dp); +	} else { +		/* aligned */ +		for (; len > 1; len -= 2) +			isp116x_raw_write_data16(isp116x, *dp2++); +		if (len) +			isp116x_write_data16(isp116x, 0xff & *((u8 *) dp2)); +	} +	if (quot == 1 || quot == 2) +		isp116x_raw_write_data16(isp116x, 0); +} + +/* Read len bytes from fifo and then read till 32-bit boundary + */ +static void read_ptddata_from_fifo(struct isp116x *isp116x, void *buf, int len) +{ +	u8 *dp = (u8 *) buf; +	u16 *dp2 = (u16 *) buf; +	u16 w; +	int quot = len % 4; + +	if ((unsigned long)dp2 & 1) { +		/* not aligned */ +		for (; len > 1; len -= 2) { +			w = isp116x_raw_read_data16(isp116x); +			*dp++ = w & 0xff; +			*dp++ = (w >> 8) & 0xff; +		} +		if (len) +			*dp = 0xff & isp116x_read_data16(isp116x); +	} else { +		/* aligned */ +		for (; len > 1; len -= 2) +			*dp2++ = isp116x_raw_read_data16(isp116x); +		if (len) +			*(u8 *) dp2 = 0xff & isp116x_read_data16(isp116x); +	} +	if (quot == 1 || quot == 2) +		isp116x_raw_read_data16(isp116x); +} + +/* Write PTD's and data for scheduled transfers into the fifo ram. + * Fifo must be empty and ready */ +static void pack_fifo(struct isp116x *isp116x, struct usb_device *dev, +		      unsigned long pipe, struct ptd *ptd, int n, void *data, +		      int len) +{ +	int buflen = n * sizeof(struct ptd) + len; +	int i, done; + +	DBG("--- pack buffer %p - %d bytes (fifo %d) ---", data, len, buflen); + +	isp116x_write_reg16(isp116x, HCuPINT, HCuPINT_AIIEOT); +	isp116x_write_reg16(isp116x, HCXFERCTR, buflen); +	isp116x_write_addr(isp116x, HCATLPORT | ISP116x_WRITE_OFFSET); + +	done = 0; +	for (i = 0; i < n; i++) { +		DBG("i=%d - done=%d - len=%d", i, done, PTD_GET_LEN(&ptd[i])); + +		dump_ptd(&ptd[i]); +		isp116x_write_data16(isp116x, ptd[i].count); +		isp116x_write_data16(isp116x, ptd[i].mps); +		isp116x_write_data16(isp116x, ptd[i].len); +		isp116x_write_data16(isp116x, ptd[i].faddr); + +		dump_ptd_data(&ptd[i], (__u8 *) data + done, 0); +		write_ptddata_to_fifo(isp116x, +				      (__u8 *) data + done, +				      PTD_GET_LEN(&ptd[i])); + +		done += PTD_GET_LEN(&ptd[i]); +	} +} + +/* Read the processed PTD's and data from fifo ram back to URBs' buffers. + * Fifo must be full and done */ +static int unpack_fifo(struct isp116x *isp116x, struct usb_device *dev, +		       unsigned long pipe, struct ptd *ptd, int n, void *data, +		       int len) +{ +	int buflen = n * sizeof(struct ptd) + len; +	int i, done, cc, ret; + +	isp116x_write_reg16(isp116x, HCuPINT, HCuPINT_AIIEOT); +	isp116x_write_reg16(isp116x, HCXFERCTR, buflen); +	isp116x_write_addr(isp116x, HCATLPORT); + +	ret = TD_CC_NOERROR; +	done = 0; +	for (i = 0; i < n; i++) { +		DBG("i=%d - done=%d - len=%d", i, done, PTD_GET_LEN(&ptd[i])); + +		ptd[i].count = isp116x_read_data16(isp116x); +		ptd[i].mps = isp116x_read_data16(isp116x); +		ptd[i].len = isp116x_read_data16(isp116x); +		ptd[i].faddr = isp116x_read_data16(isp116x); +		dump_ptd(&ptd[i]); + +		read_ptddata_from_fifo(isp116x, +				       (__u8 *) data + done, +				       PTD_GET_LEN(&ptd[i])); +		dump_ptd_data(&ptd[i], (__u8 *) data + done, 1); + +		done += PTD_GET_LEN(&ptd[i]); + +		cc = PTD_GET_CC(&ptd[i]); + +		/* Data underrun means basically that we had more buffer space than +		 * the function had data. It is perfectly normal but upper levels have +		 * to know how much we actually transferred. +		 */ +		if (cc == TD_NOTACCESSED || +				(cc != TD_CC_NOERROR && (ret == TD_CC_NOERROR || ret == TD_DATAUNDERRUN))) +			ret = cc; +	} + +	DBG("--- unpack buffer %p - %d bytes (fifo %d) ---", data, len, buflen); + +	return ret; +} + +/* Interrupt handling + */ +static int isp116x_interrupt(struct isp116x *isp116x) +{ +	u16 irqstat; +	u32 intstat; +	int ret = 0; + +	isp116x_write_reg16(isp116x, HCuPINTENB, 0); +	irqstat = isp116x_read_reg16(isp116x, HCuPINT); +	isp116x_write_reg16(isp116x, HCuPINT, irqstat); +	DBG(">>>>>> irqstat %x <<<<<<", irqstat); + +	if (irqstat & HCuPINT_ATL) { +		DBG(">>>>>> HCuPINT_ATL <<<<<<"); +		udelay(500); +		ret = 1; +	} + +	if (irqstat & HCuPINT_OPR) { +		intstat = isp116x_read_reg32(isp116x, HCINTSTAT); +		isp116x_write_reg32(isp116x, HCINTSTAT, intstat); +		DBG(">>>>>> HCuPINT_OPR %x <<<<<<", intstat); + +		if (intstat & HCINT_UE) { +			ERR("unrecoverable error, controller disabled"); + +			/* FIXME: be optimistic, hope that bug won't repeat +			 * often. Make some non-interrupt context restart the +			 * controller. Count and limit the retries though; +			 * either hardware or software errors can go forever... +			 */ +			isp116x_reset(isp116x); +			ret = -1; +			return -1; +		} + +		if (intstat & HCINT_RHSC) { +			got_rhsc = 1; +			ret = 1; +			/* When root hub or any of its ports is going +			   to come out of suspend, it may take more +			   than 10ms for status bits to stabilize. */ +			wait_ms(20); +		} + +		if (intstat & HCINT_SO) { +			ERR("schedule overrun"); +			ret = -1; +		} + +		irqstat &= ~HCuPINT_OPR; +	} + +	return ret; +} + +/* With one PTD we can transfer almost 1K in one go; + * HC does the splitting into endpoint digestible transactions + */ +struct ptd ptd[1]; + +static inline int max_transfer_len(struct usb_device *dev, unsigned long pipe) +{ +	unsigned mpck = usb_maxpacket(dev, pipe); + +	/* One PTD can transfer 1023 bytes but try to always +	 * transfer multiples of endpoint buffer size +	 */ +	return 1023 / mpck * mpck; +} + +/* Do an USB transfer + */ +static int isp116x_submit_job(struct usb_device *dev, unsigned long pipe, +			      int dir, void *buffer, int len) +{ +	struct isp116x *isp116x = &isp116x_dev; +	int type = usb_pipetype(pipe); +	int epnum = usb_pipeendpoint(pipe); +	int max = usb_maxpacket(dev, pipe); +	int dir_out = usb_pipeout(pipe); +	int speed_low = usb_pipeslow(pipe); +	int i, done = 0, stat, timeout, cc; + +	/* 500 frames or 0.5s timeout when function is busy and NAKs transactions for a while */ +	int retries = 500; + +	DBG("------------------------------------------------"); +	dump_msg(dev, pipe, buffer, len, "SUBMIT"); +	DBG("------------------------------------------------"); + +	if (len >= 1024) { +		ERR("Too big job"); +		dev->status = USB_ST_CRC_ERR; +		return -1; +	} + +	if (isp116x->disabled) { +		ERR("EPIPE"); +		dev->status = USB_ST_CRC_ERR; +		return -1; +	} + +	/* device pulled? Shortcut the action. */ +	if (devgone == dev) { +		ERR("ENODEV"); +		dev->status = USB_ST_CRC_ERR; +		return USB_ST_CRC_ERR; +	} + +	if (!max) { +		ERR("pipesize for pipe %lx is zero", pipe); +		dev->status = USB_ST_CRC_ERR; +		return -1; +	} + +	if (type == PIPE_ISOCHRONOUS) { +		ERR("isochronous transfers not supported"); +		dev->status = USB_ST_CRC_ERR; +		return -1; +	} + +	/* FIFO not empty? */ +	if (isp116x_read_reg16(isp116x, HCBUFSTAT) & HCBUFSTAT_ATL_FULL) { +		ERR("****** FIFO not empty! ******"); +		dev->status = USB_ST_BUF_ERR; +		return -1; +	} + +      retry: +	isp116x_write_reg32(isp116x, HCINTSTAT, 0xff); + +	/* Prepare the PTD data */ +	ptd->count = PTD_CC_MSK | PTD_ACTIVE_MSK | +		PTD_TOGGLE(usb_gettoggle(dev, epnum, dir_out)); +	ptd->mps = PTD_MPS(max) | PTD_SPD(speed_low) | PTD_EP(epnum) | PTD_LAST_MSK; +	ptd->len = PTD_LEN(len) | PTD_DIR(dir); +	ptd->faddr = PTD_FA(usb_pipedevice(pipe)); + +retry_same: +	/* Pack data into FIFO ram */ +	pack_fifo(isp116x, dev, pipe, ptd, 1, buffer, len); +#ifdef EXTRA_DELAY +	wait_ms(EXTRA_DELAY); +#endif + +	/* Start the data transfer */ + +	/* Allow more time for a BULK device to react - some are slow */ +	if (usb_pipebulk(pipe)) +		timeout = 5000; +	else +		timeout = 100; + +	/* Wait for it to complete */ +	for (;;) { +		/* Check whether the controller is done */ +		stat = isp116x_interrupt(isp116x); + +		if (stat < 0) { +			dev->status = USB_ST_CRC_ERR; +			break; +		} +		if (stat > 0) +			break; + +		/* Check the timeout */ +		if (--timeout) +			udelay(1); +		else { +			ERR("CTL:TIMEOUT "); +			stat = USB_ST_CRC_ERR; +			break; +		} +	} + +	/* We got an Root Hub Status Change interrupt */ +	if (got_rhsc) { +		isp116x_show_regs(isp116x); + +		got_rhsc = 0; + +		/* Abuse timeout */ +		timeout = rh_check_port_status(isp116x); +		if (timeout >= 0) { +			/* +			 * FIXME! NOTE! AAAARGH! +			 * This is potentially dangerous because it assumes +			 * that only one device is ever plugged in! +			 */ +			devgone = dev; +		} +	} + +	/* Ok, now we can read transfer status */ + +	/* FIFO not ready? */ +	if (!(isp116x_read_reg16(isp116x, HCBUFSTAT) & HCBUFSTAT_ATL_DONE)) { +		ERR("****** FIFO not ready! ******"); +		dev->status = USB_ST_BUF_ERR; +		return -1; +	} + +	/* Unpack data from FIFO ram */ +	cc = unpack_fifo(isp116x, dev, pipe, ptd, 1, buffer, len); + +	i = PTD_GET_COUNT(ptd); +	done += i; +	buffer += i; +	len -= i; + +	/* There was some kind of real problem; Prepare the PTD again +	 * and retry from the failed transaction on +	 */ +	if (cc && cc != TD_NOTACCESSED && cc != TD_DATAUNDERRUN) { +		if (retries >= 100) { +			retries -= 100; +			/* The chip will have toggled the toggle bit for the failed +			 * transaction too. We have to toggle it back. +			 */ +			usb_settoggle(dev, epnum, dir_out, !PTD_GET_TOGGLE(ptd)); +			goto retry; +		} +	} +	/* "Normal" errors; TD_NOTACCESSED would mean in effect that the function have NAKed +	 * the transactions from the first on for the whole frame. It may be busy and we retry +	 * with the same PTD. PTD_ACTIVE (and not TD_NOTACCESSED) would mean that some of the +	 * PTD didn't make it because the function was busy or the frame ended before the PTD +	 * finished. We prepare the rest of the data and try again. +	 */ +	else if (cc == TD_NOTACCESSED || PTD_GET_ACTIVE(ptd) || (cc != TD_DATAUNDERRUN && PTD_GET_COUNT(ptd) < PTD_GET_LEN(ptd))) { +		if (retries) { +			--retries; +			if (cc == TD_NOTACCESSED && PTD_GET_ACTIVE(ptd) && !PTD_GET_COUNT(ptd)) goto retry_same; +			usb_settoggle(dev, epnum, dir_out, PTD_GET_TOGGLE(ptd)); +			goto retry; +		} +	} + +	if (cc != TD_CC_NOERROR && cc != TD_DATAUNDERRUN) { +		DBG("****** completition code error %x ******", cc); +		switch (cc) { +		case TD_CC_BITSTUFFING: +			dev->status = USB_ST_BIT_ERR; +			break; +		case TD_CC_STALL: +			dev->status = USB_ST_STALLED; +			break; +		case TD_BUFFEROVERRUN: +		case TD_BUFFERUNDERRUN: +			dev->status = USB_ST_BUF_ERR; +			break; +		default: +			dev->status = USB_ST_CRC_ERR; +		} +		return -cc; +	} +	else usb_settoggle(dev, epnum, dir_out, PTD_GET_TOGGLE(ptd)); + +	dump_msg(dev, pipe, buffer, len, "SUBMIT(ret)"); + +	dev->status = 0; +	return done; +} + +/* Adapted from au1x00_usb_ohci.c + */ +static int isp116x_submit_rh_msg(struct usb_device *dev, unsigned long pipe, +				 void *buffer, int transfer_len, +				 struct devrequest *cmd) +{ +	struct isp116x *isp116x = &isp116x_dev; +	u32 tmp = 0; + +	int leni = transfer_len; +	int len = 0; +	int stat = 0; +	u32 datab[4]; +	u8 *data_buf = (u8 *) datab; +	u16 bmRType_bReq; +	u16 wValue; +	u16 wIndex; +	u16 wLength; + +	if (usb_pipeint(pipe)) { +		INFO("Root-Hub submit IRQ: NOT implemented"); +		return 0; +	} + +	bmRType_bReq = cmd->requesttype | (cmd->request << 8); +	wValue = swap_16(cmd->value); +	wIndex = swap_16(cmd->index); +	wLength = swap_16(cmd->length); + +	DBG("--- HUB ----------------------------------------"); +	DBG("submit rh urb, req=%x val=%#x index=%#x len=%d", +	    bmRType_bReq, wValue, wIndex, wLength); +	dump_msg(dev, pipe, buffer, transfer_len, "RH"); +	DBG("------------------------------------------------"); + +	switch (bmRType_bReq) { +	case RH_GET_STATUS: +		DBG("RH_GET_STATUS"); + +		*(__u16 *) data_buf = swap_16(1); +		len = 2; +		break; + +	case RH_GET_STATUS | RH_INTERFACE: +		DBG("RH_GET_STATUS | RH_INTERFACE"); + +		*(__u16 *) data_buf = swap_16(0); +		len = 2; +		break; + +	case RH_GET_STATUS | RH_ENDPOINT: +		DBG("RH_GET_STATUS | RH_ENDPOINT"); + +		*(__u16 *) data_buf = swap_16(0); +		len = 2; +		break; + +	case RH_GET_STATUS | RH_CLASS: +		DBG("RH_GET_STATUS | RH_CLASS"); + +		tmp = isp116x_read_reg32(isp116x, HCRHSTATUS); + +		*(__u32 *) data_buf = swap_32(tmp & ~(RH_HS_CRWE | RH_HS_DRWE)); +		len = 4; +		break; + +	case RH_GET_STATUS | RH_OTHER | RH_CLASS: +		DBG("RH_GET_STATUS | RH_OTHER | RH_CLASS"); + +		tmp = isp116x_read_reg32(isp116x, HCRHPORT1 + wIndex - 1); +		*(__u32 *) data_buf = swap_32(tmp); +		isp116x_show_regs(isp116x); +		len = 4; +		break; + +	case RH_CLEAR_FEATURE | RH_ENDPOINT: +		DBG("RH_CLEAR_FEATURE | RH_ENDPOINT"); + +		switch (wValue) { +		case RH_ENDPOINT_STALL: +			DBG("C_HUB_ENDPOINT_STALL"); +			len = 0; +			break; +		} +		break; + +	case RH_CLEAR_FEATURE | RH_CLASS: +		DBG("RH_CLEAR_FEATURE | RH_CLASS"); + +		switch (wValue) { +		case RH_C_HUB_LOCAL_POWER: +			DBG("C_HUB_LOCAL_POWER"); +			len = 0; +			break; + +		case RH_C_HUB_OVER_CURRENT: +			DBG("C_HUB_OVER_CURRENT"); +			isp116x_write_reg32(isp116x, HCRHSTATUS, RH_HS_OCIC); +			len = 0; +			break; +		} +		break; + +	case RH_CLEAR_FEATURE | RH_OTHER | RH_CLASS: +		DBG("RH_CLEAR_FEATURE | RH_OTHER | RH_CLASS"); + +		switch (wValue) { +		case RH_PORT_ENABLE: +			isp116x_write_reg32(isp116x, HCRHPORT1 + wIndex - 1, +					    RH_PS_CCS); +			len = 0; +			break; + +		case RH_PORT_SUSPEND: +			isp116x_write_reg32(isp116x, HCRHPORT1 + wIndex - 1, +					    RH_PS_POCI); +			len = 0; +			break; + +		case RH_PORT_POWER: +			isp116x_write_reg32(isp116x, HCRHPORT1 + wIndex - 1, +					    RH_PS_LSDA); +			len = 0; +			break; + +		case RH_C_PORT_CONNECTION: +			isp116x_write_reg32(isp116x, HCRHPORT1 + wIndex - 1, +					    RH_PS_CSC); +			len = 0; +			break; + +		case RH_C_PORT_ENABLE: +			isp116x_write_reg32(isp116x, HCRHPORT1 + wIndex - 1, +					    RH_PS_PESC); +			len = 0; +			break; + +		case RH_C_PORT_SUSPEND: +			isp116x_write_reg32(isp116x, HCRHPORT1 + wIndex - 1, +					    RH_PS_PSSC); +			len = 0; +			break; + +		case RH_C_PORT_OVER_CURRENT: +			isp116x_write_reg32(isp116x, HCRHPORT1 + wIndex - 1, +					    RH_PS_POCI); +			len = 0; +			break; + +		case RH_C_PORT_RESET: +			isp116x_write_reg32(isp116x, HCRHPORT1 + wIndex - 1, +					    RH_PS_PRSC); +			len = 0; +			break; + +		default: +			ERR("invalid wValue"); +			stat = USB_ST_STALLED; +		} + +		isp116x_show_regs(isp116x); + +		break; + +	case RH_SET_FEATURE | RH_OTHER | RH_CLASS: +		DBG("RH_SET_FEATURE | RH_OTHER | RH_CLASS"); + +		switch (wValue) { +		case RH_PORT_SUSPEND: +			isp116x_write_reg32(isp116x, HCRHPORT1 + wIndex - 1, +					    RH_PS_PSS); +			len = 0; +			break; + +		case RH_PORT_RESET: +			/* Spin until any current reset finishes */ +			while (1) { +				tmp = +				    isp116x_read_reg32(isp116x, +						       HCRHPORT1 + wIndex - 1); +				if (!(tmp & RH_PS_PRS)) +					break; +				wait_ms(1); +			} +			isp116x_write_reg32(isp116x, HCRHPORT1 + wIndex - 1, +					    RH_PS_PRS); +			wait_ms(10); + +			len = 0; +			break; + +		case RH_PORT_POWER: +			isp116x_write_reg32(isp116x, HCRHPORT1 + wIndex - 1, +					    RH_PS_PPS); +			len = 0; +			break; + +		case RH_PORT_ENABLE: +			isp116x_write_reg32(isp116x, HCRHPORT1 + wIndex - 1, +					    RH_PS_PES); +			len = 0; +			break; + +		default: +			ERR("invalid wValue"); +			stat = USB_ST_STALLED; +		} + +		isp116x_show_regs(isp116x); + +		break; + +	case RH_SET_ADDRESS: +		DBG("RH_SET_ADDRESS"); + +		rh_devnum = wValue; +		len = 0; +		break; + +	case RH_GET_DESCRIPTOR: +		DBG("RH_GET_DESCRIPTOR: %x, %d", wValue, wLength); + +		switch (wValue) { +		case (USB_DT_DEVICE << 8):	/* device descriptor */ +			len = min_t(unsigned int, +				    leni, min_t(unsigned int, +						sizeof(root_hub_dev_des), +						wLength)); +			data_buf = root_hub_dev_des; +			break; + +		case (USB_DT_CONFIG << 8):	/* configuration descriptor */ +			len = min_t(unsigned int, +				    leni, min_t(unsigned int, +						sizeof(root_hub_config_des), +						wLength)); +			data_buf = root_hub_config_des; +			break; + +		case ((USB_DT_STRING << 8) | 0x00):	/* string 0 descriptors */ +			len = min_t(unsigned int, +				    leni, min_t(unsigned int, +						sizeof(root_hub_str_index0), +						wLength)); +			data_buf = root_hub_str_index0; +			break; + +		case ((USB_DT_STRING << 8) | 0x01):	/* string 1 descriptors */ +			len = min_t(unsigned int, +				    leni, min_t(unsigned int, +						sizeof(root_hub_str_index1), +						wLength)); +			data_buf = root_hub_str_index1; +			break; + +		default: +			ERR("invalid wValue"); +			stat = USB_ST_STALLED; +		} + +		break; + +	case RH_GET_DESCRIPTOR | RH_CLASS: +		DBG("RH_GET_DESCRIPTOR | RH_CLASS"); + +		tmp = isp116x_read_reg32(isp116x, HCRHDESCA); + +		data_buf[0] = 0x09;	/* min length; */ +		data_buf[1] = 0x29; +		data_buf[2] = tmp & RH_A_NDP; +		data_buf[3] = 0; +		if (tmp & RH_A_PSM)	/* per-port power switching? */ +			data_buf[3] |= 0x01; +		if (tmp & RH_A_NOCP)	/* no overcurrent reporting? */ +			data_buf[3] |= 0x10; +		else if (tmp & RH_A_OCPM)	/* per-port overcurrent rep? */ +			data_buf[3] |= 0x08; + +		/* Corresponds to data_buf[4-7] */ +		datab[1] = 0; +		data_buf[5] = (tmp & RH_A_POTPGT) >> 24; + +		tmp = isp116x_read_reg32(isp116x, HCRHDESCB); + +		data_buf[7] = tmp & RH_B_DR; +		if (data_buf[2] < 7) +			data_buf[8] = 0xff; +		else { +			data_buf[0] += 2; +			data_buf[8] = (tmp & RH_B_DR) >> 8; +			data_buf[10] = data_buf[9] = 0xff; +		} + +		len = min_t(unsigned int, leni, +			    min_t(unsigned int, data_buf[0], wLength)); +		break; + +	case RH_GET_CONFIGURATION: +		DBG("RH_GET_CONFIGURATION"); + +		*(__u8 *) data_buf = 0x01; +		len = 1; +		break; + +	case RH_SET_CONFIGURATION: +		DBG("RH_SET_CONFIGURATION"); + +		isp116x_write_reg32(isp116x, HCRHSTATUS, RH_HS_LPSC); +		len = 0; +		break; + +	default: +		ERR("*** *** *** unsupported root hub command *** *** ***"); +		stat = USB_ST_STALLED; +	} + +	len = min_t(int, len, leni); +	if (buffer != data_buf) +		memcpy(buffer, data_buf, len); + +	dev->act_len = len; +	dev->status = stat; +	DBG("dev act_len %d, status %d", dev->act_len, dev->status); + +	dump_msg(dev, pipe, buffer, transfer_len, "RH(ret)"); + +	return stat; +} + +/* --- Transfer functions -------------------------------------------------- */ + +int submit_int_msg(struct usb_device *dev, unsigned long pipe, void *buffer, +		   int len, int interval) +{ +	DBG("dev=%p pipe=%#lx buf=%p size=%d int=%d", +	    dev, pipe, buffer, len, interval); + +	return -1; +} + +int submit_control_msg(struct usb_device *dev, unsigned long pipe, void *buffer, +		       int len, struct devrequest *setup) +{ +	int devnum = usb_pipedevice(pipe); +	int epnum = usb_pipeendpoint(pipe); +	int max = max_transfer_len(dev, pipe); +	int dir_in = usb_pipein(pipe); +	int done, ret; + +	/* Control message is for the HUB? */ +	if (devnum == rh_devnum) +		return isp116x_submit_rh_msg(dev, pipe, buffer, len, setup); + +	/* Ok, no HUB message so send the message to the device */ + +	/* Setup phase */ +	DBG("--- SETUP PHASE --------------------------------"); +	usb_settoggle(dev, epnum, 1, 0); +	ret = isp116x_submit_job(dev, pipe, +				 PTD_DIR_SETUP, +				 setup, sizeof(struct devrequest)); +	if (ret < 0) { +		DBG("control setup phase error (ret = %d", ret); +		return -1; +	} + +	/* Data phase */ +	DBG("--- DATA PHASE ---------------------------------"); +	done = 0; +	usb_settoggle(dev, epnum, !dir_in, 1); +	while (done < len) { +		ret = isp116x_submit_job(dev, pipe, +					 dir_in ? PTD_DIR_IN : PTD_DIR_OUT, +					 (__u8 *) buffer + done, +					 max > len - done ? len - done : max); +		if (ret < 0) { +			DBG("control data phase error (ret = %d)", ret); +			return -1; +		} +		done += ret; + +		if (dir_in && ret < max)	/* short packet */ +			break; +	} + +	/* Status phase */ +	DBG("--- STATUS PHASE -------------------------------"); +	usb_settoggle(dev, epnum, !dir_in, 1); +	ret = isp116x_submit_job(dev, pipe, +				 !dir_in ? PTD_DIR_IN : PTD_DIR_OUT, NULL, 0); +	if (ret < 0) { +		DBG("control status phase error (ret = %d", ret); +		return -1; +	} + +	dev->act_len = done; + +	dump_msg(dev, pipe, buffer, len, "DEV(ret)"); + +	return done; +} + +int submit_bulk_msg(struct usb_device *dev, unsigned long pipe, void *buffer, +		    int len) +{ +	int dir_out = usb_pipeout(pipe); +	int max = max_transfer_len(dev, pipe); +	int done, ret; + +	DBG("--- BULK ---------------------------------------"); +	DBG("dev=%ld pipe=%ld buf=%p size=%d dir_out=%d", +	    usb_pipedevice(pipe), usb_pipeendpoint(pipe), buffer, len, dir_out); + +	done = 0; +	while (done < len) { +		ret = isp116x_submit_job(dev, pipe, +					 !dir_out ? PTD_DIR_IN : PTD_DIR_OUT, +					 (__u8 *) buffer + done, +					 max > len - done ? len - done : max); +		if (ret < 0) { +			DBG("error on bulk message (ret = %d)", ret); +			return -1; +		} + +		done += ret; + +		if (!dir_out && ret < max)	/* short packet */ +			break; +	} + +	dev->act_len = done; + +	return 0; +} + +/* --- Basic functions ----------------------------------------------------- */ + +static int isp116x_sw_reset(struct isp116x *isp116x) +{ +	int retries = 15; +	int ret = 0; + +	DBG(""); + +	isp116x->disabled = 1; + +	isp116x_write_reg16(isp116x, HCSWRES, HCSWRES_MAGIC); +	isp116x_write_reg32(isp116x, HCCMDSTAT, HCCMDSTAT_HCR); +	while (--retries) { +		/* It usually resets within 1 ms */ +		wait_ms(1); +		if (!(isp116x_read_reg32(isp116x, HCCMDSTAT) & HCCMDSTAT_HCR)) +			break; +	} +	if (!retries) { +		ERR("software reset timeout"); +		ret = -1; +	} +	return ret; +} + +static int isp116x_reset(struct isp116x *isp116x) +{ +	unsigned long t; +	u16 clkrdy = 0; +	int ret, timeout = 15 /* ms */ ; + +	DBG(""); + +	ret = isp116x_sw_reset(isp116x); +	if (ret) +		return ret; + +	for (t = 0; t < timeout; t++) { +		clkrdy = isp116x_read_reg16(isp116x, HCuPINT) & HCuPINT_CLKRDY; +		if (clkrdy) +			break; +		wait_ms(1); +	} +	if (!clkrdy) { +		ERR("clock not ready after %dms", timeout); +		/* After sw_reset the clock won't report to be ready, if +		   H_WAKEUP pin is high. */ +		ERR("please make sure that the H_WAKEUP pin is pulled low!"); +		ret = -1; +	} +	return ret; +} + +static void isp116x_stop(struct isp116x *isp116x) +{ +	u32 val; + +	DBG(""); + +	isp116x_write_reg16(isp116x, HCuPINTENB, 0); + +	/* Switch off ports' power, some devices don't come up +	   after next 'start' without this */ +	val = isp116x_read_reg32(isp116x, HCRHDESCA); +	val &= ~(RH_A_NPS | RH_A_PSM); +	isp116x_write_reg32(isp116x, HCRHDESCA, val); +	isp116x_write_reg32(isp116x, HCRHSTATUS, RH_HS_LPS); + +	isp116x_sw_reset(isp116x); +} + +/* + *  Configure the chip. The chip must be successfully reset by now. + */ +static int isp116x_start(struct isp116x *isp116x) +{ +	struct isp116x_platform_data *board = isp116x->board; +	u32 val; + +	DBG(""); + +	/* Clear interrupt status and disable all interrupt sources */ +	isp116x_write_reg16(isp116x, HCuPINT, 0xff); +	isp116x_write_reg16(isp116x, HCuPINTENB, 0); + +	isp116x_write_reg16(isp116x, HCITLBUFLEN, ISP116x_ITL_BUFSIZE); +	isp116x_write_reg16(isp116x, HCATLBUFLEN, ISP116x_ATL_BUFSIZE); + +	/* Hardware configuration */ +	val = HCHWCFG_DBWIDTH(1); +	if (board->sel15Kres) +		val |= HCHWCFG_15KRSEL; +	/* Remote wakeup won't work without working clock */ +	if (board->remote_wakeup_enable) +		val |= HCHWCFG_CLKNOTSTOP; +	if (board->oc_enable) +		val |= HCHWCFG_ANALOG_OC; +	isp116x_write_reg16(isp116x, HCHWCFG, val); + +	/* --- Root hub configuration */ +	val = (25 << 24) & RH_A_POTPGT; +	/* AN10003_1.pdf recommends RH_A_NPS (no power switching) to +	   be always set. Yet, instead, we request individual port +	   power switching. */ +	val |= RH_A_PSM; +	/* Report overcurrent per port */ +	val |= RH_A_OCPM; +	isp116x_write_reg32(isp116x, HCRHDESCA, val); +	isp116x->rhdesca = isp116x_read_reg32(isp116x, HCRHDESCA); + +	val = RH_B_PPCM; +	isp116x_write_reg32(isp116x, HCRHDESCB, val); +	isp116x->rhdescb = isp116x_read_reg32(isp116x, HCRHDESCB); + +	val = 0; +	if (board->remote_wakeup_enable) +		val |= RH_HS_DRWE; +	isp116x_write_reg32(isp116x, HCRHSTATUS, val); +	isp116x->rhstatus = isp116x_read_reg32(isp116x, HCRHSTATUS); + +	isp116x_write_reg32(isp116x, HCFMINTVL, 0x27782edf); + +	/* Go operational */ +	val = HCCONTROL_USB_OPER; +	if (board->remote_wakeup_enable) +		val |= HCCONTROL_RWE; +	isp116x_write_reg32(isp116x, HCCONTROL, val); + +	/* Disable ports to avoid race in device enumeration */ +	isp116x_write_reg32(isp116x, HCRHPORT1, RH_PS_CCS); +	isp116x_write_reg32(isp116x, HCRHPORT2, RH_PS_CCS); + +	isp116x_show_regs(isp116x); + +	isp116x->disabled = 0; + +	return 0; +} + +/* --- Init functions ------------------------------------------------------ */ + +int isp116x_check_id(struct isp116x *isp116x) +{ +	int val; + +	val = isp116x_read_reg16(isp116x, HCCHIPID); +	if ((val & HCCHIPID_MASK) != HCCHIPID_MAGIC) { +		ERR("invalid chip ID %04x", val); +		return -1; +	} + +	return 0; +} + +int usb_lowlevel_init(void) +{ +	struct isp116x *isp116x = &isp116x_dev; + +	DBG(""); + +	got_rhsc = rh_devnum = 0; + +	/* Init device registers addr */ +	isp116x->addr_reg = (u16 *) ISP116X_HCD_ADDR; +	isp116x->data_reg = (u16 *) ISP116X_HCD_DATA; + +	/* Setup specific board settings */ +#ifdef ISP116X_HCD_SEL15kRES +	isp116x_board.sel15Kres = 1; +#endif +#ifdef ISP116X_HCD_OC_ENABLE +	isp116x_board.oc_enable = 1; +#endif +#ifdef ISP116X_HCD_REMOTE_WAKEUP_ENABLE +	isp116x_board.remote_wakeup_enable = 1; +#endif +	isp116x->board = &isp116x_board; + +	/* Try to get ISP116x silicon chip ID */ +	if (isp116x_check_id(isp116x) < 0) +		return -1; + +	isp116x->disabled = 1; +	isp116x->sleeping = 0; + +	isp116x_reset(isp116x); +	isp116x_start(isp116x); + +	return 0; +} + +int usb_lowlevel_stop(void) +{ +	struct isp116x *isp116x = &isp116x_dev; + +	DBG(""); + +	if (!isp116x->disabled) +		isp116x_stop(isp116x); + +	return 0; +} diff --git a/drivers/usb/host/isp116x.h b/drivers/usb/host/isp116x.h new file mode 100644 index 000000000..a3ce3b582 --- /dev/null +++ b/drivers/usb/host/isp116x.h @@ -0,0 +1,489 @@ +/* + * ISP116x register declarations and HCD data structures + * + * Copyright (C) 2007 Rodolfo Giometti <giometti@linux.it> + * Copyright (C) 2007 Eurotech S.p.A. <info@eurotech.it> + * Copyright (C) 2005 Olav Kongas <ok@artecdesign.ee> + * Portions: + * Copyright (C) 2004 Lothar Wassmann + * Copyright (C) 2004 Psion Teklogix + * Copyright (C) 2004 David Brownell + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#ifdef DEBUG +#define DBG(fmt, args...)	\ +		printf("isp116x: %s: " fmt "\n" , __FUNCTION__ , ## args) +#else +#define DBG(fmt, args...)	do {} while (0) +#endif + +#ifdef VERBOSE +#    define VDBG		DBG +#else +#    define VDBG(fmt, args...)	do {} while (0) +#endif + +#define ERR(fmt, args...)	\ +		printf("isp116x: %s: " fmt "\n" , __FUNCTION__ , ## args) +#define WARN(fmt, args...)	\ +		printf("isp116x: %s: " fmt "\n" , __FUNCTION__ , ## args) +#define INFO(fmt, args...)	\ +		printf("isp116x: " fmt "\n" , ## args) + +/* ------------------------------------------------------------------------- */ + +/* us of 1ms frame */ +#define  MAX_LOAD_LIMIT		850 + +/* Full speed: max # of bytes to transfer for a single urb +   at a time must be < 1024 && must be multiple of 64. +   832 allows transfering 4kiB within 5 frames. */ +#define MAX_TRANSFER_SIZE_FULLSPEED	832 + +/* Low speed: there is no reason to schedule in very big +   chunks; often the requested long transfers are for +   string descriptors containing short strings. */ +#define MAX_TRANSFER_SIZE_LOWSPEED	64 + +/* Bytetime (us), a rough indication of how much time it +   would take to transfer a byte of useful data over USB */ +#define BYTE_TIME_FULLSPEED	1 +#define BYTE_TIME_LOWSPEED	20 + +/* Buffer sizes */ +#define ISP116x_BUF_SIZE	4096 +#define ISP116x_ITL_BUFSIZE	0 +#define ISP116x_ATL_BUFSIZE	((ISP116x_BUF_SIZE) - 2*(ISP116x_ITL_BUFSIZE)) + +#define ISP116x_WRITE_OFFSET	0x80 + +/* --- ISP116x registers/bits ---------------------------------------------- */ + +#define	HCREVISION	0x00 +#define	HCCONTROL	0x01 +#define		HCCONTROL_HCFS	(3 << 6)	/* host controller +						   functional state */ +#define		HCCONTROL_USB_RESET	(0 << 6) +#define		HCCONTROL_USB_RESUME	(1 << 6) +#define		HCCONTROL_USB_OPER	(2 << 6) +#define		HCCONTROL_USB_SUSPEND	(3 << 6) +#define		HCCONTROL_RWC	(1 << 9)	/* remote wakeup connected */ +#define		HCCONTROL_RWE	(1 << 10)	/* remote wakeup enable */ +#define	HCCMDSTAT	0x02 +#define		HCCMDSTAT_HCR	(1 << 0)	/* host controller reset */ +#define		HCCMDSTAT_SOC	(3 << 16)	/* scheduling overrun count */ +#define	HCINTSTAT	0x03 +#define		HCINT_SO	(1 << 0)	/* scheduling overrun */ +#define		HCINT_WDH	(1 << 1)	/* writeback of done_head */ +#define		HCINT_SF	(1 << 2)	/* start frame */ +#define		HCINT_RD	(1 << 3)	/* resume detect */ +#define		HCINT_UE	(1 << 4)	/* unrecoverable error */ +#define		HCINT_FNO	(1 << 5)	/* frame number overflow */ +#define		HCINT_RHSC	(1 << 6)	/* root hub status change */ +#define		HCINT_OC	(1 << 30)	/* ownership change */ +#define		HCINT_MIE	(1 << 31)	/* master interrupt enable */ +#define	HCINTENB	0x04 +#define	HCINTDIS	0x05 +#define	HCFMINTVL	0x0d +#define	HCFMREM		0x0e +#define	HCFMNUM		0x0f +#define	HCLSTHRESH	0x11 +#define	HCRHDESCA	0x12 +#define		RH_A_NDP	(0x3 << 0)	/* # downstream ports */ +#define		RH_A_PSM	(1 << 8)	/* power switching mode */ +#define		RH_A_NPS	(1 << 9)	/* no power switching */ +#define		RH_A_DT		(1 << 10)	/* device type (mbz) */ +#define		RH_A_OCPM	(1 << 11)	/* overcurrent protection +						   mode */ +#define		RH_A_NOCP	(1 << 12)	/* no overcurrent protection */ +#define		RH_A_POTPGT	(0xff << 24)	/* power on -> power good +						   time */ +#define	HCRHDESCB	0x13 +#define		RH_B_DR		(0xffff << 0)	/* device removable flags */ +#define		RH_B_PPCM	(0xffff << 16)	/* port power control mask */ +#define	HCRHSTATUS	0x14 +#define		RH_HS_LPS	(1 << 0)	/* local power status */ +#define		RH_HS_OCI	(1 << 1)	/* over current indicator */ +#define		RH_HS_DRWE	(1 << 15)	/* device remote wakeup +						   enable */ +#define		RH_HS_LPSC	(1 << 16)	/* local power status change */ +#define		RH_HS_OCIC	(1 << 17)	/* over current indicator +						   change */ +#define		RH_HS_CRWE	(1 << 31)	/* clear remote wakeup +						   enable */ +#define	HCRHPORT1	0x15 +#define		RH_PS_CCS	(1 << 0)	/* current connect status */ +#define		RH_PS_PES	(1 << 1)	/* port enable status */ +#define		RH_PS_PSS	(1 << 2)	/* port suspend status */ +#define		RH_PS_POCI	(1 << 3)	/* port over current +						   indicator */ +#define		RH_PS_PRS	(1 << 4)	/* port reset status */ +#define		RH_PS_PPS	(1 << 8)	/* port power status */ +#define		RH_PS_LSDA	(1 << 9)	/* low speed device attached */ +#define		RH_PS_CSC	(1 << 16)	/* connect status change */ +#define		RH_PS_PESC	(1 << 17)	/* port enable status change */ +#define		RH_PS_PSSC	(1 << 18)	/* port suspend status +						   change */ +#define		RH_PS_OCIC	(1 << 19)	/* over current indicator +						   change */ +#define		RH_PS_PRSC	(1 << 20)	/* port reset status change */ +#define		HCRHPORT_CLRMASK	(0x1f << 16) +#define	HCRHPORT2	0x16 +#define	HCHWCFG		0x20 +#define		HCHWCFG_15KRSEL		(1 << 12) +#define		HCHWCFG_CLKNOTSTOP	(1 << 11) +#define		HCHWCFG_ANALOG_OC	(1 << 10) +#define		HCHWCFG_DACK_MODE	(1 << 8) +#define		HCHWCFG_EOT_POL		(1 << 7) +#define		HCHWCFG_DACK_POL	(1 << 6) +#define		HCHWCFG_DREQ_POL	(1 << 5) +#define		HCHWCFG_DBWIDTH_MASK	(0x03 << 3) +#define		HCHWCFG_DBWIDTH(n)	(((n) << 3) & HCHWCFG_DBWIDTH_MASK) +#define		HCHWCFG_INT_POL		(1 << 2) +#define		HCHWCFG_INT_TRIGGER	(1 << 1) +#define		HCHWCFG_INT_ENABLE	(1 << 0) +#define	HCDMACFG	0x21 +#define		HCDMACFG_BURST_LEN_MASK	(0x03 << 5) +#define		HCDMACFG_BURST_LEN(n)	(((n) << 5) & HCDMACFG_BURST_LEN_MASK) +#define		HCDMACFG_BURST_LEN_1	HCDMACFG_BURST_LEN(0) +#define		HCDMACFG_BURST_LEN_4	HCDMACFG_BURST_LEN(1) +#define		HCDMACFG_BURST_LEN_8	HCDMACFG_BURST_LEN(2) +#define		HCDMACFG_DMA_ENABLE	(1 << 4) +#define		HCDMACFG_BUF_TYPE_MASK	(0x07 << 1) +#define		HCDMACFG_CTR_SEL	(1 << 2) +#define		HCDMACFG_ITLATL_SEL	(1 << 1) +#define		HCDMACFG_DMA_RW_SELECT	(1 << 0) +#define	HCXFERCTR	0x22 +#define	HCuPINT		0x24 +#define		HCuPINT_SOF		(1 << 0) +#define		HCuPINT_ATL		(1 << 1) +#define		HCuPINT_AIIEOT		(1 << 2) +#define		HCuPINT_OPR		(1 << 4) +#define		HCuPINT_SUSP		(1 << 5) +#define		HCuPINT_CLKRDY		(1 << 6) +#define	HCuPINTENB	0x25 +#define	HCCHIPID	0x27 +#define		HCCHIPID_MASK		0xff00 +#define		HCCHIPID_MAGIC		0x6100 +#define	HCSCRATCH	0x28 +#define	HCSWRES		0x29 +#define		HCSWRES_MAGIC		0x00f6 +#define	HCITLBUFLEN	0x2a +#define	HCATLBUFLEN	0x2b +#define	HCBUFSTAT	0x2c +#define		HCBUFSTAT_ITL0_FULL	(1 << 0) +#define		HCBUFSTAT_ITL1_FULL	(1 << 1) +#define		HCBUFSTAT_ATL_FULL	(1 << 2) +#define		HCBUFSTAT_ITL0_DONE	(1 << 3) +#define		HCBUFSTAT_ITL1_DONE	(1 << 4) +#define		HCBUFSTAT_ATL_DONE	(1 << 5) +#define	HCRDITL0LEN	0x2d +#define	HCRDITL1LEN	0x2e +#define	HCITLPORT	0x40 +#define	HCATLPORT	0x41 + +/* PTD accessor macros. */ +#define PTD_GET_COUNT(p)	(((p)->count & PTD_COUNT_MSK) >> 0) +#define PTD_COUNT(v)		(((v) << 0) & PTD_COUNT_MSK) +#define PTD_GET_TOGGLE(p)	(((p)->count & PTD_TOGGLE_MSK) >> 10) +#define PTD_TOGGLE(v)		(((v) << 10) & PTD_TOGGLE_MSK) +#define PTD_GET_ACTIVE(p)	(((p)->count & PTD_ACTIVE_MSK) >> 11) +#define PTD_ACTIVE(v)		(((v) << 11) & PTD_ACTIVE_MSK) +#define PTD_GET_CC(p)		(((p)->count & PTD_CC_MSK) >> 12) +#define PTD_CC(v)		(((v) << 12) & PTD_CC_MSK) +#define PTD_GET_MPS(p)		(((p)->mps & PTD_MPS_MSK) >> 0) +#define PTD_MPS(v)		(((v) << 0) & PTD_MPS_MSK) +#define PTD_GET_SPD(p)		(((p)->mps & PTD_SPD_MSK) >> 10) +#define PTD_SPD(v)		(((v) << 10) & PTD_SPD_MSK) +#define PTD_GET_LAST(p)		(((p)->mps & PTD_LAST_MSK) >> 11) +#define PTD_LAST(v)		(((v) << 11) & PTD_LAST_MSK) +#define PTD_GET_EP(p)		(((p)->mps & PTD_EP_MSK) >> 12) +#define PTD_EP(v)		(((v) << 12) & PTD_EP_MSK) +#define PTD_GET_LEN(p)		(((p)->len & PTD_LEN_MSK) >> 0) +#define PTD_LEN(v)		(((v) << 0) & PTD_LEN_MSK) +#define PTD_GET_DIR(p)		(((p)->len & PTD_DIR_MSK) >> 10) +#define PTD_DIR(v)		(((v) << 10) & PTD_DIR_MSK) +#define PTD_GET_B5_5(p)		(((p)->len & PTD_B5_5_MSK) >> 13) +#define PTD_B5_5(v)		(((v) << 13) & PTD_B5_5_MSK) +#define PTD_GET_FA(p)		(((p)->faddr & PTD_FA_MSK) >> 0) +#define PTD_FA(v)		(((v) << 0) & PTD_FA_MSK) +#define PTD_GET_FMT(p)		(((p)->faddr & PTD_FMT_MSK) >> 7) +#define PTD_FMT(v)		(((v) << 7) & PTD_FMT_MSK) + +/*  Hardware transfer status codes -- CC from ptd->count */ +#define TD_CC_NOERROR      0x00 +#define TD_CC_CRC          0x01 +#define TD_CC_BITSTUFFING  0x02 +#define TD_CC_DATATOGGLEM  0x03 +#define TD_CC_STALL        0x04 +#define TD_DEVNOTRESP      0x05 +#define TD_PIDCHECKFAIL    0x06 +#define TD_UNEXPECTEDPID   0x07 +#define TD_DATAOVERRUN     0x08 +#define TD_DATAUNDERRUN    0x09 +    /* 0x0A, 0x0B reserved for hardware */ +#define TD_BUFFEROVERRUN   0x0C +#define TD_BUFFERUNDERRUN  0x0D +    /* 0x0E, 0x0F reserved for HCD */ +#define TD_NOTACCESSED     0x0F + +/* ------------------------------------------------------------------------- */ + +#define	LOG2_PERIODIC_SIZE	5	/* arbitrary; this matches OHCI */ +#define	PERIODIC_SIZE		(1 << LOG2_PERIODIC_SIZE) + +/* Philips transfer descriptor */ +struct ptd { +	u16 count; +#define	PTD_COUNT_MSK	(0x3ff << 0) +#define	PTD_TOGGLE_MSK	(1 << 10) +#define	PTD_ACTIVE_MSK	(1 << 11) +#define	PTD_CC_MSK	(0xf << 12) +	u16 mps; +#define	PTD_MPS_MSK	(0x3ff << 0) +#define	PTD_SPD_MSK	(1 << 10) +#define	PTD_LAST_MSK	(1 << 11) +#define	PTD_EP_MSK	(0xf << 12) +	u16 len; +#define	PTD_LEN_MSK	(0x3ff << 0) +#define	PTD_DIR_MSK	(3 << 10) +#define	PTD_DIR_SETUP	(0) +#define	PTD_DIR_OUT	(1) +#define	PTD_DIR_IN	(2) +#define	PTD_B5_5_MSK	(1 << 13) +	u16 faddr; +#define	PTD_FA_MSK	(0x7f << 0) +#define	PTD_FMT_MSK	(1 << 7) +} __attribute__ ((packed, aligned(2))); + +struct isp116x_ep { +	struct usb_device *udev; +	struct ptd ptd; + +	u8 maxpacket; +	u8 epnum; +	u8 nextpid; + +	u16 length;		/* of current packet */ +	unsigned char *data;	/* to databuf */ + +	u16 error_count; +}; + +/* URB struct */ +#define N_URB_TD		48 +#define URB_DEL			1 +typedef struct { +	struct isp116x_ep *ed; +	void *transfer_buffer;	/* (in) associated data buffer */ +	int actual_length;	/* (return) actual transfer length */ +	unsigned long pipe;	/* (in) pipe information */ +#if 0 +	int state; +#endif +} urb_priv_t; + +struct isp116x_platform_data { +	/* Enable internal resistors on downstream ports */ +	unsigned sel15Kres:1; +	/* On-chip overcurrent detection */ +	unsigned oc_enable:1; +	/* Enable wakeup by devices on usb bus (e.g. wakeup +	   by attachment/detachment or by device activity +	   such as moving a mouse). When chosen, this option +	   prevents stopping internal clock, increasing +	   thereby power consumption in suspended state. */ +	unsigned remote_wakeup_enable:1; +}; + +struct isp116x { +	u16 *addr_reg; +	u16 *data_reg; + +	struct isp116x_platform_data *board; + +	struct dentry *dentry; +	unsigned long stat1, stat2, stat4, stat8, stat16; + +	/* Status flags */ +	unsigned disabled:1; +	unsigned sleeping:1; + +	/* Root hub registers */ +	u32 rhdesca; +	u32 rhdescb; +	u32 rhstatus; +	u32 rhport[2]; + +	/* Schedule for the current frame */ +	struct isp116x_ep *atl_active; +	int atl_buflen; +	int atl_bufshrt; +	int atl_last_dir; +	int atl_finishing; +}; + +/* ------------------------------------------------- */ + +/* Inter-io delay (ns). The chip is picky about access timings; it + * expects at least: + * 150ns delay between consecutive accesses to DATA_REG, + * 300ns delay between access to ADDR_REG and DATA_REG + * OE, WE MUST NOT be changed during these intervals + */ +#if defined(UDELAY) +#define	isp116x_delay(h,d)	udelay(d) +#else +#define	isp116x_delay(h,d)	do {} while (0) +#endif + +static inline void isp116x_write_addr(struct isp116x *isp116x, unsigned reg) +{ +	writew(reg & 0xff, isp116x->addr_reg); +	isp116x_delay(isp116x, UDELAY); +} + +static inline void isp116x_write_data16(struct isp116x *isp116x, u16 val) +{ +	writew(val, isp116x->data_reg); +	isp116x_delay(isp116x, UDELAY); +} + +static inline void isp116x_raw_write_data16(struct isp116x *isp116x, u16 val) +{ +	__raw_writew(val, isp116x->data_reg); +	isp116x_delay(isp116x, UDELAY); +} + +static inline u16 isp116x_read_data16(struct isp116x *isp116x) +{ +	u16 val; + +	val = readw(isp116x->data_reg); +	isp116x_delay(isp116x, UDELAY); +	return val; +} + +static inline u16 isp116x_raw_read_data16(struct isp116x *isp116x) +{ +	u16 val; + +	val = __raw_readw(isp116x->data_reg); +	isp116x_delay(isp116x, UDELAY); +	return val; +} + +static inline void isp116x_write_data32(struct isp116x *isp116x, u32 val) +{ +	writew(val & 0xffff, isp116x->data_reg); +	isp116x_delay(isp116x, UDELAY); +	writew(val >> 16, isp116x->data_reg); +	isp116x_delay(isp116x, UDELAY); +} + +static inline u32 isp116x_read_data32(struct isp116x *isp116x) +{ +	u32 val; + +	val = (u32) readw(isp116x->data_reg); +	isp116x_delay(isp116x, UDELAY); +	val |= ((u32) readw(isp116x->data_reg)) << 16; +	isp116x_delay(isp116x, UDELAY); +	return val; +} + +/* Let's keep register access functions out of line. Hint: +   we wait at least 150 ns at every access. +*/ +static u16 isp116x_read_reg16(struct isp116x *isp116x, unsigned reg) +{ +	isp116x_write_addr(isp116x, reg); +	return isp116x_read_data16(isp116x); +} + +static u32 isp116x_read_reg32(struct isp116x *isp116x, unsigned reg) +{ +	isp116x_write_addr(isp116x, reg); +	return isp116x_read_data32(isp116x); +} + +static void isp116x_write_reg16(struct isp116x *isp116x, unsigned reg, +				unsigned val) +{ +	isp116x_write_addr(isp116x, reg | ISP116x_WRITE_OFFSET); +	isp116x_write_data16(isp116x, (u16) (val & 0xffff)); +} + +static void isp116x_write_reg32(struct isp116x *isp116x, unsigned reg, +				unsigned val) +{ +	isp116x_write_addr(isp116x, reg | ISP116x_WRITE_OFFSET); +	isp116x_write_data32(isp116x, (u32) val); +} + +/* --- USB HUB constants (not OHCI-specific; see hub.h) -------------------- */ + +/* destination of request */ +#define RH_INTERFACE               0x01 +#define RH_ENDPOINT                0x02 +#define RH_OTHER                   0x03 + +#define RH_CLASS                   0x20 +#define RH_VENDOR                  0x40 + +/* Requests: bRequest << 8 | bmRequestType */ +#define RH_GET_STATUS           0x0080 +#define RH_CLEAR_FEATURE        0x0100 +#define RH_SET_FEATURE          0x0300 +#define RH_SET_ADDRESS          0x0500 +#define RH_GET_DESCRIPTOR       0x0680 +#define RH_SET_DESCRIPTOR       0x0700 +#define RH_GET_CONFIGURATION    0x0880 +#define RH_SET_CONFIGURATION    0x0900 +#define RH_GET_STATE            0x0280 +#define RH_GET_INTERFACE        0x0A80 +#define RH_SET_INTERFACE        0x0B00 +#define RH_SYNC_FRAME           0x0C80 +/* Our Vendor Specific Request */ +#define RH_SET_EP               0x2000 + +/* Hub port features */ +#define RH_PORT_CONNECTION         0x00 +#define RH_PORT_ENABLE             0x01 +#define RH_PORT_SUSPEND            0x02 +#define RH_PORT_OVER_CURRENT       0x03 +#define RH_PORT_RESET              0x04 +#define RH_PORT_POWER              0x08 +#define RH_PORT_LOW_SPEED          0x09 + +#define RH_C_PORT_CONNECTION       0x10 +#define RH_C_PORT_ENABLE           0x11 +#define RH_C_PORT_SUSPEND          0x12 +#define RH_C_PORT_OVER_CURRENT     0x13 +#define RH_C_PORT_RESET            0x14 + +/* Hub features */ +#define RH_C_HUB_LOCAL_POWER       0x00 +#define RH_C_HUB_OVER_CURRENT      0x01 + +#define RH_DEVICE_REMOTE_WAKEUP    0x00 +#define RH_ENDPOINT_STALL          0x01 + +#define RH_ACK                     0x01 +#define RH_REQ_ERR                 -1 +#define RH_NACK                    0x00 diff --git a/drivers/usb/host/ohci-at91.c b/drivers/usb/host/ohci-at91.c new file mode 100644 index 000000000..7c44ad0e9 --- /dev/null +++ b/drivers/usb/host/ohci-at91.c @@ -0,0 +1,80 @@ +/* + * (C) Copyright 2006 + * DENX Software Engineering <mk@denx.de> + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <common.h> + +#if defined(CONFIG_USB_OHCI_NEW) && defined(CONFIG_SYS_USB_OHCI_CPU_INIT) + +#include <asm/arch/hardware.h> +#include <asm/arch/io.h> +#include <asm/arch/at91_pmc.h> + +int usb_cpu_init(void) +{ + +#if defined(CONFIG_AT91CAP9) || defined(CONFIG_AT91SAM9260) || \ +    defined(CONFIG_AT91SAM9263) || defined(CONFIG_AT91SAM9G20) +	/* Enable PLLB */ +	at91_sys_write(AT91_CKGR_PLLBR, CONFIG_SYS_AT91_PLLB); +	while ((at91_sys_read(AT91_PMC_SR) & AT91_PMC_LOCKB) != AT91_PMC_LOCKB) +		; +#endif + +	/* Enable USB host clock. */ +	at91_sys_write(AT91_PMC_PCER, 1 << AT91_ID_UHP); +#ifdef CONFIG_AT91SAM9261 +	at91_sys_write(AT91_PMC_SCER, AT91_PMC_UHP | AT91_PMC_HCK0); +#else +	at91_sys_write(AT91_PMC_SCER, AT91_PMC_UHP); +#endif + +	return 0; +} + +int usb_cpu_stop(void) +{ +	/* Disable USB host clock. */ +	at91_sys_write(AT91_PMC_PCDR, 1 << AT91_ID_UHP); +#ifdef CONFIG_AT91SAM9261 +	at91_sys_write(AT91_PMC_SCDR, AT91_PMC_UHP | AT91_PMC_HCK0); +#else +	at91_sys_write(AT91_PMC_SCDR, AT91_PMC_UHP); +#endif + +#if defined(CONFIG_AT91CAP9) || defined(CONFIG_AT91SAM9260) || \ +    defined(CONFIG_AT91SAM9263) || defined(CONFIG_AT91SAM9G20) +	/* Disable PLLB */ +	at91_sys_write(AT91_CKGR_PLLBR, 0); +	while ((at91_sys_read(AT91_PMC_SR) & AT91_PMC_LOCKB) != 0) +		; +#endif + +	return 0; +} + +int usb_cpu_init_fail(void) +{ +	return usb_cpu_stop(); +} + +#endif /* defined(CONFIG_USB_OHCI) && defined(CONFIG_SYS_USB_OHCI_CPU_INIT) */ diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c new file mode 100644 index 000000000..e2f289e6d --- /dev/null +++ b/drivers/usb/host/ohci-hcd.c @@ -0,0 +1,2016 @@ +/* + * URB OHCI HCD (Host Controller Driver) for USB on the AT91RM9200 and PCI bus. + * + * Interrupt support is added. Now, it has been tested + * on ULI1575 chip and works well with USB keyboard. + * + * (C) Copyright 2007 + * Zhang Wei, Freescale Semiconductor, Inc. <wei.zhang@freescale.com> + * + * (C) Copyright 2003 + * Gary Jennejohn, DENX Software Engineering <gj@denx.de> + * + * Note: Much of this code has been derived from Linux 2.4 + * (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at> + * (C) Copyright 2000-2002 David Brownell + * + * Modified for the MP2USB by (C) Copyright 2005 Eric Benard + * ebenard@eukrea.com - based on s3c24x0's driver + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + */ +/* + * IMPORTANT NOTES + * 1 - Read doc/README.generic_usb_ohci + * 2 - this driver is intended for use with USB Mass Storage Devices + *     (BBB) and USB keyboard. There is NO support for Isochronous pipes! + * 2 - when running on a PQFP208 AT91RM9200, define CONFIG_AT91C_PQFP_UHPBUG + *     to activate workaround for bug #41 or this driver will NOT work! + */ + +#include <common.h> +#include <asm/byteorder.h> + +#if defined(CONFIG_PCI_OHCI) +# include <pci.h> +#if !defined(CONFIG_PCI_OHCI_DEVNO) +#define CONFIG_PCI_OHCI_DEVNO	0 +#endif +#endif + +#include <malloc.h> +#include <usb.h> + +#include "ohci.h" + +#ifdef CONFIG_AT91RM9200 +#include <asm/arch/hardware.h>	/* needed for AT91_USB_HOST_BASE */ +#endif + +#if defined(CONFIG_ARM920T) || \ +    defined(CONFIG_S3C2400) || \ +    defined(CONFIG_S3C2410) || \ +    defined(CONFIG_S3C6400) || \ +    defined(CONFIG_440EP) || \ +    defined(CONFIG_PCI_OHCI) || \ +    defined(CONFIG_MPC5200) || \ +    defined(CONFIG_SYS_OHCI_USE_NPS) +# define OHCI_USE_NPS		/* force NoPowerSwitching mode */ +#endif + +#undef OHCI_VERBOSE_DEBUG	/* not always helpful */ +#undef DEBUG +#undef SHOW_INFO +#undef OHCI_FILL_TRACE + +/* For initializing controller (mask in an HCFS mode too) */ +#define OHCI_CONTROL_INIT \ +	(OHCI_CTRL_CBSR & 0x3) | OHCI_CTRL_IE | OHCI_CTRL_PLE + +/* + * e.g. PCI controllers need this + */ +#ifdef CONFIG_SYS_OHCI_SWAP_REG_ACCESS +# define readl(a) __swap_32(*((volatile u32 *)(a))) +# define writel(a, b) (*((volatile u32 *)(b)) = __swap_32((volatile u32)a)) +#else +# define readl(a) (*((volatile u32 *)(a))) +# define writel(a, b) (*((volatile u32 *)(b)) = ((volatile u32)a)) +#endif /* CONFIG_SYS_OHCI_SWAP_REG_ACCESS */ + +#define min_t(type, x, y) \ +		    ({ type __x = (x); type __y = (y); __x < __y ? __x: __y; }) + +#ifdef CONFIG_PCI_OHCI +static struct pci_device_id ohci_pci_ids[] = { +	{0x10b9, 0x5237},	/* ULI1575 PCI OHCI module ids */ +	{0x1033, 0x0035},	/* NEC PCI OHCI module ids */ +	{0x1131, 0x1561},	/* Philips 1561 PCI OHCI module ids */ +	/* Please add supported PCI OHCI controller ids here */ +	{0, 0} +}; +#endif + +#ifdef CONFIG_PCI_EHCI_DEVNO +static struct pci_device_id ehci_pci_ids[] = { +	{0x1131, 0x1562},	/* Philips 1562 PCI EHCI module ids */ +	/* Please add supported PCI EHCI controller ids here */ +	{0, 0} +}; +#endif + +#ifdef DEBUG +#define dbg(format, arg...) printf("DEBUG: " format "\n", ## arg) +#else +#define dbg(format, arg...) do {} while (0) +#endif /* DEBUG */ +#define err(format, arg...) printf("ERROR: " format "\n", ## arg) +#ifdef SHOW_INFO +#define info(format, arg...) printf("INFO: " format "\n", ## arg) +#else +#define info(format, arg...) do {} while (0) +#endif + +#ifdef CONFIG_SYS_OHCI_BE_CONTROLLER +# define m16_swap(x) cpu_to_be16(x) +# define m32_swap(x) cpu_to_be32(x) +#else +# define m16_swap(x) cpu_to_le16(x) +# define m32_swap(x) cpu_to_le32(x) +#endif /* CONFIG_SYS_OHCI_BE_CONTROLLER */ + +/* global ohci_t */ +static ohci_t gohci; +/* this must be aligned to a 256 byte boundary */ +struct ohci_hcca ghcca[1]; +/* a pointer to the aligned storage */ +struct ohci_hcca *phcca; +/* this allocates EDs for all possible endpoints */ +struct ohci_device ohci_dev; +/* device which was disconnected */ +struct usb_device *devgone; + +static inline u32 roothub_a(struct ohci *hc) +	{ return readl(&hc->regs->roothub.a); } +static inline u32 roothub_b(struct ohci *hc) +	{ return readl(&hc->regs->roothub.b); } +static inline u32 roothub_status(struct ohci *hc) +	{ return readl(&hc->regs->roothub.status); } +static inline u32 roothub_portstatus(struct ohci *hc, int i) +	{ return readl(&hc->regs->roothub.portstatus[i]); } + +/* forward declaration */ +static int hc_interrupt(void); +static void td_submit_job(struct usb_device *dev, unsigned long pipe, +			  void *buffer, int transfer_len, +			  struct devrequest *setup, urb_priv_t *urb, +			  int interval); + +/*-------------------------------------------------------------------------* + * URB support functions + *-------------------------------------------------------------------------*/ + +/* free HCD-private data associated with this URB */ + +static void urb_free_priv(urb_priv_t *urb) +{ +	int		i; +	int		last; +	struct td	*td; + +	last = urb->length - 1; +	if (last >= 0) { +		for (i = 0; i <= last; i++) { +			td = urb->td[i]; +			if (td) { +				td->usb_dev = NULL; +				urb->td[i] = NULL; +			} +		} +	} +	free(urb); +} + +/*-------------------------------------------------------------------------*/ + +#ifdef DEBUG +static int sohci_get_current_frame_number(struct usb_device *dev); + +/* debug| print the main components of an URB + * small: 0) header + data packets 1) just header */ + +static void pkt_print(urb_priv_t *purb, struct usb_device *dev, +		      unsigned long pipe, void *buffer, int transfer_len, +		      struct devrequest *setup, char *str, int small) +{ +	dbg("%s URB:[%4x] dev:%2lu,ep:%2lu-%c,type:%s,len:%d/%d stat:%#lx", +			str, +			sohci_get_current_frame_number(dev), +			usb_pipedevice(pipe), +			usb_pipeendpoint(pipe), +			usb_pipeout(pipe)? 'O': 'I', +			usb_pipetype(pipe) < 2 ? \ +				(usb_pipeint(pipe)? "INTR": "ISOC"): \ +				(usb_pipecontrol(pipe)? "CTRL": "BULK"), +			(purb ? purb->actual_length : 0), +			transfer_len, dev->status); +#ifdef	OHCI_VERBOSE_DEBUG +	if (!small) { +		int i, len; + +		if (usb_pipecontrol(pipe)) { +			printf(__FILE__ ": cmd(8):"); +			for (i = 0; i < 8 ; i++) +				printf(" %02x", ((__u8 *) setup) [i]); +			printf("\n"); +		} +		if (transfer_len > 0 && buffer) { +			printf(__FILE__ ": data(%d/%d):", +				(purb ? purb->actual_length : 0), +				transfer_len); +			len = usb_pipeout(pipe)? transfer_len: +					(purb ? purb->actual_length : 0); +			for (i = 0; i < 16 && i < len; i++) +				printf(" %02x", ((__u8 *) buffer) [i]); +			printf("%s\n", i < len? "...": ""); +		} +	} +#endif +} + +/* just for debugging; prints non-empty branches of the int ed tree + * inclusive iso eds */ +void ep_print_int_eds(ohci_t *ohci, char *str) +{ +	int i, j; +	 __u32 *ed_p; +	for (i = 0; i < 32; i++) { +		j = 5; +		ed_p = &(ohci->hcca->int_table [i]); +		if (*ed_p == 0) +		    continue; +		printf(__FILE__ ": %s branch int %2d(%2x):", str, i, i); +		while (*ed_p != 0 && j--) { +			ed_t *ed = (ed_t *)m32_swap(ed_p); +			printf(" ed: %4x;", ed->hwINFO); +			ed_p = &ed->hwNextED; +		} +		printf("\n"); +	} +} + +static void ohci_dump_intr_mask(char *label, __u32 mask) +{ +	dbg("%s: 0x%08x%s%s%s%s%s%s%s%s%s", +		label, +		mask, +		(mask & OHCI_INTR_MIE) ? " MIE" : "", +		(mask & OHCI_INTR_OC) ? " OC" : "", +		(mask & OHCI_INTR_RHSC) ? " RHSC" : "", +		(mask & OHCI_INTR_FNO) ? " FNO" : "", +		(mask & OHCI_INTR_UE) ? " UE" : "", +		(mask & OHCI_INTR_RD) ? " RD" : "", +		(mask & OHCI_INTR_SF) ? " SF" : "", +		(mask & OHCI_INTR_WDH) ? " WDH" : "", +		(mask & OHCI_INTR_SO) ? " SO" : "" +		); +} + +static void maybe_print_eds(char *label, __u32 value) +{ +	ed_t *edp = (ed_t *)value; + +	if (value) { +		dbg("%s %08x", label, value); +		dbg("%08x", edp->hwINFO); +		dbg("%08x", edp->hwTailP); +		dbg("%08x", edp->hwHeadP); +		dbg("%08x", edp->hwNextED); +	} +} + +static char *hcfs2string(int state) +{ +	switch (state) { +	case OHCI_USB_RESET:	return "reset"; +	case OHCI_USB_RESUME:	return "resume"; +	case OHCI_USB_OPER:	return "operational"; +	case OHCI_USB_SUSPEND:	return "suspend"; +	} +	return "?"; +} + +/* dump control and status registers */ +static void ohci_dump_status(ohci_t *controller) +{ +	struct ohci_regs	*regs = controller->regs; +	__u32			temp; + +	temp = readl(®s->revision) & 0xff; +	if (temp != 0x10) +		dbg("spec %d.%d", (temp >> 4), (temp & 0x0f)); + +	temp = readl(®s->control); +	dbg("control: 0x%08x%s%s%s HCFS=%s%s%s%s%s CBSR=%d", temp, +		(temp & OHCI_CTRL_RWE) ? " RWE" : "", +		(temp & OHCI_CTRL_RWC) ? " RWC" : "", +		(temp & OHCI_CTRL_IR) ? " IR" : "", +		hcfs2string(temp & OHCI_CTRL_HCFS), +		(temp & OHCI_CTRL_BLE) ? " BLE" : "", +		(temp & OHCI_CTRL_CLE) ? " CLE" : "", +		(temp & OHCI_CTRL_IE) ? " IE" : "", +		(temp & OHCI_CTRL_PLE) ? " PLE" : "", +		temp & OHCI_CTRL_CBSR +		); + +	temp = readl(®s->cmdstatus); +	dbg("cmdstatus: 0x%08x SOC=%d%s%s%s%s", temp, +		(temp & OHCI_SOC) >> 16, +		(temp & OHCI_OCR) ? " OCR" : "", +		(temp & OHCI_BLF) ? " BLF" : "", +		(temp & OHCI_CLF) ? " CLF" : "", +		(temp & OHCI_HCR) ? " HCR" : "" +		); + +	ohci_dump_intr_mask("intrstatus", readl(®s->intrstatus)); +	ohci_dump_intr_mask("intrenable", readl(®s->intrenable)); + +	maybe_print_eds("ed_periodcurrent", readl(®s->ed_periodcurrent)); + +	maybe_print_eds("ed_controlhead", readl(®s->ed_controlhead)); +	maybe_print_eds("ed_controlcurrent", readl(®s->ed_controlcurrent)); + +	maybe_print_eds("ed_bulkhead", readl(®s->ed_bulkhead)); +	maybe_print_eds("ed_bulkcurrent", readl(®s->ed_bulkcurrent)); + +	maybe_print_eds("donehead", readl(®s->donehead)); +} + +static void ohci_dump_roothub(ohci_t *controller, int verbose) +{ +	__u32			temp, ndp, i; + +	temp = roothub_a(controller); +	ndp = (temp & RH_A_NDP); +#ifdef CONFIG_AT91C_PQFP_UHPBUG +	ndp = (ndp == 2) ? 1:0; +#endif +	if (verbose) { +		dbg("roothub.a: %08x POTPGT=%d%s%s%s%s%s NDP=%d", temp, +			((temp & RH_A_POTPGT) >> 24) & 0xff, +			(temp & RH_A_NOCP) ? " NOCP" : "", +			(temp & RH_A_OCPM) ? " OCPM" : "", +			(temp & RH_A_DT) ? " DT" : "", +			(temp & RH_A_NPS) ? " NPS" : "", +			(temp & RH_A_PSM) ? " PSM" : "", +			ndp +			); +		temp = roothub_b(controller); +		dbg("roothub.b: %08x PPCM=%04x DR=%04x", +			temp, +			(temp & RH_B_PPCM) >> 16, +			(temp & RH_B_DR) +			); +		temp = roothub_status(controller); +		dbg("roothub.status: %08x%s%s%s%s%s%s", +			temp, +			(temp & RH_HS_CRWE) ? " CRWE" : "", +			(temp & RH_HS_OCIC) ? " OCIC" : "", +			(temp & RH_HS_LPSC) ? " LPSC" : "", +			(temp & RH_HS_DRWE) ? " DRWE" : "", +			(temp & RH_HS_OCI) ? " OCI" : "", +			(temp & RH_HS_LPS) ? " LPS" : "" +			); +	} + +	for (i = 0; i < ndp; i++) { +		temp = roothub_portstatus(controller, i); +		dbg("roothub.portstatus [%d] = 0x%08x%s%s%s%s%s%s%s%s%s%s%s%s", +			i, +			temp, +			(temp & RH_PS_PRSC) ? " PRSC" : "", +			(temp & RH_PS_OCIC) ? " OCIC" : "", +			(temp & RH_PS_PSSC) ? " PSSC" : "", +			(temp & RH_PS_PESC) ? " PESC" : "", +			(temp & RH_PS_CSC) ? " CSC" : "", + +			(temp & RH_PS_LSDA) ? " LSDA" : "", +			(temp & RH_PS_PPS) ? " PPS" : "", +			(temp & RH_PS_PRS) ? " PRS" : "", +			(temp & RH_PS_POCI) ? " POCI" : "", +			(temp & RH_PS_PSS) ? " PSS" : "", + +			(temp & RH_PS_PES) ? " PES" : "", +			(temp & RH_PS_CCS) ? " CCS" : "" +			); +	} +} + +static void ohci_dump(ohci_t *controller, int verbose) +{ +	dbg("OHCI controller usb-%s state", controller->slot_name); + +	/* dumps some of the state we know about */ +	ohci_dump_status(controller); +	if (verbose) +		ep_print_int_eds(controller, "hcca"); +	dbg("hcca frame #%04x", controller->hcca->frame_no); +	ohci_dump_roothub(controller, 1); +} +#endif /* DEBUG */ + +/*-------------------------------------------------------------------------* + * Interface functions (URB) + *-------------------------------------------------------------------------*/ + +/* get a transfer request */ + +int sohci_submit_job(urb_priv_t *urb, struct devrequest *setup) +{ +	ohci_t *ohci; +	ed_t *ed; +	urb_priv_t *purb_priv = urb; +	int i, size = 0; +	struct usb_device *dev = urb->dev; +	unsigned long pipe = urb->pipe; +	void *buffer = urb->transfer_buffer; +	int transfer_len = urb->transfer_buffer_length; +	int interval = urb->interval; + +	ohci = &gohci; + +	/* when controller's hung, permit only roothub cleanup attempts +	 * such as powering down ports */ +	if (ohci->disabled) { +		err("sohci_submit_job: EPIPE"); +		return -1; +	} + +	/* we're about to begin a new transaction here so mark the +	 * URB unfinished */ +	urb->finished = 0; + +	/* every endpoint has a ed, locate and fill it */ +	ed = ep_add_ed(dev, pipe, interval, 1); +	if (!ed) { +		err("sohci_submit_job: ENOMEM"); +		return -1; +	} + +	/* for the private part of the URB we need the number of TDs (size) */ +	switch (usb_pipetype(pipe)) { +	case PIPE_BULK: /* one TD for every 4096 Byte */ +		size = (transfer_len - 1) / 4096 + 1; +		break; +	case PIPE_CONTROL:/* 1 TD for setup, 1 for ACK and 1 for every 4096 B */ +		size = (transfer_len == 0)? 2: +					(transfer_len - 1) / 4096 + 3; +		break; +	case PIPE_INTERRUPT: /* 1 TD */ +		size = 1; +		break; +	} + +	ed->purb = urb; + +	if (size >= (N_URB_TD - 1)) { +		err("need %d TDs, only have %d", size, N_URB_TD); +		return -1; +	} +	purb_priv->pipe = pipe; + +	/* fill the private part of the URB */ +	purb_priv->length = size; +	purb_priv->ed = ed; +	purb_priv->actual_length = 0; + +	/* allocate the TDs */ +	/* note that td[0] was allocated in ep_add_ed */ +	for (i = 0; i < size; i++) { +		purb_priv->td[i] = td_alloc(dev); +		if (!purb_priv->td[i]) { +			purb_priv->length = i; +			urb_free_priv(purb_priv); +			err("sohci_submit_job: ENOMEM"); +			return -1; +		} +	} + +	if (ed->state == ED_NEW || (ed->state & ED_DEL)) { +		urb_free_priv(purb_priv); +		err("sohci_submit_job: EINVAL"); +		return -1; +	} + +	/* link the ed into a chain if is not already */ +	if (ed->state != ED_OPER) +		ep_link(ohci, ed); + +	/* fill the TDs and link it to the ed */ +	td_submit_job(dev, pipe, buffer, transfer_len, +		      setup, purb_priv, interval); + +	return 0; +} + +static inline int sohci_return_job(struct ohci *hc, urb_priv_t *urb) +{ +	struct ohci_regs *regs = hc->regs; + +	switch (usb_pipetype(urb->pipe)) { +	case PIPE_INTERRUPT: +		/* implicitly requeued */ +		if (urb->dev->irq_handle && +				(urb->dev->irq_act_len = urb->actual_length)) { +			writel(OHCI_INTR_WDH, ®s->intrenable); +			readl(®s->intrenable); /* PCI posting flush */ +			urb->dev->irq_handle(urb->dev); +			writel(OHCI_INTR_WDH, ®s->intrdisable); +			readl(®s->intrdisable); /* PCI posting flush */ +		} +		urb->actual_length = 0; +		td_submit_job( +				urb->dev, +				urb->pipe, +				urb->transfer_buffer, +				urb->transfer_buffer_length, +				NULL, +				urb, +				urb->interval); +		break; +	case PIPE_CONTROL: +	case PIPE_BULK: +		break; +	default: +		return 0; +	} +	return 1; +} + +/*-------------------------------------------------------------------------*/ + +#ifdef DEBUG +/* tell us the current USB frame number */ + +static int sohci_get_current_frame_number(struct usb_device *usb_dev) +{ +	ohci_t *ohci = &gohci; + +	return m16_swap(ohci->hcca->frame_no); +} +#endif + +/*-------------------------------------------------------------------------* + * ED handling functions + *-------------------------------------------------------------------------*/ + +/* search for the right branch to insert an interrupt ed into the int tree + * do some load ballancing; + * returns the branch and + * sets the interval to interval = 2^integer (ld (interval)) */ + +static int ep_int_ballance(ohci_t *ohci, int interval, int load) +{ +	int i, branch = 0; + +	/* search for the least loaded interrupt endpoint +	 * branch of all 32 branches +	 */ +	for (i = 0; i < 32; i++) +		if (ohci->ohci_int_load [branch] > ohci->ohci_int_load [i]) +			branch = i; + +	branch = branch % interval; +	for (i = branch; i < 32; i += interval) +		ohci->ohci_int_load [i] += load; + +	return branch; +} + +/*-------------------------------------------------------------------------*/ + +/*  2^int( ld (inter)) */ + +static int ep_2_n_interval(int inter) +{ +	int i; +	for (i = 0; ((inter >> i) > 1) && (i < 5); i++); +	return 1 << i; +} + +/*-------------------------------------------------------------------------*/ + +/* the int tree is a binary tree + * in order to process it sequentially the indexes of the branches have to + * be mapped the mapping reverses the bits of a word of num_bits length */ +static int ep_rev(int num_bits, int word) +{ +	int i, wout = 0; + +	for (i = 0; i < num_bits; i++) +		wout |= (((word >> i) & 1) << (num_bits - i - 1)); +	return wout; +} + +/*-------------------------------------------------------------------------* + * ED handling functions + *-------------------------------------------------------------------------*/ + +/* link an ed into one of the HC chains */ + +static int ep_link(ohci_t *ohci, ed_t *edi) +{ +	volatile ed_t *ed = edi; +	int int_branch; +	int i; +	int inter; +	int interval; +	int load; +	__u32 *ed_p; + +	ed->state = ED_OPER; +	ed->int_interval = 0; + +	switch (ed->type) { +	case PIPE_CONTROL: +		ed->hwNextED = 0; +		if (ohci->ed_controltail == NULL) +			writel(ed, &ohci->regs->ed_controlhead); +		else +			ohci->ed_controltail->hwNextED = +						   m32_swap((unsigned long)ed); + +		ed->ed_prev = ohci->ed_controltail; +		if (!ohci->ed_controltail && !ohci->ed_rm_list[0] && +			!ohci->ed_rm_list[1] && !ohci->sleeping) { +			ohci->hc_control |= OHCI_CTRL_CLE; +			writel(ohci->hc_control, &ohci->regs->control); +		} +		ohci->ed_controltail = edi; +		break; + +	case PIPE_BULK: +		ed->hwNextED = 0; +		if (ohci->ed_bulktail == NULL) +			writel(ed, &ohci->regs->ed_bulkhead); +		else +			ohci->ed_bulktail->hwNextED = +						   m32_swap((unsigned long)ed); + +		ed->ed_prev = ohci->ed_bulktail; +		if (!ohci->ed_bulktail && !ohci->ed_rm_list[0] && +			!ohci->ed_rm_list[1] && !ohci->sleeping) { +			ohci->hc_control |= OHCI_CTRL_BLE; +			writel(ohci->hc_control, &ohci->regs->control); +		} +		ohci->ed_bulktail = edi; +		break; + +	case PIPE_INTERRUPT: +		load = ed->int_load; +		interval = ep_2_n_interval(ed->int_period); +		ed->int_interval = interval; +		int_branch = ep_int_ballance(ohci, interval, load); +		ed->int_branch = int_branch; + +		for (i = 0; i < ep_rev(6, interval); i += inter) { +			inter = 1; +			for (ed_p = &(ohci->hcca->int_table[\ +						ep_rev(5, i) + int_branch]); +				(*ed_p != 0) && +				(((ed_t *)ed_p)->int_interval >= interval); +				ed_p = &(((ed_t *)ed_p)->hwNextED)) +					inter = ep_rev(6, +						 ((ed_t *)ed_p)->int_interval); +			ed->hwNextED = *ed_p; +			*ed_p = m32_swap((unsigned long)ed); +		} +		break; +	} +	return 0; +} + +/*-------------------------------------------------------------------------*/ + +/* scan the periodic table to find and unlink this ED */ +static void periodic_unlink(struct ohci *ohci, volatile struct ed *ed, +			    unsigned index, unsigned period) +{ +	for (; index < NUM_INTS; index += period) { +		__u32	*ed_p = &ohci->hcca->int_table [index]; + +		/* ED might have been unlinked through another path */ +		while (*ed_p != 0) { +			if (((struct ed *) +					m32_swap((unsigned long)ed_p)) == ed) { +				*ed_p = ed->hwNextED; +				break; +			} +			ed_p = &(((struct ed *) +				     m32_swap((unsigned long)ed_p))->hwNextED); +		} +	} +} + +/* unlink an ed from one of the HC chains. + * just the link to the ed is unlinked. + * the link from the ed still points to another operational ed or 0 + * so the HC can eventually finish the processing of the unlinked ed */ + +static int ep_unlink(ohci_t *ohci, ed_t *edi) +{ +	volatile ed_t *ed = edi; +	int i; + +	ed->hwINFO |= m32_swap(OHCI_ED_SKIP); + +	switch (ed->type) { +	case PIPE_CONTROL: +		if (ed->ed_prev == NULL) { +			if (!ed->hwNextED) { +				ohci->hc_control &= ~OHCI_CTRL_CLE; +				writel(ohci->hc_control, &ohci->regs->control); +			} +			writel(m32_swap(*((__u32 *)&ed->hwNextED)), +				&ohci->regs->ed_controlhead); +		} else { +			ed->ed_prev->hwNextED = ed->hwNextED; +		} +		if (ohci->ed_controltail == ed) { +			ohci->ed_controltail = ed->ed_prev; +		} else { +			((ed_t *)m32_swap( +			    *((__u32 *)&ed->hwNextED)))->ed_prev = ed->ed_prev; +		} +		break; + +	case PIPE_BULK: +		if (ed->ed_prev == NULL) { +			if (!ed->hwNextED) { +				ohci->hc_control &= ~OHCI_CTRL_BLE; +				writel(ohci->hc_control, &ohci->regs->control); +			} +			writel(m32_swap(*((__u32 *)&ed->hwNextED)), +			       &ohci->regs->ed_bulkhead); +		} else { +			ed->ed_prev->hwNextED = ed->hwNextED; +		} +		if (ohci->ed_bulktail == ed) { +			ohci->ed_bulktail = ed->ed_prev; +		} else { +			((ed_t *)m32_swap( +			     *((__u32 *)&ed->hwNextED)))->ed_prev = ed->ed_prev; +		} +		break; + +	case PIPE_INTERRUPT: +		periodic_unlink(ohci, ed, 0, 1); +		for (i = ed->int_branch; i < 32; i += ed->int_interval) +		    ohci->ohci_int_load[i] -= ed->int_load; +		break; +	} +	ed->state = ED_UNLINK; +	return 0; +} + +/*-------------------------------------------------------------------------*/ + +/* add/reinit an endpoint; this should be done once at the + * usb_set_configuration command, but the USB stack is a little bit + * stateless so we do it at every transaction if the state of the ed + * is ED_NEW then a dummy td is added and the state is changed to + * ED_UNLINK in all other cases the state is left unchanged the ed + * info fields are setted anyway even though most of them should not + * change + */ +static ed_t *ep_add_ed(struct usb_device *usb_dev, unsigned long pipe, +			int interval, int load) +{ +	td_t *td; +	ed_t *ed_ret; +	volatile ed_t *ed; + +	ed = ed_ret = &ohci_dev.ed[(usb_pipeendpoint(pipe) << 1) | +			(usb_pipecontrol(pipe)? 0: usb_pipeout(pipe))]; + +	if ((ed->state & ED_DEL) || (ed->state & ED_URB_DEL)) { +		err("ep_add_ed: pending delete"); +		/* pending delete request */ +		return NULL; +	} + +	if (ed->state == ED_NEW) { +		/* dummy td; end of td list for ed */ +		td = td_alloc(usb_dev); +		ed->hwTailP = m32_swap((unsigned long)td); +		ed->hwHeadP = ed->hwTailP; +		ed->state = ED_UNLINK; +		ed->type = usb_pipetype(pipe); +		ohci_dev.ed_cnt++; +	} + +	ed->hwINFO = m32_swap(usb_pipedevice(pipe) +			| usb_pipeendpoint(pipe) << 7 +			| (usb_pipeisoc(pipe)? 0x8000: 0) +			| (usb_pipecontrol(pipe)? 0: \ +					   (usb_pipeout(pipe)? 0x800: 0x1000)) +			| usb_pipeslow(pipe) << 13 +			| usb_maxpacket(usb_dev, pipe) << 16); + +	if (ed->type == PIPE_INTERRUPT && ed->state == ED_UNLINK) { +		ed->int_period = interval; +		ed->int_load = load; +	} + +	return ed_ret; +} + +/*-------------------------------------------------------------------------* + * TD handling functions + *-------------------------------------------------------------------------*/ + +/* enqueue next TD for this URB (OHCI spec 5.2.8.2) */ + +static void td_fill(ohci_t *ohci, unsigned int info, +	void *data, int len, +	struct usb_device *dev, int index, urb_priv_t *urb_priv) +{ +	volatile td_t  *td, *td_pt; +#ifdef OHCI_FILL_TRACE +	int i; +#endif + +	if (index > urb_priv->length) { +		err("index > length"); +		return; +	} +	/* use this td as the next dummy */ +	td_pt = urb_priv->td [index]; +	td_pt->hwNextTD = 0; + +	/* fill the old dummy TD */ +	td = urb_priv->td [index] = +			     (td_t *)(m32_swap(urb_priv->ed->hwTailP) & ~0xf); + +	td->ed = urb_priv->ed; +	td->next_dl_td = NULL; +	td->index = index; +	td->data = (__u32)data; +#ifdef OHCI_FILL_TRACE +	if (usb_pipebulk(urb_priv->pipe) && usb_pipeout(urb_priv->pipe)) { +		for (i = 0; i < len; i++) +		printf("td->data[%d] %#2x ", i, ((unsigned char *)td->data)[i]); +		printf("\n"); +	} +#endif +	if (!len) +		data = 0; + +	td->hwINFO = m32_swap(info); +	td->hwCBP = m32_swap((unsigned long)data); +	if (data) +		td->hwBE = m32_swap((unsigned long)(data + len - 1)); +	else +		td->hwBE = 0; + +	td->hwNextTD = m32_swap((unsigned long)td_pt); + +	/* append to queue */ +	td->ed->hwTailP = td->hwNextTD; +} + +/*-------------------------------------------------------------------------*/ + +/* prepare all TDs of a transfer */ + +static void td_submit_job(struct usb_device *dev, unsigned long pipe, +			  void *buffer, int transfer_len, +			  struct devrequest *setup, urb_priv_t *urb, +			  int interval) +{ +	ohci_t *ohci = &gohci; +	int data_len = transfer_len; +	void *data; +	int cnt = 0; +	__u32 info = 0; +	unsigned int toggle = 0; + +	/* OHCI handles the DATA-toggles itself, we just use the USB-toggle +	 * bits for reseting */ +	if (usb_gettoggle(dev, usb_pipeendpoint(pipe), usb_pipeout(pipe))) { +		toggle = TD_T_TOGGLE; +	} else { +		toggle = TD_T_DATA0; +		usb_settoggle(dev, usb_pipeendpoint(pipe), +				usb_pipeout(pipe), 1); +	} +	urb->td_cnt = 0; +	if (data_len) +		data = buffer; +	else +		data = 0; + +	switch (usb_pipetype(pipe)) { +	case PIPE_BULK: +		info = usb_pipeout(pipe)? +			TD_CC | TD_DP_OUT : TD_CC | TD_DP_IN ; +		while (data_len > 4096) { +			td_fill(ohci, info | (cnt? TD_T_TOGGLE:toggle), +				data, 4096, dev, cnt, urb); +			data += 4096; data_len -= 4096; cnt++; +		} +		info = usb_pipeout(pipe)? +			TD_CC | TD_DP_OUT : TD_CC | TD_R | TD_DP_IN ; +		td_fill(ohci, info | (cnt? TD_T_TOGGLE:toggle), data, +			data_len, dev, cnt, urb); +		cnt++; + +		if (!ohci->sleeping) { +			/* start bulk list */ +			writel(OHCI_BLF, &ohci->regs->cmdstatus); +		} +		break; + +	case PIPE_CONTROL: +		/* Setup phase */ +		info = TD_CC | TD_DP_SETUP | TD_T_DATA0; +		td_fill(ohci, info, setup, 8, dev, cnt++, urb); + +		/* Optional Data phase */ +		if (data_len > 0) { +			info = usb_pipeout(pipe)? +				TD_CC | TD_R | TD_DP_OUT | TD_T_DATA1 : +				TD_CC | TD_R | TD_DP_IN | TD_T_DATA1; +			/* NOTE:  mishandles transfers >8K, some >4K */ +			td_fill(ohci, info, data, data_len, dev, cnt++, urb); +		} + +		/* Status phase */ +		info = usb_pipeout(pipe)? +			TD_CC | TD_DP_IN | TD_T_DATA1: +			TD_CC | TD_DP_OUT | TD_T_DATA1; +		td_fill(ohci, info, data, 0, dev, cnt++, urb); + +		if (!ohci->sleeping) { +			/* start Control list */ +			writel(OHCI_CLF, &ohci->regs->cmdstatus); +		} +		break; + +	case PIPE_INTERRUPT: +		info = usb_pipeout(urb->pipe)? +			TD_CC | TD_DP_OUT | toggle: +			TD_CC | TD_R | TD_DP_IN | toggle; +		td_fill(ohci, info, data, data_len, dev, cnt++, urb); +		break; +	} +	if (urb->length != cnt) +		dbg("TD LENGTH %d != CNT %d", urb->length, cnt); +} + +/*-------------------------------------------------------------------------* + * Done List handling functions + *-------------------------------------------------------------------------*/ + +/* calculate the transfer length and update the urb */ + +static void dl_transfer_length(td_t *td) +{ +	__u32 tdINFO, tdBE, tdCBP; +	urb_priv_t *lurb_priv = td->ed->purb; + +	tdINFO = m32_swap(td->hwINFO); +	tdBE   = m32_swap(td->hwBE); +	tdCBP  = m32_swap(td->hwCBP); + +	if (!(usb_pipecontrol(lurb_priv->pipe) && +	    ((td->index == 0) || (td->index == lurb_priv->length - 1)))) { +		if (tdBE != 0) { +			if (td->hwCBP == 0) +				lurb_priv->actual_length += tdBE - td->data + 1; +			else +				lurb_priv->actual_length += tdCBP - td->data; +		} +	} +} + +/*-------------------------------------------------------------------------*/ +static void check_status(td_t *td_list) +{ +	urb_priv_t *lurb_priv = td_list->ed->purb; +	int	   urb_len    = lurb_priv->length; +	__u32      *phwHeadP  = &td_list->ed->hwHeadP; +	int	   cc; + +	cc = TD_CC_GET(m32_swap(td_list->hwINFO)); +	if (cc) { +		err(" USB-error: %s (%x)", cc_to_string[cc], cc); + +		if (*phwHeadP & m32_swap(0x1)) { +			if (lurb_priv && +			    ((td_list->index + 1) < urb_len)) { +				*phwHeadP = +					(lurb_priv->td[urb_len - 1]->hwNextTD &\ +							m32_swap(0xfffffff0)) | +						   (*phwHeadP & m32_swap(0x2)); + +				lurb_priv->td_cnt += urb_len - +						     td_list->index - 1; +			} else +				*phwHeadP &= m32_swap(0xfffffff2); +		} +#ifdef CONFIG_MPC5200 +		td_list->hwNextTD = 0; +#endif +	} +} + +/* replies to the request have to be on a FIFO basis so + * we reverse the reversed done-list */ +static td_t *dl_reverse_done_list(ohci_t *ohci) +{ +	__u32 td_list_hc; +	td_t *td_rev = NULL; +	td_t *td_list = NULL; + +	td_list_hc = m32_swap(ohci->hcca->done_head) & 0xfffffff0; +	ohci->hcca->done_head = 0; + +	while (td_list_hc) { +		td_list = (td_t *)td_list_hc; +		check_status(td_list); +		td_list->next_dl_td = td_rev; +		td_rev = td_list; +		td_list_hc = m32_swap(td_list->hwNextTD) & 0xfffffff0; +	} +	return td_list; +} + +/*-------------------------------------------------------------------------*/ +/*-------------------------------------------------------------------------*/ + +static void finish_urb(ohci_t *ohci, urb_priv_t *urb, int status) +{ +	if ((status & (ED_OPER | ED_UNLINK)) && (urb->state != URB_DEL)) +		urb->finished = sohci_return_job(ohci, urb); +	else +		dbg("finish_urb: strange.., ED state %x, \n", status); +} + +/* + * Used to take back a TD from the host controller. This would normally be + * called from within dl_done_list, however it may be called directly if the + * HC no longer sees the TD and it has not appeared on the donelist (after + * two frames).  This bug has been observed on ZF Micro systems. + */ +static int takeback_td(ohci_t *ohci, td_t *td_list) +{ +	ed_t *ed; +	int cc; +	int stat = 0; +	/* urb_t *urb; */ +	urb_priv_t *lurb_priv; +	__u32 tdINFO, edHeadP, edTailP; + +	tdINFO = m32_swap(td_list->hwINFO); + +	ed = td_list->ed; +	lurb_priv = ed->purb; + +	dl_transfer_length(td_list); + +	lurb_priv->td_cnt++; + +	/* error code of transfer */ +	cc = TD_CC_GET(tdINFO); +	if (cc) { +		err("USB-error: %s (%x)", cc_to_string[cc], cc); +		stat = cc_to_error[cc]; +	} + +	/* see if this done list makes for all TD's of current URB, +	* and mark the URB finished if so */ +	if (lurb_priv->td_cnt == lurb_priv->length) +		finish_urb(ohci, lurb_priv, ed->state); + +	dbg("dl_done_list: processing TD %x, len %x\n", +		lurb_priv->td_cnt, lurb_priv->length); + +	if (ed->state != ED_NEW && (!usb_pipeint(lurb_priv->pipe))) { +		edHeadP = m32_swap(ed->hwHeadP) & 0xfffffff0; +		edTailP = m32_swap(ed->hwTailP); + +		/* unlink eds if they are not busy */ +		if ((edHeadP == edTailP) && (ed->state == ED_OPER)) +			ep_unlink(ohci, ed); +	} +	return stat; +} + +static int dl_done_list(ohci_t *ohci) +{ +	int stat = 0; +	td_t	*td_list = dl_reverse_done_list(ohci); + +	while (td_list) { +		td_t	*td_next = td_list->next_dl_td; +		stat = takeback_td(ohci, td_list); +		td_list = td_next; +	} +	return stat; +} + +/*-------------------------------------------------------------------------* + * Virtual Root Hub + *-------------------------------------------------------------------------*/ + +/* Device descriptor */ +static __u8 root_hub_dev_des[] = +{ +	0x12,	    /*	__u8  bLength; */ +	0x01,	    /*	__u8  bDescriptorType; Device */ +	0x10,	    /*	__u16 bcdUSB; v1.1 */ +	0x01, +	0x09,	    /*	__u8  bDeviceClass; HUB_CLASSCODE */ +	0x00,	    /*	__u8  bDeviceSubClass; */ +	0x00,	    /*	__u8  bDeviceProtocol; */ +	0x08,	    /*	__u8  bMaxPacketSize0; 8 Bytes */ +	0x00,	    /*	__u16 idVendor; */ +	0x00, +	0x00,	    /*	__u16 idProduct; */ +	0x00, +	0x00,	    /*	__u16 bcdDevice; */ +	0x00, +	0x00,	    /*	__u8  iManufacturer; */ +	0x01,	    /*	__u8  iProduct; */ +	0x00,	    /*	__u8  iSerialNumber; */ +	0x01	    /*	__u8  bNumConfigurations; */ +}; + +/* Configuration descriptor */ +static __u8 root_hub_config_des[] = +{ +	0x09,	    /*	__u8  bLength; */ +	0x02,	    /*	__u8  bDescriptorType; Configuration */ +	0x19,	    /*	__u16 wTotalLength; */ +	0x00, +	0x01,	    /*	__u8  bNumInterfaces; */ +	0x01,	    /*	__u8  bConfigurationValue; */ +	0x00,	    /*	__u8  iConfiguration; */ +	0x40,	    /*	__u8  bmAttributes; +	 Bit 7: Bus-powered, 6: Self-powered, 5 Remote-wakwup, 4..0: resvd */ +	0x00,	    /*	__u8  MaxPower; */ + +	/* interface */ +	0x09,	    /*	__u8  if_bLength; */ +	0x04,	    /*	__u8  if_bDescriptorType; Interface */ +	0x00,	    /*	__u8  if_bInterfaceNumber; */ +	0x00,	    /*	__u8  if_bAlternateSetting; */ +	0x01,	    /*	__u8  if_bNumEndpoints; */ +	0x09,	    /*	__u8  if_bInterfaceClass; HUB_CLASSCODE */ +	0x00,	    /*	__u8  if_bInterfaceSubClass; */ +	0x00,	    /*	__u8  if_bInterfaceProtocol; */ +	0x00,	    /*	__u8  if_iInterface; */ + +	/* endpoint */ +	0x07,	    /*	__u8  ep_bLength; */ +	0x05,	    /*	__u8  ep_bDescriptorType; Endpoint */ +	0x81,	    /*	__u8  ep_bEndpointAddress; IN Endpoint 1 */ +	0x03,	    /*	__u8  ep_bmAttributes; Interrupt */ +	0x02,	    /*	__u16 ep_wMaxPacketSize; ((MAX_ROOT_PORTS + 1) / 8 */ +	0x00, +	0xff	    /*	__u8  ep_bInterval; 255 ms */ +}; + +static unsigned char root_hub_str_index0[] = +{ +	0x04,			/*  __u8  bLength; */ +	0x03,			/*  __u8  bDescriptorType; String-descriptor */ +	0x09,			/*  __u8  lang ID */ +	0x04,			/*  __u8  lang ID */ +}; + +static unsigned char root_hub_str_index1[] = +{ +	28,			/*  __u8  bLength; */ +	0x03,			/*  __u8  bDescriptorType; String-descriptor */ +	'O',			/*  __u8  Unicode */ +	0,				/*  __u8  Unicode */ +	'H',			/*  __u8  Unicode */ +	0,				/*  __u8  Unicode */ +	'C',			/*  __u8  Unicode */ +	0,				/*  __u8  Unicode */ +	'I',			/*  __u8  Unicode */ +	0,				/*  __u8  Unicode */ +	' ',			/*  __u8  Unicode */ +	0,				/*  __u8  Unicode */ +	'R',			/*  __u8  Unicode */ +	0,				/*  __u8  Unicode */ +	'o',			/*  __u8  Unicode */ +	0,				/*  __u8  Unicode */ +	'o',			/*  __u8  Unicode */ +	0,				/*  __u8  Unicode */ +	't',			/*  __u8  Unicode */ +	0,				/*  __u8  Unicode */ +	' ',			/*  __u8  Unicode */ +	0,				/*  __u8  Unicode */ +	'H',			/*  __u8  Unicode */ +	0,				/*  __u8  Unicode */ +	'u',			/*  __u8  Unicode */ +	0,				/*  __u8  Unicode */ +	'b',			/*  __u8  Unicode */ +	0,				/*  __u8  Unicode */ +}; + +/* Hub class-specific descriptor is constructed dynamically */ + +/*-------------------------------------------------------------------------*/ + +#define OK(x)			len = (x); break +#ifdef DEBUG +#define WR_RH_STAT(x)		{info("WR:status %#8x", (x)); writel((x), \ +						&gohci.regs->roothub.status); } +#define WR_RH_PORTSTAT(x)	{info("WR:portstatus[%d] %#8x", wIndex-1, \ +		(x)); writel((x), &gohci.regs->roothub.portstatus[wIndex-1]); } +#else +#define WR_RH_STAT(x)		writel((x), &gohci.regs->roothub.status) +#define WR_RH_PORTSTAT(x)	writel((x), \ +				    &gohci.regs->roothub.portstatus[wIndex-1]) +#endif +#define RD_RH_STAT		roothub_status(&gohci) +#define RD_RH_PORTSTAT		roothub_portstatus(&gohci, wIndex-1) + +/* request to virtual root hub */ + +int rh_check_port_status(ohci_t *controller) +{ +	__u32 temp, ndp, i; +	int res; + +	res = -1; +	temp = roothub_a(controller); +	ndp = (temp & RH_A_NDP); +#ifdef CONFIG_AT91C_PQFP_UHPBUG +	ndp = (ndp == 2) ? 1:0; +#endif +	for (i = 0; i < ndp; i++) { +		temp = roothub_portstatus(controller, i); +		/* check for a device disconnect */ +		if (((temp & (RH_PS_PESC | RH_PS_CSC)) == +			(RH_PS_PESC | RH_PS_CSC)) && +			((temp & RH_PS_CCS) == 0)) { +			res = i; +			break; +		} +	} +	return res; +} + +static int ohci_submit_rh_msg(struct usb_device *dev, unsigned long pipe, +		void *buffer, int transfer_len, struct devrequest *cmd) +{ +	void *data = buffer; +	int leni = transfer_len; +	int len = 0; +	int stat = 0; +	__u32 datab[4]; +	__u8 *data_buf = (__u8 *)datab; +	__u16 bmRType_bReq; +	__u16 wValue; +	__u16 wIndex; +	__u16 wLength; + +#ifdef DEBUG +pkt_print(NULL, dev, pipe, buffer, transfer_len, +	  cmd, "SUB(rh)", usb_pipein(pipe)); +#else +	wait_ms(1); +#endif +	if (usb_pipeint(pipe)) { +		info("Root-Hub submit IRQ: NOT implemented"); +		return 0; +	} + +	bmRType_bReq  = cmd->requesttype | (cmd->request << 8); +	wValue	      = le16_to_cpu(cmd->value); +	wIndex	      = le16_to_cpu(cmd->index); +	wLength	      = le16_to_cpu(cmd->length); + +	info("Root-Hub: adr: %2x cmd(%1x): %08x %04x %04x %04x", +		dev->devnum, 8, bmRType_bReq, wValue, wIndex, wLength); + +	switch (bmRType_bReq) { +	/* Request Destination: +	   without flags: Device, +	   RH_INTERFACE: interface, +	   RH_ENDPOINT: endpoint, +	   RH_CLASS means HUB here, +	   RH_OTHER | RH_CLASS	almost ever means HUB_PORT here +	*/ + +	case RH_GET_STATUS: +		*(__u16 *) data_buf = cpu_to_le16(1); +		OK(2); +	case RH_GET_STATUS | RH_INTERFACE: +		*(__u16 *) data_buf = cpu_to_le16(0); +		OK(2); +	case RH_GET_STATUS | RH_ENDPOINT: +		*(__u16 *) data_buf = cpu_to_le16(0); +		OK(2); +	case RH_GET_STATUS | RH_CLASS: +		*(__u32 *) data_buf = cpu_to_le32( +				RD_RH_STAT & ~(RH_HS_CRWE | RH_HS_DRWE)); +		OK(4); +	case RH_GET_STATUS | RH_OTHER | RH_CLASS: +		*(__u32 *) data_buf = cpu_to_le32(RD_RH_PORTSTAT); +		OK(4); + +	case RH_CLEAR_FEATURE | RH_ENDPOINT: +		switch (wValue) { +		case (RH_ENDPOINT_STALL): +			OK(0); +		} +		break; + +	case RH_CLEAR_FEATURE | RH_CLASS: +		switch (wValue) { +		case RH_C_HUB_LOCAL_POWER: +			OK(0); +		case (RH_C_HUB_OVER_CURRENT): +			WR_RH_STAT(RH_HS_OCIC); +			OK(0); +		} +		break; + +	case RH_CLEAR_FEATURE | RH_OTHER | RH_CLASS: +		switch (wValue) { +		case (RH_PORT_ENABLE):        WR_RH_PORTSTAT(RH_PS_CCS);  OK(0); +		case (RH_PORT_SUSPEND):       WR_RH_PORTSTAT(RH_PS_POCI); OK(0); +		case (RH_PORT_POWER):         WR_RH_PORTSTAT(RH_PS_LSDA); OK(0); +		case (RH_C_PORT_CONNECTION):  WR_RH_PORTSTAT(RH_PS_CSC);  OK(0); +		case (RH_C_PORT_ENABLE):      WR_RH_PORTSTAT(RH_PS_PESC); OK(0); +		case (RH_C_PORT_SUSPEND):     WR_RH_PORTSTAT(RH_PS_PSSC); OK(0); +		case (RH_C_PORT_OVER_CURRENT):WR_RH_PORTSTAT(RH_PS_OCIC); OK(0); +		case (RH_C_PORT_RESET):       WR_RH_PORTSTAT(RH_PS_PRSC); OK(0); +		} +		break; + +	case RH_SET_FEATURE | RH_OTHER | RH_CLASS: +		switch (wValue) { +		case (RH_PORT_SUSPEND): +			WR_RH_PORTSTAT(RH_PS_PSS);  OK(0); +		case (RH_PORT_RESET): /* BUG IN HUP CODE *********/ +			if (RD_RH_PORTSTAT & RH_PS_CCS) +				WR_RH_PORTSTAT(RH_PS_PRS); +			OK(0); +		case (RH_PORT_POWER): +			WR_RH_PORTSTAT(RH_PS_PPS); +			wait_ms(100); +			OK(0); +		case (RH_PORT_ENABLE): /* BUG IN HUP CODE *********/ +			if (RD_RH_PORTSTAT & RH_PS_CCS) +				WR_RH_PORTSTAT(RH_PS_PES); +			OK(0); +		} +		break; + +	case RH_SET_ADDRESS: +		gohci.rh.devnum = wValue; +		OK(0); + +	case RH_GET_DESCRIPTOR: +		switch ((wValue & 0xff00) >> 8) { +		case (0x01): /* device descriptor */ +			len = min_t(unsigned int, +					leni, +					min_t(unsigned int, +					sizeof(root_hub_dev_des), +					wLength)); +			data_buf = root_hub_dev_des; OK(len); +		case (0x02): /* configuration descriptor */ +			len = min_t(unsigned int, +					leni, +					min_t(unsigned int, +					sizeof(root_hub_config_des), +					wLength)); +			data_buf = root_hub_config_des; OK(len); +		case (0x03): /* string descriptors */ +			if (wValue == 0x0300) { +				len = min_t(unsigned int, +						leni, +						min_t(unsigned int, +						sizeof(root_hub_str_index0), +						wLength)); +				data_buf = root_hub_str_index0; +				OK(len); +			} +			if (wValue == 0x0301) { +				len = min_t(unsigned int, +						leni, +						min_t(unsigned int, +						sizeof(root_hub_str_index1), +						wLength)); +				data_buf = root_hub_str_index1; +				OK(len); +		} +		default: +			stat = USB_ST_STALLED; +		} +		break; + +	case RH_GET_DESCRIPTOR | RH_CLASS: +	{ +		__u32 temp = roothub_a(&gohci); + +		data_buf [0] = 9;		/* min length; */ +		data_buf [1] = 0x29; +		data_buf [2] = temp & RH_A_NDP; +#ifdef CONFIG_AT91C_PQFP_UHPBUG +		data_buf [2] = (data_buf [2] == 2) ? 1:0; +#endif +		data_buf [3] = 0; +		if (temp & RH_A_PSM)	/* per-port power switching? */ +			data_buf [3] |= 0x1; +		if (temp & RH_A_NOCP)	/* no overcurrent reporting? */ +			data_buf [3] |= 0x10; +		else if (temp & RH_A_OCPM)/* per-port overcurrent reporting? */ +			data_buf [3] |= 0x8; + +		/* corresponds to data_buf[4-7] */ +		datab [1] = 0; +		data_buf [5] = (temp & RH_A_POTPGT) >> 24; +		temp = roothub_b(&gohci); +		data_buf [7] = temp & RH_B_DR; +		if (data_buf [2] < 7) { +			data_buf [8] = 0xff; +		} else { +			data_buf [0] += 2; +			data_buf [8] = (temp & RH_B_DR) >> 8; +			data_buf [10] = data_buf [9] = 0xff; +		} + +		len = min_t(unsigned int, leni, +			    min_t(unsigned int, data_buf [0], wLength)); +		OK(len); +	} + +	case RH_GET_CONFIGURATION:	*(__u8 *) data_buf = 0x01; OK(1); + +	case RH_SET_CONFIGURATION:	WR_RH_STAT(0x10000); OK(0); + +	default: +		dbg("unsupported root hub command"); +		stat = USB_ST_STALLED; +	} + +#ifdef	DEBUG +	ohci_dump_roothub(&gohci, 1); +#else +	wait_ms(1); +#endif + +	len = min_t(int, len, leni); +	if (data != data_buf) +	    memcpy(data, data_buf, len); +	dev->act_len = len; +	dev->status = stat; + +#ifdef DEBUG +	pkt_print(NULL, dev, pipe, buffer, +		  transfer_len, cmd, "RET(rh)", 0/*usb_pipein(pipe)*/); +#else +	wait_ms(1); +#endif + +	return stat; +} + +/*-------------------------------------------------------------------------*/ + +/* common code for handling submit messages - used for all but root hub */ +/* accesses. */ +int submit_common_msg(struct usb_device *dev, unsigned long pipe, void *buffer, +		int transfer_len, struct devrequest *setup, int interval) +{ +	int stat = 0; +	int maxsize = usb_maxpacket(dev, pipe); +	int timeout; +	urb_priv_t *urb; + +	urb = malloc(sizeof(urb_priv_t)); +	memset(urb, 0, sizeof(urb_priv_t)); + +	urb->dev = dev; +	urb->pipe = pipe; +	urb->transfer_buffer = buffer; +	urb->transfer_buffer_length = transfer_len; +	urb->interval = interval; + +	/* device pulled? Shortcut the action. */ +	if (devgone == dev) { +		dev->status = USB_ST_CRC_ERR; +		return 0; +	} + +#ifdef DEBUG +	urb->actual_length = 0; +	pkt_print(urb, dev, pipe, buffer, transfer_len, +		  setup, "SUB", usb_pipein(pipe)); +#else +	wait_ms(1); +#endif +	if (!maxsize) { +		err("submit_common_message: pipesize for pipe %lx is zero", +			pipe); +		return -1; +	} + +	if (sohci_submit_job(urb, setup) < 0) { +		err("sohci_submit_job failed"); +		return -1; +	} + +#if 0 +	wait_ms(10); +	/* ohci_dump_status(&gohci); */ +#endif + +	/* allow more time for a BULK device to react - some are slow */ +#define BULK_TO	 5000	/* timeout in milliseconds */ +	if (usb_pipebulk(pipe)) +		timeout = BULK_TO; +	else +		timeout = 100; + +	/* wait for it to complete */ +	for (;;) { +		/* check whether the controller is done */ +		stat = hc_interrupt(); +		if (stat < 0) { +			stat = USB_ST_CRC_ERR; +			break; +		} + +		/* NOTE: since we are not interrupt driven in U-Boot and always +		 * handle only one URB at a time, we cannot assume the +		 * transaction finished on the first successful return from +		 * hc_interrupt().. unless the flag for current URB is set, +		 * meaning that all TD's to/from device got actually +		 * transferred and processed. If the current URB is not +		 * finished we need to re-iterate this loop so as +		 * hc_interrupt() gets called again as there needs to be some +		 * more TD's to process still */ +		if ((stat >= 0) && (stat != 0xff) && (urb->finished)) { +			/* 0xff is returned for an SF-interrupt */ +			break; +		} + +		if (--timeout) { +			wait_ms(1); +			if (!urb->finished) +				dbg("*"); + +		} else { +			err("CTL:TIMEOUT "); +			dbg("submit_common_msg: TO status %x\n", stat); +			urb->finished = 1; +			stat = USB_ST_CRC_ERR; +			break; +		} +	} + +	dev->status = stat; +	dev->act_len = transfer_len; + +#ifdef DEBUG +	pkt_print(urb, dev, pipe, buffer, transfer_len, +		  setup, "RET(ctlr)", usb_pipein(pipe)); +#else +	wait_ms(1); +#endif + +	/* free TDs in urb_priv */ +	if (!usb_pipeint(pipe)) +		urb_free_priv(urb); +	return 0; +} + +/* submit routines called from usb.c */ +int submit_bulk_msg(struct usb_device *dev, unsigned long pipe, void *buffer, +		int transfer_len) +{ +	info("submit_bulk_msg"); +	return submit_common_msg(dev, pipe, buffer, transfer_len, NULL, 0); +} + +int submit_control_msg(struct usb_device *dev, unsigned long pipe, void *buffer, +		int transfer_len, struct devrequest *setup) +{ +	int maxsize = usb_maxpacket(dev, pipe); + +	info("submit_control_msg"); +#ifdef DEBUG +	pkt_print(NULL, dev, pipe, buffer, transfer_len, +		  setup, "SUB", usb_pipein(pipe)); +#else +	wait_ms(1); +#endif +	if (!maxsize) { +		err("submit_control_message: pipesize for pipe %lx is zero", +			pipe); +		return -1; +	} +	if (((pipe >> 8) & 0x7f) == gohci.rh.devnum) { +		gohci.rh.dev = dev; +		/* root hub - redirect */ +		return ohci_submit_rh_msg(dev, pipe, buffer, transfer_len, +			setup); +	} + +	return submit_common_msg(dev, pipe, buffer, transfer_len, setup, 0); +} + +int submit_int_msg(struct usb_device *dev, unsigned long pipe, void *buffer, +		int transfer_len, int interval) +{ +	info("submit_int_msg"); +	return submit_common_msg(dev, pipe, buffer, transfer_len, NULL, +			interval); +} + +/*-------------------------------------------------------------------------* + * HC functions + *-------------------------------------------------------------------------*/ + +/* reset the HC and BUS */ + +static int hc_reset(ohci_t *ohci) +{ +#ifdef CONFIG_PCI_EHCI_DEVNO +	pci_dev_t pdev; +#endif +	int timeout = 30; +	int smm_timeout = 50; /* 0,5 sec */ + +	dbg("%s\n", __FUNCTION__); + +#ifdef CONFIG_PCI_EHCI_DEVNO +	/* +	 *  Some multi-function controllers (e.g. ISP1562) allow root hub +	 * resetting via EHCI registers only. +	 */ +	pdev = pci_find_devices(ehci_pci_ids, CONFIG_PCI_EHCI_DEVNO); +	if (pdev != -1) { +		u32 base; +		int timeout = 1000; + +		pci_read_config_dword(pdev, PCI_BASE_ADDRESS_0, &base); +		writel(readl(base + EHCI_USBCMD_OFF) | EHCI_USBCMD_HCRESET, +			base + EHCI_USBCMD_OFF); + +		while (readl(base + EHCI_USBCMD_OFF) & EHCI_USBCMD_HCRESET) { +			if (timeout-- <= 0) { +				printf("USB RootHub reset timed out!"); +				break; +			} +			udelay(1); +		} +	} else +		printf("No EHCI func at %d index!\n", CONFIG_PCI_EHCI_DEVNO); +#endif +	if (readl(&ohci->regs->control) & OHCI_CTRL_IR) { +		/* SMM owns the HC */ +		writel(OHCI_OCR, &ohci->regs->cmdstatus);/* request ownership */ +		info("USB HC TakeOver from SMM"); +		while (readl(&ohci->regs->control) & OHCI_CTRL_IR) { +			wait_ms(10); +			if (--smm_timeout == 0) { +				err("USB HC TakeOver failed!"); +				return -1; +			} +		} +	} + +	/* Disable HC interrupts */ +	writel(OHCI_INTR_MIE, &ohci->regs->intrdisable); + +	dbg("USB HC reset_hc usb-%s: ctrl = 0x%X ;\n", +		ohci->slot_name, +		readl(&ohci->regs->control)); + +	/* Reset USB (needed by some controllers) */ +	ohci->hc_control = 0; +	writel(ohci->hc_control, &ohci->regs->control); + +	/* HC Reset requires max 10 us delay */ +	writel(OHCI_HCR,  &ohci->regs->cmdstatus); +	while ((readl(&ohci->regs->cmdstatus) & OHCI_HCR) != 0) { +		if (--timeout == 0) { +			err("USB HC reset timed out!"); +			return -1; +		} +		udelay(1); +	} +	return 0; +} + +/*-------------------------------------------------------------------------*/ + +/* Start an OHCI controller, set the BUS operational + * enable interrupts + * connect the virtual root hub */ + +static int hc_start(ohci_t *ohci) +{ +	__u32 mask; +	unsigned int fminterval; + +	ohci->disabled = 1; + +	/* Tell the controller where the control and bulk lists are +	 * The lists are empty now. */ + +	writel(0, &ohci->regs->ed_controlhead); +	writel(0, &ohci->regs->ed_bulkhead); + +	writel((__u32)ohci->hcca, &ohci->regs->hcca); /* a reset clears this */ + +	fminterval = 0x2edf; +	writel((fminterval * 9) / 10, &ohci->regs->periodicstart); +	fminterval |= ((((fminterval - 210) * 6) / 7) << 16); +	writel(fminterval, &ohci->regs->fminterval); +	writel(0x628, &ohci->regs->lsthresh); + +	/* start controller operations */ +	ohci->hc_control = OHCI_CONTROL_INIT | OHCI_USB_OPER; +	ohci->disabled = 0; +	writel(ohci->hc_control, &ohci->regs->control); + +	/* disable all interrupts */ +	mask = (OHCI_INTR_SO | OHCI_INTR_WDH | OHCI_INTR_SF | OHCI_INTR_RD | +			OHCI_INTR_UE | OHCI_INTR_FNO | OHCI_INTR_RHSC | +			OHCI_INTR_OC | OHCI_INTR_MIE); +	writel(mask, &ohci->regs->intrdisable); +	/* clear all interrupts */ +	mask &= ~OHCI_INTR_MIE; +	writel(mask, &ohci->regs->intrstatus); +	/* Choose the interrupts we care about now  - but w/o MIE */ +	mask = OHCI_INTR_RHSC | OHCI_INTR_UE | OHCI_INTR_WDH | OHCI_INTR_SO; +	writel(mask, &ohci->regs->intrenable); + +#ifdef	OHCI_USE_NPS +	/* required for AMD-756 and some Mac platforms */ +	writel((roothub_a(ohci) | RH_A_NPS) & ~RH_A_PSM, +		&ohci->regs->roothub.a); +	writel(RH_HS_LPSC, &ohci->regs->roothub.status); +#endif	/* OHCI_USE_NPS */ + +#define mdelay(n) ({unsigned long msec = (n); while (msec--) udelay(1000); }) +	/* POTPGT delay is bits 24-31, in 2 ms units. */ +	mdelay((roothub_a(ohci) >> 23) & 0x1fe); + +	/* connect the virtual root hub */ +	ohci->rh.devnum = 0; + +	return 0; +} + +/*-------------------------------------------------------------------------*/ + +/* Poll USB interrupt. */ +void usb_event_poll(void) +{ +	hc_interrupt(); +} + +/* an interrupt happens */ + +static int hc_interrupt(void) +{ +	ohci_t *ohci = &gohci; +	struct ohci_regs *regs = ohci->regs; +	int ints; +	int stat = -1; + +	if ((ohci->hcca->done_head != 0) && +				!(m32_swap(ohci->hcca->done_head) & 0x01)) { +		ints =  OHCI_INTR_WDH; +	} else { +		ints = readl(®s->intrstatus); +		if (ints == ~(u32)0) { +			ohci->disabled++; +			err("%s device removed!", ohci->slot_name); +			return -1; +		} else { +			ints &= readl(®s->intrenable); +			if (ints == 0) { +				dbg("hc_interrupt: returning..\n"); +				return 0xff; +			} +		} +	} + +	/* dbg("Interrupt: %x frame: %x", ints, +					le16_to_cpu(ohci->hcca->frame_no)); */ + +	if (ints & OHCI_INTR_RHSC) +		stat = 0xff; + +	if (ints & OHCI_INTR_UE) { +		ohci->disabled++; +		err("OHCI Unrecoverable Error, controller usb-%s disabled", +			ohci->slot_name); +		/* e.g. due to PCI Master/Target Abort */ + +#ifdef	DEBUG +		ohci_dump(ohci, 1); +#else +		wait_ms(1); +#endif +		/* FIXME: be optimistic, hope that bug won't repeat often. */ +		/* Make some non-interrupt context restart the controller. */ +		/* Count and limit the retries though; either hardware or */ +		/* software errors can go forever... */ +		hc_reset(ohci); +		return -1; +	} + +	if (ints & OHCI_INTR_WDH) { +		wait_ms(1); +		writel(OHCI_INTR_WDH, ®s->intrdisable); +		(void)readl(®s->intrdisable); /* flush */ +		stat = dl_done_list(&gohci); +		writel(OHCI_INTR_WDH, ®s->intrenable); +		(void)readl(®s->intrdisable); /* flush */ +	} + +	if (ints & OHCI_INTR_SO) { +		dbg("USB Schedule overrun\n"); +		writel(OHCI_INTR_SO, ®s->intrenable); +		stat = -1; +	} + +	/* FIXME:  this assumes SOF (1/ms) interrupts don't get lost... */ +	if (ints & OHCI_INTR_SF) { +		unsigned int frame = m16_swap(ohci->hcca->frame_no) & 1; +		wait_ms(1); +		writel(OHCI_INTR_SF, ®s->intrdisable); +		if (ohci->ed_rm_list[frame] != NULL) +			writel(OHCI_INTR_SF, ®s->intrenable); +		stat = 0xff; +	} + +	writel(ints, ®s->intrstatus); +	return stat; +} + +/*-------------------------------------------------------------------------*/ + +/*-------------------------------------------------------------------------*/ + +/* De-allocate all resources.. */ + +static void hc_release_ohci(ohci_t *ohci) +{ +	dbg("USB HC release ohci usb-%s", ohci->slot_name); + +	if (!ohci->disabled) +		hc_reset(ohci); +} + +/*-------------------------------------------------------------------------*/ + +/* + * low level initalisation routine, called from usb.c + */ +static char ohci_inited = 0; + +int usb_lowlevel_init(void) +{ +#ifdef CONFIG_PCI_OHCI +	pci_dev_t pdev; +#endif + +#ifdef CONFIG_SYS_USB_OHCI_CPU_INIT +	/* cpu dependant init */ +	if (usb_cpu_init()) +		return -1; +#endif + +#ifdef CONFIG_SYS_USB_OHCI_BOARD_INIT +	/*  board dependant init */ +	if (usb_board_init()) +		return -1; +#endif +	memset(&gohci, 0, sizeof(ohci_t)); + +	/* align the storage */ +	if ((__u32)&ghcca[0] & 0xff) { +		err("HCCA not aligned!!"); +		return -1; +	} +	phcca = &ghcca[0]; +	info("aligned ghcca %p", phcca); +	memset(&ohci_dev, 0, sizeof(struct ohci_device)); +	if ((__u32)&ohci_dev.ed[0] & 0x7) { +		err("EDs not aligned!!"); +		return -1; +	} +	memset(gtd, 0, sizeof(td_t) * (NUM_TD + 1)); +	if ((__u32)gtd & 0x7) { +		err("TDs not aligned!!"); +		return -1; +	} +	ptd = gtd; +	gohci.hcca = phcca; +	memset(phcca, 0, sizeof(struct ohci_hcca)); + +	gohci.disabled = 1; +	gohci.sleeping = 0; +	gohci.irq = -1; +#ifdef CONFIG_PCI_OHCI +	pdev = pci_find_devices(ohci_pci_ids, CONFIG_PCI_OHCI_DEVNO); + +	if (pdev != -1) { +		u16 vid, did; +		u32 base; +		pci_read_config_word(pdev, PCI_VENDOR_ID, &vid); +		pci_read_config_word(pdev, PCI_DEVICE_ID, &did); +		printf("OHCI pci controller (%04x, %04x) found @(%d:%d:%d)\n", +				vid, did, (pdev >> 16) & 0xff, +				(pdev >> 11) & 0x1f, (pdev >> 8) & 0x7); +		pci_read_config_dword(pdev, PCI_BASE_ADDRESS_0, &base); +		printf("OHCI regs address 0x%08x\n", base); +		gohci.regs = (struct ohci_regs *)base; +	} else +		return -1; +#else +	gohci.regs = (struct ohci_regs *)CONFIG_SYS_USB_OHCI_REGS_BASE; +#endif + +	gohci.flags = 0; +	gohci.slot_name = CONFIG_SYS_USB_OHCI_SLOT_NAME; + +	if (hc_reset (&gohci) < 0) { +		hc_release_ohci (&gohci); +		err ("can't reset usb-%s", gohci.slot_name); +#ifdef CONFIG_SYS_USB_OHCI_BOARD_INIT +		/* board dependant cleanup */ +		usb_board_init_fail(); +#endif + +#ifdef CONFIG_SYS_USB_OHCI_CPU_INIT +		/* cpu dependant cleanup */ +		usb_cpu_init_fail(); +#endif +		return -1; +	} + +	if (hc_start(&gohci) < 0) { +		err("can't start usb-%s", gohci.slot_name); +		hc_release_ohci(&gohci); +		/* Initialization failed */ +#ifdef CONFIG_SYS_USB_OHCI_BOARD_INIT +		/* board dependant cleanup */ +		usb_board_stop(); +#endif + +#ifdef CONFIG_SYS_USB_OHCI_CPU_INIT +		/* cpu dependant cleanup */ +		usb_cpu_stop(); +#endif +		return -1; +	} + +#ifdef	DEBUG +	ohci_dump(&gohci, 1); +#else +	wait_ms(1); +#endif +	ohci_inited = 1; +	return 0; +} + +int usb_lowlevel_stop(void) +{ +	/* this gets called really early - before the controller has */ +	/* even been initialized! */ +	if (!ohci_inited) +		return 0; +	/* TODO release any interrupts, etc. */ +	/* call hc_release_ohci() here ? */ +	hc_reset(&gohci); + +#ifdef CONFIG_SYS_USB_OHCI_BOARD_INIT +	/* board dependant cleanup */ +	if (usb_board_stop()) +		return -1; +#endif + +#ifdef CONFIG_SYS_USB_OHCI_CPU_INIT +	/* cpu dependant cleanup */ +	if (usb_cpu_stop()) +		return -1; +#endif +	/* This driver is no longer initialised. It needs a new low-level +	 * init (board/cpu) before it can be used again. */ +	ohci_inited = 0; +	return 0; +} diff --git a/drivers/usb/host/ohci.h b/drivers/usb/host/ohci.h new file mode 100644 index 000000000..a547337e7 --- /dev/null +++ b/drivers/usb/host/ohci.h @@ -0,0 +1,483 @@ +/* + * URB OHCI HCD (Host Controller Driver) for USB. + * + * (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at> + * (C) Copyright 2000-2001 David Brownell <dbrownell@users.sourceforge.net> + * + * usb-ohci.h + */ + +/* functions for doing board or CPU specific setup/cleanup */ +extern int usb_board_init(void); +extern int usb_board_stop(void); +extern int usb_board_init_fail(void); + +extern int usb_cpu_init(void); +extern int usb_cpu_stop(void); +extern int usb_cpu_init_fail(void); + + +static int cc_to_error[16] = { + +/* mapping of the OHCI CC status to error codes */ +	/* No  Error  */	       0, +	/* CRC Error  */	       USB_ST_CRC_ERR, +	/* Bit Stuff  */	       USB_ST_BIT_ERR, +	/* Data Togg  */	       USB_ST_CRC_ERR, +	/* Stall      */	       USB_ST_STALLED, +	/* DevNotResp */	       -1, +	/* PIDCheck   */	       USB_ST_BIT_ERR, +	/* UnExpPID   */	       USB_ST_BIT_ERR, +	/* DataOver   */	       USB_ST_BUF_ERR, +	/* DataUnder  */	       USB_ST_BUF_ERR, +	/* reservd    */	       -1, +	/* reservd    */	       -1, +	/* BufferOver */	       USB_ST_BUF_ERR, +	/* BuffUnder  */	       USB_ST_BUF_ERR, +	/* Not Access */	       -1, +	/* Not Access */	       -1 +}; + +static const char *cc_to_string[16] = { +	"No Error", +	"CRC: Last data packet from endpoint contained a CRC error.", +	"BITSTUFFING: Last data packet from endpoint contained a bit " \ +		     "stuffing violation", +	"DATATOGGLEMISMATCH: Last packet from endpoint had data toggle PID\n" \ +		     "that did not match the expected value.", +	"STALL: TD was moved to the Done Queue because the endpoint returned" \ +		     " a STALL PID", +	"DEVICENOTRESPONDING: Device did not respond to token (IN) or did\n" \ +		     "not provide a handshake (OUT)", +	"PIDCHECKFAILURE: Check bits on PID from endpoint failed on data PID\n"\ +		     "(IN) or handshake (OUT)", +	"UNEXPECTEDPID: Receive PID was not valid when encountered or PID\n" \ +		     "value is not defined.", +	"DATAOVERRUN: The amount of data returned by the endpoint exceeded\n" \ +		     "either the size of the maximum data packet allowed\n" \ +		     "from the endpoint (found in MaximumPacketSize field\n" \ +		     "of ED) or the remaining buffer size.", +	"DATAUNDERRUN: The endpoint returned less than MaximumPacketSize\n" \ +		     "and that amount was not sufficient to fill the\n" \ +		     "specified buffer", +	"reserved1", +	"reserved2", +	"BUFFEROVERRUN: During an IN, HC received data from endpoint faster\n" \ +		     "than it could be written to system memory", +	"BUFFERUNDERRUN: During an OUT, HC could not retrieve data from\n" \ +		     "system memory fast enough to keep up with data USB " \ +		     "data rate.", +	"NOT ACCESSED: This code is set by software before the TD is placed" \ +		     "on a list to be processed by the HC.(1)", +	"NOT ACCESSED: This code is set by software before the TD is placed" \ +		     "on a list to be processed by the HC.(2)", +}; + +/* ED States */ + +#define ED_NEW		0x00 +#define ED_UNLINK	0x01 +#define ED_OPER		0x02 +#define ED_DEL		0x04 +#define ED_URB_DEL	0x08 + +/* usb_ohci_ed */ +struct ed { +	__u32 hwINFO; +	__u32 hwTailP; +	__u32 hwHeadP; +	__u32 hwNextED; + +	struct ed *ed_prev; +	__u8 int_period; +	__u8 int_branch; +	__u8 int_load; +	__u8 int_interval; +	__u8 state; +	__u8 type; +	__u16 last_iso; +	struct ed *ed_rm_list; + +	struct usb_device *usb_dev; +	void *purb; +	__u32 unused[2]; +} __attribute((aligned(16))); +typedef struct ed ed_t; + + +/* TD info field */ +#define TD_CC	    0xf0000000 +#define TD_CC_GET(td_p) ((td_p >>28) & 0x0f) +#define TD_CC_SET(td_p, cc) (td_p) = ((td_p) & 0x0fffffff) | (((cc) & 0x0f) << 28) +#define TD_EC	    0x0C000000 +#define TD_T	    0x03000000 +#define TD_T_DATA0  0x02000000 +#define TD_T_DATA1  0x03000000 +#define TD_T_TOGGLE 0x00000000 +#define TD_R	    0x00040000 +#define TD_DI	    0x00E00000 +#define TD_DI_SET(X) (((X) & 0x07)<< 21) +#define TD_DP	    0x00180000 +#define TD_DP_SETUP 0x00000000 +#define TD_DP_IN    0x00100000 +#define TD_DP_OUT   0x00080000 + +#define TD_ISO	    0x00010000 +#define TD_DEL	    0x00020000 + +/* CC Codes */ +#define TD_CC_NOERROR	   0x00 +#define TD_CC_CRC	   0x01 +#define TD_CC_BITSTUFFING  0x02 +#define TD_CC_DATATOGGLEM  0x03 +#define TD_CC_STALL	   0x04 +#define TD_DEVNOTRESP	   0x05 +#define TD_PIDCHECKFAIL	   0x06 +#define TD_UNEXPECTEDPID   0x07 +#define TD_DATAOVERRUN	   0x08 +#define TD_DATAUNDERRUN	   0x09 +#define TD_BUFFEROVERRUN   0x0C +#define TD_BUFFERUNDERRUN  0x0D +#define TD_NOTACCESSED	   0x0F + + +#define MAXPSW 1 + +struct td { +	__u32 hwINFO; +	__u32 hwCBP;		/* Current Buffer Pointer */ +	__u32 hwNextTD;		/* Next TD Pointer */ +	__u32 hwBE;		/* Memory Buffer End Pointer */ + +/* #ifndef CONFIG_MPC5200 /\* this seems wrong *\/ */ +	__u16 hwPSW[MAXPSW]; +/* #endif */ +	__u8 unused; +	__u8 index; +	struct ed *ed; +	struct td *next_dl_td; +	struct usb_device *usb_dev; +	int transfer_len; +	__u32 data; + +	__u32 unused2[2]; +} __attribute((aligned(32))); +typedef struct td td_t; + +#define OHCI_ED_SKIP	(1 << 14) + +/* + * The HCCA (Host Controller Communications Area) is a 256 byte + * structure defined in the OHCI spec. that the host controller is + * told the base address of.  It must be 256-byte aligned. + */ + +#define NUM_INTS 32	/* part of the OHCI standard */ +struct ohci_hcca { +	__u32	int_table[NUM_INTS];	/* Interrupt ED table */ +#if defined(CONFIG_MPC5200) +	__u16	pad1;			/* set to 0 on each frame_no change */ +	__u16	frame_no;		/* current frame number */ +#else +	__u16	frame_no;		/* current frame number */ +	__u16	pad1;			/* set to 0 on each frame_no change */ +#endif +	__u32	done_head;		/* info returned for an interrupt */ +	u8		reserved_for_hc[116]; +} __attribute((aligned(256))); + + +/* + * Maximum number of root hub ports. + */ +#ifndef CONFIG_SYS_USB_OHCI_MAX_ROOT_PORTS +# error "CONFIG_SYS_USB_OHCI_MAX_ROOT_PORTS undefined!" +#endif + +/* + * This is the structure of the OHCI controller's memory mapped I/O + * region.  This is Memory Mapped I/O.	You must use the readl() and + * writel() macros defined in asm/io.h to access these!! + */ +struct ohci_regs { +	/* control and status registers */ +	__u32	revision; +	__u32	control; +	__u32	cmdstatus; +	__u32	intrstatus; +	__u32	intrenable; +	__u32	intrdisable; +	/* memory pointers */ +	__u32	hcca; +	__u32	ed_periodcurrent; +	__u32	ed_controlhead; +	__u32	ed_controlcurrent; +	__u32	ed_bulkhead; +	__u32	ed_bulkcurrent; +	__u32	donehead; +	/* frame counters */ +	__u32	fminterval; +	__u32	fmremaining; +	__u32	fmnumber; +	__u32	periodicstart; +	__u32	lsthresh; +	/* Root hub ports */ +	struct	ohci_roothub_regs { +		__u32	a; +		__u32	b; +		__u32	status; +		__u32	portstatus[CONFIG_SYS_USB_OHCI_MAX_ROOT_PORTS]; +	} roothub; +} __attribute((aligned(32))); + +/* Some EHCI controls */ +#define EHCI_USBCMD_OFF		0x20 +#define EHCI_USBCMD_HCRESET	(1 << 1) + +/* OHCI CONTROL AND STATUS REGISTER MASKS */ + +/* + * HcControl (control) register masks + */ +#define OHCI_CTRL_CBSR	(3 << 0)	/* control/bulk service ratio */ +#define OHCI_CTRL_PLE	(1 << 2)	/* periodic list enable */ +#define OHCI_CTRL_IE	(1 << 3)	/* isochronous enable */ +#define OHCI_CTRL_CLE	(1 << 4)	/* control list enable */ +#define OHCI_CTRL_BLE	(1 << 5)	/* bulk list enable */ +#define OHCI_CTRL_HCFS	(3 << 6)	/* host controller functional state */ +#define OHCI_CTRL_IR	(1 << 8)	/* interrupt routing */ +#define OHCI_CTRL_RWC	(1 << 9)	/* remote wakeup connected */ +#define OHCI_CTRL_RWE	(1 << 10)	/* remote wakeup enable */ + +/* pre-shifted values for HCFS */ +#	define OHCI_USB_RESET	(0 << 6) +#	define OHCI_USB_RESUME	(1 << 6) +#	define OHCI_USB_OPER	(2 << 6) +#	define OHCI_USB_SUSPEND (3 << 6) + +/* + * HcCommandStatus (cmdstatus) register masks + */ +#define OHCI_HCR	(1 << 0)	/* host controller reset */ +#define OHCI_CLF	(1 << 1)	/* control list filled */ +#define OHCI_BLF	(1 << 2)	/* bulk list filled */ +#define OHCI_OCR	(1 << 3)	/* ownership change request */ +#define OHCI_SOC	(3 << 16)	/* scheduling overrun count */ + +/* + * masks used with interrupt registers: + * HcInterruptStatus (intrstatus) + * HcInterruptEnable (intrenable) + * HcInterruptDisable (intrdisable) + */ +#define OHCI_INTR_SO	(1 << 0)	/* scheduling overrun */ +#define OHCI_INTR_WDH	(1 << 1)	/* writeback of done_head */ +#define OHCI_INTR_SF	(1 << 2)	/* start frame */ +#define OHCI_INTR_RD	(1 << 3)	/* resume detect */ +#define OHCI_INTR_UE	(1 << 4)	/* unrecoverable error */ +#define OHCI_INTR_FNO	(1 << 5)	/* frame number overflow */ +#define OHCI_INTR_RHSC	(1 << 6)	/* root hub status change */ +#define OHCI_INTR_OC	(1 << 30)	/* ownership change */ +#define OHCI_INTR_MIE	(1 << 31)	/* master interrupt enable */ + + +/* Virtual Root HUB */ +struct virt_root_hub { +	int devnum; /* Address of Root Hub endpoint */ +	void *dev;  /* was urb */ +	void *int_addr; +	int send; +	int interval; +}; + +/* USB HUB CONSTANTS (not OHCI-specific; see hub.h) */ + +/* destination of request */ +#define RH_INTERFACE		   0x01 +#define RH_ENDPOINT		   0x02 +#define RH_OTHER		   0x03 + +#define RH_CLASS		   0x20 +#define RH_VENDOR		   0x40 + +/* Requests: bRequest << 8 | bmRequestType */ +#define RH_GET_STATUS		0x0080 +#define RH_CLEAR_FEATURE	0x0100 +#define RH_SET_FEATURE		0x0300 +#define RH_SET_ADDRESS		0x0500 +#define RH_GET_DESCRIPTOR	0x0680 +#define RH_SET_DESCRIPTOR	0x0700 +#define RH_GET_CONFIGURATION	0x0880 +#define RH_SET_CONFIGURATION	0x0900 +#define RH_GET_STATE		0x0280 +#define RH_GET_INTERFACE	0x0A80 +#define RH_SET_INTERFACE	0x0B00 +#define RH_SYNC_FRAME		0x0C80 +/* Our Vendor Specific Request */ +#define RH_SET_EP		0x2000 + + +/* Hub port features */ +#define RH_PORT_CONNECTION	   0x00 +#define RH_PORT_ENABLE		   0x01 +#define RH_PORT_SUSPEND		   0x02 +#define RH_PORT_OVER_CURRENT	   0x03 +#define RH_PORT_RESET		   0x04 +#define RH_PORT_POWER		   0x08 +#define RH_PORT_LOW_SPEED	   0x09 + +#define RH_C_PORT_CONNECTION	   0x10 +#define RH_C_PORT_ENABLE	   0x11 +#define RH_C_PORT_SUSPEND	   0x12 +#define RH_C_PORT_OVER_CURRENT	   0x13 +#define RH_C_PORT_RESET		   0x14 + +/* Hub features */ +#define RH_C_HUB_LOCAL_POWER	   0x00 +#define RH_C_HUB_OVER_CURRENT	   0x01 + +#define RH_DEVICE_REMOTE_WAKEUP	   0x00 +#define RH_ENDPOINT_STALL	   0x01 + +#define RH_ACK			   0x01 +#define RH_REQ_ERR		   -1 +#define RH_NACK			   0x00 + + +/* OHCI ROOT HUB REGISTER MASKS */ + +/* roothub.portstatus [i] bits */ +#define RH_PS_CCS	     0x00000001		/* current connect status */ +#define RH_PS_PES	     0x00000002		/* port enable status*/ +#define RH_PS_PSS	     0x00000004		/* port suspend status */ +#define RH_PS_POCI	     0x00000008		/* port over current indicator */ +#define RH_PS_PRS	     0x00000010		/* port reset status */ +#define RH_PS_PPS	     0x00000100		/* port power status */ +#define RH_PS_LSDA	     0x00000200		/* low speed device attached */ +#define RH_PS_CSC	     0x00010000		/* connect status change */ +#define RH_PS_PESC	     0x00020000		/* port enable status change */ +#define RH_PS_PSSC	     0x00040000		/* port suspend status change */ +#define RH_PS_OCIC	     0x00080000		/* over current indicator change */ +#define RH_PS_PRSC	     0x00100000		/* port reset status change */ + +/* roothub.status bits */ +#define RH_HS_LPS	     0x00000001		/* local power status */ +#define RH_HS_OCI	     0x00000002		/* over current indicator */ +#define RH_HS_DRWE	     0x00008000		/* device remote wakeup enable */ +#define RH_HS_LPSC	     0x00010000		/* local power status change */ +#define RH_HS_OCIC	     0x00020000		/* over current indicator change */ +#define RH_HS_CRWE	     0x80000000		/* clear remote wakeup enable */ + +/* roothub.b masks */ +#define RH_B_DR		0x0000ffff		/* device removable flags */ +#define RH_B_PPCM	0xffff0000		/* port power control mask */ + +/* roothub.a masks */ +#define RH_A_NDP	(0xff << 0)		/* number of downstream ports */ +#define RH_A_PSM	(1 << 8)		/* power switching mode */ +#define RH_A_NPS	(1 << 9)		/* no power switching */ +#define RH_A_DT		(1 << 10)		/* device type (mbz) */ +#define RH_A_OCPM	(1 << 11)		/* over current protection mode */ +#define RH_A_NOCP	(1 << 12)		/* no over current protection */ +#define RH_A_POTPGT	(0xff << 24)		/* power on to power good time */ + +/* urb */ +#define N_URB_TD 48 +typedef struct +{ +	ed_t *ed; +	__u16 length;	/* number of tds associated with this request */ +	__u16 td_cnt;	/* number of tds already serviced */ +	struct usb_device *dev; +	int   state; +	unsigned long pipe; +	void *transfer_buffer; +	int transfer_buffer_length; +	int interval; +	int actual_length; +	int finished; +	td_t *td[N_URB_TD];	/* list pointer to all corresponding TDs associated with this request */ +} urb_priv_t; +#define URB_DEL 1 + +/* + * This is the full ohci controller description + * + * Note how the "proper" USB information is just + * a subset of what the full implementation needs. (Linus) + */ + + +typedef struct ohci { +	struct ohci_hcca *hcca;		/* hcca */ +	/*dma_addr_t hcca_dma;*/ + +	int irq; +	int disabled;			/* e.g. got a UE, we're hung */ +	int sleeping; +	unsigned long flags;		/* for HC bugs */ + +	struct ohci_regs *regs; /* OHCI controller's memory */ + +	int ohci_int_load[32];	 /* load of the 32 Interrupt Chains (for load balancing)*/ +	ed_t *ed_rm_list[2];	 /* lists of all endpoints to be removed */ +	ed_t *ed_bulktail;	 /* last endpoint of bulk list */ +	ed_t *ed_controltail;	 /* last endpoint of control list */ +	int intrstatus; +	__u32 hc_control;		/* copy of the hc control reg */ +	struct usb_device *dev[32]; +	struct virt_root_hub rh; + +	const char	*slot_name; +} ohci_t; + +#define NUM_EDS 8		/* num of preallocated endpoint descriptors */ + +struct ohci_device { +	ed_t	ed[NUM_EDS]; +	int ed_cnt; +}; + +/* hcd */ +/* endpoint */ +static int ep_link(ohci_t * ohci, ed_t * ed); +static int ep_unlink(ohci_t * ohci, ed_t * ed); +static ed_t * ep_add_ed(struct usb_device * usb_dev, unsigned long pipe, +		int interval, int load); + +/*-------------------------------------------------------------------------*/ + +/* we need more TDs than EDs */ +#define NUM_TD 64 + +/* +1 so we can align the storage */ +td_t gtd[NUM_TD+1]; +/* pointers to aligned storage */ +td_t *ptd; + +/* TDs ... */ +static inline struct td * +td_alloc (struct usb_device *usb_dev) +{ +	int i; +	struct td	*td; + +	td = NULL; +	for (i = 0; i < NUM_TD; i++) +	{ +		if (ptd[i].usb_dev == NULL) +		{ +			td = &ptd[i]; +			td->usb_dev = usb_dev; +			break; +		} +	} + +	return td; +} + +static inline void +ed_free (struct ed *ed) +{ +	ed->usb_dev = NULL; +} diff --git a/drivers/usb/host/r8a66597-hcd.c b/drivers/usb/host/r8a66597-hcd.c new file mode 100644 index 000000000..db611b641 --- /dev/null +++ b/drivers/usb/host/r8a66597-hcd.c @@ -0,0 +1,945 @@ +/* + * R8A66597 HCD (Host Controller Driver) for u-boot + * + * Copyright (C) 2008  Yoshihiro Shimoda <shimoda.yoshihiro@renesas.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA + * + */ + +#include <common.h> +#include <usb.h> +#include <asm/io.h> + +#include "r8a66597.h" + +#ifdef R8A66597_DEBUG +#define R8A66597_DPRINT		printf +#else +#define R8A66597_DPRINT(...) +#endif + +static const char hcd_name[] = "r8a66597_hcd"; +static unsigned short clock = CONFIG_R8A66597_XTAL; +static unsigned short vif = CONFIG_R8A66597_LDRV; +static unsigned short endian = CONFIG_R8A66597_ENDIAN; +static struct r8a66597 gr8a66597; + +static void get_hub_data(struct usb_device *dev, u16 *hub_devnum, u16 *hubport) +{ +	int i; + +	*hub_devnum = 0; +	*hubport = 0; + +	/* check a device connected to root_hub */ +	if ((dev->parent && dev->parent->devnum == 1) || +	    (dev->devnum == 1)) +		return; + +	for (i = 0; i < USB_MAXCHILDREN; i++) { +		if (dev->parent->children[i] == dev) { +			*hub_devnum = (u8)dev->parent->devnum; +			*hubport = i; +			return; +		} +	} + +	printf("get_hub_data error.\n"); +} + +static void set_devadd(struct r8a66597 *r8a66597, u8 r8a66597_address, +			struct usb_device *dev, int port) +{ +	u16 val, usbspd, upphub, hubport; +	unsigned long devadd_reg = get_devadd_addr(r8a66597_address); + +	get_hub_data(dev, &upphub, &hubport); +	usbspd = r8a66597->speed; +	val = (upphub << 11) | (hubport << 8) | (usbspd << 6) | (port & 0x0001); +	r8a66597_write(r8a66597, val, devadd_reg); +} + +static int r8a66597_clock_enable(struct r8a66597 *r8a66597) +{ +	u16 tmp; +	int i = 0; + +#if defined(CONFIG_SUPERH_ON_CHIP_R8A66597) +	do { +		r8a66597_write(r8a66597, SCKE, SYSCFG0); +		tmp = r8a66597_read(r8a66597, SYSCFG0); +		if (i++ > 1000) { +			printf("register access fail.\n"); +			return -1; +		} +	} while ((tmp & SCKE) != SCKE); +	r8a66597_write(r8a66597, 0x04, 0x02); +#else +	do { +		r8a66597_write(r8a66597, USBE, SYSCFG0); +		tmp = r8a66597_read(r8a66597, SYSCFG0); +		if (i++ > 1000) { +			printf("register access fail.\n"); +			return -1; +		} +	} while ((tmp & USBE) != USBE); +	r8a66597_bclr(r8a66597, USBE, SYSCFG0); +	r8a66597_mdfy(r8a66597, clock, XTAL, SYSCFG0); + +	i = 0; +	r8a66597_bset(r8a66597, XCKE, SYSCFG0); +	do { +		udelay(1000); +		tmp = r8a66597_read(r8a66597, SYSCFG0); +		if (i++ > 500) { +			printf("register access fail.\n"); +			return -1; +		} +	} while ((tmp & SCKE) != SCKE); +#endif	/* #if defined(CONFIG_SUPERH_ON_CHIP_R8A66597) */ + +	return 0; +} + +static void r8a66597_clock_disable(struct r8a66597 *r8a66597) +{ +	r8a66597_bclr(r8a66597, SCKE, SYSCFG0); +	udelay(1); +#if !defined(CONFIG_SUPERH_ON_CHIP_R8A66597) +	r8a66597_bclr(r8a66597, PLLC, SYSCFG0); +	r8a66597_bclr(r8a66597, XCKE, SYSCFG0); +	r8a66597_bclr(r8a66597, USBE, SYSCFG0); +#endif +} + +static void r8a66597_enable_port(struct r8a66597 *r8a66597, int port) +{ +	u16 val; + +	val = port ? DRPD : DCFM | DRPD; +	r8a66597_bset(r8a66597, val, get_syscfg_reg(port)); +	r8a66597_bset(r8a66597, HSE, get_syscfg_reg(port)); + +	r8a66597_write(r8a66597, BURST | CPU_ADR_RD_WR, get_dmacfg_reg(port)); +} + +static void r8a66597_disable_port(struct r8a66597 *r8a66597, int port) +{ +	u16 val, tmp; + +	r8a66597_write(r8a66597, 0, get_intenb_reg(port)); +	r8a66597_write(r8a66597, 0, get_intsts_reg(port)); + +	r8a66597_port_power(r8a66597, port, 0); + +	do { +		tmp = r8a66597_read(r8a66597, SOFCFG) & EDGESTS; +		udelay(640); +	} while (tmp == EDGESTS); + +	val = port ? DRPD : DCFM | DRPD; +	r8a66597_bclr(r8a66597, val, get_syscfg_reg(port)); +	r8a66597_bclr(r8a66597, HSE, get_syscfg_reg(port)); +} + +static int enable_controller(struct r8a66597 *r8a66597) +{ +	int ret, port; + +	ret = r8a66597_clock_enable(r8a66597); +	if (ret < 0) +		return ret; + +	r8a66597_bset(r8a66597, vif & LDRV, PINCFG); +	r8a66597_bset(r8a66597, USBE, SYSCFG0); + +	r8a66597_bset(r8a66597, INTL, SOFCFG); +	r8a66597_write(r8a66597, 0, INTENB0); +	r8a66597_write(r8a66597, 0, INTENB1); +	r8a66597_write(r8a66597, 0, INTENB2); + +	r8a66597_bset(r8a66597, endian & BIGEND, CFIFOSEL); +	r8a66597_bset(r8a66597, endian & BIGEND, D0FIFOSEL); +	r8a66597_bset(r8a66597, endian & BIGEND, D1FIFOSEL); +	r8a66597_bset(r8a66597, TRNENSEL, SOFCFG); + +	for (port = 0; port < R8A66597_MAX_ROOT_HUB; port++) +		r8a66597_enable_port(r8a66597, port); + +	return 0; +} + +static void disable_controller(struct r8a66597 *r8a66597) +{ +	int i; + +	if (!(r8a66597_read(r8a66597, SYSCFG0) & USBE)) +		return; + +	r8a66597_write(r8a66597, 0, INTENB0); +	r8a66597_write(r8a66597, 0, INTSTS0); + +	r8a66597_write(r8a66597, 0, D0FIFOSEL); +	r8a66597_write(r8a66597, 0, D1FIFOSEL); +	r8a66597_write(r8a66597, 0, DCPCFG); +	r8a66597_write(r8a66597, 0x40, DCPMAXP); +	r8a66597_write(r8a66597, 0, DCPCTR); + +	for (i = 0; i <= 10; i++) +		r8a66597_write(r8a66597, 0, get_devadd_addr(i)); +	for (i = 1; i <= 5; i++) { +		r8a66597_write(r8a66597, 0, get_pipetre_addr(i)); +		r8a66597_write(r8a66597, 0, get_pipetrn_addr(i)); +	} +	for (i = 1; i < R8A66597_MAX_NUM_PIPE; i++) { +		r8a66597_write(r8a66597, 0, get_pipectr_addr(i)); +		r8a66597_write(r8a66597, i, PIPESEL); +		r8a66597_write(r8a66597, 0, PIPECFG); +		r8a66597_write(r8a66597, 0, PIPEBUF); +		r8a66597_write(r8a66597, 0, PIPEMAXP); +		r8a66597_write(r8a66597, 0, PIPEPERI); +	} + +	for (i = 0; i < R8A66597_MAX_ROOT_HUB; i++) +		r8a66597_disable_port(r8a66597, i); + +	r8a66597_clock_disable(r8a66597); +} + +static void r8a66597_reg_wait(struct r8a66597 *r8a66597, unsigned long reg, +			      u16 mask, u16 loop) +{ +	u16 tmp; +	int i = 0; + +	do { +		tmp = r8a66597_read(r8a66597, reg); +		if (i++ > 1000000) { +			printf("register%lx, loop %x is timeout\n", reg, loop); +			break; +		} +	} while ((tmp & mask) != loop); +} + +static void pipe_buffer_setting(struct r8a66597 *r8a66597, +				struct usb_device *dev, unsigned long pipe) +{ +	u16 val = 0; +	u16 pipenum, bufnum, maxpacket; + +	if (usb_pipein(pipe)) { +		pipenum = BULK_IN_PIPENUM; +		bufnum = BULK_IN_BUFNUM; +		maxpacket = dev->epmaxpacketin[usb_pipeendpoint(pipe)]; +	} else { +		pipenum = BULK_OUT_PIPENUM; +		bufnum = BULK_OUT_BUFNUM; +		maxpacket = dev->epmaxpacketout[usb_pipeendpoint(pipe)]; +	} + +	if (r8a66597->pipe_config & (1 << pipenum)) +		return; +	r8a66597->pipe_config |= (1 << pipenum); + +	r8a66597_bset(r8a66597, ACLRM, get_pipectr_addr(pipenum)); +	r8a66597_bclr(r8a66597, ACLRM, get_pipectr_addr(pipenum)); +	r8a66597_write(r8a66597, pipenum, PIPESEL); + +	/* FIXME: This driver support bulk transfer only. */ +	if (!usb_pipein(pipe)) +		val |= R8A66597_DIR; +	else +		val |= R8A66597_SHTNAK; +	val |= R8A66597_BULK | R8A66597_DBLB | usb_pipeendpoint(pipe); +	r8a66597_write(r8a66597, val, PIPECFG); + +	r8a66597_write(r8a66597, (8 << 10) | bufnum, PIPEBUF); +	r8a66597_write(r8a66597, make_devsel(usb_pipedevice(pipe)) | +				 maxpacket, PIPEMAXP); +	r8a66597_write(r8a66597, 0, PIPEPERI); +	r8a66597_write(r8a66597, SQCLR, get_pipectr_addr(pipenum)); +} + +static int send_setup_packet(struct r8a66597 *r8a66597, struct usb_device *dev, +			     struct devrequest *setup) +{ +	int i; +	unsigned short *p = (unsigned short *)setup; +	unsigned long setup_addr = USBREQ; +	u16 intsts1; +	int timeout = 3000; +	u16 devsel = setup->request == USB_REQ_SET_ADDRESS ? 0 : dev->devnum; + +	r8a66597_write(r8a66597, make_devsel(devsel) | +				 (8 << dev->maxpacketsize), DCPMAXP); +	r8a66597_write(r8a66597, ~(SIGN | SACK), INTSTS1); + +	for (i = 0; i < 4; i++) { +		r8a66597_write(r8a66597, le16_to_cpu(p[i]), setup_addr); +		setup_addr += 2; +	} +	r8a66597_write(r8a66597, ~0x0001, BRDYSTS); +	r8a66597_write(r8a66597, SUREQ, DCPCTR); + +	while (1) { +		intsts1 = r8a66597_read(r8a66597, INTSTS1); +		if (intsts1 & SACK) +			break; +		if (intsts1 & SIGN) { +			printf("setup packet send error\n"); +			return -1; +		} +		if (timeout-- < 0) { +			printf("setup packet timeout\n"); +			return -1; +		} +		udelay(500); +	} + +	return 0; +} + +static int send_bulk_packet(struct r8a66597 *r8a66597, struct usb_device *dev, +			    unsigned long pipe, void *buffer, int transfer_len) +{ +	u16 tmp, bufsize; +	u16 *buf; +	size_t size; + +	R8A66597_DPRINT("%s\n", __func__); + +	r8a66597_mdfy(r8a66597, MBW | BULK_OUT_PIPENUM, +			MBW | CURPIPE, CFIFOSEL); +	r8a66597_reg_wait(r8a66597, CFIFOSEL, CURPIPE, BULK_OUT_PIPENUM); +	tmp = r8a66597_read(r8a66597, CFIFOCTR); +	if ((tmp & FRDY) == 0) { +		printf("%s FRDY is not set (%x)\n", __func__, tmp); +		return -1; +	} + +	/* prepare parameters */ +	bufsize = dev->epmaxpacketout[usb_pipeendpoint(pipe)]; +	buf = (u16 *)(buffer + dev->act_len); +	size = min((int)bufsize, transfer_len - dev->act_len); + +	/* write fifo */ +	r8a66597_write(r8a66597, ~(1 << BULK_OUT_PIPENUM), BEMPSTS); +	if (buffer) { +		r8a66597_write_fifo(r8a66597, CFIFO, buf, size); +		r8a66597_write(r8a66597, BVAL, CFIFOCTR); +	} + +	/* update parameters */ +	dev->act_len += size; + +	r8a66597_mdfy(r8a66597, PID_BUF, PID, +			get_pipectr_addr(BULK_OUT_PIPENUM)); + +	while (!(r8a66597_read(r8a66597, BEMPSTS) & (1 << BULK_OUT_PIPENUM))) +		if (ctrlc()) +			return -1; +	r8a66597_write(r8a66597, ~(1 << BULK_OUT_PIPENUM), BEMPSTS); + +	if (dev->act_len >= transfer_len) +		r8a66597_mdfy(r8a66597, PID_NAK, PID, +				get_pipectr_addr(BULK_OUT_PIPENUM)); + +	return 0; +} + +static int receive_bulk_packet(struct r8a66597 *r8a66597, +			       struct usb_device *dev, +			       unsigned long pipe, +			       void *buffer, int transfer_len) +{ +	u16 tmp; +	u16 *buf; +	const u16 pipenum = BULK_IN_PIPENUM; +	int rcv_len; +	int maxpacket = dev->epmaxpacketin[usb_pipeendpoint(pipe)]; + +	R8A66597_DPRINT("%s\n", __func__); + +	/* prepare */ +	if (dev->act_len == 0) { +		r8a66597_mdfy(r8a66597, PID_NAK, PID, +				get_pipectr_addr(pipenum)); +		r8a66597_write(r8a66597, ~(1 << pipenum), BRDYSTS); + +		r8a66597_write(r8a66597, TRCLR, get_pipetre_addr(pipenum)); +		r8a66597_write(r8a66597, +				(transfer_len + maxpacket - 1) / maxpacket, +				get_pipetrn_addr(pipenum)); +		r8a66597_bset(r8a66597, TRENB, get_pipetre_addr(pipenum)); + +		r8a66597_mdfy(r8a66597, PID_BUF, PID, +				get_pipectr_addr(pipenum)); +	} + +	r8a66597_mdfy(r8a66597, MBW | pipenum, MBW | CURPIPE, CFIFOSEL); +	r8a66597_reg_wait(r8a66597, CFIFOSEL, CURPIPE, pipenum); + +	while (!(r8a66597_read(r8a66597, BRDYSTS) & (1 << pipenum))) +		if (ctrlc()) +			return -1; +	r8a66597_write(r8a66597, ~(1 << pipenum), BRDYSTS); + +	tmp = r8a66597_read(r8a66597, CFIFOCTR); +	if ((tmp & FRDY) == 0) { +		printf("%s FRDY is not set. (%x)\n", __func__, tmp); +		return -1; +	} + +	buf = (u16 *)(buffer + dev->act_len); +	rcv_len = tmp & DTLN; +	dev->act_len += rcv_len; + +	if (buffer) { +		if (rcv_len == 0) +			r8a66597_write(r8a66597, BCLR, CFIFOCTR); +		else +			r8a66597_read_fifo(r8a66597, CFIFO, buf, rcv_len); +	} + +	return 0; +} + +static int receive_control_packet(struct r8a66597 *r8a66597, +				  struct usb_device *dev, +				  void *buffer, int transfer_len) +{ +	u16 tmp; +	int rcv_len; + +	/* FIXME: limit transfer size : 64byte or less */ + +	r8a66597_bclr(r8a66597, R8A66597_DIR, DCPCFG); +	r8a66597_mdfy(r8a66597, 0, ISEL | CURPIPE, CFIFOSEL); +	r8a66597_reg_wait(r8a66597, CFIFOSEL, CURPIPE, 0); +	r8a66597_bset(r8a66597, SQSET, DCPCTR); +	r8a66597_write(r8a66597, BCLR, CFIFOCTR); +	r8a66597_mdfy(r8a66597, PID_BUF, PID, DCPCTR); + +	while (!(r8a66597_read(r8a66597, BRDYSTS) & 0x0001)) +		if (ctrlc()) +			return -1; +	r8a66597_write(r8a66597, ~0x0001, BRDYSTS); + +	r8a66597_mdfy(r8a66597, MBW, MBW | CURPIPE, CFIFOSEL); +	r8a66597_reg_wait(r8a66597, CFIFOSEL, CURPIPE, 0); + +	tmp = r8a66597_read(r8a66597, CFIFOCTR); +	if ((tmp & FRDY) == 0) { +		printf("%s FRDY is not set. (%x)\n", __func__, tmp); +		return -1; +	} + +	rcv_len = tmp & DTLN; +	dev->act_len += rcv_len; + +	r8a66597_mdfy(r8a66597, PID_NAK, PID, DCPCTR); + +	if (buffer) { +		if (rcv_len == 0) +			r8a66597_write(r8a66597, BCLR, DCPCTR); +		else +			r8a66597_read_fifo(r8a66597, CFIFO, buffer, rcv_len); +	} + +	return 0; +} + +static int send_status_packet(struct r8a66597 *r8a66597, +			       unsigned long pipe) +{ +	r8a66597_bset(r8a66597, SQSET, DCPCTR); +	r8a66597_mdfy(r8a66597, PID_NAK, PID, DCPCTR); + +	if (usb_pipein(pipe)) { +		r8a66597_bset(r8a66597, R8A66597_DIR, DCPCFG); +		r8a66597_mdfy(r8a66597, ISEL, ISEL | CURPIPE, CFIFOSEL); +		r8a66597_reg_wait(r8a66597, CFIFOSEL, CURPIPE, 0); +		r8a66597_write(r8a66597, ~BEMP0, BEMPSTS); +		r8a66597_write(r8a66597, BCLR | BVAL, CFIFOCTR); +	} else { +		r8a66597_bclr(r8a66597, R8A66597_DIR, DCPCFG); +		r8a66597_mdfy(r8a66597, 0, ISEL | CURPIPE, CFIFOSEL); +		r8a66597_reg_wait(r8a66597, CFIFOSEL, CURPIPE, 0); +		r8a66597_write(r8a66597, BCLR, CFIFOCTR); +	} +	r8a66597_mdfy(r8a66597, PID_BUF, PID, DCPCTR); + +	while (!(r8a66597_read(r8a66597, BEMPSTS) & 0x0001)) +		if (ctrlc()) +			return -1; + +	return 0; +} + +static void r8a66597_check_syssts(struct r8a66597 *r8a66597, int port) +{ +	int count = R8A66597_MAX_SAMPLING; +	unsigned short syssts, old_syssts; + +	R8A66597_DPRINT("%s\n", __func__); + +	old_syssts = r8a66597_read(r8a66597, get_syssts_reg(port) & LNST); +	while (count > 0) { +		wait_ms(R8A66597_RH_POLL_TIME); + +		syssts = r8a66597_read(r8a66597, get_syssts_reg(port) & LNST); +		if (syssts == old_syssts) { +			count--; +		} else { +			count = R8A66597_MAX_SAMPLING; +			old_syssts = syssts; +		} +	} +} + +static void r8a66597_bus_reset(struct r8a66597 *r8a66597, int port) +{ +	wait_ms(10); +	r8a66597_mdfy(r8a66597, USBRST, USBRST | UACT, get_dvstctr_reg(port)); +	wait_ms(50); +	r8a66597_mdfy(r8a66597, UACT, USBRST | UACT, get_dvstctr_reg(port)); +	wait_ms(50); +} + +static int check_usb_device_connecting(struct r8a66597 *r8a66597) +{ +	int timeout = 10000;	/* 100usec * 10000 = 1sec */ +	int i; + +	for (i = 0; i < 5; i++) { +		/* check a usb cable connect */ +		while (!(r8a66597_read(r8a66597, INTSTS1) & ATTCH)) { +			if (timeout-- < 0) { +				printf("%s timeout.\n", __func__); +				return -1; +			} +			udelay(100); +		} + +		/* check a data line */ +		r8a66597_check_syssts(r8a66597, 0); + +		r8a66597_bus_reset(r8a66597, 0); +		r8a66597->speed = get_rh_usb_speed(r8a66597, 0); + +		if (!(r8a66597_read(r8a66597, INTSTS1) & DTCH)) { +			r8a66597->port_change = USB_PORT_STAT_C_CONNECTION; +			r8a66597->port_status = USB_PORT_STAT_CONNECTION | +						USB_PORT_STAT_ENABLE; +			return 0;	/* success */ +		} + +		R8A66597_DPRINT("USB device has detached. retry = %d\n", i); +		r8a66597_write(r8a66597, ~DTCH, INTSTS1); +	} + +	return -1;	/* fail */ +} + +/* based on usb_ohci.c */ +#define min_t(type, x, y) \ +		({ type __x = (x); type __y = (y); __x < __y ? __x : __y; }) +/*-------------------------------------------------------------------------* + * Virtual Root Hub + *-------------------------------------------------------------------------*/ + +/* Device descriptor */ +static __u8 root_hub_dev_des[] = +{ +	0x12,	    /*	__u8  bLength; */ +	0x01,	    /*	__u8  bDescriptorType; Device */ +	0x10,	    /*	__u16 bcdUSB; v1.1 */ +	0x01, +	0x09,	    /*	__u8  bDeviceClass; HUB_CLASSCODE */ +	0x00,	    /*	__u8  bDeviceSubClass; */ +	0x00,	    /*	__u8  bDeviceProtocol; */ +	0x08,	    /*	__u8  bMaxPacketSize0; 8 Bytes */ +	0x00,	    /*	__u16 idVendor; */ +	0x00, +	0x00,	    /*	__u16 idProduct; */ +	0x00, +	0x00,	    /*	__u16 bcdDevice; */ +	0x00, +	0x00,	    /*	__u8  iManufacturer; */ +	0x01,	    /*	__u8  iProduct; */ +	0x00,	    /*	__u8  iSerialNumber; */ +	0x01	    /*	__u8  bNumConfigurations; */ +}; + +/* Configuration descriptor */ +static __u8 root_hub_config_des[] = +{ +	0x09,	    /*	__u8  bLength; */ +	0x02,	    /*	__u8  bDescriptorType; Configuration */ +	0x19,	    /*	__u16 wTotalLength; */ +	0x00, +	0x01,	    /*	__u8  bNumInterfaces; */ +	0x01,	    /*	__u8  bConfigurationValue; */ +	0x00,	    /*	__u8  iConfiguration; */ +	0x40,	    /*	__u8  bmAttributes; */ + +	0x00,	    /*	__u8  MaxPower; */ + +	/* interface */ +	0x09,	    /*	__u8  if_bLength; */ +	0x04,	    /*	__u8  if_bDescriptorType; Interface */ +	0x00,	    /*	__u8  if_bInterfaceNumber; */ +	0x00,	    /*	__u8  if_bAlternateSetting; */ +	0x01,	    /*	__u8  if_bNumEndpoints; */ +	0x09,	    /*	__u8  if_bInterfaceClass; HUB_CLASSCODE */ +	0x00,	    /*	__u8  if_bInterfaceSubClass; */ +	0x00,	    /*	__u8  if_bInterfaceProtocol; */ +	0x00,	    /*	__u8  if_iInterface; */ + +	/* endpoint */ +	0x07,	    /*	__u8  ep_bLength; */ +	0x05,	    /*	__u8  ep_bDescriptorType; Endpoint */ +	0x81,	    /*	__u8  ep_bEndpointAddress; IN Endpoint 1 */ +	0x03,	    /*	__u8  ep_bmAttributes; Interrupt */ +	0x02,	    /*	__u16 ep_wMaxPacketSize; ((MAX_ROOT_PORTS + 1) / 8 */ +	0x00, +	0xff	    /*	__u8  ep_bInterval; 255 ms */ +}; + +static unsigned char root_hub_str_index0[] = +{ +	0x04,			/*  __u8  bLength; */ +	0x03,			/*  __u8  bDescriptorType; String-descriptor */ +	0x09,			/*  __u8  lang ID */ +	0x04,			/*  __u8  lang ID */ +}; + +static unsigned char root_hub_str_index1[] = +{ +	34,			/*  __u8  bLength; */ +	0x03,			/*  __u8  bDescriptorType; String-descriptor */ +	'R',			/*  __u8  Unicode */ +	0,				/*  __u8  Unicode */ +	'8',			/*  __u8  Unicode */ +	0,				/*  __u8  Unicode */ +	'A',			/*  __u8  Unicode */ +	0,				/*  __u8  Unicode */ +	'6',			/*  __u8  Unicode */ +	0,				/*  __u8  Unicode */ +	'6',			/*  __u8  Unicode */ +	0,				/*  __u8  Unicode */ +	'5',			/*  __u8  Unicode */ +	0,				/*  __u8  Unicode */ +	'9',			/*  __u8  Unicode */ +	0,				/*  __u8  Unicode */ +	'7',			/*  __u8  Unicode */ +	0,				/*  __u8  Unicode */ +	' ',			/*  __u8  Unicode */ +	0,				/*  __u8  Unicode */ +	'R',			/*  __u8  Unicode */ +	0,				/*  __u8  Unicode */ +	'o',			/*  __u8  Unicode */ +	0,				/*  __u8  Unicode */ +	'o',			/*  __u8  Unicode */ +	0,				/*  __u8  Unicode */ +	't',			/*  __u8  Unicode */ +	0,				/*  __u8  Unicode */ +	'H',			/*  __u8  Unicode */ +	0,				/*  __u8  Unicode */ +	'u',			/*  __u8  Unicode */ +	0,				/*  __u8  Unicode */ +	'b',			/*  __u8  Unicode */ +	0,				/*  __u8  Unicode */ +}; + +static int r8a66597_submit_rh_msg(struct usb_device *dev, unsigned long pipe, +			void *buffer, int transfer_len, struct devrequest *cmd) +{ +	struct r8a66597 *r8a66597 = &gr8a66597; +	int leni = transfer_len; +	int len = 0; +	int stat = 0; +	__u16 bmRType_bReq; +	__u16 wValue; +	__u16 wIndex; +	__u16 wLength; +	unsigned char data[32]; + +	R8A66597_DPRINT("%s\n", __func__); + +	if (usb_pipeint(pipe)) { +		printf("Root-Hub submit IRQ: NOT implemented"); +		return 0; +	} + +	bmRType_bReq  = cmd->requesttype | (cmd->request << 8); +	wValue	      = cpu_to_le16 (cmd->value); +	wIndex	      = cpu_to_le16 (cmd->index); +	wLength	      = cpu_to_le16 (cmd->length); + +	switch (bmRType_bReq) { +	case RH_GET_STATUS: +		*(__u16 *)buffer = cpu_to_le16(1); +		len = 2; +		break; +	case RH_GET_STATUS | RH_INTERFACE: +		*(__u16 *)buffer = cpu_to_le16(0); +		len = 2; +		break; +	case RH_GET_STATUS | RH_ENDPOINT: +		*(__u16 *)buffer = cpu_to_le16(0); +		len = 2; +		break; +	case RH_GET_STATUS | RH_CLASS: +		*(__u32 *)buffer = cpu_to_le32(0); +		len = 4; +		break; +	case RH_GET_STATUS | RH_OTHER | RH_CLASS: +		*(__u32 *)buffer = cpu_to_le32(r8a66597->port_status | +						(r8a66597->port_change << 16)); +		len = 4; +		break; +	case RH_CLEAR_FEATURE | RH_ENDPOINT: +	case RH_CLEAR_FEATURE | RH_CLASS: +		break; + +	case RH_CLEAR_FEATURE | RH_OTHER | RH_CLASS: +		switch (wValue) { +		case RH_C_PORT_CONNECTION: +			r8a66597->port_change &= ~USB_PORT_STAT_C_CONNECTION; +			break; +		} +		break; + +	case RH_SET_FEATURE | RH_OTHER | RH_CLASS: +		switch (wValue) { +		case (RH_PORT_SUSPEND): +			break; +		case (RH_PORT_RESET): +			r8a66597_bus_reset(r8a66597, 0); +			break; +		case (RH_PORT_POWER): +			break; +		case (RH_PORT_ENABLE): +			break; +		} +		break; +	case RH_SET_ADDRESS: +		gr8a66597.rh_devnum = wValue; +		break; +	case RH_GET_DESCRIPTOR: +		switch ((wValue & 0xff00) >> 8) { +		case (0x01): /* device descriptor */ +			len = min_t(unsigned int, +				  leni, +				  min_t(unsigned int, +				      sizeof(root_hub_dev_des), +				      wLength)); +			memcpy(buffer, root_hub_dev_des, len); +			break; +		case (0x02): /* configuration descriptor */ +			len = min_t(unsigned int, +				  leni, +				  min_t(unsigned int, +				      sizeof(root_hub_config_des), +				      wLength)); +			memcpy(buffer, root_hub_config_des, len); +			break; +		case (0x03): /* string descriptors */ +			if (wValue == 0x0300) { +				len = min_t(unsigned int, +					  leni, +					  min_t(unsigned int, +					      sizeof(root_hub_str_index0), +					      wLength)); +				memcpy(buffer, root_hub_str_index0, len); +			} +			if (wValue == 0x0301) { +				len = min_t(unsigned int, +					  leni, +					  min_t(unsigned int, +					      sizeof(root_hub_str_index1), +					      wLength)); +				memcpy(buffer, root_hub_str_index1, len); +			} +			break; +		default: +			stat = USB_ST_STALLED; +		} +		break; + +	case RH_GET_DESCRIPTOR | RH_CLASS: +	{ +		__u32 temp = 0x00000001; + +		data[0] = 9;		/* min length; */ +		data[1] = 0x29; +		data[2] = temp & RH_A_NDP; +		data[3] = 0; +		if (temp & RH_A_PSM) +			data[3] |= 0x1; +		if (temp & RH_A_NOCP) +			data[3] |= 0x10; +		else if (temp & RH_A_OCPM) +			data[3] |= 0x8; + +		/* corresponds to data[4-7] */ +		data[5] = (temp & RH_A_POTPGT) >> 24; +		data[7] = temp & RH_B_DR; +		if (data[2] < 7) { +			data[8] = 0xff; +		} else { +			data[0] += 2; +			data[8] = (temp & RH_B_DR) >> 8; +			data[10] = data[9] = 0xff; +		} + +		len = min_t(unsigned int, leni, +			    min_t(unsigned int, data[0], wLength)); +		memcpy(buffer, data, len); +		break; +	} + +	case RH_GET_CONFIGURATION: +		*(__u8 *) buffer = 0x01; +		len = 1; +		break; +	case RH_SET_CONFIGURATION: +		break; +	default: +		R8A66597_DPRINT("unsupported root hub command"); +		stat = USB_ST_STALLED; +	} + +	wait_ms(1); + +	len = min_t(int, len, leni); + +	dev->act_len = len; +	dev->status = stat; + +	return stat; +} + +int submit_bulk_msg(struct usb_device *dev, unsigned long pipe, void *buffer, +		    int transfer_len) +{ +	struct r8a66597 *r8a66597 = &gr8a66597; +	int ret = 0; + +	R8A66597_DPRINT("%s\n", __func__); +	R8A66597_DPRINT("pipe = %08x, buffer = %p, len = %d, devnum = %d\n", +			pipe, buffer, transfer_len, dev->devnum); + +	set_devadd(r8a66597, dev->devnum, dev, 0); + +	pipe_buffer_setting(r8a66597, dev, pipe); + +	dev->act_len = 0; +	while (dev->act_len < transfer_len && ret == 0) { +		if (ctrlc()) +			return -1; + +		if (usb_pipein(pipe)) +			ret = receive_bulk_packet(r8a66597, dev, pipe, buffer, +							transfer_len); +		else +			ret = send_bulk_packet(r8a66597, dev, pipe, buffer, +							transfer_len); +	} + +	if (ret == 0) +		dev->status = 0; + +	return ret; +} + +int submit_control_msg(struct usb_device *dev, unsigned long pipe, +		       void *buffer, int transfer_len, struct devrequest *setup) +{ +	struct r8a66597 *r8a66597 = &gr8a66597; +	u16 r8a66597_address = setup->request == USB_REQ_SET_ADDRESS ? +					0 : dev->devnum; + +	R8A66597_DPRINT("%s\n", __func__); +	if (usb_pipedevice(pipe) == r8a66597->rh_devnum) +		return r8a66597_submit_rh_msg(dev, pipe, buffer, transfer_len, +						setup); + +	R8A66597_DPRINT("%s: setup\n", __func__); +	set_devadd(r8a66597, r8a66597_address, dev, 0); + +	if (send_setup_packet(r8a66597, dev, setup) < 0) { +		printf("setup packet send error\n"); +		return -1; +	} + +	dev->act_len = 0; +	if (usb_pipein(pipe)) +		if (receive_control_packet(r8a66597, dev, buffer, +						transfer_len) < 0) +			return -1; + +	if (send_status_packet(r8a66597, pipe) < 0) +		return -1; + +	dev->status = 0; + +	return 0; +} + +int submit_int_msg(struct usb_device *dev, unsigned long pipe, void *buffer, +			int transfer_len, int interval) +{ +	/* no implement */ +	R8A66597_DPRINT("%s\n", __func__); +	return 0; +} + +void usb_event_poll(void) +{ +	/* no implement */ +	R8A66597_DPRINT("%s\n", __func__); +} + +int usb_lowlevel_init(void) +{ +	struct r8a66597 *r8a66597 = &gr8a66597; + +	R8A66597_DPRINT("%s\n", __func__); + +	memset(r8a66597, 0, sizeof(r8a66597)); +	r8a66597->reg = CONFIG_R8A66597_BASE_ADDR; + +	disable_controller(r8a66597); +	wait_ms(100); + +	enable_controller(r8a66597); +	r8a66597_port_power(r8a66597, 0 , 1); + +	/* check usb device */ +	check_usb_device_connecting(r8a66597); + +	wait_ms(50); + +	return 0; +} + +int usb_lowlevel_stop(void) +{ +	disable_controller(&gr8a66597); + +	return 0; +} diff --git a/drivers/usb/host/r8a66597.h b/drivers/usb/host/r8a66597.h new file mode 100644 index 000000000..9af6446c1 --- /dev/null +++ b/drivers/usb/host/r8a66597.h @@ -0,0 +1,659 @@ +/* + * R8A66597 HCD (Host Controller Driver) for u-boot + * + * Copyright (C) 2008  Yoshihiro Shimoda <shimoda.yoshihiro@renesas.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA + * + */ + +#ifndef __R8A66597_H__ +#define __R8A66597_H__ + +#define SYSCFG0		0x00 +#define SYSCFG1		0x02 +#define SYSSTS0		0x04 +#define SYSSTS1		0x06 +#define DVSTCTR0	0x08 +#define DVSTCTR1	0x0A +#define TESTMODE	0x0C +#define PINCFG		0x0E +#define DMA0CFG		0x10 +#define DMA1CFG		0x12 +#define CFIFO		0x14 +#define D0FIFO		0x18 +#define D1FIFO		0x1C +#define CFIFOSEL	0x20 +#define CFIFOCTR	0x22 +#define CFIFOSIE	0x24 +#define D0FIFOSEL	0x28 +#define D0FIFOCTR	0x2A +#define D1FIFOSEL	0x2C +#define D1FIFOCTR	0x2E +#define INTENB0		0x30 +#define INTENB1		0x32 +#define INTENB2		0x34 +#define BRDYENB		0x36 +#define NRDYENB		0x38 +#define BEMPENB		0x3A +#define SOFCFG		0x3C +#define INTSTS0		0x40 +#define INTSTS1		0x42 +#define INTSTS2		0x44 +#define BRDYSTS		0x46 +#define NRDYSTS		0x48 +#define BEMPSTS		0x4A +#define FRMNUM		0x4C +#define UFRMNUM		0x4E +#define USBADDR		0x50 +#define USBREQ		0x54 +#define USBVAL		0x56 +#define USBINDX		0x58 +#define USBLENG		0x5A +#define DCPCFG		0x5C +#define DCPMAXP		0x5E +#define DCPCTR		0x60 +#define PIPESEL		0x64 +#define PIPECFG		0x68 +#define PIPEBUF		0x6A +#define PIPEMAXP	0x6C +#define PIPEPERI	0x6E +#define PIPE1CTR	0x70 +#define PIPE2CTR	0x72 +#define PIPE3CTR	0x74 +#define PIPE4CTR	0x76 +#define PIPE5CTR	0x78 +#define PIPE6CTR	0x7A +#define PIPE7CTR	0x7C +#define PIPE8CTR	0x7E +#define PIPE9CTR	0x80 +#define PIPE1TRE	0x90 +#define PIPE1TRN	0x92 +#define PIPE2TRE	0x94 +#define PIPE2TRN	0x96 +#define PIPE3TRE	0x98 +#define PIPE3TRN	0x9A +#define PIPE4TRE	0x9C +#define	PIPE4TRN	0x9E +#define	PIPE5TRE	0xA0 +#define	PIPE5TRN	0xA2 +#define DEVADD0		0xD0 +#define DEVADD1		0xD2 +#define DEVADD2		0xD4 +#define DEVADD3		0xD6 +#define DEVADD4		0xD8 +#define DEVADD5		0xDA +#define DEVADD6		0xDC +#define DEVADD7		0xDE +#define DEVADD8		0xE0 +#define DEVADD9		0xE2 +#define DEVADDA		0xE4 + +/* System Configuration Control Register */ +#define	XTAL		0xC000	/* b15-14: Crystal selection */ +#define	  XTAL48	 0x8000	  /* 48MHz */ +#define	  XTAL24	 0x4000	  /* 24MHz */ +#define	  XTAL12	 0x0000	  /* 12MHz */ +#define	XCKE		0x2000	/* b13: External clock enable */ +#define	PLLC		0x0800	/* b11: PLL control */ +#define	SCKE		0x0400	/* b10: USB clock enable */ +#define	PCSDIS		0x0200	/* b9: not CS wakeup */ +#define	LPSME		0x0100	/* b8: Low power sleep mode */ +#define	HSE		0x0080	/* b7: Hi-speed enable */ +#define	DCFM		0x0040	/* b6: Controller function select  */ +#define	DRPD		0x0020	/* b5: D+/- pull down control */ +#define	DPRPU		0x0010	/* b4: D+ pull up control */ +#define	USBE		0x0001	/* b0: USB module operation enable */ + +/* System Configuration Status Register */ +#define	OVCBIT		0x8000	/* b15-14: Over-current bit */ +#define	OVCMON		0xC000	/* b15-14: Over-current monitor */ +#define	SOFEA		0x0020	/* b5: SOF monitor */ +#define	IDMON		0x0004	/* b3: ID-pin monitor */ +#define	LNST		0x0003	/* b1-0: D+, D- line status */ +#define	  SE1		 0x0003	  /* SE1 */ +#define	  FS_KSTS	 0x0002	  /* Full-Speed K State */ +#define	  FS_JSTS	 0x0001	  /* Full-Speed J State */ +#define	  LS_JSTS	 0x0002	  /* Low-Speed J State */ +#define	  LS_KSTS	 0x0001	  /* Low-Speed K State */ +#define	  SE0		 0x0000	  /* SE0 */ + +/* Device State Control Register */ +#define	EXTLP0		0x0400	/* b10: External port */ +#define	VBOUT		0x0200	/* b9: VBUS output */ +#define	WKUP		0x0100	/* b8: Remote wakeup */ +#define	RWUPE		0x0080	/* b7: Remote wakeup sense */ +#define	USBRST		0x0040	/* b6: USB reset enable */ +#define	RESUME		0x0020	/* b5: Resume enable */ +#define	UACT		0x0010	/* b4: USB bus enable */ +#define	RHST		0x0007	/* b1-0: Reset handshake status */ +#define	  HSPROC	 0x0004	  /* HS handshake is processing */ +#define	  HSMODE	 0x0003	  /* Hi-Speed mode */ +#define	  FSMODE	 0x0002	  /* Full-Speed mode */ +#define	  LSMODE	 0x0001	  /* Low-Speed mode */ +#define	  UNDECID	 0x0000	  /* Undecided */ + +/* Test Mode Register */ +#define	UTST			0x000F	/* b3-0: Test select */ +#define	  H_TST_PACKET		 0x000C	  /* HOST TEST Packet */ +#define	  H_TST_SE0_NAK		 0x000B	  /* HOST TEST SE0 NAK */ +#define	  H_TST_K		 0x000A	  /* HOST TEST K */ +#define	  H_TST_J		 0x0009	  /* HOST TEST J */ +#define	  H_TST_NORMAL		 0x0000	  /* HOST Normal Mode */ +#define	  P_TST_PACKET		 0x0004	  /* PERI TEST Packet */ +#define	  P_TST_SE0_NAK		 0x0003	  /* PERI TEST SE0 NAK */ +#define	  P_TST_K		 0x0002	  /* PERI TEST K */ +#define	  P_TST_J		 0x0001	  /* PERI TEST J */ +#define	  P_TST_NORMAL		 0x0000	  /* PERI Normal Mode */ + +/* Data Pin Configuration Register */ +#define	LDRV			0x8000	/* b15: Drive Current Adjust */ +#define	  VIF1			  0x0000		/* VIF = 1.8V */ +#define	  VIF3			  0x8000		/* VIF = 3.3V */ +#define	INTA			0x0001	/* b1: USB INT-pin active */ + +/* DMAx Pin Configuration Register */ +#define	DREQA			0x4000	/* b14: Dreq active select */ +#define	BURST			0x2000	/* b13: Burst mode */ +#define	DACKA			0x0400	/* b10: Dack active select */ +#define	DFORM			0x0380	/* b9-7: DMA mode select */ +#define	  CPU_ADR_RD_WR		 0x0000	  /* Address + RD/WR mode (CPU bus) */ +#define	  CPU_DACK_RD_WR	 0x0100	  /* DACK + RD/WR mode (CPU bus) */ +#define	  CPU_DACK_ONLY		 0x0180	  /* DACK only mode (CPU bus) */ +#define	  SPLIT_DACK_ONLY	 0x0200	  /* DACK only mode (SPLIT bus) */ +#define	DENDA			0x0040	/* b6: Dend active select */ +#define	PKTM			0x0020	/* b5: Packet mode */ +#define	DENDE			0x0010	/* b4: Dend enable */ +#define	OBUS			0x0004	/* b2: OUTbus mode */ + +/* CFIFO/DxFIFO Port Select Register */ +#define	RCNT		0x8000	/* b15: Read count mode */ +#define	REW		0x4000	/* b14: Buffer rewind */ +#define	DCLRM		0x2000	/* b13: DMA buffer clear mode */ +#define	DREQE		0x1000	/* b12: DREQ output enable */ +#if defined(CONFIG_SUPERH_ON_CHIP_R8A66597) +#define	MBW		0x0800 +#else +#define	MBW		0x0400	/* b10: Maximum bit width for FIFO access */ +#endif +#define	  MBW_8		 0x0000	  /*  8bit */ +#define	  MBW_16	 0x0400	  /* 16bit */ +#define	BIGEND		0x0100	/* b8: Big endian mode */ +#define	  BYTE_LITTLE	 0x0000		/* little dendian */ +#define	  BYTE_BIG	 0x0100		/* big endifan */ +#define	ISEL		0x0020	/* b5: DCP FIFO port direction select */ +#define	CURPIPE		0x000F	/* b2-0: PIPE select */ + +/* CFIFO/DxFIFO Port Control Register */ +#define	BVAL		0x8000	/* b15: Buffer valid flag */ +#define	BCLR		0x4000	/* b14: Buffer clear */ +#define	FRDY		0x2000	/* b13: FIFO ready */ +#define	DTLN		0x0FFF	/* b11-0: FIFO received data length */ + +/* Interrupt Enable Register 0 */ +#define	VBSE	0x8000	/* b15: VBUS interrupt */ +#define	RSME	0x4000	/* b14: Resume interrupt */ +#define	SOFE	0x2000	/* b13: Frame update interrupt */ +#define	DVSE	0x1000	/* b12: Device state transition interrupt */ +#define	CTRE	0x0800	/* b11: Control transfer stage transition interrupt */ +#define	BEMPE	0x0400	/* b10: Buffer empty interrupt */ +#define	NRDYE	0x0200	/* b9: Buffer not ready interrupt */ +#define	BRDYE	0x0100	/* b8: Buffer ready interrupt */ + +/* Interrupt Enable Register 1 */ +#define	OVRCRE		0x8000	/* b15: Over-current interrupt */ +#define	BCHGE		0x4000	/* b14: USB us chenge interrupt */ +#define	DTCHE		0x1000	/* b12: Detach sense interrupt */ +#define	ATTCHE		0x0800	/* b11: Attach sense interrupt */ +#define	EOFERRE		0x0040	/* b6: EOF error interrupt */ +#define	SIGNE		0x0020	/* b5: SETUP IGNORE interrupt */ +#define	SACKE		0x0010	/* b4: SETUP ACK interrupt */ + +/* BRDY Interrupt Enable/Status Register */ +#define	BRDY9		0x0200	/* b9: PIPE9 */ +#define	BRDY8		0x0100	/* b8: PIPE8 */ +#define	BRDY7		0x0080	/* b7: PIPE7 */ +#define	BRDY6		0x0040	/* b6: PIPE6 */ +#define	BRDY5		0x0020	/* b5: PIPE5 */ +#define	BRDY4		0x0010	/* b4: PIPE4 */ +#define	BRDY3		0x0008	/* b3: PIPE3 */ +#define	BRDY2		0x0004	/* b2: PIPE2 */ +#define	BRDY1		0x0002	/* b1: PIPE1 */ +#define	BRDY0		0x0001	/* b1: PIPE0 */ + +/* NRDY Interrupt Enable/Status Register */ +#define	NRDY9		0x0200	/* b9: PIPE9 */ +#define	NRDY8		0x0100	/* b8: PIPE8 */ +#define	NRDY7		0x0080	/* b7: PIPE7 */ +#define	NRDY6		0x0040	/* b6: PIPE6 */ +#define	NRDY5		0x0020	/* b5: PIPE5 */ +#define	NRDY4		0x0010	/* b4: PIPE4 */ +#define	NRDY3		0x0008	/* b3: PIPE3 */ +#define	NRDY2		0x0004	/* b2: PIPE2 */ +#define	NRDY1		0x0002	/* b1: PIPE1 */ +#define	NRDY0		0x0001	/* b1: PIPE0 */ + +/* BEMP Interrupt Enable/Status Register */ +#define	BEMP9		0x0200	/* b9: PIPE9 */ +#define	BEMP8		0x0100	/* b8: PIPE8 */ +#define	BEMP7		0x0080	/* b7: PIPE7 */ +#define	BEMP6		0x0040	/* b6: PIPE6 */ +#define	BEMP5		0x0020	/* b5: PIPE5 */ +#define	BEMP4		0x0010	/* b4: PIPE4 */ +#define	BEMP3		0x0008	/* b3: PIPE3 */ +#define	BEMP2		0x0004	/* b2: PIPE2 */ +#define	BEMP1		0x0002	/* b1: PIPE1 */ +#define	BEMP0		0x0001	/* b0: PIPE0 */ + +/* SOF Pin Configuration Register */ +#define	TRNENSEL	0x0100	/* b8: Select transaction enable period */ +#define	BRDYM		0x0040	/* b6: BRDY clear timing */ +#define	INTL		0x0020	/* b5: Interrupt sense select */ +#define	EDGESTS		0x0010	/* b4:  */ +#define	SOFMODE		0x000C	/* b3-2: SOF pin select */ +#define	  SOF_125US	 0x0008	  /* SOF OUT 125us Frame Signal */ +#define	  SOF_1MS	 0x0004	  /* SOF OUT 1ms Frame Signal */ +#define	  SOF_DISABLE	 0x0000	  /* SOF OUT Disable */ + +/* Interrupt Status Register 0 */ +#define	VBINT	0x8000	/* b15: VBUS interrupt */ +#define	RESM	0x4000	/* b14: Resume interrupt */ +#define	SOFR	0x2000	/* b13: SOF frame update interrupt */ +#define	DVST	0x1000	/* b12: Device state transition interrupt */ +#define	CTRT	0x0800	/* b11: Control transfer stage transition interrupt */ +#define	BEMP	0x0400	/* b10: Buffer empty interrupt */ +#define	NRDY	0x0200	/* b9: Buffer not ready interrupt */ +#define	BRDY	0x0100	/* b8: Buffer ready interrupt */ +#define	VBSTS	0x0080	/* b7: VBUS input port */ +#define	DVSQ	0x0070	/* b6-4: Device state */ +#define	  DS_SPD_CNFG	 0x0070	  /* Suspend Configured */ +#define	  DS_SPD_ADDR	 0x0060	  /* Suspend Address */ +#define	  DS_SPD_DFLT	 0x0050	  /* Suspend Default */ +#define	  DS_SPD_POWR	 0x0040	  /* Suspend Powered */ +#define	  DS_SUSP	 0x0040	  /* Suspend */ +#define	  DS_CNFG	 0x0030	  /* Configured */ +#define	  DS_ADDS	 0x0020	  /* Address */ +#define	  DS_DFLT	 0x0010	  /* Default */ +#define	  DS_POWR	 0x0000	  /* Powered */ +#define	DVSQS		0x0030	/* b5-4: Device state */ +#define	VALID		0x0008	/* b3: Setup packet detected flag */ +#define	CTSQ		0x0007	/* b2-0: Control transfer stage */ +#define	  CS_SQER	 0x0006	  /* Sequence error */ +#define	  CS_WRND	 0x0005	  /* Control write nodata status stage */ +#define	  CS_WRSS	 0x0004	  /* Control write status stage */ +#define	  CS_WRDS	 0x0003	  /* Control write data stage */ +#define	  CS_RDSS	 0x0002	  /* Control read status stage */ +#define	  CS_RDDS	 0x0001	  /* Control read data stage */ +#define	  CS_IDST	 0x0000	  /* Idle or setup stage */ + +/* Interrupt Status Register 1 */ +#define	OVRCR		0x8000	/* b15: Over-current interrupt */ +#define	BCHG		0x4000	/* b14: USB bus chenge interrupt */ +#define	DTCH		0x1000	/* b12: Detach sense interrupt */ +#define	ATTCH		0x0800	/* b11: Attach sense interrupt */ +#define	EOFERR		0x0040	/* b6: EOF-error interrupt */ +#define	SIGN		0x0020	/* b5: Setup ignore interrupt */ +#define	SACK		0x0010	/* b4: Setup acknowledge interrupt */ + +/* Frame Number Register */ +#define	OVRN		0x8000	/* b15: Overrun error */ +#define	CRCE		0x4000	/* b14: Received data error */ +#define	FRNM		0x07FF	/* b10-0: Frame number */ + +/* Micro Frame Number Register */ +#define	UFRNM		0x0007	/* b2-0: Micro frame number */ + +/* Default Control Pipe Maxpacket Size Register */ +/* Pipe Maxpacket Size Register */ +#define	DEVSEL	0xF000	/* b15-14: Device address select */ +#define	MAXP	0x007F	/* b6-0: Maxpacket size of default control pipe */ + +/* Default Control Pipe Control Register */ +#define	BSTS		0x8000	/* b15: Buffer status */ +#define	SUREQ		0x4000	/* b14: Send USB request  */ +#define	CSCLR		0x2000	/* b13: complete-split status clear */ +#define	CSSTS		0x1000	/* b12: complete-split status */ +#define	SUREQCLR	0x0800	/* b11: stop setup request */ +#define	SQCLR		0x0100	/* b8: Sequence toggle bit clear */ +#define	SQSET		0x0080	/* b7: Sequence toggle bit set */ +#define	SQMON		0x0040	/* b6: Sequence toggle bit monitor */ +#define	PBUSY		0x0020	/* b5: pipe busy */ +#define	PINGE		0x0010	/* b4: ping enable */ +#define	CCPL		0x0004	/* b2: Enable control transfer complete */ +#define	PID		0x0003	/* b1-0: Response PID */ +#define	  PID_STALL11	 0x0003	  /* STALL */ +#define	  PID_STALL	 0x0002	  /* STALL */ +#define	  PID_BUF	 0x0001	  /* BUF */ +#define	  PID_NAK	 0x0000	  /* NAK */ + +/* Pipe Window Select Register */ +#define	PIPENM		0x0007	/* b2-0: Pipe select */ + +/* Pipe Configuration Register */ +#define	R8A66597_TYP	0xC000	/* b15-14: Transfer type */ +#define	  R8A66597_ISO	 0xC000		  /* Isochronous */ +#define	  R8A66597_INT	 0x8000		  /* Interrupt */ +#define	  R8A66597_BULK	 0x4000		  /* Bulk */ +#define	R8A66597_BFRE	0x0400	/* b10: Buffer ready interrupt mode select */ +#define	R8A66597_DBLB	0x0200	/* b9: Double buffer mode select */ +#define	R8A66597_CNTMD	0x0100	/* b8: Continuous transfer mode select */ +#define	R8A66597_SHTNAK	0x0080	/* b7: Transfer end NAK */ +#define	R8A66597_DIR	0x0010	/* b4: Transfer direction select */ +#define	R8A66597_EPNUM	0x000F	/* b3-0: Eendpoint number select */ + +/* Pipe Buffer Configuration Register */ +#define	BUFSIZE		0x7C00	/* b14-10: Pipe buffer size */ +#define	BUFNMB		0x007F	/* b6-0: Pipe buffer number */ +#define	PIPE0BUF	256 +#define	PIPExBUF	64 + +/* Pipe Maxpacket Size Register */ +#define	MXPS		0x07FF	/* b10-0: Maxpacket size */ + +/* Pipe Cycle Configuration Register */ +#define	IFIS	0x1000	/* b12: Isochronous in-buffer flush mode select */ +#define	IITV	0x0007	/* b2-0: Isochronous interval */ + +/* Pipex Control Register */ +#define	BSTS	0x8000	/* b15: Buffer status */ +#define	INBUFM	0x4000	/* b14: IN buffer monitor (Only for PIPE1 to 5) */ +#define	CSCLR	0x2000	/* b13: complete-split status clear */ +#define	CSSTS	0x1000	/* b12: complete-split status */ +#define	ATREPM	0x0400	/* b10: Auto repeat mode */ +#define	ACLRM	0x0200	/* b9: Out buffer auto clear mode */ +#define	SQCLR	0x0100	/* b8: Sequence toggle bit clear */ +#define	SQSET	0x0080	/* b7: Sequence toggle bit set */ +#define	SQMON	0x0040	/* b6: Sequence toggle bit monitor */ +#define	PBUSY	0x0020	/* b5: pipe busy */ +#define	PID	0x0003	/* b1-0: Response PID */ + +/* PIPExTRE */ +#define	TRENB		0x0200	/* b9: Transaction counter enable */ +#define	TRCLR		0x0100	/* b8: Transaction counter clear */ + +/* PIPExTRN */ +#define	TRNCNT		0xFFFF	/* b15-0: Transaction counter */ + +/* DEVADDx */ +#define	UPPHUB		0x7800 +#define	HUBPORT		0x0700 +#define	USBSPD		0x00C0 +#define	RTPORT		0x0001 + +#define R8A66597_MAX_NUM_PIPE		10 +#define R8A66597_BUF_BSIZE		8 +#define R8A66597_MAX_DEVICE		10 +#if defined(CONFIG_SUPERH_ON_CHIP_R8A66597) +#define R8A66597_MAX_ROOT_HUB		1 +#else +#define R8A66597_MAX_ROOT_HUB		2 +#endif +#define R8A66597_MAX_SAMPLING		5 +#define R8A66597_RH_POLL_TIME		10 + +#define BULK_IN_PIPENUM		3 +#define BULK_IN_BUFNUM		8 + +#define BULK_OUT_PIPENUM	4 +#define BULK_OUT_BUFNUM		40 + +#define check_bulk_or_isoc(pipenum)	((pipenum >= 1 && pipenum <= 5)) +#define check_interrupt(pipenum)	((pipenum >= 6 && pipenum <= 9)) +#define make_devsel(addr)		(addr << 12) + +struct r8a66597 { +	unsigned long reg; +	unsigned short pipe_config;	/* bit field */ +	unsigned short port_status; +	unsigned short port_change; +	u16 speed;	/* HSMODE or FSMODE or LSMODE */ +	unsigned char rh_devnum; +}; + +static inline u16 r8a66597_read(struct r8a66597 *r8a66597, unsigned long offset) +{ +	return inw(r8a66597->reg + offset); +} + +static inline void r8a66597_read_fifo(struct r8a66597 *r8a66597, +				      unsigned long offset, void *buf, +				      int len) +{ +	int i; +#if defined(CONFIG_SUPERH_ON_CHIP_R8A66597) +	unsigned long fifoaddr = r8a66597->reg + offset; +	unsigned long count; +	unsigned long *p = buf; + +	count = len / 4; +	for (i = 0; i < count; i++) +		inl(p[i], r8a66597->reg + offset); + +	if (len & 0x00000003) { +		unsigned long tmp = inl(fifoaddr); +		memcpy((unsigned char *)buf + count * 4, &tmp, len & 0x03); +	} +#else +	unsigned short *p = buf; + +	len = (len + 1) / 2; +	for (i = 0; i < len; i++) +		p[i] = inw(r8a66597->reg + offset); +#endif +} + +static inline void r8a66597_write(struct r8a66597 *r8a66597, u16 val, +				  unsigned long offset) +{ +	outw(val, r8a66597->reg + offset); +} + +static inline void r8a66597_write_fifo(struct r8a66597 *r8a66597, +				       unsigned long offset, void *buf, +				       int len) +{ +	int i; +	unsigned long fifoaddr = r8a66597->reg + offset; +#if defined(CONFIG_SUPERH_ON_CHIP_R8A66597) +	unsigned long count; +	unsigned char *pb; +	unsigned long *p = buf; + +	count = len / 4; +	for (i = 0; i < count; i++) +		outl(p[i], fifoaddr); + +	if (len & 0x00000003) { +		pb = (unsigned char *)buf + count * 4; +		for (i = 0; i < (len & 0x00000003); i++) { +			if (r8a66597_read(r8a66597, CFIFOSEL) & BIGEND) +				outb(pb[i], fifoaddr + i); +			else +				outb(pb[i], fifoaddr + 3 - i); +		} +	} +#else +	int odd = len & 0x0001; +	unsigned short *p = buf; + +	len = len / 2; +	for (i = 0; i < len; i++) +		outw(p[i], fifoaddr); + +	if (odd) { +		unsigned char *pb = (unsigned char *)(buf + len); +		outb(*pb, fifoaddr); +	} +#endif +} + +static inline void r8a66597_mdfy(struct r8a66597 *r8a66597, +				 u16 val, u16 pat, unsigned long offset) +{ +	u16 tmp; +	tmp = r8a66597_read(r8a66597, offset); +	tmp = tmp & (~pat); +	tmp = tmp | val; +	r8a66597_write(r8a66597, tmp, offset); +} + +#define r8a66597_bclr(r8a66597, val, offset)	\ +			r8a66597_mdfy(r8a66597, 0, val, offset) +#define r8a66597_bset(r8a66597, val, offset)	\ +			r8a66597_mdfy(r8a66597, val, 0, offset) + +static inline unsigned long get_syscfg_reg(int port) +{ +	return port == 0 ? SYSCFG0 : SYSCFG1; +} + +static inline unsigned long get_syssts_reg(int port) +{ +	return port == 0 ? SYSSTS0 : SYSSTS1; +} + +static inline unsigned long get_dvstctr_reg(int port) +{ +	return port == 0 ? DVSTCTR0 : DVSTCTR1; +} + +static inline unsigned long get_dmacfg_reg(int port) +{ +	return port == 0 ? DMA0CFG : DMA1CFG; +} + +static inline unsigned long get_intenb_reg(int port) +{ +	return port == 0 ? INTENB1 : INTENB2; +} + +static inline unsigned long get_intsts_reg(int port) +{ +	return port == 0 ? INTSTS1 : INTSTS2; +} + +static inline u16 get_rh_usb_speed(struct r8a66597 *r8a66597, int port) +{ +	unsigned long dvstctr_reg = get_dvstctr_reg(port); + +	return r8a66597_read(r8a66597, dvstctr_reg) & RHST; +} + +static inline void r8a66597_port_power(struct r8a66597 *r8a66597, int port, +				       int power) +{ +	unsigned long dvstctr_reg = get_dvstctr_reg(port); + +	if (power) +		r8a66597_bset(r8a66597, VBOUT, dvstctr_reg); +	else +		r8a66597_bclr(r8a66597, VBOUT, dvstctr_reg); +} + +#define get_pipectr_addr(pipenum)	(PIPE1CTR + (pipenum - 1) * 2) +#define get_pipetre_addr(pipenum)	(PIPE1TRE + (pipenum - 1) * 4) +#define get_pipetrn_addr(pipenum)	(PIPE1TRN + (pipenum - 1) * 4) +#define get_devadd_addr(address)	(DEVADD0 + address * 2) + + +/* USB HUB CONSTANTS (not OHCI-specific; see hub.h, based on usb_ohci.h) */ + +/* destination of request */ +#define RH_INTERFACE		   0x01 +#define RH_ENDPOINT		   0x02 +#define RH_OTHER		   0x03 + +#define RH_CLASS		   0x20 +#define RH_VENDOR		   0x40 + +/* Requests: bRequest << 8 | bmRequestType */ +#define RH_GET_STATUS		0x0080 +#define RH_CLEAR_FEATURE	0x0100 +#define RH_SET_FEATURE		0x0300 +#define RH_SET_ADDRESS		0x0500 +#define RH_GET_DESCRIPTOR	0x0680 +#define RH_SET_DESCRIPTOR	0x0700 +#define RH_GET_CONFIGURATION	0x0880 +#define RH_SET_CONFIGURATION	0x0900 +#define RH_GET_STATE		0x0280 +#define RH_GET_INTERFACE	0x0A80 +#define RH_SET_INTERFACE	0x0B00 +#define RH_SYNC_FRAME		0x0C80 +/* Our Vendor Specific Request */ +#define RH_SET_EP		0x2000 + +/* Hub port features */ +#define RH_PORT_CONNECTION	   0x00 +#define RH_PORT_ENABLE		   0x01 +#define RH_PORT_SUSPEND		   0x02 +#define RH_PORT_OVER_CURRENT	   0x03 +#define RH_PORT_RESET		   0x04 +#define RH_PORT_POWER		   0x08 +#define RH_PORT_LOW_SPEED	   0x09 + +#define RH_C_PORT_CONNECTION	   0x10 +#define RH_C_PORT_ENABLE	   0x11 +#define RH_C_PORT_SUSPEND	   0x12 +#define RH_C_PORT_OVER_CURRENT	   0x13 +#define RH_C_PORT_RESET		   0x14 + +/* Hub features */ +#define RH_C_HUB_LOCAL_POWER	   0x00 +#define RH_C_HUB_OVER_CURRENT	   0x01 + +#define RH_DEVICE_REMOTE_WAKEUP	   0x00 +#define RH_ENDPOINT_STALL	   0x01 + +#define RH_ACK			   0x01 +#define RH_REQ_ERR		   -1 +#define RH_NACK			   0x00 + +/* OHCI ROOT HUB REGISTER MASKS */ + +/* roothub.portstatus [i] bits */ +#define RH_PS_CCS	0x00000001	/* current connect status */ +#define RH_PS_PES	0x00000002	/* port enable status*/ +#define RH_PS_PSS	0x00000004	/* port suspend status */ +#define RH_PS_POCI	0x00000008	/* port over current indicator */ +#define RH_PS_PRS	0x00000010	/* port reset status */ +#define RH_PS_PPS	0x00000100	/* port power status */ +#define RH_PS_LSDA	0x00000200	/* low speed device attached */ +#define RH_PS_CSC	0x00010000	/* connect status change */ +#define RH_PS_PESC	0x00020000	/* port enable status change */ +#define RH_PS_PSSC	0x00040000	/* port suspend status change */ +#define RH_PS_OCIC	0x00080000	/* over current indicator change */ +#define RH_PS_PRSC	0x00100000	/* port reset status change */ + +/* roothub.status bits */ +#define RH_HS_LPS	0x00000001	/* local power status */ +#define RH_HS_OCI	0x00000002	/* over current indicator */ +#define RH_HS_DRWE	0x00008000	/* device remote wakeup enable */ +#define RH_HS_LPSC	0x00010000	/* local power status change */ +#define RH_HS_OCIC	0x00020000	/* over current indicator change */ +#define RH_HS_CRWE	0x80000000	/* clear remote wakeup enable */ + +/* roothub.b masks */ +#define RH_B_DR		0x0000ffff	/* device removable flags */ +#define RH_B_PPCM	0xffff0000	/* port power control mask */ + +/* roothub.a masks */ +#define RH_A_NDP	(0xff << 0)	/* number of downstream ports */ +#define RH_A_PSM	(1 << 8)	/* power switching mode */ +#define RH_A_NPS	(1 << 9)	/* no power switching */ +#define RH_A_DT		(1 << 10)	/* device type (mbz) */ +#define RH_A_OCPM	(1 << 11)	/* over current protection mode */ +#define RH_A_NOCP	(1 << 12)	/* no over current protection */ +#define RH_A_POTPGT	(0xff << 24)	/* power on to power good time */ + +#endif	/* __R8A66597_H__ */ diff --git a/drivers/usb/host/s3c64xx-hcd.c b/drivers/usb/host/s3c64xx-hcd.c new file mode 100644 index 000000000..274a4ed30 --- /dev/null +++ b/drivers/usb/host/s3c64xx-hcd.c @@ -0,0 +1,45 @@ +/* + * URB OHCI HCD (Host Controller Driver) initialization for USB on the S3C64XX. + * + * Copyright (C) 2008, + * Guennadi Liakhovetski, DENX Software Engineering <lg@denx.de> + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + */ + +#include <common.h> +#include <s3c6400.h> + +int usb_cpu_init(void) +{ +	OTHERS_REG |= 0x10000; +	return 0; +} + +int usb_cpu_stop(void) +{ +	OTHERS_REG &= ~0x10000; +	return 0; +} + +void usb_cpu_init_fail(void) +{ +	OTHERS_REG &= ~0x10000; +} diff --git a/drivers/usb/host/sl811-hcd.c b/drivers/usb/host/sl811-hcd.c new file mode 100644 index 000000000..82a8b365f --- /dev/null +++ b/drivers/usb/host/sl811-hcd.c @@ -0,0 +1,734 @@ +/* + * (C) Copyright 2004 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + * + * This code is based on linux driver for sl811hs chip, source at + * drivers/usb/host/sl811.c: + * + * SL811 Host Controller Interface driver for USB. + * + * Copyright (c) 2003/06, Courage Co., Ltd. + * + * Based on: + *	1.uhci.c by Linus Torvalds, Johannes Erdfelt, Randy Dunlap, + *	  Georg Acher, Deti Fliegl, Thomas Sailer, Roman Weissgaerber, + *	  Adam Richter, Gregory P. Smith; + *	2.Original SL811 driver (hc_sl811.o) by Pei Liu <pbl@cypress.com> + *	3.Rewrited as sl811.o by Yin Aihua <yinah:couragetech.com.cn> + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <common.h> +#include <mpc8xx.h> +#include <usb.h> +#include "sl811.h" + +#include "../../../board/kup/common/kup.h" + +#ifdef __PPC__ +# define EIEIO		__asm__ volatile ("eieio") +#else +# define EIEIO		/* nothing */ +#endif + +#define	 SL811_ADR (0x50000000) +#define	 SL811_DAT (0x50000001) + +#define mdelay(n) ({unsigned long msec=(n); while (msec--) udelay(1000);}) + +#ifdef SL811_DEBUG +static int debug = 9; +#endif + +static int root_hub_devnum = 0; +static struct usb_port_status rh_status = { 0 };/* root hub port status */ + +static int sl811_rh_submit_urb(struct usb_device *usb_dev, unsigned long pipe, +			       void *data, int buf_len, struct devrequest *cmd); + +static void sl811_write (__u8 index, __u8 data) +{ +	*(volatile unsigned char *) (SL811_ADR) = index; +	EIEIO; +	*(volatile unsigned char *) (SL811_DAT) = data; +	EIEIO; +} + +static __u8 sl811_read (__u8 index) +{ +	__u8 data; + +	*(volatile unsigned char *) (SL811_ADR) = index; +	EIEIO; +	data = *(volatile unsigned char *) (SL811_DAT); +	EIEIO; +	return (data); +} + +/* + * Read consecutive bytes of data from the SL811H/SL11H buffer + */ +static void inline sl811_read_buf(__u8 offset, __u8 *buf, __u8 size) +{ +	*(volatile unsigned char *) (SL811_ADR) = offset; +	EIEIO; +	while (size--) { +		*buf++ = *(volatile unsigned char *) (SL811_DAT); +		EIEIO; +	} +} + +/* + * Write consecutive bytes of data to the SL811H/SL11H buffer + */ +static void inline sl811_write_buf(__u8 offset, __u8 *buf, __u8 size) +{ +	*(volatile unsigned char *) (SL811_ADR) = offset; +	EIEIO; +	while (size--) { +		*(volatile unsigned char *) (SL811_DAT) = *buf++; +		EIEIO; +	} +} + +int usb_init_kup4x (void) +{ +	volatile immap_t *immap = (immap_t *) CONFIG_SYS_IMMR; +	volatile memctl8xx_t *memctl = &immap->im_memctl; +	int i; +	unsigned char tmp; + +	memctl = &immap->im_memctl; +	memctl->memc_or7 = 0xFFFF8726; +	memctl->memc_br7 = 0x50000401;	/* start at 0x50000000 */ +	/* BP 14 low = USB ON */ +	immap->im_cpm.cp_pbdat &= ~(BP_USB_VCC); +	/* PB 14 nomal port */ +	immap->im_cpm.cp_pbpar &= ~(BP_USB_VCC); +	/* output */ +	immap->im_cpm.cp_pbdir |= (BP_USB_VCC); + +	puts ("USB:   "); + +	for (i = 0x10; i < 0xff; i++) { +		sl811_write(i, i); +		tmp = (sl811_read(i)); +		if (tmp != i) { +			printf ("SL811 compare error index=0x%02x read=0x%02x\n", i, tmp); +			return (-1); +		} +	} +	printf ("SL811 ready\n"); +	return (0); +} + +/* + * This function resets SL811HS controller and detects the speed of + * the connecting device + * + * Return: 0 = no device attached; 1 = USB device attached + */ +static int sl811_hc_reset(void) +{ +	int status ; + +	sl811_write(SL811_CTRL2, SL811_CTL2_HOST | SL811_12M_HI); +	sl811_write(SL811_CTRL1, SL811_CTRL1_RESET); + +	mdelay(20); + +	/* Disable hardware SOF generation, clear all irq status. */ +	sl811_write(SL811_CTRL1, 0); +	mdelay(2); +	sl811_write(SL811_INTRSTS, 0xff); +	status = sl811_read(SL811_INTRSTS); + +	if (status & SL811_INTR_NOTPRESENT) { +		/* Device is not present */ +		PDEBUG(0, "Device not present\n"); +		rh_status.wPortStatus &= ~(USB_PORT_STAT_CONNECTION | USB_PORT_STAT_ENABLE); +		rh_status.wPortChange |= USB_PORT_STAT_C_CONNECTION; +		sl811_write(SL811_INTR, SL811_INTR_INSRMV); +		return 0; +	} + +	/* Send SOF to address 0, endpoint 0. */ +	sl811_write(SL811_LEN_B, 0); +	sl811_write(SL811_PIDEP_B, PIDEP(USB_PID_SOF, 0)); +	sl811_write(SL811_DEV_B, 0x00); +	sl811_write(SL811_SOFLOW, SL811_12M_LOW); + +	if (status & SL811_INTR_SPEED_FULL) { +		/* full speed device connect directly to root hub */ +		PDEBUG (0, "Full speed Device attached\n"); + +		sl811_write(SL811_CTRL1, SL811_CTRL1_RESET); +		mdelay(20); +		sl811_write(SL811_CTRL2, SL811_CTL2_HOST | SL811_12M_HI); +		sl811_write(SL811_CTRL1, SL811_CTRL1_SOF); + +		/* start the SOF or EOP */ +		sl811_write(SL811_CTRL_B, SL811_USB_CTRL_ARM); +		rh_status.wPortStatus |= USB_PORT_STAT_CONNECTION; +		rh_status.wPortStatus &= ~USB_PORT_STAT_LOW_SPEED; +		mdelay(2); +		sl811_write(SL811_INTRSTS, 0xff); +	} else { +		/* slow speed device connect directly to root-hub */ +		PDEBUG(0, "Low speed Device attached\n"); + +		sl811_write(SL811_CTRL1, SL811_CTRL1_RESET); +		mdelay(20); +		sl811_write(SL811_CTRL2, SL811_CTL2_HOST | SL811_CTL2_DSWAP | SL811_12M_HI); +		sl811_write(SL811_CTRL1, SL811_CTRL1_SPEED_LOW | SL811_CTRL1_SOF); + +		/* start the SOF or EOP */ +		sl811_write(SL811_CTRL_B, SL811_USB_CTRL_ARM); +		rh_status.wPortStatus |= USB_PORT_STAT_CONNECTION | USB_PORT_STAT_LOW_SPEED; +		mdelay(2); +		sl811_write(SL811_INTRSTS, 0xff); +	} + +	rh_status.wPortChange |= USB_PORT_STAT_C_CONNECTION; +	sl811_write(SL811_INTR, /*SL811_INTR_INSRMV*/SL811_INTR_DONE_A); + +	return 1; +} + +int usb_lowlevel_init(void) +{ +	root_hub_devnum = 0; +	sl811_hc_reset(); +	return 0; +} + +int usb_lowlevel_stop(void) +{ +	sl811_hc_reset(); +	return 0; +} + +static int calc_needed_buswidth(int bytes, int need_preamble) +{ +	return !need_preamble ? bytes * 8 + 256 : 8 * 8 * bytes + 2048; +} + +static int sl811_send_packet(struct usb_device *dev, unsigned long pipe, __u8 *buffer, int len) +{ +	__u8 ctrl = SL811_USB_CTRL_ARM | SL811_USB_CTRL_ENABLE; +	__u16 status = 0; +	int err = 0, time_start = get_timer(0); +	int need_preamble = !(rh_status.wPortStatus & USB_PORT_STAT_LOW_SPEED) && +		usb_pipeslow(pipe); + +	if (len > 239) +		return -1; + +	if (usb_pipeout(pipe)) +		ctrl |= SL811_USB_CTRL_DIR_OUT; +	if (usb_gettoggle(dev, usb_pipeendpoint(pipe), usb_pipeout(pipe))) +		ctrl |= SL811_USB_CTRL_TOGGLE_1; +	if (need_preamble) +		ctrl |= SL811_USB_CTRL_PREAMBLE; + +	sl811_write(SL811_INTRSTS, 0xff); + +	while (err < 3) { +		sl811_write(SL811_ADDR_A, 0x10); +		sl811_write(SL811_LEN_A, len); +		if (usb_pipeout(pipe) && len) +			sl811_write_buf(0x10, buffer, len); + +		if (!(rh_status.wPortStatus & USB_PORT_STAT_LOW_SPEED) && +		    sl811_read(SL811_SOFCNTDIV)*64 < calc_needed_buswidth(len, need_preamble)) +			ctrl |= SL811_USB_CTRL_SOF; +		else +			ctrl &= ~SL811_USB_CTRL_SOF; + +		sl811_write(SL811_CTRL_A, ctrl); +		while (!(sl811_read(SL811_INTRSTS) & SL811_INTR_DONE_A)) { +			if (5*CONFIG_SYS_HZ < get_timer(time_start)) { +				printf("USB transmit timed out\n"); +				return -USB_ST_CRC_ERR; +			} +		} + +		sl811_write(SL811_INTRSTS, 0xff); +		status = sl811_read(SL811_STS_A); + +		if (status & SL811_USB_STS_ACK) { +			int remainder = sl811_read(SL811_CNT_A); +			if (remainder) { +				PDEBUG(0, "usb transfer remainder = %d\n", remainder); +				len -= remainder; +			} +			if (usb_pipein(pipe) && len) +				sl811_read_buf(0x10, buffer, len); +			return len; +		} + +		if ((status & SL811_USB_STS_NAK) == SL811_USB_STS_NAK) +			continue; + +		PDEBUG(0, "usb transfer error %#x\n", (int)status); +		err++; +	} + +	err = 0; + +	if (status & SL811_USB_STS_ERROR) +		err |= USB_ST_BUF_ERR; +	if (status & SL811_USB_STS_TIMEOUT) +		err |= USB_ST_CRC_ERR; +	if (status & SL811_USB_STS_STALL) +		err |= USB_ST_STALLED; + +	return -err; +} + +int submit_bulk_msg(struct usb_device *dev, unsigned long pipe, void *buffer, +		    int len) +{ +	int dir_out = usb_pipeout(pipe); +	int ep = usb_pipeendpoint(pipe); +	int max = usb_maxpacket(dev, pipe); +	int done = 0; + +	PDEBUG(7, "dev = %ld pipe = %ld buf = %p size = %d dir_out = %d\n", +	       usb_pipedevice(pipe), usb_pipeendpoint(pipe), buffer, len, dir_out); + +	dev->status = 0; + +	sl811_write(SL811_DEV_A, usb_pipedevice(pipe)); +	sl811_write(SL811_PIDEP_A, PIDEP(!dir_out ? USB_PID_IN : USB_PID_OUT, ep)); +	while (done < len) { +		int res = sl811_send_packet(dev, pipe, (__u8*)buffer+done, +					    max > len - done ? len - done : max); +		if (res < 0) { +			dev->status = -res; +			return res; +		} + +		if (!dir_out && res < max) /* short packet */ +			break; + +		done += res; +		usb_dotoggle(dev, ep, dir_out); +	} + +	dev->act_len = done; + +	return 0; +} + +int submit_control_msg(struct usb_device *dev, unsigned long pipe, void *buffer, +		       int len,struct devrequest *setup) +{ +	int done = 0; +	int devnum = usb_pipedevice(pipe); +	int ep = usb_pipeendpoint(pipe); + +	dev->status = 0; + +	if (devnum == root_hub_devnum) +		return sl811_rh_submit_urb(dev, pipe, buffer, len, setup); + +	PDEBUG(7, "dev = %d pipe = %ld buf = %p size = %d rt = %#x req = %#x bus = %i\n", +	       devnum, ep, buffer, len, (int)setup->requesttype, +	       (int)setup->request, sl811_read(SL811_SOFCNTDIV)*64); + +	sl811_write(SL811_DEV_A, devnum); +	sl811_write(SL811_PIDEP_A, PIDEP(USB_PID_SETUP, ep)); +	/* setup phase */ +	usb_settoggle(dev, ep, 1, 0); +	if (sl811_send_packet(dev, usb_sndctrlpipe(dev, ep), +			      (__u8*)setup, sizeof(*setup)) == sizeof(*setup)) { +		int dir_in = usb_pipein(pipe); +		int max = usb_maxpacket(dev, pipe); + +		/* data phase */ +		sl811_write(SL811_PIDEP_A, +			    PIDEP(dir_in ? USB_PID_IN : USB_PID_OUT, ep)); +		usb_settoggle(dev, ep, usb_pipeout(pipe), 1); +		while (done < len) { +			int res = sl811_send_packet(dev, pipe, (__u8*)buffer+done, +						    max > len - done ? len - done : max); +			if (res < 0) { +				PDEBUG(0, "status data failed!\n"); +				dev->status = -res; +				return 0; +			} +			done += res; +			usb_dotoggle(dev, ep, usb_pipeout(pipe)); +			if (dir_in && res < max) /* short packet */ +				break; +		} + +		/* status phase */ +		sl811_write(SL811_PIDEP_A, +			    PIDEP(!dir_in ? USB_PID_IN : USB_PID_OUT, ep)); +		usb_settoggle(dev, ep, !usb_pipeout(pipe), 1); +		if (sl811_send_packet(dev, +				      !dir_in ? usb_rcvctrlpipe(dev, ep) : +				      usb_sndctrlpipe(dev, ep), +				      0, 0) < 0) { +			PDEBUG(0, "status phase failed!\n"); +			dev->status = -1; +		} +	} else { +		PDEBUG(0, "setup phase failed!\n"); +		dev->status = -1; +	} + +	dev->act_len = done; + +	return done; +} + +int submit_int_msg(struct usb_device *dev, unsigned long pipe, void *buffer, +		   int len, int interval) +{ +	PDEBUG(0, "dev = %p pipe = %#lx buf = %p size = %d int = %d\n", dev, pipe, +	       buffer, len, interval); +	return -1; +} + +/* + * SL811 Virtual Root Hub + */ + +/* Device descriptor */ +static __u8 sl811_rh_dev_des[] = +{ +	0x12,	    /*	__u8  bLength; */ +	0x01,	    /*	__u8  bDescriptorType; Device */ +	0x10,	    /*	__u16 bcdUSB; v1.1 */ +	0x01, +	0x09,	    /*	__u8  bDeviceClass; HUB_CLASSCODE */ +	0x00,	    /*	__u8  bDeviceSubClass; */ +	0x00,	    /*	__u8  bDeviceProtocol; */ +	0x08,	    /*	__u8  bMaxPacketSize0; 8 Bytes */ +	0x00,	    /*	__u16 idVendor; */ +	0x00, +	0x00,	    /*	__u16 idProduct; */ +	0x00, +	0x00,	    /*	__u16 bcdDevice; */ +	0x00, +	0x00,	    /*	__u8  iManufacturer; */ +	0x02,	    /*	__u8  iProduct; */ +	0x01,	    /*	__u8  iSerialNumber; */ +	0x01	    /*	__u8  bNumConfigurations; */ +}; + +/* Configuration descriptor */ +static __u8 sl811_rh_config_des[] = +{ +	0x09,	    /*	__u8  bLength; */ +	0x02,	    /*	__u8  bDescriptorType; Configuration */ +	0x19,	    /*	__u16 wTotalLength; */ +	0x00, +	0x01,	    /*	__u8  bNumInterfaces; */ +	0x01,	    /*	__u8  bConfigurationValue; */ +	0x00,	    /*	__u8  iConfiguration; */ +	0x40,	    /*	__u8  bmAttributes; +		    Bit 7: Bus-powered, 6: Self-powered, 5 Remote-wakwup, +		    4..0: resvd */ +	0x00,	    /*	__u8  MaxPower; */ + +	/* interface */ +	0x09,	    /*	__u8  if_bLength; */ +	0x04,	    /*	__u8  if_bDescriptorType; Interface */ +	0x00,	    /*	__u8  if_bInterfaceNumber; */ +	0x00,	    /*	__u8  if_bAlternateSetting; */ +	0x01,	    /*	__u8  if_bNumEndpoints; */ +	0x09,	    /*	__u8  if_bInterfaceClass; HUB_CLASSCODE */ +	0x00,	    /*	__u8  if_bInterfaceSubClass; */ +	0x00,	    /*	__u8  if_bInterfaceProtocol; */ +	0x00,	    /*	__u8  if_iInterface; */ + +	/* endpoint */ +	0x07,	    /*	__u8  ep_bLength; */ +	0x05,	    /*	__u8  ep_bDescriptorType; Endpoint */ +	0x81,	    /*	__u8  ep_bEndpointAddress; IN Endpoint 1 */ +	0x03,	    /*	__u8  ep_bmAttributes; Interrupt */ +	0x08,	    /*	__u16 ep_wMaxPacketSize; */ +	0x00, +	0xff	    /*	__u8  ep_bInterval; 255 ms */ +}; + +/* root hub class descriptor*/ +static __u8 sl811_rh_hub_des[] = +{ +	0x09,			/*  __u8  bLength; */ +	0x29,			/*  __u8  bDescriptorType; Hub-descriptor */ +	0x01,			/*  __u8  bNbrPorts; */ +	0x00,			/* __u16  wHubCharacteristics; */ +	0x00, +	0x50,			/*  __u8  bPwrOn2pwrGood; 2ms */ +	0x00,			/*  __u8  bHubContrCurrent; 0 mA */ +	0xfc,			/*  __u8  DeviceRemovable; *** 7 Ports max *** */ +	0xff			/*  __u8  PortPwrCtrlMask; *** 7 ports max *** */ +}; + +/* + * helper routine for returning string descriptors in UTF-16LE + * input can actually be ISO-8859-1; ASCII is its 7-bit subset + */ +static int ascii2utf (char *s, u8 *utf, int utfmax) +{ +	int retval; + +	for (retval = 0; *s && utfmax > 1; utfmax -= 2, retval += 2) { +		*utf++ = *s++; +		*utf++ = 0; +	} +	return retval; +} + +/* + * root_hub_string is used by each host controller's root hub code, + * so that they're identified consistently throughout the system. + */ +static int usb_root_hub_string (int id, int serial, char *type, __u8 *data, int len) +{ +	char buf [30]; + +	/* assert (len > (2 * (sizeof (buf) + 1))); +	   assert (strlen (type) <= 8);*/ + +	/* language ids */ +	if (id == 0) { +		*data++ = 4; *data++ = 3;	/* 4 bytes data */ +		*data++ = 0; *data++ = 0;	/* some language id */ +		return 4; + +	/* serial number */ +	} else if (id == 1) { +		sprintf (buf, "%#x", serial); + +	/* product description */ +	} else if (id == 2) { +		sprintf (buf, "USB %s Root Hub", type); + +	/* id 3 == vendor description */ + +	/* unsupported IDs --> "stall" */ +	} else +	    return 0; + +	ascii2utf (buf, data + 2, len - 2); +	data [0] = 2 + strlen(buf) * 2; +	data [1] = 3; +	return data [0]; +} + +/* helper macro */ +#define OK(x)	len = (x); break + +/* + * This function handles all USB request to the the virtual root hub + */ +static int sl811_rh_submit_urb(struct usb_device *usb_dev, unsigned long pipe, +			       void *data, int buf_len, struct devrequest *cmd) +{ +	__u8 data_buf[16]; +	__u8 *bufp = data_buf; +	int len = 0; +	int status = 0; + +	__u16 bmRType_bReq; +	__u16 wValue; +	__u16 wIndex; +	__u16 wLength; + +	if (usb_pipeint(pipe)) { +		PDEBUG(0, "interrupt transfer unimplemented!\n"); +		return 0; +	} + +	bmRType_bReq  = cmd->requesttype | (cmd->request << 8); +	wValue	      = le16_to_cpu (cmd->value); +	wIndex	      = le16_to_cpu (cmd->index); +	wLength	      = le16_to_cpu (cmd->length); + +	PDEBUG(5, "submit rh urb, req = %d(%x) val = %#x index = %#x len=%d\n", +	       bmRType_bReq, bmRType_bReq, wValue, wIndex, wLength); + +	/* Request Destination: +		   without flags: Device, +		   USB_RECIP_INTERFACE: interface, +		   USB_RECIP_ENDPOINT: endpoint, +		   USB_TYPE_CLASS means HUB here, +		   USB_RECIP_OTHER | USB_TYPE_CLASS  almost ever means HUB_PORT here +	*/ +	switch (bmRType_bReq) { +	case RH_GET_STATUS: +		*(__u16 *)bufp = cpu_to_le16(1); +		OK(2); + +	case RH_GET_STATUS | USB_RECIP_INTERFACE: +		*(__u16 *)bufp = cpu_to_le16(0); +		OK(2); + +	case RH_GET_STATUS | USB_RECIP_ENDPOINT: +		*(__u16 *)bufp = cpu_to_le16(0); +		OK(2); + +	case RH_GET_STATUS | USB_TYPE_CLASS: +		*(__u32 *)bufp = cpu_to_le32(0); +		OK(4); + +	case RH_GET_STATUS | USB_RECIP_OTHER | USB_TYPE_CLASS: +		*(__u32 *)bufp = cpu_to_le32(rh_status.wPortChange<<16 | rh_status.wPortStatus); +		OK(4); + +	case RH_CLEAR_FEATURE | USB_RECIP_ENDPOINT: +		switch (wValue) { +		case 1: +			OK(0); +		} +		break; + +	case RH_CLEAR_FEATURE | USB_TYPE_CLASS: +		switch (wValue) { +		case C_HUB_LOCAL_POWER: +			OK(0); + +		case C_HUB_OVER_CURRENT: +			OK(0); +		} +		break; + +	case RH_CLEAR_FEATURE | USB_RECIP_OTHER | USB_TYPE_CLASS: +		switch (wValue) { +		case USB_PORT_FEAT_ENABLE: +			rh_status.wPortStatus &= ~USB_PORT_STAT_ENABLE; +			OK(0); + +		case USB_PORT_FEAT_SUSPEND: +			rh_status.wPortStatus &= ~USB_PORT_STAT_SUSPEND; +			OK(0); + +		case USB_PORT_FEAT_POWER: +			rh_status.wPortStatus &= ~USB_PORT_STAT_POWER; +			OK(0); + +		case USB_PORT_FEAT_C_CONNECTION: +			rh_status.wPortChange &= ~USB_PORT_STAT_C_CONNECTION; +			OK(0); + +		case USB_PORT_FEAT_C_ENABLE: +			rh_status.wPortChange &= ~USB_PORT_STAT_C_ENABLE; +			OK(0); + +		case USB_PORT_FEAT_C_SUSPEND: +			rh_status.wPortChange &= ~USB_PORT_STAT_C_SUSPEND; +			OK(0); + +		case USB_PORT_FEAT_C_OVER_CURRENT: +			rh_status.wPortChange &= ~USB_PORT_STAT_C_OVERCURRENT; +			OK(0); + +		case USB_PORT_FEAT_C_RESET: +			rh_status.wPortChange &= ~USB_PORT_STAT_C_RESET; +			OK(0); +		} +		break; + +	case RH_SET_FEATURE | USB_RECIP_OTHER | USB_TYPE_CLASS: +		switch (wValue) { +		case USB_PORT_FEAT_SUSPEND: +			rh_status.wPortStatus |= USB_PORT_STAT_SUSPEND; +			OK(0); + +		case USB_PORT_FEAT_RESET: +			rh_status.wPortStatus |= USB_PORT_STAT_RESET; +			rh_status.wPortChange = 0; +			rh_status.wPortChange |= USB_PORT_STAT_C_RESET; +			rh_status.wPortStatus &= ~USB_PORT_STAT_RESET; +			rh_status.wPortStatus |= USB_PORT_STAT_ENABLE; +			OK(0); + +		case USB_PORT_FEAT_POWER: +			rh_status.wPortStatus |= USB_PORT_STAT_POWER; +			OK(0); + +		case USB_PORT_FEAT_ENABLE: +			rh_status.wPortStatus |= USB_PORT_STAT_ENABLE; +			OK(0); +		} +		break; + +	case RH_SET_ADDRESS: +		root_hub_devnum = wValue; +		OK(0); + +	case RH_GET_DESCRIPTOR: +		switch ((wValue & 0xff00) >> 8) { +		case USB_DT_DEVICE: +			len = sizeof(sl811_rh_dev_des); +			bufp = sl811_rh_dev_des; +			OK(len); + +		case USB_DT_CONFIG: +			len = sizeof(sl811_rh_config_des); +			bufp = sl811_rh_config_des; +			OK(len); + +		case USB_DT_STRING: +			len = usb_root_hub_string(wValue & 0xff, (int)(long)0,	"SL811HS", data, wLength); +			if (len > 0) { +				bufp = data; +				OK(len); +			} + +		default: +			status = -32; +		} +		break; + +	case RH_GET_DESCRIPTOR | USB_TYPE_CLASS: +		len = sizeof(sl811_rh_hub_des); +		bufp = sl811_rh_hub_des; +		OK(len); + +	case RH_GET_CONFIGURATION: +		bufp[0] = 0x01; +		OK(1); + +	case RH_SET_CONFIGURATION: +		OK(0); + +	default: +		PDEBUG(1, "unsupported root hub command\n"); +		status = -32; +	} + +	len = min(len, buf_len); +	if (data != bufp) +		memcpy(data, bufp, len); + +	PDEBUG(5, "len = %d, status = %d\n", len, status); + +	usb_dev->status = status; +	usb_dev->act_len = len; + +	return status == 0 ? len : status; +} diff --git a/drivers/usb/host/sl811.h b/drivers/usb/host/sl811.h new file mode 100644 index 000000000..c1f9f013b --- /dev/null +++ b/drivers/usb/host/sl811.h @@ -0,0 +1,104 @@ +#ifndef __UBOOT_SL811_H +#define __UBOOT_SL811_H + +#undef SL811_DEBUG + +#ifdef SL811_DEBUG +	#define PDEBUG(level, fmt, args...) \ +		if (debug >= (level)) printf("[%s:%d] " fmt, \ +		__PRETTY_FUNCTION__, __LINE__ , ## args) +#else +	#define PDEBUG(level, fmt, args...) do {} while(0) +#endif + +/* Sl811 host control register */ +#define	SL811_CTRL_A		0x00 +#define	SL811_ADDR_A		0x01 +#define	SL811_LEN_A		0x02 +#define	SL811_STS_A		0x03	/* read	*/ +#define	SL811_PIDEP_A		0x03	/* write */ +#define	SL811_CNT_A		0x04	/* read	*/ +#define	SL811_DEV_A		0x04	/* write */ +#define	SL811_CTRL1		0x05 +#define	SL811_INTR		0x06 +#define	SL811_CTRL_B		0x08 +#define	SL811_ADDR_B		0x09 +#define	SL811_LEN_B		0x0A +#define	SL811_STS_B		0x0B	/* read	*/ +#define	SL811_PIDEP_B		0x0B	/* write */ +#define	SL811_CNT_B		0x0C	/* read	*/ +#define	SL811_DEV_B		0x0C	/* write */ +#define	SL811_INTRSTS		0x0D	/* write clears	bitwise	*/ +#define	SL811_HWREV		0x0E	/* read	*/ +#define	SL811_SOFLOW		0x0E	/* write */ +#define	SL811_SOFCNTDIV		0x0F	/* read	*/ +#define	SL811_CTRL2		0x0F	/* write */ + +/* USB control register bits (addr 0x00 and addr 0x08) */ +#define	SL811_USB_CTRL_ARM	0x01 +#define	SL811_USB_CTRL_ENABLE	0x02 +#define	SL811_USB_CTRL_DIR_OUT	0x04 +#define	SL811_USB_CTRL_ISO	0x10 +#define	SL811_USB_CTRL_SOF	0x20 +#define	SL811_USB_CTRL_TOGGLE_1	0x40 +#define	SL811_USB_CTRL_PREAMBLE	0x80 + +/* USB status register bits (addr 0x03 and addr 0x0B) */ +#define	SL811_USB_STS_ACK	0x01 +#define	SL811_USB_STS_ERROR	0x02 +#define	SL811_USB_STS_TIMEOUT	0x04 +#define	SL811_USB_STS_TOGGLE_1	0x08 +#define	SL811_USB_STS_SETUP	0x10 +#define	SL811_USB_STS_OVERFLOW	0x20 +#define	SL811_USB_STS_NAK	0x40 +#define	SL811_USB_STS_STALL	0x80 + +/* Control register 1 bits (addr 0x05) */ +#define	SL811_CTRL1_SOF		0x01 +#define	SL811_CTRL1_RESET	0x08 +#define	SL811_CTRL1_JKSTATE	0x10 +#define	SL811_CTRL1_SPEED_LOW	0x20 +#define	SL811_CTRL1_SUSPEND	0x40 + +/* Interrut enable (addr 0x06) and interrupt status register bits (addr 0x0D) */ +#define	SL811_INTR_DONE_A	0x01 +#define	SL811_INTR_DONE_B	0x02 +#define	SL811_INTR_SOF		0x10 +#define	SL811_INTR_INSRMV	0x20 +#define	SL811_INTR_DETECT	0x40 +#define	SL811_INTR_NOTPRESENT	0x40 +#define	SL811_INTR_SPEED_FULL	0x80    /* only in status reg */ + +/* HW rev and SOF lo register bits (addr 0x0E) */ +#define	SL811_HWR_HWREV		0xF0 + +/* SOF counter and control reg 2 (addr 0x0F) */ +#define	SL811_CTL2_SOFHI	0x3F +#define	SL811_CTL2_DSWAP	0x40 +#define	SL811_CTL2_HOST		0x80 + +/* Set up for 1-ms SOF time. */ +#define SL811_12M_LOW		0xE0 +#define SL811_12M_HI		0x2E + +#define SL811_DATA_START	0x10 +#define SL811_DATA_LIMIT	240 + +/* Requests: bRequest << 8 | bmRequestType */ +#define RH_GET_STATUS           0x0080 +#define RH_CLEAR_FEATURE        0x0100 +#define RH_SET_FEATURE          0x0300 +#define RH_SET_ADDRESS		0x0500 +#define RH_GET_DESCRIPTOR	0x0680 +#define RH_SET_DESCRIPTOR       0x0700 +#define RH_GET_CONFIGURATION	0x0880 +#define RH_SET_CONFIGURATION	0x0900 +#define RH_GET_STATE            0x0280 +#define RH_GET_INTERFACE        0x0A80 +#define RH_SET_INTERFACE        0x0B00 +#define RH_SYNC_FRAME           0x0C80 + + +#define PIDEP(pid, ep) (((pid) & 0x0f) << 4 | (ep)) + +#endif	/* __UBOOT_SL811_H */ |