diff options
author | 2023-10-10 14:33:42 +0000 | |
---|---|---|
committer | 2023-10-10 14:33:42 +0000 | |
commit | af1a266670d040d2f4083ff309d732d648afba2a (patch) | |
tree | 2fc46203448ddcc6f81546d379abfaeb323575e9 /roms/u-boot-sam460ex/drivers/usb | |
parent | e02cda008591317b1625707ff8e115a4841aa889 (diff) |
Change-Id: Iaf8d18082d3991dec7c0ebbea540f092188eb4ec
Diffstat (limited to 'roms/u-boot-sam460ex/drivers/usb')
46 files changed, 20851 insertions, 0 deletions
diff --git a/roms/u-boot-sam460ex/drivers/usb/gadget/Makefile b/roms/u-boot-sam460ex/drivers/usb/gadget/Makefile new file mode 100644 index 000000000..1d7362dc9 --- /dev/null +++ b/roms/u-boot-sam460ex/drivers/usb/gadget/Makefile @@ -0,0 +1,54 @@ +# +# (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_gadget.a + +ifdef CONFIG_USB_DEVICE +COBJS-y += core.o +COBJS-y += ep0.o +COBJS-$(CONFIG_OMAP1510) += omap1510_udc.o +COBJS-$(CONFIG_OMAP1610) += omap1510_udc.o +COBJS-$(CONFIG_MPC885_FAMILY) += mpc8xx_udc.o +COBJS-$(CONFIG_PXA27X) += pxa27x_udc.o +COBJS-$(CONFIG_SPEARUDC) += spr_udc.o +endif + +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/roms/u-boot-sam460ex/drivers/usb/gadget/core.c b/roms/u-boot-sam460ex/drivers/usb/gadget/core.c new file mode 100644 index 000000000..67b6681a6 --- /dev/null +++ b/roms/u-boot-sam460ex/drivers/usb/gadget/core.c @@ -0,0 +1,683 @@ +/* + * (C) Copyright 2003 + * Gerry Hamel, geh@ti.com, Texas Instruments + * + * Based on + * linux/drivers/usbd/usbd.c.c - USB Device Core Layer + * + * Copyright (c) 2000, 2001, 2002 Lineo + * Copyright (c) 2001 Hewlett Packard + * + * By: + * Stuart Lynne <sl@lineo.com>, + * Tom Rushworth <tbr@lineo.com>, + * Bruce Balden <balden@lineo.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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <malloc.h> +#include <usbdevice.h> + +#define MAX_INTERFACES 2 + + +int maxstrings = 20; + +/* Global variables ************************************************************************** */ + +struct usb_string_descriptor **usb_strings; + +int usb_devices; + +extern struct usb_function_driver ep0_driver; + +int registered_functions; +int registered_devices; + +char *usbd_device_events[] = { + "DEVICE_UNKNOWN", + "DEVICE_INIT", + "DEVICE_CREATE", + "DEVICE_HUB_CONFIGURED", + "DEVICE_RESET", + "DEVICE_ADDRESS_ASSIGNED", + "DEVICE_CONFIGURED", + "DEVICE_SET_INTERFACE", + "DEVICE_SET_FEATURE", + "DEVICE_CLEAR_FEATURE", + "DEVICE_DE_CONFIGURED", + "DEVICE_BUS_INACTIVE", + "DEVICE_BUS_ACTIVITY", + "DEVICE_POWER_INTERRUPTION", + "DEVICE_HUB_RESET", + "DEVICE_DESTROY", + "DEVICE_FUNCTION_PRIVATE", +}; + +char *usbd_device_states[] = { + "STATE_INIT", + "STATE_CREATED", + "STATE_ATTACHED", + "STATE_POWERED", + "STATE_DEFAULT", + "STATE_ADDRESSED", + "STATE_CONFIGURED", + "STATE_UNKNOWN", +}; + +char *usbd_device_requests[] = { + "GET STATUS", /* 0 */ + "CLEAR FEATURE", /* 1 */ + "RESERVED", /* 2 */ + "SET FEATURE", /* 3 */ + "RESERVED", /* 4 */ + "SET ADDRESS", /* 5 */ + "GET DESCRIPTOR", /* 6 */ + "SET DESCRIPTOR", /* 7 */ + "GET CONFIGURATION", /* 8 */ + "SET CONFIGURATION", /* 9 */ + "GET INTERFACE", /* 10 */ + "SET INTERFACE", /* 11 */ + "SYNC FRAME", /* 12 */ +}; + +char *usbd_device_descriptors[] = { + "UNKNOWN", /* 0 */ + "DEVICE", /* 1 */ + "CONFIG", /* 2 */ + "STRING", /* 3 */ + "INTERFACE", /* 4 */ + "ENDPOINT", /* 5 */ + "DEVICE QUALIFIER", /* 6 */ + "OTHER SPEED", /* 7 */ + "INTERFACE POWER", /* 8 */ +}; + +char *usbd_device_status[] = { + "USBD_OPENING", + "USBD_OK", + "USBD_SUSPENDED", + "USBD_CLOSING", +}; + + +/* Descriptor support functions ************************************************************** */ + + +/** + * usbd_get_string - find and return a string descriptor + * @index: string index to return + * + * Find an indexed string and return a pointer to a it. + */ +struct usb_string_descriptor *usbd_get_string (__u8 index) +{ + if (index >= maxstrings) { + return NULL; + } + return usb_strings[index]; +} + + +/* Access to device descriptor functions ***************************************************** */ + + +/* * + * usbd_device_configuration_instance - find a configuration instance for this device + * @device: + * @configuration: index to configuration, 0 - N-1 + * + * Get specifed device configuration. Index should be bConfigurationValue-1. + */ +static struct usb_configuration_instance *usbd_device_configuration_instance (struct usb_device_instance *device, + unsigned int port, unsigned int configuration) +{ + if (configuration >= device->configurations) + return NULL; + + return device->configuration_instance_array + configuration; +} + + +/* * + * usbd_device_interface_instance + * @device: + * @configuration: index to configuration, 0 - N-1 + * @interface: index to interface + * + * Return the specified interface descriptor for the specified device. + */ +struct usb_interface_instance *usbd_device_interface_instance (struct usb_device_instance *device, int port, int configuration, int interface) +{ + struct usb_configuration_instance *configuration_instance; + + if ((configuration_instance = usbd_device_configuration_instance (device, port, configuration)) == NULL) { + return NULL; + } + if (interface >= configuration_instance->interfaces) { + return NULL; + } + return configuration_instance->interface_instance_array + interface; +} + +/* * + * usbd_device_alternate_descriptor_list + * @device: + * @configuration: index to configuration, 0 - N-1 + * @interface: index to interface + * @alternate: alternate setting + * + * Return the specified alternate descriptor for the specified device. + */ +struct usb_alternate_instance *usbd_device_alternate_instance (struct usb_device_instance *device, int port, int configuration, int interface, int alternate) +{ + struct usb_interface_instance *interface_instance; + + if ((interface_instance = usbd_device_interface_instance (device, port, configuration, interface)) == NULL) { + return NULL; + } + + if (alternate >= interface_instance->alternates) { + return NULL; + } + + return interface_instance->alternates_instance_array + alternate; +} + + +/* * + * usbd_device_device_descriptor + * @device: which device + * @configuration: index to configuration, 0 - N-1 + * @port: which port + * + * Return the specified configuration descriptor for the specified device. + */ +struct usb_device_descriptor *usbd_device_device_descriptor (struct usb_device_instance *device, int port) +{ + return (device->device_descriptor); +} + + +/** + * usbd_device_configuration_descriptor + * @device: which device + * @port: which port + * @configuration: index to configuration, 0 - N-1 + * + * Return the specified configuration descriptor for the specified device. + */ +struct usb_configuration_descriptor *usbd_device_configuration_descriptor (struct + usb_device_instance + *device, int port, int configuration) +{ + struct usb_configuration_instance *configuration_instance; + if (!(configuration_instance = usbd_device_configuration_instance (device, port, configuration))) { + return NULL; + } + return (configuration_instance->configuration_descriptor); +} + + +/** + * usbd_device_interface_descriptor + * @device: which device + * @port: which port + * @configuration: index to configuration, 0 - N-1 + * @interface: index to interface + * @alternate: alternate setting + * + * Return the specified interface descriptor for the specified device. + */ +struct usb_interface_descriptor *usbd_device_interface_descriptor (struct usb_device_instance + *device, int port, int configuration, int interface, int alternate) +{ + struct usb_interface_instance *interface_instance; + if (!(interface_instance = usbd_device_interface_instance (device, port, configuration, interface))) { + return NULL; + } + if ((alternate < 0) || (alternate >= interface_instance->alternates)) { + return NULL; + } + return (interface_instance->alternates_instance_array[alternate].interface_descriptor); +} + +/** + * usbd_device_endpoint_descriptor_index + * @device: which device + * @port: which port + * @configuration: index to configuration, 0 - N-1 + * @interface: index to interface + * @alternate: index setting + * @index: which index + * + * Return the specified endpoint descriptor for the specified device. + */ +struct usb_endpoint_descriptor *usbd_device_endpoint_descriptor_index (struct usb_device_instance + *device, int port, int configuration, int interface, int alternate, int index) +{ + struct usb_alternate_instance *alternate_instance; + + if (!(alternate_instance = usbd_device_alternate_instance (device, port, configuration, interface, alternate))) { + return NULL; + } + if (index >= alternate_instance->endpoints) { + return NULL; + } + return *(alternate_instance->endpoints_descriptor_array + index); +} + + +/** + * usbd_device_endpoint_transfersize + * @device: which device + * @port: which port + * @configuration: index to configuration, 0 - N-1 + * @interface: index to interface + * @index: which index + * + * Return the specified endpoint transfer size; + */ +int usbd_device_endpoint_transfersize (struct usb_device_instance *device, int port, int configuration, int interface, int alternate, int index) +{ + struct usb_alternate_instance *alternate_instance; + + if (!(alternate_instance = usbd_device_alternate_instance (device, port, configuration, interface, alternate))) { + return 0; + } + if (index >= alternate_instance->endpoints) { + return 0; + } + return *(alternate_instance->endpoint_transfersize_array + index); +} + + +/** + * usbd_device_endpoint_descriptor + * @device: which device + * @port: which port + * @configuration: index to configuration, 0 - N-1 + * @interface: index to interface + * @alternate: alternate setting + * @endpoint: which endpoint + * + * Return the specified endpoint descriptor for the specified device. + */ +struct usb_endpoint_descriptor *usbd_device_endpoint_descriptor (struct usb_device_instance *device, int port, int configuration, int interface, int alternate, int endpoint) +{ + struct usb_endpoint_descriptor *endpoint_descriptor; + int i; + + for (i = 0; !(endpoint_descriptor = usbd_device_endpoint_descriptor_index (device, port, configuration, interface, alternate, i)); i++) { + if (endpoint_descriptor->bEndpointAddress == endpoint) { + return endpoint_descriptor; + } + } + return NULL; +} + +/** + * usbd_endpoint_halted + * @device: point to struct usb_device_instance + * @endpoint: endpoint to check + * + * Return non-zero if endpoint is halted. + */ +int usbd_endpoint_halted (struct usb_device_instance *device, int endpoint) +{ + return (device->status == USB_STATUS_HALT); +} + + +/** + * usbd_rcv_complete - complete a receive + * @endpoint: + * @len: + * @urb_bad: + * + * Called from rcv interrupt to complete. + */ +void usbd_rcv_complete(struct usb_endpoint_instance *endpoint, int len, int urb_bad) +{ + if (endpoint) { + struct urb *rcv_urb; + + /*usbdbg("len: %d urb: %p\n", len, endpoint->rcv_urb); */ + + /* if we had an urb then update actual_length, dispatch if neccessary */ + if ((rcv_urb = endpoint->rcv_urb)) { + + /*usbdbg("actual: %d buffer: %d\n", */ + /*rcv_urb->actual_length, rcv_urb->buffer_length); */ + + /* check the urb is ok, are we adding data less than the packetsize */ + if (!urb_bad && (len <= endpoint->rcv_packetSize)) { + /*usbdbg("updating actual_length by %d\n",len); */ + + /* increment the received data size */ + rcv_urb->actual_length += len; + + } else { + usberr(" RECV_ERROR actual: %d buffer: %d urb_bad: %d\n", + rcv_urb->actual_length, rcv_urb->buffer_length, urb_bad); + + rcv_urb->actual_length = 0; + rcv_urb->status = RECV_ERROR; + } + } else { + usberr("no rcv_urb!"); + } + } else { + usberr("no endpoint!"); + } + +} + +/** + * usbd_tx_complete - complete a transmit + * @endpoint: + * @resetart: + * + * Called from tx interrupt to complete. + */ +void usbd_tx_complete (struct usb_endpoint_instance *endpoint) +{ + if (endpoint) { + struct urb *tx_urb; + + /* if we have a tx_urb advance or reset, finish if complete */ + if ((tx_urb = endpoint->tx_urb)) { + int sent = endpoint->last; + endpoint->sent += sent; + endpoint->last -= sent; + + if( (endpoint->tx_urb->actual_length - endpoint->sent) <= 0 ) { + tx_urb->actual_length = 0; + endpoint->sent = 0; + endpoint->last = 0; + + /* Remove from active, save for re-use */ + urb_detach(tx_urb); + urb_append(&endpoint->done, tx_urb); + /*usbdbg("done->next %p, tx_urb %p, done %p", */ + /* endpoint->done.next, tx_urb, &endpoint->done); */ + + endpoint->tx_urb = first_urb_detached(&endpoint->tx); + if( endpoint->tx_urb ) { + endpoint->tx_queue--; + usbdbg("got urb from tx list"); + } + if( !endpoint->tx_urb ) { + /*usbdbg("taking urb from done list"); */ + endpoint->tx_urb = first_urb_detached(&endpoint->done); + } + if( !endpoint->tx_urb ) { + usbdbg("allocating new urb for tx_urb"); + endpoint->tx_urb = usbd_alloc_urb(tx_urb->device, endpoint); + } + } + } + } +} + +/* URB linked list functions ***************************************************** */ + +/* + * Initialize an urb_link to be a single element list. + * If the urb_link is being used as a distinguished list head + * the list is empty when the head is the only link in the list. + */ +void urb_link_init (urb_link * ul) +{ + if (ul) { + ul->prev = ul->next = ul; + } +} + +/* + * Detach an urb_link from a list, and set it + * up as a single element list, so no dangling + * pointers can be followed, and so it can be + * joined to another list if so desired. + */ +void urb_detach (struct urb *urb) +{ + if (urb) { + urb_link *ul = &urb->link; + ul->next->prev = ul->prev; + ul->prev->next = ul->next; + urb_link_init (ul); + } +} + +/* + * Return the first urb_link in a list with a distinguished + * head "hd", or NULL if the list is empty. This will also + * work as a predicate, returning NULL if empty, and non-NULL + * otherwise. + */ +urb_link *first_urb_link (urb_link * hd) +{ + urb_link *nx; + if (NULL != hd && NULL != (nx = hd->next) && nx != hd) { + /* There is at least one element in the list */ + /* (besides the distinguished head). */ + return (nx); + } + /* The list is empty */ + return (NULL); +} + +/* + * Return the first urb in a list with a distinguished + * head "hd", or NULL if the list is empty. + */ +struct urb *first_urb (urb_link * hd) +{ + urb_link *nx; + if (NULL == (nx = first_urb_link (hd))) { + /* The list is empty */ + return (NULL); + } + return (p2surround (struct urb, link, nx)); +} + +/* + * Detach and return the first urb in a list with a distinguished + * head "hd", or NULL if the list is empty. + * + */ +struct urb *first_urb_detached (urb_link * hd) +{ + struct urb *urb; + if ((urb = first_urb (hd))) { + urb_detach (urb); + } + return urb; +} + + +/* + * Append an urb_link (or a whole list of + * urb_links) to the tail of another list + * of urb_links. + */ +void urb_append (urb_link * hd, struct urb *urb) +{ + if (hd && urb) { + urb_link *new = &urb->link; + + /* This allows the new urb to be a list of urbs, */ + /* with new pointing at the first, but the link */ + /* must be initialized. */ + /* Order is important here... */ + urb_link *pul = hd->prev; + new->prev->next = hd; + hd->prev = new->prev; + new->prev = pul; + pul->next = new; + } +} + +/* URB create/destroy functions ***************************************************** */ + +/** + * usbd_alloc_urb - allocate an URB appropriate for specified endpoint + * @device: device instance + * @endpoint: endpoint + * + * Allocate an urb structure. The usb device urb structure is used to + * contain all data associated with a transfer, including a setup packet for + * control transfers. + * + * NOTE: endpoint_address MUST contain a direction flag. + */ +struct urb *usbd_alloc_urb (struct usb_device_instance *device, + struct usb_endpoint_instance *endpoint) +{ + struct urb *urb; + + if (!(urb = (struct urb *) malloc (sizeof (struct urb)))) { + usberr (" F A T A L: malloc(%zu) FAILED!!!!", + sizeof (struct urb)); + return NULL; + } + + /* Fill in known fields */ + memset (urb, 0, sizeof (struct urb)); + urb->endpoint = endpoint; + urb->device = device; + urb->buffer = (u8 *) urb->buffer_data; + urb->buffer_length = sizeof (urb->buffer_data); + + urb_link_init (&urb->link); + + return urb; +} + +/** + * usbd_dealloc_urb - deallocate an URB and associated buffer + * @urb: pointer to an urb structure + * + * Deallocate an urb structure and associated data. + */ +void usbd_dealloc_urb (struct urb *urb) +{ + if (urb) { + free (urb); + } +} + +/* Event signaling functions ***************************************************** */ + +/** + * usbd_device_event - called to respond to various usb events + * @device: pointer to struct device + * @event: event to respond to + * + * Used by a Bus driver to indicate an event. + */ +void usbd_device_event_irq (struct usb_device_instance *device, usb_device_event_t event, int data) +{ + usb_device_state_t state; + + if (!device || !device->bus) { + usberr("(%p,%d) NULL device or device->bus", device, event); + return; + } + + state = device->device_state; + + usbinfo("%s", usbd_device_events[event]); + + switch (event) { + case DEVICE_UNKNOWN: + break; + case DEVICE_INIT: + device->device_state = STATE_INIT; + break; + + case DEVICE_CREATE: + device->device_state = STATE_ATTACHED; + break; + + case DEVICE_HUB_CONFIGURED: + device->device_state = STATE_POWERED; + break; + + case DEVICE_RESET: + device->device_state = STATE_DEFAULT; + device->address = 0; + break; + + case DEVICE_ADDRESS_ASSIGNED: + device->device_state = STATE_ADDRESSED; + break; + + case DEVICE_CONFIGURED: + device->device_state = STATE_CONFIGURED; + break; + + case DEVICE_DE_CONFIGURED: + device->device_state = STATE_ADDRESSED; + break; + + case DEVICE_BUS_INACTIVE: + if (device->status != USBD_CLOSING) { + device->status = USBD_SUSPENDED; + } + break; + case DEVICE_BUS_ACTIVITY: + if (device->status != USBD_CLOSING) { + device->status = USBD_OK; + } + break; + + case DEVICE_SET_INTERFACE: + break; + case DEVICE_SET_FEATURE: + break; + case DEVICE_CLEAR_FEATURE: + break; + + case DEVICE_POWER_INTERRUPTION: + device->device_state = STATE_POWERED; + break; + case DEVICE_HUB_RESET: + device->device_state = STATE_ATTACHED; + break; + case DEVICE_DESTROY: + device->device_state = STATE_UNKNOWN; + break; + + case DEVICE_FUNCTION_PRIVATE: + break; + + default: + usbdbg("event %d - not handled",event); + break; + } + /*usbdbg("%s event: %d oldstate: %d newstate: %d status: %d address: %d", + device->name, event, state, + device->device_state, device->status, device->address); */ + + /* tell the bus interface driver */ + if( device->event ) { + /* usbdbg("calling device->event"); */ + device->event(device, event, data); + } +} diff --git a/roms/u-boot-sam460ex/drivers/usb/gadget/ep0.c b/roms/u-boot-sam460ex/drivers/usb/gadget/ep0.c new file mode 100644 index 000000000..2b4ec44e1 --- /dev/null +++ b/roms/u-boot-sam460ex/drivers/usb/gadget/ep0.c @@ -0,0 +1,597 @@ +/* + * (C) Copyright 2003 + * Gerry Hamel, geh@ti.com, Texas Instruments + * + * (C) Copyright 2006 + * Bryan O'Donoghue, deckard@CodeHermit.ie + * + * Based on + * linux/drivers/usbd/ep0.c + * + * Copyright (c) 2000, 2001, 2002 Lineo + * Copyright (c) 2001 Hewlett Packard + * + * By: + * Stuart Lynne <sl@lineo.com>, + * Tom Rushworth <tbr@lineo.com>, + * Bruce Balden <balden@lineo.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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* + * This is the builtin ep0 control function. It implements all required functionality + * for responding to control requests (SETUP packets). + * + * XXX + * + * Currently we do not pass any SETUP packets (or other) to the configured + * function driver. This may need to change. + * + * XXX + * + * As alluded to above, a simple callback cdc_recv_setup has been implemented + * in the usb_device data structure to facilicate passing + * Common Device Class packets to a function driver. + * + * XXX + */ + +#include <common.h> +#include <usbdevice.h> + +#if 0 +#define dbg_ep0(lvl,fmt,args...) serial_printf("[%s] %s:%d: "fmt"\n",__FILE__,__FUNCTION__,__LINE__,##args) +#else +#define dbg_ep0(lvl,fmt,args...) +#endif + +/* EP0 Configuration Set ********************************************************************* */ + + +/** + * ep0_get_status - fill in URB data with appropriate status + * @device: + * @urb: + * @index: + * @requesttype: + * + */ +static int ep0_get_status (struct usb_device_instance *device, + struct urb *urb, int index, int requesttype) +{ + char *cp; + + urb->actual_length = 2; + cp = (char*)urb->buffer; + cp[0] = cp[1] = 0; + + switch (requesttype) { + case USB_REQ_RECIPIENT_DEVICE: + cp[0] = USB_STATUS_SELFPOWERED; + break; + case USB_REQ_RECIPIENT_INTERFACE: + break; + case USB_REQ_RECIPIENT_ENDPOINT: + cp[0] = usbd_endpoint_halted (device, index); + break; + case USB_REQ_RECIPIENT_OTHER: + urb->actual_length = 0; + default: + break; + } + dbg_ep0 (2, "%02x %02x", cp[0], cp[1]); + return 0; +} + +/** + * ep0_get_one + * @device: + * @urb: + * @result: + * + * Set a single byte value in the urb send buffer. Return non-zero to signal + * a request error. + */ +static int ep0_get_one (struct usb_device_instance *device, struct urb *urb, + __u8 result) +{ + urb->actual_length = 1; /* XXX 2? */ + ((char *) urb->buffer)[0] = result; + return 0; +} + +/** + * copy_config + * @urb: pointer to urb + * @data: pointer to configuration data + * @length: length of data + * + * Copy configuration data to urb transfer buffer if there is room for it. + */ +void copy_config (struct urb *urb, void *data, int max_length, + int max_buf) +{ + int available; + int length; + + /*dbg_ep0(3, "-> actual: %d buf: %d max_buf: %d max_length: %d data: %p", */ + /* urb->actual_length, urb->buffer_length, max_buf, max_length, data); */ + + if (!data) { + dbg_ep0 (1, "data is NULL"); + return; + } + length = max_length; + + if (length > max_length) { + dbg_ep0 (1, "length: %d >= max_length: %d", length, + max_length); + return; + } + /*dbg_ep0(1, " actual: %d buf: %d max_buf: %d max_length: %d length: %d", */ + /* urb->actual_length, urb->buffer_length, max_buf, max_length, length); */ + + if ((available = + /*urb->buffer_length */ max_buf - urb->actual_length) <= 0) { + return; + } + /*dbg_ep0(1, "actual: %d buf: %d max_buf: %d length: %d available: %d", */ + /* urb->actual_length, urb->buffer_length, max_buf, length, available); */ + + if (length > available) { + length = available; + } + /*dbg_ep0(1, "actual: %d buf: %d max_buf: %d length: %d available: %d", */ + /* urb->actual_length, urb->buffer_length, max_buf, length, available); */ + + memcpy (urb->buffer + urb->actual_length, data, length); + urb->actual_length += length; + + dbg_ep0 (3, + "copy_config: <- actual: %d buf: %d max_buf: %d max_length: %d available: %d", + urb->actual_length, urb->buffer_length, max_buf, max_length, + available); +} + +/** + * ep0_get_descriptor + * @device: + * @urb: + * @max: + * @descriptor_type: + * @index: + * + * Called by ep0_rx_process for a get descriptor device command. Determine what + * descriptor is being requested, copy to send buffer. Return zero if ok to send, + * return non-zero to signal a request error. + */ +static int ep0_get_descriptor (struct usb_device_instance *device, + struct urb *urb, int max, int descriptor_type, + int index) +{ + int port = 0; /* XXX compound device */ + char *cp; + + /*dbg_ep0(3, "max: %x type: %x index: %x", max, descriptor_type, index); */ + + if (!urb || !urb->buffer || !urb->buffer_length + || (urb->buffer_length < 255)) { + dbg_ep0 (2, "invalid urb %p", urb); + return -1L; + } + + /* setup tx urb */ + urb->actual_length = 0; + cp = (char*)urb->buffer; + + dbg_ep0 (2, "%s", USBD_DEVICE_DESCRIPTORS (descriptor_type)); + + switch (descriptor_type) { + case USB_DESCRIPTOR_TYPE_DEVICE: + { + struct usb_device_descriptor *device_descriptor; + if (! + (device_descriptor = + usbd_device_device_descriptor (device, port))) { + return -1; + } + /* copy descriptor for this device */ + copy_config (urb, device_descriptor, + sizeof (struct usb_device_descriptor), + max); + + /* correct the correct control endpoint 0 max packet size into the descriptor */ + device_descriptor = + (struct usb_device_descriptor *) urb->buffer; + + } + dbg_ep0(3, "copied device configuration, actual_length: 0x%x", urb->actual_length); + break; + + case USB_DESCRIPTOR_TYPE_CONFIGURATION: + { + struct usb_configuration_descriptor + *configuration_descriptor; + struct usb_device_descriptor *device_descriptor; + if (! + (device_descriptor = + usbd_device_device_descriptor (device, port))) { + return -1; + } + /*dbg_ep0(2, "%d %d", index, device_descriptor->bNumConfigurations); */ + if (index >= device_descriptor->bNumConfigurations) { + dbg_ep0 (0, "index too large: %d >= %d", index, + device_descriptor-> + bNumConfigurations); + return -1; + } + + if (! + (configuration_descriptor = + usbd_device_configuration_descriptor (device, + port, + index))) { + dbg_ep0 (0, + "usbd_device_configuration_descriptor failed: %d", + index); + return -1; + } + dbg_ep0(0, "attempt to copy %d bytes to urb\n",cpu_to_le16(configuration_descriptor->wTotalLength)); + copy_config (urb, configuration_descriptor, + + cpu_to_le16(configuration_descriptor->wTotalLength), + max); + } + + break; + + case USB_DESCRIPTOR_TYPE_STRING: + { + struct usb_string_descriptor *string_descriptor; + if (!(string_descriptor = usbd_get_string (index))) { + serial_printf("Invalid string index %d\n", index); + return -1; + } + dbg_ep0(3, "string_descriptor: %p length %d", string_descriptor, string_descriptor->bLength); + copy_config (urb, string_descriptor, string_descriptor->bLength, max); + } + break; + case USB_DESCRIPTOR_TYPE_INTERFACE: + serial_printf("USB_DESCRIPTOR_TYPE_INTERFACE - error not implemented\n"); + return -1; + case USB_DESCRIPTOR_TYPE_ENDPOINT: + serial_printf("USB_DESCRIPTOR_TYPE_ENDPOINT - error not implemented\n"); + return -1; + case USB_DESCRIPTOR_TYPE_HID: + { + serial_printf("USB_DESCRIPTOR_TYPE_HID - error not implemented\n"); + return -1; /* unsupported at this time */ +#if 0 + int bNumInterface = + le16_to_cpu (urb->device_request.wIndex); + int bAlternateSetting = 0; + int class = 0; + struct usb_class_descriptor *class_descriptor; + + if (!(class_descriptor = + usbd_device_class_descriptor_index (device, + port, 0, + bNumInterface, + bAlternateSetting, + class)) + || class_descriptor->descriptor.hid.bDescriptorType != USB_DT_HID) { + dbg_ep0 (3, "[%d] interface is not HID", + bNumInterface); + return -1; + } + /* copy descriptor for this class */ + copy_config (urb, class_descriptor, + class_descriptor->descriptor.hid.bLength, + max); +#endif + } + break; + case USB_DESCRIPTOR_TYPE_REPORT: + { + serial_printf("USB_DESCRIPTOR_TYPE_REPORT - error not implemented\n"); + return -1; /* unsupported at this time */ +#if 0 + int bNumInterface = + le16_to_cpu (urb->device_request.wIndex); + int bAlternateSetting = 0; + int class = 0; + struct usb_class_report_descriptor *report_descriptor; + + if (!(report_descriptor = + usbd_device_class_report_descriptor_index + (device, port, 0, bNumInterface, + bAlternateSetting, class)) + || report_descriptor->bDescriptorType != + USB_DT_REPORT) { + dbg_ep0 (3, "[%d] descriptor is not REPORT", + bNumInterface); + return -1; + } + /* copy report descriptor for this class */ + /*copy_config(urb, &report_descriptor->bData[0], report_descriptor->wLength, max); */ + if (max - urb->actual_length > 0) { + int length = + MIN (report_descriptor->wLength, + max - urb->actual_length); + memcpy (urb->buffer + urb->actual_length, + &report_descriptor->bData[0], length); + urb->actual_length += length; + } +#endif + } + break; + case USB_DESCRIPTOR_TYPE_DEVICE_QUALIFIER: + { + /* If a USB device supports both a full speed and low speed operation + * we must send a Device_Qualifier descriptor here + */ + return -1; + } + default: + return -1; + } + + + dbg_ep0 (1, "urb: buffer: %p buffer_length: %2d actual_length: %2d tx_packetSize: %2d", + urb->buffer, urb->buffer_length, urb->actual_length, + device->bus->endpoint_array[0].tx_packetSize); +/* + if ((urb->actual_length < max) && !(urb->actual_length % device->bus->endpoint_array[0].tx_packetSize)) { + dbg_ep0(0, "adding null byte"); + urb->buffer[urb->actual_length++] = 0; + dbg_ep0(0, "urb: buffer_length: %2d actual_length: %2d packet size: %2d", + urb->buffer_length, urb->actual_length device->bus->endpoint_array[0].tx_packetSize); + } +*/ + return 0; + +} + +/** + * ep0_recv_setup - called to indicate URB has been received + * @urb: pointer to struct urb + * + * Check if this is a setup packet, process the device request, put results + * back into the urb and return zero or non-zero to indicate success (DATA) + * or failure (STALL). + * + */ +int ep0_recv_setup (struct urb *urb) +{ + /*struct usb_device_request *request = urb->buffer; */ + /*struct usb_device_instance *device = urb->device; */ + + struct usb_device_request *request; + struct usb_device_instance *device; + int address; + + dbg_ep0 (0, "entering ep0_recv_setup()"); + if (!urb || !urb->device) { + dbg_ep0 (3, "invalid URB %p", urb); + return -1; + } + + request = &urb->device_request; + device = urb->device; + + dbg_ep0 (3, "urb: %p device: %p", urb, urb->device); + + + /*dbg_ep0(2, "- - - - - - - - - -"); */ + + dbg_ep0 (2, + "bmRequestType:%02x bRequest:%02x wValue:%04x wIndex:%04x wLength:%04x %s", + request->bmRequestType, request->bRequest, + le16_to_cpu (request->wValue), le16_to_cpu (request->wIndex), + le16_to_cpu (request->wLength), + USBD_DEVICE_REQUESTS (request->bRequest)); + + /* handle USB Standard Request (c.f. USB Spec table 9-2) */ + if ((request->bmRequestType & USB_REQ_TYPE_MASK) != 0) { + if(device->device_state <= STATE_CONFIGURED){ + /* Attempt to handle a CDC specific request if we are + * in the configured state. + */ + return device->cdc_recv_setup(request,urb); + } + dbg_ep0 (1, "non standard request: %x", + request->bmRequestType & USB_REQ_TYPE_MASK); + return -1; /* Stall here */ + } + + switch (device->device_state) { + case STATE_CREATED: + case STATE_ATTACHED: + case STATE_POWERED: + /* It actually is important to allow requests in these states, + * Windows will request descriptors before assigning an + * address to the client. + */ + + /*dbg_ep0 (1, "request %s not allowed in this state: %s", */ + /* USBD_DEVICE_REQUESTS(request->bRequest), */ + /* usbd_device_states[device->device_state]); */ + /*return -1; */ + break; + + case STATE_INIT: + case STATE_DEFAULT: + switch (request->bRequest) { + case USB_REQ_GET_STATUS: + case USB_REQ_GET_INTERFACE: + case USB_REQ_SYNCH_FRAME: /* XXX should never see this (?) */ + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_FEATURE: + case USB_REQ_SET_DESCRIPTOR: + /* case USB_REQ_SET_CONFIGURATION: */ + case USB_REQ_SET_INTERFACE: + dbg_ep0 (1, + "request %s not allowed in DEFAULT state: %s", + USBD_DEVICE_REQUESTS (request->bRequest), + usbd_device_states[device->device_state]); + return -1; + + case USB_REQ_SET_CONFIGURATION: + case USB_REQ_SET_ADDRESS: + case USB_REQ_GET_DESCRIPTOR: + case USB_REQ_GET_CONFIGURATION: + break; + } + case STATE_ADDRESSED: + case STATE_CONFIGURED: + break; + case STATE_UNKNOWN: + dbg_ep0 (1, "request %s not allowed in UNKNOWN state: %s", + USBD_DEVICE_REQUESTS (request->bRequest), + usbd_device_states[device->device_state]); + return -1; + } + + /* handle all requests that return data (direction bit set on bm RequestType) */ + if ((request->bmRequestType & USB_REQ_DIRECTION_MASK)) { + + dbg_ep0 (3, "Device-to-Host"); + + switch (request->bRequest) { + + case USB_REQ_GET_STATUS: + return ep0_get_status (device, urb, request->wIndex, + request->bmRequestType & + USB_REQ_RECIPIENT_MASK); + + case USB_REQ_GET_DESCRIPTOR: + return ep0_get_descriptor (device, urb, + le16_to_cpu (request->wLength), + le16_to_cpu (request->wValue) >> 8, + le16_to_cpu (request->wValue) & 0xff); + + case USB_REQ_GET_CONFIGURATION: + serial_printf("get config %d\n", device->configuration); + return ep0_get_one (device, urb, + device->configuration); + + case USB_REQ_GET_INTERFACE: + return ep0_get_one (device, urb, device->alternate); + + case USB_REQ_SYNCH_FRAME: /* XXX should never see this (?) */ + return -1; + + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_FEATURE: + case USB_REQ_SET_ADDRESS: + case USB_REQ_SET_DESCRIPTOR: + case USB_REQ_SET_CONFIGURATION: + case USB_REQ_SET_INTERFACE: + return -1; + } + } + /* handle the requests that do not return data */ + else { + + + /*dbg_ep0(3, "Host-to-Device"); */ + switch (request->bRequest) { + + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_FEATURE: + dbg_ep0 (0, "Host-to-Device"); + switch (request-> + bmRequestType & USB_REQ_RECIPIENT_MASK) { + case USB_REQ_RECIPIENT_DEVICE: + /* XXX DEVICE_REMOTE_WAKEUP or TEST_MODE would be added here */ + /* XXX fall through for now as we do not support either */ + case USB_REQ_RECIPIENT_INTERFACE: + case USB_REQ_RECIPIENT_OTHER: + dbg_ep0 (0, "request %s not", + USBD_DEVICE_REQUESTS (request->bRequest)); + default: + return -1; + + case USB_REQ_RECIPIENT_ENDPOINT: + dbg_ep0 (0, "ENDPOINT: %x", le16_to_cpu (request->wValue)); + if (le16_to_cpu (request->wValue) == USB_ENDPOINT_HALT) { + /*return usbd_device_feature (device, le16_to_cpu (request->wIndex), */ + /* request->bRequest == USB_REQ_SET_FEATURE); */ + /* NEED TO IMPLEMENT THIS!!! */ + return -1; + } else { + dbg_ep0 (1, "request %s bad wValue: %04x", + USBD_DEVICE_REQUESTS + (request->bRequest), + le16_to_cpu (request->wValue)); + return -1; + } + } + + case USB_REQ_SET_ADDRESS: + /* check if this is a re-address, reset first if it is (this shouldn't be possible) */ + if (device->device_state != STATE_DEFAULT) { + dbg_ep0 (1, "set_address: %02x state: %s", + le16_to_cpu (request->wValue), + usbd_device_states[device->device_state]); + return -1; + } + address = le16_to_cpu (request->wValue); + if ((address & 0x7f) != address) { + dbg_ep0 (1, "invalid address %04x %04x", + address, address & 0x7f); + return -1; + } + device->address = address; + + /*dbg_ep0(2, "address: %d %d %d", */ + /* request->wValue, le16_to_cpu(request->wValue), device->address); */ + + return 0; + + case USB_REQ_SET_DESCRIPTOR: /* XXX should we support this? */ + dbg_ep0 (0, "set descriptor: NOT SUPPORTED"); + return -1; + + case USB_REQ_SET_CONFIGURATION: + /* c.f. 9.4.7 - the top half of wValue is reserved */ + device->configuration = le16_to_cpu(request->wValue) & 0xff; + + /* reset interface and alternate settings */ + device->interface = device->alternate = 0; + + /*dbg_ep0(2, "set configuration: %d", device->configuration); */ + /*serial_printf("DEVICE_CONFIGURED.. event?\n"); */ + return 0; + + case USB_REQ_SET_INTERFACE: + device->interface = le16_to_cpu (request->wIndex); + device->alternate = le16_to_cpu (request->wValue); + /*dbg_ep0(2, "set interface: %d alternate: %d", device->interface, device->alternate); */ + serial_printf ("DEVICE_SET_INTERFACE.. event?\n"); + return 0; + + case USB_REQ_GET_STATUS: + case USB_REQ_GET_DESCRIPTOR: + case USB_REQ_GET_CONFIGURATION: + case USB_REQ_GET_INTERFACE: + case USB_REQ_SYNCH_FRAME: /* XXX should never see this (?) */ + return -1; + } + } + return -1; +} diff --git a/roms/u-boot-sam460ex/drivers/usb/gadget/ep0.h b/roms/u-boot-sam460ex/drivers/usb/gadget/ep0.h new file mode 100644 index 000000000..3bec106a4 --- /dev/null +++ b/roms/u-boot-sam460ex/drivers/usb/gadget/ep0.h @@ -0,0 +1,39 @@ +/* + * (C) Copyright 2003 + * Gerry Hamel, geh@ti.com, Texas Instruments + * + * Based on + * linux/drivers/usbd/ep0.c + * + * Copyright (c) 2000, 2001, 2002 Lineo + * Copyright (c) 2001 Hewlett Packard + * + * By: + * Stuart Lynne <sl@lineo.com>, + * Tom Rushworth <tbr@lineo.com>, + * Bruce Balden <balden@lineo.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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef __USBDCORE_EP0_H__ +#define __USBDCORE_EP0_H__ + + +int ep0_recv_setup (struct urb *urb); + + +#endif diff --git a/roms/u-boot-sam460ex/drivers/usb/gadget/mpc8xx_udc.c b/roms/u-boot-sam460ex/drivers/usb/gadget/mpc8xx_udc.c new file mode 100644 index 000000000..da3fbba72 --- /dev/null +++ b/roms/u-boot-sam460ex/drivers/usb/gadget/mpc8xx_udc.c @@ -0,0 +1,1399 @@ +/* + * Copyright (C) 2006 by Bryan O'Donoghue, CodeHermit + * bodonoghue@CodeHermit.ie + * + * References + * DasUBoot/drivers/usb/gadget/omap1510_udc.c, for design and implementation + * ideas. + * + * 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. + * + */ + +/* + * Notes : + * 1. #define __SIMULATE_ERROR__ to inject a CRC error into every 2nd TX + * packet to force the USB re-transmit protocol. + * + * 2. #define __DEBUG_UDC__ to switch on debug tracing to serial console + * be careful that tracing doesn't create Hiesen-bugs with respect to + * response timeouts to control requests. + * + * 3. This driver should be able to support any higher level driver that + * that wants to do either of the two standard UDC implementations + * Control-Bulk-Interrupt or Bulk-IN/Bulk-Out standards. Hence + * gserial and cdc_acm should work with this code. + * + * 4. NAK events never actually get raised at all, the documentation + * is just wrong ! + * + * 5. For some reason, cbd_datlen is *always* +2 the value it should be. + * this means that having an RX cbd of 16 bytes is not possible, since + * the same size is reported for 14 bytes received as 16 bytes received + * until we can find out why this happens, RX cbds must be limited to 8 + * bytes. TODO: check errata for this behaviour. + * + * 6. Right now this code doesn't support properly powering up with the USB + * cable attached to the USB host my development board the Adder87x doesn't + * have a pull-up fitted to allow this, so it is necessary to power the + * board and *then* attached the USB cable to the host. However somebody + * with a different design in their board may be able to keep the cable + * constantly connected and simply enable/disable a pull-up re + * figure 31.1 in MPC885RM.pdf instead of having to power up the board and + * then attach the cable ! + * + */ +#include <common.h> +#include <config.h> +#include <commproc.h> +#include <usbdevice.h> +#include <usb/mpc8xx_udc.h> + +#include "ep0.h" + +DECLARE_GLOBAL_DATA_PTR; + +#define ERR(fmt, args...)\ + serial_printf("ERROR : [%s] %s:%d: "fmt,\ + __FILE__,__FUNCTION__,__LINE__, ##args) +#ifdef __DEBUG_UDC__ +#define DBG(fmt,args...)\ + serial_printf("[%s] %s:%d: "fmt,\ + __FILE__,__FUNCTION__,__LINE__, ##args) +#else +#define DBG(fmt,args...) +#endif + +/* Static Data */ +#ifdef __SIMULATE_ERROR__ +static char err_poison_test = 0; +#endif +static struct mpc8xx_ep ep_ref[MAX_ENDPOINTS]; +static u32 address_base = STATE_NOT_READY; +static mpc8xx_udc_state_t udc_state = 0; +static struct usb_device_instance *udc_device = 0; +static volatile usb_epb_t *endpoints[MAX_ENDPOINTS]; +static volatile cbd_t *tx_cbd[TX_RING_SIZE]; +static volatile cbd_t *rx_cbd[RX_RING_SIZE]; +static volatile immap_t *immr = 0; +static volatile cpm8xx_t *cp = 0; +static volatile usb_pram_t *usb_paramp = 0; +static volatile usb_t *usbp = 0; +static int rx_ct = 0; +static int tx_ct = 0; + +/* Static Function Declarations */ +static void mpc8xx_udc_state_transition_up (usb_device_state_t initial, + usb_device_state_t final); +static void mpc8xx_udc_state_transition_down (usb_device_state_t initial, + usb_device_state_t final); +static void mpc8xx_udc_stall (unsigned int ep); +static void mpc8xx_udc_flush_tx_fifo (int epid); +static void mpc8xx_udc_flush_rx_fifo (void); +static void mpc8xx_udc_clear_rxbd (volatile cbd_t * rx_cbdp); +static void mpc8xx_udc_init_tx (struct usb_endpoint_instance *epi, + struct urb *tx_urb); +static void mpc8xx_udc_dump_request (struct usb_device_request *request); +static void mpc8xx_udc_clock_init (volatile immap_t * immr, + volatile cpm8xx_t * cp); +static int mpc8xx_udc_ep_tx (struct usb_endpoint_instance *epi); +static int mpc8xx_udc_epn_rx (unsigned int epid, volatile cbd_t * rx_cbdp); +static void mpc8xx_udc_ep0_rx (volatile cbd_t * rx_cbdp); +static void mpc8xx_udc_cbd_init (void); +static void mpc8xx_udc_endpoint_init (void); +static void mpc8xx_udc_cbd_attach (int ep, uchar tx_size, uchar rx_size); +static u32 mpc8xx_udc_alloc (u32 data_size, u32 alignment); +static int mpc8xx_udc_ep0_rx_setup (volatile cbd_t * rx_cbdp); +static void mpc8xx_udc_set_nak (unsigned int ep); +static short mpc8xx_udc_handle_txerr (void); +static void mpc8xx_udc_advance_rx (volatile cbd_t ** rx_cbdp, int epid); + +/****************************************************************************** + Global Linkage + *****************************************************************************/ + +/* udc_init + * + * Do initial bus gluing + */ +int udc_init (void) +{ + /* Init various pointers */ + immr = (immap_t *) CONFIG_SYS_IMMR; + cp = (cpm8xx_t *) & (immr->im_cpm); + usb_paramp = (usb_pram_t *) & (cp->cp_dparam[PROFF_USB]); + usbp = (usb_t *) & (cp->cp_scc[0]); + + memset (ep_ref, 0x00, (sizeof (struct mpc8xx_ep) * MAX_ENDPOINTS)); + + udc_device = 0; + udc_state = STATE_NOT_READY; + + usbp->usmod = 0x00; + usbp->uscom = 0; + + /* Set USB Frame #0, Respond at Address & Get a clock source */ + usbp->usaddr = 0x00; + mpc8xx_udc_clock_init (immr, cp); + + /* PA15, PA14 as perhiperal USBRXD and USBOE */ + immr->im_ioport.iop_padir &= ~0x0003; + immr->im_ioport.iop_papar |= 0x0003; + + /* PC11/PC10 as peripheral USBRXP USBRXN */ + immr->im_ioport.iop_pcso |= 0x0030; + + /* PC7/PC6 as perhiperal USBTXP and USBTXN */ + immr->im_ioport.iop_pcdir |= 0x0300; + immr->im_ioport.iop_pcpar |= 0x0300; + + /* Set the base address */ + address_base = (u32) (cp->cp_dpmem + CPM_USB_BASE); + + /* Initialise endpoints and circular buffers */ + mpc8xx_udc_endpoint_init (); + mpc8xx_udc_cbd_init (); + + /* Assign allocated Dual Port Endpoint descriptors */ + usb_paramp->ep0ptr = (u32) endpoints[0]; + usb_paramp->ep1ptr = (u32) endpoints[1]; + usb_paramp->ep2ptr = (u32) endpoints[2]; + usb_paramp->ep3ptr = (u32) endpoints[3]; + usb_paramp->frame_n = 0; + + DBG ("ep0ptr=0x%08x ep1ptr=0x%08x ep2ptr=0x%08x ep3ptr=0x%08x\n", + usb_paramp->ep0ptr, usb_paramp->ep1ptr, usb_paramp->ep2ptr, + usb_paramp->ep3ptr); + + return 0; +} + +/* udc_irq + * + * Poll for whatever events may have occured + */ +void udc_irq (void) +{ + int epid = 0; + volatile cbd_t *rx_cbdp = 0; + volatile cbd_t *rx_cbdp_base = 0; + + if (udc_state != STATE_READY) { + return; + } + + if (usbp->usber & USB_E_BSY) { + /* This shouldn't happen. If it does then it's a bug ! */ + usbp->usber |= USB_E_BSY; + mpc8xx_udc_flush_rx_fifo (); + } + + /* Scan all RX/Bidirectional Endpoints for RX data. */ + for (epid = 0; epid < MAX_ENDPOINTS; epid++) { + if (!ep_ref[epid].prx) { + continue; + } + rx_cbdp = rx_cbdp_base = ep_ref[epid].prx; + + do { + if (!(rx_cbdp->cbd_sc & RX_BD_E)) { + + if (rx_cbdp->cbd_sc & 0x1F) { + /* Corrupt data discard it. + * Controller has NAK'd this packet. + */ + mpc8xx_udc_clear_rxbd (rx_cbdp); + + } else { + if (!epid) { + mpc8xx_udc_ep0_rx (rx_cbdp); + + } else { + /* Process data */ + mpc8xx_udc_set_nak (epid); + mpc8xx_udc_epn_rx (epid, rx_cbdp); + mpc8xx_udc_clear_rxbd (rx_cbdp); + } + } + + /* Advance RX CBD pointer */ + mpc8xx_udc_advance_rx (&rx_cbdp, epid); + ep_ref[epid].prx = rx_cbdp; + } else { + /* Advance RX CBD pointer */ + mpc8xx_udc_advance_rx (&rx_cbdp, epid); + } + + } while (rx_cbdp != rx_cbdp_base); + } + + /* Handle TX events as appropiate, the correct place to do this is + * in a tx routine. Perhaps TX on epn was pre-empted by ep0 + */ + + if (usbp->usber & USB_E_TXB) { + usbp->usber |= USB_E_TXB; + } + + if (usbp->usber & (USB_TX_ERRMASK)) { + mpc8xx_udc_handle_txerr (); + } + + /* Switch to the default state, respond at the default address */ + if (usbp->usber & USB_E_RESET) { + usbp->usber |= USB_E_RESET; + usbp->usaddr = 0x00; + udc_device->device_state = STATE_DEFAULT; + } + + /* if(usbp->usber&USB_E_IDLE){ + We could suspend here ! + usbp->usber|=USB_E_IDLE; + DBG("idle state change\n"); + } + if(usbp->usbs){ + We could resume here when IDLE is deasserted ! + Not worth doing, so long as we are self powered though. + } + */ + + return; +} + +/* udc_endpoint_write + * + * Write some data to an endpoint + */ +int udc_endpoint_write (struct usb_endpoint_instance *epi) +{ + int ep = 0; + short epid = 1, unnak = 0, ret = 0; + + if (udc_state != STATE_READY) { + ERR ("invalid udc_state != STATE_READY!\n"); + return -1; + } + + if (!udc_device || !epi) { + return -1; + } + + if (udc_device->device_state != STATE_CONFIGURED) { + return -1; + } + + ep = epi->endpoint_address & 0x03; + if (ep >= MAX_ENDPOINTS) { + return -1; + } + + /* Set NAK for all RX endpoints during TX */ + for (epid = 1; epid < MAX_ENDPOINTS; epid++) { + + /* Don't set NAK on DATA IN/CONTROL endpoints */ + if (ep_ref[epid].sc & USB_DIR_IN) { + continue; + } + + if (!(usbp->usep[epid] & (USEP_THS_NAK | USEP_RHS_NAK))) { + unnak |= 1 << epid; + } + + mpc8xx_udc_set_nak (epid); + } + + mpc8xx_udc_init_tx (&udc_device->bus->endpoint_array[ep], + epi->tx_urb); + ret = mpc8xx_udc_ep_tx (&udc_device->bus->endpoint_array[ep]); + + /* Remove temporary NAK */ + for (epid = 1; epid < MAX_ENDPOINTS; epid++) { + if (unnak & (1 << epid)) { + udc_unset_nak (epid); + } + } + + return ret; +} + +/* mpc8xx_udc_assign_urb + * + * Associate a given urb to an endpoint TX or RX transmit/receive buffers + */ +static int mpc8xx_udc_assign_urb (int ep, char direction) +{ + struct usb_endpoint_instance *epi = 0; + + if (ep >= MAX_ENDPOINTS) { + goto err; + } + epi = &udc_device->bus->endpoint_array[ep]; + if (!epi) { + goto err; + } + + if (!ep_ref[ep].urb) { + ep_ref[ep].urb = usbd_alloc_urb (udc_device, udc_device->bus->endpoint_array); + if (!ep_ref[ep].urb) { + goto err; + } + } else { + ep_ref[ep].urb->actual_length = 0; + } + + switch (direction) { + case USB_DIR_IN: + epi->tx_urb = ep_ref[ep].urb; + break; + case USB_DIR_OUT: + epi->rcv_urb = ep_ref[ep].urb; + break; + default: + goto err; + } + return 0; + + err: + udc_state = STATE_ERROR; + return -1; +} + +/* udc_setup_ep + * + * Associate U-Boot software endpoints to mpc8xx endpoint parameter ram + * Isochronous endpoints aren't yet supported! + */ +void udc_setup_ep (struct usb_device_instance *device, unsigned int ep, + struct usb_endpoint_instance *epi) +{ + uchar direction = 0; + int ep_attrib = 0; + + if (epi && (ep < MAX_ENDPOINTS)) { + + if (ep == 0) { + if (epi->rcv_attributes != USB_ENDPOINT_XFER_CONTROL + || epi->tx_attributes != + USB_ENDPOINT_XFER_CONTROL) { + + /* ep0 must be a control endpoint */ + udc_state = STATE_ERROR; + return; + + } + if (!(ep_ref[ep].sc & EP_ATTACHED)) { + mpc8xx_udc_cbd_attach (ep, epi->tx_packetSize, + epi->rcv_packetSize); + } + usbp->usep[ep] = 0x0000; + return; + } + + if ((epi->endpoint_address & USB_ENDPOINT_DIR_MASK) + == USB_DIR_IN) { + + direction = 1; + ep_attrib = epi->tx_attributes; + epi->rcv_packetSize = 0; + ep_ref[ep].sc |= USB_DIR_IN; + } else { + + direction = 0; + ep_attrib = epi->rcv_attributes; + epi->tx_packetSize = 0; + ep_ref[ep].sc &= ~USB_DIR_IN; + } + + if (mpc8xx_udc_assign_urb (ep, epi->endpoint_address + & USB_ENDPOINT_DIR_MASK)) { + return; + } + + switch (ep_attrib) { + case USB_ENDPOINT_XFER_CONTROL: + if (!(ep_ref[ep].sc & EP_ATTACHED)) { + mpc8xx_udc_cbd_attach (ep, + epi->tx_packetSize, + epi->rcv_packetSize); + } + usbp->usep[ep] = ep << 12; + epi->rcv_urb = epi->tx_urb = ep_ref[ep].urb; + + break; + case USB_ENDPOINT_XFER_BULK: + case USB_ENDPOINT_XFER_INT: + if (!(ep_ref[ep].sc & EP_ATTACHED)) { + if (direction) { + mpc8xx_udc_cbd_attach (ep, + epi->tx_packetSize, + 0); + } else { + mpc8xx_udc_cbd_attach (ep, + 0, + epi->rcv_packetSize); + } + } + usbp->usep[ep] = (ep << 12) | ((ep_attrib) << 8); + + break; + case USB_ENDPOINT_XFER_ISOC: + default: + serial_printf ("Error endpoint attrib %d>3\n", ep_attrib); + udc_state = STATE_ERROR; + break; + } + } + +} + +/* udc_connect + * + * Move state, switch on the USB + */ +void udc_connect (void) +{ + /* Enable pull-up resistor on D+ + * TODO: fit a pull-up resistor to drive SE0 for > 2.5us + */ + + if (udc_state != STATE_ERROR) { + udc_state = STATE_READY; + usbp->usmod |= USMOD_EN; + } +} + +/* udc_disconnect + * + * Disconnect is not used but, is included for completeness + */ +void udc_disconnect (void) +{ + /* Disable pull-up resistor on D- + * TODO: fix a pullup resistor to control this + */ + + if (udc_state != STATE_ERROR) { + udc_state = STATE_NOT_READY; + } + usbp->usmod &= ~USMOD_EN; +} + +/* udc_enable + * + * Grab an EP0 URB, register interest in a subset of USB events + */ +void udc_enable (struct usb_device_instance *device) +{ + if (udc_state == STATE_ERROR) { + return; + } + + udc_device = device; + + if (!ep_ref[0].urb) { + ep_ref[0].urb = usbd_alloc_urb (device, device->bus->endpoint_array); + } + + /* Register interest in all events except SOF, enable transceiver */ + usbp->usber = 0x03FF; + usbp->usbmr = 0x02F7; + + return; +} + +/* udc_disable + * + * disable the currently hooked device + */ +void udc_disable (void) +{ + int i = 0; + + if (udc_state == STATE_ERROR) { + DBG ("Won't disable UDC. udc_state==STATE_ERROR !\n"); + return; + } + + udc_device = 0; + + for (; i < MAX_ENDPOINTS; i++) { + if (ep_ref[i].urb) { + usbd_dealloc_urb (ep_ref[i].urb); + ep_ref[i].urb = 0; + } + } + + usbp->usbmr = 0x00; + usbp->usmod = ~USMOD_EN; + udc_state = STATE_NOT_READY; +} + +/* udc_startup_events + * + * Enable the specified device + */ +void udc_startup_events (struct usb_device_instance *device) +{ + udc_enable (device); + if (udc_state == STATE_READY) { + usbd_device_event_irq (device, DEVICE_CREATE, 0); + } +} + +/* udc_set_nak + * + * Allow upper layers to signal lower layers should not accept more RX data + * + */ +void udc_set_nak (int epid) +{ + if (epid) { + mpc8xx_udc_set_nak (epid); + } +} + +/* udc_unset_nak + * + * Suspend sending of NAK tokens for DATA OUT tokens on a given endpoint. + * Switch off NAKing on this endpoint to accept more data output from host. + * + */ +void udc_unset_nak (int epid) +{ + if (epid > MAX_ENDPOINTS) { + return; + } + + if (usbp->usep[epid] & (USEP_THS_NAK | USEP_RHS_NAK)) { + usbp->usep[epid] &= ~(USEP_THS_NAK | USEP_RHS_NAK); + __asm__ ("eieio"); + } +} + +/****************************************************************************** + Static Linkage +******************************************************************************/ + +/* udc_state_transition_up + * udc_state_transition_down + * + * Helper functions to implement device state changes. The device states and + * the events that transition between them are: + * + * STATE_ATTACHED + * || /\ + * \/ || + * DEVICE_HUB_CONFIGURED DEVICE_HUB_RESET + * || /\ + * \/ || + * STATE_POWERED + * || /\ + * \/ || + * DEVICE_RESET DEVICE_POWER_INTERRUPTION + * || /\ + * \/ || + * STATE_DEFAULT + * || /\ + * \/ || + * DEVICE_ADDRESS_ASSIGNED DEVICE_RESET + * || /\ + * \/ || + * STATE_ADDRESSED + * || /\ + * \/ || + * DEVICE_CONFIGURED DEVICE_DE_CONFIGURED + * || /\ + * \/ || + * STATE_CONFIGURED + * + * udc_state_transition_up transitions up (in the direction from STATE_ATTACHED + * to STATE_CONFIGURED) from the specified initial state to the specified final + * state, passing through each intermediate state on the way. If the initial + * state is at or above (i.e. nearer to STATE_CONFIGURED) the final state, then + * no state transitions will take place. + * + * udc_state_transition_down transitions down (in the direction from + * STATE_CONFIGURED to STATE_ATTACHED) from the specified initial state to the + * specified final state, passing through each intermediate state on the way. + * If the initial state is at or below (i.e. nearer to STATE_ATTACHED) the final + * state, then no state transitions will take place. + * + */ + +static void mpc8xx_udc_state_transition_up (usb_device_state_t initial, + usb_device_state_t final) +{ + if (initial < final) { + switch (initial) { + case STATE_ATTACHED: + usbd_device_event_irq (udc_device, + DEVICE_HUB_CONFIGURED, 0); + if (final == STATE_POWERED) + break; + case STATE_POWERED: + usbd_device_event_irq (udc_device, DEVICE_RESET, 0); + if (final == STATE_DEFAULT) + break; + case STATE_DEFAULT: + usbd_device_event_irq (udc_device, + DEVICE_ADDRESS_ASSIGNED, 0); + if (final == STATE_ADDRESSED) + break; + case STATE_ADDRESSED: + usbd_device_event_irq (udc_device, DEVICE_CONFIGURED, + 0); + case STATE_CONFIGURED: + break; + default: + break; + } + } +} + +static void mpc8xx_udc_state_transition_down (usb_device_state_t initial, + usb_device_state_t final) +{ + if (initial > final) { + switch (initial) { + case STATE_CONFIGURED: + usbd_device_event_irq (udc_device, + DEVICE_DE_CONFIGURED, 0); + if (final == STATE_ADDRESSED) + break; + case STATE_ADDRESSED: + usbd_device_event_irq (udc_device, DEVICE_RESET, 0); + if (final == STATE_DEFAULT) + break; + case STATE_DEFAULT: + usbd_device_event_irq (udc_device, + DEVICE_POWER_INTERRUPTION, 0); + if (final == STATE_POWERED) + break; + case STATE_POWERED: + usbd_device_event_irq (udc_device, DEVICE_HUB_RESET, + 0); + case STATE_ATTACHED: + break; + default: + break; + } + } +} + +/* mpc8xx_udc_stall + * + * Force returning of STALL tokens on the given endpoint. Protocol or function + * STALL conditions are permissable here + */ +static void mpc8xx_udc_stall (unsigned int ep) +{ + usbp->usep[ep] |= STALL_BITMASK; +} + +/* mpc8xx_udc_set_nak + * + * Force returning of NAK responses for the given endpoint as a kind of very + * simple flow control + */ +static void mpc8xx_udc_set_nak (unsigned int ep) +{ + usbp->usep[ep] |= NAK_BITMASK; + __asm__ ("eieio"); +} + +/* mpc8xx_udc_handle_txerr + * + * Handle errors relevant to TX. Return a status code to allow calling + * indicative of what if anything happened + */ +static short mpc8xx_udc_handle_txerr () +{ + short ep = 0, ret = 0; + + for (; ep < TX_RING_SIZE; ep++) { + if (usbp->usber & (0x10 << ep)) { + + /* Timeout or underrun */ + if (tx_cbd[ep]->cbd_sc & 0x06) { + ret = 1; + mpc8xx_udc_flush_tx_fifo (ep); + + } else { + if (usbp->usep[ep] & STALL_BITMASK) { + if (!ep) { + usbp->usep[ep] &= ~STALL_BITMASK; + } + } /* else NAK */ + } + usbp->usber |= (0x10 << ep); + } + } + return ret; +} + +/* mpc8xx_udc_advance_rx + * + * Advance cbd rx + */ +static void mpc8xx_udc_advance_rx (volatile cbd_t ** rx_cbdp, int epid) +{ + if ((*rx_cbdp)->cbd_sc & RX_BD_W) { + *rx_cbdp = (volatile cbd_t *) (endpoints[epid]->rbase + CONFIG_SYS_IMMR); + + } else { + (*rx_cbdp)++; + } +} + + +/* mpc8xx_udc_flush_tx_fifo + * + * Flush a given TX fifo. Assumes one tx cbd per endpoint + */ +static void mpc8xx_udc_flush_tx_fifo (int epid) +{ + volatile cbd_t *tx_cbdp = 0; + + if (epid > MAX_ENDPOINTS) { + return; + } + + /* TX stop */ + immr->im_cpm.cp_cpcr = ((epid << 2) | 0x1D01); + __asm__ ("eieio"); + while (immr->im_cpm.cp_cpcr & 0x01); + + usbp->uscom = 0x40 | 0; + + /* reset ring */ + tx_cbdp = (cbd_t *) (endpoints[epid]->tbptr + CONFIG_SYS_IMMR); + tx_cbdp->cbd_sc = (TX_BD_I | TX_BD_W); + + + endpoints[epid]->tptr = endpoints[epid]->tbase; + endpoints[epid]->tstate = 0x00; + endpoints[epid]->tbcnt = 0x00; + + /* TX start */ + immr->im_cpm.cp_cpcr = ((epid << 2) | 0x2D01); + __asm__ ("eieio"); + while (immr->im_cpm.cp_cpcr & 0x01); + + return; +} + +/* mpc8xx_udc_flush_rx_fifo + * + * For the sake of completeness of the namespace, it seems like + * a good-design-decision (tm) to include mpc8xx_udc_flush_rx_fifo(); + * If RX_BD_E is true => a driver bug either here or in an upper layer + * not polling frequently enough. If RX_BD_E is true we have told the host + * we have accepted data but, the CPM found it had no-where to put that data + * which needless to say would be a bad thing. + */ +static void mpc8xx_udc_flush_rx_fifo () +{ + int i = 0; + + for (i = 0; i < RX_RING_SIZE; i++) { + if (!(rx_cbd[i]->cbd_sc & RX_BD_E)) { + ERR ("buf %p used rx data len = 0x%x sc=0x%x!\n", + rx_cbd[i], rx_cbd[i]->cbd_datlen, + rx_cbd[i]->cbd_sc); + + } + } + ERR ("BUG : Input over-run\n"); +} + +/* mpc8xx_udc_clear_rxbd + * + * Release control of RX CBD to CP. + */ +static void mpc8xx_udc_clear_rxbd (volatile cbd_t * rx_cbdp) +{ + rx_cbdp->cbd_datlen = 0x0000; + rx_cbdp->cbd_sc = ((rx_cbdp->cbd_sc & RX_BD_W) | (RX_BD_E | RX_BD_I)); + __asm__ ("eieio"); +} + +/* mpc8xx_udc_tx_irq + * + * Parse for tx timeout, control RX or USB reset/busy conditions + * Return -1 on timeout, -2 on fatal error, else return zero + */ +static int mpc8xx_udc_tx_irq (int ep) +{ + int i = 0; + + if (usbp->usber & (USB_TX_ERRMASK)) { + if (mpc8xx_udc_handle_txerr ()) { + /* Timeout, controlling function must retry send */ + return -1; + } + } + + if (usbp->usber & (USB_E_RESET | USB_E_BSY)) { + /* Fatal, abandon TX transaction */ + return -2; + } + + if (usbp->usber & USB_E_RXB) { + for (i = 0; i < RX_RING_SIZE; i++) { + if (!(rx_cbd[i]->cbd_sc & RX_BD_E)) { + if ((rx_cbd[i] == ep_ref[0].prx) || ep) { + return -2; + } + } + } + } + + return 0; +} + +/* mpc8xx_udc_ep_tx + * + * Transmit in a re-entrant fashion outbound USB packets. + * Implement retry/timeout mechanism described in USB specification + * Toggle DATA0/DATA1 pids as necessary + * Introduces non-standard tx_retry. The USB standard has no scope for slave + * devices to give up TX, however tx_retry stops us getting stuck in an endless + * TX loop. + */ +static int mpc8xx_udc_ep_tx (struct usb_endpoint_instance *epi) +{ + struct urb *urb = epi->tx_urb; + volatile cbd_t *tx_cbdp = 0; + unsigned int ep = 0, pkt_len = 0, x = 0, tx_retry = 0; + int ret = 0; + + if (!epi || (epi->endpoint_address & 0x03) >= MAX_ENDPOINTS || !urb) { + return -1; + } + + ep = epi->endpoint_address & 0x03; + tx_cbdp = (cbd_t *) (endpoints[ep]->tbptr + CONFIG_SYS_IMMR); + + if (tx_cbdp->cbd_sc & TX_BD_R || usbp->usber & USB_E_TXB) { + mpc8xx_udc_flush_tx_fifo (ep); + usbp->usber |= USB_E_TXB; + }; + + while (tx_retry++ < 100) { + ret = mpc8xx_udc_tx_irq (ep); + if (ret == -1) { + /* ignore timeout here */ + } else if (ret == -2) { + /* Abandon TX */ + mpc8xx_udc_flush_tx_fifo (ep); + return -1; + } + + tx_cbdp = (cbd_t *) (endpoints[ep]->tbptr + CONFIG_SYS_IMMR); + while (tx_cbdp->cbd_sc & TX_BD_R) { + }; + tx_cbdp->cbd_sc = (tx_cbdp->cbd_sc & TX_BD_W); + + pkt_len = urb->actual_length - epi->sent; + + if (pkt_len > epi->tx_packetSize || pkt_len > EP_MAX_PKT) { + pkt_len = MIN (epi->tx_packetSize, EP_MAX_PKT); + } + + for (x = 0; x < pkt_len; x++) { + *((unsigned char *) (tx_cbdp->cbd_bufaddr + x)) = + urb->buffer[epi->sent + x]; + } + tx_cbdp->cbd_datlen = pkt_len; + tx_cbdp->cbd_sc |= (CBD_TX_BITMASK | ep_ref[ep].pid); + __asm__ ("eieio"); + +#ifdef __SIMULATE_ERROR__ + if (++err_poison_test == 2) { + err_poison_test = 0; + tx_cbdp->cbd_sc &= ~TX_BD_TC; + } +#endif + + usbp->uscom = (USCOM_STR | ep); + + while (!(usbp->usber & USB_E_TXB)) { + ret = mpc8xx_udc_tx_irq (ep); + if (ret == -1) { + /* TX timeout */ + break; + } else if (ret == -2) { + if (usbp->usber & USB_E_TXB) { + usbp->usber |= USB_E_TXB; + } + mpc8xx_udc_flush_tx_fifo (ep); + return -1; + } + }; + + if (usbp->usber & USB_E_TXB) { + usbp->usber |= USB_E_TXB; + } + + /* ACK must be present <= 18bit times from TX */ + if (ret == -1) { + continue; + } + + /* TX ACK : USB 2.0 8.7.2, Toggle PID, Advance TX */ + epi->sent += pkt_len; + epi->last = MIN (urb->actual_length - epi->sent, epi->tx_packetSize); + TOGGLE_TX_PID (ep_ref[ep].pid); + + if (epi->sent >= epi->tx_urb->actual_length) { + + epi->tx_urb->actual_length = 0; + epi->sent = 0; + + if (ep_ref[ep].sc & EP_SEND_ZLP) { + ep_ref[ep].sc &= ~EP_SEND_ZLP; + } else { + return 0; + } + } + } + + ERR ("TX fail, endpoint 0x%x tx bytes 0x%x/0x%x\n", ep, epi->sent, + epi->tx_urb->actual_length); + + return -1; +} + +/* mpc8xx_udc_dump_request + * + * Dump a control request to console + */ +static void mpc8xx_udc_dump_request (struct usb_device_request *request) +{ + DBG ("bmRequestType:%02x bRequest:%02x wValue:%04x " + "wIndex:%04x wLength:%04x ?\n", + request->bmRequestType, + request->bRequest, + request->wValue, request->wIndex, request->wLength); + + return; +} + +/* mpc8xx_udc_ep0_rx_setup + * + * Decode received ep0 SETUP packet. return non-zero on error + */ +static int mpc8xx_udc_ep0_rx_setup (volatile cbd_t * rx_cbdp) +{ + unsigned int x = 0; + struct urb *purb = ep_ref[0].urb; + struct usb_endpoint_instance *epi = + &udc_device->bus->endpoint_array[0]; + + for (; x < rx_cbdp->cbd_datlen; x++) { + *(((unsigned char *) &ep_ref[0].urb->device_request) + x) = + *((unsigned char *) (rx_cbdp->cbd_bufaddr + x)); + } + + mpc8xx_udc_clear_rxbd (rx_cbdp); + + if (ep0_recv_setup (purb)) { + mpc8xx_udc_dump_request (&purb->device_request); + return -1; + } + + if ((purb->device_request.bmRequestType & USB_REQ_DIRECTION_MASK) + == USB_REQ_HOST2DEVICE) { + + switch (purb->device_request.bRequest) { + case USB_REQ_SET_ADDRESS: + /* Send the Status OUT ZLP */ + ep_ref[0].pid = TX_BD_PID_DATA1; + purb->actual_length = 0; + mpc8xx_udc_init_tx (epi, purb); + mpc8xx_udc_ep_tx (epi); + + /* Move to the addressed state */ + usbp->usaddr = udc_device->address; + mpc8xx_udc_state_transition_up (udc_device->device_state, + STATE_ADDRESSED); + return 0; + + case USB_REQ_SET_CONFIGURATION: + if (!purb->device_request.wValue) { + /* Respond at default address */ + usbp->usaddr = 0x00; + mpc8xx_udc_state_transition_down (udc_device->device_state, + STATE_ADDRESSED); + } else { + /* TODO: Support multiple configurations */ + mpc8xx_udc_state_transition_up (udc_device->device_state, + STATE_CONFIGURED); + for (x = 1; x < MAX_ENDPOINTS; x++) { + if ((udc_device->bus->endpoint_array[x].endpoint_address & USB_ENDPOINT_DIR_MASK) + == USB_DIR_IN) { + ep_ref[x].pid = TX_BD_PID_DATA0; + } else { + ep_ref[x].pid = RX_BD_PID_DATA0; + } + /* Set configuration must unstall endpoints */ + usbp->usep[x] &= ~STALL_BITMASK; + } + } + break; + default: + /* CDC/Vendor specific */ + break; + } + + /* Send ZLP as ACK in Status OUT phase */ + ep_ref[0].pid = TX_BD_PID_DATA1; + purb->actual_length = 0; + mpc8xx_udc_init_tx (epi, purb); + mpc8xx_udc_ep_tx (epi); + + } else { + + if (purb->actual_length) { + ep_ref[0].pid = TX_BD_PID_DATA1; + mpc8xx_udc_init_tx (epi, purb); + + if (!(purb->actual_length % EP0_MAX_PACKET_SIZE)) { + ep_ref[0].sc |= EP_SEND_ZLP; + } + + if (purb->device_request.wValue == + USB_DESCRIPTOR_TYPE_DEVICE) { + if (le16_to_cpu (purb->device_request.wLength) + > purb->actual_length) { + /* Send EP0_MAX_PACKET_SIZE bytes + * unless correct size requested. + */ + if (purb->actual_length > epi->tx_packetSize) { + purb->actual_length = epi->tx_packetSize; + } + } + } + mpc8xx_udc_ep_tx (epi); + + } else { + /* Corrupt SETUP packet? */ + ERR ("Zero length data or SETUP with DATA-IN phase ?\n"); + return 1; + } + } + return 0; +} + +/* mpc8xx_udc_init_tx + * + * Setup some basic parameters for a TX transaction + */ +static void mpc8xx_udc_init_tx (struct usb_endpoint_instance *epi, + struct urb *tx_urb) +{ + epi->sent = 0; + epi->last = 0; + epi->tx_urb = tx_urb; +} + +/* mpc8xx_udc_ep0_rx + * + * Receive ep0/control USB data. Parse and possibly send a response. + */ +static void mpc8xx_udc_ep0_rx (volatile cbd_t * rx_cbdp) +{ + if (rx_cbdp->cbd_sc & RX_BD_PID_SETUP) { + + /* Unconditionally accept SETUP packets */ + if (mpc8xx_udc_ep0_rx_setup (rx_cbdp)) { + mpc8xx_udc_stall (0); + } + + } else { + + mpc8xx_udc_clear_rxbd (rx_cbdp); + + if ((rx_cbdp->cbd_datlen - 2)) { + /* SETUP with a DATA phase + * outside of SETUP packet. + * Reply with STALL. + */ + mpc8xx_udc_stall (0); + } + } +} + +/* mpc8xx_udc_epn_rx + * + * Receive some data from cbd into USB system urb data abstraction + * Upper layers should NAK if there is insufficient RX data space + */ +static int mpc8xx_udc_epn_rx (unsigned int epid, volatile cbd_t * rx_cbdp) +{ + struct usb_endpoint_instance *epi = 0; + struct urb *urb = 0; + unsigned int x = 0; + + if (epid >= MAX_ENDPOINTS || !rx_cbdp->cbd_datlen) { + return 0; + } + + /* USB 2.0 PDF section 8.6.4 + * Discard data with invalid PID it is a resend. + */ + if (ep_ref[epid].pid != (rx_cbdp->cbd_sc & 0xC0)) { + return 1; + } + TOGGLE_RX_PID (ep_ref[epid].pid); + + epi = &udc_device->bus->endpoint_array[epid]; + urb = epi->rcv_urb; + + for (; x < (rx_cbdp->cbd_datlen - 2); x++) { + *((unsigned char *) (urb->buffer + urb->actual_length + x)) = + *((unsigned char *) (rx_cbdp->cbd_bufaddr + x)); + } + + if (x) { + usbd_rcv_complete (epi, x, 0); + if (ep_ref[epid].urb->status == RECV_ERROR) { + DBG ("RX error unset NAK\n"); + udc_unset_nak (epid); + } + } + return x; +} + +/* mpc8xx_udc_clock_init + * + * Obtain a clock reference for Full Speed Signaling + */ +static void mpc8xx_udc_clock_init (volatile immap_t * immr, + volatile cpm8xx_t * cp) +{ + +#if defined(CONFIG_SYS_USB_EXTC_CLK) + + /* This has been tested with a 48MHz crystal on CLK6 */ + switch (CONFIG_SYS_USB_EXTC_CLK) { + case 1: + immr->im_ioport.iop_papar |= 0x0100; + immr->im_ioport.iop_padir &= ~0x0100; + cp->cp_sicr |= 0x24; + break; + case 2: + immr->im_ioport.iop_papar |= 0x0200; + immr->im_ioport.iop_padir &= ~0x0200; + cp->cp_sicr |= 0x2D; + break; + case 3: + immr->im_ioport.iop_papar |= 0x0400; + immr->im_ioport.iop_padir &= ~0x0400; + cp->cp_sicr |= 0x36; + break; + case 4: + immr->im_ioport.iop_papar |= 0x0800; + immr->im_ioport.iop_padir &= ~0x0800; + cp->cp_sicr |= 0x3F; + break; + default: + udc_state = STATE_ERROR; + break; + } + +#elif defined(CONFIG_SYS_USB_BRGCLK) + + /* This has been tested with brgclk == 50MHz */ + int divisor = 0; + + if (gd->cpu_clk < 48000000L) { + ERR ("brgclk is too slow for full-speed USB!\n"); + udc_state = STATE_ERROR; + return; + } + + /* Assume the brgclk is 'good enough', we want !(gd->cpu_clk%48MHz) + * but, can /probably/ live with close-ish alternative rates. + */ + divisor = (gd->cpu_clk / 48000000L) - 1; + cp->cp_sicr &= ~0x0000003F; + + switch (CONFIG_SYS_USB_BRGCLK) { + case 1: + cp->cp_brgc1 |= (divisor | CPM_BRG_EN); + cp->cp_sicr &= ~0x2F; + break; + case 2: + cp->cp_brgc2 |= (divisor | CPM_BRG_EN); + cp->cp_sicr |= 0x00000009; + break; + case 3: + cp->cp_brgc3 |= (divisor | CPM_BRG_EN); + cp->cp_sicr |= 0x00000012; + break; + case 4: + cp->cp_brgc4 = (divisor | CPM_BRG_EN); + cp->cp_sicr |= 0x0000001B; + break; + default: + udc_state = STATE_ERROR; + break; + } + +#else +#error "CONFIG_SYS_USB_EXTC_CLK or CONFIG_SYS_USB_BRGCLK must be defined" +#endif + +} + +/* mpc8xx_udc_cbd_attach + * + * attach a cbd to and endpoint + */ +static void mpc8xx_udc_cbd_attach (int ep, uchar tx_size, uchar rx_size) +{ + + if (!tx_cbd[ep] || !rx_cbd[ep] || ep >= MAX_ENDPOINTS) { + udc_state = STATE_ERROR; + return; + } + + if (tx_size > USB_MAX_PKT || rx_size > USB_MAX_PKT || + (!tx_size && !rx_size)) { + udc_state = STATE_ERROR; + return; + } + + /* Attach CBD to appropiate Parameter RAM Endpoint data structure */ + if (rx_size) { + endpoints[ep]->rbase = (u32) rx_cbd[rx_ct]; + endpoints[ep]->rbptr = (u32) rx_cbd[rx_ct]; + rx_ct++; + + if (!ep) { + + endpoints[ep]->rbptr = (u32) rx_cbd[rx_ct]; + rx_cbd[rx_ct]->cbd_sc |= RX_BD_W; + rx_ct++; + + } else { + rx_ct += 2; + endpoints[ep]->rbptr = (u32) rx_cbd[rx_ct]; + rx_cbd[rx_ct]->cbd_sc |= RX_BD_W; + rx_ct++; + } + + /* Where we expect to RX data on this endpoint */ + ep_ref[ep].prx = rx_cbd[rx_ct - 1]; + } else { + + ep_ref[ep].prx = 0; + endpoints[ep]->rbase = 0; + endpoints[ep]->rbptr = 0; + } + + if (tx_size) { + endpoints[ep]->tbase = (u32) tx_cbd[tx_ct]; + endpoints[ep]->tbptr = (u32) tx_cbd[tx_ct]; + tx_ct++; + } else { + endpoints[ep]->tbase = 0; + endpoints[ep]->tbptr = 0; + } + + endpoints[ep]->tstate = 0; + endpoints[ep]->tbcnt = 0; + endpoints[ep]->mrblr = EP_MAX_PKT; + endpoints[ep]->rfcr = 0x18; + endpoints[ep]->tfcr = 0x18; + ep_ref[ep].sc |= EP_ATTACHED; + + DBG ("ep %d rbase 0x%08x rbptr 0x%08x tbase 0x%08x tbptr 0x%08x prx = %p\n", + ep, endpoints[ep]->rbase, endpoints[ep]->rbptr, + endpoints[ep]->tbase, endpoints[ep]->tbptr, + ep_ref[ep].prx); + + return; +} + +/* mpc8xx_udc_cbd_init + * + * Allocate space for a cbd and allocate TX/RX data space + */ +static void mpc8xx_udc_cbd_init (void) +{ + int i = 0; + + for (; i < TX_RING_SIZE; i++) { + tx_cbd[i] = (cbd_t *) + mpc8xx_udc_alloc (sizeof (cbd_t), sizeof (int)); + } + + for (i = 0; i < RX_RING_SIZE; i++) { + rx_cbd[i] = (cbd_t *) + mpc8xx_udc_alloc (sizeof (cbd_t), sizeof (int)); + } + + for (i = 0; i < TX_RING_SIZE; i++) { + tx_cbd[i]->cbd_bufaddr = + mpc8xx_udc_alloc (EP_MAX_PKT, sizeof (int)); + + tx_cbd[i]->cbd_sc = (TX_BD_I | TX_BD_W); + tx_cbd[i]->cbd_datlen = 0x0000; + } + + + for (i = 0; i < RX_RING_SIZE; i++) { + rx_cbd[i]->cbd_bufaddr = + mpc8xx_udc_alloc (EP_MAX_PKT, sizeof (int)); + rx_cbd[i]->cbd_sc = (RX_BD_I | RX_BD_E); + rx_cbd[i]->cbd_datlen = 0x0000; + + } + + return; +} + +/* mpc8xx_udc_endpoint_init + * + * Attach an endpoint to some dpram + */ +static void mpc8xx_udc_endpoint_init (void) +{ + int i = 0; + + for (; i < MAX_ENDPOINTS; i++) { + endpoints[i] = (usb_epb_t *) + mpc8xx_udc_alloc (sizeof (usb_epb_t), 32); + } +} + +/* mpc8xx_udc_alloc + * + * Grab the address of some dpram + */ +static u32 mpc8xx_udc_alloc (u32 data_size, u32 alignment) +{ + u32 retaddr = address_base; + + while (retaddr % alignment) { + retaddr++; + } + address_base += data_size; + + return retaddr; +} diff --git a/roms/u-boot-sam460ex/drivers/usb/gadget/omap1510_udc.c b/roms/u-boot-sam460ex/drivers/usb/gadget/omap1510_udc.c new file mode 100644 index 000000000..90f79070d --- /dev/null +++ b/roms/u-boot-sam460ex/drivers/usb/gadget/omap1510_udc.c @@ -0,0 +1,1567 @@ +/* + * (C) Copyright 2003 + * Gerry Hamel, geh@ti.com, Texas Instruments + * + * Based on + * linux/drivers/usb/device/bi/omap.c + * TI OMAP1510 USB bus interface driver + * + * Author: MontaVista Software, Inc. + * source@mvista.com + * (C) Copyright 2002 + * + * 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 <asm/io.h> +#ifdef CONFIG_OMAP_SX1 +#include <i2c.h> +#endif +#include <usbdevice.h> +#include <usb/omap1510_udc.h> + +#include "ep0.h" + + +#define UDC_INIT_MDELAY 80 /* Device settle delay */ +#define UDC_MAX_ENDPOINTS 31 /* Number of endpoints on this UDC */ + +/* Some kind of debugging output... */ +#if 1 +#define UDCDBG(str) +#define UDCDBGA(fmt,args...) +#else /* The bugs still exists... */ +#define UDCDBG(str) serial_printf("[%s] %s:%d: " str "\n", __FILE__,__FUNCTION__,__LINE__) +#define UDCDBGA(fmt,args...) serial_printf("[%s] %s:%d: " fmt "\n", __FILE__,__FUNCTION__,__LINE__, ##args) +#endif + +#if 1 +#define UDCREG(name) +#define UDCREGL(name) +#else /* The bugs still exists... */ +#define UDCREG(name) serial_printf("%s():%d: %s[%08x]=%.4x\n",__FUNCTION__,__LINE__, (#name), name, inw(name)) /* For 16-bit regs */ +#define UDCREGL(name) serial_printf("%s():%d: %s[%08x]=%.8x\n",__FUNCTION__,__LINE__, (#name), name, inl(name)) /* For 32-bit regs */ +#endif + + +static struct urb *ep0_urb = NULL; + +static struct usb_device_instance *udc_device; /* Used in interrupt handler */ +static u16 udc_devstat = 0; /* UDC status (DEVSTAT) */ +static u32 udc_interrupts = 0; + +static void udc_stall_ep (unsigned int ep_addr); + + +static struct usb_endpoint_instance *omap1510_find_ep (int ep) +{ + int i; + + for (i = 0; i < udc_device->bus->max_endpoints; i++) { + if (udc_device->bus->endpoint_array[i].endpoint_address == ep) + return &udc_device->bus->endpoint_array[i]; + } + return NULL; +} + +/* ************************************************************************** */ +/* IO + */ + +/* + * omap1510_prepare_endpoint_for_rx + * + * This function implements TRM Figure 14-11. + * + * The endpoint to prepare for transfer is specified as a physical endpoint + * number. For OUT (rx) endpoints 1 through 15, the corresponding endpoint + * configuration register is checked to see if the endpoint is ISO or not. + * If the OUT endpoint is valid and is non-ISO then its FIFO is enabled. + * No action is taken for endpoint 0 or for IN (tx) endpoints 16 through 30. + */ +static void omap1510_prepare_endpoint_for_rx (int ep_addr) +{ + int ep_num = ep_addr & USB_ENDPOINT_NUMBER_MASK; + + UDCDBGA ("omap1510_prepare_endpoint %x", ep_addr); + if (((ep_addr & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT)) { + if ((inw (UDC_EP_RX (ep_num)) & + (UDC_EPn_RX_Valid | UDC_EPn_RX_Iso)) == + UDC_EPn_RX_Valid) { + /* rx endpoint is valid, non-ISO, so enable its FIFO */ + outw (UDC_EP_Sel | ep_num, UDC_EP_NUM); + outw (UDC_Set_FIFO_En, UDC_CTRL); + outw (0, UDC_EP_NUM); + } + } +} + +/* omap1510_configure_endpoints + * + * This function implements TRM Figure 14-10. + */ +static void omap1510_configure_endpoints (struct usb_device_instance *device) +{ + int ep; + struct usb_bus_instance *bus; + struct usb_endpoint_instance *endpoint; + unsigned short ep_ptr; + unsigned short ep_size; + unsigned short ep_isoc; + unsigned short ep_doublebuffer; + int ep_addr; + int packet_size; + int buffer_size; + int attributes; + + bus = device->bus; + + /* There is a dedicated 2048 byte buffer for USB packets that may be + * arbitrarily partitioned among the endpoints on 8-byte boundaries. + * The first 8 bytes are reserved for receiving setup packets on + * endpoint 0. + */ + ep_ptr = 8; /* reserve the first 8 bytes for the setup fifo */ + + for (ep = 0; ep < bus->max_endpoints; ep++) { + endpoint = bus->endpoint_array + ep; + ep_addr = endpoint->endpoint_address; + if ((ep_addr & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN) { + /* IN endpoint */ + packet_size = endpoint->tx_packetSize; + attributes = endpoint->tx_attributes; + } else { + /* OUT endpoint */ + packet_size = endpoint->rcv_packetSize; + attributes = endpoint->rcv_attributes; + } + + switch (packet_size) { + case 0: + ep_size = 0; + break; + case 8: + ep_size = 0; + break; + case 16: + ep_size = 1; + break; + case 32: + ep_size = 2; + break; + case 64: + ep_size = 3; + break; + case 128: + ep_size = 4; + break; + case 256: + ep_size = 5; + break; + case 512: + ep_size = 6; + break; + default: + UDCDBGA ("ep 0x%02x has bad packet size %d", + ep_addr, packet_size); + packet_size = 0; + ep_size = 0; + break; + } + + switch (attributes & USB_ENDPOINT_XFERTYPE_MASK) { + case USB_ENDPOINT_XFER_CONTROL: + case USB_ENDPOINT_XFER_BULK: + case USB_ENDPOINT_XFER_INT: + default: + /* A non-isochronous endpoint may optionally be + * double-buffered. For now we disable + * double-buffering. + */ + ep_doublebuffer = 0; + ep_isoc = 0; + if (packet_size > 64) + packet_size = 0; + if (!ep || !ep_doublebuffer) + buffer_size = packet_size; + else + buffer_size = packet_size * 2; + break; + case USB_ENDPOINT_XFER_ISOC: + /* Isochronous endpoints are always double- + * buffered, but the double-buffering bit + * in the endpoint configuration register + * becomes the msb of the endpoint size so we + * set the double-buffering flag to zero. + */ + ep_doublebuffer = 0; + ep_isoc = 1; + buffer_size = packet_size * 2; + break; + } + + /* check to see if our packet buffer RAM is exhausted */ + if ((ep_ptr + buffer_size) > 2048) { + UDCDBGA ("out of packet RAM for ep 0x%02x buf size %d", ep_addr, buffer_size); + buffer_size = packet_size = 0; + } + + /* force a default configuration for endpoint 0 since it is + * always enabled + */ + if (!ep && ((packet_size < 8) || (packet_size > 64))) { + buffer_size = packet_size = 64; + ep_size = 3; + } + + if (!ep) { + /* configure endpoint 0 */ + outw ((ep_size << 12) | (ep_ptr >> 3), UDC_EP0); + /*UDCDBGA("ep 0 buffer offset 0x%03x packet size 0x%03x", */ + /* ep_ptr, packet_size); */ + } else if ((ep_addr & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN) { + /* IN endpoint */ + if (packet_size) { + outw ((1 << 15) | (ep_doublebuffer << 14) | + (ep_size << 12) | (ep_isoc << 11) | + (ep_ptr >> 3), + UDC_EP_TX (ep_addr & + USB_ENDPOINT_NUMBER_MASK)); + UDCDBGA ("IN ep %d buffer offset 0x%03x" + " packet size 0x%03x", + ep_addr & USB_ENDPOINT_NUMBER_MASK, + ep_ptr, packet_size); + } else { + outw (0, + UDC_EP_TX (ep_addr & + USB_ENDPOINT_NUMBER_MASK)); + } + } else { + /* OUT endpoint */ + if (packet_size) { + outw ((1 << 15) | (ep_doublebuffer << 14) | + (ep_size << 12) | (ep_isoc << 11) | + (ep_ptr >> 3), + UDC_EP_RX (ep_addr & + USB_ENDPOINT_NUMBER_MASK)); + UDCDBGA ("OUT ep %d buffer offset 0x%03x" + " packet size 0x%03x", + ep_addr & USB_ENDPOINT_NUMBER_MASK, + ep_ptr, packet_size); + } else { + outw (0, + UDC_EP_RX (ep_addr & + USB_ENDPOINT_NUMBER_MASK)); + } + } + ep_ptr += buffer_size; + } +} + +/* omap1510_deconfigure_device + * + * This function balances omap1510_configure_device. + */ +static void omap1510_deconfigure_device (void) +{ + int epnum; + + UDCDBG ("clear Cfg_Lock"); + outw (inw (UDC_SYSCON1) & ~UDC_Cfg_Lock, UDC_SYSCON1); + UDCREG (UDC_SYSCON1); + + /* deconfigure all endpoints */ + for (epnum = 1; epnum <= 15; epnum++) { + outw (0, UDC_EP_RX (epnum)); + outw (0, UDC_EP_TX (epnum)); + } +} + +/* omap1510_configure_device + * + * This function implements TRM Figure 14-9. + */ +static void omap1510_configure_device (struct usb_device_instance *device) +{ + omap1510_configure_endpoints (device); + + + /* Figure 14-9 indicates we should enable interrupts here, but we have + * other routines (udc_all_interrupts, udc_suspended_interrupts) to + * do that. + */ + + UDCDBG ("set Cfg_Lock"); + outw (inw (UDC_SYSCON1) | UDC_Cfg_Lock, UDC_SYSCON1); + UDCREG (UDC_SYSCON1); +} + +/* omap1510_write_noniso_tx_fifo + * + * This function implements TRM Figure 14-30. + * + * If the endpoint has an active tx_urb, then the next packet of data from the + * URB is written to the tx FIFO. The total amount of data in the urb is given + * by urb->actual_length. The maximum amount of data that can be sent in any + * one packet is given by endpoint->tx_packetSize. The number of data bytes + * from this URB that have already been transmitted is given by endpoint->sent. + * endpoint->last is updated by this routine with the number of data bytes + * transmitted in this packet. + * + * In accordance with Figure 14-30, the EP_NUM register must already have been + * written with the value to select the appropriate tx FIFO before this routine + * is called. + */ +static void omap1510_write_noniso_tx_fifo (struct usb_endpoint_instance + *endpoint) +{ + struct urb *urb = endpoint->tx_urb; + + if (urb) { + unsigned int last, i; + + UDCDBGA ("urb->buffer %p, buffer_length %d, actual_length %d", + urb->buffer, urb->buffer_length, urb->actual_length); + if ((last = + MIN (urb->actual_length - endpoint->sent, + endpoint->tx_packetSize))) { + u8 *cp = urb->buffer + endpoint->sent; + + UDCDBGA ("endpoint->sent %d, tx_packetSize %d, last %d", endpoint->sent, endpoint->tx_packetSize, last); + + if (((u32) cp & 1) == 0) { /* word aligned? */ + outsw (UDC_DATA, cp, last >> 1); + } else { /* byte aligned. */ + for (i = 0; i < (last >> 1); i++) { + u16 w = ((u16) cp[2 * i + 1] << 8) | + (u16) cp[2 * i]; + outw (w, UDC_DATA); + } + } + if (last & 1) { + outb (*(cp + last - 1), UDC_DATA); + } + } + endpoint->last = last; + } +} + +/* omap1510_read_noniso_rx_fifo + * + * This function implements TRM Figure 14-28. + * + * If the endpoint has an active rcv_urb, then the next packet of data is read + * from the rcv FIFO and written to rcv_urb->buffer at offset + * rcv_urb->actual_length to append the packet data to the data from any + * previous packets for this transfer. We assume that there is sufficient room + * left in the buffer to hold an entire packet of data. + * + * The return value is the number of bytes read from the FIFO for this packet. + * + * In accordance with Figure 14-28, the EP_NUM register must already have been + * written with the value to select the appropriate rcv FIFO before this routine + * is called. + */ +static int omap1510_read_noniso_rx_fifo (struct usb_endpoint_instance + *endpoint) +{ + struct urb *urb = endpoint->rcv_urb; + int len = 0; + + if (urb) { + len = inw (UDC_RXFSTAT); + + if (len) { + unsigned char *cp = urb->buffer + urb->actual_length; + + insw (UDC_DATA, cp, len >> 1); + if (len & 1) + *(cp + len - 1) = inb (UDC_DATA); + } + } + return len; +} + +/* omap1510_prepare_for_control_write_status + * + * This function implements TRM Figure 14-17. + * + * We have to deal here with non-autodecoded control writes that haven't already + * been dealt with by ep0_recv_setup. The non-autodecoded standard control + * write requests are: set/clear endpoint feature, set configuration, set + * interface, and set descriptor. ep0_recv_setup handles set/clear requests for + * ENDPOINT_HALT by halting the endpoint for a set request and resetting the + * endpoint for a clear request. ep0_recv_setup returns an error for + * SET_DESCRIPTOR requests which causes them to be terminated with a stall by + * the setup handler. A SET_INTERFACE request is handled by ep0_recv_setup by + * generating a DEVICE_SET_INTERFACE event. This leaves only the + * SET_CONFIGURATION event for us to deal with here. + * + */ +static void omap1510_prepare_for_control_write_status (struct urb *urb) +{ + struct usb_device_request *request = &urb->device_request;; + + /* check for a SET_CONFIGURATION request */ + if (request->bRequest == USB_REQ_SET_CONFIGURATION) { + int configuration = le16_to_cpu (request->wValue) & 0xff; + unsigned short devstat = inw (UDC_DEVSTAT); + + if ((devstat & (UDC_ADD | UDC_CFG)) == UDC_ADD) { + /* device is currently in ADDRESSED state */ + if (configuration) { + /* Assume the specified non-zero configuration + * value is valid and switch to the CONFIGURED + * state. + */ + outw (UDC_Dev_Cfg, UDC_SYSCON2); + } + } else if ((devstat & UDC_CFG) == UDC_CFG) { + /* device is currently in CONFIGURED state */ + if (!configuration) { + /* Switch to ADDRESSED state. */ + outw (UDC_Clr_Cfg, UDC_SYSCON2); + } + } + } + + /* select EP0 tx FIFO */ + outw (UDC_EP_Dir | UDC_EP_Sel, UDC_EP_NUM); + /* clear endpoint (no data bytes in status stage) */ + outw (UDC_Clr_EP, UDC_CTRL); + /* enable the EP0 tx FIFO */ + outw (UDC_Set_FIFO_En, UDC_CTRL); + /* deselect the endpoint */ + outw (UDC_EP_Dir, UDC_EP_NUM); +} + +/* udc_state_transition_up + * udc_state_transition_down + * + * Helper functions to implement device state changes. The device states and + * the events that transition between them are: + * + * STATE_ATTACHED + * || /\ + * \/ || + * DEVICE_HUB_CONFIGURED DEVICE_HUB_RESET + * || /\ + * \/ || + * STATE_POWERED + * || /\ + * \/ || + * DEVICE_RESET DEVICE_POWER_INTERRUPTION + * || /\ + * \/ || + * STATE_DEFAULT + * || /\ + * \/ || + * DEVICE_ADDRESS_ASSIGNED DEVICE_RESET + * || /\ + * \/ || + * STATE_ADDRESSED + * || /\ + * \/ || + * DEVICE_CONFIGURED DEVICE_DE_CONFIGURED + * || /\ + * \/ || + * STATE_CONFIGURED + * + * udc_state_transition_up transitions up (in the direction from STATE_ATTACHED + * to STATE_CONFIGURED) from the specified initial state to the specified final + * state, passing through each intermediate state on the way. If the initial + * state is at or above (i.e. nearer to STATE_CONFIGURED) the final state, then + * no state transitions will take place. + * + * udc_state_transition_down transitions down (in the direction from + * STATE_CONFIGURED to STATE_ATTACHED) from the specified initial state to the + * specified final state, passing through each intermediate state on the way. + * If the initial state is at or below (i.e. nearer to STATE_ATTACHED) the final + * state, then no state transitions will take place. + * + * These functions must only be called with interrupts disabled. + */ +static void udc_state_transition_up (usb_device_state_t initial, + usb_device_state_t final) +{ + if (initial < final) { + switch (initial) { + case STATE_ATTACHED: + usbd_device_event_irq (udc_device, + DEVICE_HUB_CONFIGURED, 0); + if (final == STATE_POWERED) + break; + case STATE_POWERED: + usbd_device_event_irq (udc_device, DEVICE_RESET, 0); + if (final == STATE_DEFAULT) + break; + case STATE_DEFAULT: + usbd_device_event_irq (udc_device, + DEVICE_ADDRESS_ASSIGNED, 0); + if (final == STATE_ADDRESSED) + break; + case STATE_ADDRESSED: + usbd_device_event_irq (udc_device, DEVICE_CONFIGURED, + 0); + case STATE_CONFIGURED: + break; + default: + break; + } + } +} + +static void udc_state_transition_down (usb_device_state_t initial, + usb_device_state_t final) +{ + if (initial > final) { + switch (initial) { + case STATE_CONFIGURED: + usbd_device_event_irq (udc_device, DEVICE_DE_CONFIGURED, 0); + if (final == STATE_ADDRESSED) + break; + case STATE_ADDRESSED: + usbd_device_event_irq (udc_device, DEVICE_RESET, 0); + if (final == STATE_DEFAULT) + break; + case STATE_DEFAULT: + usbd_device_event_irq (udc_device, DEVICE_POWER_INTERRUPTION, 0); + if (final == STATE_POWERED) + break; + case STATE_POWERED: + usbd_device_event_irq (udc_device, DEVICE_HUB_RESET, 0); + case STATE_ATTACHED: + break; + default: + break; + } + } +} + +/* Handle all device state changes. + * This function implements TRM Figure 14-21. + */ +static void omap1510_udc_state_changed (void) +{ + u16 bits; + u16 devstat = inw (UDC_DEVSTAT); + + UDCDBGA ("state changed, devstat %x, old %x", devstat, udc_devstat); + + bits = devstat ^ udc_devstat; + if (bits) { + if (bits & UDC_ATT) { + if (devstat & UDC_ATT) { + UDCDBG ("device attached and powered"); + udc_state_transition_up (udc_device->device_state, STATE_POWERED); + } else { + UDCDBG ("device detached or unpowered"); + udc_state_transition_down (udc_device->device_state, STATE_ATTACHED); + } + } + if (bits & UDC_USB_Reset) { + if (devstat & UDC_USB_Reset) { + UDCDBG ("device reset in progess"); + udc_state_transition_down (udc_device->device_state, STATE_POWERED); + } else { + UDCDBG ("device reset completed"); + } + } + if (bits & UDC_DEF) { + if (devstat & UDC_DEF) { + UDCDBG ("device entering default state"); + udc_state_transition_up (udc_device->device_state, STATE_DEFAULT); + } else { + UDCDBG ("device leaving default state"); + udc_state_transition_down (udc_device->device_state, STATE_POWERED); + } + } + if (bits & UDC_SUS) { + if (devstat & UDC_SUS) { + UDCDBG ("entering suspended state"); + usbd_device_event_irq (udc_device, DEVICE_BUS_INACTIVE, 0); + } else { + UDCDBG ("leaving suspended state"); + usbd_device_event_irq (udc_device, DEVICE_BUS_ACTIVITY, 0); + } + } + if (bits & UDC_R_WK_OK) { + UDCDBGA ("remote wakeup %s", (devstat & UDC_R_WK_OK) + ? "enabled" : "disabled"); + } + if (bits & UDC_ADD) { + if (devstat & UDC_ADD) { + UDCDBG ("default -> addressed"); + udc_state_transition_up (udc_device->device_state, STATE_ADDRESSED); + } else { + UDCDBG ("addressed -> default"); + udc_state_transition_down (udc_device->device_state, STATE_DEFAULT); + } + } + if (bits & UDC_CFG) { + if (devstat & UDC_CFG) { + UDCDBG ("device configured"); + /* The ep0_recv_setup function generates the + * DEVICE_CONFIGURED event when a + * USB_REQ_SET_CONFIGURATION setup packet is + * received, so we should already be in the + * state STATE_CONFIGURED. + */ + udc_state_transition_up (udc_device->device_state, STATE_CONFIGURED); + } else { + UDCDBG ("device deconfigured"); + udc_state_transition_down (udc_device->device_state, STATE_ADDRESSED); + } + } + } + + /* Clear interrupt source */ + outw (UDC_DS_Chg, UDC_IRQ_SRC); + + /* Save current DEVSTAT */ + udc_devstat = devstat; +} + +/* Handle SETUP USB interrupt. + * This function implements TRM Figure 14-14. + */ +static void omap1510_udc_setup (struct usb_endpoint_instance *endpoint) +{ + UDCDBG ("-> Entering device setup"); + + do { + const int setup_pktsize = 8; + unsigned char *datap = + (unsigned char *) &ep0_urb->device_request; + + /* Gain access to EP 0 setup FIFO */ + outw (UDC_Setup_Sel, UDC_EP_NUM); + + /* Read control request data */ + insb (UDC_DATA, datap, setup_pktsize); + + UDCDBGA ("EP0 setup read [%x %x %x %x %x %x %x %x]", + *(datap + 0), *(datap + 1), *(datap + 2), + *(datap + 3), *(datap + 4), *(datap + 5), + *(datap + 6), *(datap + 7)); + + /* Reset EP0 setup FIFO */ + outw (0, UDC_EP_NUM); + } while (inw (UDC_IRQ_SRC) & UDC_Setup); + + /* Try to process setup packet */ + if (ep0_recv_setup (ep0_urb)) { + /* Not a setup packet, stall next EP0 transaction */ + udc_stall_ep (0); + UDCDBG ("can't parse setup packet, still waiting for setup"); + return; + } + + /* Check direction */ + if ((ep0_urb->device_request.bmRequestType & USB_REQ_DIRECTION_MASK) + == USB_REQ_HOST2DEVICE) { + UDCDBG ("control write on EP0"); + if (le16_to_cpu (ep0_urb->device_request.wLength)) { + /* We don't support control write data stages. + * The only standard control write request with a data + * stage is SET_DESCRIPTOR, and ep0_recv_setup doesn't + * support that so we just stall those requests. A + * function driver might support a non-standard + * write request with a data stage, but it isn't + * obvious what we would do with the data if we read it + * so we'll just stall it. It seems like the API isn't + * quite right here. + */ +#if 0 + /* Here is what we would do if we did support control + * write data stages. + */ + ep0_urb->actual_length = 0; + outw (0, UDC_EP_NUM); + /* enable the EP0 rx FIFO */ + outw (UDC_Set_FIFO_En, UDC_CTRL); +#else + /* Stall this request */ + UDCDBG ("Stalling unsupported EP0 control write data " + "stage."); + udc_stall_ep (0); +#endif + } else { + omap1510_prepare_for_control_write_status (ep0_urb); + } + } else { + UDCDBG ("control read on EP0"); + /* The ep0_recv_setup function has already placed our response + * packet data in ep0_urb->buffer and the packet length in + * ep0_urb->actual_length. + */ + endpoint->tx_urb = ep0_urb; + endpoint->sent = 0; + /* select the EP0 tx FIFO */ + outw (UDC_EP_Dir | UDC_EP_Sel, UDC_EP_NUM); + /* Write packet data to the FIFO. omap1510_write_noniso_tx_fifo + * will update endpoint->last with the number of bytes written + * to the FIFO. + */ + omap1510_write_noniso_tx_fifo (endpoint); + /* enable the FIFO to start the packet transmission */ + outw (UDC_Set_FIFO_En, UDC_CTRL); + /* deselect the EP0 tx FIFO */ + outw (UDC_EP_Dir, UDC_EP_NUM); + } + + UDCDBG ("<- Leaving device setup"); +} + +/* Handle endpoint 0 RX interrupt + * This routine implements TRM Figure 14-16. + */ +static void omap1510_udc_ep0_rx (struct usb_endpoint_instance *endpoint) +{ + unsigned short status; + + UDCDBG ("RX on EP0"); + /* select EP0 rx FIFO */ + outw (UDC_EP_Sel, UDC_EP_NUM); + + status = inw (UDC_STAT_FLG); + + if (status & UDC_ACK) { + /* Check direction */ + if ((ep0_urb->device_request.bmRequestType + & USB_REQ_DIRECTION_MASK) == USB_REQ_HOST2DEVICE) { + /* This rx interrupt must be for a control write data + * stage packet. + * + * We don't support control write data stages. + * We should never end up here. + */ + + /* clear the EP0 rx FIFO */ + outw (UDC_Clr_EP, UDC_CTRL); + + /* deselect the EP0 rx FIFO */ + outw (0, UDC_EP_NUM); + + UDCDBG ("Stalling unexpected EP0 control write " + "data stage packet"); + udc_stall_ep (0); + } else { + /* This rx interrupt must be for a control read status + * stage packet. + */ + UDCDBG ("ACK on EP0 control read status stage packet"); + /* deselect EP0 rx FIFO */ + outw (0, UDC_EP_NUM); + } + } else if (status & UDC_STALL) { + UDCDBG ("EP0 stall during RX"); + /* deselect EP0 rx FIFO */ + outw (0, UDC_EP_NUM); + } else { + /* deselect EP0 rx FIFO */ + outw (0, UDC_EP_NUM); + } +} + +/* Handle endpoint 0 TX interrupt + * This routine implements TRM Figure 14-18. + */ +static void omap1510_udc_ep0_tx (struct usb_endpoint_instance *endpoint) +{ + unsigned short status; + struct usb_device_request *request = &ep0_urb->device_request; + + UDCDBG ("TX on EP0"); + /* select EP0 TX FIFO */ + outw (UDC_EP_Dir | UDC_EP_Sel, UDC_EP_NUM); + + status = inw (UDC_STAT_FLG); + if (status & UDC_ACK) { + /* Check direction */ + if ((request->bmRequestType & USB_REQ_DIRECTION_MASK) == + USB_REQ_HOST2DEVICE) { + /* This tx interrupt must be for a control write status + * stage packet. + */ + UDCDBG ("ACK on EP0 control write status stage packet"); + /* deselect EP0 TX FIFO */ + outw (UDC_EP_Dir, UDC_EP_NUM); + } else { + /* This tx interrupt must be for a control read data + * stage packet. + */ + int wLength = le16_to_cpu (request->wLength); + + /* Update our count of bytes sent so far in this + * transfer. + */ + endpoint->sent += endpoint->last; + + /* We are finished with this transfer if we have sent + * all of the bytes in our tx urb (urb->actual_length) + * unless we need a zero-length terminating packet. We + * need a zero-length terminating packet if we returned + * fewer bytes than were requested (wLength) by the host, + * and the number of bytes we returned is an exact + * multiple of the packet size endpoint->tx_packetSize. + */ + if ((endpoint->sent == ep0_urb->actual_length) + && ((ep0_urb->actual_length == wLength) + || (endpoint->last != + endpoint->tx_packetSize))) { + /* Done with control read data stage. */ + UDCDBG ("control read data stage complete"); + /* deselect EP0 TX FIFO */ + outw (UDC_EP_Dir, UDC_EP_NUM); + /* select EP0 RX FIFO to prepare for control + * read status stage. + */ + outw (UDC_EP_Sel, UDC_EP_NUM); + /* clear the EP0 RX FIFO */ + outw (UDC_Clr_EP, UDC_CTRL); + /* enable the EP0 RX FIFO */ + outw (UDC_Set_FIFO_En, UDC_CTRL); + /* deselect the EP0 RX FIFO */ + outw (0, UDC_EP_NUM); + } else { + /* We still have another packet of data to send + * in this control read data stage or else we + * need a zero-length terminating packet. + */ + UDCDBG ("ACK control read data stage packet"); + omap1510_write_noniso_tx_fifo (endpoint); + /* enable the EP0 tx FIFO to start transmission */ + outw (UDC_Set_FIFO_En, UDC_CTRL); + /* deselect EP0 TX FIFO */ + outw (UDC_EP_Dir, UDC_EP_NUM); + } + } + } else if (status & UDC_STALL) { + UDCDBG ("EP0 stall during TX"); + /* deselect EP0 TX FIFO */ + outw (UDC_EP_Dir, UDC_EP_NUM); + } else { + /* deselect EP0 TX FIFO */ + outw (UDC_EP_Dir, UDC_EP_NUM); + } +} + +/* Handle RX transaction on non-ISO endpoint. + * This function implements TRM Figure 14-27. + * The ep argument is a physical endpoint number for a non-ISO OUT endpoint + * in the range 1 to 15. + */ +static void omap1510_udc_epn_rx (int ep) +{ + unsigned short status; + + /* Check endpoint status */ + status = inw (UDC_STAT_FLG); + + if (status & UDC_ACK) { + int nbytes; + struct usb_endpoint_instance *endpoint = + omap1510_find_ep (ep); + + nbytes = omap1510_read_noniso_rx_fifo (endpoint); + usbd_rcv_complete (endpoint, nbytes, 0); + + /* enable rx FIFO to prepare for next packet */ + outw (UDC_Set_FIFO_En, UDC_CTRL); + } else if (status & UDC_STALL) { + UDCDBGA ("STALL on RX endpoint %d", ep); + } else if (status & UDC_NAK) { + UDCDBGA ("NAK on RX ep %d", ep); + } else { + serial_printf ("omap-bi: RX on ep %d with status %x", ep, + status); + } +} + +/* Handle TX transaction on non-ISO endpoint. + * This function implements TRM Figure 14-29. + * The ep argument is a physical endpoint number for a non-ISO IN endpoint + * in the range 16 to 30. + */ +static void omap1510_udc_epn_tx (int ep) +{ + unsigned short status; + + /*serial_printf("omap1510_udc_epn_tx( %x )\n",ep); */ + + /* Check endpoint status */ + status = inw (UDC_STAT_FLG); + + if (status & UDC_ACK) { + struct usb_endpoint_instance *endpoint = + omap1510_find_ep (ep); + + /* We need to transmit a terminating zero-length packet now if + * we have sent all of the data in this URB and the transfer + * size was an exact multiple of the packet size. + */ + if (endpoint->tx_urb + && (endpoint->last == endpoint->tx_packetSize) + && (endpoint->tx_urb->actual_length - endpoint->sent - + endpoint->last == 0)) { + /* Prepare to transmit a zero-length packet. */ + endpoint->sent += endpoint->last; + /* write 0 bytes of data to FIFO */ + omap1510_write_noniso_tx_fifo (endpoint); + /* enable tx FIFO to start transmission */ + outw (UDC_Set_FIFO_En, UDC_CTRL); + } else if (endpoint->tx_urb + && endpoint->tx_urb->actual_length) { + /* retire the data that was just sent */ + usbd_tx_complete (endpoint); + /* Check to see if we have more data ready to transmit + * now. + */ + if (endpoint->tx_urb + && endpoint->tx_urb->actual_length) { + /* write data to FIFO */ + omap1510_write_noniso_tx_fifo (endpoint); + /* enable tx FIFO to start transmission */ + outw (UDC_Set_FIFO_En, UDC_CTRL); + } + } + } else if (status & UDC_STALL) { + UDCDBGA ("STALL on TX endpoint %d", ep); + } else if (status & UDC_NAK) { + UDCDBGA ("NAK on TX endpoint %d", ep); + } else { + /*serial_printf("omap-bi: TX on ep %d with status %x\n", ep, status); */ + } +} + + +/* +------------------------------------------------------------------------------- +*/ + +/* Handle general USB interrupts and dispatch according to type. + * This function implements TRM Figure 14-13. + */ +void omap1510_udc_irq (void) +{ + u16 irq_src = inw (UDC_IRQ_SRC); + int valid_irq = 0; + + if (!(irq_src & ~UDC_SOF_Flg)) /* ignore SOF interrupts ) */ + return; + + UDCDBGA ("< IRQ #%d start >- %x", udc_interrupts, irq_src); + /*serial_printf("< IRQ #%d start >- %x\n", udc_interrupts, irq_src); */ + + if (irq_src & UDC_DS_Chg) { + /* Device status changed */ + omap1510_udc_state_changed (); + valid_irq++; + } + if (irq_src & UDC_EP0_RX) { + /* Endpoint 0 receive */ + outw (UDC_EP0_RX, UDC_IRQ_SRC); /* ack interrupt */ + omap1510_udc_ep0_rx (udc_device->bus->endpoint_array + 0); + valid_irq++; + } + if (irq_src & UDC_EP0_TX) { + /* Endpoint 0 transmit */ + outw (UDC_EP0_TX, UDC_IRQ_SRC); /* ack interrupt */ + omap1510_udc_ep0_tx (udc_device->bus->endpoint_array + 0); + valid_irq++; + } + if (irq_src & UDC_Setup) { + /* Device setup */ + omap1510_udc_setup (udc_device->bus->endpoint_array + 0); + valid_irq++; + } + /*if (!valid_irq) */ + /* serial_printf("unknown interrupt, IRQ_SRC %.4x\n", irq_src); */ + UDCDBGA ("< IRQ #%d end >", udc_interrupts); + udc_interrupts++; +} + +/* This function implements TRM Figure 14-26. */ +void omap1510_udc_noniso_irq (void) +{ + unsigned short epnum; + unsigned short irq_src = inw (UDC_IRQ_SRC); + int valid_irq = 0; + + if (!(irq_src & (UDC_EPn_RX | UDC_EPn_TX))) + return; + + UDCDBGA ("non-ISO IRQ, IRQ_SRC %x", inw (UDC_IRQ_SRC)); + + if (irq_src & UDC_EPn_RX) { /* Endpoint N OUT transaction */ + /* Determine the endpoint number for this interrupt */ + epnum = (inw (UDC_EPN_STAT) & 0x0f00) >> 8; + UDCDBGA ("RX on ep %x", epnum); + + /* acknowledge interrupt */ + outw (UDC_EPn_RX, UDC_IRQ_SRC); + + if (epnum) { + /* select the endpoint FIFO */ + outw (UDC_EP_Sel | epnum, UDC_EP_NUM); + + omap1510_udc_epn_rx (epnum); + + /* deselect the endpoint FIFO */ + outw (epnum, UDC_EP_NUM); + } + valid_irq++; + } + if (irq_src & UDC_EPn_TX) { /* Endpoint N IN transaction */ + /* Determine the endpoint number for this interrupt */ + epnum = (inw (UDC_EPN_STAT) & 0x000f) | USB_DIR_IN; + UDCDBGA ("TX on ep %x", epnum); + + /* acknowledge interrupt */ + outw (UDC_EPn_TX, UDC_IRQ_SRC); + + if (epnum) { + /* select the endpoint FIFO */ + outw (UDC_EP_Sel | UDC_EP_Dir | epnum, UDC_EP_NUM); + + omap1510_udc_epn_tx (epnum); + + /* deselect the endpoint FIFO */ + outw (UDC_EP_Dir | epnum, UDC_EP_NUM); + } + valid_irq++; + } + if (!valid_irq) + serial_printf (": unknown non-ISO interrupt, IRQ_SRC %.4x\n", + irq_src); +} + +/* +------------------------------------------------------------------------------- +*/ + + +/* + * Start of public functions. + */ + +/* Called to start packet transmission. */ +int udc_endpoint_write (struct usb_endpoint_instance *endpoint) +{ + unsigned short epnum = + endpoint->endpoint_address & USB_ENDPOINT_NUMBER_MASK; + + UDCDBGA ("Starting transmit on ep %x", epnum); + + if (endpoint->tx_urb) { + /* select the endpoint FIFO */ + outw (UDC_EP_Sel | UDC_EP_Dir | epnum, UDC_EP_NUM); + /* write data to FIFO */ + omap1510_write_noniso_tx_fifo (endpoint); + /* enable tx FIFO to start transmission */ + outw (UDC_Set_FIFO_En, UDC_CTRL); + /* deselect the endpoint FIFO */ + outw (UDC_EP_Dir | epnum, UDC_EP_NUM); + } + + return 0; +} + +/* Start to initialize h/w stuff */ +int udc_init (void) +{ + u16 udc_rev; + uchar value; + ulong gpio; + int i; + + /* Let the device settle down before we start */ + for (i = 0; i < UDC_INIT_MDELAY; i++) udelay(1000); + + udc_device = NULL; + + UDCDBG ("starting"); + + /* Check peripheral reset. Must be 1 to make sure + MPU TIPB peripheral reset is inactive */ + UDCREG (ARM_RSTCT2); + + /* Set and check clock control. + * We might ought to be using the clock control API to do + * this instead of fiddling with the clock registers directly + * here. + */ + outw ((1 << 4) | (1 << 5), CLOCK_CTRL); + UDCREG (CLOCK_CTRL); + +#ifdef CONFIG_OMAP1510 + /* This code was originally implemented for OMAP1510 and + * therefore is only applicable for OMAP1510 boards. For + * OMAP5912 or OMAP16xx the register APLL_CTRL does not + * exist and DPLL_CTRL is already configured. + */ + + /* Set and check APLL */ + outw (0x0008, APLL_CTRL); + UDCREG (APLL_CTRL); + /* Set and check DPLL */ + outw (0x2210, DPLL_CTRL); + UDCREG (DPLL_CTRL); +#endif + /* Set and check SOFT + * The below line of code has been changed to perform a + * read-modify-write instead of a simple write for + * configuring the SOFT_REQ register. This allows the code + * to be compatible with OMAP5912 and OMAP16xx devices + */ + outw ((1 << 4) | (1 << 3) | 1 | (inw(SOFT_REQ)), SOFT_REQ); + + /* Short delay to wait for DPLL */ + udelay (1000); + + /* Print banner with device revision */ + udc_rev = inw (UDC_REV) & 0xff; +#ifdef CONFIG_OMAP1510 + printf ("USB: TI OMAP1510 USB function module rev %d.%d\n", + udc_rev >> 4, udc_rev & 0xf); +#endif + +#ifdef CONFIG_OMAP1610 + printf ("USB: TI OMAP5912 USB function module rev %d.%d\n", + udc_rev >> 4, udc_rev & 0xf); +#endif + +#ifdef CONFIG_OMAP_SX1 + i2c_read (0x32, 0x04, 1, &value, 1); + value |= 0x04; + i2c_write (0x32, 0x04, 1, &value, 1); + + i2c_read (0x32, 0x03, 1, &value, 1); + value |= 0x01; + i2c_write (0x32, 0x03, 1, &value, 1); + + gpio = inl(GPIO_PIN_CONTROL_REG); + gpio |= 0x0002; /* A_IRDA_OFF */ + gpio |= 0x0800; /* A_SWITCH */ + gpio |= 0x8000; /* A_USB_ON */ + outl (gpio, GPIO_PIN_CONTROL_REG); + + gpio = inl(GPIO_DIR_CONTROL_REG); + gpio &= ~0x0002; /* A_IRDA_OFF */ + gpio &= ~0x0800; /* A_SWITCH */ + gpio &= ~0x8000; /* A_USB_ON */ + outl (gpio, GPIO_DIR_CONTROL_REG); + + gpio = inl(GPIO_DATA_OUTPUT_REG); + gpio |= 0x0002; /* A_IRDA_OFF */ + gpio &= ~0x0800; /* A_SWITCH */ + gpio &= ~0x8000; /* A_USB_ON */ + outl (gpio, GPIO_DATA_OUTPUT_REG); +#endif + + /* The VBUS_MODE bit selects whether VBUS detection is done via + * software (1) or hardware (0). When software detection is + * selected, VBUS_CTRL selects whether USB is not connected (0) + * or connected (1). + */ + outl (inl (FUNC_MUX_CTRL_0) | UDC_VBUS_MODE, FUNC_MUX_CTRL_0); + outl (inl (FUNC_MUX_CTRL_0) & ~UDC_VBUS_CTRL, FUNC_MUX_CTRL_0); + UDCREGL (FUNC_MUX_CTRL_0); + + /* + * At this point, device is ready for configuration... + */ + + UDCDBG ("disable USB interrupts"); + outw (0, UDC_IRQ_EN); + UDCREG (UDC_IRQ_EN); + + UDCDBG ("disable USB DMA"); + outw (0, UDC_DMA_IRQ_EN); + UDCREG (UDC_DMA_IRQ_EN); + + UDCDBG ("initialize SYSCON1"); + outw (UDC_Self_Pwr | UDC_Pullup_En, UDC_SYSCON1); + UDCREG (UDC_SYSCON1); + + return 0; +} + +/* Stall endpoint */ +static void udc_stall_ep (unsigned int ep_addr) +{ + /*int ep_addr = PHYS_EP_TO_EP_ADDR(ep); */ + int ep_num = ep_addr & USB_ENDPOINT_NUMBER_MASK; + + UDCDBGA ("stall ep_addr %d", ep_addr); + + /* REVISIT? + * The OMAP TRM section 14.2.4.2 says we must check that the FIFO + * is empty before halting the endpoint. The current implementation + * doesn't check that the FIFO is empty. + */ + + if (!ep_num) { + outw (UDC_Stall_Cmd, UDC_SYSCON2); + } else if ((ep_addr & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT) { + if (inw (UDC_EP_RX (ep_num)) & UDC_EPn_RX_Valid) { + /* we have a valid rx endpoint, so halt it */ + outw (UDC_EP_Sel | ep_num, UDC_EP_NUM); + outw (UDC_Set_Halt, UDC_CTRL); + outw (ep_num, UDC_EP_NUM); + } + } else { + if (inw (UDC_EP_TX (ep_num)) & UDC_EPn_TX_Valid) { + /* we have a valid tx endpoint, so halt it */ + outw (UDC_EP_Sel | UDC_EP_Dir | ep_num, UDC_EP_NUM); + outw (UDC_Set_Halt, UDC_CTRL); + outw (ep_num, UDC_EP_NUM); + } + } +} + +/* Reset endpoint */ +#if 0 +static void udc_reset_ep (unsigned int ep_addr) +{ + /*int ep_addr = PHYS_EP_TO_EP_ADDR(ep); */ + int ep_num = ep_addr & USB_ENDPOINT_NUMBER_MASK; + + UDCDBGA ("reset ep_addr %d", ep_addr); + + if (!ep_num) { + /* control endpoint 0 can't be reset */ + } else if ((ep_addr & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT) { + UDCDBGA ("UDC_EP_RX(%d) = 0x%04x", ep_num, + inw (UDC_EP_RX (ep_num))); + if (inw (UDC_EP_RX (ep_num)) & UDC_EPn_RX_Valid) { + /* we have a valid rx endpoint, so reset it */ + outw (ep_num | UDC_EP_Sel, UDC_EP_NUM); + outw (UDC_Reset_EP, UDC_CTRL); + outw (ep_num, UDC_EP_NUM); + UDCDBGA ("OUT endpoint %d reset", ep_num); + } + } else { + UDCDBGA ("UDC_EP_TX(%d) = 0x%04x", ep_num, + inw (UDC_EP_TX (ep_num))); + /* Resetting of tx endpoints seems to be causing the USB function + * module to fail, which causes problems when the driver is + * uninstalled. We'll skip resetting tx endpoints for now until + * we figure out what the problem is. + */ +#if 0 + if (inw (UDC_EP_TX (ep_num)) & UDC_EPn_TX_Valid) { + /* we have a valid tx endpoint, so reset it */ + outw (ep_num | UDC_EP_Dir | UDC_EP_Sel, UDC_EP_NUM); + outw (UDC_Reset_EP, UDC_CTRL); + outw (ep_num | UDC_EP_Dir, UDC_EP_NUM); + UDCDBGA ("IN endpoint %d reset", ep_num); + } +#endif + } +} +#endif + +/* ************************************************************************** */ + +/** + * udc_check_ep - check logical endpoint + * + * Return physical endpoint number to use for this logical endpoint or zero if not valid. + */ +#if 0 +int udc_check_ep (int logical_endpoint, int packetsize) +{ + if ((logical_endpoint == 0x80) || + ((logical_endpoint & 0x8f) != logical_endpoint)) { + return 0; + } + + switch (packetsize) { + case 8: + case 16: + case 32: + case 64: + case 128: + case 256: + case 512: + break; + default: + return 0; + } + + return EP_ADDR_TO_PHYS_EP (logical_endpoint); +} +#endif + +/* + * udc_setup_ep - setup endpoint + * + * Associate a physical endpoint with endpoint_instance + */ +void udc_setup_ep (struct usb_device_instance *device, + unsigned int ep, struct usb_endpoint_instance *endpoint) +{ + UDCDBGA ("setting up endpoint addr %x", endpoint->endpoint_address); + + /* This routine gets called by bi_modinit for endpoint 0 and from + * bi_config for all of the other endpoints. bi_config gets called + * during the DEVICE_CREATE, DEVICE_CONFIGURED, and + * DEVICE_SET_INTERFACE events. We need to reconfigure the OMAP packet + * RAM after bi_config scans the selected device configuration and + * initializes the endpoint structures, but before this routine enables + * the OUT endpoint FIFOs. Since bi_config calls this routine in a + * loop for endpoints 1 through UDC_MAX_ENDPOINTS, we reconfigure our + * packet RAM here when ep==1. + * I really hate to do this here, but it seems like the API exported + * by the USB bus interface controller driver to the usbd-bi module + * isn't quite right so there is no good place to do this. + */ + if (ep == 1) { + omap1510_deconfigure_device (); + omap1510_configure_device (device); + } + + if (endpoint && (ep < UDC_MAX_ENDPOINTS)) { + int ep_addr = endpoint->endpoint_address; + + if (!ep_addr) { + /* nothing to do for endpoint 0 */ + } else if ((ep_addr & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN) { + /* nothing to do for IN (tx) endpoints */ + } else { /* OUT (rx) endpoint */ + if (endpoint->rcv_packetSize) { + /*struct urb* urb = &(urb_out_array[ep&0xFF]); */ + /*urb->endpoint = endpoint; */ + /*urb->device = device; */ + /*urb->buffer_length = sizeof(urb->buffer); */ + + /*endpoint->rcv_urb = urb; */ + omap1510_prepare_endpoint_for_rx (ep_addr); + } + } + } +} + +/** + * udc_disable_ep - disable endpoint + * @ep: + * + * Disable specified endpoint + */ +#if 0 +void udc_disable_ep (unsigned int ep_addr) +{ + /*int ep_addr = PHYS_EP_TO_EP_ADDR(ep); */ + int ep_num = ep_addr & USB_ENDPOINT_NUMBER_MASK; + struct usb_endpoint_instance *endpoint = omap1510_find_ep (ep_addr); /*udc_device->bus->endpoint_array + ep; */ + + UDCDBGA ("disable ep_addr %d", ep_addr); + + if (!ep_num) { + /* nothing to do for endpoint 0 */ ; + } else if ((ep_addr & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN) { + if (endpoint->tx_packetSize) { + /* we have a valid tx endpoint */ + /*usbd_flush_tx(endpoint); */ + endpoint->tx_urb = NULL; + } + } else { + if (endpoint->rcv_packetSize) { + /* we have a valid rx endpoint */ + /*usbd_flush_rcv(endpoint); */ + endpoint->rcv_urb = NULL; + } + } +} +#endif + +/* ************************************************************************** */ + +/** + * udc_connected - is the USB cable connected + * + * Return non-zero if cable is connected. + */ +#if 0 +int udc_connected (void) +{ + return ((inw (UDC_DEVSTAT) & UDC_ATT) == UDC_ATT); +} +#endif + +/* Turn on the USB connection by enabling the pullup resistor */ +void udc_connect (void) +{ + UDCDBG ("connect, enable Pullup"); + outl (0x00000018, FUNC_MUX_CTRL_D); +} + +/* Turn off the USB connection by disabling the pullup resistor */ +void udc_disconnect (void) +{ + UDCDBG ("disconnect, disable Pullup"); + outl (0x00000000, FUNC_MUX_CTRL_D); +} + +/* ************************************************************************** */ + + +/* + * udc_disable_interrupts - disable interrupts + * switch off interrupts + */ +#if 0 +void udc_disable_interrupts (struct usb_device_instance *device) +{ + UDCDBG ("disabling all interrupts"); + outw (0, UDC_IRQ_EN); +} +#endif + +/* ************************************************************************** */ + +/** + * udc_ep0_packetsize - return ep0 packetsize + */ +#if 0 +int udc_ep0_packetsize (void) +{ + return EP0_PACKETSIZE; +} +#endif + +/* Switch on the UDC */ +void udc_enable (struct usb_device_instance *device) +{ + UDCDBGA ("enable device %p, status %d", device, device->status); + + /* initialize driver state variables */ + udc_devstat = 0; + + /* Save the device structure pointer */ + udc_device = device; + + /* Setup ep0 urb */ + if (!ep0_urb) { + ep0_urb = + usbd_alloc_urb (udc_device, + udc_device->bus->endpoint_array); + } else { + serial_printf ("udc_enable: ep0_urb already allocated %p\n", + ep0_urb); + } + + UDCDBG ("Check clock status"); + UDCREG (STATUS_REQ); + + /* The VBUS_MODE bit selects whether VBUS detection is done via + * software (1) or hardware (0). When software detection is + * selected, VBUS_CTRL selects whether USB is not connected (0) + * or connected (1). + */ + outl (inl (FUNC_MUX_CTRL_0) | UDC_VBUS_CTRL | UDC_VBUS_MODE, + FUNC_MUX_CTRL_0); + UDCREGL (FUNC_MUX_CTRL_0); + + omap1510_configure_device (device); +} + +/* Switch off the UDC */ +void udc_disable (void) +{ + UDCDBG ("disable UDC"); + + omap1510_deconfigure_device (); + + /* The VBUS_MODE bit selects whether VBUS detection is done via + * software (1) or hardware (0). When software detection is + * selected, VBUS_CTRL selects whether USB is not connected (0) + * or connected (1). + */ + outl (inl (FUNC_MUX_CTRL_0) | UDC_VBUS_MODE, FUNC_MUX_CTRL_0); + outl (inl (FUNC_MUX_CTRL_0) & ~UDC_VBUS_CTRL, FUNC_MUX_CTRL_0); + UDCREGL (FUNC_MUX_CTRL_0); + + /* Free ep0 URB */ + if (ep0_urb) { + /*usbd_dealloc_urb(ep0_urb); */ + ep0_urb = NULL; + } + + /* Reset device pointer. + * We ought to do this here to balance the initialization of udc_device + * in udc_enable, but some of our other exported functions get called + * by the bus interface driver after udc_disable, so we have to hang on + * to the device pointer to avoid a null pointer dereference. */ + /* udc_device = NULL; */ +} + +/** + * udc_startup - allow udc code to do any additional startup + */ +void udc_startup_events (struct usb_device_instance *device) +{ + /* The DEVICE_INIT event puts the USB device in the state STATE_INIT. */ + usbd_device_event_irq (device, DEVICE_INIT, 0); + + /* The DEVICE_CREATE event puts the USB device in the state + * STATE_ATTACHED. + */ + usbd_device_event_irq (device, DEVICE_CREATE, 0); + + /* Some USB controller driver implementations signal + * DEVICE_HUB_CONFIGURED and DEVICE_RESET events here. + * DEVICE_HUB_CONFIGURED causes a transition to the state STATE_POWERED, + * and DEVICE_RESET causes a transition to the state STATE_DEFAULT. + * The OMAP USB client controller has the capability to detect when the + * USB cable is connected to a powered USB bus via the ATT bit in the + * DEVSTAT register, so we will defer the DEVICE_HUB_CONFIGURED and + * DEVICE_RESET events until later. + */ + + udc_enable (device); +} + +/** + * udc_irq - do pseudo interrupts + */ +void udc_irq(void) +{ + /* Loop while we have interrupts. + * If we don't do this, the input chain + * polling delay is likely to miss + * host requests. + */ + while (inw (UDC_IRQ_SRC) & ~UDC_SOF_Flg) { + /* Handle any new IRQs */ + omap1510_udc_irq (); + omap1510_udc_noniso_irq (); + } +} + +/* Flow control */ +void udc_set_nak(int epid) +{ + /* TODO: implement this functionality in omap1510 */ +} + +void udc_unset_nak (int epid) +{ + /* TODO: implement this functionality in omap1510 */ +} diff --git a/roms/u-boot-sam460ex/drivers/usb/gadget/pxa27x_udc.c b/roms/u-boot-sam460ex/drivers/usb/gadget/pxa27x_udc.c new file mode 100644 index 000000000..e6ccd49ac --- /dev/null +++ b/roms/u-boot-sam460ex/drivers/usb/gadget/pxa27x_udc.c @@ -0,0 +1,702 @@ +/* + * PXA27x USB device driver for u-boot. + * + * Copyright (C) 2007 Rodolfo Giometti <giometti@linux.it> + * Copyright (C) 2007 Eurotech S.p.A. <info@eurotech.it> + * Copyright (C) 2008 Vivek Kutal <vivek.kutal@azingo.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 <config.h> +#include <asm/byteorder.h> +#include <usbdcore.h> +#include <usbdcore_ep0.h> +#include <asm/arch/hardware.h> +#include <usb/pxa27x_udc.h> + +/* number of endpoints on this UDC */ +#define UDC_MAX_ENDPOINTS 24 + +static struct urb *ep0_urb; +static struct usb_device_instance *udc_device; +static int ep0state = EP0_IDLE; + +#ifdef USBDDBG +static void udc_dump_buffer(char *name, u8 *buf, int len) +{ + usbdbg("%s - buf %p, len %d", name, buf, len); + print_buffer(0, buf, 1, len, 0); +} +#else +#define udc_dump_buffer(name, buf, len) /* void */ +#endif + +static inline void udc_ack_int_UDCCR(int mask) +{ + USIR1 = mask | USIR1; +} + +/* + * If the endpoint has an active tx_urb, then the next packet of data from the + * URB is written to the tx FIFO. + * The total amount of data in the urb is given by urb->actual_length. + * The maximum amount of data that can be sent in any one packet is given by + * endpoint->tx_packetSize. + * The number of data bytes from this URB that have already been transmitted + * is given by endpoint->sent. + * endpoint->last is updated by this routine with the number of data bytes + * transmitted in this packet. + */ +static int udc_write_urb(struct usb_endpoint_instance *endpoint) +{ + struct urb *urb = endpoint->tx_urb; + int ep_num = endpoint->endpoint_address & USB_ENDPOINT_NUMBER_MASK; + u32 *addr32 = (u32 *) &UDCDN(ep_num); + u32 *data32 = (u32 *) urb->buffer; + u8 *addr8 = (u8 *) &UDCDN(ep_num); + u8 *data8 = (u8 *) urb->buffer; + unsigned int i, n, w, b, is_short; + int timeout = 2000; /* 2ms */ + + if (!urb || !urb->actual_length) + return -1; + + n = MIN(urb->actual_length - endpoint->sent, endpoint->tx_packetSize); + if (n <= 0) + return -1; + + usbdbg("write urb on ep %d", ep_num); +#if defined(USBDDBG) && defined(USBDPARANOIA) + usbdbg("urb: buf %p, buf_len %d, actual_len %d", + urb->buffer, urb->buffer_length, urb->actual_length); + usbdbg("endpoint: sent %d, tx_packetSize %d, last %d", + endpoint->sent, endpoint->tx_packetSize, endpoint->last); +#endif + + is_short = n != endpoint->tx_packetSize; + w = n / 4; + b = n % 4; + usbdbg("n %d%s w %d b %d", n, is_short ? "-s" : "", w, b); + udc_dump_buffer("urb write", data8 + endpoint->sent, n); + + /* Prepare for data send */ + if (ep_num) + UDCCSN(ep_num) = UDCCSR_PC; + + for (i = 0; i < w; i++) + *addr32 = data32[endpoint->sent/4 + i]; + for (i = 0; i < b; i++) + *addr8 = data8[endpoint->sent + w*4 + i]; + + /* Set "Packet Complete" if less data then tx_packetSize */ + if (is_short) + UDCCSN(ep_num) = ep_num ? UDCCSR_SP : UDCCSR0_IPR; + + /* Wait for data sent */ + while (!(UDCCSN(ep_num) & (ep_num ? UDCCSR_PC : UDCCSR0_IPR))) { + if (ep_num) { + if (timeout-- == 0) + return -1; + else + udelay(1); + }; + } + endpoint->last = n; + + if (ep_num) { + usbd_tx_complete(endpoint); + } else { + endpoint->sent += n; + endpoint->last -= n; + } + + if ((endpoint->tx_urb->actual_length - endpoint->sent) <= 0) { + urb->actual_length = 0; + endpoint->sent = 0; + endpoint->last = 0; + } + + if ((endpoint->sent >= urb->actual_length) && (!ep_num)) { + usbdbg("ep0 IN stage done"); + if (is_short) + ep0state = EP0_IDLE; + else + ep0state = EP0_XFER_COMPLETE; + } + + return 0; +} + +static int udc_read_urb(struct usb_endpoint_instance *endpoint) +{ + struct urb *urb = endpoint->rcv_urb; + int ep_num = endpoint->endpoint_address & USB_ENDPOINT_NUMBER_MASK; + u32 *addr32 = (u32 *) &UDCDN(ep_num); + u32 *data32 = (u32 *) urb->buffer; + unsigned int i, n, is_short ; + + usbdbg("read urb on ep %d", ep_num); +#if defined(USBDDBG) && defined(USBDPARANOIA) + usbdbg("urb: buf %p, buf_len %d, actual_len %d", + urb->buffer, urb->buffer_length, urb->actual_length); + usbdbg("endpoint: rcv_packetSize %d", + endpoint->rcv_packetSize); +#endif + + if (UDCCSN(ep_num) & UDCCSR_BNE) + n = UDCBCN(ep_num) & 0x3ff; + else /* zlp */ + n = 0; + is_short = n != endpoint->rcv_packetSize; + + usbdbg("n %d%s", n, is_short ? "-s" : ""); + for (i = 0; i < n; i += 4) + data32[urb->actual_length/4 + i/4] = *addr32; + + udc_dump_buffer("urb read", (u8 *) data32, urb->actual_length + n); + usbd_rcv_complete(endpoint, n, 0); + + return 0; +} + +static int udc_read_urb_ep0(void) +{ + u32 *addr32 = (u32 *) &UDCDN(0); + u32 *data32 = (u32 *) ep0_urb->buffer; + u8 *addr8 = (u8 *) &UDCDN(0); + u8 *data8 = (u8 *) ep0_urb->buffer; + unsigned int i, n, w, b; + + n = UDCBCR0; + w = n / 4; + b = n % 4; + + for (i = 0; i < w; i++) { + data32[ep0_urb->actual_length/4 + i] = *addr32; + ep0_urb->actual_length += 4; + } + + for (i = 0; i < b; i++) { + data8[ep0_urb->actual_length + w*4 + i] = *addr8; + ep0_urb->actual_length++; + } + + UDCCSR0 = UDCCSR0_OPC | UDCCSR0_IPR; + if (ep0_urb->actual_length == ep0_urb->device_request.wLength) + return 1; + + return 0; +} + +static void udc_handle_ep0(struct usb_endpoint_instance *endpoint) +{ + u32 udccsr0 = UDCCSR0; + u32 *data = (u32 *) &ep0_urb->device_request; + int i; + + usbdbg("udccsr0 %x", udccsr0); + + /* Clear stall status */ + if (udccsr0 & UDCCSR0_SST) { + usberr("clear stall status"); + UDCCSR0 = UDCCSR0_SST; + ep0state = EP0_IDLE; + } + + /* previous request unfinished? non-error iff back-to-back ... */ + if ((udccsr0 & UDCCSR0_SA) != 0 && ep0state != EP0_IDLE) + ep0state = EP0_IDLE; + + switch (ep0state) { + + case EP0_IDLE: + + udccsr0 = UDCCSR0; + /* Start control request? */ + if ((udccsr0 & (UDCCSR0_OPC | UDCCSR0_SA | UDCCSR0_RNE)) + == (UDCCSR0_OPC | UDCCSR0_SA | UDCCSR0_RNE)) { + + /* Read SETUP packet. + * SETUP packet size is 8 bytes (aka 2 words) + */ + usbdbg("try reading SETUP packet"); + for (i = 0; i < 2; i++) { + if ((UDCCSR0 & UDCCSR0_RNE) == 0) { + usberr("setup packet too short:%d", i); + goto stall; + } + data[i] = UDCDR0; + } + + UDCCSR0 |= (UDCCSR0_OPC | UDCCSR0_SA); + if ((UDCCSR0 & UDCCSR0_RNE) != 0) { + usberr("setup packet too long"); + goto stall; + } + + udc_dump_buffer("ep0 setup read", (u8 *) data, 8); + + if (ep0_urb->device_request.wLength == 0) { + usbdbg("Zero Data control Packet\n"); + if (ep0_recv_setup(ep0_urb)) { + usberr("Invalid Setup Packet\n"); + udc_dump_buffer("ep0 setup read", + (u8 *)data, 8); + goto stall; + } + UDCCSR0 = UDCCSR0_IPR; + ep0state = EP0_IDLE; + } else { + /* Check direction */ + if ((ep0_urb->device_request.bmRequestType & + USB_REQ_DIRECTION_MASK) + == USB_REQ_HOST2DEVICE) { + ep0state = EP0_OUT_DATA; + ep0_urb->buffer = + (u8 *)ep0_urb->buffer_data; + ep0_urb->buffer_length = + sizeof(ep0_urb->buffer_data); + ep0_urb->actual_length = 0; + UDCCSR0 = UDCCSR0_IPR; + } else { + /* The ep0_recv_setup function has + * already placed our response packet + * data in ep0_urb->buffer and the + * packet length in + * ep0_urb->actual_length. + */ + if (ep0_recv_setup(ep0_urb)) { +stall: + usberr("Invalid setup packet"); + udc_dump_buffer("ep0 setup read" + , (u8 *) data, 8); + ep0state = EP0_IDLE; + + UDCCSR0 = UDCCSR0_SA | + UDCCSR0_OPC | UDCCSR0_FST | + UDCCS0_FTF; + + return; + } + + endpoint->tx_urb = ep0_urb; + endpoint->sent = 0; + usbdbg("EP0_IN_DATA"); + ep0state = EP0_IN_DATA; + if (udc_write_urb(endpoint) < 0) + goto stall; + + } + } + return; + } else if ((udccsr0 & (UDCCSR0_OPC | UDCCSR0_SA)) + == (UDCCSR0_OPC|UDCCSR0_SA)) { + usberr("Setup Active but no data. Stalling ....\n"); + goto stall; + } else { + usbdbg("random early IRQs"); + /* Some random early IRQs: + * - we acked FST + * - IPR cleared + * - OPC got set, without SA (likely status stage) + */ + UDCCSR0 = udccsr0 & (UDCCSR0_SA | UDCCSR0_OPC); + } + break; + + case EP0_OUT_DATA: + + if ((udccsr0 & UDCCSR0_OPC) && !(udccsr0 & UDCCSR0_SA)) { + if (udc_read_urb_ep0()) { +read_complete: + ep0state = EP0_IDLE; + if (ep0_recv_setup(ep0_urb)) { + /* Not a setup packet, stall next + * EP0 transaction + */ + udc_dump_buffer("ep0 setup read", + (u8 *) data, 8); + usberr("can't parse setup packet\n"); + goto stall; + } + } + } else if (!(udccsr0 & UDCCSR0_OPC) && + !(udccsr0 & UDCCSR0_IPR)) { + if (ep0_urb->device_request.wLength == + ep0_urb->actual_length) + goto read_complete; + + usberr("Premature Status\n"); + ep0state = EP0_IDLE; + } + break; + + case EP0_IN_DATA: + /* GET_DESCRIPTOR etc */ + if (udccsr0 & UDCCSR0_OPC) { + UDCCSR0 = UDCCSR0_OPC | UDCCSR0_FTF; + usberr("ep0in premature status"); + ep0state = EP0_IDLE; + } else { + /* irq was IPR clearing */ + if (udc_write_urb(endpoint) < 0) { + usberr("ep0_write_error\n"); + goto stall; + } + } + break; + + case EP0_XFER_COMPLETE: + UDCCSR0 = UDCCSR0_IPR; + ep0state = EP0_IDLE; + break; + + default: + usbdbg("Default\n"); + } + USIR0 = USIR0_IR0; +} + +static void udc_handle_ep(struct usb_endpoint_instance *endpoint) +{ + int ep_addr = endpoint->endpoint_address; + int ep_num = ep_addr & USB_ENDPOINT_NUMBER_MASK; + int ep_isout = (ep_addr & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT; + + u32 flags = UDCCSN(ep_num) & (UDCCSR_SST | UDCCSR_TRN); + if (flags) + UDCCSN(ep_num) = flags; + + if (ep_isout) + udc_read_urb(endpoint); + else + udc_write_urb(endpoint); + + UDCCSN(ep_num) = UDCCSR_PC; +} + +static void udc_state_changed(void) +{ + int config, interface, alternate; + + UDCCR |= UDCCR_SMAC; + + config = (UDCCR & UDCCR_ACN) >> UDCCR_ACN_S; + interface = (UDCCR & UDCCR_AIN) >> UDCCR_AIN_S; + alternate = (UDCCR & UDCCR_AAISN) >> UDCCR_AAISN_S; + + usbdbg("New UDC settings are: conf %d - inter %d - alter %d", + config, interface, alternate); + + usbd_device_event_irq(udc_device, DEVICE_CONFIGURED, 0); + UDCISR1 = UDCISR1_IRCC; +} + +void udc_irq(void) +{ + int handled; + struct usb_endpoint_instance *endpoint; + int ep_num, i; + u32 udcisr0; + + do { + handled = 0; + /* Suspend Interrupt Request */ + if (USIR1 & UDCCR_SUSIR) { + usbdbg("Suspend\n"); + udc_ack_int_UDCCR(UDCCR_SUSIR); + handled = 1; + ep0state = EP0_IDLE; + } + + /* Resume Interrupt Request */ + if (USIR1 & UDCCR_RESIR) { + udc_ack_int_UDCCR(UDCCR_RESIR); + handled = 1; + usbdbg("USB resume\n"); + } + + if (USIR1 & (1<<31)) { + handled = 1; + udc_state_changed(); + } + + /* Reset Interrupt Request */ + if (USIR1 & UDCCR_RSTIR) { + udc_ack_int_UDCCR(UDCCR_RSTIR); + handled = 1; + usbdbg("Reset\n"); + usbd_device_event_irq(udc_device, DEVICE_RESET, 0); + } else { + if (USIR0) + usbdbg("UISR0: %x \n", USIR0); + + if (USIR0 & 0x2) + USIR0 = 0x2; + + /* Control traffic */ + if (USIR0 & USIR0_IR0) { + handled = 1; + udc_handle_ep0(udc_device->bus->endpoint_array); + USIR0 = USIR0_IR0; + } + + endpoint = udc_device->bus->endpoint_array; + for (i = 0; i < udc_device->bus->max_endpoints; i++) { + ep_num = (endpoint[i].endpoint_address) & + USB_ENDPOINT_NUMBER_MASK; + if (!ep_num) + continue; + udcisr0 = UDCISR0; + if (udcisr0 & + UDCISR_INT(ep_num, UDC_INT_PACKETCMP)) { + UDCISR0 = UDCISR_INT(ep_num, + UDC_INT_PACKETCMP); + udc_handle_ep(&endpoint[i]); + } + } + } + + } while (handled); +} + +/* The UDCCR reg contains mask and interrupt status bits, + * so using '|=' isn't safe as it may ack an interrupt. + */ +#define UDCCR_OEN (1 << 31) /* On-the-Go Enable */ +#define UDCCR_MASK_BITS (UDCCR_OEN | UDCCR_UDE) + +static inline void udc_set_mask_UDCCR(int mask) +{ + UDCCR = (UDCCR & UDCCR_MASK_BITS) | (mask & UDCCR_MASK_BITS); +} + +static inline void udc_clear_mask_UDCCR(int mask) +{ + UDCCR = (UDCCR & UDCCR_MASK_BITS) & ~(mask & UDCCR_MASK_BITS); +} + +static void pio_irq_enable(int ep_num) +{ + if (ep_num < 16) + UDCICR0 |= 3 << (ep_num * 2); + else { + ep_num -= 16; + UDCICR1 |= 3 << (ep_num * 2); + } +} + +/* + * udc_set_nak + * + * Allow upper layers to signal lower layers should not accept more RX data + */ +void udc_set_nak(int ep_num) +{ + /* TODO */ +} + +/* + * udc_unset_nak + * + * Suspend sending of NAK tokens for DATA OUT tokens on a given endpoint. + * Switch off NAKing on this endpoint to accept more data output from host. + */ +void udc_unset_nak(int ep_num) +{ + /* TODO */ +} + +int udc_endpoint_write(struct usb_endpoint_instance *endpoint) +{ + return udc_write_urb(endpoint); +} + +/* Associate a physical endpoint with endpoint instance */ +void udc_setup_ep(struct usb_device_instance *device, unsigned int id, + struct usb_endpoint_instance *endpoint) +{ + int ep_num, ep_addr, ep_isout, ep_type, ep_size; + int config, interface, alternate; + u32 tmp; + + usbdbg("setting up endpoint id %d", id); + + if (!endpoint) { + usberr("endpoint void!"); + return; + } + + ep_num = endpoint->endpoint_address & USB_ENDPOINT_NUMBER_MASK; + if (ep_num >= UDC_MAX_ENDPOINTS) { + usberr("unable to setup ep %d!", ep_num); + return; + } + + pio_irq_enable(ep_num); + if (ep_num == 0) { + /* Done for ep0 */ + return; + } + + config = 1; + interface = 0; + alternate = 0; + + usbdbg("config %d - interface %d - alternate %d", + config, interface, alternate); + + ep_addr = endpoint->endpoint_address; + ep_num = ep_addr & USB_ENDPOINT_NUMBER_MASK; + ep_isout = (ep_addr & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT; + ep_type = ep_isout ? endpoint->rcv_attributes : endpoint->tx_attributes; + ep_size = ep_isout ? endpoint->rcv_packetSize : endpoint->tx_packetSize; + + usbdbg("addr %x, num %d, dir %s, type %s, packet size %d", + ep_addr, ep_num, + ep_isout ? "out" : "in", + ep_type == USB_ENDPOINT_XFER_ISOC ? "isoc" : + ep_type == USB_ENDPOINT_XFER_BULK ? "bulk" : + ep_type == USB_ENDPOINT_XFER_INT ? "int" : "???", + ep_size + ); + + /* Configure UDCCRx */ + tmp = 0; + tmp |= (config << UDCCONR_CN_S) & UDCCONR_CN; + tmp |= (interface << UDCCONR_IN_S) & UDCCONR_IN; + tmp |= (alternate << UDCCONR_AISN_S) & UDCCONR_AISN; + tmp |= (ep_num << UDCCONR_EN_S) & UDCCONR_EN; + tmp |= (ep_type << UDCCONR_ET_S) & UDCCONR_ET; + tmp |= ep_isout ? 0 : UDCCONR_ED; + tmp |= (ep_size << UDCCONR_MPS_S) & UDCCONR_MPS; + tmp |= UDCCONR_EE; + + UDCCN(ep_num) = tmp; + + usbdbg("UDCCR%c = %x", 'A' + ep_num-1, UDCCN(ep_num)); + usbdbg("UDCCSR%c = %x", 'A' + ep_num-1, UDCCSN(ep_num)); +} + +#define CONFIG_USB_DEV_PULLUP_GPIO 87 + +/* Connect the USB device to the bus */ +void udc_connect(void) +{ + usbdbg("UDC connect"); + + /* Turn on the USB connection by enabling the pullup resistor */ + set_GPIO_mode(CONFIG_USB_DEV_PULLUP_GPIO | GPIO_OUT); + GPSR(CONFIG_USB_DEV_PULLUP_GPIO) = GPIO_bit(CONFIG_USB_DEV_PULLUP_GPIO); +} + +/* Disconnect the USB device to the bus */ +void udc_disconnect(void) +{ + usbdbg("UDC disconnect"); + + /* Turn off the USB connection by disabling the pullup resistor */ + GPCR(CONFIG_USB_DEV_PULLUP_GPIO) = GPIO_bit(CONFIG_USB_DEV_PULLUP_GPIO); +} + +/* Switch on the UDC */ +void udc_enable(struct usb_device_instance *device) +{ + + ep0state = EP0_IDLE; + CKEN |= CKEN11_USB; + + /* enable endpoint 0, A, B's Packet Complete Interrupt. */ + UDCICR0 = 0x0000003f; + UDCICR1 = 0xa8000000; + + /* clear the interrupt status/control registers */ + UDCISR0 = 0xffffffff; + UDCISR1 = 0xffffffff; + + /* set UDC-enable */ + udc_set_mask_UDCCR(UDCCR_UDE); + + udc_device = device; + if (!ep0_urb) + ep0_urb = usbd_alloc_urb(udc_device, + udc_device->bus->endpoint_array); + else + usbinfo("ep0_urb %p already allocated", ep0_urb); + + usbdbg("UDC Enabled\n"); +} + +/* Need to check this again */ +void udc_disable(void) +{ + usbdbg("disable UDC"); + + udc_clear_mask_UDCCR(UDCCR_UDE); + + /* Disable clock for USB device */ + CKEN &= ~CKEN11_USB; + + /* Free ep0 URB */ + if (ep0_urb) { + usbd_dealloc_urb(ep0_urb); + ep0_urb = NULL; + } + + /* Reset device pointer */ + udc_device = NULL; +} + +/* Allow udc code to do any additional startup */ +void udc_startup_events(struct usb_device_instance *device) +{ + /* The DEVICE_INIT event puts the USB device in the state STATE_INIT */ + usbd_device_event_irq(device, DEVICE_INIT, 0); + + /* The DEVICE_CREATE event puts the USB device in the state + * STATE_ATTACHED */ + usbd_device_event_irq(device, DEVICE_CREATE, 0); + + /* Some USB controller driver implementations signal + * DEVICE_HUB_CONFIGURED and DEVICE_RESET events here. + * DEVICE_HUB_CONFIGURED causes a transition to the state + * STATE_POWERED, and DEVICE_RESET causes a transition to + * the state STATE_DEFAULT. + */ + udc_enable(device); +} + +/* Initialize h/w stuff */ +int udc_init(void) +{ + udc_device = NULL; + usbdbg("PXA27x usbd start"); + + /* Disable the UDC */ + udc_clear_mask_UDCCR(UDCCR_UDE); + + /* Disable clock for USB device */ + CKEN &= ~CKEN11_USB; + + /* Disable IRQs: we don't use them */ + UDCICR0 = UDCICR1 = 0; + + return 0; +} diff --git a/roms/u-boot-sam460ex/drivers/usb/gadget/spr_udc.c b/roms/u-boot-sam460ex/drivers/usb/gadget/spr_udc.c new file mode 100644 index 000000000..f2b06d68e --- /dev/null +++ b/roms/u-boot-sam460ex/drivers/usb/gadget/spr_udc.c @@ -0,0 +1,998 @@ +/* + * Based on drivers/usb/gadget/omap1510_udc.c + * TI OMAP1510 USB bus interface driver + * + * (C) Copyright 2009 + * Vipin Kumar, ST Micoelectronics, vipin.kumar@st.com. + * + * 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 <asm/io.h> + +#include <usbdevice.h> +#include "ep0.h" +#include <usb/spr_udc.h> +#include <asm/arch/hardware.h> +#include <asm/arch/spr_misc.h> + +#define UDC_INIT_MDELAY 80 /* Device settle delay */ + +/* Some kind of debugging output... */ +#ifndef DEBUG_SPRUSBTTY +#define UDCDBG(str) +#define UDCDBGA(fmt, args...) +#else +#define UDCDBG(str) serial_printf(str "\n") +#define UDCDBGA(fmt, args...) serial_printf(fmt "\n", ##args) +#endif + +static struct urb *ep0_urb; +static struct usb_device_instance *udc_device; + +static struct plug_regs *const plug_regs_p = + (struct plug_regs * const)CONFIG_SYS_PLUG_BASE; +static struct udc_regs *const udc_regs_p = + (struct udc_regs * const)CONFIG_SYS_USBD_BASE; +static struct udc_endp_regs *const outep_regs_p = + &((struct udc_regs * const)CONFIG_SYS_USBD_BASE)->out_regs[0]; +static struct udc_endp_regs *const inep_regs_p = + &((struct udc_regs * const)CONFIG_SYS_USBD_BASE)->in_regs[0]; + +/* + * udc_state_transition - Write the next packet to TxFIFO. + * @initial: Initial state. + * @final: Final state. + * + * Helper function to implement device state changes. The device states and + * the events that transition between them are: + * + * STATE_ATTACHED + * || /\ + * \/ || + * DEVICE_HUB_CONFIGURED DEVICE_HUB_RESET + * || /\ + * \/ || + * STATE_POWERED + * || /\ + * \/ || + * DEVICE_RESET DEVICE_POWER_INTERRUPTION + * || /\ + * \/ || + * STATE_DEFAULT + * || /\ + * \/ || + * DEVICE_ADDRESS_ASSIGNED DEVICE_RESET + * || /\ + * \/ || + * STATE_ADDRESSED + * || /\ + * \/ || + * DEVICE_CONFIGURED DEVICE_DE_CONFIGURED + * || /\ + * \/ || + * STATE_CONFIGURED + * + * udc_state_transition transitions up (in the direction from STATE_ATTACHED + * to STATE_CONFIGURED) from the specified initial state to the specified final + * state, passing through each intermediate state on the way. If the initial + * state is at or above (i.e. nearer to STATE_CONFIGURED) the final state, then + * no state transitions will take place. + * + * udc_state_transition also transitions down (in the direction from + * STATE_CONFIGURED to STATE_ATTACHED) from the specified initial state to the + * specified final state, passing through each intermediate state on the way. + * If the initial state is at or below (i.e. nearer to STATE_ATTACHED) the final + * state, then no state transitions will take place. + * + * This function must only be called with interrupts disabled. + */ +static void udc_state_transition(usb_device_state_t initial, + usb_device_state_t final) +{ + if (initial < final) { + switch (initial) { + case STATE_ATTACHED: + usbd_device_event_irq(udc_device, + DEVICE_HUB_CONFIGURED, 0); + if (final == STATE_POWERED) + break; + case STATE_POWERED: + usbd_device_event_irq(udc_device, DEVICE_RESET, 0); + if (final == STATE_DEFAULT) + break; + case STATE_DEFAULT: + usbd_device_event_irq(udc_device, + DEVICE_ADDRESS_ASSIGNED, 0); + if (final == STATE_ADDRESSED) + break; + case STATE_ADDRESSED: + usbd_device_event_irq(udc_device, DEVICE_CONFIGURED, 0); + case STATE_CONFIGURED: + break; + default: + break; + } + } else if (initial > final) { + switch (initial) { + case STATE_CONFIGURED: + usbd_device_event_irq(udc_device, + DEVICE_DE_CONFIGURED, 0); + if (final == STATE_ADDRESSED) + break; + case STATE_ADDRESSED: + usbd_device_event_irq(udc_device, DEVICE_RESET, 0); + if (final == STATE_DEFAULT) + break; + case STATE_DEFAULT: + usbd_device_event_irq(udc_device, + DEVICE_POWER_INTERRUPTION, 0); + if (final == STATE_POWERED) + break; + case STATE_POWERED: + usbd_device_event_irq(udc_device, DEVICE_HUB_RESET, 0); + case STATE_ATTACHED: + break; + default: + break; + } + } +} + +/* Stall endpoint */ +static void udc_stall_ep(u32 ep_num) +{ + writel(readl(&inep_regs_p[ep_num].endp_cntl) | ENDP_CNTL_STALL, + &inep_regs_p[ep_num].endp_cntl); + + writel(readl(&outep_regs_p[ep_num].endp_cntl) | ENDP_CNTL_STALL, + &outep_regs_p[ep_num].endp_cntl); +} + +static void *get_fifo(int ep_num, int in) +{ + u32 *fifo_ptr = (u32 *)CONFIG_SYS_FIFO_BASE; + + switch (ep_num) { + case UDC_EP3: + fifo_ptr += readl(&inep_regs_p[1].endp_bsorfn); + /* break intentionally left out */ + + case UDC_EP1: + fifo_ptr += readl(&inep_regs_p[0].endp_bsorfn); + /* break intentionally left out */ + + case UDC_EP0: + default: + if (in) { + fifo_ptr += + readl(&outep_regs_p[2].endp_maxpacksize) >> 16; + /* break intentionally left out */ + } else { + break; + } + + case UDC_EP2: + fifo_ptr += readl(&outep_regs_p[0].endp_maxpacksize) >> 16; + /* break intentionally left out */ + } + + return (void *)fifo_ptr; +} + +static int usbgetpckfromfifo(int epNum, u8 *bufp, u32 len) +{ + u8 *fifo_ptr = (u8 *)get_fifo(epNum, 0); + u32 i, nw, nb; + u32 *wrdp; + u8 *bytp; + + if (readl(&udc_regs_p->dev_stat) & DEV_STAT_RXFIFO_EMPTY) + return -1; + + nw = len / sizeof(u32); + nb = len % sizeof(u32); + + wrdp = (u32 *)bufp; + for (i = 0; i < nw; i++) { + writel(readl(fifo_ptr), wrdp); + wrdp++; + } + + bytp = (u8 *)wrdp; + for (i = 0; i < nb; i++) { + writeb(readb(fifo_ptr), bytp); + fifo_ptr++; + bytp++; + } + readl(&outep_regs_p[epNum].write_done); + + return 0; +} + +static void usbputpcktofifo(int epNum, u8 *bufp, u32 len) +{ + u32 i, nw, nb; + u32 *wrdp; + u8 *bytp; + u8 *fifo_ptr = get_fifo(epNum, 1); + + nw = len / sizeof(int); + nb = len % sizeof(int); + wrdp = (u32 *)bufp; + for (i = 0; i < nw; i++) { + writel(*wrdp, fifo_ptr); + wrdp++; + } + + bytp = (u8 *)wrdp; + for (i = 0; i < nb; i++) { + writeb(*bytp, fifo_ptr); + fifo_ptr++; + bytp++; + } +} + +/* + * spear_write_noniso_tx_fifo - Write the next packet to TxFIFO. + * @endpoint: Endpoint pointer. + * + * If the endpoint has an active tx_urb, then the next packet of data from the + * URB is written to the tx FIFO. The total amount of data in the urb is given + * by urb->actual_length. The maximum amount of data that can be sent in any + * one packet is given by endpoint->tx_packetSize. The number of data bytes + * from this URB that have already been transmitted is given by endpoint->sent. + * endpoint->last is updated by this routine with the number of data bytes + * transmitted in this packet. + * + */ +static void spear_write_noniso_tx_fifo(struct usb_endpoint_instance + *endpoint) +{ + struct urb *urb = endpoint->tx_urb; + int align; + + if (urb) { + u32 last; + + UDCDBGA("urb->buffer %p, buffer_length %d, actual_length %d", + urb->buffer, urb->buffer_length, urb->actual_length); + + last = MIN(urb->actual_length - endpoint->sent, + endpoint->tx_packetSize); + + if (last) { + u8 *cp = urb->buffer + endpoint->sent; + + /* + * This ensures that USBD packet fifo is accessed + * - through word aligned pointer or + * - through non word aligned pointer but only + * with a max length to make the next packet + * word aligned + */ + + align = ((ulong)cp % sizeof(int)); + if (align) + last = MIN(last, sizeof(int) - align); + + UDCDBGA("endpoint->sent %d, tx_packetSize %d, last %d", + endpoint->sent, endpoint->tx_packetSize, last); + + usbputpcktofifo(endpoint->endpoint_address & + USB_ENDPOINT_NUMBER_MASK, cp, last); + } + endpoint->last = last; + } +} + +/* + * Handle SETUP USB interrupt. + * This function implements TRM Figure 14-14. + */ +static void spear_udc_setup(struct usb_endpoint_instance *endpoint) +{ + u8 *datap = (u8 *)&ep0_urb->device_request; + int ep_addr = endpoint->endpoint_address; + + UDCDBG("-> Entering device setup"); + usbgetpckfromfifo(ep_addr, datap, 8); + + /* Try to process setup packet */ + if (ep0_recv_setup(ep0_urb)) { + /* Not a setup packet, stall next EP0 transaction */ + udc_stall_ep(0); + UDCDBG("can't parse setup packet, still waiting for setup"); + return; + } + + /* Check direction */ + if ((ep0_urb->device_request.bmRequestType & USB_REQ_DIRECTION_MASK) + == USB_REQ_HOST2DEVICE) { + UDCDBG("control write on EP0"); + if (le16_to_cpu(ep0_urb->device_request.wLength)) { + /* Stall this request */ + UDCDBG("Stalling unsupported EP0 control write data " + "stage."); + udc_stall_ep(0); + } + } else { + + UDCDBG("control read on EP0"); + /* + * The ep0_recv_setup function has already placed our response + * packet data in ep0_urb->buffer and the packet length in + * ep0_urb->actual_length. + */ + endpoint->tx_urb = ep0_urb; + endpoint->sent = 0; + /* + * Write packet data to the FIFO. spear_write_noniso_tx_fifo + * will update endpoint->last with the number of bytes written + * to the FIFO. + */ + spear_write_noniso_tx_fifo(endpoint); + + writel(0x0, &inep_regs_p[ep_addr].write_done); + } + + udc_unset_nak(endpoint->endpoint_address); + + UDCDBG("<- Leaving device setup"); +} + +/* + * Handle endpoint 0 RX interrupt + */ +static void spear_udc_ep0_rx(struct usb_endpoint_instance *endpoint) +{ + u8 dummy[64]; + + UDCDBG("RX on EP0"); + + /* Check direction */ + if ((ep0_urb->device_request.bmRequestType + & USB_REQ_DIRECTION_MASK) == USB_REQ_HOST2DEVICE) { + /* + * This rx interrupt must be for a control write data + * stage packet. + * + * We don't support control write data stages. + * We should never end up here. + */ + + UDCDBG("Stalling unexpected EP0 control write " + "data stage packet"); + udc_stall_ep(0); + } else { + /* + * This rx interrupt must be for a control read status + * stage packet. + */ + UDCDBG("ACK on EP0 control read status stage packet"); + u32 len = (readl(&outep_regs_p[0].endp_status) >> 11) & 0xfff; + usbgetpckfromfifo(0, dummy, len); + } +} + +/* + * Handle endpoint 0 TX interrupt + */ +static void spear_udc_ep0_tx(struct usb_endpoint_instance *endpoint) +{ + struct usb_device_request *request = &ep0_urb->device_request; + int ep_addr; + + UDCDBG("TX on EP0"); + + /* Check direction */ + if ((request->bmRequestType & USB_REQ_DIRECTION_MASK) == + USB_REQ_HOST2DEVICE) { + /* + * This tx interrupt must be for a control write status + * stage packet. + */ + UDCDBG("ACK on EP0 control write status stage packet"); + } else { + /* + * This tx interrupt must be for a control read data + * stage packet. + */ + int wLength = le16_to_cpu(request->wLength); + + /* + * Update our count of bytes sent so far in this + * transfer. + */ + endpoint->sent += endpoint->last; + + /* + * We are finished with this transfer if we have sent + * all of the bytes in our tx urb (urb->actual_length) + * unless we need a zero-length terminating packet. We + * need a zero-length terminating packet if we returned + * fewer bytes than were requested (wLength) by the host, + * and the number of bytes we returned is an exact + * multiple of the packet size endpoint->tx_packetSize. + */ + if ((endpoint->sent == ep0_urb->actual_length) && + ((ep0_urb->actual_length == wLength) || + (endpoint->last != endpoint->tx_packetSize))) { + /* Done with control read data stage. */ + UDCDBG("control read data stage complete"); + } else { + /* + * We still have another packet of data to send + * in this control read data stage or else we + * need a zero-length terminating packet. + */ + UDCDBG("ACK control read data stage packet"); + spear_write_noniso_tx_fifo(endpoint); + + ep_addr = endpoint->endpoint_address; + writel(0x0, &inep_regs_p[ep_addr].write_done); + } + } +} + +static struct usb_endpoint_instance *spear_find_ep(int ep) +{ + int i; + + for (i = 0; i < udc_device->bus->max_endpoints; i++) { + if ((udc_device->bus->endpoint_array[i].endpoint_address & + USB_ENDPOINT_NUMBER_MASK) == ep) + return &udc_device->bus->endpoint_array[i]; + } + return NULL; +} + +/* + * Handle RX transaction on non-ISO endpoint. + * The ep argument is a physical endpoint number for a non-ISO IN endpoint + * in the range 1 to 15. + */ +static void spear_udc_epn_rx(int ep) +{ + int nbytes = 0; + struct urb *urb; + struct usb_endpoint_instance *endpoint = spear_find_ep(ep); + + if (endpoint) { + urb = endpoint->rcv_urb; + + if (urb) { + u8 *cp = urb->buffer + urb->actual_length; + + nbytes = (readl(&outep_regs_p[ep].endp_status) >> 11) & + 0xfff; + usbgetpckfromfifo(ep, cp, nbytes); + usbd_rcv_complete(endpoint, nbytes, 0); + } + } +} + +/* + * Handle TX transaction on non-ISO endpoint. + * The ep argument is a physical endpoint number for a non-ISO IN endpoint + * in the range 16 to 30. + */ +static void spear_udc_epn_tx(int ep) +{ + struct usb_endpoint_instance *endpoint = spear_find_ep(ep); + + /* + * We need to transmit a terminating zero-length packet now if + * we have sent all of the data in this URB and the transfer + * size was an exact multiple of the packet size. + */ + if (endpoint && endpoint->tx_urb && endpoint->tx_urb->actual_length) { + if (endpoint->last == endpoint->tx_packetSize) { + /* handle zero length packet here */ + writel(0x0, &inep_regs_p[ep].write_done); + } + /* retire the data that was just sent */ + usbd_tx_complete(endpoint); + /* + * Check to see if we have more data ready to transmit + * now. + */ + if (endpoint->tx_urb && endpoint->tx_urb->actual_length) { + /* write data to FIFO */ + spear_write_noniso_tx_fifo(endpoint); + writel(0x0, &inep_regs_p[ep].write_done); + + } else if (endpoint->tx_urb + && (endpoint->tx_urb->actual_length == 0)) { + /* udc_set_nak(ep); */ + } + } +} + +/* + * Start of public functions. + */ + +/* Called to start packet transmission. */ +int udc_endpoint_write(struct usb_endpoint_instance *endpoint) +{ + udc_unset_nak(endpoint->endpoint_address & USB_ENDPOINT_NUMBER_MASK); + return 0; +} + +/* Start to initialize h/w stuff */ +int udc_init(void) +{ + int i; + u32 plug_st; + + udc_device = NULL; + + UDCDBG("starting"); + + readl(&plug_regs_p->plug_pending); + + udc_disconnect(); + + for (i = 0; i < UDC_INIT_MDELAY; i++) + udelay(1000); + + plug_st = readl(&plug_regs_p->plug_state); + writel(plug_st | PLUG_STATUS_EN, &plug_regs_p->plug_state); + + writel(~0x0, &udc_regs_p->endp_int); + writel(~0x0, &udc_regs_p->dev_int_mask); + writel(~0x0, &udc_regs_p->endp_int_mask); + + writel(DEV_CONF_FS_SPEED | DEV_CONF_REMWAKEUP | DEV_CONF_SELFPOW | + /* Dev_Conf_SYNCFRAME | */ + DEV_CONF_PHYINT_16, &udc_regs_p->dev_conf); + + writel(0x0, &udc_regs_p->dev_cntl); + + /* Clear all interrupts pending */ + writel(DEV_INT_MSK, &udc_regs_p->dev_int); + + return 0; +} + +/* + * udc_setup_ep - setup endpoint + * Associate a physical endpoint with endpoint_instance + */ +void udc_setup_ep(struct usb_device_instance *device, + u32 ep, struct usb_endpoint_instance *endpoint) +{ + UDCDBGA("setting up endpoint addr %x", endpoint->endpoint_address); + int ep_addr; + int ep_num, ep_type; + int packet_size; + int buffer_size; + int attributes; + char *tt; + u32 endp_intmask; + + tt = getenv("usbtty"); + if (!tt) + tt = "generic"; + + ep_addr = endpoint->endpoint_address; + ep_num = ep_addr & USB_ENDPOINT_NUMBER_MASK; + + if ((ep_addr & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN) { + /* IN endpoint */ + packet_size = endpoint->tx_packetSize; + buffer_size = packet_size * 2; + attributes = endpoint->tx_attributes; + } else { + /* OUT endpoint */ + packet_size = endpoint->rcv_packetSize; + buffer_size = packet_size * 2; + attributes = endpoint->rcv_attributes; + } + + switch (attributes & USB_ENDPOINT_XFERTYPE_MASK) { + case USB_ENDPOINT_XFER_CONTROL: + ep_type = ENDP_EPTYPE_CNTL; + break; + case USB_ENDPOINT_XFER_BULK: + default: + ep_type = ENDP_EPTYPE_BULK; + break; + case USB_ENDPOINT_XFER_INT: + ep_type = ENDP_EPTYPE_INT; + break; + case USB_ENDPOINT_XFER_ISOC: + ep_type = ENDP_EPTYPE_ISO; + break; + } + + struct udc_endp_regs *out_p = &outep_regs_p[ep_num]; + struct udc_endp_regs *in_p = &inep_regs_p[ep_num]; + + if (!ep_addr) { + /* Setup endpoint 0 */ + buffer_size = packet_size; + + writel(readl(&in_p->endp_cntl) | ENDP_CNTL_CNAK, + &in_p->endp_cntl); + + writel(readl(&out_p->endp_cntl) | ENDP_CNTL_CNAK, + &out_p->endp_cntl); + + writel(ENDP_CNTL_CONTROL | ENDP_CNTL_FLUSH, &in_p->endp_cntl); + + writel(buffer_size / sizeof(int), &in_p->endp_bsorfn); + + writel(packet_size, &in_p->endp_maxpacksize); + + writel(ENDP_CNTL_CONTROL | ENDP_CNTL_RRDY, &out_p->endp_cntl); + + writel(packet_size | ((buffer_size / sizeof(int)) << 16), + &out_p->endp_maxpacksize); + + writel((packet_size << 19) | ENDP_EPTYPE_CNTL, + &udc_regs_p->udc_endp_reg[ep_num]); + + } else if ((ep_addr & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN) { + /* Setup the IN endpoint */ + writel(0x0, &in_p->endp_status); + writel((ep_type << 4) | ENDP_CNTL_RRDY, &in_p->endp_cntl); + writel(buffer_size / sizeof(int), &in_p->endp_bsorfn); + writel(packet_size, &in_p->endp_maxpacksize); + + if (!strcmp(tt, "cdc_acm")) { + if (ep_type == ENDP_EPTYPE_INT) { + /* Conf no. 1 Interface no. 0 */ + writel((packet_size << 19) | + ENDP_EPDIR_IN | (1 << 7) | + (0 << 11) | (ep_type << 5) | ep_num, + &udc_regs_p->udc_endp_reg[ep_num]); + } else { + /* Conf no. 1 Interface no. 1 */ + writel((packet_size << 19) | + ENDP_EPDIR_IN | (1 << 7) | + (1 << 11) | (ep_type << 5) | ep_num, + &udc_regs_p->udc_endp_reg[ep_num]); + } + } else { + /* Conf no. 1 Interface no. 0 */ + writel((packet_size << 19) | + ENDP_EPDIR_IN | (1 << 7) | + (0 << 11) | (ep_type << 5) | ep_num, + &udc_regs_p->udc_endp_reg[ep_num]); + } + + } else { + /* Setup the OUT endpoint */ + writel(0x0, &out_p->endp_status); + writel((ep_type << 4) | ENDP_CNTL_RRDY, &out_p->endp_cntl); + writel(packet_size | ((buffer_size / sizeof(int)) << 16), + &out_p->endp_maxpacksize); + + if (!strcmp(tt, "cdc_acm")) { + writel((packet_size << 19) | + ENDP_EPDIR_OUT | (1 << 7) | + (1 << 11) | (ep_type << 5) | ep_num, + &udc_regs_p->udc_endp_reg[ep_num]); + } else { + writel((packet_size << 19) | + ENDP_EPDIR_OUT | (1 << 7) | + (0 << 11) | (ep_type << 5) | ep_num, + &udc_regs_p->udc_endp_reg[ep_num]); + } + + } + + endp_intmask = readl(&udc_regs_p->endp_int_mask); + endp_intmask &= ~((1 << ep_num) | 0x10000 << ep_num); + writel(endp_intmask, &udc_regs_p->endp_int_mask); +} + +/* Turn on the USB connection by enabling the pullup resistor */ +void udc_connect(void) +{ + u32 plug_st; + + plug_st = readl(&plug_regs_p->plug_state); + plug_st &= ~(PLUG_STATUS_PHY_RESET | PLUG_STATUS_PHY_MODE); + writel(plug_st, &plug_regs_p->plug_state); +} + +/* Turn off the USB connection by disabling the pullup resistor */ +void udc_disconnect(void) +{ + u32 plug_st; + + plug_st = readl(&plug_regs_p->plug_state); + plug_st |= (PLUG_STATUS_PHY_RESET | PLUG_STATUS_PHY_MODE); + writel(plug_st, &plug_regs_p->plug_state); +} + +/* Switch on the UDC */ +void udc_enable(struct usb_device_instance *device) +{ + UDCDBGA("enable device %p, status %d", device, device->status); + + /* Save the device structure pointer */ + udc_device = device; + + /* Setup ep0 urb */ + if (!ep0_urb) { + ep0_urb = + usbd_alloc_urb(udc_device, udc_device->bus->endpoint_array); + } else { + serial_printf("udc_enable: ep0_urb already allocated %p\n", + ep0_urb); + } + + writel(DEV_INT_SOF, &udc_regs_p->dev_int_mask); +} + +/** + * udc_startup - allow udc code to do any additional startup + */ +void udc_startup_events(struct usb_device_instance *device) +{ + /* The DEVICE_INIT event puts the USB device in the state STATE_INIT. */ + usbd_device_event_irq(device, DEVICE_INIT, 0); + + /* + * The DEVICE_CREATE event puts the USB device in the state + * STATE_ATTACHED. + */ + usbd_device_event_irq(device, DEVICE_CREATE, 0); + + /* + * Some USB controller driver implementations signal + * DEVICE_HUB_CONFIGURED and DEVICE_RESET events here. + * DEVICE_HUB_CONFIGURED causes a transition to the state STATE_POWERED, + * and DEVICE_RESET causes a transition to the state STATE_DEFAULT. + * The SPEAr USB client controller has the capability to detect when the + * USB cable is connected to a powered USB bus, so we will defer the + * DEVICE_HUB_CONFIGURED and DEVICE_RESET events until later. + */ + + udc_enable(device); +} + +/* + * Plug detection interrupt handling + */ +void spear_udc_plug_irq(void) +{ + if (readl(&plug_regs_p->plug_state) & PLUG_STATUS_ATTACHED) { + /* + * USB cable attached + * Turn off PHY reset bit (PLUG detect). + * Switch PHY opmode to normal operation (PLUG detect). + */ + udc_connect(); + writel(DEV_INT_SOF, &udc_regs_p->dev_int_mask); + + UDCDBG("device attached and powered"); + udc_state_transition(udc_device->device_state, STATE_POWERED); + } else { + /* + * USB cable detached + * Reset the PHY and switch the mode. + */ + udc_disconnect(); + writel(~0x0, &udc_regs_p->dev_int_mask); + + UDCDBG("device detached or unpowered"); + udc_state_transition(udc_device->device_state, STATE_ATTACHED); + } +} + +/* + * Device interrupt handling + */ +void spear_udc_dev_irq(void) +{ + if (readl(&udc_regs_p->dev_int) & DEV_INT_USBRESET) { + writel(~0x0, &udc_regs_p->endp_int_mask); + + udc_connect(); + + writel(readl(&inep_regs_p[0].endp_cntl) | ENDP_CNTL_FLUSH, + &inep_regs_p[0].endp_cntl); + + writel(DEV_INT_USBRESET, &udc_regs_p->dev_int); + + UDCDBG("device reset in progess"); + udc_state_transition(udc_device->device_state, STATE_DEFAULT); + } + + /* Device Enumeration completed */ + if (readl(&udc_regs_p->dev_int) & DEV_INT_ENUM) { + writel(DEV_INT_ENUM, &udc_regs_p->dev_int); + + /* Endpoint interrupt enabled for Ctrl IN & Ctrl OUT */ + writel(readl(&udc_regs_p->endp_int_mask) & ~0x10001, + &udc_regs_p->endp_int_mask); + + UDCDBG("default -> addressed"); + udc_state_transition(udc_device->device_state, STATE_ADDRESSED); + } + + /* The USB will be in SUSPEND in 3 ms */ + if (readl(&udc_regs_p->dev_int) & DEV_INT_INACTIVE) { + writel(DEV_INT_INACTIVE, &udc_regs_p->dev_int); + + UDCDBG("entering inactive state"); + /* usbd_device_event_irq(udc_device, DEVICE_BUS_INACTIVE, 0); */ + } + + /* SetConfiguration command received */ + if (readl(&udc_regs_p->dev_int) & DEV_INT_SETCFG) { + writel(DEV_INT_SETCFG, &udc_regs_p->dev_int); + + UDCDBG("entering configured state"); + udc_state_transition(udc_device->device_state, + STATE_CONFIGURED); + } + + /* SetInterface command received */ + if (readl(&udc_regs_p->dev_int) & DEV_INT_SETINTF) + writel(DEV_INT_SETINTF, &udc_regs_p->dev_int); + + /* USB Suspend detected on cable */ + if (readl(&udc_regs_p->dev_int) & DEV_INT_SUSPUSB) { + writel(DEV_INT_SUSPUSB, &udc_regs_p->dev_int); + + UDCDBG("entering suspended state"); + usbd_device_event_irq(udc_device, DEVICE_BUS_INACTIVE, 0); + } + + /* USB Start-Of-Frame detected on cable */ + if (readl(&udc_regs_p->dev_int) & DEV_INT_SOF) + writel(DEV_INT_SOF, &udc_regs_p->dev_int); +} + +/* + * Endpoint interrupt handling + */ +void spear_udc_endpoint_irq(void) +{ + while (readl(&udc_regs_p->endp_int) & ENDP0_INT_CTRLOUT) { + + writel(ENDP0_INT_CTRLOUT, &udc_regs_p->endp_int); + + if ((readl(&outep_regs_p[0].endp_status) & ENDP_STATUS_OUTMSK) + == ENDP_STATUS_OUT_SETUP) { + spear_udc_setup(udc_device->bus->endpoint_array + 0); + writel(ENDP_STATUS_OUT_SETUP, + &outep_regs_p[0].endp_status); + + } else if ((readl(&outep_regs_p[0].endp_status) & + ENDP_STATUS_OUTMSK) == ENDP_STATUS_OUT_DATA) { + spear_udc_ep0_rx(udc_device->bus->endpoint_array + 0); + writel(ENDP_STATUS_OUT_DATA, + &outep_regs_p[0].endp_status); + + } else if ((readl(&outep_regs_p[0].endp_status) & + ENDP_STATUS_OUTMSK) == ENDP_STATUS_OUT_NONE) { + /* NONE received */ + } + + writel(0x0, &outep_regs_p[0].endp_status); + } + + if (readl(&udc_regs_p->endp_int) & ENDP0_INT_CTRLIN) { + spear_udc_ep0_tx(udc_device->bus->endpoint_array + 0); + + writel(ENDP_STATUS_IN, &inep_regs_p[0].endp_status); + writel(ENDP0_INT_CTRLIN, &udc_regs_p->endp_int); + } + + while (readl(&udc_regs_p->endp_int) & ENDP_INT_NONISOOUT_MSK) { + u32 epnum = 0; + u32 ep_int = readl(&udc_regs_p->endp_int) & + ENDP_INT_NONISOOUT_MSK; + + ep_int >>= 16; + while (0x0 == (ep_int & 0x1)) { + ep_int >>= 1; + epnum++; + } + + writel((1 << 16) << epnum, &udc_regs_p->endp_int); + + if ((readl(&outep_regs_p[epnum].endp_status) & + ENDP_STATUS_OUTMSK) == ENDP_STATUS_OUT_DATA) { + + spear_udc_epn_rx(epnum); + writel(ENDP_STATUS_OUT_DATA, + &outep_regs_p[epnum].endp_status); + } else if ((readl(&outep_regs_p[epnum].endp_status) & + ENDP_STATUS_OUTMSK) == ENDP_STATUS_OUT_NONE) { + writel(0x0, &outep_regs_p[epnum].endp_status); + } + } + + if (readl(&udc_regs_p->endp_int) & ENDP_INT_NONISOIN_MSK) { + u32 epnum = 0; + u32 ep_int = readl(&udc_regs_p->endp_int) & + ENDP_INT_NONISOIN_MSK; + + while (0x0 == (ep_int & 0x1)) { + ep_int >>= 1; + epnum++; + } + + if (readl(&inep_regs_p[epnum].endp_status) & ENDP_STATUS_IN) { + writel(ENDP_STATUS_IN, + &outep_regs_p[epnum].endp_status); + spear_udc_epn_tx(epnum); + + writel(ENDP_STATUS_IN, + &outep_regs_p[epnum].endp_status); + } + + writel((1 << epnum), &udc_regs_p->endp_int); + } +} + +/* + * UDC interrupts + */ +void udc_irq(void) +{ + /* + * Loop while we have interrupts. + * If we don't do this, the input chain + * polling delay is likely to miss + * host requests. + */ + while (readl(&plug_regs_p->plug_pending)) + spear_udc_plug_irq(); + + while (readl(&udc_regs_p->dev_int)) + spear_udc_dev_irq(); + + if (readl(&udc_regs_p->endp_int)) + spear_udc_endpoint_irq(); +} + +/* Flow control */ +void udc_set_nak(int epid) +{ + writel(readl(&inep_regs_p[epid].endp_cntl) | ENDP_CNTL_SNAK, + &inep_regs_p[epid].endp_cntl); + + writel(readl(&outep_regs_p[epid].endp_cntl) | ENDP_CNTL_SNAK, + &outep_regs_p[epid].endp_cntl); +} + +void udc_unset_nak(int epid) +{ + u32 val; + + val = readl(&inep_regs_p[epid].endp_cntl); + val &= ~ENDP_CNTL_SNAK; + val |= ENDP_CNTL_CNAK; + writel(val, &inep_regs_p[epid].endp_cntl); + + val = readl(&outep_regs_p[epid].endp_cntl); + val &= ~ENDP_CNTL_SNAK; + val |= ENDP_CNTL_CNAK; + writel(val, &outep_regs_p[epid].endp_cntl); +} diff --git a/roms/u-boot-sam460ex/drivers/usb/host/Makefile b/roms/u-boot-sam460ex/drivers/usb/host/Makefile new file mode 100644 index 000000000..255679a18 --- /dev/null +++ b/roms/u-boot-sam460ex/drivers/usb/host/Makefile @@ -0,0 +1,61 @@ +# +# (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_PPC4XX) += ehci-ppc4xx.o +COBJS-$(CONFIG_USB_EHCI_IXP4XX) += ehci-ixp.o +COBJS-$(CONFIG_USB_EHCI_KIRKWOOD) += ehci-kirkwood.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/roms/u-boot-sam460ex/drivers/usb/host/ehci-core.h b/roms/u-boot-sam460ex/drivers/usb/host/ehci-core.h new file mode 100644 index 000000000..39e5c5e58 --- /dev/null +++ b/roms/u-boot-sam460ex/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/roms/u-boot-sam460ex/drivers/usb/host/ehci-fsl.c b/roms/u-boot-sam460ex/drivers/usb/host/ehci-fsl.c new file mode 100644 index 000000000..c67492972 --- /dev/null +++ b/roms/u-boot-sam460ex/drivers/usb/host/ehci-fsl.c @@ -0,0 +1,79 @@ +/* + * (C) Copyright 2009 Freescale Semiconductor, Inc. + * + * (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 <asm/io.h> +#include <usb/ehci-fsl.h> + +#include "ehci.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) +{ + struct usb_ehci *ehci; + + ehci = (struct usb_ehci *)CONFIG_SYS_MPC8xxx_USB_ADDR; + hccr = (struct ehci_hccr *)((uint32_t)&ehci->caplength); + hcor = (struct ehci_hcor *)((uint32_t) hccr + + HC_LENGTH(ehci_readl(&hccr->cr_capbase))); + + /* Set to Host mode */ + setbits_le32(&ehci->usbmode, CM_HOST); + + out_be32(&ehci->snoop1, SNOOP_SIZE_2GB); + out_be32(&ehci->snoop2, 0x80000000 | SNOOP_SIZE_2GB); + + /* Init phy */ + if (!strcmp(getenv("usb_phy_type"), "utmi")) + out_le32(&(hcor->or_portsc[0]), PORT_PTS_UTMI); + else + out_le32(&(hcor->or_portsc[0]), PORT_PTS_ULPI); + + /* Enable interface. */ + setbits_be32(&ehci->control, USB_EN); + + out_be32(&ehci->prictrl, 0x0000000c); + out_be32(&ehci->age_cnt_limit, 0x00000040); + out_be32(&ehci->sictrl, 0x00000001); + + in_le32(&ehci->usbmode); + + return 0; +} + +/* + * Destroy the appropriate control structures corresponding + * the the EHCI host controller. + */ +int ehci_hcd_stop(void) +{ + return 0; +} diff --git a/roms/u-boot-sam460ex/drivers/usb/host/ehci-hcd.c b/roms/u-boot-sam460ex/drivers/usb/host/ehci-hcd.c new file mode 100644 index 000000000..6eb38a413 --- /dev/null +++ b/roms/u-boot-sam460ex/drivers/usb/host/ehci-hcd.c @@ -0,0 +1,892 @@ +/*- + * 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 <watchdog.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 */ + cpu_to_le16(0x0200), /* bcdUSB: v2.0 */ + 9, /* bDeviceClass: UDCLASS_HUB */ + 0, /* bDeviceSubClass: UDSUBCLASS_HUB */ + 1, /* bDeviceProtocol: UDPROTO_HSHUBSTT */ + 64, /* bMaxPacketSize: 64 bytes */ + 0x0000, /* idVendor */ + 0x0000, /* idProduct */ + cpu_to_le16(0x0100), /* 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, /* 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); + udelay(5); + if (result == ~(uint32_t)0) + return -1; + result &= mask; + if (result == done) + return 0; + 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 & ~CMD_RUN) | 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, 0, sz); + 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); + td->qt_buffer_hi[idx] = 0; + 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); + + 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; + WATCHDOG_RESET(); + } 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; + if ((token & 0x40) == 0x40) + dev->status |= USB_ST_STALLED; + 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 | req->requesttype << 8; + + switch (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) + tmpbuf[0] |= USB_PORT_STAT_RESET; + 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 { + int ret; + + 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); + /* terminate the reset */ + ehci_writel(status_reg, reg & ~EHCI_PS_PR); + /* + * A host controller must terminate the reset + * and stabilize the state of the port within + * 2 milliseconds + */ + ret = handshake(status_reg, EHCI_PS_PR, 0, + 2 * 1000); + if (!ret) + portreset |= + 1 << le16_to_cpu(req->index); + else + printf("port(%d) reset error\n", + le16_to_cpu(req->index) - 1); + } + break; + default: + debug("unknown feature %x\n", le16_to_cpu(req->value)); + goto unknown; + } + /* unblock posted writes */ + (void) 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 */ + (void) 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/roms/u-boot-sam460ex/drivers/usb/host/ehci-ixp4xx.c b/roms/u-boot-sam460ex/drivers/usb/host/ehci-ixp4xx.c new file mode 100644 index 000000000..b8f15ae52 --- /dev/null +++ b/roms/u-boot-sam460ex/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/roms/u-boot-sam460ex/drivers/usb/host/ehci-kirkwood.c b/roms/u-boot-sam460ex/drivers/usb/host/ehci-kirkwood.c new file mode 100644 index 000000000..5570fc699 --- /dev/null +++ b/roms/u-boot-sam460ex/drivers/usb/host/ehci-kirkwood.c @@ -0,0 +1,107 @@ +/* + * (C) Copyright 2009 + * Marvell Semiconductor <www.marvell.com> + * Written-by: Prafulla Wadaskar <prafulla@marvell.com> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include <common.h> +#include <asm/io.h> +#include <usb.h> +#include "ehci.h" +#include "ehci-core.h" +#include <asm/arch/kirkwood.h> + +#define rdl(off) readl(KW_USB20_BASE + (off)) +#define wrl(off, val) writel((val), KW_USB20_BASE + (off)) + +#define USB_WINDOW_CTRL(i) (0x320 + ((i) << 4)) +#define USB_WINDOW_BASE(i) (0x324 + ((i) << 4)) +#define USB_TARGET_DRAM 0x0 + +/* + * USB 2.0 Bridge Address Decoding registers setup + */ +static void usb_brg_adrdec_setup(void) +{ + int i; + u32 size, attrib; + + for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) { + + /* Enable DRAM bank */ + switch (i) { + case 0: + attrib = KWCPU_ATTR_DRAM_CS0; + break; + case 1: + attrib = KWCPU_ATTR_DRAM_CS1; + break; + case 2: + attrib = KWCPU_ATTR_DRAM_CS2; + break; + case 3: + attrib = KWCPU_ATTR_DRAM_CS3; + break; + default: + /* invalide bank, disable access */ + attrib = 0; + break; + } + + size = kw_sdram_bs(i); + if ((size) && (attrib)) + wrl(USB_WINDOW_CTRL(i), + KWCPU_WIN_CTRL_DATA(size, USB_TARGET_DRAM, + attrib, KWCPU_WIN_ENABLE)); + else + wrl(USB_WINDOW_CTRL(i), KWCPU_WIN_DISABLE); + + wrl(USB_WINDOW_BASE(i), kw_sdram_bar(i)); + } +} + +/* + * Create the appropriate control structures to manage + * a new EHCI host controller. + */ +int ehci_hcd_init(void) +{ + usb_brg_adrdec_setup(); + + hccr = (struct ehci_hccr *)(KW_USB20_BASE + 0x100); + hcor = (struct ehci_hcor *)((uint32_t) hccr + + HC_LENGTH(ehci_readl(&hccr->cr_capbase))); + + debug("Kirkwood-ehci: 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/roms/u-boot-sam460ex/drivers/usb/host/ehci-pci.c b/roms/u-boot-sam460ex/drivers/usb/host/ehci-pci.c new file mode 100644 index 000000000..047902a0c --- /dev/null +++ b/roms/u-boot-sam460ex/drivers/usb/host/ehci-pci.c @@ -0,0 +1,66 @@ +/*- + * 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 */ + {0x1033, 0x00E0}, + {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/roms/u-boot-sam460ex/drivers/usb/host/ehci-ppc4xx.c b/roms/u-boot-sam460ex/drivers/usb/host/ehci-ppc4xx.c new file mode 100644 index 000000000..238a1dc28 --- /dev/null +++ b/roms/u-boot-sam460ex/drivers/usb/host/ehci-ppc4xx.c @@ -0,0 +1,48 @@ +/* + * (C) Copyright 2010, Chris Zhang <chris@seamicro.com> + * + * Author: Chris Zhang <chris@seamicro.com> + * 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 *)(CONFIG_SYS_PPC4XX_USB_ADDR); + hcor = (struct ehci_hcor *)((uint32_t) hccr + + HC_LENGTH(ehci_readl(&hccr->cr_capbase))); + //usb_dev_init(); + return 0; +} + +/* + * Destroy the appropriate control structures corresponding + * the the EHCI host controller. + */ +int ehci_hcd_stop(void) +{ + return 0; +} diff --git a/roms/u-boot-sam460ex/drivers/usb/host/ehci-vct.c b/roms/u-boot-sam460ex/drivers/usb/host/ehci-vct.c new file mode 100644 index 000000000..3063dd172 --- /dev/null +++ b/roms/u-boot-sam460ex/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/roms/u-boot-sam460ex/drivers/usb/host/ehci.h b/roms/u-boot-sam460ex/drivers/usb/host/ehci.h new file mode 100644 index 000000000..b3c1d5d72 --- /dev/null +++ b/roms/u-boot-sam460ex/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/roms/u-boot-sam460ex/drivers/usb/host/isp116x-hcd.c b/roms/u-boot-sam460ex/drivers/usb/host/isp116x-hcd.c new file mode 100644 index 000000000..abdcbb4b2 --- /dev/null +++ b/roms/u-boot-sam460ex/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/arch/mips/cpu/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 <garyj@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/roms/u-boot-sam460ex/drivers/usb/host/isp116x.h b/roms/u-boot-sam460ex/drivers/usb/host/isp116x.h new file mode 100644 index 000000000..a3ce3b582 --- /dev/null +++ b/roms/u-boot-sam460ex/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/roms/u-boot-sam460ex/drivers/usb/host/ohci-at91.c b/roms/u-boot-sam460ex/drivers/usb/host/ohci-at91.c new file mode 100644 index 000000000..b2e03bc58 --- /dev/null +++ b/roms/u-boot-sam460ex/drivers/usb/host/ohci-at91.c @@ -0,0 +1,85 @@ +/* + * (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> +#include <asm/arch/clk.h> + +int usb_cpu_init(void) +{ + at91_pmc_t *pmc = (at91_pmc_t *)AT91_PMC_BASE; + +#if defined(CONFIG_AT91CAP9) || defined(CONFIG_AT91SAM9260) || \ + defined(CONFIG_AT91SAM9263) || defined(CONFIG_AT91SAM9G20) || \ + defined(CONFIG_AT91SAM9261) + /* Enable PLLB */ + writel(get_pllb_init(), &pmc->pllbr); + while ((readl(&pmc->sr) & AT91_PMC_LOCKB) != AT91_PMC_LOCKB) + ; +#endif + + /* Enable USB host clock. */ + writel(1 << AT91_ID_UHP, &pmc->pcer); +#ifdef CONFIG_AT91SAM9261 + writel(AT91_PMC_UHP | AT91_PMC_HCK0, &pmc->scer); +#else + writel(AT91_PMC_UHP, &pmc->scer); +#endif + + return 0; +} + +int usb_cpu_stop(void) +{ + at91_pmc_t *pmc = (at91_pmc_t *)AT91_PMC_BASE; + + /* Disable USB host clock. */ + writel(1 << AT91_ID_UHP, &pmc->pcdr); +#ifdef CONFIG_AT91SAM9261 + writel(AT91_PMC_UHP | AT91_PMC_HCK0, &pmc->scdr); +#else + writel(AT91_PMC_UHP, &pmc->scdr); +#endif + +#if defined(CONFIG_AT91CAP9) || defined(CONFIG_AT91SAM9260) || \ + defined(CONFIG_AT91SAM9263) || defined(CONFIG_AT91SAM9G20) + /* Disable PLLB */ + writel(0, &pmc->pllbr); + while ((readl(&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/roms/u-boot-sam460ex/drivers/usb/host/ohci-hcd-test.c b/roms/u-boot-sam460ex/drivers/usb/host/ohci-hcd-test.c new file mode 100644 index 000000000..e29d4eb4d --- /dev/null +++ b/roms/u-boot-sam460ex/drivers/usb/host/ohci-hcd-test.c @@ -0,0 +1,2024 @@ +/* + * 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 <garyj@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_S3C24X0) || \ + 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 */ + {0x126f, 0x0501}, /* SM502 */ + /* 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(5); +#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 *********/ + //printf("port reset\n"); + 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(5); +#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(5); +#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(5); +#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(5); + /* 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 = 1000; + + /* 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(5); +#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(5); +#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(5); +#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); + if ((vid == 0x126f) && (did == 0x0501)) + { + pci_read_config_dword(pdev, PCI_BASE_ADDRESS_1, &base); + base += 0x40000; + } + else + 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(5); +#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/roms/u-boot-sam460ex/drivers/usb/host/ohci-hcd.c b/roms/u-boot-sam460ex/drivers/usb/host/ohci-hcd.c new file mode 100644 index 000000000..8ed05b3aa --- /dev/null +++ b/roms/u-boot-sam460ex/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 <garyj@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_S3C24X0) || \ + 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) +#define err(format, arg...) do {} while (0) +#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(2); // fixme +#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(2); // fixme +#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(2); // fixme +#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(2); // fixme +#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 = 1000; + + /* 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(2); // fixme + 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(2); // fixme +#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(2); // fixme +#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(2); // fixme +#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(2); // fixme + 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(2); // fixme + 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(2); // fixme +#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/roms/u-boot-sam460ex/drivers/usb/host/ohci.h b/roms/u-boot-sam460ex/drivers/usb/host/ohci.h new file mode 100644 index 000000000..79aa79dc6 --- /dev/null +++ b/roms/u-boot-sam460ex/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/roms/u-boot-sam460ex/drivers/usb/host/r8a66597-hcd.c b/roms/u-boot-sam460ex/drivers/usb/host/r8a66597-hcd.c new file mode 100644 index 000000000..db611b641 --- /dev/null +++ b/roms/u-boot-sam460ex/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/roms/u-boot-sam460ex/drivers/usb/host/r8a66597.h b/roms/u-boot-sam460ex/drivers/usb/host/r8a66597.h new file mode 100644 index 000000000..9af6446c1 --- /dev/null +++ b/roms/u-boot-sam460ex/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/roms/u-boot-sam460ex/drivers/usb/host/s3c64xx-hcd.c b/roms/u-boot-sam460ex/drivers/usb/host/s3c64xx-hcd.c new file mode 100644 index 000000000..cd295dabb --- /dev/null +++ b/roms/u-boot-sam460ex/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 <asm/arch/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/roms/u-boot-sam460ex/drivers/usb/host/sl811-hcd.c b/roms/u-boot-sam460ex/drivers/usb/host/sl811-hcd.c new file mode 100644 index 000000000..82a8b365f --- /dev/null +++ b/roms/u-boot-sam460ex/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/roms/u-boot-sam460ex/drivers/usb/host/sl811.h b/roms/u-boot-sam460ex/drivers/usb/host/sl811.h new file mode 100644 index 000000000..c1f9f013b --- /dev/null +++ b/roms/u-boot-sam460ex/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 */ diff --git a/roms/u-boot-sam460ex/drivers/usb/musb/Makefile b/roms/u-boot-sam460ex/drivers/usb/musb/Makefile new file mode 100644 index 000000000..397f5fe7a --- /dev/null +++ b/roms/u-boot-sam460ex/drivers/usb/musb/Makefile @@ -0,0 +1,51 @@ +# +# (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_musb.a + +COBJS-$(CONFIG_MUSB_HCD) += musb_hcd.o musb_core.o +COBJS-$(CONFIG_MUSB_UDC) += musb_udc.o musb_core.o +COBJS-$(CONFIG_USB_BLACKFIN) += blackfin_usb.o +COBJS-$(CONFIG_USB_DAVINCI) += davinci.o +COBJS-$(CONFIG_USB_OMAP3) += omap3.o +COBJS-$(CONFIG_USB_DA8XX) += da8xx.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/roms/u-boot-sam460ex/drivers/usb/musb/blackfin_usb.c b/roms/u-boot-sam460ex/drivers/usb/musb/blackfin_usb.c new file mode 100644 index 000000000..38aceb2e9 --- /dev/null +++ b/roms/u-boot-sam460ex/drivers/usb/musb/blackfin_usb.c @@ -0,0 +1,143 @@ +/* + * Blackfin MUSB HCD (Host Controller Driver) for u-boot + * + * Copyright (c) 2008-2009 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include <common.h> + +#include <usb.h> + +#include <asm/blackfin.h> +#include <asm/mach-common/bits/usb.h> + +#include "musb_core.h" + +/* MUSB platform configuration */ +struct musb_config musb_cfg = { + .regs = (struct musb_regs *)USB_FADDR, + .timeout = 0x3FFFFFF, + .musb_speed = 0, +}; + +/* + * This function read or write data to endpoint fifo + * Blackfin use DMA polling method to avoid buffer alignment issues + * + * ep - Endpoint number + * length - Number of bytes to write to FIFO + * fifo_data - Pointer to data buffer to be read/write + * is_write - Flag for read or write + */ +void rw_fifo(u8 ep, u32 length, void *fifo_data, int is_write) +{ + struct bfin_musb_dma_regs *regs; + u32 val = (u32)fifo_data; + + blackfin_dcache_flush_invalidate_range(fifo_data, fifo_data + length); + + regs = (void *)USB_DMA_INTERRUPT; + regs += ep; + + /* Setup DMA address register */ + bfin_write16(®s->addr_low, val); + SSYNC(); + + bfin_write16(®s->addr_high, val >> 16); + SSYNC(); + + /* Setup DMA count register */ + bfin_write16(®s->count_low, length); + bfin_write16(®s->count_high, 0); + SSYNC(); + + /* Enable the DMA */ + val = (ep << 4) | DMA_ENA | INT_ENA; + if (is_write) + val |= DIRECTION; + bfin_write16(®s->control, val); + SSYNC(); + + /* Wait for compelete */ + while (!(bfin_read_USB_DMA_INTERRUPT() & (1 << ep))) + continue; + + /* acknowledge dma interrupt */ + bfin_write_USB_DMA_INTERRUPT(1 << ep); + SSYNC(); + + /* Reset DMA */ + bfin_write16(®s->control, 0); + SSYNC(); +} + +void write_fifo(u8 ep, u32 length, void *fifo_data) +{ + rw_fifo(ep, length, fifo_data, 1); +} + +void read_fifo(u8 ep, u32 length, void *fifo_data) +{ + rw_fifo(ep, length, fifo_data, 0); +} + + +/* + * CPU and board-specific MUSB initializations. Aliased function + * signals caller to move on. + */ +static void __def_musb_init(void) +{ +} +void board_musb_init(void) __attribute__((weak, alias("__def_musb_init"))); + +int musb_platform_init(void) +{ + /* board specific initialization */ + board_musb_init(); + + if (ANOMALY_05000346) { + bfin_write_USB_APHY_CALIB(ANOMALY_05000346_value); + SSYNC(); + } + + if (ANOMALY_05000347) { + bfin_write_USB_APHY_CNTRL(0x0); + SSYNC(); + } + + /* Configure PLL oscillator register */ + bfin_write_USB_PLLOSC_CTRL(0x30a8); + SSYNC(); + + bfin_write_USB_SRP_CLKDIV((get_sclk()/1000) / 32 - 1); + SSYNC(); + + bfin_write_USB_EP_NI0_RXMAXP(64); + SSYNC(); + + bfin_write_USB_EP_NI0_TXMAXP(64); + SSYNC(); + + /* Route INTRUSB/INTR_RX/INTR_TX to USB_INT0*/ + bfin_write_USB_GLOBINTR(0x7); + SSYNC(); + + bfin_write_USB_GLOBAL_CTL(GLOBAL_ENA | EP1_TX_ENA | EP2_TX_ENA | + EP3_TX_ENA | EP4_TX_ENA | EP5_TX_ENA | + EP6_TX_ENA | EP7_TX_ENA | EP1_RX_ENA | + EP2_RX_ENA | EP3_RX_ENA | EP4_RX_ENA | + EP5_RX_ENA | EP6_RX_ENA | EP7_RX_ENA); + SSYNC(); + + return 0; +} + +/* + * This function performs Blackfin platform specific deinitialization for usb. +*/ +void musb_platform_deinit(void) +{ +} diff --git a/roms/u-boot-sam460ex/drivers/usb/musb/blackfin_usb.h b/roms/u-boot-sam460ex/drivers/usb/musb/blackfin_usb.h new file mode 100644 index 000000000..de994bf33 --- /dev/null +++ b/roms/u-boot-sam460ex/drivers/usb/musb/blackfin_usb.h @@ -0,0 +1,99 @@ +/* + * Blackfin MUSB HCD (Host Controller Driver) for u-boot + * + * Copyright (c) 2008-2009 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#ifndef __BLACKFIN_USB_H__ +#define __BLACKFIN_USB_H__ + +#include <linux/types.h> + +/* Every register is 32bit aligned, but only 16bits in size */ +#define ureg(name) u16 name; u16 __pad_##name; + +#define musb_regs musb_regs +struct musb_regs { + /* common registers */ + ureg(faddr) + ureg(power) + ureg(intrtx) + ureg(intrrx) + ureg(intrtxe) + ureg(intrrxe) + ureg(intrusb) + ureg(intrusbe) + ureg(frame) + ureg(index) + ureg(testmode) + ureg(globintr) + ureg(global_ctl) + u32 reserved0[3]; + /* indexed registers */ + ureg(txmaxp) + ureg(txcsr) + ureg(rxmaxp) + ureg(rxcsr) + ureg(rxcount) + ureg(txtype) + ureg(txinterval) + ureg(rxtype) + ureg(rxinterval) + u32 reserved1; + ureg(txcount) + u32 reserved2[5]; + /* fifo */ + u16 fifox[32]; + /* OTG, dynamic FIFO, version & vendor registers */ + u32 reserved3[16]; + ureg(devctl) + ureg(vbus_irq) + ureg(vbus_mask) + u32 reserved4[15]; + ureg(linkinfo) + ureg(vplen) + ureg(hseof1) + ureg(fseof1) + ureg(lseof1) + u32 reserved5[41]; + /* target address registers */ + struct musb_tar_regs { + ureg(txmaxp) + ureg(txcsr) + ureg(rxmaxp) + ureg(rxcsr) + ureg(rxcount) + ureg(txtype) + ureg(txinternal) + ureg(rxtype) + ureg(rxinternal) + u32 reserved6; + ureg(txcount) + u32 reserved7[5]; + } tar[8]; +} __attribute__((packed)); + +struct bfin_musb_dma_regs { + ureg(interrupt); + ureg(control); + ureg(addr_low); + ureg(addr_high); + ureg(count_low); + ureg(count_high); + u32 reserved0[2]; +}; + +#undef ureg + +/* EP5-EP7 are the only ones with 1024 byte FIFOs which BULK really needs */ +#define MUSB_BULK_EP 5 + +/* Blackfin FIFO's are static */ +#define MUSB_NO_DYNAMIC_FIFO + +/* No HUB support :( */ +#define MUSB_NO_MULTIPOINT + +#endif diff --git a/roms/u-boot-sam460ex/drivers/usb/musb/da8xx.c b/roms/u-boot-sam460ex/drivers/usb/musb/da8xx.c new file mode 100644 index 000000000..40bfe440d --- /dev/null +++ b/roms/u-boot-sam460ex/drivers/usb/musb/da8xx.c @@ -0,0 +1,139 @@ +/* + * da8xx.c - TI's DA8xx platform specific usb wrapper functions. + * + * Author: Ajay Kumar Gupta <ajay.gupta@ti.com> + * + * Based on drivers/usb/musb/davinci.c + * + * Copyright (C) 2009 Texas Instruments Incorporated + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#include <common.h> + +#include "da8xx.h" + +/* MUSB platform configuration */ +struct musb_config musb_cfg = { + (struct musb_regs *)DA8XX_USB_OTG_CORE_BASE, + DA8XX_USB_OTG_TIMEOUT, + 0 +}; + +/* + * This function enables VBUS by driving the GPIO Bank4 Pin 15 high. + */ +static void enable_vbus(void) +{ + u32 value; + + /* configure GPIO bank4 pin 15 in output direction */ + value = readl(&davinci_gpio_bank45->dir); + writel((value & (~DA8XX_USB_VBUS_GPIO)), &davinci_gpio_bank45->dir); + + /* set GPIO bank4 pin 15 high to drive VBUS */ + value = readl(&davinci_gpio_bank45->set_data); + writel((value | DA8XX_USB_VBUS_GPIO), &davinci_gpio_bank45->set_data); +} + +/* + * Enable the usb0 phy. This initialization procedure is explained in + * the DA8xx USB user guide document. + */ +static u8 phy_on(void) +{ + u32 timeout; + u32 cfgchip2; + + cfgchip2 = readl(&davinci_syscfg_regs->cfgchip2); + + cfgchip2 &= ~(CFGCHIP2_RESET | CFGCHIP2_PHYPWRDN | CFGCHIP2_OTGPWRDN | + CFGCHIP2_OTGMODE | CFGCHIP2_REFFREQ); + cfgchip2 |= CFGCHIP2_SESENDEN | CFGCHIP2_VBDTCTEN | CFGCHIP2_PHY_PLLON | + CFGCHIP2_REFFREQ_24MHZ; + + writel(cfgchip2, &davinci_syscfg_regs->cfgchip2); + + /* wait until the usb phy pll locks */ + timeout = musb_cfg.timeout; + while (timeout--) + if (readl(&davinci_syscfg_regs->cfgchip2) & CFGCHIP2_PHYCLKGD) + return 1; + + /* USB phy was not turned on */ + return 0; +} + +/* + * Disable the usb phy + */ +static void phy_off(void) +{ + u32 cfgchip2; + + /* + * Power down the on-chip PHY. + */ + cfgchip2 = readl(&davinci_syscfg_regs->cfgchip2); + cfgchip2 &= ~CFGCHIP2_PHY_PLLON; + cfgchip2 |= CFGCHIP2_PHYPWRDN | CFGCHIP2_OTGPWRDN; + writel(cfgchip2, &davinci_syscfg_regs->cfgchip2); +} + +/* + * This function performs DA8xx platform specific initialization for usb0. + */ +int musb_platform_init(void) +{ + u32 revision; + + /* enable psc for usb2.0 */ + lpsc_on(33); + + /* enable usb vbus */ + enable_vbus(); + + /* reset the controller */ + writel(0x1, &da8xx_usb_regs->control); + udelay(5000); + + /* start the on-chip usb phy and its pll */ + if (phy_on() == 0) + return -1; + + /* Returns zero if e.g. not clocked */ + revision = readl(&da8xx_usb_regs->revision); + if (revision == 0) + return -1; + + /* Disable all interrupts */ + writel((DA8XX_USB_USBINT_MASK | DA8XX_USB_TXINT_MASK | + DA8XX_USB_RXINT_MASK), &da8xx_usb_regs->intmsk_set); + return 0; +} + +/* + * This function performs DA8xx platform specific deinitialization for usb0. + */ +void musb_platform_deinit(void) +{ + /* Turn of the phy */ + phy_off(); + + /* flush any interrupts */ + writel((DA8XX_USB_USBINT_MASK | DA8XX_USB_TXINT_MASK | + DA8XX_USB_RXINT_MASK), &da8xx_usb_regs->intmsk_clr); + writel(0, &da8xx_usb_regs->eoi); +} diff --git a/roms/u-boot-sam460ex/drivers/usb/musb/da8xx.h b/roms/u-boot-sam460ex/drivers/usb/musb/da8xx.h new file mode 100644 index 000000000..b9660ba80 --- /dev/null +++ b/roms/u-boot-sam460ex/drivers/usb/musb/da8xx.h @@ -0,0 +1,102 @@ +/* + * da8xx.h -- TI's DA8xx platform specific usb wrapper definitions. + * + * Author: Ajay Kumar Gupta <ajay.gupta@ti.com> + * + * Based on drivers/usb/musb/davinci.h + * + * Copyright (C) 2009 Texas Instruments Incorporated + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#ifndef __DA8XX_MUSB_H__ +#define __DA8XX_MUSB_H__ + +#include <asm/arch/hardware.h> +#include <asm/arch/gpio_defs.h> +#include "musb_core.h" + +/* Base address of da8xx usb0 wrapper */ +#define DA8XX_USB_OTG_BASE 0x01E00000 + +/* Base address of da8xx musb core */ +#define DA8XX_USB_OTG_CORE_BASE (DA8XX_USB_OTG_BASE + 0x400) + +/* Timeout for DA8xx usb module */ +#define DA8XX_USB_OTG_TIMEOUT 0x3FFFFFF + +/* + * DA8xx platform USB wrapper register overlay. + */ +struct da8xx_usb_regs { + dv_reg revision; + dv_reg control; + dv_reg status; + dv_reg emulation; + dv_reg mode; + dv_reg autoreq; + dv_reg srpfixtime; + dv_reg teardown; + dv_reg intsrc; + dv_reg intsrc_set; + dv_reg intsrc_clr; + dv_reg intmsk; + dv_reg intmsk_set; + dv_reg intmsk_clr; + dv_reg intsrcmsk; + dv_reg eoi; + dv_reg intvector; + dv_reg grndis_size[4]; +}; + +#define da8xx_usb_regs ((struct da8xx_usb_regs *)DA8XX_USB_OTG_BASE) + +/* DA8XX interrupt bits definitions */ +#define DA8XX_USB_TX_ENDPTS_MASK 0x1f /* ep0 + 4 tx */ +#define DA8XX_USB_RX_ENDPTS_MASK 0x1e /* 4 rx */ +#define DA8XX_USB_TXINT_SHIFT 0 +#define DA8XX_USB_RXINT_SHIFT 8 + +#define DA8XX_USB_USBINT_MASK 0x01ff0000 /* 8 Mentor, DRVVBUS */ +#define DA8XX_USB_TXINT_MASK \ + (DA8XX_USB_TX_ENDPTS_MASK << DA8XX_USB_TXINT_SHIFT) +#define DA8XX_USB_RXINT_MASK \ + (DA8XX_USB_RX_ENDPTS_MASK << DA8XX_USB_RXINT_SHIFT) + +/* DA8xx CFGCHIP2 (USB 2.0 PHY Control) register bits */ +#define CFGCHIP2_PHYCLKGD (1 << 17) +#define CFGCHIP2_VBUSSENSE (1 << 16) +#define CFGCHIP2_RESET (1 << 15) +#define CFGCHIP2_OTGMODE (3 << 13) +#define CFGCHIP2_NO_OVERRIDE (0 << 13) +#define CFGCHIP2_FORCE_HOST (1 << 13) +#define CFGCHIP2_FORCE_DEVICE (2 << 13) +#define CFGCHIP2_FORCE_HOST_VBUS_LOW (3 << 13) +#define CFGCHIP2_USB1PHYCLKMUX (1 << 12) +#define CFGCHIP2_USB2PHYCLKMUX (1 << 11) +#define CFGCHIP2_PHYPWRDN (1 << 10) +#define CFGCHIP2_OTGPWRDN (1 << 9) +#define CFGCHIP2_DATPOL (1 << 8) +#define CFGCHIP2_USB1SUSPENDM (1 << 7) +#define CFGCHIP2_PHY_PLLON (1 << 6) /* override PLL suspend */ +#define CFGCHIP2_SESENDEN (1 << 5) /* Vsess_end comparator */ +#define CFGCHIP2_VBDTCTEN (1 << 4) /* Vbus comparator */ +#define CFGCHIP2_REFFREQ (0xf << 0) +#define CFGCHIP2_REFFREQ_12MHZ (1 << 0) +#define CFGCHIP2_REFFREQ_24MHZ (2 << 0) +#define CFGCHIP2_REFFREQ_48MHZ (3 << 0) + +#define DA8XX_USB_VBUS_GPIO (1 << 15) +#endif /* __DA8XX_MUSB_H__ */ diff --git a/roms/u-boot-sam460ex/drivers/usb/musb/davinci.c b/roms/u-boot-sam460ex/drivers/usb/musb/davinci.c new file mode 100644 index 000000000..8fbadc962 --- /dev/null +++ b/roms/u-boot-sam460ex/drivers/usb/musb/davinci.c @@ -0,0 +1,123 @@ +/* + * TI's Davinci platform specific USB wrapper functions. + * + * Copyright (c) 2008 Texas Instruments + * + * 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 + * + * Author: Thomas Abraham t-abraham@ti.com, Texas Instruments + */ + +#include <common.h> +#include <asm/io.h> +#include "davinci.h" +#include <asm/arch/hardware.h> + +/* MUSB platform configuration */ +struct musb_config musb_cfg = { + (struct musb_regs *)MENTOR_USB0_BASE, + DAVINCI_USB_TIMEOUT, + 0 +}; + +/* MUSB module register overlay */ +struct davinci_usb_regs *dregs; + +/* + * Enable the USB phy + */ +static u8 phy_on(void) +{ + u32 timeout; +#ifdef DAVINCI_DM365EVM + u32 val; +#endif + /* Wait until the USB phy is turned on */ +#ifdef DAVINCI_DM365EVM + writel(USBPHY_PHY24MHZ | USBPHY_SESNDEN | + USBPHY_VBDTCTEN, USBPHY_CTL_PADDR); +#else + writel(USBPHY_SESNDEN | USBPHY_VBDTCTEN, USBPHY_CTL_PADDR); +#endif + timeout = musb_cfg.timeout; + +#ifdef DAVINCI_DM365EVM + /* Set the ownership of GIO33 to USB */ + val = readl(PINMUX4); + val &= ~(PINMUX4_USBDRVBUS_BITCLEAR); + val |= PINMUX4_USBDRVBUS_BITSET; + writel(val, PINMUX4); +#endif + while (timeout--) + if (readl(USBPHY_CTL_PADDR) & USBPHY_PHYCLKGD) + return 1; + + /* USB phy was not turned on */ + return 0; +} + +/* + * Disable the USB phy + */ +static void phy_off(void) +{ + /* powerdown the on-chip PHY and its oscillator */ + writel(USBPHY_OSCPDWN | USBPHY_PHYPDWN, USBPHY_CTL_PADDR); +} + +/* + * This function performs Davinci platform specific initialization for usb0. + */ +int musb_platform_init(void) +{ + u32 revision; + + /* enable USB VBUS */ +#ifndef DAVINCI_DM365EVM + enable_vbus(); +#endif + /* start the on-chip USB phy and its pll */ + if (!phy_on()) + return -1; + + /* reset the controller */ + dregs = (struct davinci_usb_regs *)DAVINCI_USB0_BASE; + writel(1, &dregs->ctrlr); + udelay(5000); + + /* Returns zero if e.g. not clocked */ + revision = readl(&dregs->version); + if (!revision) + return -1; + + /* Disable all interrupts */ + writel(DAVINCI_USB_USBINT_MASK | DAVINCI_USB_RXINT_MASK | + DAVINCI_USB_TXINT_MASK , &dregs->intmsksetr); + return 0; +} + +/* + * This function performs Davinci platform specific deinitialization for usb0. + */ +void musb_platform_deinit(void) +{ + /* Turn of the phy */ + phy_off(); + + /* flush any interrupts */ + writel(DAVINCI_USB_USBINT_MASK | DAVINCI_USB_TXINT_MASK | + DAVINCI_USB_RXINT_MASK , &dregs->intclrr); +} diff --git a/roms/u-boot-sam460ex/drivers/usb/musb/davinci.h b/roms/u-boot-sam460ex/drivers/usb/musb/davinci.h new file mode 100644 index 000000000..e0829d60d --- /dev/null +++ b/roms/u-boot-sam460ex/drivers/usb/musb/davinci.h @@ -0,0 +1,87 @@ +/* + * TI's Davinci platform specific USB wrapper functions. + * + * Copyright (c) 2008 Texas Instruments + * + * 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 + * + * Author: Thomas Abraham t-abraham@ti.com, Texas Instruments + */ + +#ifndef __DAVINCI_USB_H__ +#define __DAVINCI_USB_H__ + +#include <asm/arch/hardware.h> +#include "musb_core.h" + +/* Base address of DAVINCI usb0 wrapper */ +#define DAVINCI_USB0_BASE 0x01C64000 + +/* Base address of DAVINCI musb core */ +#define MENTOR_USB0_BASE (DAVINCI_USB0_BASE+0x400) + +/* + * Davinci platform USB wrapper register overlay. Note: Only the required + * registers are included in this structure. It can be expanded as required. + */ +struct davinci_usb_regs { + u32 version; + u32 ctrlr; + u32 reserved[0x20]; + u32 intclrr; + u32 intmskr; + u32 intmsksetr; +}; + +#define DAVINCI_USB_TX_ENDPTS_MASK 0x1f /* ep0 + 4 tx */ +#define DAVINCI_USB_RX_ENDPTS_MASK 0x1e /* 4 rx */ +#define DAVINCI_USB_USBINT_SHIFT 16 +#define DAVINCI_USB_TXINT_SHIFT 0 +#define DAVINCI_USB_RXINT_SHIFT 8 +#define DAVINCI_INTR_DRVVBUS 0x0100 + +#define DAVINCI_USB_USBINT_MASK 0x01ff0000 /* 8 Mentor, DRVVBUS */ +#define DAVINCI_USB_TXINT_MASK \ + (DAVINCI_USB_TX_ENDPTS_MASK << DAVINCI_USB_TXINT_SHIFT) +#define DAVINCI_USB_RXINT_MASK \ + (DAVINCI_USB_RX_ENDPTS_MASK << DAVINCI_USB_RXINT_SHIFT) +#define MGC_BUSCTL_OFFSET(_bEnd, _bOffset) \ + (0x80 + (8*(_bEnd)) + (_bOffset)) + +/* Integrated highspeed/otg PHY */ +#define USBPHY_CTL_PADDR (DAVINCI_SYSTEM_MODULE_BASE + 0x34) +#define USBPHY_PHY24MHZ (1 << 13) +#define USBPHY_PHYCLKGD (1 << 8) +#define USBPHY_SESNDEN (1 << 7) /* v(sess_end) comparator */ +#define USBPHY_VBDTCTEN (1 << 6) /* v(bus) comparator */ +#define USBPHY_PHYPLLON (1 << 4) /* override pll suspend */ +#define USBPHY_CLKO1SEL (1 << 3) +#define USBPHY_OSCPDWN (1 << 2) +#define USBPHY_PHYPDWN (1 << 0) + +/* Timeout for Davinci USB module */ +#define DAVINCI_USB_TIMEOUT 0x3FFFFFF + +/* IO Expander I2C address and VBUS enable mask */ +#define IOEXP_I2C_ADDR 0x3A +#define IOEXP_VBUSEN_MASK 1 + +/* extern functions */ +extern void lpsc_on(unsigned int id); +extern int i2c_write(uchar chip, uint addr, int alen, uchar *buffer, int len); +extern int i2c_read(uchar chip, uint addr, int alen, uchar *buffer, int len); +extern void enable_vbus(void); +#endif /* __DAVINCI_USB_H__ */ diff --git a/roms/u-boot-sam460ex/drivers/usb/musb/musb_core.c b/roms/u-boot-sam460ex/drivers/usb/musb/musb_core.c new file mode 100644 index 000000000..7766069bf --- /dev/null +++ b/roms/u-boot-sam460ex/drivers/usb/musb/musb_core.c @@ -0,0 +1,155 @@ +/* + * Mentor USB OTG Core functionality common for both Host and Device + * functionality. + * + * Copyright (c) 2008 Texas Instruments + * + * 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 + * + * Author: Thomas Abraham t-abraham@ti.com, Texas Instruments + */ + +#include <common.h> + +#include "musb_core.h" +struct musb_regs *musbr; + +/* + * program the mentor core to start (enable interrupts, dma, etc.) + */ +void musb_start(void) +{ +#if defined(CONFIG_MUSB_HCD) + u8 devctl; +#endif + + /* disable all interrupts */ + writew(0, &musbr->intrtxe); + writew(0, &musbr->intrrxe); + writeb(0, &musbr->intrusbe); + writeb(0, &musbr->testmode); + + /* put into basic highspeed mode and start session */ + writeb(MUSB_POWER_HSENAB, &musbr->power); +#if defined(CONFIG_MUSB_HCD) + devctl = readb(&musbr->devctl); + writeb(devctl | MUSB_DEVCTL_SESSION, &musbr->devctl); +#endif +} + +#ifdef MUSB_NO_DYNAMIC_FIFO +# define config_fifo(dir, idx, addr) +#else +# define config_fifo(dir, idx, addr) \ + do { \ + writeb(idx, &musbr->dir##fifosz); \ + writew(fifoaddr >> 3, &musbr->dir##fifoadd); \ + } while (0) +#endif + +/* + * This function configures the endpoint configuration. The musb hcd or musb + * device implementation can use this function to configure the endpoints + * and set the FIFO sizes. Note: The summation of FIFO sizes of all endpoints + * should not be more than the available FIFO size. + * + * epinfo - Pointer to EP configuration table + * cnt - Number of entries in the EP conf table. + */ +void musb_configure_ep(struct musb_epinfo *epinfo, u8 cnt) +{ + u16 csr; + u16 fifoaddr = 64; /* First 64 bytes of FIFO reserved for EP0 */ + u32 fifosize; + u8 idx; + + while (cnt--) { + /* prepare fifosize to write to register */ + fifosize = epinfo->epsize >> 3; + idx = ffs(fifosize) - 1; + + writeb(epinfo->epnum, &musbr->index); + if (epinfo->epdir) { + /* Configure fifo size and fifo base address */ + config_fifo(tx, idx, fifoaddr); + + csr = readw(&musbr->txcsr); +#if defined(CONFIG_MUSB_HCD) + /* clear the data toggle bit */ + writew(csr | MUSB_TXCSR_CLRDATATOG, &musbr->txcsr); +#endif + /* Flush fifo if required */ + if (csr & MUSB_TXCSR_TXPKTRDY) + writew(csr | MUSB_TXCSR_FLUSHFIFO, + &musbr->txcsr); + } else { + /* Configure fifo size and fifo base address */ + config_fifo(rx, idx, fifoaddr); + + csr = readw(&musbr->rxcsr); +#if defined(CONFIG_MUSB_HCD) + /* clear the data toggle bit */ + writew(csr | MUSB_RXCSR_CLRDATATOG, &musbr->rxcsr); +#endif + /* Flush fifo if required */ + if (csr & MUSB_RXCSR_RXPKTRDY) + writew(csr | MUSB_RXCSR_FLUSHFIFO, + &musbr->rxcsr); + } + fifoaddr += epinfo->epsize; + epinfo++; + } +} + +/* + * This function writes data to endpoint fifo + * + * ep - endpoint number + * length - number of bytes to write to FIFO + * fifo_data - Pointer to data buffer that contains the data to write + */ +__attribute__((weak)) +void write_fifo(u8 ep, u32 length, void *fifo_data) +{ + u8 *data = (u8 *)fifo_data; + + /* select the endpoint index */ + writeb(ep, &musbr->index); + + /* write the data to the fifo */ + while (length--) + writeb(*data++, &musbr->fifox[ep]); +} + +/* + * This function reads data from endpoint fifo + * + * ep - endpoint number + * length - number of bytes to read from FIFO + * fifo_data - pointer to data buffer into which data is read + */ +__attribute__((weak)) +void read_fifo(u8 ep, u32 length, void *fifo_data) +{ + u8 *data = (u8 *)fifo_data; + + /* select the endpoint index */ + writeb(ep, &musbr->index); + + /* read the data to the fifo */ + while (length--) + *data++ = readb(&musbr->fifox[ep]); +} diff --git a/roms/u-boot-sam460ex/drivers/usb/musb/musb_core.h b/roms/u-boot-sam460ex/drivers/usb/musb/musb_core.h new file mode 100644 index 000000000..9a1fb4f31 --- /dev/null +++ b/roms/u-boot-sam460ex/drivers/usb/musb/musb_core.h @@ -0,0 +1,366 @@ +/****************************************************************** + * Copyright 2008 Mentor Graphics Corporation + * Copyright (C) 2008 by Texas Instruments + * + * This file is part of the Inventra Controller Driver for Linux. + * + * The Inventra Controller Driver for Linux is free software; you + * can redistribute it and/or modify it under the terms of the GNU + * General Public License version 2 as published by the Free Software + * Foundation. + * + * The Inventra Controller Driver for Linux 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 The Inventra Controller Driver for Linux ; if not, + * write to the Free Software Foundation, Inc., 59 Temple Place, + * Suite 330, Boston, MA 02111-1307 USA + * + * ANY DOWNLOAD, USE, REPRODUCTION, MODIFICATION OR DISTRIBUTION + * OF THIS DRIVER INDICATES YOUR COMPLETE AND UNCONDITIONAL ACCEPTANCE + * OF THOSE TERMS.THIS DRIVER IS PROVIDED "AS IS" AND MENTOR GRAPHICS + * MAKES NO WARRANTIES, EXPRESS OR IMPLIED, RELATED TO THIS DRIVER. + * MENTOR GRAPHICS SPECIFICALLY DISCLAIMS ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY; FITNESS FOR A PARTICULAR PURPOSE AND + * NON-INFRINGEMENT. MENTOR GRAPHICS DOES NOT PROVIDE SUPPORT + * SERVICES OR UPDATES FOR THIS DRIVER, EVEN IF YOU ARE A MENTOR + * GRAPHICS SUPPORT CUSTOMER. + ******************************************************************/ + +#ifndef __MUSB_HDRC_DEFS_H__ +#define __MUSB_HDRC_DEFS_H__ + +#include <usb.h> +#include <usb_defs.h> +#include <asm/io.h> + +#ifdef CONFIG_USB_BLACKFIN +# include "blackfin_usb.h" +#endif + +#define MUSB_EP0_FIFOSIZE 64 /* This is non-configurable */ + +/* EP0 */ +struct musb_ep0_regs { + u16 reserved4; + u16 csr0; + u16 reserved5; + u16 reserved6; + u16 count0; + u8 host_type0; + u8 host_naklimit0; + u8 reserved7; + u8 reserved8; + u8 reserved9; + u8 configdata; +}; + +/* EP 1-15 */ +struct musb_epN_regs { + u16 txmaxp; + u16 txcsr; + u16 rxmaxp; + u16 rxcsr; + u16 rxcount; + u8 txtype; + u8 txinterval; + u8 rxtype; + u8 rxinterval; + u8 reserved0; + u8 fifosize; +}; + +/* Mentor USB core register overlay structure */ +#ifndef musb_regs +struct musb_regs { + /* common registers */ + u8 faddr; + u8 power; + u16 intrtx; + u16 intrrx; + u16 intrtxe; + u16 intrrxe; + u8 intrusb; + u8 intrusbe; + u16 frame; + u8 index; + u8 testmode; + /* indexed registers */ + u16 txmaxp; + u16 txcsr; + u16 rxmaxp; + u16 rxcsr; + u16 rxcount; + u8 txtype; + u8 txinterval; + u8 rxtype; + u8 rxinterval; + u8 reserved0; + u8 fifosize; + /* fifo */ + u32 fifox[16]; + /* OTG, dynamic FIFO, version & vendor registers */ + u8 devctl; + u8 reserved1; + u8 txfifosz; + u8 rxfifosz; + u16 txfifoadd; + u16 rxfifoadd; + u32 vcontrol; + u16 hwvers; + u16 reserved2[5]; + u8 epinfo; + u8 raminfo; + u8 linkinfo; + u8 vplen; + u8 hseof1; + u8 fseof1; + u8 lseof1; + u8 reserved3; + /* target address registers */ + struct musb_tar_regs { + u8 txfuncaddr; + u8 reserved0; + u8 txhubaddr; + u8 txhubport; + u8 rxfuncaddr; + u8 reserved1; + u8 rxhubaddr; + u8 rxhubport; + } tar[16]; + /* + * endpoint registers + * ep0 elements are valid when array index is 0 + * otherwise epN is valid + */ + union musb_ep_regs { + struct musb_ep0_regs ep0; + struct musb_epN_regs epN; + } ep[16]; + +} __attribute__((packed, aligned(32))); +#endif + +/* + * MUSB Register bits + */ + +/* POWER */ +#define MUSB_POWER_ISOUPDATE 0x80 +#define MUSB_POWER_SOFTCONN 0x40 +#define MUSB_POWER_HSENAB 0x20 +#define MUSB_POWER_HSMODE 0x10 +#define MUSB_POWER_RESET 0x08 +#define MUSB_POWER_RESUME 0x04 +#define MUSB_POWER_SUSPENDM 0x02 +#define MUSB_POWER_ENSUSPEND 0x01 +#define MUSB_POWER_HSMODE_SHIFT 4 + +/* INTRUSB */ +#define MUSB_INTR_SUSPEND 0x01 +#define MUSB_INTR_RESUME 0x02 +#define MUSB_INTR_RESET 0x04 +#define MUSB_INTR_BABBLE 0x04 +#define MUSB_INTR_SOF 0x08 +#define MUSB_INTR_CONNECT 0x10 +#define MUSB_INTR_DISCONNECT 0x20 +#define MUSB_INTR_SESSREQ 0x40 +#define MUSB_INTR_VBUSERROR 0x80 /* For SESSION end */ + +/* DEVCTL */ +#define MUSB_DEVCTL_BDEVICE 0x80 +#define MUSB_DEVCTL_FSDEV 0x40 +#define MUSB_DEVCTL_LSDEV 0x20 +#define MUSB_DEVCTL_VBUS 0x18 +#define MUSB_DEVCTL_VBUS_SHIFT 3 +#define MUSB_DEVCTL_HM 0x04 +#define MUSB_DEVCTL_HR 0x02 +#define MUSB_DEVCTL_SESSION 0x01 + +/* TESTMODE */ +#define MUSB_TEST_FORCE_HOST 0x80 +#define MUSB_TEST_FIFO_ACCESS 0x40 +#define MUSB_TEST_FORCE_FS 0x20 +#define MUSB_TEST_FORCE_HS 0x10 +#define MUSB_TEST_PACKET 0x08 +#define MUSB_TEST_K 0x04 +#define MUSB_TEST_J 0x02 +#define MUSB_TEST_SE0_NAK 0x01 + +/* Allocate for double-packet buffering (effectively doubles assigned _SIZE) */ +#define MUSB_FIFOSZ_DPB 0x10 +/* Allocation size (8, 16, 32, ... 4096) */ +#define MUSB_FIFOSZ_SIZE 0x0f + +/* CSR0 */ +#define MUSB_CSR0_FLUSHFIFO 0x0100 +#define MUSB_CSR0_TXPKTRDY 0x0002 +#define MUSB_CSR0_RXPKTRDY 0x0001 + +/* CSR0 in Peripheral mode */ +#define MUSB_CSR0_P_SVDSETUPEND 0x0080 +#define MUSB_CSR0_P_SVDRXPKTRDY 0x0040 +#define MUSB_CSR0_P_SENDSTALL 0x0020 +#define MUSB_CSR0_P_SETUPEND 0x0010 +#define MUSB_CSR0_P_DATAEND 0x0008 +#define MUSB_CSR0_P_SENTSTALL 0x0004 + +/* CSR0 in Host mode */ +#define MUSB_CSR0_H_DIS_PING 0x0800 +#define MUSB_CSR0_H_WR_DATATOGGLE 0x0400 /* Set to allow setting: */ +#define MUSB_CSR0_H_DATATOGGLE 0x0200 /* Data toggle control */ +#define MUSB_CSR0_H_NAKTIMEOUT 0x0080 +#define MUSB_CSR0_H_STATUSPKT 0x0040 +#define MUSB_CSR0_H_REQPKT 0x0020 +#define MUSB_CSR0_H_ERROR 0x0010 +#define MUSB_CSR0_H_SETUPPKT 0x0008 +#define MUSB_CSR0_H_RXSTALL 0x0004 + +/* CSR0 bits to avoid zeroing (write zero clears, write 1 ignored) */ +#define MUSB_CSR0_P_WZC_BITS \ + (MUSB_CSR0_P_SENTSTALL) +#define MUSB_CSR0_H_WZC_BITS \ + (MUSB_CSR0_H_NAKTIMEOUT | MUSB_CSR0_H_RXSTALL \ + | MUSB_CSR0_RXPKTRDY) + +/* TxType/RxType */ +#define MUSB_TYPE_SPEED 0xc0 +#define MUSB_TYPE_SPEED_SHIFT 6 +#define MUSB_TYPE_SPEED_HIGH 1 +#define MUSB_TYPE_SPEED_FULL 2 +#define MUSB_TYPE_SPEED_LOW 3 +#define MUSB_TYPE_PROTO 0x30 /* Implicitly zero for ep0 */ +#define MUSB_TYPE_PROTO_SHIFT 4 +#define MUSB_TYPE_REMOTE_END 0xf /* Implicitly zero for ep0 */ +#define MUSB_TYPE_PROTO_BULK 2 +#define MUSB_TYPE_PROTO_INTR 3 + +/* CONFIGDATA */ +#define MUSB_CONFIGDATA_MPRXE 0x80 /* Auto bulk pkt combining */ +#define MUSB_CONFIGDATA_MPTXE 0x40 /* Auto bulk pkt splitting */ +#define MUSB_CONFIGDATA_BIGENDIAN 0x20 +#define MUSB_CONFIGDATA_HBRXE 0x10 /* HB-ISO for RX */ +#define MUSB_CONFIGDATA_HBTXE 0x08 /* HB-ISO for TX */ +#define MUSB_CONFIGDATA_DYNFIFO 0x04 /* Dynamic FIFO sizing */ +#define MUSB_CONFIGDATA_SOFTCONE 0x02 /* SoftConnect */ +#define MUSB_CONFIGDATA_UTMIDW 0x01 /* Data width 0/1 => 8/16bits */ + +/* TXCSR in Peripheral and Host mode */ +#define MUSB_TXCSR_AUTOSET 0x8000 +#define MUSB_TXCSR_MODE 0x2000 +#define MUSB_TXCSR_DMAENAB 0x1000 +#define MUSB_TXCSR_FRCDATATOG 0x0800 +#define MUSB_TXCSR_DMAMODE 0x0400 +#define MUSB_TXCSR_CLRDATATOG 0x0040 +#define MUSB_TXCSR_FLUSHFIFO 0x0008 +#define MUSB_TXCSR_FIFONOTEMPTY 0x0002 +#define MUSB_TXCSR_TXPKTRDY 0x0001 + +/* TXCSR in Peripheral mode */ +#define MUSB_TXCSR_P_ISO 0x4000 +#define MUSB_TXCSR_P_INCOMPTX 0x0080 +#define MUSB_TXCSR_P_SENTSTALL 0x0020 +#define MUSB_TXCSR_P_SENDSTALL 0x0010 +#define MUSB_TXCSR_P_UNDERRUN 0x0004 + +/* TXCSR in Host mode */ +#define MUSB_TXCSR_H_WR_DATATOGGLE 0x0200 +#define MUSB_TXCSR_H_DATATOGGLE 0x0100 +#define MUSB_TXCSR_H_NAKTIMEOUT 0x0080 +#define MUSB_TXCSR_H_RXSTALL 0x0020 +#define MUSB_TXCSR_H_ERROR 0x0004 +#define MUSB_TXCSR_H_DATATOGGLE_SHIFT 8 + +/* TXCSR bits to avoid zeroing (write zero clears, write 1 ignored) */ +#define MUSB_TXCSR_P_WZC_BITS \ + (MUSB_TXCSR_P_INCOMPTX | MUSB_TXCSR_P_SENTSTALL \ + | MUSB_TXCSR_P_UNDERRUN | MUSB_TXCSR_FIFONOTEMPTY) +#define MUSB_TXCSR_H_WZC_BITS \ + (MUSB_TXCSR_H_NAKTIMEOUT | MUSB_TXCSR_H_RXSTALL \ + | MUSB_TXCSR_H_ERROR | MUSB_TXCSR_FIFONOTEMPTY) + +/* RXCSR in Peripheral and Host mode */ +#define MUSB_RXCSR_AUTOCLEAR 0x8000 +#define MUSB_RXCSR_DMAENAB 0x2000 +#define MUSB_RXCSR_DISNYET 0x1000 +#define MUSB_RXCSR_PID_ERR 0x1000 +#define MUSB_RXCSR_DMAMODE 0x0800 +#define MUSB_RXCSR_INCOMPRX 0x0100 +#define MUSB_RXCSR_CLRDATATOG 0x0080 +#define MUSB_RXCSR_FLUSHFIFO 0x0010 +#define MUSB_RXCSR_DATAERROR 0x0008 +#define MUSB_RXCSR_FIFOFULL 0x0002 +#define MUSB_RXCSR_RXPKTRDY 0x0001 + +/* RXCSR in Peripheral mode */ +#define MUSB_RXCSR_P_ISO 0x4000 +#define MUSB_RXCSR_P_SENTSTALL 0x0040 +#define MUSB_RXCSR_P_SENDSTALL 0x0020 +#define MUSB_RXCSR_P_OVERRUN 0x0004 + +/* RXCSR in Host mode */ +#define MUSB_RXCSR_H_AUTOREQ 0x4000 +#define MUSB_RXCSR_H_WR_DATATOGGLE 0x0400 +#define MUSB_RXCSR_H_DATATOGGLE 0x0200 +#define MUSB_RXCSR_H_RXSTALL 0x0040 +#define MUSB_RXCSR_H_REQPKT 0x0020 +#define MUSB_RXCSR_H_ERROR 0x0004 +#define MUSB_S_RXCSR_H_DATATOGGLE 9 + +/* RXCSR bits to avoid zeroing (write zero clears, write 1 ignored) */ +#define MUSB_RXCSR_P_WZC_BITS \ + (MUSB_RXCSR_P_SENTSTALL | MUSB_RXCSR_P_OVERRUN \ + | MUSB_RXCSR_RXPKTRDY) +#define MUSB_RXCSR_H_WZC_BITS \ + (MUSB_RXCSR_H_RXSTALL | MUSB_RXCSR_H_ERROR \ + | MUSB_RXCSR_DATAERROR | MUSB_RXCSR_RXPKTRDY) + +/* HUBADDR */ +#define MUSB_HUBADDR_MULTI_TT 0x80 + +/* Endpoint configuration information. Note: The value of endpoint fifo size + * element should be either 8,16,32,64,128,256,512,1024,2048 or 4096. Other + * values are not supported + */ +struct musb_epinfo { + u8 epnum; /* endpoint number */ + u8 epdir; /* endpoint direction */ + u16 epsize; /* endpoint FIFO size */ +}; + +/* + * Platform specific MUSB configuration. Any platform using the musb + * functionality should create one instance of this structure in the + * platform specific file. + */ +struct musb_config { + struct musb_regs *regs; + u32 timeout; + u8 musb_speed; +}; + +/* externally defined data */ +extern struct musb_config musb_cfg; +extern struct musb_regs *musbr; + +/* exported functions */ +extern void musb_start(void); +extern void musb_configure_ep(struct musb_epinfo *epinfo, u8 cnt); +extern void write_fifo(u8 ep, u32 length, void *fifo_data); +extern void read_fifo(u8 ep, u32 length, void *fifo_data); + +#if defined(CONFIG_USB_BLACKFIN) +/* Every USB register is accessed as a 16-bit even if the value itself + * is only 8-bits in size. Fun stuff. + */ +# undef readb +# define readb(addr) (u8)bfin_read16(addr) +# undef writeb +# define writeb(b, addr) bfin_write16(addr, b) +#endif + +#endif /* __MUSB_HDRC_DEFS_H__ */ diff --git a/roms/u-boot-sam460ex/drivers/usb/musb/musb_debug.h b/roms/u-boot-sam460ex/drivers/usb/musb/musb_debug.h new file mode 100644 index 000000000..62380ffc6 --- /dev/null +++ b/roms/u-boot-sam460ex/drivers/usb/musb/musb_debug.h @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2009 Wind River Systems, Inc. + * Tom Rix <Tom.Rix@windriver.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 + */ + +/* Define MUSB_DEBUG before including this file to get debug macros */ +#ifdef MUSB_DEBUG + +#define MUSB_FLAGS_PRINT(v, x, y) \ + if (((v) & MUSB_##x##_##y)) \ + serial_printf("\t\t"#y"\n") + +static inline void musb_print_pwr(u8 b) +{ + serial_printf("\tpower 0x%2.2x\n", b); + MUSB_FLAGS_PRINT(b, POWER, ISOUPDATE); + MUSB_FLAGS_PRINT(b, POWER, SOFTCONN); + MUSB_FLAGS_PRINT(b, POWER, HSENAB); + MUSB_FLAGS_PRINT(b, POWER, HSMODE); + MUSB_FLAGS_PRINT(b, POWER, RESET); + MUSB_FLAGS_PRINT(b, POWER, RESUME); + MUSB_FLAGS_PRINT(b, POWER, SUSPENDM); + MUSB_FLAGS_PRINT(b, POWER, ENSUSPEND); +} + +static inline void musb_print_csr0(u16 w) +{ + serial_printf("\tcsr0 0x%4.4x\n", w); + MUSB_FLAGS_PRINT(w, CSR0, FLUSHFIFO); + MUSB_FLAGS_PRINT(w, CSR0_P, SVDSETUPEND); + MUSB_FLAGS_PRINT(w, CSR0_P, SVDRXPKTRDY); + MUSB_FLAGS_PRINT(w, CSR0_P, SENDSTALL); + MUSB_FLAGS_PRINT(w, CSR0_P, SETUPEND); + MUSB_FLAGS_PRINT(w, CSR0_P, DATAEND); + MUSB_FLAGS_PRINT(w, CSR0_P, SENTSTALL); + MUSB_FLAGS_PRINT(w, CSR0, TXPKTRDY); + MUSB_FLAGS_PRINT(w, CSR0, RXPKTRDY); +} + +static inline void musb_print_intrusb(u8 b) +{ + serial_printf("\tintrusb 0x%2.2x\n", b); + MUSB_FLAGS_PRINT(b, INTR, VBUSERROR); + MUSB_FLAGS_PRINT(b, INTR, SESSREQ); + MUSB_FLAGS_PRINT(b, INTR, DISCONNECT); + MUSB_FLAGS_PRINT(b, INTR, CONNECT); + MUSB_FLAGS_PRINT(b, INTR, SOF); + MUSB_FLAGS_PRINT(b, INTR, RESUME); + MUSB_FLAGS_PRINT(b, INTR, SUSPEND); + + if (b & MUSB_INTR_BABBLE) + serial_printf("\t\tMUSB_INTR_RESET or MUSB_INTR_BABBLE\n"); + +} + +static inline void musb_print_intrtx(u16 w) +{ + serial_printf("\tintrtx 0x%4.4x\n", w); +} + +static inline void musb_print_intrrx(u16 w) +{ + serial_printf("\tintrx 0x%4.4x\n", w); +} + +static inline void musb_print_devctl(u8 b) +{ + serial_printf("\tdevctl 0x%2.2x\n", b); + if (b & MUSB_DEVCTL_BDEVICE) + serial_printf("\t\tB device\n"); + else + serial_printf("\t\tA device\n"); + if (b & MUSB_DEVCTL_FSDEV) + serial_printf("\t\tFast Device -(host mode)\n"); + if (b & MUSB_DEVCTL_LSDEV) + serial_printf("\t\tSlow Device -(host mode)\n"); + if (b & MUSB_DEVCTL_HM) + serial_printf("\t\tHost mode\n"); + else + serial_printf("\t\tPeripherial mode\n"); + if (b & MUSB_DEVCTL_HR) + serial_printf("\t\tHost request started(B device)\n"); + else + serial_printf("\t\tHost request finished(B device)\n"); + if (b & MUSB_DEVCTL_BDEVICE) { + if (b & MUSB_DEVCTL_SESSION) + serial_printf("\t\tStart of session(B device)\n"); + else + serial_printf("\t\tEnd of session(B device)\n"); + } else { + if (b & MUSB_DEVCTL_SESSION) + serial_printf("\t\tStart of session(A device)\n"); + else + serial_printf("\t\tEnd of session(A device)\n"); + } +} + +static inline void musb_print_config(u8 b) +{ + serial_printf("\tconfig 0x%2.2x\n", b); + if (b & MUSB_CONFIGDATA_MPRXE) + serial_printf("\t\tAuto combine rx bulk packets\n"); + if (b & MUSB_CONFIGDATA_MPTXE) + serial_printf("\t\tAuto split tx bulk packets\n"); + if (b & MUSB_CONFIGDATA_BIGENDIAN) + serial_printf("\t\tBig Endian ordering\n"); + else + serial_printf("\t\tLittle Endian ordering\n"); + if (b & MUSB_CONFIGDATA_HBRXE) + serial_printf("\t\tHigh speed rx iso endpoint\n"); + if (b & MUSB_CONFIGDATA_HBTXE) + serial_printf("\t\tHigh speed tx iso endpoint\n"); + if (b & MUSB_CONFIGDATA_DYNFIFO) + serial_printf("\t\tDynamic fifo sizing\n"); + if (b & MUSB_CONFIGDATA_SOFTCONE) + serial_printf("\t\tSoft Connect\n"); + if (b & MUSB_CONFIGDATA_UTMIDW) + serial_printf("\t\t16 bit data width\n"); + else + serial_printf("\t\t8 bit data width\n"); +} + +static inline void musb_print_rxmaxp(u16 w) +{ + serial_printf("\trxmaxp 0x%4.4x\n", w); +} + +static inline void musb_print_rxcsr(u16 w) +{ + serial_printf("\trxcsr 0x%4.4x\n", w); + MUSB_FLAGS_PRINT(w, RXCSR, AUTOCLEAR); + MUSB_FLAGS_PRINT(w, RXCSR, DMAENAB); + MUSB_FLAGS_PRINT(w, RXCSR, DISNYET); + MUSB_FLAGS_PRINT(w, RXCSR, PID_ERR); + MUSB_FLAGS_PRINT(w, RXCSR, DMAMODE); + MUSB_FLAGS_PRINT(w, RXCSR, CLRDATATOG); + MUSB_FLAGS_PRINT(w, RXCSR, FLUSHFIFO); + MUSB_FLAGS_PRINT(w, RXCSR, DATAERROR); + MUSB_FLAGS_PRINT(w, RXCSR, FIFOFULL); + MUSB_FLAGS_PRINT(w, RXCSR, RXPKTRDY); + MUSB_FLAGS_PRINT(w, RXCSR_P, SENTSTALL); + MUSB_FLAGS_PRINT(w, RXCSR_P, SENDSTALL); + MUSB_FLAGS_PRINT(w, RXCSR_P, OVERRUN); + + if (w & MUSB_RXCSR_P_ISO) + serial_printf("\t\tiso mode\n"); + else + serial_printf("\t\tbulk mode\n"); + +} + +static inline void musb_print_txmaxp(u16 w) +{ + serial_printf("\ttxmaxp 0x%4.4x\n", w); +} + +static inline void musb_print_txcsr(u16 w) +{ + serial_printf("\ttxcsr 0x%4.4x\n", w); + MUSB_FLAGS_PRINT(w, TXCSR, TXPKTRDY); + MUSB_FLAGS_PRINT(w, TXCSR, FIFONOTEMPTY); + MUSB_FLAGS_PRINT(w, TXCSR, FLUSHFIFO); + MUSB_FLAGS_PRINT(w, TXCSR, CLRDATATOG); + MUSB_FLAGS_PRINT(w, TXCSR_P, UNDERRUN); + MUSB_FLAGS_PRINT(w, TXCSR_P, SENTSTALL); + MUSB_FLAGS_PRINT(w, TXCSR_P, SENDSTALL); + + if (w & MUSB_TXCSR_MODE) + serial_printf("\t\tTX mode\n"); + else + serial_printf("\t\tRX mode\n"); +} + +#else + +/* stubs */ + +#define musb_print_pwr(b) +#define musb_print_csr0(w) +#define musb_print_intrusb(b) +#define musb_print_intrtx(w) +#define musb_print_intrrx(w) +#define musb_print_devctl(b) +#define musb_print_config(b) +#define musb_print_rxmaxp(w) +#define musb_print_rxcsr(w) +#define musb_print_txmaxp(w) +#define musb_print_txcsr(w) + +#endif /* MUSB_DEBUG */ diff --git a/roms/u-boot-sam460ex/drivers/usb/musb/musb_hcd.c b/roms/u-boot-sam460ex/drivers/usb/musb/musb_hcd.c new file mode 100644 index 000000000..767acbef1 --- /dev/null +++ b/roms/u-boot-sam460ex/drivers/usb/musb/musb_hcd.c @@ -0,0 +1,1242 @@ +/* + * Mentor USB OTG Core host controller driver. + * + * Copyright (c) 2008 Texas Instruments + * + * 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 + * + * Author: Thomas Abraham t-abraham@ti.com, Texas Instruments + */ + +#include <common.h> +#include "musb_hcd.h" + +/* MSC control transfers */ +#define USB_MSC_BBB_RESET 0xFF +#define USB_MSC_BBB_GET_MAX_LUN 0xFE + +/* Endpoint configuration information */ +static struct musb_epinfo epinfo[3] = { + {MUSB_BULK_EP, 1, 512}, /* EP1 - Bluk Out - 512 Bytes */ + {MUSB_BULK_EP, 0, 512}, /* EP1 - Bluk In - 512 Bytes */ + {MUSB_INTR_EP, 0, 64} /* EP2 - Interrupt IN - 64 Bytes */ +}; + +/* --- Virtual Root Hub ---------------------------------------------------- */ +#ifdef MUSB_NO_MULTIPOINT +static int rh_devnum; +static u32 port_status; + +/* Device descriptor */ +static u8 root_hub_dev_des[] = { + 0x12, /* __u8 bLength; */ + 0x01, /* __u8 bDescriptorType; Device */ + 0x00, /* __u16 bcdUSB; v1.1 */ + 0x02, + 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[] = { + 0x1c, /* __u8 bLength; */ + 0x03, /* __u8 bDescriptorType; String-descriptor */ + 'M', /* __u8 Unicode */ + 0, /* __u8 Unicode */ + 'U', /* __u8 Unicode */ + 0, /* __u8 Unicode */ + 'S', /* __u8 Unicode */ + 0, /* __u8 Unicode */ + 'B', /* __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 */ +}; +#endif + +/* + * This function writes the data toggle value. + */ +static void write_toggle(struct usb_device *dev, u8 ep, u8 dir_out) +{ + u16 toggle = usb_gettoggle(dev, ep, dir_out); + u16 csr; + + if (dir_out) { + if (!toggle) + writew(MUSB_TXCSR_CLRDATATOG, &musbr->txcsr); + else { + csr = readw(&musbr->txcsr); + csr |= MUSB_TXCSR_H_WR_DATATOGGLE; + writew(csr, &musbr->txcsr); + csr |= (toggle << MUSB_TXCSR_H_DATATOGGLE_SHIFT); + writew(csr, &musbr->txcsr); + } + } else { + if (!toggle) + writew(MUSB_RXCSR_CLRDATATOG, &musbr->rxcsr); + else { + csr = readw(&musbr->rxcsr); + csr |= MUSB_RXCSR_H_WR_DATATOGGLE; + writew(csr, &musbr->rxcsr); + csr |= (toggle << MUSB_S_RXCSR_H_DATATOGGLE); + writew(csr, &musbr->rxcsr); + } + } +} + +/* + * This function checks if RxStall has occured on the endpoint. If a RxStall + * has occured, the RxStall is cleared and 1 is returned. If RxStall has + * not occured, 0 is returned. + */ +static u8 check_stall(u8 ep, u8 dir_out) +{ + u16 csr; + + /* For endpoint 0 */ + if (!ep) { + csr = readw(&musbr->txcsr); + if (csr & MUSB_CSR0_H_RXSTALL) { + csr &= ~MUSB_CSR0_H_RXSTALL; + writew(csr, &musbr->txcsr); + return 1; + } + } else { /* For non-ep0 */ + if (dir_out) { /* is it tx ep */ + csr = readw(&musbr->txcsr); + if (csr & MUSB_TXCSR_H_RXSTALL) { + csr &= ~MUSB_TXCSR_H_RXSTALL; + writew(csr, &musbr->txcsr); + return 1; + } + } else { /* is it rx ep */ + csr = readw(&musbr->rxcsr); + if (csr & MUSB_RXCSR_H_RXSTALL) { + csr &= ~MUSB_RXCSR_H_RXSTALL; + writew(csr, &musbr->rxcsr); + return 1; + } + } + } + return 0; +} + +/* + * waits until ep0 is ready. Returns 0 if ep is ready, -1 for timeout + * error and -2 for stall. + */ +static int wait_until_ep0_ready(struct usb_device *dev, u32 bit_mask) +{ + u16 csr; + int result = 1; + int timeout = CONFIG_MUSB_TIMEOUT; + + while (result > 0) { + csr = readw(&musbr->txcsr); + if (csr & MUSB_CSR0_H_ERROR) { + csr &= ~MUSB_CSR0_H_ERROR; + writew(csr, &musbr->txcsr); + dev->status = USB_ST_CRC_ERR; + result = -1; + break; + } + + switch (bit_mask) { + case MUSB_CSR0_TXPKTRDY: + if (!(csr & MUSB_CSR0_TXPKTRDY)) { + if (check_stall(MUSB_CONTROL_EP, 0)) { + dev->status = USB_ST_STALLED; + result = -2; + } else + result = 0; + } + break; + + case MUSB_CSR0_RXPKTRDY: + if (check_stall(MUSB_CONTROL_EP, 0)) { + dev->status = USB_ST_STALLED; + result = -2; + } else + if (csr & MUSB_CSR0_RXPKTRDY) + result = 0; + break; + + case MUSB_CSR0_H_REQPKT: + if (!(csr & MUSB_CSR0_H_REQPKT)) { + if (check_stall(MUSB_CONTROL_EP, 0)) { + dev->status = USB_ST_STALLED; + result = -2; + } else + result = 0; + } + break; + } + + /* Check the timeout */ + if (--timeout) + udelay(1); + else { + dev->status = USB_ST_CRC_ERR; + result = -1; + break; + } + } + + return result; +} + +/* + * waits until tx ep is ready. Returns 1 when ep is ready and 0 on error. + */ +static u8 wait_until_txep_ready(struct usb_device *dev, u8 ep) +{ + u16 csr; + int timeout = CONFIG_MUSB_TIMEOUT; + + do { + if (check_stall(ep, 1)) { + dev->status = USB_ST_STALLED; + return 0; + } + + csr = readw(&musbr->txcsr); + if (csr & MUSB_TXCSR_H_ERROR) { + dev->status = USB_ST_CRC_ERR; + return 0; + } + + /* Check the timeout */ + if (--timeout) + udelay(1); + else { + dev->status = USB_ST_CRC_ERR; + return -1; + } + + } while (csr & MUSB_TXCSR_TXPKTRDY); + return 1; +} + +/* + * waits until rx ep is ready. Returns 1 when ep is ready and 0 on error. + */ +static u8 wait_until_rxep_ready(struct usb_device *dev, u8 ep) +{ + u16 csr; + int timeout = CONFIG_MUSB_TIMEOUT; + + do { + if (check_stall(ep, 0)) { + dev->status = USB_ST_STALLED; + return 0; + } + + csr = readw(&musbr->rxcsr); + if (csr & MUSB_RXCSR_H_ERROR) { + dev->status = USB_ST_CRC_ERR; + return 0; + } + + /* Check the timeout */ + if (--timeout) + udelay(1); + else { + dev->status = USB_ST_CRC_ERR; + return -1; + } + + } while (!(csr & MUSB_RXCSR_RXPKTRDY)); + return 1; +} + +/* + * This function performs the setup phase of the control transfer + */ +static int ctrlreq_setup_phase(struct usb_device *dev, struct devrequest *setup) +{ + int result; + u16 csr; + + /* write the control request to ep0 fifo */ + write_fifo(MUSB_CONTROL_EP, sizeof(struct devrequest), (void *)setup); + + /* enable transfer of setup packet */ + csr = readw(&musbr->txcsr); + csr |= (MUSB_CSR0_TXPKTRDY|MUSB_CSR0_H_SETUPPKT); + writew(csr, &musbr->txcsr); + + /* wait until the setup packet is transmitted */ + result = wait_until_ep0_ready(dev, MUSB_CSR0_TXPKTRDY); + dev->act_len = 0; + return result; +} + +/* + * This function handles the control transfer in data phase + */ +static int ctrlreq_in_data_phase(struct usb_device *dev, u32 len, void *buffer) +{ + u16 csr; + u32 rxlen = 0; + u32 nextlen = 0; + u8 maxpktsize = (1 << dev->maxpacketsize) * 8; + u8 *rxbuff = (u8 *)buffer; + u8 rxedlength; + int result; + + while (rxlen < len) { + /* Determine the next read length */ + nextlen = ((len-rxlen) > maxpktsize) ? maxpktsize : (len-rxlen); + + /* Set the ReqPkt bit */ + csr = readw(&musbr->txcsr); + writew(csr | MUSB_CSR0_H_REQPKT, &musbr->txcsr); + result = wait_until_ep0_ready(dev, MUSB_CSR0_RXPKTRDY); + if (result < 0) + return result; + + /* Actual number of bytes received by usb */ + rxedlength = readb(&musbr->rxcount); + + /* Read the data from the RxFIFO */ + read_fifo(MUSB_CONTROL_EP, rxedlength, &rxbuff[rxlen]); + + /* Clear the RxPktRdy Bit */ + csr = readw(&musbr->txcsr); + csr &= ~MUSB_CSR0_RXPKTRDY; + writew(csr, &musbr->txcsr); + + /* short packet? */ + if (rxedlength != nextlen) { + dev->act_len += rxedlength; + break; + } + rxlen += nextlen; + dev->act_len = rxlen; + } + return 0; +} + +/* + * This function handles the control transfer out data phase + */ +static int ctrlreq_out_data_phase(struct usb_device *dev, u32 len, void *buffer) +{ + u16 csr; + u32 txlen = 0; + u32 nextlen = 0; + u8 maxpktsize = (1 << dev->maxpacketsize) * 8; + u8 *txbuff = (u8 *)buffer; + int result = 0; + + while (txlen < len) { + /* Determine the next write length */ + nextlen = ((len-txlen) > maxpktsize) ? maxpktsize : (len-txlen); + + /* Load the data to send in FIFO */ + write_fifo(MUSB_CONTROL_EP, txlen, &txbuff[txlen]); + + /* Set TXPKTRDY bit */ + csr = readw(&musbr->txcsr); + writew(csr | MUSB_CSR0_H_DIS_PING | MUSB_CSR0_TXPKTRDY, + &musbr->txcsr); + result = wait_until_ep0_ready(dev, MUSB_CSR0_TXPKTRDY); + if (result < 0) + break; + + txlen += nextlen; + dev->act_len = txlen; + } + return result; +} + +/* + * This function handles the control transfer out status phase + */ +static int ctrlreq_out_status_phase(struct usb_device *dev) +{ + u16 csr; + int result; + + /* Set the StatusPkt bit */ + csr = readw(&musbr->txcsr); + csr |= (MUSB_CSR0_H_DIS_PING | MUSB_CSR0_TXPKTRDY | + MUSB_CSR0_H_STATUSPKT); + writew(csr, &musbr->txcsr); + + /* Wait until TXPKTRDY bit is cleared */ + result = wait_until_ep0_ready(dev, MUSB_CSR0_TXPKTRDY); + return result; +} + +/* + * This function handles the control transfer in status phase + */ +static int ctrlreq_in_status_phase(struct usb_device *dev) +{ + u16 csr; + int result; + + /* Set the StatusPkt bit and ReqPkt bit */ + csr = MUSB_CSR0_H_DIS_PING | MUSB_CSR0_H_REQPKT | MUSB_CSR0_H_STATUSPKT; + writew(csr, &musbr->txcsr); + result = wait_until_ep0_ready(dev, MUSB_CSR0_H_REQPKT); + + /* clear StatusPkt bit and RxPktRdy bit */ + csr = readw(&musbr->txcsr); + csr &= ~(MUSB_CSR0_RXPKTRDY | MUSB_CSR0_H_STATUSPKT); + writew(csr, &musbr->txcsr); + return result; +} + +/* + * determines the speed of the device (High/Full/Slow) + */ +static u8 get_dev_speed(struct usb_device *dev) +{ + return (dev->speed & USB_SPEED_HIGH) ? MUSB_TYPE_SPEED_HIGH : + ((dev->speed & USB_SPEED_LOW) ? MUSB_TYPE_SPEED_LOW : + MUSB_TYPE_SPEED_FULL); +} + +/* + * configure the hub address and the port address. + */ +static void config_hub_port(struct usb_device *dev, u8 ep) +{ + u8 chid; + u8 hub; + + /* Find out the nearest parent which is high speed */ + while (dev->parent->parent != NULL) + if (get_dev_speed(dev->parent) != MUSB_TYPE_SPEED_HIGH) + dev = dev->parent; + else + break; + + /* determine the port address at that hub */ + hub = dev->parent->devnum; + for (chid = 0; chid < USB_MAXCHILDREN; chid++) + if (dev->parent->children[chid] == dev) + break; + +#ifndef MUSB_NO_MULTIPOINT + /* configure the hub address and the port address */ + writeb(hub, &musbr->tar[ep].txhubaddr); + writeb((chid + 1), &musbr->tar[ep].txhubport); + writeb(hub, &musbr->tar[ep].rxhubaddr); + writeb((chid + 1), &musbr->tar[ep].rxhubport); +#endif +} + +#ifdef MUSB_NO_MULTIPOINT + +static void musb_port_reset(int do_reset) +{ + u8 power = readb(&musbr->power); + + if (do_reset) { + power &= 0xf0; + writeb(power | MUSB_POWER_RESET, &musbr->power); + port_status |= USB_PORT_STAT_RESET; + port_status &= ~USB_PORT_STAT_ENABLE; + udelay(30000); + } else { + writeb(power & ~MUSB_POWER_RESET, &musbr->power); + + power = readb(&musbr->power); + if (power & MUSB_POWER_HSMODE) + port_status |= USB_PORT_STAT_HIGH_SPEED; + + port_status &= ~(USB_PORT_STAT_RESET | (USB_PORT_STAT_C_CONNECTION << 16)); + port_status |= USB_PORT_STAT_ENABLE + | (USB_PORT_STAT_C_RESET << 16) + | (USB_PORT_STAT_C_ENABLE << 16); + } +} + +/* + * root hub control + */ +static int musb_submit_rh_msg(struct usb_device *dev, unsigned long pipe, + void *buffer, int transfer_len, + struct devrequest *cmd) +{ + 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; + u16 int_usb; + + if ((pipe & PIPE_INTERRUPT) == PIPE_INTERRUPT) { + debug("Root-Hub submit IRQ: NOT implemented\n"); + return 0; + } + + bmRType_bReq = cmd->requesttype | (cmd->request << 8); + wValue = swap_16(cmd->value); + wIndex = swap_16(cmd->index); + wLength = swap_16(cmd->length); + + debug("--- HUB ----------------------------------------\n"); + debug("submit rh urb, req=%x val=%#x index=%#x len=%d\n", + bmRType_bReq, wValue, wIndex, wLength); + debug("------------------------------------------------\n"); + + switch (bmRType_bReq) { + case RH_GET_STATUS: + debug("RH_GET_STATUS\n"); + + *(__u16 *) data_buf = swap_16(1); + len = 2; + break; + + case RH_GET_STATUS | RH_INTERFACE: + debug("RH_GET_STATUS | RH_INTERFACE\n"); + + *(__u16 *) data_buf = swap_16(0); + len = 2; + break; + + case RH_GET_STATUS | RH_ENDPOINT: + debug("RH_GET_STATUS | RH_ENDPOINT\n"); + + *(__u16 *) data_buf = swap_16(0); + len = 2; + break; + + case RH_GET_STATUS | RH_CLASS: + debug("RH_GET_STATUS | RH_CLASS\n"); + + *(__u32 *) data_buf = swap_32(0); + len = 4; + break; + + case RH_GET_STATUS | RH_OTHER | RH_CLASS: + debug("RH_GET_STATUS | RH_OTHER | RH_CLASS\n"); + + int_usb = readw(&musbr->intrusb); + if (int_usb & MUSB_INTR_CONNECT) { + port_status |= USB_PORT_STAT_CONNECTION + | (USB_PORT_STAT_C_CONNECTION << 16); + port_status |= USB_PORT_STAT_HIGH_SPEED + | USB_PORT_STAT_ENABLE; + } + + if (port_status & USB_PORT_STAT_RESET) + musb_port_reset(0); + + *(__u32 *) data_buf = swap_32(port_status); + len = 4; + break; + + case RH_CLEAR_FEATURE | RH_ENDPOINT: + debug("RH_CLEAR_FEATURE | RH_ENDPOINT\n"); + + switch (wValue) { + case RH_ENDPOINT_STALL: + debug("C_HUB_ENDPOINT_STALL\n"); + len = 0; + break; + } + port_status &= ~(1 << wValue); + break; + + case RH_CLEAR_FEATURE | RH_CLASS: + debug("RH_CLEAR_FEATURE | RH_CLASS\n"); + + switch (wValue) { + case RH_C_HUB_LOCAL_POWER: + debug("C_HUB_LOCAL_POWER\n"); + len = 0; + break; + + case RH_C_HUB_OVER_CURRENT: + debug("C_HUB_OVER_CURRENT\n"); + len = 0; + break; + } + port_status &= ~(1 << wValue); + break; + + case RH_CLEAR_FEATURE | RH_OTHER | RH_CLASS: + debug("RH_CLEAR_FEATURE | RH_OTHER | RH_CLASS\n"); + + switch (wValue) { + case RH_PORT_ENABLE: + len = 0; + break; + + case RH_PORT_SUSPEND: + len = 0; + break; + + case RH_PORT_POWER: + len = 0; + break; + + case RH_C_PORT_CONNECTION: + len = 0; + break; + + case RH_C_PORT_ENABLE: + len = 0; + break; + + case RH_C_PORT_SUSPEND: + len = 0; + break; + + case RH_C_PORT_OVER_CURRENT: + len = 0; + break; + + case RH_C_PORT_RESET: + len = 0; + break; + + default: + debug("invalid wValue\n"); + stat = USB_ST_STALLED; + } + + port_status &= ~(1 << wValue); + break; + + case RH_SET_FEATURE | RH_OTHER | RH_CLASS: + debug("RH_SET_FEATURE | RH_OTHER | RH_CLASS\n"); + + switch (wValue) { + case RH_PORT_SUSPEND: + len = 0; + break; + + case RH_PORT_RESET: + musb_port_reset(1); + len = 0; + break; + + case RH_PORT_POWER: + len = 0; + break; + + case RH_PORT_ENABLE: + len = 0; + break; + + default: + debug("invalid wValue\n"); + stat = USB_ST_STALLED; + } + + port_status |= 1 << wValue; + break; + + case RH_SET_ADDRESS: + debug("RH_SET_ADDRESS\n"); + + rh_devnum = wValue; + len = 0; + break; + + case RH_GET_DESCRIPTOR: + debug("RH_GET_DESCRIPTOR: %x, %d\n", 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: + debug("invalid wValue\n"); + stat = USB_ST_STALLED; + } + + break; + + case RH_GET_DESCRIPTOR | RH_CLASS: + debug("RH_GET_DESCRIPTOR | RH_CLASS\n"); + + data_buf[0] = 0x09; /* min length; */ + data_buf[1] = 0x29; + data_buf[2] = 0x1; /* 1 port */ + data_buf[3] = 0x01; /* per-port power switching */ + data_buf[3] |= 0x10; /* no overcurrent reporting */ + + /* Corresponds to data_buf[4-7] */ + data_buf[4] = 0; + data_buf[5] = 5; + data_buf[6] = 0; + data_buf[7] = 0x02; + data_buf[8] = 0xff; + + len = min_t(unsigned int, leni, + min_t(unsigned int, data_buf[0], wLength)); + break; + + case RH_GET_CONFIGURATION: + debug("RH_GET_CONFIGURATION\n"); + + *(__u8 *) data_buf = 0x01; + len = 1; + break; + + case RH_SET_CONFIGURATION: + debug("RH_SET_CONFIGURATION\n"); + + len = 0; + break; + + default: + debug("*** *** *** unsupported root hub command *** *** ***\n"); + 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; + debug("dev act_len %d, status %d\n", dev->act_len, dev->status); + + return stat; +} + +static void musb_rh_init(void) +{ + rh_devnum = 0; + port_status = 0; +} + +#else + +static void musb_rh_init(void) {} + +#endif + +/* + * do a control transfer + */ +int submit_control_msg(struct usb_device *dev, unsigned long pipe, void *buffer, + int len, struct devrequest *setup) +{ + int devnum = usb_pipedevice(pipe); + u16 csr; + u8 devspeed; + +#ifdef MUSB_NO_MULTIPOINT + /* Control message is for the HUB? */ + if (devnum == rh_devnum) + return musb_submit_rh_msg(dev, pipe, buffer, len, setup); +#endif + + /* select control endpoint */ + writeb(MUSB_CONTROL_EP, &musbr->index); + csr = readw(&musbr->txcsr); + +#ifndef MUSB_NO_MULTIPOINT + /* target addr and (for multipoint) hub addr/port */ + writeb(devnum, &musbr->tar[MUSB_CONTROL_EP].txfuncaddr); + writeb(devnum, &musbr->tar[MUSB_CONTROL_EP].rxfuncaddr); +#endif + + /* configure the hub address and the port number as required */ + devspeed = get_dev_speed(dev); + if ((musb_ishighspeed()) && (dev->parent != NULL) && + (devspeed != MUSB_TYPE_SPEED_HIGH)) { + config_hub_port(dev, MUSB_CONTROL_EP); + writeb(devspeed << 6, &musbr->txtype); + } else { + writeb(musb_cfg.musb_speed << 6, &musbr->txtype); +#ifndef MUSB_NO_MULTIPOINT + writeb(0, &musbr->tar[MUSB_CONTROL_EP].txhubaddr); + writeb(0, &musbr->tar[MUSB_CONTROL_EP].txhubport); + writeb(0, &musbr->tar[MUSB_CONTROL_EP].rxhubaddr); + writeb(0, &musbr->tar[MUSB_CONTROL_EP].rxhubport); +#endif + } + + /* Control transfer setup phase */ + if (ctrlreq_setup_phase(dev, setup) < 0) + return 0; + + switch (setup->request) { + case USB_REQ_GET_DESCRIPTOR: + case USB_REQ_GET_CONFIGURATION: + case USB_REQ_GET_INTERFACE: + case USB_REQ_GET_STATUS: + case USB_MSC_BBB_GET_MAX_LUN: + /* control transfer in-data-phase */ + if (ctrlreq_in_data_phase(dev, len, buffer) < 0) + return 0; + /* control transfer out-status-phase */ + if (ctrlreq_out_status_phase(dev) < 0) + return 0; + break; + + case USB_REQ_SET_ADDRESS: + case USB_REQ_SET_CONFIGURATION: + case USB_REQ_SET_FEATURE: + case USB_REQ_SET_INTERFACE: + case USB_REQ_CLEAR_FEATURE: + case USB_MSC_BBB_RESET: + /* control transfer in status phase */ + if (ctrlreq_in_status_phase(dev) < 0) + return 0; + break; + + case USB_REQ_SET_DESCRIPTOR: + /* control transfer out data phase */ + if (ctrlreq_out_data_phase(dev, len, buffer) < 0) + return 0; + /* control transfer in status phase */ + if (ctrlreq_in_status_phase(dev) < 0) + return 0; + break; + + default: + /* unhandled control transfer */ + return -1; + } + + dev->status = 0; + dev->act_len = len; + return len; +} + +/* + * do a bulk transfer + */ +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); +#ifndef MUSB_NO_MULTIPOINT + int devnum = usb_pipedevice(pipe); +#endif + u8 type; + u16 csr; + u32 txlen = 0; + u32 nextlen = 0; + u8 devspeed; + + /* select bulk endpoint */ + writeb(MUSB_BULK_EP, &musbr->index); + +#ifndef MUSB_NO_MULTIPOINT + /* write the address of the device */ + if (dir_out) + writeb(devnum, &musbr->tar[MUSB_BULK_EP].txfuncaddr); + else + writeb(devnum, &musbr->tar[MUSB_BULK_EP].rxfuncaddr); +#endif + + /* configure the hub address and the port number as required */ + devspeed = get_dev_speed(dev); + if ((musb_ishighspeed()) && (dev->parent != NULL) && + (devspeed != MUSB_TYPE_SPEED_HIGH)) { + /* + * MUSB is in high speed and the destination device is full + * speed device. So configure the hub address and port + * address registers. + */ + config_hub_port(dev, MUSB_BULK_EP); + } else { +#ifndef MUSB_NO_MULTIPOINT + if (dir_out) { + writeb(0, &musbr->tar[MUSB_BULK_EP].txhubaddr); + writeb(0, &musbr->tar[MUSB_BULK_EP].txhubport); + } else { + writeb(0, &musbr->tar[MUSB_BULK_EP].rxhubaddr); + writeb(0, &musbr->tar[MUSB_BULK_EP].rxhubport); + } +#endif + devspeed = musb_cfg.musb_speed; + } + + /* Write the saved toggle bit value */ + write_toggle(dev, ep, dir_out); + + if (dir_out) { /* bulk-out transfer */ + /* Program the TxType register */ + type = (devspeed << MUSB_TYPE_SPEED_SHIFT) | + (MUSB_TYPE_PROTO_BULK << MUSB_TYPE_PROTO_SHIFT) | + (ep & MUSB_TYPE_REMOTE_END); + writeb(type, &musbr->txtype); + + /* Write maximum packet size to the TxMaxp register */ + writew(dev->epmaxpacketout[ep], &musbr->txmaxp); + while (txlen < len) { + nextlen = ((len-txlen) < dev->epmaxpacketout[ep]) ? + (len-txlen) : dev->epmaxpacketout[ep]; + + /* Write the data to the FIFO */ + write_fifo(MUSB_BULK_EP, nextlen, + (void *)(((u8 *)buffer) + txlen)); + + /* Set the TxPktRdy bit */ + csr = readw(&musbr->txcsr); + writew(csr | MUSB_TXCSR_TXPKTRDY, &musbr->txcsr); + + /* Wait until the TxPktRdy bit is cleared */ + if (!wait_until_txep_ready(dev, MUSB_BULK_EP)) { + readw(&musbr->txcsr); + usb_settoggle(dev, ep, dir_out, + (csr >> MUSB_TXCSR_H_DATATOGGLE_SHIFT) & 1); + dev->act_len = txlen; + return 0; + } + txlen += nextlen; + } + + /* Keep a copy of the data toggle bit */ + csr = readw(&musbr->txcsr); + usb_settoggle(dev, ep, dir_out, + (csr >> MUSB_TXCSR_H_DATATOGGLE_SHIFT) & 1); + } else { /* bulk-in transfer */ + /* Write the saved toggle bit value */ + write_toggle(dev, ep, dir_out); + + /* Program the RxType register */ + type = (devspeed << MUSB_TYPE_SPEED_SHIFT) | + (MUSB_TYPE_PROTO_BULK << MUSB_TYPE_PROTO_SHIFT) | + (ep & MUSB_TYPE_REMOTE_END); + writeb(type, &musbr->rxtype); + + /* Write the maximum packet size to the RxMaxp register */ + writew(dev->epmaxpacketin[ep], &musbr->rxmaxp); + while (txlen < len) { + nextlen = ((len-txlen) < dev->epmaxpacketin[ep]) ? + (len-txlen) : dev->epmaxpacketin[ep]; + + /* Set the ReqPkt bit */ + csr = readw(&musbr->rxcsr); + writew(csr | MUSB_RXCSR_H_REQPKT, &musbr->rxcsr); + + /* Wait until the RxPktRdy bit is set */ + if (!wait_until_rxep_ready(dev, MUSB_BULK_EP)) { + csr = readw(&musbr->rxcsr); + usb_settoggle(dev, ep, dir_out, + (csr >> MUSB_S_RXCSR_H_DATATOGGLE) & 1); + csr &= ~MUSB_RXCSR_RXPKTRDY; + writew(csr, &musbr->rxcsr); + dev->act_len = txlen; + return 0; + } + + /* Read the data from the FIFO */ + read_fifo(MUSB_BULK_EP, nextlen, + (void *)(((u8 *)buffer) + txlen)); + + /* Clear the RxPktRdy bit */ + csr = readw(&musbr->rxcsr); + csr &= ~MUSB_RXCSR_RXPKTRDY; + writew(csr, &musbr->rxcsr); + txlen += nextlen; + } + + /* Keep a copy of the data toggle bit */ + csr = readw(&musbr->rxcsr); + usb_settoggle(dev, ep, dir_out, + (csr >> MUSB_S_RXCSR_H_DATATOGGLE) & 1); + } + + /* bulk transfer is complete */ + dev->status = 0; + dev->act_len = len; + return 0; +} + +/* + * This function initializes the usb controller module. + */ +int usb_lowlevel_init(void) +{ + u8 power; + u32 timeout; + + musb_rh_init(); + + if (musb_platform_init() == -1) + return -1; + + /* Configure all the endpoint FIFO's and start usb controller */ + musbr = musb_cfg.regs; + musb_configure_ep(&epinfo[0], + sizeof(epinfo) / sizeof(struct musb_epinfo)); + musb_start(); + + /* + * Wait until musb is enabled in host mode with a timeout. There + * should be a usb device connected. + */ + timeout = musb_cfg.timeout; + while (timeout--) + if (readb(&musbr->devctl) & MUSB_DEVCTL_HM) + break; + + /* if musb core is not in host mode, then return */ + if (!timeout) + return -1; + + /* start usb bus reset */ + power = readb(&musbr->power); + writeb(power | MUSB_POWER_RESET, &musbr->power); + + /* After initiating a usb reset, wait for about 20ms to 30ms */ + udelay(30000); + + /* stop usb bus reset */ + power = readb(&musbr->power); + power &= ~MUSB_POWER_RESET; + writeb(power, &musbr->power); + + /* Determine if the connected device is a high/full/low speed device */ + musb_cfg.musb_speed = (readb(&musbr->power) & MUSB_POWER_HSMODE) ? + MUSB_TYPE_SPEED_HIGH : + ((readb(&musbr->devctl) & MUSB_DEVCTL_FSDEV) ? + MUSB_TYPE_SPEED_FULL : MUSB_TYPE_SPEED_LOW); + return 0; +} + +/* + * This function stops the operation of the davinci usb module. + */ +int usb_lowlevel_stop(void) +{ + /* Reset the USB module */ + musb_platform_deinit(); + writeb(0, &musbr->devctl); + return 0; +} + +/* + * This function supports usb interrupt transfers. Currently, usb interrupt + * transfers are not supported. + */ +int submit_int_msg(struct usb_device *dev, unsigned long pipe, + void *buffer, int len, int interval) +{ + int dir_out = usb_pipeout(pipe); + int ep = usb_pipeendpoint(pipe); +#ifndef MUSB_NO_MULTIPOINT + int devnum = usb_pipedevice(pipe); +#endif + u8 type; + u16 csr; + u32 txlen = 0; + u32 nextlen = 0; + u8 devspeed; + + /* select interrupt endpoint */ + writeb(MUSB_INTR_EP, &musbr->index); + +#ifndef MUSB_NO_MULTIPOINT + /* write the address of the device */ + if (dir_out) + writeb(devnum, &musbr->tar[MUSB_INTR_EP].txfuncaddr); + else + writeb(devnum, &musbr->tar[MUSB_INTR_EP].rxfuncaddr); +#endif + + /* configure the hub address and the port number as required */ + devspeed = get_dev_speed(dev); + if ((musb_ishighspeed()) && (dev->parent != NULL) && + (devspeed != MUSB_TYPE_SPEED_HIGH)) { + /* + * MUSB is in high speed and the destination device is full + * speed device. So configure the hub address and port + * address registers. + */ + config_hub_port(dev, MUSB_INTR_EP); + } else { +#ifndef MUSB_NO_MULTIPOINT + if (dir_out) { + writeb(0, &musbr->tar[MUSB_INTR_EP].txhubaddr); + writeb(0, &musbr->tar[MUSB_INTR_EP].txhubport); + } else { + writeb(0, &musbr->tar[MUSB_INTR_EP].rxhubaddr); + writeb(0, &musbr->tar[MUSB_INTR_EP].rxhubport); + } +#endif + devspeed = musb_cfg.musb_speed; + } + + /* Write the saved toggle bit value */ + write_toggle(dev, ep, dir_out); + + if (!dir_out) { /* intrrupt-in transfer */ + /* Write the saved toggle bit value */ + write_toggle(dev, ep, dir_out); + writeb(interval, &musbr->rxinterval); + + /* Program the RxType register */ + type = (devspeed << MUSB_TYPE_SPEED_SHIFT) | + (MUSB_TYPE_PROTO_INTR << MUSB_TYPE_PROTO_SHIFT) | + (ep & MUSB_TYPE_REMOTE_END); + writeb(type, &musbr->rxtype); + + /* Write the maximum packet size to the RxMaxp register */ + writew(dev->epmaxpacketin[ep], &musbr->rxmaxp); + + while (txlen < len) { + nextlen = ((len-txlen) < dev->epmaxpacketin[ep]) ? + (len-txlen) : dev->epmaxpacketin[ep]; + + /* Set the ReqPkt bit */ + csr = readw(&musbr->rxcsr); + writew(csr | MUSB_RXCSR_H_REQPKT, &musbr->rxcsr); + + /* Wait until the RxPktRdy bit is set */ + if (!wait_until_rxep_ready(dev, MUSB_INTR_EP)) { + csr = readw(&musbr->rxcsr); + usb_settoggle(dev, ep, dir_out, + (csr >> MUSB_S_RXCSR_H_DATATOGGLE) & 1); + csr &= ~MUSB_RXCSR_RXPKTRDY; + writew(csr, &musbr->rxcsr); + dev->act_len = txlen; + return 0; + } + + /* Read the data from the FIFO */ + read_fifo(MUSB_INTR_EP, nextlen, + (void *)(((u8 *)buffer) + txlen)); + + /* Clear the RxPktRdy bit */ + csr = readw(&musbr->rxcsr); + csr &= ~MUSB_RXCSR_RXPKTRDY; + writew(csr, &musbr->rxcsr); + txlen += nextlen; + } + + /* Keep a copy of the data toggle bit */ + csr = readw(&musbr->rxcsr); + usb_settoggle(dev, ep, dir_out, + (csr >> MUSB_S_RXCSR_H_DATATOGGLE) & 1); + } + + /* interrupt transfer is complete */ + dev->irq_status = 0; + dev->irq_act_len = len; + dev->irq_handle(dev); + dev->status = 0; + dev->act_len = len; + return 0; +} diff --git a/roms/u-boot-sam460ex/drivers/usb/musb/musb_hcd.h b/roms/u-boot-sam460ex/drivers/usb/musb/musb_hcd.h new file mode 100644 index 000000000..dde7d3772 --- /dev/null +++ b/roms/u-boot-sam460ex/drivers/usb/musb/musb_hcd.h @@ -0,0 +1,112 @@ +/* + * Mentor USB OTG Core host controller driver. + * + * Copyright (c) 2008 Texas Instruments + * + * 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 + * + * Author: Thomas Abraham t-abraham@ti.com, Texas Instruments + */ + +#ifndef __MUSB_HCD_H__ +#define __MUSB_HCD_H__ + +#include "musb_core.h" +#ifdef CONFIG_USB_KEYBOARD +#include <stdio_dev.h> +extern unsigned char new[]; +#endif + +#ifndef CONFIG_MUSB_TIMEOUT +# define CONFIG_MUSB_TIMEOUT 100000 +#endif + +/* This defines the endpoint number used for control transfers */ +#define MUSB_CONTROL_EP 0 + +/* This defines the endpoint number used for bulk transfer */ +#ifndef MUSB_BULK_EP +# define MUSB_BULK_EP 1 +#endif + +/* This defines the endpoint number used for interrupt transfer */ +#define MUSB_INTR_EP 2 + +/* Determine the operating speed of MUSB core */ +#define musb_ishighspeed() \ + ((readb(&musbr->power) & MUSB_POWER_HSMODE) \ + >> MUSB_POWER_HSMODE_SHIFT) + +#define min_t(type, x, y) \ + ({ type __x = (x); type __y = (y); __x < __y ? __x : __y; }) + +/* 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 + +/* extern functions */ +extern int musb_platform_init(void); +extern void musb_platform_deinit(void); + +#endif /* __MUSB_HCD_H__ */ diff --git a/roms/u-boot-sam460ex/drivers/usb/musb/musb_udc.c b/roms/u-boot-sam460ex/drivers/usb/musb/musb_udc.c new file mode 100644 index 000000000..fc43cf4f0 --- /dev/null +++ b/roms/u-boot-sam460ex/drivers/usb/musb/musb_udc.c @@ -0,0 +1,963 @@ +/* + * Copyright (c) 2009 Wind River Systems, Inc. + * Tom Rix <Tom.Rix@windriver.com> + * + * This file is a rewrite of the usb device part of + * repository git.omapzoom.org/repo/u-boot.git, branch master, + * file cpu/omap3/fastboot.c + * + * This is the unique part of its copyright : + * + * ------------------------------------------------------------------------- + * + * (C) Copyright 2008 - 2009 + * Windriver, <www.windriver.com> + * Tom Rix <Tom.Rix@windriver.com> + * + * ------------------------------------------------------------------------- + * + * The details of connecting the device to the uboot usb device subsystem + * came from the old omap3 repository www.sakoman.net/u-boot-omap3.git, + * branch omap3-dev-usb, file drivers/usb/usbdcore_musb.c + * + * This is the unique part of its copyright : + * + * ------------------------------------------------------------------------- + * + * (C) Copyright 2008 Texas Instruments Incorporated. + * + * Based on + * u-boot OMAP1510 USB drivers (drivers/usbdcore_omap1510.c) + * twl4030 init based on linux (drivers/i2c/chips/twl4030_usb.c) + * + * Author: Diego Dompe (diego.dompe@ridgerun.com) + * Atin Malaviya (atin.malaviya@gmail.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 <usb/musb_udc.h> +#include "../gadget/ep0.h" +#include "musb_core.h" +#if defined(CONFIG_USB_OMAP3) +#include "omap3.h" +#elif defined(CONFIG_USB_DAVINCI) +#include "davinci.h" +#endif + +/* Define MUSB_DEBUG for debugging */ +/* #define MUSB_DEBUG */ +#include "musb_debug.h" + +#define MAX_ENDPOINT 15 + +#define GET_ENDPOINT(dev,ep) \ +(((struct usb_device_instance *)(dev))->bus->endpoint_array + ep) + +#define SET_EP0_STATE(s) \ +do { \ + if ((0 <= (s)) && (SET_ADDRESS >= (s))) { \ + if ((s) != ep0_state) { \ + if ((debug_setup) && (debug_level > 1)) \ + serial_printf("INFO : Changing state " \ + "from %s to %s in %s at " \ + "line %d\n", \ + ep0_state_strings[ep0_state],\ + ep0_state_strings[s], \ + __PRETTY_FUNCTION__, \ + __LINE__); \ + ep0_state = s; \ + } \ + } else { \ + if (debug_level > 0) \ + serial_printf("Error at %s %d with setting " \ + "state %d is invalid\n", \ + __PRETTY_FUNCTION__, __LINE__, s); \ + } \ +} while (0) + +/* static implies these initialized to 0 or NULL */ +static int debug_setup; +static int debug_level; +static struct musb_epinfo epinfo[MAX_ENDPOINT * 2]; +static enum ep0_state_enum { + IDLE = 0, + TX, + RX, + SET_ADDRESS +} ep0_state = IDLE; +static char *ep0_state_strings[4] = { + "IDLE", + "TX", + "RX", + "SET_ADDRESS", +}; + +static struct urb *ep0_urb; +struct usb_endpoint_instance *ep0_endpoint; +static struct usb_device_instance *udc_device; +static int enabled; + +#ifdef MUSB_DEBUG +static void musb_db_regs(void) +{ + u8 b; + u16 w; + + b = readb(&musbr->faddr); + serial_printf("\tfaddr 0x%2.2x\n", b); + + b = readb(&musbr->power); + musb_print_pwr(b); + + w = readw(&musbr->ep[0].ep0.csr0); + musb_print_csr0(w); + + b = readb(&musbr->devctl); + musb_print_devctl(b); + + b = readb(&musbr->ep[0].ep0.configdata); + musb_print_config(b); + + w = readw(&musbr->frame); + serial_printf("\tframe 0x%4.4x\n", w); + + b = readb(&musbr->index); + serial_printf("\tindex 0x%2.2x\n", b); + + w = readw(&musbr->ep[1].epN.rxmaxp); + musb_print_rxmaxp(w); + + w = readw(&musbr->ep[1].epN.rxcsr); + musb_print_rxcsr(w); + + w = readw(&musbr->ep[1].epN.txmaxp); + musb_print_txmaxp(w); + + w = readw(&musbr->ep[1].epN.txcsr); + musb_print_txcsr(w); +} +#else +#define musb_db_regs() +#endif /* DEBUG_MUSB */ + +static void musb_peri_softconnect(void) +{ + u8 power, devctl; + u8 intrusb; + u16 intrrx, intrtx; + + /* Power off MUSB */ + power = readb(&musbr->power); + power &= ~MUSB_POWER_SOFTCONN; + writeb(power, &musbr->power); + + /* Read intr to clear */ + intrusb = readb(&musbr->intrusb); + intrrx = readw(&musbr->intrrx); + intrtx = readw(&musbr->intrtx); + + udelay(1000 * 1000); /* 1 sec */ + + /* Power on MUSB */ + power = readb(&musbr->power); + power |= MUSB_POWER_SOFTCONN; + /* + * The usb device interface is usb 1.1 + * Disable 2.0 high speed by clearring the hsenable bit. + */ + power &= ~MUSB_POWER_HSENAB; + writeb(power, &musbr->power); + + /* Check if device is in b-peripheral mode */ + devctl = readb(&musbr->devctl); + if (!(devctl & MUSB_DEVCTL_BDEVICE) || + (devctl & MUSB_DEVCTL_HM)) { + serial_printf("ERROR : Unsupport USB mode\n"); + serial_printf("Check that mini-B USB cable is attached " + "to the device\n"); + } + + if (debug_setup && (debug_level > 1)) + musb_db_regs(); +} + +static void musb_peri_reset(void) +{ + if ((debug_setup) && (debug_level > 1)) + serial_printf("INFO : %s reset\n", __PRETTY_FUNCTION__); + + if (ep0_endpoint) + ep0_endpoint->endpoint_address = 0xff; + + /* Sync sw and hw addresses */ + writeb(udc_device->address, &musbr->faddr); + + SET_EP0_STATE(IDLE); +} + +static void musb_peri_resume(void) +{ + /* noop */ +} + +static void musb_peri_ep0_stall(void) +{ + u16 csr0; + + csr0 = readw(&musbr->ep[0].ep0.csr0); + csr0 |= MUSB_CSR0_P_SENDSTALL; + writew(csr0, &musbr->ep[0].ep0.csr0); + if ((debug_setup) && (debug_level > 1)) + serial_printf("INFO : %s stall\n", __PRETTY_FUNCTION__); +} + +static void musb_peri_ep0_ack_req(void) +{ + u16 csr0; + + csr0 = readw(&musbr->ep[0].ep0.csr0); + csr0 |= MUSB_CSR0_P_SVDRXPKTRDY; + writew(csr0, &musbr->ep[0].ep0.csr0); +} + +static void musb_ep0_tx_ready(void) +{ + u16 csr0; + + csr0 = readw(&musbr->ep[0].ep0.csr0); + csr0 |= MUSB_CSR0_TXPKTRDY; + writew(csr0, &musbr->ep[0].ep0.csr0); +} + +static void musb_ep0_tx_ready_and_last(void) +{ + u16 csr0; + + csr0 = readw(&musbr->ep[0].ep0.csr0); + csr0 |= (MUSB_CSR0_TXPKTRDY | MUSB_CSR0_P_DATAEND); + writew(csr0, &musbr->ep[0].ep0.csr0); +} + +static void musb_peri_ep0_last(void) +{ + u16 csr0; + + csr0 = readw(&musbr->ep[0].ep0.csr0); + csr0 |= MUSB_CSR0_P_DATAEND; + writew(csr0, &musbr->ep[0].ep0.csr0); +} + +static void musb_peri_ep0_set_address(void) +{ + u8 faddr; + writeb(udc_device->address, &musbr->faddr); + + /* Verify */ + faddr = readb(&musbr->faddr); + if (udc_device->address == faddr) { + SET_EP0_STATE(IDLE); + usbd_device_event_irq(udc_device, DEVICE_ADDRESS_ASSIGNED, 0); + if ((debug_setup) && (debug_level > 1)) + serial_printf("INFO : %s Address set to %d\n", + __PRETTY_FUNCTION__, udc_device->address); + } else { + if (debug_level > 0) + serial_printf("ERROR : %s Address missmatch " + "sw %d vs hw %d\n", + __PRETTY_FUNCTION__, + udc_device->address, faddr); + } +} + +static void musb_peri_rx_ack(unsigned int ep) +{ + u16 peri_rxcsr; + + peri_rxcsr = readw(&musbr->ep[ep].epN.rxcsr); + peri_rxcsr &= ~MUSB_RXCSR_RXPKTRDY; + writew(peri_rxcsr, &musbr->ep[ep].epN.rxcsr); +} + +static void musb_peri_tx_ready(unsigned int ep) +{ + u16 peri_txcsr; + + peri_txcsr = readw(&musbr->ep[ep].epN.txcsr); + peri_txcsr |= MUSB_TXCSR_TXPKTRDY; + writew(peri_txcsr, &musbr->ep[ep].epN.txcsr); +} + +static void musb_peri_ep0_zero_data_request(int err) +{ + musb_peri_ep0_ack_req(); + + if (err) { + musb_peri_ep0_stall(); + SET_EP0_STATE(IDLE); + } else { + + musb_peri_ep0_last(); + + /* USBD state */ + switch (ep0_urb->device_request.bRequest) { + case USB_REQ_SET_ADDRESS: + if ((debug_setup) && (debug_level > 1)) + serial_printf("INFO : %s received set " + "address\n", __PRETTY_FUNCTION__); + break; + + case USB_REQ_SET_CONFIGURATION: + if ((debug_setup) && (debug_level > 1)) + serial_printf("INFO : %s Configured\n", + __PRETTY_FUNCTION__); + usbd_device_event_irq(udc_device, DEVICE_CONFIGURED, 0); + break; + } + + /* EP0 state */ + if (USB_REQ_SET_ADDRESS == ep0_urb->device_request.bRequest) { + SET_EP0_STATE(SET_ADDRESS); + } else { + SET_EP0_STATE(IDLE); + } + } +} + +static void musb_peri_ep0_rx_data_request(void) +{ + /* + * This is the completion of the data OUT / RX + * + * Host is sending data to ep0 that is not + * part of setup. This comes from the cdc_recv_setup + * op that is device specific. + * + */ + musb_peri_ep0_ack_req(); + + ep0_endpoint->rcv_urb = ep0_urb; + ep0_urb->actual_length = 0; + SET_EP0_STATE(RX); +} + +static void musb_peri_ep0_tx_data_request(int err) +{ + if (err) { + musb_peri_ep0_stall(); + SET_EP0_STATE(IDLE); + } else { + musb_peri_ep0_ack_req(); + + ep0_endpoint->tx_urb = ep0_urb; + ep0_endpoint->sent = 0; + SET_EP0_STATE(TX); + } +} + +static void musb_peri_ep0_idle(void) +{ + u16 count0; + int err; + u16 csr0; + + /* + * Verify addresses + * A lot of confusion can be caused if the address + * in software, udc layer, does not agree with the + * hardware. Since the setting of the hardware address + * must be set after the set address request, the + * usb state machine is out of sync for a few frame. + * It is a good idea to run this check when changes + * are made to the state machine. + */ + if ((debug_level > 0) && + (ep0_state != SET_ADDRESS)) { + u8 faddr; + + faddr = readb(&musbr->faddr); + if (udc_device->address != faddr) { + serial_printf("ERROR : %s addresses do not" + "match sw %d vs hw %d\n", + __PRETTY_FUNCTION__, + udc_device->address, faddr); + udelay(1000 * 1000); + hang(); + } + } + + csr0 = readw(&musbr->ep[0].ep0.csr0); + + if (!(MUSB_CSR0_RXPKTRDY & csr0)) + goto end; + + count0 = readw(&musbr->ep[0].ep0.count0); + if (count0 == 0) + goto end; + + if (count0 != 8) { + if ((debug_setup) && (debug_level > 1)) + serial_printf("WARN : %s SETUP incorrect size %d\n", + __PRETTY_FUNCTION__, count0); + musb_peri_ep0_stall(); + goto end; + } + + read_fifo(0, count0, &ep0_urb->device_request); + + if (debug_level > 2) + print_usb_device_request(&ep0_urb->device_request); + + if (ep0_urb->device_request.wLength == 0) { + err = ep0_recv_setup(ep0_urb); + + /* Zero data request */ + musb_peri_ep0_zero_data_request(err); + } else { + /* Is data coming or going ? */ + u8 reqType = ep0_urb->device_request.bmRequestType; + + if (USB_REQ_DEVICE2HOST == (reqType & USB_REQ_DIRECTION_MASK)) { + err = ep0_recv_setup(ep0_urb); + /* Device to host */ + musb_peri_ep0_tx_data_request(err); + } else { + /* + * Host to device + * + * The RX routine will call ep0_recv_setup + * when the data packet has arrived. + */ + musb_peri_ep0_rx_data_request(); + } + } + +end: + return; +} + +static void musb_peri_ep0_rx(void) +{ + /* + * This is the completion of the data OUT / RX + * + * Host is sending data to ep0 that is not + * part of setup. This comes from the cdc_recv_setup + * op that is device specific. + * + * Pass the data back to driver ep0_recv_setup which + * should give the cdc_recv_setup the chance to handle + * the rx + */ + u16 csr0; + u16 count0; + + if (debug_level > 3) { + if (0 != ep0_urb->actual_length) { + serial_printf("%s finished ? %d of %d\n", + __PRETTY_FUNCTION__, + ep0_urb->actual_length, + ep0_urb->device_request.wLength); + } + } + + if (ep0_urb->device_request.wLength == ep0_urb->actual_length) { + musb_peri_ep0_last(); + SET_EP0_STATE(IDLE); + ep0_recv_setup(ep0_urb); + return; + } + + csr0 = readw(&musbr->ep[0].ep0.csr0); + if (!(MUSB_CSR0_RXPKTRDY & csr0)) + return; + + count0 = readw(&musbr->ep[0].ep0.count0); + + if (count0) { + struct usb_endpoint_instance *endpoint; + u32 length; + u8 *data; + + endpoint = ep0_endpoint; + if (endpoint && endpoint->rcv_urb) { + struct urb *urb = endpoint->rcv_urb; + unsigned int remaining_space = urb->buffer_length - + urb->actual_length; + + if (remaining_space) { + int urb_bad = 0; /* urb is good */ + + if (count0 > remaining_space) + length = remaining_space; + else + length = count0; + + data = (u8 *) urb->buffer_data; + data += urb->actual_length; + + /* The common musb fifo reader */ + read_fifo(0, length, data); + + musb_peri_ep0_ack_req(); + + /* + * urb's actual_length is updated in + * usbd_rcv_complete + */ + usbd_rcv_complete(endpoint, length, urb_bad); + + } else { + if (debug_level > 0) + serial_printf("ERROR : %s no space in " + "rcv buffer\n", + __PRETTY_FUNCTION__); + } + } else { + if (debug_level > 0) + serial_printf("ERROR : %s problem with " + "endpoint\n", + __PRETTY_FUNCTION__); + } + } else { + if (debug_level > 0) + serial_printf("ERROR : %s with nothing to do\n", + __PRETTY_FUNCTION__); + } +} + +static void musb_peri_ep0_tx(void) +{ + u16 csr0; + int transfer_size = 0; + unsigned int p, pm; + + csr0 = readw(&musbr->ep[0].ep0.csr0); + + /* Check for pending tx */ + if (csr0 & MUSB_CSR0_TXPKTRDY) + goto end; + + /* Check if this is the last packet sent */ + if (ep0_endpoint->sent >= ep0_urb->actual_length) { + SET_EP0_STATE(IDLE); + goto end; + } + + transfer_size = ep0_urb->actual_length - ep0_endpoint->sent; + /* Is the transfer size negative ? */ + if (transfer_size <= 0) { + if (debug_level > 0) + serial_printf("ERROR : %s problem with the" + " transfer size %d\n", + __PRETTY_FUNCTION__, + transfer_size); + SET_EP0_STATE(IDLE); + goto end; + } + + /* Truncate large transfers to the fifo size */ + if (transfer_size > ep0_endpoint->tx_packetSize) + transfer_size = ep0_endpoint->tx_packetSize; + + write_fifo(0, transfer_size, &ep0_urb->buffer[ep0_endpoint->sent]); + ep0_endpoint->sent += transfer_size; + + /* Done or more to send ? */ + if (ep0_endpoint->sent >= ep0_urb->actual_length) + musb_ep0_tx_ready_and_last(); + else + musb_ep0_tx_ready(); + + /* Wait a bit */ + pm = 10; + for (p = 0; p < pm; p++) { + csr0 = readw(&musbr->ep[0].ep0.csr0); + if (!(csr0 & MUSB_CSR0_TXPKTRDY)) + break; + + /* Double the delay. */ + udelay(1 << pm); + } + + if ((ep0_endpoint->sent >= ep0_urb->actual_length) && (p < pm)) + SET_EP0_STATE(IDLE); + +end: + return; +} + +static void musb_peri_ep0(void) +{ + u16 csr0; + + if (SET_ADDRESS == ep0_state) + return; + + csr0 = readw(&musbr->ep[0].ep0.csr0); + + /* Error conditions */ + if (MUSB_CSR0_P_SENTSTALL & csr0) { + csr0 &= ~MUSB_CSR0_P_SENTSTALL; + writew(csr0, &musbr->ep[0].ep0.csr0); + SET_EP0_STATE(IDLE); + } + if (MUSB_CSR0_P_SETUPEND & csr0) { + csr0 |= MUSB_CSR0_P_SVDSETUPEND; + writew(csr0, &musbr->ep[0].ep0.csr0); + SET_EP0_STATE(IDLE); + if ((debug_setup) && (debug_level > 1)) + serial_printf("WARN: %s SETUPEND\n", + __PRETTY_FUNCTION__); + } + + /* Normal states */ + if (IDLE == ep0_state) + musb_peri_ep0_idle(); + + if (TX == ep0_state) + musb_peri_ep0_tx(); + + if (RX == ep0_state) + musb_peri_ep0_rx(); +} + +static void musb_peri_rx_ep(unsigned int ep) +{ + u16 peri_rxcount = readw(&musbr->ep[ep].epN.rxcount); + + if (peri_rxcount) { + struct usb_endpoint_instance *endpoint; + u32 length; + u8 *data; + + endpoint = GET_ENDPOINT(udc_device, ep); + if (endpoint && endpoint->rcv_urb) { + struct urb *urb = endpoint->rcv_urb; + unsigned int remaining_space = urb->buffer_length - + urb->actual_length; + + if (remaining_space) { + int urb_bad = 0; /* urb is good */ + + if (peri_rxcount > remaining_space) + length = remaining_space; + else + length = peri_rxcount; + + data = (u8 *) urb->buffer_data; + data += urb->actual_length; + + /* The common musb fifo reader */ + read_fifo(ep, length, data); + + musb_peri_rx_ack(ep); + + /* + * urb's actual_length is updated in + * usbd_rcv_complete + */ + usbd_rcv_complete(endpoint, length, urb_bad); + + } else { + if (debug_level > 0) + serial_printf("ERROR : %s %d no space " + "in rcv buffer\n", + __PRETTY_FUNCTION__, ep); + } + } else { + if (debug_level > 0) + serial_printf("ERROR : %s %d problem with " + "endpoint\n", + __PRETTY_FUNCTION__, ep); + } + + } else { + if (debug_level > 0) + serial_printf("ERROR : %s %d with nothing to do\n", + __PRETTY_FUNCTION__, ep); + } +} + +static void musb_peri_rx(u16 intr) +{ + unsigned int ep; + + /* Check for EP0 */ + if (0x01 & intr) + musb_peri_ep0(); + + for (ep = 1; ep < 16; ep++) { + if ((1 << ep) & intr) + musb_peri_rx_ep(ep); + } +} + +static void musb_peri_tx(u16 intr) +{ + /* Check for EP0 */ + if (0x01 & intr) + musb_peri_ep0_tx(); + + /* + * Use this in the future when handling epN tx + * + * u8 ep; + * + * for (ep = 1; ep < 16; ep++) { + * if ((1 << ep) & intr) { + * / * handle tx for this endpoint * / + * } + * } + */ +} + +void udc_irq(void) +{ + /* This is a high freq called function */ + if (enabled) { + u8 intrusb; + + intrusb = readb(&musbr->intrusb); + + /* + * See drivers/usb/gadget/mpc8xx_udc.c for + * state diagram going from detached through + * configuration. + */ + if (MUSB_INTR_RESUME & intrusb) { + usbd_device_event_irq(udc_device, + DEVICE_BUS_ACTIVITY, 0); + musb_peri_resume(); + } + + musb_peri_ep0(); + + if (MUSB_INTR_RESET & intrusb) { + usbd_device_event_irq(udc_device, DEVICE_RESET, 0); + musb_peri_reset(); + } + + if (MUSB_INTR_DISCONNECT & intrusb) { + /* cable unplugged from hub/host */ + usbd_device_event_irq(udc_device, DEVICE_RESET, 0); + musb_peri_reset(); + usbd_device_event_irq(udc_device, DEVICE_HUB_RESET, 0); + } + + if (MUSB_INTR_SOF & intrusb) { + usbd_device_event_irq(udc_device, + DEVICE_BUS_ACTIVITY, 0); + musb_peri_resume(); + } + + if (MUSB_INTR_SUSPEND & intrusb) { + usbd_device_event_irq(udc_device, + DEVICE_BUS_INACTIVE, 0); + } + + if (ep0_state != SET_ADDRESS) { + u16 intrrx, intrtx; + + intrrx = readw(&musbr->intrrx); + intrtx = readw(&musbr->intrtx); + + if (intrrx) + musb_peri_rx(intrrx); + + if (intrtx) + musb_peri_tx(intrtx); + } else { + if (MUSB_INTR_SOF & intrusb) { + u8 faddr; + faddr = readb(&musbr->faddr); + /* + * Setting of the address can fail. + * Normally it succeeds the second time. + */ + if (udc_device->address != faddr) + musb_peri_ep0_set_address(); + } + } + } +} + +void udc_set_nak(int ep_num) +{ + /* noop */ +} + +void udc_unset_nak(int ep_num) +{ + /* noop */ +} + +int udc_endpoint_write(struct usb_endpoint_instance *endpoint) +{ + int ret = 0; + + /* Transmit only if the hardware is available */ + if (endpoint->tx_urb && endpoint->state == 0) { + unsigned int ep = endpoint->endpoint_address & + USB_ENDPOINT_NUMBER_MASK; + + u16 peri_txcsr = readw(&musbr->ep[ep].epN.txcsr); + + /* Error conditions */ + if (peri_txcsr & MUSB_TXCSR_P_UNDERRUN) { + peri_txcsr &= ~MUSB_TXCSR_P_UNDERRUN; + writew(peri_txcsr, &musbr->ep[ep].epN.txcsr); + } + + if (debug_level > 1) + musb_print_txcsr(peri_txcsr); + + /* Check if a packet is waiting to be sent */ + if (!(peri_txcsr & MUSB_TXCSR_TXPKTRDY)) { + u32 length; + u8 *data; + struct urb *urb = endpoint->tx_urb; + unsigned int remaining_packet = urb->actual_length - + endpoint->sent; + + if (endpoint->tx_packetSize < remaining_packet) + length = endpoint->tx_packetSize; + else + length = remaining_packet; + + data = (u8 *) urb->buffer; + data += endpoint->sent; + + /* common musb fifo function */ + write_fifo(ep, length, data); + + musb_peri_tx_ready(ep); + + endpoint->last = length; + /* usbd_tx_complete will take care of updating 'sent' */ + usbd_tx_complete(endpoint); + } + } else { + if (debug_level > 0) + serial_printf("ERROR : %s Problem with urb %p " + "or ep state %d\n", + __PRETTY_FUNCTION__, + endpoint->tx_urb, endpoint->state); + } + + return ret; +} + +void udc_setup_ep(struct usb_device_instance *device, unsigned int id, + struct usb_endpoint_instance *endpoint) +{ + if (0 == id) { + /* EP0 */ + ep0_endpoint = endpoint; + ep0_endpoint->endpoint_address = 0xff; + ep0_urb = usbd_alloc_urb(device, endpoint); + } else if (MAX_ENDPOINT >= id) { + int ep_addr; + + /* Check the direction */ + ep_addr = endpoint->endpoint_address; + if (USB_DIR_IN == (ep_addr & USB_ENDPOINT_DIR_MASK)) { + /* IN */ + epinfo[(id * 2) + 1].epsize = endpoint->tx_packetSize; + } else { + /* OUT */ + epinfo[id * 2].epsize = endpoint->rcv_packetSize; + } + + musb_configure_ep(&epinfo[0], + sizeof(epinfo) / sizeof(struct musb_epinfo)); + } else { + if (debug_level > 0) + serial_printf("ERROR : %s endpoint request %d " + "exceeds maximum %d\n", + __PRETTY_FUNCTION__, id, MAX_ENDPOINT); + } +} + +void udc_connect(void) +{ + /* noop */ +} + +void udc_disconnect(void) +{ + /* noop */ +} + +void udc_enable(struct usb_device_instance *device) +{ + /* Save the device structure pointer */ + udc_device = device; + + enabled = 1; +} + +void udc_disable(void) +{ + enabled = 0; +} + +void udc_startup_events(struct usb_device_instance *device) +{ + /* The DEVICE_INIT event puts the USB device in the state STATE_INIT. */ + usbd_device_event_irq(device, DEVICE_INIT, 0); + + /* + * The DEVICE_CREATE event puts the USB device in the state + * STATE_ATTACHED. + */ + usbd_device_event_irq(device, DEVICE_CREATE, 0); + + /* Resets the address to 0 */ + usbd_device_event_irq(device, DEVICE_RESET, 0); + + udc_enable(device); +} + +int udc_init(void) +{ + int ret; + int ep_loop; + + ret = musb_platform_init(); + if (ret < 0) + goto end; + + /* Configure all the endpoint FIFO's and start usb controller */ + musbr = musb_cfg.regs; + + /* Initialize the endpoints */ + for (ep_loop = 0; ep_loop < MAX_ENDPOINT * 2; ep_loop++) { + epinfo[ep_loop].epnum = (ep_loop / 2) + 1; + epinfo[ep_loop].epdir = ep_loop % 2; /* OUT, IN */ + epinfo[ep_loop].epsize = 0; + } + + musb_peri_softconnect(); + + ret = 0; +end: + + return ret; +} diff --git a/roms/u-boot-sam460ex/drivers/usb/musb/omap3.c b/roms/u-boot-sam460ex/drivers/usb/musb/omap3.c new file mode 100644 index 000000000..3bfd0a085 --- /dev/null +++ b/roms/u-boot-sam460ex/drivers/usb/musb/omap3.c @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2009 Wind River Systems, Inc. + * Tom Rix <Tom.Rix@windriver.com> + * + * This is file is based on + * repository git.gitorious.org/u-boot-omap3/mainline.git, + * branch omap3-dev-usb, file drivers/usb/host/omap3530_usb.c + * + * This is the unique part of its copyright : + * + * ------------------------------------------------------------------------ + * + * Copyright (c) 2009 Texas Instruments + * + * ------------------------------------------------------------------------ + * + * 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 <twl4030.h> +#include "omap3.h" + +static int platform_needs_initialization = 1; + +struct musb_config musb_cfg = { + (struct musb_regs *)MENTOR_USB0_BASE, + OMAP3_USB_TIMEOUT, + 0 +}; + +/* + * OMAP3 USB OTG registers. + */ +struct omap3_otg_regs { + u32 revision; + u32 sysconfig; + u32 sysstatus; + u32 interfsel; + u32 simenable; + u32 forcestdby; +}; + +static struct omap3_otg_regs *otg; + +#define OMAP3_OTG_SYSCONFIG_SMART_STANDBY_MODE 0x2000 +#define OMAP3_OTG_SYSCONFIG_NO_STANDBY_MODE 0x1000 +#define OMAP3_OTG_SYSCONFIG_SMART_IDLE_MODE 0x0010 +#define OMAP3_OTG_SYSCONFIG_NO_IDLE_MODE 0x0008 +#define OMAP3_OTG_SYSCONFIG_ENABLEWAKEUP 0x0004 +#define OMAP3_OTG_SYSCONFIG_SOFTRESET 0x0002 +#define OMAP3_OTG_SYSCONFIG_AUTOIDLE 0x0001 + +#define OMAP3_OTG_SYSSTATUS_RESETDONE 0x0001 + +#define OMAP3_OTG_INTERFSEL_OMAP 0x0001 + +#define OMAP3_OTG_FORCESTDBY_STANDBY 0x0001 + + +#ifdef DEBUG_MUSB_OMAP3 +static void musb_db_otg_regs(void) +{ + u32 l; + l = readl(&otg->revision); + serial_printf("OTG_REVISION 0x%x\n", l); + l = readl(&otg->sysconfig); + serial_printf("OTG_SYSCONFIG 0x%x\n", l); + l = readl(&otg->sysstatus); + serial_printf("OTG_SYSSTATUS 0x%x\n", l); + l = readl(&otg->interfsel); + serial_printf("OTG_INTERFSEL 0x%x\n", l); + l = readl(&otg->forcestdby); + serial_printf("OTG_FORCESTDBY 0x%x\n", l); +} +#endif + +int musb_platform_init(void) +{ + int ret = -1; + + if (platform_needs_initialization) { + u32 stdby; + + /* + * OMAP3EVM uses ISP1504 phy and so + * twl4030 related init is not required. + */ +#ifdef CONFIG_TWL4030_USB + if (twl4030_usb_ulpi_init()) { + serial_printf("ERROR: %s Could not initialize PHY\n", + __PRETTY_FUNCTION__); + goto end; + } +#endif + otg = (struct omap3_otg_regs *)OMAP3_OTG_BASE; + + /* Set OTG to always be on */ + writel(OMAP3_OTG_SYSCONFIG_NO_STANDBY_MODE | + OMAP3_OTG_SYSCONFIG_NO_IDLE_MODE, &otg->sysconfig); + + /* Set the interface */ + writel(OMAP3_OTG_INTERFSEL_OMAP, &otg->interfsel); + + /* Clear force standby */ + stdby = readl(&otg->forcestdby); + stdby &= ~OMAP3_OTG_FORCESTDBY_STANDBY; + writel(stdby, &otg->forcestdby); + + platform_needs_initialization = 0; + } + + ret = platform_needs_initialization; + +#ifdef CONFIG_TWL4030_USB +end: +#endif + return ret; + +} + +void musb_platform_deinit(void) +{ + /* noop */ +} diff --git a/roms/u-boot-sam460ex/drivers/usb/musb/omap3.h b/roms/u-boot-sam460ex/drivers/usb/musb/omap3.h new file mode 100644 index 000000000..b591862b2 --- /dev/null +++ b/roms/u-boot-sam460ex/drivers/usb/musb/omap3.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2009 Wind River Systems, Inc. + * Tom Rix <Tom.Rix@windriver.com> + * + * This file is based on the file drivers/usb/musb/davinci.h + * + * This is the unique part of its copyright: + * + * -------------------------------------------------------------------- + * + * Copyright (c) 2008 Texas Instruments + * Author: Thomas Abraham t-abraham@ti.com, Texas Instruments + * + * -------------------------------------------------------------------- + * + * 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 _MUSB_OMAP3_H_ +#define _MUSB_OMAP3_H_ + +#include "musb_core.h" + +/* Base address of MUSB registers */ +#define MENTOR_USB0_BASE (OMAP34XX_CORE_L4_IO_BASE + 0xAB000) + +/* Base address of OTG registers */ +#define OMAP3_OTG_BASE (MENTOR_USB0_BASE + 0x400) + +/* Timeout for USB module */ +#define OMAP3_USB_TIMEOUT 0x3FFFFFF + +int musb_platform_init(void); + +#endif /* _MUSB_OMAP3_H */ diff --git a/roms/u-boot-sam460ex/drivers/usb/phy/Makefile b/roms/u-boot-sam460ex/drivers/usb/phy/Makefile new file mode 100644 index 000000000..f09e55fd4 --- /dev/null +++ b/roms/u-boot-sam460ex/drivers/usb/phy/Makefile @@ -0,0 +1,43 @@ +# +# Copyright (c) 2009 Wind River Systems, Inc. +# Tom Rix <Tom.Rix@windriver.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 $(TOPDIR)/config.mk + +LIB := $(obj)libusb_phy.a + +COBJS-$(CONFIG_TWL4030_USB) += twl4030.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/roms/u-boot-sam460ex/drivers/usb/phy/twl4030.c b/roms/u-boot-sam460ex/drivers/usb/phy/twl4030.c new file mode 100644 index 000000000..54d2e615c --- /dev/null +++ b/roms/u-boot-sam460ex/drivers/usb/phy/twl4030.c @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2009 Wind River Systems, Inc. + * Tom Rix <Tom.Rix@windriver.com> + * + * This is file is based on + * repository git.gitorious.org/u-boot-omap3/mainline.git, + * branch omap3-dev-usb, file drivers/usb/gadget/twl4030_usb.c + * + * This is the unique part of its copyright : + * + * ------------------------------------------------------------------------ + * + * * (C) Copyright 2009 Atin Malaviya (atin.malaviya@gmail.com) + * + * Based on: twl4030_usb.c in linux 2.6 (drivers/i2c/chips/twl4030_usb.c) + * Copyright (C) 2004-2007 Texas Instruments + * Copyright (C) 2008 Nokia Corporation + * Contact: Felipe Balbi <felipe.balbi@nokia.com> + * + * Author: Atin Malaviya (atin.malaviya@gmail.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 <twl4030.h> + +/* Defines for bits in registers */ +#define OPMODE_MASK (3 << 3) +#define XCVRSELECT_MASK (3 << 0) +#define CARKITMODE (1 << 2) +#define OTG_ENAB (1 << 5) +#define PHYPWD (1 << 0) +#define CLOCKGATING_EN (1 << 2) +#define CLK32K_EN (1 << 1) +#define REQ_PHY_DPLL_CLK (1 << 0) +#define PHY_DPLL_CLK (1 << 0) + +static int twl4030_usb_write(u8 address, u8 data) +{ + int ret; + + ret = twl4030_i2c_write_u8(TWL4030_CHIP_USB, data, address); + if (ret != 0) + printf("TWL4030:USB:Write[0x%x] Error %d\n", address, ret); + + return ret; +} + +static int twl4030_usb_read(u8 address) +{ + u8 data; + int ret; + + ret = twl4030_i2c_read_u8(TWL4030_CHIP_USB, &data, address); + if (ret == 0) + ret = data; + else + printf("TWL4030:USB:Read[0x%x] Error %d\n", address, ret); + + return ret; +} + +static void twl4030_usb_ldo_init(void) +{ + /* Enable writing to power configuration registers */ + twl4030_i2c_write_u8(TWL4030_CHIP_PM_MASTER, 0xC0, + TWL4030_PM_MASTER_PROTECT_KEY); + twl4030_i2c_write_u8(TWL4030_CHIP_PM_MASTER, 0x0C, + TWL4030_PM_MASTER_PROTECT_KEY); + + /* put VUSB3V1 LDO in active state */ + twl4030_i2c_write_u8(TWL4030_CHIP_PM_RECEIVER, 0x00, + TWL4030_PM_RECEIVER_VUSB_DEDICATED2); + + /* input to VUSB3V1 LDO is from VBAT, not VBUS */ + twl4030_i2c_write_u8(TWL4030_CHIP_PM_RECEIVER, 0x14, + TWL4030_PM_RECEIVER_VUSB_DEDICATED1); + + /* turn on 3.1V regulator */ + twl4030_i2c_write_u8(TWL4030_CHIP_PM_RECEIVER, 0x20, + TWL4030_PM_RECEIVER_VUSB3V1_DEV_GRP); + twl4030_i2c_write_u8(TWL4030_CHIP_PM_RECEIVER, 0x00, + TWL4030_PM_RECEIVER_VUSB3V1_TYPE); + + /* turn on 1.5V regulator */ + twl4030_i2c_write_u8(TWL4030_CHIP_PM_RECEIVER, 0x20, + TWL4030_PM_RECEIVER_VUSB1V5_DEV_GRP); + twl4030_i2c_write_u8(TWL4030_CHIP_PM_RECEIVER, 0x00, + TWL4030_PM_RECEIVER_VUSB1V5_TYPE); + + /* turn on 1.8V regulator */ + twl4030_i2c_write_u8(TWL4030_CHIP_PM_RECEIVER, 0x20, + TWL4030_PM_RECEIVER_VUSB1V8_DEV_GRP); + twl4030_i2c_write_u8(TWL4030_CHIP_PM_RECEIVER, 0x00, + TWL4030_PM_RECEIVER_VUSB1V8_TYPE); + + /* disable access to power configuration registers */ + twl4030_i2c_write_u8(TWL4030_CHIP_PM_MASTER, 0x00, + TWL4030_PM_MASTER_PROTECT_KEY); +} + +static void twl4030_phy_power(void) +{ + u8 pwr, clk; + + /* Power the PHY */ + pwr = twl4030_usb_read(TWL4030_USB_PHY_PWR_CTRL); + pwr &= ~PHYPWD; + twl4030_usb_write(TWL4030_USB_PHY_PWR_CTRL, pwr); + /* Enable clocks */ + clk = twl4030_usb_read(TWL4030_USB_PHY_CLK_CTRL); + clk |= CLOCKGATING_EN | CLK32K_EN; + twl4030_usb_write(TWL4030_USB_PHY_CLK_CTRL, clk); +} + +/* + * Initiaze the ULPI interface + * ULPI : Universal Transceiver Macrocell Low Pin Interface + * An interface between the USB link controller like musb and the + * the PHY or transceiver that drives the actual bus. + */ +int twl4030_usb_ulpi_init(void) +{ + long timeout = 1000 * 1000; /* 1 sec */; + u8 clk, sts, pwr; + + /* twl4030 ldo init */ + twl4030_usb_ldo_init(); + + /* Enable the twl4030 phy */ + twl4030_phy_power(); + + /* Enable DPLL to access PHY registers over I2C */ + clk = twl4030_usb_read(TWL4030_USB_PHY_CLK_CTRL); + clk |= REQ_PHY_DPLL_CLK; + twl4030_usb_write(TWL4030_USB_PHY_CLK_CTRL, clk); + + /* Check if the PHY DPLL is locked */ + sts = twl4030_usb_read(TWL4030_USB_PHY_CLK_CTRL_STS); + while (!(sts & PHY_DPLL_CLK) && 0 < timeout) { + udelay(10); + sts = twl4030_usb_read(TWL4030_USB_PHY_CLK_CTRL_STS); + timeout -= 10; + } + + /* Final check */ + sts = twl4030_usb_read(TWL4030_USB_PHY_CLK_CTRL_STS); + if (!(sts & PHY_DPLL_CLK)) { + printf("Error:TWL4030:USB Timeout setting PHY DPLL clock\n"); + return -1; + } + + /* + * There are two circuit blocks attached to the PHY, + * Carkit and USB OTG. Disable Carkit and enable USB OTG + */ + twl4030_usb_write(TWL4030_USB_IFC_CTRL_CLR, CARKITMODE); + pwr = twl4030_usb_read(TWL4030_USB_POWER_CTRL); + pwr |= OTG_ENAB; + twl4030_usb_write(TWL4030_USB_POWER_CTRL_SET, pwr); + + /* Clear the opmode bits to ensure normal encode */ + twl4030_usb_write(TWL4030_USB_FUNC_CTRL_CLR, OPMODE_MASK); + + /* Clear the xcvrselect bits to enable the high speed transeiver */ + twl4030_usb_write(TWL4030_USB_FUNC_CTRL_CLR, XCVRSELECT_MASK); + + /* Let ULPI control the DPLL clock */ + clk = twl4030_usb_read(TWL4030_USB_PHY_CLK_CTRL); + clk &= ~REQ_PHY_DPLL_CLK; + twl4030_usb_write(TWL4030_USB_PHY_CLK_CTRL, clk); + + return 0; +} |