diff options
Diffstat (limited to 'drivers/usb_ohci.c')
| -rw-r--r-- | drivers/usb_ohci.c | 1642 | 
1 files changed, 1642 insertions, 0 deletions
| diff --git a/drivers/usb_ohci.c b/drivers/usb_ohci.c new file mode 100644 index 000000000..4065489f3 --- /dev/null +++ b/drivers/usb_ohci.c @@ -0,0 +1,1642 @@ +/* + * URB OHCI HCD (Host Controller Driver) for USB on the AT91RM9200. + * + * (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 - you MUST define LITTLEENDIAN in the configuration file for the + *     board or this driver will NOT work! + * 2 - this driver is intended for use with USB Mass Storage Devices + *     (BBB) ONLY. There is NO support for Interrupt or Isochronous pipes! + * 3 - 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 <pci.h> no PCI on the S3C24X0 */ + +#ifdef CONFIG_USB_OHCI + +#include <asm/arch/hardware.h> + +#include <malloc.h> +#include <usb.h> +#include "usb_ohci.h" + +/* #define OHCI_USE_NPS		/\* force NoPowerSwitching mode *\/ */ +#undef OHCI_VERBOSE_DEBUG	/* not always helpful */ + +/* For initializing controller (mask in an HCFS mode too) */ +#define OHCI_CONTROL_INIT \ +	(OHCI_CTRL_CBSR & 0x3) | OHCI_CTRL_IE | OHCI_CTRL_PLE + +#define readl(a) (*((vu_long *)(a))) +#define writel(a, b) (*((vu_long *)(b)) = ((vu_long)a)) + +#define min_t(type,x,y) ({ type __x = (x); type __y = (y); __x < __y ? __x: __y; }) + +#undef DEBUG +#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) +#undef SHOW_INFO +#ifdef SHOW_INFO +#define info(format, arg...) printf("INFO: " format "\n", ## arg) +#else +#define info(format, arg...) do {} while(0) +#endif + +#define m16_swap(x) swap_16(x) +#define m32_swap(x) swap_32(x) + +/* 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; +/* urb_priv */ +urb_priv_t urb_priv; +/* RHSC flag */ +int got_rhsc; +/* device which was disconnected */ +struct usb_device *devgone; + +/*-------------------------------------------------------------------------*/ + +/* AMD-756 (D2 rev) reports corrupt register contents in some cases. + * The erratum (#4) description is incorrect.  AMD's workaround waits + * till some bits (mostly reserved) are clear; ok for all revs. + */ +#define OHCI_QUIRK_AMD756 0xabcd +#define read_roothub(hc, register, mask) ({ \ +	u32 temp = readl (&hc->regs->roothub.register); \ +	if (hc->flags & OHCI_QUIRK_AMD756) \ +		while (temp & mask) \ +			temp = readl (&hc->regs->roothub.register); \ +	temp; }) + +static u32 roothub_a (struct ohci *hc) +	{ return read_roothub (hc, a, 0xfc0fe000); } +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 u32 roothub_portstatus (struct ohci *hc, int i) +	{ return read_roothub (hc, portstatus [i], 0xffe0fce0); } + + +/* 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; +			} +		} +	} +} + +/*-------------------------------------------------------------------------*/ + +#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 (struct usb_device * dev, unsigned long pipe, void * buffer, +	int transfer_len, struct devrequest * setup, char * str, int small) +{ +	urb_priv_t * purb = &urb_priv; + +	dbg("%s URB:[%4x] dev:%2d,ep:%2d-%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->actual_length, +			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->actual_length, +				transfer_len); +			len = usb_pipeout (pipe)? +					transfer_len: purb->actual_length; +			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 0 /* def CONFIG_CPU_MONAHANS */ +		data_buf [2] = (data_buf [2] == 2) ? 3: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(struct usb_device *dev, unsigned long pipe, void *buffer, +		int transfer_len, struct devrequest *setup, int interval) +{ +	ohci_t *ohci; +	ed_t * ed; +	urb_priv_t *purb_priv; +	int i, size = 0; + +	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; +	} + +	/* every endpoint has a ed, locate and fill it */ +	if (!(ed = ep_add_ed (dev, pipe))) { +		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; +	} + +	if (size >= (N_URB_TD - 1)) { +		err("need %d TDs, only have %d", size, N_URB_TD); +		return -1; +	} +	purb_priv = &urb_priv; +	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; +} + +/*-------------------------------------------------------------------------*/ + +#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 + *-------------------------------------------------------------------------*/ + +/* link an ed into one of the HC chains */ + +static int ep_link (ohci_t *ohci, ed_t *edi) +{ +	volatile ed_t *ed = edi; + +	ed->state = ED_OPER; + +	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 (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 (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; +	} +	return 0; +} + +/*-------------------------------------------------------------------------*/ + +/* 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 *ed) +{ +	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; +	} +	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) +{ +	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) { +		ed->hwINFO = m32_swap (OHCI_ED_SKIP); /* skip ed */ +		/* dummy td; end of td list for ed */ +		td = td_alloc (usb_dev); +		ed->hwTailP = m32_swap (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); + +	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_pipetype(urb_priv->pipe) == PIPE_BULK) && 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 (data); +	if (data) +		td->hwBE = m32_swap (data + len - 1); +	else +		td->hwBE = 0; +	td->hwNextTD = m32_swap (td_pt); +	td->hwPSW [0] = m16_swap (((__u32)data & 0x0FFF) | 0xE000); + +	/* 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) +			writel (OHCI_BLF, &ohci->regs->cmdstatus); /* start bulk list */ +		break; + +	case PIPE_CONTROL: +		info = TD_CC | TD_DP_SETUP | TD_T_DATA0; +		td_fill (ohci, info, setup, 8, dev, cnt++, urb); +		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); +		} +		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) +			writel (OHCI_CLF, &ohci->regs->cmdstatus); /* start Control list */ +		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 = &urb_priv; + +	tdINFO = m32_swap (td->hwINFO); +	tdBE   = m32_swap (td->hwBE); +	tdCBP  = m32_swap (td->hwCBP); + + +	if (!(usb_pipetype (lurb_priv->pipe) == PIPE_CONTROL && +	    ((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; +		} +	} +} + +/*-------------------------------------------------------------------------*/ + +/* 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; +	urb_priv_t *lurb_priv = 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; + +		if (TD_CC_GET (m32_swap (td_list->hwINFO))) { +			lurb_priv = &urb_priv; +			dbg(" USB-error/status: %x : %p", +					TD_CC_GET (m32_swap (td_list->hwINFO)), td_list); +			if (td_list->ed->hwHeadP & m32_swap (0x1)) { +				if (lurb_priv && ((td_list->index + 1) < lurb_priv->length)) { +					td_list->ed->hwHeadP = +						(lurb_priv->td[lurb_priv->length - 1]->hwNextTD & m32_swap (0xfffffff0)) | +									(td_list->ed->hwHeadP & m32_swap (0x2)); +					lurb_priv->td_cnt += lurb_priv->length - td_list->index - 1; +				} else +					td_list->ed->hwHeadP &= m32_swap (0xfffffff2); +			} +		} + +		td_list->next_dl_td = td_rev; +		td_rev = td_list; +		td_list_hc = m32_swap (td_list->hwNextTD) & 0xfffffff0; +	} +	return td_list; +} + +/*-------------------------------------------------------------------------*/ + +/* td done list */ +static int dl_done_list (ohci_t *ohci, td_t *td_list) +{ +	td_t *td_list_next = NULL; +	ed_t *ed; +	int cc = 0; +	int stat = 0; +	/* urb_t *urb; */ +	urb_priv_t *lurb_priv; +	__u32 tdINFO, edHeadP, edTailP; + +	while (td_list) { +		td_list_next = td_list->next_dl_td; + +		lurb_priv = &urb_priv; +		tdINFO = m32_swap (td_list->hwINFO); + +		ed = td_list->ed; + +		dl_transfer_length(td_list); + +		/* error code of transfer */ +		cc = TD_CC_GET (tdINFO); +		if (cc != 0) { +			dbg("ConditionCode %#x", cc); +			stat = cc_to_error[cc]; +		} + +		if (ed->state != ED_NEW) { +			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); +		} + +		td_list = td_list_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 +urb_priv.actual_length = 0; +pkt_print(dev, pipe, buffer, transfer_len, cmd, "SUB(rh)", usb_pipein(pipe)); +#else +	wait_ms(1); +#endif +	if ((pipe & PIPE_INTERRUPT) == PIPE_INTERRUPT) { +		info("Root-Hub submit IRQ: NOT implemented"); +		return 0; +	} + +	bmRType_bReq  = cmd->requesttype | (cmd->request << 8); +	wValue	      = m16_swap (cmd->value); +	wIndex	      = m16_swap (cmd->index); +	wLength	      = m16_swap (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 = m16_swap (1); OK (2); +	case RH_GET_STATUS | RH_INTERFACE: +			*(__u16 *) data_buf = m16_swap (0); OK (2); +	case RH_GET_STATUS | RH_ENDPOINT: +			*(__u16 *) data_buf = m16_swap (0); OK (2); +	case RH_GET_STATUS | RH_CLASS: +			*(__u32 *) data_buf = m32_swap ( +				RD_RH_STAT & ~(RH_HS_CRWE | RH_HS_DRWE)); +			OK (4); +	case RH_GET_STATUS | RH_OTHER | RH_CLASS: +			*(__u32 *) data_buf = m32_swap (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 ); 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 +#if 0 /* def CONFIG_CPU_MONAHANS */ +		data_buf [2] = (data_buf [2] == 2) ? 3: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; +#if 1 +		else if (temp & RH_A_OCPM)	/* per-port overcurrent reporting? */ +			data_buf [3] |= 0x8; +#endif + +		/* 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 +	if (transfer_len) +		urb_priv.actual_length = transfer_len; +	pkt_print(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; + +	/* device pulled? Shortcut the action. */ +	if (devgone == dev) { +		dev->status = USB_ST_CRC_ERR; +		return 0; +	} + +#ifdef DEBUG +	urb_priv.actual_length = 0; +	pkt_print(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(dev, pipe, buffer, transfer_len, setup, interval) < 0) { +		err("sohci_submit_job failed"); +		return -1; +	} + +	wait_ms(10); +	/* ohci_dump_status(&gohci); */ + +	/* allow more time for a BULK device to react - some are slow */ +#define BULK_TO	 5000	/* timeout in milliseconds */ +	if (usb_pipetype (pipe) == PIPE_BULK) +		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; +		} +		if (stat >= 0 && stat != 0xff) { +			/* 0xff is returned for an SF-interrupt */ +			break; +		} +		if (--timeout) { +			wait_ms(1); +		} else { +			err("CTL:TIMEOUT "); +			stat = USB_ST_CRC_ERR; +			break; +		} +	} +	/* we got an Root Hub Status Change interrupt */ +	if (got_rhsc) { +#ifdef DEBUG +		ohci_dump_roothub (&gohci, 1); +#endif +		got_rhsc = 0; +		/* abuse timeout */ +		timeout = rh_check_port_status(&gohci); +		if (timeout >= 0) { +#if 0 /* this does nothing useful, but leave it here in case that changes */ +			/* the called routine adds 1 to the passed value */ +			usb_hub_port_connect_change(gohci.rh.dev, timeout - 1); +#endif +			/* +			 * XXX +			 * This is potentially dangerous because it assumes +			 * that only one device is ever plugged in! +			 */ +			devgone = dev; +		} +	} + +	dev->status = stat; +	dev->act_len = transfer_len; + +#ifdef DEBUG +	pkt_print(dev, pipe, buffer, transfer_len, setup, "RET(ctlr)", usb_pipein(pipe)); +#else +	wait_ms(1); +#endif + +	/* free TDs in urb_priv */ +	urb_free_priv (&urb_priv); +	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 +	urb_priv.actual_length = 0; +	pkt_print(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 -1; +} + +/*-------------------------------------------------------------------------* + * HC functions + *-------------------------------------------------------------------------*/ + +/* reset the HC and BUS */ + +static int hc_reset (ohci_t *ohci) +{ +	int timeout = 30; +	int smm_timeout = 50; /* 0,5 sec */ + +	dbg("%s\n", __FUNCTION__); + +	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) */ +	writel (0, &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; +} + +/*-------------------------------------------------------------------------*/ + +/* 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); +	} + +	/* dbg("Interrupt: %x frame: %x", ints, le16_to_cpu (ohci->hcca->frame_no)); */ + +	if (ints & OHCI_INTR_RHSC) { +		got_rhsc = 1; +	} + +	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); +		stat = dl_done_list (&gohci, dl_reverse_done_list (&gohci)); +		writel (OHCI_INTR_WDH, ®s->intrenable); +	} + +	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) +{ +	/* do board dependant init */ +	if(usb_board_init()) +		return -1; + +	memset (&gohci, 0, sizeof (ohci_t)); +	memset (&urb_priv, 0, sizeof (urb_priv_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; +	gohci.regs = (struct ohci_regs *)OHCI_REGS_BASE; + +	gohci.flags = 0; +	gohci.slot_name = "delta/zylonite"; + +	if (hc_reset (&gohci) < 0) { +		hc_release_ohci (&gohci); +		err ("can't reset usb-%s", gohci.slot_name); +		/* Initialization failed disable clocks */ +		CKENA &= ~(CKENA_2_USBHOST |  CKENA_20_UDC); +		return -1; +	} + +	/* FIXME this is a second HC reset; why?? */ +	/* writel(gohci.hc_control = OHCI_USB_RESET, &gohci.regs->control); +	   wait_ms(10); */ + +	if (hc_start (&gohci) < 0) { +		err ("can't start usb-%s", gohci.slot_name); +		hc_release_ohci (&gohci); +		/* Initialization failed */ +		CKENA &= ~(CKENA_2_USBHOST |  CKENA_20_UDC); +		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); + +	/* board dependant cleanup */ +	if(usb_board_stop()) +		return -1; + +	return 0; +} + +#endif /* CONFIG_USB_OHCI */ |