diff options
Diffstat (limited to 'drivers/usb')
109 files changed, 29391 insertions, 403 deletions
diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index 48f1781..e9660f1 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -120,6 +120,8 @@ source "drivers/usb/musb/Kconfig" source "drivers/usb/renesas_usbhs/Kconfig" +source "drivers/usb/dwc/Kconfig" + source "drivers/usb/class/Kconfig" source "drivers/usb/storage/Kconfig" @@ -168,4 +170,6 @@ source "drivers/usb/gadget/Kconfig" source "drivers/usb/otg/Kconfig" +source "drivers/usb/notify/Kconfig" + endif # USB_SUPPORT diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile index 30ddf8d..5adcecf 100644 --- a/drivers/usb/Makefile +++ b/drivers/usb/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_USB) += core/ obj-$(CONFIG_USB_MON) += mon/ obj-$(CONFIG_PCI) += host/ +obj-$(CONFIG_USB_S3C_OTG_HOST) += host/ obj-$(CONFIG_USB_EHCI_HCD) += host/ obj-$(CONFIG_USB_ISP116X_HCD) += host/ obj-$(CONFIG_USB_OHCI_HCD) += host/ @@ -50,4 +51,7 @@ obj-$(CONFIG_USB_SPEEDTOUCH) += atm/ obj-$(CONFIG_USB_MUSB_HDRC) += musb/ obj-$(CONFIG_USB_RENESAS_USBHS) += renesas_usbhs/ obj-$(CONFIG_USB_OTG_UTILS) += otg/ +obj-$(CONFIG_USB_DWC_OTG) += dwc/ obj-$(CONFIG_USB_GADGET) += gadget/ + +obj-$(CONFIG_USB_HOST_NOTIFY) += notify/ diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index 158f631..b107339 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -498,6 +498,14 @@ static int acm_tty_open(struct tty_struct *tty, struct file *filp) usb_autopm_put_interface(acm->control); + /* + * Unthrottle device in case the TTY was closed while throttled. + */ + spin_lock_irq(&acm->read_lock); + acm->throttled = 0; + acm->throttle_req = 0; + spin_unlock_irq(&acm->read_lock); + if (acm_submit_read_urbs(acm, GFP_KERNEL)) goto bail_out; @@ -752,10 +760,6 @@ static const __u32 acm_tty_speed[] = { 2500000, 3000000, 3500000, 4000000 }; -static const __u8 acm_tty_size[] = { - 5, 6, 7, 8 -}; - static void acm_tty_set_termios(struct tty_struct *tty, struct ktermios *termios_old) { @@ -772,7 +776,21 @@ static void acm_tty_set_termios(struct tty_struct *tty, newline.bParityType = termios->c_cflag & PARENB ? (termios->c_cflag & PARODD ? 1 : 2) + (termios->c_cflag & CMSPAR ? 2 : 0) : 0; - newline.bDataBits = acm_tty_size[(termios->c_cflag & CSIZE) >> 4]; + switch (termios->c_cflag & CSIZE) { + case CS5: + newline.bDataBits = 5; + break; + case CS6: + newline.bDataBits = 6; + break; + case CS7: + newline.bDataBits = 7; + break; + case CS8: + default: + newline.bDataBits = 8; + break; + } /* FIXME: Needs to clear unsupported bits in the termios */ acm->clocal = ((termios->c_cflag & CLOCAL) != 0); @@ -1035,7 +1053,8 @@ skip_normal_probe: } - if (data_interface->cur_altsetting->desc.bNumEndpoints < 2) + if (data_interface->cur_altsetting->desc.bNumEndpoints < 2 || + control_interface->cur_altsetting->desc.bNumEndpoints == 0) return -EINVAL; epctrl = &control_interface->cur_altsetting->endpoint[0].desc; @@ -1163,7 +1182,7 @@ made_compressed_probe: if (usb_endpoint_xfer_int(epwrite)) usb_fill_int_urb(snd->urb, usb_dev, - usb_sndbulkpipe(usb_dev, epwrite->bEndpointAddress), + usb_sndintpipe(usb_dev, epwrite->bEndpointAddress), NULL, acm->writesize, acm_write_bulk, snd, epwrite->bInterval); else usb_fill_bulk_urb(snd->urb, usb_dev, @@ -1487,6 +1506,9 @@ static const struct usb_device_id acm_ids[] = { Maybe we should define a new quirk for this. */ }, + { USB_DEVICE(0x0572, 0x1340), /* Conexant CX93010-2x UCMxx */ + .driver_info = NO_UNION_NORMAL, + }, { USB_DEVICE(0x1bbb, 0x0003), /* Alcatel OT-I650 */ .driver_info = NO_UNION_NORMAL, /* reports zero length descriptor */ }, diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c index 00b7bf9..8a72e05 100644 --- a/drivers/usb/class/cdc-wdm.c +++ b/drivers/usb/class/cdc-wdm.c @@ -457,6 +457,8 @@ retry: goto retry; } if (!desc->reslength) { /* zero length read */ + dev_dbg(&desc->intf->dev, "%s: zero length - clearing WDM_READ\n", __func__); + clear_bit(WDM_READ, &desc->flags); spin_unlock_irq(&desc->iuspin); goto retry; } @@ -511,7 +513,7 @@ static unsigned int wdm_poll(struct file *file, struct poll_table_struct *wait) spin_lock_irqsave(&desc->iuspin, flags); if (test_bit(WDM_DISCONNECTING, &desc->flags)) { - mask = POLLERR; + mask = POLLHUP | POLLERR; spin_unlock_irqrestore(&desc->iuspin, flags); goto desc_out; } diff --git a/drivers/usb/core/devices.c b/drivers/usb/core/devices.c index 0149c09..ca98341 100644 --- a/drivers/usb/core/devices.c +++ b/drivers/usb/core/devices.c @@ -624,7 +624,7 @@ static ssize_t usb_device_read(struct file *file, char __user *buf, /* print devices for all busses */ list_for_each_entry(bus, &usb_bus_list, bus_list) { /* recurse through all children of the root hub */ - if (!bus->root_hub) + if (!bus_to_hcd(bus)->rh_registered) continue; usb_lock_device(bus->root_hub); ret = usb_device_dump(&buf, &nbytes, &skip_bytes, ppos, diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c index 0ca54e2..4d1f996 100644 --- a/drivers/usb/core/devio.c +++ b/drivers/usb/core/devio.c @@ -292,17 +292,14 @@ static struct async *async_getcompleted(struct dev_state *ps) static struct async *async_getpending(struct dev_state *ps, void __user *userurb) { - unsigned long flags; struct async *as; - spin_lock_irqsave(&ps->lock, flags); list_for_each_entry(as, &ps->async_pending, asynclist) if (as->userurb == userurb) { list_del_init(&as->asynclist); - spin_unlock_irqrestore(&ps->lock, flags); return as; } - spin_unlock_irqrestore(&ps->lock, flags); + return NULL; } @@ -357,6 +354,7 @@ static void cancel_bulk_urbs(struct dev_state *ps, unsigned bulk_addr) __releases(ps->lock) __acquires(ps->lock) { + struct urb *urb; struct async *as; /* Mark all the pending URBs that match bulk_addr, up to but not @@ -379,8 +377,11 @@ __acquires(ps->lock) list_for_each_entry(as, &ps->async_pending, asynclist) { if (as->bulk_status == AS_UNLINK) { as->bulk_status = 0; /* Only once */ + urb = as->urb; + usb_get_urb(urb); spin_unlock(&ps->lock); /* Allow completions */ - usb_unlink_urb(as->urb); + usb_unlink_urb(urb); + usb_put_urb(urb); spin_lock(&ps->lock); goto rescan; } @@ -433,6 +434,7 @@ static void async_completed(struct urb *urb) static void destroy_async(struct dev_state *ps, struct list_head *list) { + struct urb *urb; struct async *as; unsigned long flags; @@ -440,10 +442,13 @@ static void destroy_async(struct dev_state *ps, struct list_head *list) while (!list_empty(list)) { as = list_entry(list->next, struct async, asynclist); list_del_init(&as->asynclist); + urb = as->urb; + usb_get_urb(urb); /* drop the spinlock so the completion handler can run */ spin_unlock_irqrestore(&ps->lock, flags); - usb_kill_urb(as->urb); + usb_kill_urb(urb); + usb_put_urb(urb); spin_lock_irqsave(&ps->lock, flags); } spin_unlock_irqrestore(&ps->lock, flags); @@ -1352,12 +1357,24 @@ static int proc_submiturb(struct dev_state *ps, void __user *arg) static int proc_unlinkurb(struct dev_state *ps, void __user *arg) { + struct urb *urb; struct async *as; + unsigned long flags; + spin_lock_irqsave(&ps->lock, flags); as = async_getpending(ps, arg); - if (!as) + if (!as) { + spin_unlock_irqrestore(&ps->lock, flags); return -EINVAL; - usb_kill_urb(as->urb); + } + + urb = as->urb; + usb_get_urb(urb); + spin_unlock_irqrestore(&ps->lock, flags); + + usb_kill_urb(urb); + usb_put_urb(urb); + return 0; } @@ -1540,10 +1557,14 @@ static int processcompl_compat(struct async *as, void __user * __user *arg) void __user *addr = as->userurb; unsigned int i; - if (as->userbuffer && urb->actual_length) - if (copy_to_user(as->userbuffer, urb->transfer_buffer, - urb->actual_length)) + if (as->userbuffer && urb->actual_length) { + if (urb->number_of_packets > 0) /* Isochronous */ + i = urb->transfer_buffer_length; + else /* Non-Isoc */ + i = urb->actual_length; + if (copy_to_user(as->userbuffer, urb->transfer_buffer, i)) return -EFAULT; + } if (put_user(as->status, &userurb->status)) return -EFAULT; if (put_user(urb->actual_length, &userurb->actual_length)) diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index 75b4bc0..962f2b5 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -1617,6 +1617,13 @@ static int autosuspend_check(struct usb_device *udev) /* Fail if autosuspend is disabled, or any interfaces are in use, or * any interface drivers require remote wakeup but it isn't available. */ + +#if defined CONFIG_USB_S3C_OTG_HOST || defined CONFIG_USB_DWC_OTG + /* temporarily disabled autosuspend for otg host */ + if (udev->bus->busnum == 2) + return -EOPNOTSUPP; +#endif + w = 0; if (udev->actconfig) { for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) { diff --git a/drivers/usb/core/hcd-pci.c b/drivers/usb/core/hcd-pci.c index aa7bbbc..6c1642b 100644 --- a/drivers/usb/core/hcd-pci.c +++ b/drivers/usb/core/hcd-pci.c @@ -495,15 +495,6 @@ static int hcd_pci_suspend_noirq(struct device *dev) pci_save_state(pci_dev); - /* - * Some systems crash if an EHCI controller is in D3 during - * a sleep transition. We have to leave such controllers in D0. - */ - if (hcd->broken_pci_sleep) { - dev_dbg(dev, "Staying in PCI D0\n"); - return retval; - } - /* If the root hub is dead rather than suspended, disallow remote * wakeup. usb_hc_died() should ensure that both hosts are marked as * dying, so we only need to check the primary roothub. diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 45e0908..9c24bf5 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -156,7 +156,11 @@ static const u8 usb2_rh_dev_descriptor [18] = { 0x09, /* __u8 bDeviceClass; HUB_CLASSCODE */ 0x00, /* __u8 bDeviceSubClass; */ +#if defined CONFIG_USB_S3C_OTG_HOST || defined CONFIG_USB_DWC_OTG + 0x01, /* __u8 bDeviceProtocol; [ usb 2.0 single TT ] */ +#else 0x00, /* __u8 bDeviceProtocol; [ usb 2.0 no TT ] */ +#endif 0x40, /* __u8 bMaxPacketSize0; 64 Bytes */ 0x6b, 0x1d, /* __le16 idVendor; Linux Foundation */ @@ -977,10 +981,7 @@ static int register_root_hub(struct usb_hcd *hcd) if (retval) { dev_err (parent_dev, "can't register root hub for %s, %d\n", dev_name(&usb_dev->dev), retval); - } - mutex_unlock(&usb_bus_list_lock); - - if (retval == 0) { + } else { spin_lock_irq (&hcd_root_hub_lock); hcd->rh_registered = 1; spin_unlock_irq (&hcd_root_hub_lock); @@ -989,6 +990,7 @@ static int register_root_hub(struct usb_hcd *hcd) if (HCD_DEAD(hcd)) usb_hc_died (hcd); /* This time clean up */ } + mutex_unlock(&usb_bus_list_lock); return retval; } diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 34bb059..b4688fa 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -24,6 +24,7 @@ #include <linux/kthread.h> #include <linux/mutex.h> #include <linux/freezer.h> +#include <linux/random.h> #include <asm/uaccess.h> #include <asm/byteorder.h> @@ -481,13 +482,16 @@ static void hub_tt_work(struct work_struct *work) int limit = 100; spin_lock_irqsave (&hub->tt.lock, flags); - while (--limit && !list_empty (&hub->tt.clear_list)) { + while (!list_empty(&hub->tt.clear_list)) { struct list_head *next; struct usb_tt_clear *clear; struct usb_device *hdev = hub->hdev; const struct hc_driver *drv; int status; + if (!hub->quiescing && --limit < 0) + break; + next = hub->tt.clear_list.next; clear = list_entry (next, struct usb_tt_clear, clear_list); list_del (&clear->clear_list); @@ -951,7 +955,7 @@ static void hub_quiesce(struct usb_hub *hub, enum hub_quiescing_type type) if (hub->has_indicators) cancel_delayed_work_sync(&hub->leds); if (hub->tt.hub) - cancel_work_sync(&hub->tt.clear_work); + flush_work_sync(&hub->tt.clear_work); } /* caller has locked the hub device */ @@ -1902,6 +1906,14 @@ int usb_new_device(struct usb_device *udev) /* Tell the world! */ announce_device(udev); + if (udev->serial) + add_device_randomness(udev->serial, strlen(udev->serial)); + if (udev->product) + add_device_randomness(udev->product, strlen(udev->product)); + if (udev->manufacturer) + add_device_randomness(udev->manufacturer, + strlen(udev->manufacturer)); + device_enable_async_suspend(&udev->dev); /* Register the device. The device driver is responsible * for configuring the device and invoking the add-device diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index 1eebd45..806060c 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -1803,7 +1803,6 @@ free_interfaces: intfc = cp->intf_cache[i]; intf->altsetting = intfc->altsetting; intf->num_altsetting = intfc->num_altsetting; - intf->intf_assoc = find_iad(dev, cp, i); kref_get(&intfc->ref); alt = usb_altnum_to_altsetting(intf, 0); @@ -1816,6 +1815,8 @@ free_interfaces: if (!alt) alt = &intf->altsetting[0]; + intf->intf_assoc = + find_iad(dev, cp, alt->desc.bInterfaceNumber); intf->cur_altsetting = alt; usb_enable_interface(dev, intf, true); intf->dev.parent = &dev->dev; diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c index 4c65eb6..8b2a9d8 100644 --- a/drivers/usb/core/quirks.c +++ b/drivers/usb/core/quirks.c @@ -96,6 +96,10 @@ static const struct usb_device_id usb_quirk_list[] = { { USB_DEVICE(0x04b4, 0x0526), .driver_info = USB_QUIRK_CONFIG_INTF_STRINGS }, + /* Microchip Joss Optical infrared touchboard device */ + { USB_DEVICE(0x04d8, 0x000c), .driver_info = + USB_QUIRK_CONFIG_INTF_STRINGS }, + /* Samsung Android phone modem - ID conflict with SPH-I500 */ { USB_DEVICE(0x04e8, 0x6601), .driver_info = USB_QUIRK_CONFIG_INTF_STRINGS }, @@ -123,6 +127,9 @@ static const struct usb_device_id usb_quirk_list[] = { /* Guillemot Webcam Hercules Dualpix Exchange*/ { USB_DEVICE(0x06f8, 0x3005), .driver_info = USB_QUIRK_RESET_RESUME }, + /* Midiman M-Audio Keystation 88es */ + { USB_DEVICE(0x0763, 0x0192), .driver_info = USB_QUIRK_RESET_RESUME }, + /* M-Systems Flash Disk Pioneers */ { USB_DEVICE(0x08ec, 0x1000), .driver_info = USB_QUIRK_RESET_RESUME }, diff --git a/drivers/usb/dwc/Kconfig b/drivers/usb/dwc/Kconfig new file mode 100644 index 0000000..4439810 --- /dev/null +++ b/drivers/usb/dwc/Kconfig @@ -0,0 +1,84 @@ +# +# USB Dual Role (OTG-ready) Controller Drivers +# for silicon based on Synopsys DesignWare IP +# + +comment "Enable Host or Gadget support for DesignWare OTG controller" + depends on !USB && USB_GADGET=n + +config USB_DWC_OTG + tristate "Synopsys DWC OTG Controller" + depends on USB || USB_GADGET + select NOP_USB_XCEIV + select USB_OTG_UTILS + default USB_GADGET + help + This driver provides USB Device Controller support for the + Synopsys DesignWare USB OTG Core used on the AppliedMicro PowerPC SoC. + +config DWC_DEBUG + bool "Enable DWC Debugging" + depends on USB_DWC_OTG + default n + help + Enable DWC driver debugging + +choice + prompt "DWC Mode Selection" + depends on USB_DWC_OTG + default DWC_HOST_ONLY + help + Select the DWC Core in OTG, Host only, or Device only mode. + +config DWC_HOST_ONLY + bool "DWC Host Only Mode" + +config DWC_OTG_MODE + bool "DWC OTG Mode" + select USB_GADGET_SELECTED + +config DWC_DEVICE_ONLY + bool "DWC Device Only Mode" + select USB_GADGET_SELECTED + +endchoice + +# enable peripheral support (including with OTG) +choice + prompt "DWC DMA/SlaveMode Selection" + depends on USB_DWC_OTG + default DWC_DMA_MODE + help + Select the DWC DMA or Slave Mode. + DMA mode uses the DWC core internal DMA engines. + Slave mode uses the processor PIO to tranfer data. + In Slave mode, processor's DMA channels can be used if available. + +config DWC_SLAVE + bool "DWC Slave Mode" + +config DWC_DMA_MODE + bool "DWC DMA Mode" + +endchoice + +config DWC_OTG_REG_LE + bool "DWC Little Endian Register" + depends on USB_DWC_OTG + default y + help + OTG core register access is Little-Endian. + +config DWC_OTG_FIFO_LE + bool "DWC FIFO Little Endian" + depends on USB_DWC_OTG + default y + help + OTG core FIFO access is Little-Endian. + +config DWC_LIMITED_XFER_SIZE + bool "DWC Endpoint Limited Xfer Size" + depends on USB_GADGET_DWC_HDRC + default n + help + Bit fields in the Device EP Transfer Size Register is 11 bits. diff --git a/drivers/usb/dwc/Makefile b/drivers/usb/dwc/Makefile new file mode 100644 index 0000000..c79d6e6 --- /dev/null +++ b/drivers/usb/dwc/Makefile @@ -0,0 +1,17 @@ +# +# OTG infrastructure and transceiver drivers +# +obj-$(CONFIG_USB_DWC_OTG) += dwc.o + +dwc-objs := cil.o cil_intr.o param.o + +dwc-objs += apmppc.o + +ifneq ($(CONFIG_DWC_DEVICE_ONLY),y) +dwc-objs += hcd.o hcd_intr.o \ + hcd_queue.o +endif + +ifneq ($(CONFIG_DWC_HOST_ONLY),y) +dwc-objs += pcd.o pcd_intr.o +endif diff --git a/drivers/usb/dwc/apmppc.c b/drivers/usb/dwc/apmppc.c new file mode 100644 index 0000000..81881d0 --- /dev/null +++ b/drivers/usb/dwc/apmppc.c @@ -0,0 +1,372 @@ +/* + * DesignWare HS OTG controller driver + * Copyright (C) 2006 Synopsys, Inc. + * Portions Copyright (C) 2010 Applied Micro Circuits Corporation. + * + * This program 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. + * + * 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 version 2 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see http://www.gnu.org/licenses + * or write to the Free Software Foundation, Inc., 51 Franklin Street, + * Suite 500, Boston, MA 02110-1335 USA. + * + * Based on Synopsys driver version 2.60a + * Modified by Mark Miesfeld <mmiesfeld@apm.com> + * Modified by Stefan Roese <sr@denx.de>, DENX Software Engineering + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL SYNOPSYS, INC. BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES + * (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/* + * The dwc_otg module provides the initialization and cleanup entry + * points for the dwcotg driver. This module will be dynamically installed + * after Linux is booted using the insmod command. When the module is + * installed, the dwc_otg_driver_init function is called. When the module is + * removed (using rmmod), the dwc_otg_driver_cleanup function is called. + * + * This module also defines a data structure for the dwc_otg driver, which is + * used in conjunction with the standard device structure. These + * structures allow the OTG driver to comply with the standard Linux driver + * model in which devices and drivers are registered with a bus driver. This + * has the benefit that Linux can expose attributes of the driver and device + * in its special sysfs file system. Users can then read or write files in + * this file system to perform diagnostics on the driver components or the + * device. + */ + +#include <linux/of_platform.h> +#include <mach/map.h> +#include <linux/platform_device.h> +#include "driver.h" +#include <linux/delay.h> + +#define DWC_DRIVER_VERSION "1.05" +#define DWC_DRIVER_DESC "HS OTG USB Controller driver" +static const char dwc_driver_name[] = "dwc_otg"; + +static irqreturn_t dwc_otg_common_irq(int _irq, void *dev) +{ + struct dwc_otg_device *dwc_dev = dev; + int retval; + struct dwc_hcd *dwc_hcd; + + dwc_hcd = dwc_dev->hcd; + spin_lock(&dwc_hcd->lock); + retval = dwc_otg_handle_common_intr(dwc_dev->core_if); + spin_unlock(&dwc_hcd->lock); + return IRQ_RETVAL(retval); +} + +static irqreturn_t dwc_otg_externalchgpump_irq(int _irq, void *dev) +{ + struct dwc_otg_device *dwc_dev = dev; + + if (dwc_otg_is_host_mode(dwc_dev->core_if)) { + struct dwc_hcd *dwc_hcd; + u32 hprt0 = 0; + + dwc_hcd = dwc_dev->hcd; + spin_lock(&dwc_hcd->lock); + dwc_hcd->flags.b.port_over_current_change = 1; + + hprt0 = DWC_HPRT0_PRT_PWR_RW(hprt0, 0); + dwc_reg_write(dwc_dev->core_if->host_if->hprt0, 0, hprt0); + spin_unlock(&dwc_hcd->lock); + } else { + /* Device mode - This int is n/a for device mode */ + dev_dbg(dev, "DeviceMode: OTG OverCurrent Detected\n"); + } + + return IRQ_HANDLED; +} + +static int __devexit dwc_otg_driver_remove(struct platform_device *ofdev) +{ + struct device *dev = &ofdev->dev; + struct dwc_otg_device *dwc_dev = dev_get_drvdata(dev); + + /* Memory allocation for dwc_otg_device may have failed. */ + if (!dwc_dev) + return 0; + + /* Free the IRQ */ + free_irq(dwc_dev->irq, dwc_dev); + /* Free external charge pump irq */ + free_irq(dwc_dev->hcd->cp_irq, dwc_dev); + + if (dwc_dev->hcd) + dwc_otg_hcd_remove(dev); + + if (dwc_dev->pcd) + dwc_otg_pcd_remove(dev); + + if (dwc_dev->core_if) + dwc_otg_cil_remove(dwc_dev->core_if); + + /* Return the memory. */ + if (dwc_dev->base) + iounmap(dwc_dev->base); + + if (dwc_dev->phys_addr) + release_mem_region(dwc_dev->phys_addr, dwc_dev->base_len); + + otg_put_transceiver(dwc_dev->core_if->xceiv); + dwc_dev->core_if->xceiv = NULL; + + kfree(dwc_dev); + + /* Clear the drvdata pointer. */ + dev_set_drvdata(dev, NULL); + return 0; +} + +static int dwc_otg_driver_probe(struct platform_device *ofdev) +{ + int retval,reg_val; + struct dwc_otg_device *dwc_dev; + struct device *dev = &ofdev->dev; + struct resource res; + ulong gusbcfg_addr; + u32 usbcfg = 0; + + dwc_dev = kzalloc(sizeof(*dwc_dev), GFP_KERNEL); + if (!dwc_dev) { + dev_err(dev, "kmalloc of dwc_otg_device failed\n"); + retval = -ENOMEM; + goto fail_dwc_dev; + } + + /* Retrieve the memory and IRQ resources. */ + dwc_dev->irq = ofdev->resource[1].start; + if (dwc_dev->irq == NO_IRQ) { + dev_err(dev, "no device irq\n"); + retval = -ENODEV; + goto fail_of_irq; + } + + res = ofdev->resource[0]; +/* if (of_address_to_resource(ofdev->dev.of_node, 0, &res)) { + dev_err(dev, "%s: Can't get USB-OTG register address\n", + __func__); + retval = -ENOMEM; + goto fail_of_irq; + }*/ + + dwc_dev->phys_addr = res.start; + dwc_dev->base_len = res.end - res.start + 1; + if (!request_mem_region(dwc_dev->phys_addr, + dwc_dev->base_len, dwc_driver_name)) { + dev_err(dev, "request_mem_region failed\n"); + retval = -EBUSY; + goto fail_of_irq; + } + + /* Map the DWC_otg Core memory into virtual address space. */ + dwc_dev->base = ioremap(dwc_dev->phys_addr, dwc_dev->base_len); + if (!dwc_dev->base) { + dev_err(dev, "ioremap() failed\n"); + retval = -ENOMEM; + goto fail_ioremap; + } + dev_dbg(dev, "mapped base=0x%08x\n", (__force u32)dwc_dev->base); + + /** + * Attempt to ensure this device is really a Synopsys USB-OTG Controller. + * Read and verify the SNPSID register contents. The value should be + * 0x45F42XXX, which corresponds to "OT2", as in "OTG version 2.XX". + */ + reg_val = readl(dwc_dev->base+0x40); + if ((reg_val & 0xFFFFF000) != 0x4F542000) { + dev_err(dev,"Bad value for SNPSID: 0x%x\n", reg_val); + retval = -EINVAL; + goto fail_invalid_device; + } + + /* + * Initialize driver data to point to the global DWC_otg + * Device structure. + */ + dev_set_drvdata(dev, dwc_dev); + + dwc_dev->core_if = + dwc_otg_cil_init(dwc_dev->base, &dwc_otg_module_params); + if (!dwc_dev->core_if) { + dev_err(dev, "CIL initialization failed!\n"); + retval = -ENOMEM; + goto fail_cil_init; + } + + /* + * Validate parameter values after dwc_otg_cil_init. + */ + if (check_parameters(dwc_dev->core_if)) { + retval = -EINVAL; + goto fail_check_param; + } + + usb_nop_xceiv_register(); + dwc_dev->core_if->xceiv = otg_get_transceiver(); + if (!dwc_dev->core_if->xceiv) { + retval = -ENODEV; + goto fail_xceiv; + } + dwc_set_feature(dwc_dev->core_if); + + /* Initialize the DWC_otg core. */ + dwc_otg_core_init(dwc_dev->core_if); + + /* + * Disable the global interrupt until all the interrupt + * handlers are installed. + */ + spin_lock(&dwc_dev->hcd->lock); + dwc_otg_disable_global_interrupts(dwc_dev->core_if); + spin_unlock(&dwc_dev->hcd->lock); + + /* + * Install the interrupt handler for the common interrupts before + * enabling common interrupts in core_init below. + */ + retval = request_irq(dwc_dev->irq, dwc_otg_common_irq, + IRQF_SHARED, "dwc_otg", dwc_dev); + if (retval) { + dev_err(dev, "request of irq%d failed retval: %d\n", + dwc_dev->irq, retval); + retval = -EBUSY; + goto fail_req_irq; + } else { + dwc_dev->common_irq_installed = 1; + } + + if (!dwc_has_feature(dwc_dev->core_if, DWC_HOST_ONLY)) { + /* Initialize the PCD */ + retval = dwc_otg_pcd_init(dev); + if (retval) { + dev_err(dev, "dwc_otg_pcd_init failed\n"); + dwc_dev->pcd = NULL; + goto fail_req_irq; + } + } + + gusbcfg_addr = (ulong) (dwc_dev->core_if->core_global_regs) + + DWC_GUSBCFG; + if (!dwc_has_feature(dwc_dev->core_if, DWC_DEVICE_ONLY)) { + /* Initialize the HCD and force_host_mode */ + usbcfg = dwc_reg_read(gusbcfg_addr, 0); + usbcfg |= DWC_USBCFG_FRC_HST_MODE; + dwc_reg_write(gusbcfg_addr, 0, usbcfg); + + retval = dwc_otg_hcd_init(dev, dwc_dev); + if (retval) { + dev_err(dev, "dwc_otg_hcd_init failed\n"); + dwc_dev->hcd = NULL; + goto fail_hcd; + } + /* configure chargepump interrupt */ + dwc_dev->hcd->cp_irq = 0;//irq_of_parse_and_map(ofdev->dev.of_node, 3); + if (dwc_dev->hcd->cp_irq) { + retval = request_irq(dwc_dev->hcd->cp_irq, + dwc_otg_externalchgpump_irq, + IRQF_SHARED, + "dwc_otg_ext_chg_pump", dwc_dev); + if (retval) { + dev_err(dev, + "request of irq failed retval: %d\n", + retval); + retval = -EBUSY; + goto fail_hcd; + } else { + dev_dbg(dev, "%s: ExtChgPump Detection " + "IRQ registered\n", dwc_driver_name); + } + } + } + /* + * Enable the global interrupt after all the interrupt + * handlers are installed. + */ + dwc_otg_enable_global_interrupts(dwc_dev->core_if); + + usbcfg = dwc_reg_read(gusbcfg_addr, 0); + usbcfg &= ~DWC_USBCFG_FRC_HST_MODE; + dwc_reg_write(gusbcfg_addr, 0, usbcfg); + + printk("Done\n");msleep(100); + + return 0; +fail_hcd: + free_irq(dwc_dev->irq, dwc_dev); + if (!dwc_has_feature(dwc_dev->core_if, DWC_HOST_ONLY)) { + if (dwc_dev->pcd) + dwc_otg_pcd_remove(dev); + } +fail_req_irq: + otg_put_transceiver(dwc_dev->core_if->xceiv); +fail_xceiv: + usb_nop_xceiv_unregister(); +fail_check_param: + dwc_otg_cil_remove(dwc_dev->core_if); +fail_cil_init: + dev_set_drvdata(dev, NULL); +fail_invalid_device: + iounmap(dwc_dev->base); +fail_ioremap: + release_mem_region(dwc_dev->phys_addr, dwc_dev->base_len); +fail_of_irq: + kfree(dwc_dev); +fail_dwc_dev: + return retval; +} + +/* static const struct of_device_id dwc_otg_match[] = { + {.compatible = "amcc,dwc-otg",}, + {} +}; + +MODULE_DEVICE_TABLE(of, dwc_otg_match); */ + +struct platform_driver dwc_otg_driver = { + .probe = dwc_otg_driver_probe, + .remove = __devexit_p(dwc_otg_driver_remove), + .driver = { + .name = "dwc_otg", + .owner = THIS_MODULE, +// .of_match_table = dwc_otg_match, + }, +}; + +/*static int dwc_otg_driver_init(void) +{ + + return platform_driver_register(&dwc_otg_driver); +} + +module_init(dwc_otg_driver_init); + +static void __exit dwc_otg_driver_cleanup(void) +{ + platform_driver_unregister(&dwc_otg_driver); +} + +module_exit(dwc_otg_driver_cleanup);*/ + +MODULE_DESCRIPTION(DWC_DRIVER_DESC); +MODULE_AUTHOR("Mark Miesfeld <mmiesfeld@apm.com"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/dwc/cil.c b/drivers/usb/dwc/cil.c new file mode 100644 index 0000000..8d7a9e8 --- /dev/null +++ b/drivers/usb/dwc/cil.c @@ -0,0 +1,893 @@ +/* + * DesignWare HS OTG controller driver + * Copyright (C) 2006 Synopsys, Inc. + * Portions Copyright (C) 2010 Applied Micro Circuits Corporation. + * + * This program 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. + * + * 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 version 2 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see http://www.gnu.org/licenses + * or write to the Free Software Foundation, Inc., 51 Franklin Street, + * Suite 500, Boston, MA 02110-1335 USA. + * + * Based on Synopsys driver version 2.60a + * Modified by Mark Miesfeld <mmiesfeld@apm.com> + * Modified by Stefan Roese <sr@denx.de>, DENX Software Engineering + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL SYNOPSYS, INC. BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES + * (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <linux/delay.h> + +#include "cil.h" + +void dwc_otg_enable_global_interrupts(struct core_if *core_if) +{ + u32 ahbcfg = 0; + + ahbcfg |= DWC_AHBCFG_GLBL_INT_MASK; + dwc_reg_modify(core_if->core_global_regs, DWC_GAHBCFG, 0, + ahbcfg); +} + +void dwc_otg_disable_global_interrupts(struct core_if *core_if) +{ + u32 ahbcfg = 0; + + ahbcfg |= DWC_AHBCFG_GLBL_INT_MASK; + dwc_reg_modify(core_if->core_global_regs, DWC_GAHBCFG, + ahbcfg, 0); +} + +/** + * Tests if the current hardware is using a full speed phy. + */ +static inline int full_speed_phy(struct core_if *core_if) +{ + if ((DWC_HWCFG2_HS_PHY_TYPE_RD(core_if->hwcfg2) == 2 && + DWC_HWCFG2_FS_PHY_TYPE_RD(core_if->hwcfg2) == 1 && + core_if->core_params->ulpi_fs_ls) || + core_if->core_params->phy_type == DWC_PHY_TYPE_PARAM_FS) + return 1; + return 0; +} + +/** + * Initializes the FSLSPClkSel field of the HCFG register depending on the PHY + * type. + */ +void init_fslspclksel(struct core_if *core_if) +{ + u32 val; + u32 hcfg = 0; + + if (full_speed_phy(core_if)) + val = DWC_HCFG_48_MHZ; + else + /* High speed PHY running at full speed or high speed */ + val = DWC_HCFG_30_60_MHZ; + + hcfg = dwc_reg_read(core_if->host_if->host_global_regs, DWC_HCFG); + hcfg = DWC_HCFG_FSLSP_CLK_RW(hcfg, val); + dwc_reg_write(core_if->host_if->host_global_regs, DWC_HCFG, hcfg); +} + +/** + * Initializes the DevSpd field of the DCFG register depending on the PHY type + * and the enumeration speed of the device. + */ +static void init_devspd(struct core_if *core_if) +{ + u32 val; + u32 dcfg; + + if (full_speed_phy(core_if)) + val = 0x3; + else if (core_if->core_params->speed == DWC_SPEED_PARAM_FULL) + /* High speed PHY running at full speed */ + val = 0x1; + else + /* High speed PHY running at high speed */ + val = 0x0; + + dcfg = dwc_reg_read(core_if->dev_if->dev_global_regs, DWC_DCFG); + dcfg = DWC_DCFG_DEV_SPEED_WR(dcfg, val); + dwc_reg_write(core_if->dev_if->dev_global_regs, DWC_DCFG, dcfg); +} + +/** + * This function calculates the number of IN EPS using GHWCFG1 and GHWCFG2 + * registers values + */ +static u32 calc_num_in_eps(struct core_if *core_if) +{ + u32 num_in_eps = 0; + u32 num_eps = DWC_HWCFG2_NO_DEV_EP_RD(core_if->hwcfg2); + u32 hwcfg1 = core_if->hwcfg1 >> 2; + u32 num_tx_fifos = DWC_HWCFG4_NUM_IN_EPS_RD(core_if->hwcfg4); + u32 i; + + for (i = 0; i < num_eps; ++i) { + if (!(hwcfg1 & 0x1)) + num_in_eps++; + hwcfg1 >>= 2; + } + + if (DWC_HWCFG4_DED_FIFO_ENA_RD(core_if->hwcfg4)) + num_in_eps = num_in_eps > num_tx_fifos ? + num_tx_fifos : num_in_eps; + + return num_in_eps; +} + +/** + * This function calculates the number of OUT EPS using GHWCFG1 and GHWCFG2 + * registers values + */ +static u32 calc_num_out_eps(struct core_if *core_if) +{ + u32 num_out_eps = 0; + u32 num_eps = DWC_HWCFG2_NO_DEV_EP_RD(core_if->hwcfg2); + u32 hwcfg1 = core_if->hwcfg1 >> 2; + u32 i; + + for (i = 0; i < num_eps; ++i) { + if (!(hwcfg1 & 0x2)) + num_out_eps++; + hwcfg1 >>= 2; + } + return num_out_eps; +} + +/** + * Do core a soft reset of the core. Be careful with this because it + * resets all the internal state machines of the core. + */ +extern void otg_host_phy_init(void); +static void dwc_otg_core_reset(struct core_if *core_if) +{ + ulong global_regs = core_if->core_global_regs; + u32 greset = 0; + int count = 0; + + otg_host_phy_init(); + + /* Wait for AHB master IDLE state. */ + do { + udelay(10); + greset = dwc_reg_read(global_regs, DWC_GRSTCTL); + if (++count > 100000) { + pr_warning("%s() HANG! AHB Idle GRSTCTL=%0x\n", + __func__, greset); + return; + } + } while (!(greset & DWC_RSTCTL_AHB_IDLE)); + + /* Core Soft Reset */ + count = 0; + greset |= DWC_RSTCTL_SFT_RST; + dwc_reg_write(global_regs, DWC_GRSTCTL, greset); + + do { + greset = dwc_reg_read(global_regs, DWC_GRSTCTL); + if (++count > 10000) { + pr_warning("%s() HANG! Soft Reset " + "GRSTCTL=%0x\n", __func__, greset); + break; + } + udelay(1); + } while (greset & DWC_RSTCTL_SFT_RST); + + /* Wait for 3 PHY Clocks */ + msleep(100); +} + +/** + * This function initializes the commmon interrupts, used in both + * device and host modes. + */ +void dwc_otg_enable_common_interrupts(struct core_if *core_if) +{ + ulong global_regs = core_if->core_global_regs; + u32 intr_mask = 0; + + /* Clear any pending OTG Interrupts */ + dwc_reg_write(global_regs, DWC_GOTGINT, 0xFFFFFFFF); + + /* Clear any pending interrupts */ + dwc_reg_write(global_regs, DWC_GINTSTS, 0xFFFFFFFF); + + /* Enable the interrupts in the GINTMSK. */ + intr_mask |= DWC_INTMSK_MODE_MISMTC; + intr_mask |= DWC_INTMSK_OTG; + intr_mask |= DWC_INTMSK_CON_ID_STS_CHG; + intr_mask |= DWC_INTMSK_WKP; + intr_mask |= DWC_INTMSK_SES_DISCON_DET; + intr_mask |= DWC_INTMSK_USB_SUSP; + intr_mask |= DWC_INTMSK_NEW_SES_DET; + if (!core_if->dma_enable) + intr_mask |= DWC_INTMSK_RXFIFO_NOT_EMPT; + dwc_reg_write(global_regs, DWC_GINTMSK, intr_mask); +} + +/** + * This function initializes the DWC_otg controller registers and prepares the + * core for device mode or host mode operation. + */ +void dwc_otg_core_init(struct core_if *core_if) +{ + u32 i; + ulong global_reg = core_if->core_global_regs; + struct device_if *dev_if = core_if->dev_if; + u32 ahbcfg = 0; + u32 i2cctl = 0; + u32 gusbcfg; + + /* Common Initialization */ + gusbcfg = dwc_reg_read(global_reg, DWC_GUSBCFG); + + /* Program the ULPI External VBUS bit if needed */ + gusbcfg |= DWC_USBCFG_ULPI_EXT_VBUS_DRV; + + /* Set external TS Dline pulsing */ + if (core_if->core_params->ts_dline == 1) + gusbcfg |= DWC_USBCFG_TERM_SEL_DL_PULSE; + else + gusbcfg = gusbcfg & (~((u32) DWC_USBCFG_TERM_SEL_DL_PULSE)); + + dwc_reg_write(global_reg, DWC_GUSBCFG, gusbcfg); + + /* Reset the Controller */ + dwc_otg_core_reset(core_if); + + /* Initialize parameters from Hardware configuration registers. */ + dev_if->num_in_eps = calc_num_in_eps(core_if); + dev_if->num_out_eps = calc_num_out_eps(core_if); + + for (i = 0; i < DWC_HWCFG4_NUM_DEV_PERIO_IN_EP_RD(core_if->hwcfg4); + i++) { + dev_if->perio_tx_fifo_size[i] = + dwc_reg_read(global_reg, DWC_DPTX_FSIZ_DIPTXF(i)) >> 16; + } + for (i = 0; i < DWC_HWCFG4_NUM_IN_EPS_RD(core_if->hwcfg4); i++) { + dev_if->tx_fifo_size[i] = + dwc_reg_read(global_reg, DWC_DPTX_FSIZ_DIPTXF(i)) >> 16; + } + + core_if->total_fifo_size = DWC_HWCFG3_DFIFO_DEPTH_RD(core_if->hwcfg3); + core_if->rx_fifo_size = dwc_reg_read(global_reg, DWC_GRXFSIZ); + core_if->nperio_tx_fifo_size = + dwc_reg_read(global_reg, DWC_GRXFSIZ) >> 16; + /* + * This programming sequence needs to happen in FS mode before any + * other programming occurs + */ + if (core_if->core_params->speed == DWC_SPEED_PARAM_FULL && + core_if->core_params->phy_type == DWC_PHY_TYPE_PARAM_FS) { + /* + * core_init() is now called on every switch so only call the + * following for the first time through. + */ + if (!core_if->phy_init_done) { + core_if->phy_init_done = 1; + gusbcfg = dwc_reg_read(global_reg, DWC_GUSBCFG); + gusbcfg |= DWC_USBCFG_ULPI_UTMI_SEL; + dwc_reg_write(global_reg, DWC_GUSBCFG, gusbcfg); + + /* Reset after a PHY select */ + dwc_otg_core_reset(core_if); + } + + /* + * Program DCFG.DevSpd or HCFG.FSLSPclkSel to 48Mhz in FS. + * Also do this on HNP Dev/Host mode switches (done in dev_init + * and host_init). + */ + if (dwc_otg_is_host_mode(core_if)) + init_fslspclksel(core_if); + else + init_devspd(core_if); + + if (core_if->core_params->i2c_enable) { + /* Program GUSBCFG.OtgUtmifsSel to I2C */ + gusbcfg = dwc_reg_read(global_reg, DWC_GUSBCFG); + gusbcfg |= DWC_USBCFG_OTGUTMIFSSEL; + dwc_reg_write(global_reg, DWC_GUSBCFG, gusbcfg); + + /* Program GI2CCTL.I2CEn */ + i2cctl = dwc_reg_read(global_reg, DWC_GI2CCTL); + i2cctl |= DWC_I2CCTL_I2CDEVADDR(1); + i2cctl &= ~DWC_I2CCTL_I2CEN; + dwc_reg_write(global_reg, DWC_GI2CCTL, i2cctl); + i2cctl |= DWC_I2CCTL_I2CEN; + dwc_reg_write(global_reg, DWC_GI2CCTL, i2cctl); + } + } else if (!core_if->phy_init_done) { + gusbcfg = dwc_reg_read(global_reg, DWC_GUSBCFG); + core_if->phy_init_done = 1; + if (core_if->core_params->phy_type) + gusbcfg |= DWC_USBCFG_ULPI_UTMI_SEL; + else + gusbcfg &= ~((u32) DWC_USBCFG_ULPI_UTMI_SEL); + + if (gusbcfg & DWC_USBCFG_ULPI_UTMI_SEL) { + /* ULPI interface */ + gusbcfg |= DWC_USBCFG_PHYIF; + if (core_if->core_params->phy_ulpi_ddr) + gusbcfg |= DWC_USBCFG_DDRSEL; + else + gusbcfg &= ~((u32) DWC_USBCFG_DDRSEL); + } else { + /* UTMI+ interface */ + if (core_if->core_params->phy_utmi_width == 16) + gusbcfg |= DWC_USBCFG_PHYIF; + else + gusbcfg &= ~((u32) DWC_USBCFG_PHYIF); + } + dwc_reg_write(global_reg, DWC_GUSBCFG, gusbcfg); + + /* Reset after setting the PHY parameters */ + dwc_otg_core_reset(core_if); + } + + if (DWC_HWCFG2_HS_PHY_TYPE_RD(core_if->hwcfg2) == 2 && + DWC_HWCFG2_FS_PHY_TYPE_RD(core_if->hwcfg2) == 1 && + core_if->core_params->ulpi_fs_ls) { + gusbcfg = dwc_reg_read(global_reg, DWC_GUSBCFG); + gusbcfg |= DWC_USBCFG_ULPI_FSLS; + gusbcfg |= DWC_USBCFG_ULPI_CLK_SUS_M; + dwc_reg_write(global_reg, DWC_GUSBCFG, gusbcfg); + } else { + gusbcfg = dwc_reg_read(global_reg, DWC_GUSBCFG); + gusbcfg &= ~((u32) DWC_USBCFG_ULPI_FSLS); + gusbcfg &= ~((u32) DWC_USBCFG_ULPI_CLK_SUS_M); + dwc_reg_write(global_reg, DWC_GUSBCFG, gusbcfg); + } + + /* Program the GAHBCFG Register. */ + switch (DWC_HWCFG2_ARCH_RD(core_if->hwcfg2)) { + case DWC_SLAVE_ONLY_ARCH: + ahbcfg &= ~DWC_AHBCFG_NPFIFO_EMPTY; /* HALF empty */ + ahbcfg &= ~DWC_AHBCFG_FIFO_EMPTY; /* HALF empty */ + core_if->dma_enable = 0; + break; + case DWC_EXT_DMA_ARCH: + ahbcfg = (ahbcfg & ~DWC_AHBCFG_BURST_LEN(0xf)) | + DWC_AHBCFG_BURST_LEN(core_if->core_params->dma_burst_size); + core_if->dma_enable = (core_if->core_params->dma_enable != 0); + break; + case DWC_INT_DMA_ARCH: + ahbcfg = (ahbcfg & ~DWC_AHBCFG_BURST_LEN(0xf)) | + DWC_AHBCFG_BURST_LEN(DWC_GAHBCFG_INT_DMA_BURST_INCR); + core_if->dma_enable = (core_if->core_params->dma_enable != 0); + break; + } + + if (core_if->dma_enable) + ahbcfg |= DWC_AHBCFG_DMA_ENA; + else + ahbcfg &= ~DWC_AHBCFG_DMA_ENA; + dwc_reg_write(global_reg, DWC_GAHBCFG, ahbcfg); + core_if->en_multiple_tx_fifo = + DWC_HWCFG4_DED_FIFO_ENA_RD(core_if->hwcfg4); + + /* Program the GUSBCFG register. */ + gusbcfg = dwc_reg_read(global_reg, DWC_GUSBCFG); + switch (DWC_HWCFG2_OP_MODE_RD(core_if->hwcfg2)) { + case DWC_MODE_HNP_SRP_CAPABLE: + if (core_if->core_params->otg_cap == + DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE) + gusbcfg |= DWC_USBCFG_HNP_CAP; + else + gusbcfg &= ~((u32) DWC_USBCFG_HNP_CAP); + if (core_if->core_params->otg_cap != + DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE) + gusbcfg |= DWC_USBCFG_SRP_CAP; + else + gusbcfg &= ~((u32) DWC_USBCFG_SRP_CAP); + break; + case DWC_MODE_SRP_ONLY_CAPABLE: + gusbcfg &= ~((u32) DWC_USBCFG_HNP_CAP); + if (core_if->core_params->otg_cap != + DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE) + gusbcfg |= DWC_USBCFG_SRP_CAP; + else + gusbcfg &= ~((u32) DWC_USBCFG_SRP_CAP); + break; + case DWC_MODE_NO_HNP_SRP_CAPABLE: + gusbcfg &= ~((u32) DWC_USBCFG_HNP_CAP); + gusbcfg &= ~((u32) DWC_USBCFG_SRP_CAP); + break; + case DWC_MODE_SRP_CAPABLE_DEVICE: + gusbcfg &= ~((u32) DWC_USBCFG_HNP_CAP); + if (core_if->core_params->otg_cap != + DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE) + gusbcfg |= DWC_USBCFG_SRP_CAP; + else + gusbcfg &= ~((u32) DWC_USBCFG_SRP_CAP); + break; + case DWC_MODE_NO_SRP_CAPABLE_DEVICE: + gusbcfg &= ~((u32) DWC_USBCFG_HNP_CAP); + gusbcfg &= ~((u32) DWC_USBCFG_SRP_CAP); + break; + case DWC_MODE_SRP_CAPABLE_HOST: + gusbcfg &= ~((u32) DWC_USBCFG_HNP_CAP); + if (core_if->core_params->otg_cap != + DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE) + gusbcfg |= DWC_USBCFG_SRP_CAP; + else + gusbcfg &= ~((u32) DWC_USBCFG_SRP_CAP); + break; + case DWC_MODE_NO_SRP_CAPABLE_HOST: + gusbcfg &= ~((u32) DWC_USBCFG_HNP_CAP); + gusbcfg &= ~((u32) DWC_USBCFG_SRP_CAP); + break; + } + dwc_reg_write(global_reg, DWC_GUSBCFG, gusbcfg); + + /* Enable common interrupts */ + dwc_otg_enable_common_interrupts(core_if); + + /* + * Do device or host intialization based on mode during PCD + * and HCD initialization + */ + if (dwc_otg_is_host_mode(core_if)) { + core_if->xceiv->state = OTG_STATE_A_HOST; + } else { + core_if->xceiv->state = OTG_STATE_B_PERIPHERAL; + if (dwc_has_feature(core_if, DWC_DEVICE_ONLY)) + dwc_otg_core_dev_init(core_if); + } +} + +/** + * This function enables the Device mode interrupts. + * + * Note that the bits in the Device IN endpoint mask register are laid out + * exactly the same as the Device IN endpoint interrupt register. + */ +static void dwc_otg_enable_device_interrupts(struct core_if *core_if) +{ + u32 intr_mask = 0; + u32 msk = 0; + ulong global_regs = core_if->core_global_regs; + + /* Disable all interrupts. */ + dwc_reg_write(global_regs, DWC_GINTMSK, 0); + + /* Clear any pending interrupts */ + dwc_reg_write(global_regs, DWC_GINTSTS, 0xFFFFFFFF); + + /* Enable the common interrupts */ + dwc_otg_enable_common_interrupts(core_if); + + /* Enable interrupts */ + intr_mask |= DWC_INTMSK_USB_RST; + intr_mask |= DWC_INTMSK_ENUM_DONE; + intr_mask |= DWC_INTMSK_IN_ENDP; + intr_mask |= DWC_INTMSK_OUT_ENDP; + intr_mask |= DWC_INTMSK_EARLY_SUSP; + if (!core_if->en_multiple_tx_fifo) + intr_mask |= DWC_INTMSK_ENDP_MIS_MTCH; + + /* Periodic EP */ + intr_mask |= DWC_INTMSK_ISYNC_OUTPKT_DRP; + intr_mask |= DWC_INTMSK_END_OF_PFRM; + intr_mask |= DWC_INTMSK_INCMP_IN_ATX; + intr_mask |= DWC_INTMSK_INCMP_OUT_PTX; + + dwc_reg_modify(global_regs, DWC_GINTMSK, intr_mask, intr_mask); + + msk = DWC_DIEPMSK_TXFIFO_UNDERN_RW(msk, 1); + dwc_reg_modify(core_if->dev_if->dev_global_regs, DWC_DIEPMSK, + msk, msk); +} + +/** + * Configures the device data fifo sizes when dynamic sizing is enabled. + */ +static void config_dev_dynamic_fifos(struct core_if *core_if) +{ + u32 i; + ulong regs = core_if->core_global_regs; + struct core_params *params = core_if->core_params; + u32 txsize = 0; + u32 nptxsize = 0; + u32 ptxsize = 0; + + /* Rx FIFO */ + dwc_reg_write(regs, DWC_GRXFSIZ, params->dev_rx_fifo_size); + + /* Set Periodic and Non-periodic Tx FIFO Mask bits to all 0 */ + core_if->p_tx_msk = 0; + core_if->tx_msk = 0; + + if (core_if->en_multiple_tx_fifo == 0) { + /* Non-periodic Tx FIFO */ + nptxsize = DWC_RX_FIFO_DEPTH_WR(nptxsize, + params-> + dev_nperio_tx_fifo_size); + nptxsize = + DWC_RX_FIFO_START_ADDR_WR(nptxsize, + params->dev_rx_fifo_size); + dwc_reg_write(regs, DWC_GNPTXFSIZ, nptxsize); + + ptxsize = DWC_RX_FIFO_START_ADDR_WR(ptxsize, + (DWC_RX_FIFO_START_ADDR_RD + (nptxsize) + + DWC_RX_FIFO_DEPTH_RD + (nptxsize))); + for (i = 0; + i < DWC_HWCFG4_NUM_DEV_PERIO_IN_EP_RD(core_if->hwcfg4); + i++) { + ptxsize = + DWC_RX_FIFO_DEPTH_WR(ptxsize, + params-> + dev_perio_tx_fifo_size[i]); + dwc_reg_write(regs, DWC_DPTX_FSIZ_DIPTXF(i), ptxsize); + ptxsize = DWC_RX_FIFO_START_ADDR_WR(ptxsize, + (DWC_RX_FIFO_START_ADDR_RD + (ptxsize) + + DWC_RX_FIFO_DEPTH_RD + (ptxsize))); + } + } else { + nptxsize = DWC_RX_FIFO_DEPTH_WR(nptxsize, + params-> + dev_nperio_tx_fifo_size); + nptxsize = + DWC_RX_FIFO_START_ADDR_WR(nptxsize, + params->dev_rx_fifo_size); + dwc_reg_write(regs, DWC_GNPTXFSIZ, nptxsize); + + txsize = DWC_RX_FIFO_START_ADDR_WR(txsize, + (DWC_RX_FIFO_START_ADDR_RD + (nptxsize) + + DWC_RX_FIFO_DEPTH_RD + (nptxsize))); + for (i = 1; + i < DWC_HWCFG4_NUM_DEV_PERIO_IN_EP_RD(core_if->hwcfg4); + i++) { + txsize = + DWC_RX_FIFO_DEPTH_WR(txsize, + params->dev_tx_fifo_size[i]); + dwc_reg_write(regs, DWC_DPTX_FSIZ_DIPTXF(i - 1), + txsize); + txsize = DWC_RX_FIFO_START_ADDR_WR(txsize, + (DWC_RX_FIFO_START_ADDR_RD + (txsize) + + DWC_RX_FIFO_DEPTH_RD + (txsize))); + } + } +} + +/** + * This function initializes the DWC_otg controller registers for + * device mode. + */ +void dwc_otg_core_dev_init(struct core_if *c_if) +{ + u32 i; + struct device_if *d_if = c_if->dev_if; + struct core_params *params = c_if->core_params; + u32 dcfg = 0; + u32 resetctl = 0; + u32 dthrctl = 0; + + /* Restart the Phy Clock */ + dwc_reg_write(c_if->pcgcctl, 0, 0); + + /* Device configuration register */ + init_devspd(c_if); + dcfg = dwc_reg_read(d_if->dev_global_regs, DWC_DCFG); + dcfg = DWC_DCFG_P_FRM_INTRVL_WR(dcfg, DWC_DCFG_FRAME_INTERVAL_80); + dwc_reg_write(d_if->dev_global_regs, DWC_DCFG, dcfg); + + /* If needed configure data FIFO sizes */ + if (DWC_HWCFG2_DYN_FIFO_RD(c_if->hwcfg2) && params->enable_dynamic_fifo) + config_dev_dynamic_fifos(c_if); + + /* Flush the FIFOs */ + dwc_otg_flush_tx_fifo(c_if, DWC_GRSTCTL_TXFNUM_ALL); + dwc_otg_flush_rx_fifo(c_if); + + /* Flush the Learning Queue. */ + resetctl |= DWC_RSTCTL_TKN_QUE_FLUSH; + dwc_reg_write(c_if->core_global_regs, DWC_GRSTCTL, resetctl); + + /* Clear all pending Device Interrupts */ + dwc_reg_write(d_if->dev_global_regs, DWC_DIEPMSK, 0); + dwc_reg_write(d_if->dev_global_regs, DWC_DOEPMSK, 0); + dwc_reg_write(d_if->dev_global_regs, DWC_DAINT, 0xFFFFFFFF); + dwc_reg_write(d_if->dev_global_regs, DWC_DAINTMSK, 0); + + for (i = 0; i <= d_if->num_in_eps; i++) { + u32 depctl = 0; + + depctl = dwc_reg_read(d_if->in_ep_regs[i], DWC_DIEPCTL); + if (DWC_DEPCTL_EPENA_RD(depctl)) { + depctl = 0; + depctl = DWC_DEPCTL_EPDIS_RW(depctl, 1); + depctl = DWC_DEPCTL_SET_NAK_RW(depctl, 1); + } else { + depctl = 0; + } + + dwc_reg_write(d_if->in_ep_regs[i], DWC_DIEPCTL, depctl); + dwc_reg_write(d_if->in_ep_regs[i], DWC_DIEPTSIZ, 0); + dwc_reg_write(d_if->in_ep_regs[i], DWC_DIEPDMA, 0); + dwc_reg_write(d_if->in_ep_regs[i], DWC_DIEPINT, 0xFF); + } + + for (i = 0; i <= d_if->num_out_eps; i++) { + u32 depctl = 0; + depctl = dwc_reg_read(d_if->out_ep_regs[i], DWC_DOEPCTL); + if (DWC_DEPCTL_EPENA_RD(depctl)) { + depctl = 0; + depctl = DWC_DEPCTL_EPDIS_RW(depctl, 1); + depctl = DWC_DEPCTL_SET_NAK_RW(depctl, 1); + } else { + depctl = 0; + } + dwc_reg_write(d_if->out_ep_regs[i], DWC_DOEPCTL, depctl); + dwc_reg_write(d_if->out_ep_regs[i], DWC_DOEPTSIZ, 0); + dwc_reg_write(d_if->out_ep_regs[i], DWC_DOEPDMA, 0); + dwc_reg_write(d_if->out_ep_regs[i], DWC_DOEPINT, 0xFF); + } + + if (c_if->en_multiple_tx_fifo && c_if->dma_enable) { + d_if->non_iso_tx_thr_en = c_if->core_params->thr_ctl & 0x1; + d_if->iso_tx_thr_en = (c_if->core_params->thr_ctl >> 1) & 0x1; + d_if->rx_thr_en = (c_if->core_params->thr_ctl >> 2) & 0x1; + d_if->rx_thr_length = c_if->core_params->rx_thr_length; + d_if->tx_thr_length = c_if->core_params->tx_thr_length; + + dthrctl = 0; + dthrctl = DWC_DTHCTRL_NON_ISO_THR_ENA_RW + (dthrctl, d_if->non_iso_tx_thr_en); + dthrctl = DWC_DTHCTRL_ISO_THR_EN_RW + (dthrctl, d_if->iso_tx_thr_en); + dthrctl = DWC_DTHCTRL_TX_THR_LEN_RW + (dthrctl, d_if->tx_thr_length); + dthrctl = DWC_DTHCTRL_RX_THR_EN_RW(dthrctl, d_if->rx_thr_en); + dthrctl = DWC_DTHCTRL_RX_THR_LEN_RW + (dthrctl, d_if->rx_thr_length); + dwc_reg_write(d_if->dev_global_regs, + DWC_DTKNQR3_DTHRCTL, dthrctl); + + } + + dwc_otg_enable_device_interrupts(c_if); +} + +/** + * This function reads a packet from the Rx FIFO into the destination buffer. + * To read SETUP data use dwc_otg_read_setup_packet. + */ +void dwc_otg_read_packet(struct core_if *core_if, u8 * dest, u16 _bytes) +{ + u32 i; + int word_count = (_bytes + 3) / 4; + u32 fifo = core_if->data_fifo[0]; + u32 *data_buff = (u32 *) dest; + + /* + * This requires reading data from the FIFO into a u32 temp buffer, + * then moving it into the data buffer. + */ + for (i = 0; i < word_count; i++, data_buff++) + *data_buff = dwc_read_fifo32(fifo); +} + +/** + * Flush a Tx FIFO. + */ +void dwc_otg_flush_tx_fifo(struct core_if *core_if, const int num) +{ + ulong global_regs = core_if->core_global_regs; + u32 greset = 0; + int count = 0; + + greset |= DWC_RSTCTL_TX_FIFO_FLUSH; + greset = DWC_RSTCTL_TX_FIFO_NUM(greset, num); + dwc_reg_write(global_regs, DWC_GRSTCTL, greset); + + do { + greset = dwc_reg_read(global_regs, DWC_GRSTCTL); + if (++count > 10000) { + pr_warning("%s() HANG! GRSTCTL=%0x " + "GNPTXSTS=0x%08x\n", __func__, greset, + dwc_reg_read(global_regs, DWC_GNPTXSTS)); + break; + } + udelay(1); + } while (greset & DWC_RSTCTL_TX_FIFO_FLUSH); + + /* Wait for 3 PHY Clocks */ + udelay(1); +} + +/** + * Flush Rx FIFO. + */ +void dwc_otg_flush_rx_fifo(struct core_if *core_if) +{ + ulong global_regs = core_if->core_global_regs; + u32 greset = 0; + int count = 0; + + greset |= DWC_RSTCTL_RX_FIFO_FLUSH; + dwc_reg_write(global_regs, DWC_GRSTCTL, greset); + + do { + greset = dwc_reg_read(global_regs, DWC_GRSTCTL); + if (++count > 10000) { + pr_warning("%s() HANG! GRSTCTL=%0x\n", + __func__, greset); + break; + } + udelay(1); + } while (greset & DWC_RSTCTL_RX_FIFO_FLUSH); + + /* Wait for 3 PHY Clocks */ + udelay(1); +} + +/** + * Register HCD callbacks. + * The callbacks are used to start and stop the HCD for interrupt processing. + */ +void dwc_otg_cil_register_hcd_callbacks(struct core_if *c_if, + struct cil_callbacks *cb, + void *p) +{ + c_if->hcd_cb = cb; + cb->p = p; +} + +/** + * Register PCD callbacks. + * The callbacks are used to start and stop the PCD for interrupt processing. + */ +void dwc_otg_cil_register_pcd_callbacks(struct core_if *c_if, + struct cil_callbacks *cb, + void *p) +{ + c_if->pcd_cb = cb; + cb->p = p; +} + +/** + * This function is called to initialize the DWC_otg CSR data structures. + * + * The register addresses in the device and host structures are initialized from + * the base address supplied by the caller. The calling function must make the + * OS calls to get the base address of the DWC_otg controller registers. + * + * The params argument holds the parameters that specify how the core should be + * configured. + */ +struct core_if *dwc_otg_cil_init(const __iomem u32 * base, + struct core_params *params) +{ + struct core_if *core_if; + struct device_if *dev_if; + struct dwc_host_if *host_if; + u8 *reg_base = (__force u8 *)base; + u32 offset; + u32 i; + + core_if = kzalloc(sizeof(*core_if), GFP_KERNEL); + if (!core_if) + return NULL; + + core_if->core_params = params; + core_if->core_global_regs = (ulong)reg_base; + + /* Allocate the Device Mode structures. */ + dev_if = kmalloc(sizeof(*dev_if), GFP_KERNEL); + if (!dev_if) { + kfree(core_if); + return NULL; + } + + dev_if->dev_global_regs = (ulong)(reg_base + DWC_DEV_GLOBAL_REG_OFFSET); + + for (i = 0; i < MAX_EPS_CHANNELS; i++) { + offset = i * DWC_EP_REG_OFFSET; + + dev_if->in_ep_regs[i] = (ulong)(reg_base + + DWC_DEV_IN_EP_REG_OFFSET + + offset); + + dev_if->out_ep_regs[i] = (ulong)(reg_base + + DWC_DEV_OUT_EP_REG_OFFSET + + offset); + } + + dev_if->speed = 0; /* unknown */ + core_if->dev_if = dev_if; + + /* Allocate the Host Mode structures. */ + host_if = kmalloc(sizeof(*host_if), GFP_KERNEL); + if (!host_if) { + kfree(dev_if); + kfree(core_if); + return NULL; + } + + host_if->host_global_regs = (ulong)(reg_base + + DWC_OTG_HOST_GLOBAL_REG_OFFSET); + + host_if->hprt0 = (ulong)(reg_base + DWC_OTG_HOST_PORT_REGS_OFFSET); + + for (i = 0; i < MAX_EPS_CHANNELS; i++) { + offset = i * DWC_OTG_CHAN_REGS_OFFSET; + + host_if->hc_regs[i] = (ulong)(reg_base + + DWC_OTG_HOST_CHAN_REGS_OFFSET + + offset); + } + + host_if->num_host_channels = MAX_EPS_CHANNELS; + core_if->host_if = host_if; + for (i = 0; i < MAX_EPS_CHANNELS; i++) { + core_if->data_fifo[i] = + (ulong)(reg_base + DWC_OTG_DATA_FIFO_OFFSET + + (i * DWC_OTG_DATA_FIFO_SIZE)); + } + core_if->pcgcctl = (ulong)(reg_base + DWC_OTG_PCGCCTL_OFFSET); + + /* + * Store the contents of the hardware configuration registers here for + * easy access later. + */ + core_if->hwcfg1 = + dwc_reg_read(core_if->core_global_regs, DWC_GHWCFG1); + core_if->hwcfg2 = + dwc_reg_read(core_if->core_global_regs, DWC_GHWCFG2); + core_if->hwcfg3 = + dwc_reg_read(core_if->core_global_regs, DWC_GHWCFG3); + core_if->hwcfg4 = + dwc_reg_read(core_if->core_global_regs, DWC_GHWCFG4); + + /* Set the SRP sucess bit for FS-I2c */ + core_if->srp_success = 0; + core_if->srp_timer_started = 0; + return core_if; +} + +/** + * This function frees the structures allocated by dwc_otg_cil_init(). + */ +void dwc_otg_cil_remove(struct core_if *core_if) +{ + /* Disable all interrupts */ + dwc_reg_modify(core_if->core_global_regs, DWC_GAHBCFG, 1, 0); + dwc_reg_write(core_if->core_global_regs, DWC_GINTMSK, 0); + + if (core_if) { + kfree(core_if->dev_if); + kfree(core_if->host_if); + } + kfree(core_if); +} diff --git a/drivers/usb/dwc/cil.h b/drivers/usb/dwc/cil.h new file mode 100644 index 0000000..80c4361 --- /dev/null +++ b/drivers/usb/dwc/cil.h @@ -0,0 +1,1179 @@ +/* + * DesignWare HS OTG controller driver + * Copyright (C) 2006 Synopsys, Inc. + * Portions Copyright (C) 2010 Applied Micro Circuits Corporation. + * + * This program 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. + * + * 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 version 2 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see http://www.gnu.org/licenses + * or write to the Free Software Foundation, Inc., 51 Franklin Street, + * Suite 500, Boston, MA 02110-1335 USA. + * + * Based on Synopsys driver version 2.60a + * Modified by Mark Miesfeld <mmiesfeld@apm.com> + * Modified by Stefan Roese <sr@denx.de>, DENX Software Engineering + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL SYNOPSYS, INC. BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES + * (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#if !defined(__DWC_CIL_H__) +#define __DWC_CIL_H__ +#include <linux/io.h> +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <linux/interrupt.h> +#include <linux/dmapool.h> +#include <linux/spinlock.h> +#include <linux/usb/otg.h> + +#include "regs.h" + +#ifdef CONFIG_DWC_DEBUG +#define DEBUG +#endif + +#ifdef CONFIG_ARM +#define in_le32(x) readl(x) +#define out_le32(x,y) writel(y,x) +#endif + +/** + * Reads the content of a register. + */ +static inline u32 dwc_reg_read(ulong reg , u32 offset) +{ + +#ifdef CONFIG_DWC_OTG_REG_LE + return in_le32((unsigned __iomem *)(reg + offset)); +#else + return in_be32((unsigned __iomem *)(reg + offset)); +#endif +} + +static inline void dwc_reg_write(ulong reg, u32 offset, const u32 value) +{ +#ifdef CONFIG_DWC_OTG_REG_LE + out_le32((unsigned __iomem *)(reg + offset), value); +#else + out_be32((unsigned __iomem *)(reg + offset), value); +#endif +}; +/** + * This function modifies bit values in a register. Using the + * algorithm: (reg_contents & ~clear_mask) | set_mask. + */ +static inline void dwc_reg_modify(ulong reg, u32 offset, const u32 _clear_mask, + const u32 _set_mask) +{ +#ifdef CONFIG_DWC_OTG_REG_LE + out_le32((unsigned __iomem *)(reg + offset), + (in_le32((unsigned __iomem *)(reg + offset)) + & ~_clear_mask) | _set_mask); +#else + out_be32((unsigned __iomem *)(reg + offset), + (in_be32(((unsigned __iomem *))(reg + offset)) + & ~_clear_mask) | _set_mask); +#endif +}; + +static inline void dwc_write_fifo32(ulong reg, const u32 _value) +{ +#ifdef CONFIG_DWC_OTG_FIFO_LE + out_le32((unsigned __iomem *)reg, _value); +#else + out_be32((unsigned __iomem *)reg, _value); +#endif +}; + +static inline u32 dwc_read_fifo32(ulong _reg) +{ +#ifdef CONFIG_DWC_OTG_FIFO_LE + return in_le32((unsigned __iomem *) _reg); +#else + return in_be32((unsigned __iomem *) _reg); +#endif +}; + +/* + * Debugging support vanishes in non-debug builds. + */ +/* Display CIL Debug messages */ +#define dwc_dbg_cil (0x2) + +/* Display CIL Verbose debug messages */ +#define dwc_dbg_cilv (0x20) + +/* Display PCD (Device) debug messages */ +#define dwc_dbg_pcd (0x4) + +/* Display PCD (Device) Verbose debug messages */ +#define dwc_dbg_pcdv (0x40) + +/* Display Host debug messages */ +#define dwc_dbg_hcd (0x8) + +/* Display Verbose Host debug messages */ +#define dwc_dbg_hcdv (0x80) + +/* Display enqueued URBs in host mode. */ +#define dwc_dbg_hcd_urb (0x800) + +/* Display "special purpose" debug messages */ +#define dwc_dbg_sp (0x400) + +/* Display all debug messages */ +#define dwc_dbg_any (0xFF) + +/* All debug messages off */ +#define dwc_dbg_off 0 + +/* Prefix string for DWC_DEBUG print macros. */ +#define usb_dwc "dwc_otg: " + +/* + * This file contains the interface to the Core Interface Layer. + */ + +/* + * Added-sr: 2007-07-26 + * + * Since the 405EZ (Ultra) only support 2047 bytes as + * max transfer size, we have to split up bigger transfers + * into multiple transfers of 1024 bytes sized messages. + * I happens often, that transfers of 4096 bytes are + * required (zero-gadget, file_storage-gadget). + * + * MAX_XFER_LEN is set to 1024 right now, but could be 2047, + * since the xfer-size field in the 405EZ USB device controller + * implementation has 11 bits. Using 1024 seems to work for now. + */ +#define MAX_XFER_LEN 1024 + +/* + * The dwc_ep structure represents the state of a single endpoint when acting in + * device mode. It contains the data items needed for an endpoint to be + * activated and transfer packets. + */ +struct dwc_ep { + /* EP number used for register address lookup */ + u8 num; + /* EP direction 0 = OUT */ + unsigned is_in:1; + /* EP active. */ + unsigned active:1; + + /* + * Periodic Tx FIFO # for IN EPs For INTR EP set to 0 to use + * non-periodic Tx FIFO If dedicated Tx FIFOs are enabled for all + * IN Eps - Tx FIFO # FOR IN EPs + */ + unsigned tx_fifo_num:4; + /* EP type: 0 - Control, 1 - ISOC, 2 - BULK, 3 - INTR */ + unsigned type:2; +#define DWC_OTG_EP_TYPE_CONTROL 0 +#define DWC_OTG_EP_TYPE_ISOC 1 +#define DWC_OTG_EP_TYPE_BULK 2 +#define DWC_OTG_EP_TYPE_INTR 3 + + /* DATA start PID for INTR and BULK EP */ + unsigned data_pid_start:1; + /* Frame (even/odd) for ISOC EP */ + unsigned even_odd_frame:1; + /* Max Packet bytes */ + unsigned maxpacket:11; + + ulong dma_addr; + + /* + * Pointer to the beginning of the transfer buffer -- do not modify + * during transfer. + */ + u8 *start_xfer_buff; + /* pointer to the transfer buffer */ + u8 *xfer_buff; + /* Number of bytes to transfer */ + unsigned xfer_len:19; + /* Number of bytes transferred. */ + unsigned xfer_count:19; + /* Sent ZLP */ + unsigned sent_zlp:1; + /* Total len for control transfer */ + unsigned total_len:19; + + /* stall clear flag */ + unsigned stall_clear_flag:1; + + /* + * Added-sr: 2007-07-26 + * + * Since the 405EZ (Ultra) only support 2047 bytes as + * max transfer size, we have to split up bigger transfers + * into multiple transfers of 1024 bytes sized messages. + * I happens often, that transfers of 4096 bytes are + * required (zero-gadget, file_storage-gadget). + * + * "bytes_pending" will hold the amount of bytes that are + * still pending to be send in further messages to complete + * the bigger transfer. + */ + u32 bytes_pending; +}; + +/* + * States of EP0. + */ +enum ep0_state { + EP0_DISCONNECT = 0, /* no host */ + EP0_IDLE = 1, + EP0_IN_DATA_PHASE = 2, + EP0_OUT_DATA_PHASE = 3, + EP0_STATUS = 4, + EP0_STALL = 5, +}; + +/* Fordward declaration.*/ +struct dwc_pcd; + +/* + * This structure describes an EP, there is an array of EPs in the PCD + * structure. + */ +struct pcd_ep { + /* USB EP data */ + struct usb_ep ep; + /* USB EP Descriptor */ + const struct usb_endpoint_descriptor *desc; + + /* queue of dwc_otg_pcd_requests. */ + struct list_head queue; + unsigned stopped:1; + unsigned disabling:1; + unsigned dma:1; + unsigned queue_sof:1; + unsigned wedged:1; + + /* DWC_otg ep data. */ + struct dwc_ep dwc_ep; + + /* Pointer to PCD */ + struct dwc_pcd *pcd; +}; + +/* + * DWC_otg PCD Structure. + * This structure encapsulates the data for the dwc_otg PCD. + */ +struct dwc_pcd { + /* USB gadget */ + struct usb_gadget gadget; + /* USB gadget driver pointer */ + struct usb_gadget_driver *driver; + /* The DWC otg device pointer. */ + struct dwc_otg_device *otg_dev; + + /* State of EP0 */ + enum ep0_state ep0state; + /* EP0 Request is pending */ + unsigned ep0_pending:1; + /* Indicates when SET CONFIGURATION Request is in process */ + unsigned request_config:1; + /* The state of the Remote Wakeup Enable. */ + unsigned remote_wakeup_enable:1; + /* The state of the B-Device HNP Enable. */ + unsigned b_hnp_enable:1; + /* The state of A-Device HNP Support. */ + unsigned a_hnp_support:1; + /* The state of the A-Device Alt HNP support. */ + unsigned a_alt_hnp_support:1; + /* Count of pending Requests */ + unsigned request_pending; + + /* + * SETUP packet for EP0. This structure is allocated as a DMA buffer on + * PCD initialization with enough space for up to 3 setup packets. + */ + union { + struct usb_ctrlrequest req; + u32 d32[2]; + } *setup_pkt; + + struct dma_pool *dwc_pool; + dma_addr_t setup_pkt_dma_handle; + + /* 2-byte dma buffer used to return status from GET_STATUS */ + u16 *status_buf; + dma_addr_t status_buf_dma_handle; + + /* Array of EPs. */ + struct pcd_ep ep0; + /* Array of IN EPs. */ + struct pcd_ep in_ep[MAX_EPS_CHANNELS - 1]; + /* Array of OUT EPs. */ + struct pcd_ep out_ep[MAX_EPS_CHANNELS - 1]; + spinlock_t lock; + /* + * Timer for SRP. If it expires before SRP is successful clear the + * SRP. + */ + struct timer_list srp_timer; + + /* + * Tasklet to defer starting of TEST mode transmissions until Status + * Phase has been completed. + */ + struct tasklet_struct test_mode_tasklet; + + /* Tasklet to delay starting of xfer in DMA mode */ + struct tasklet_struct *start_xfer_tasklet; + + /* The test mode to enter when the tasklet is executed. */ + unsigned test_mode; +}; + +/* + * This structure holds the state of the HCD, including the non-periodic and + * periodic schedules. + */ +struct dwc_hcd { + spinlock_t lock; + + /* DWC OTG Core Interface Layer */ + struct core_if *core_if; + + /* Internal DWC HCD Flags */ + union dwc_otg_hcd_internal_flags { + u32 d32; + struct { + unsigned port_connect_status_change:1; + unsigned port_connect_status:1; + unsigned port_reset_change:1; + unsigned port_enable_change:1; + unsigned port_suspend_change:1; + unsigned port_over_current_change:1; + unsigned reserved:27; + } b; + } flags; + + /* + * Inactive items in the non-periodic schedule. This is a list of + * Queue Heads. Transfers associated with these Queue Heads are not + * currently assigned to a host channel. + */ + struct list_head non_periodic_sched_inactive; + + /* + * Deferred items in the non-periodic schedule. This is a list of + * Queue Heads. Transfers associated with these Queue Heads are not + * currently assigned to a host channel. + * When we get an NAK, the QH goes here. + */ + struct list_head non_periodic_sched_deferred; + + /* + * Active items in the non-periodic schedule. This is a list of + * Queue Heads. Transfers associated with these Queue Heads are + * currently assigned to a host channel. + */ + struct list_head non_periodic_sched_active; + + /* + * Pointer to the next Queue Head to process in the active + * non-periodic schedule. + */ + struct list_head *non_periodic_qh_ptr; + + /* + * Inactive items in the periodic schedule. This is a list of QHs for + * periodic transfers that are _not_ scheduled for the next frame. + * Each QH in the list has an interval counter that determines when it + * needs to be scheduled for execution. This scheduling mechanism + * allows only a simple calculation for periodic bandwidth used (i.e. + * must assume that all periodic transfers may need to execute in the + * same frame). However, it greatly simplifies scheduling and should + * be sufficient for the vast majority of OTG hosts, which need to + * connect to a small number of peripherals at one time. + * + * Items move from this list to periodic_sched_ready when the QH + * interval counter is 0 at SOF. + */ + struct list_head periodic_sched_inactive; + + /* + * List of periodic QHs that are ready for execution in the next + * frame, but have not yet been assigned to host channels. + * + * Items move from this list to periodic_sched_assigned as host + * channels become available during the current frame. + */ + struct list_head periodic_sched_ready; + + /* + * List of periodic QHs to be executed in the next frame that are + * assigned to host channels. + * + * Items move from this list to periodic_sched_queued as the + * transactions for the QH are queued to the DWC_otg controller. + */ + struct list_head periodic_sched_assigned; + + /* + * List of periodic QHs that have been queued for execution. + * + * Items move from this list to either periodic_sched_inactive or + * periodic_sched_ready when the channel associated with the transfer + * is released. If the interval for the QH is 1, the item moves to + * periodic_sched_ready because it must be rescheduled for the next + * frame. Otherwise, the item moves to periodic_sched_inactive. + */ + struct list_head periodic_sched_queued; + + /* + * Total bandwidth claimed so far for periodic transfers. This value + * is in microseconds per (micro)frame. The assumption is that all + * periodic transfers may occur in the same (micro)frame. + */ + u16 periodic_usecs; + + /* + * Total bandwidth claimed so far for all periodic transfers + * in a frame. + * This will include a mixture of HS and FS transfers. + * Units are microseconds per (micro)frame. + * We have a budget per frame and have to schedule + * transactions accordingly. + * Watch out for the fact that things are actually scheduled for the + * "next frame". + */ + u16 frame_usecs[8]; + + /* + * Frame number read from the core at SOF. The value ranges from 0 to + * DWC_HFNUM_MAX_FRNUM. + */ + u16 frame_number; + + /* + * Free host channels in the controller. This is a list of + * struct dwc_hc items. + */ + struct list_head free_hc_list; + + /* + * Number of available host channels. + */ + u32 available_host_channels; + + /* + * Array of pointers to the host channel descriptors. Allows accessing + * a host channel descriptor given the host channel number. This is + * useful in interrupt handlers. + */ + struct dwc_hc *hc_ptr_array[MAX_EPS_CHANNELS]; + + /* + * Buffer to use for any data received during the status phase of a + * control transfer. Normally no data is transferred during the status + * phase. This buffer is used as a bit bucket. + */ + u8 *status_buf; + + /* + * DMA address for status_buf. + */ + dma_addr_t status_buf_dma; +#define DWC_OTG_HCD_STATUS_BUF_SIZE 64 + + /* + * Structure to allow starting the HCD in a non-interrupt context + * during an OTG role change. + */ + struct work_struct start_work; + struct usb_hcd *_p; + + /* + * Connection timer. An OTG host must display a message if the device + * does not connect. Started when the VBus power is turned on via + * sysfs attribute "buspower". + */ + struct timer_list conn_timer; + + /* workqueue for port wakeup */ + struct work_struct usb_port_reset; + + /* Addition HCD interrupt */ + int cp_irq; /* charge pump interrupt */ + int cp_irq_installed; +}; + +/* + * Reasons for halting a host channel. + */ +enum dwc_halt_status { + DWC_OTG_HC_XFER_NO_HALT_STATUS, + DWC_OTG_HC_XFER_COMPLETE, + DWC_OTG_HC_XFER_URB_COMPLETE, + DWC_OTG_HC_XFER_ACK, + DWC_OTG_HC_XFER_NAK, + DWC_OTG_HC_XFER_NYET, + DWC_OTG_HC_XFER_STALL, + DWC_OTG_HC_XFER_XACT_ERR, + DWC_OTG_HC_XFER_FRAME_OVERRUN, + DWC_OTG_HC_XFER_BABBLE_ERR, + DWC_OTG_HC_XFER_DATA_TOGGLE_ERR, + DWC_OTG_HC_XFER_AHB_ERR, + DWC_OTG_HC_XFER_PERIODIC_INCOMPLETE, + DWC_OTG_HC_XFER_URB_DEQUEUE +}; + +/* + * Host channel descriptor. This structure represents the state of a single + * host channel when acting in host mode. It contains the data items needed to + * transfer packets to an endpoint via a host channel. + */ +struct dwc_hc { + /* Host channel number used for register address lookup */ + u8 hc_num; + + /* Device to access */ + unsigned dev_addr:7; + + /* EP to access */ + unsigned ep_num:4; + + /* EP direction. 0: OUT, 1: IN */ + unsigned ep_is_in:1; + + /* + * EP speed. + * One of the following values: + * - DWC_OTG_EP_SPEED_LOW + * - DWC_OTG_EP_SPEED_FULL + * - DWC_OTG_EP_SPEED_HIGH + */ + unsigned speed:2; +#define DWC_OTG_EP_SPEED_LOW 0 +#define DWC_OTG_EP_SPEED_FULL 1 +#define DWC_OTG_EP_SPEED_HIGH 2 + + /* + * Endpoint type. + * One of the following values: + * - DWC_OTG_EP_TYPE_CONTROL: 0 + * - DWC_OTG_EP_TYPE_ISOC: 1 + * - DWC_OTG_EP_TYPE_BULK: 2 + * - DWC_OTG_EP_TYPE_INTR: 3 + */ + unsigned ep_type:2; + + /* Max packet size in bytes */ + unsigned max_packet:11; + + /* + * PID for initial transaction. + * 0: DATA0, + * 1: DATA2, + * 2: DATA1, + * 3: MDATA (non-Control EP), + * SETUP (Control EP) + */ + unsigned data_pid_start:2; +#define DWC_OTG_HC_PID_DATA0 0 +#define DWC_OTG_HC_PID_DATA2 1 +#define DWC_OTG_HC_PID_DATA1 2 +#define DWC_OTG_HC_PID_MDATA 3 +#define DWC_OTG_HC_PID_SETUP 3 + + /* Number of periodic transactions per (micro)frame */ + unsigned multi_count:2; + + /* Pointer to the current transfer buffer position. */ + u8 *xfer_buff; + /* Total number of bytes to transfer. */ + u32 xfer_len; + /* Number of bytes transferred so far. */ + u32 xfer_count; + /* Packet count at start of transfer. */ + u16 start_pkt_count; + + /* + * Flag to indicate whether the transfer has been started. Set to 1 if + * it has been started, 0 otherwise. + */ + u8 xfer_started; + + /* + * Set to 1 to indicate that a PING request should be issued on this + * channel. If 0, process normally. + */ + u8 do_ping; + + /* + * Set to 1 to indicate that the error count for this transaction is + * non-zero. Set to 0 if the error count is 0. + */ + u8 error_state; + + /* + * Set to 1 to indicate that this channel should be halted the next + * time a request is queued for the channel. This is necessary in + * slave mode if no request queue space is available when an attempt + * is made to halt the channel. + */ + u8 halt_on_queue; + + /* + * Set to 1 if the host channel has been halted, but the core is not + * finished flushing queued requests. Otherwise 0. + */ + u8 halt_pending; + + /* Reason for halting the host channel. */ + enum dwc_halt_status halt_status; + + /* Split settings for the host channel */ + u8 do_split; /* Enable split for the channel */ + u8 complete_split; /* Enable complete split */ + u8 hub_addr; /* Address of high speed hub */ + u8 port_addr; /* Port of the low/full speed device */ + + /* + * Split transaction position. One of the following values: + * - DWC_HCSPLIT_XACTPOS_MID + * - DWC_HCSPLIT_XACTPOS_BEGIN + * - DWC_HCSPLIT_XACTPOS_END + * - DWC_HCSPLIT_XACTPOS_ALL */ + u8 xact_pos; + + /* Set when the host channel does a short read. */ + u8 short_read; + + /* + * Number of requests issued for this channel since it was assigned to + * the current transfer (not counting PINGs). + */ + u8 requests; + + /* Queue Head for the transfer being processed by this channel. */ + struct dwc_qh *qh; + + /* Entry in list of host channels. */ + struct list_head hc_list_entry; +}; + +/* + * The following parameters may be specified when starting the module. These + * parameters define how the DWC_otg controller should be configured. Parameter + * values are passed to the CIL initialization function dwc_otg_cil_init. + */ +struct core_params { + /* + * Specifies the OTG capabilities. The driver will automatically + * detect the value for this parameter if none is specified. + * 0 - HNP and SRP capable (default) + * 1 - SRP Only capable + * 2 - No HNP/SRP capable + */ + int otg_cap; +#define DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE 0 +#define DWC_OTG_CAP_PARAM_SRP_ONLY_CAPABLE 1 +#define DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE 2 + +#define dwc_param_otg_cap_default DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE + + /* + * Specifies whether to use slave or DMA mode for accessing the data + * FIFOs. The driver will automatically detect the value for this + * parameter if none is specified. + * 0 - Slave + * 1 - DMA (default, if available) + */ + int dma_enable; +#ifdef CONFIG_DWC_SLAVE +#define dwc_param_dma_enable_default 0 +#else +#define dwc_param_dma_enable_default 1 +#endif + + /* + * The DMA Burst size (applicable only for External DMA Mode). + * 1, 4, 8 16, 32, 64, 128, 256 (default 32) + */ + int dma_burst_size; /* Translate this to GAHBCFG values */ +#define dwc_param_dma_burst_size_default 32 + + /* + * Specifies the maximum speed of operation in host and device mode. + * The actual speed depends on the speed of the attached device and + * the value of phy_type. The actual speed depends on the speed of the + * attached device. + * 0 - High Speed (default) + * 1 - Full Speed + */ + int speed; +#define dwc_param_speed_default 0 +#define DWC_SPEED_PARAM_HIGH 0 +#define DWC_SPEED_PARAM_FULL 1 + + /* + * Specifies whether low power mode is supported when attached to a Full + * Speed or Low Speed device in host mode. + * 0 - Don't support low power mode (default) + * 1 - Support low power mode + */ + int host_support_fs_ls_low_power; +#define dwc_param_host_support_fs_ls_low_power_default 0 + + /* + * Specifies the PHY clock rate in low power mode when connected to a + * Low Speed device in host mode. This parameter is applicable only if + * HOST_SUPPORT_FS_LS_LOW_POWER is enabled. If PHY_TYPE is set to FS + * then defaults to 6 MHZ otherwise 48 MHZ. + * + * 0 - 48 MHz + * 1 - 6 MHz + */ + int host_ls_low_power_phy_clk; +#define dwc_param_host_ls_low_power_phy_clk_default 0 +#define DWC_HOST_LS_LOW_POWER_PHY_CLK_PARAM_48MHZ 0 +#define DWC_HOST_LS_LOW_POWER_PHY_CLK_PARAM_6MHZ 1 + + /* + * 0 - Use cC FIFO size parameters + * 1 - Allow dynamic FIFO sizing (default) + */ + int enable_dynamic_fifo; +#define dwc_param_enable_dynamic_fifo_default 1 + + /* + * Number of 4-byte words in the Rx FIFO in device mode when dynamic + * FIFO sizing is enabled. 16 to 32768 (default 1064) + */ + int dev_rx_fifo_size; +#define dwc_param_dev_rx_fifo_size_default 1064 + + /* + * Number of 4-byte words in the non-periodic Tx FIFO in device mode + * when dynamic FIFO sizing is enabled. 16 to 32768 (default 1024) + */ + int dev_nperio_tx_fifo_size; +#define dwc_param_dev_nperio_tx_fifo_size_default 1024 + + /* + * Number of 4-byte words in each of the periodic Tx FIFOs in device + * mode when dynamic FIFO sizing is enabled. 4 to 768 (default 256) + */ + u32 dev_perio_tx_fifo_size[MAX_PERIO_FIFOS]; +#define dwc_param_dev_perio_tx_fifo_size_default 256 + + /* + * Number of 4-byte words in the Rx FIFO in host mode when dynamic + * FIFO sizing is enabled. 16 to 32768 (default 1024) + */ + int host_rx_fifo_size; +#define dwc_param_host_rx_fifo_size_default 1024 + + /* + * Number of 4-byte words in the non-periodic Tx FIFO in host mode + * when Dynamic FIFO sizing is enabled in the core. 16 to 32768 + * (default 1024) + */ + int host_nperio_tx_fifo_size; +#define dwc_param_host_nperio_tx_fifo_size_default 1024 + + /* + Number of 4-byte words in the host periodic Tx FIFO when dynamic + * FIFO sizing is enabled. 16 to 32768 (default 1024) + */ + int host_perio_tx_fifo_size; +#define dwc_param_host_perio_tx_fifo_size_default 1024 + + /* + * The maximum transfer size supported in bytes. 2047 to 65,535 + * (default 65,535) + */ + int max_transfer_size; +#define dwc_param_max_transfer_size_default 65535 + + /* + * The maximum number of packets in a transfer. 15 to 511 (default 511) + */ + int max_packet_count; +#define dwc_param_max_packet_count_default 511 + + /* + * The number of host channel registers to use. + * 1 to 16 (default 12) + * Note: The FPGA configuration supports a maximum of 12 host channels. + */ + int host_channels; +#define dwc_param_host_channels_default 12 + + /* + * The number of endpoints in addition to EP0 available for device + * mode operations. + * 1 to 15 (default 6 IN and OUT) + * Note: The FPGA configuration supports a maximum of 6 IN and OUT + * endpoints in addition to EP0. + */ + int dev_endpoints; +#define dwc_param_dev_endpoints_default 6 + + /* + * Specifies the type of PHY interface to use. By default, the driver + * will automatically detect the phy_type. + * + * 0 - Full Speed PHY + * 1 - UTMI+ (default) + * 2 - ULPI + */ + int phy_type; +#define DWC_PHY_TYPE_PARAM_FS 0 +#define DWC_PHY_TYPE_PARAM_UTMI 1 +#define DWC_PHY_TYPE_PARAM_ULPI 2 +#define dwc_param_phy_type_default DWC_PHY_TYPE_PARAM_UTMI + + /* + * Specifies the UTMI+ Data Width. This parameter is applicable for a + * PHY_TYPE of UTMI+ or ULPI. (For a ULPI PHY_TYPE, this parameter + * indicates the data width between the MAC and the ULPI Wrapper.) Also, + * this parameter is applicable only if the OTG_HSPHY_WIDTH cC parameter + * was set to "8 and 16 bits", meaning that the core has been configured + * to work at either data path width. + * + * 8 or 16 bits (default 16) + */ + int phy_utmi_width; +#define dwc_param_phy_utmi_width_default 16 + + /* + * Specifies whether the ULPI operates at double or single + * data rate. This parameter is only applicable if PHY_TYPE is + * ULPI. + * + * 0 - single data rate ULPI interface with 8 bit wide data + * bus (default) + * 1 - double data rate ULPI interface with 4 bit wide data + * bus + */ + int phy_ulpi_ddr; +#define dwc_param_phy_ulpi_ddr_default 0 + + /* + * Specifies whether to use the internal or external supply to + * drive the vbus with a ULPI phy. + */ + int phy_ulpi_ext_vbus; +#define DWC_PHY_ULPI_INTERNAL_VBUS 0 +#define DWC_PHY_ULPI_EXTERNAL_VBUS 1 +#define dwc_param_phy_ulpi_ext_vbus_default DWC_PHY_ULPI_INTERNAL_VBUS + + /* + * Specifies whether to use the I2Cinterface for full speed PHY. This + * parameter is only applicable if PHY_TYPE is FS. + * 0 - No (default) + * 1 - Yes + */ + int i2c_enable; +#define dwc_param_i2c_enable_default 0 + + int ulpi_fs_ls; +#define dwc_param_ulpi_fs_ls_default 0 + + int ts_dline; +#define dwc_param_ts_dline_default 0 + + /* + * Specifies whether dedicated transmit FIFOs are enabled for non + * periodic IN endpoints in device mode + * 0 - No + * 1 - Yes + */ + int en_multiple_tx_fifo; +#define dwc_param_en_multiple_tx_fifo_default 1 + + /* + * Number of 4-byte words in each of the Tx FIFOs in device + * mode when dynamic FIFO sizing is enabled. 4 to 768 (default 256) + */ + u32 dev_tx_fifo_size[MAX_TX_FIFOS]; +#define dwc_param_dev_tx_fifo_size_default 256 + + /* + * Thresholding enable flag + * bit 0 - enable non-ISO Tx thresholding + * bit 1 - enable ISO Tx thresholding + * bit 2 - enable Rx thresholding + */ + u32 thr_ctl; +#define dwc_param_thr_ctl_default 0 + + /* Thresholding length for Tx FIFOs in 32 bit DWORDs */ + u32 tx_thr_length; +#define dwc_param_tx_thr_length_default 64 + + /* Thresholding length for Rx FIFOs in 32 bit DWORDs */ + u32 rx_thr_length; +#define dwc_param_rx_thr_length_default 64 + +}; + +/* + * The core_if structure contains information needed to manage the + * DWC_otg controller acting in either host or device mode. It represents the + * programming view of the controller as a whole. + */ +struct core_if { + /* Parameters that define how the core should be configured. */ + struct core_params *core_params; + + /* Core Global registers starting at offset 000h. */ + ulong core_global_regs; + + /* Device-specific information */ + struct device_if *dev_if; + /* Host-specific information */ + struct dwc_host_if *host_if; + + /* + * Set to 1 if the core PHY interface bits in USBCFG have been + * initialized. + */ + u8 phy_init_done; + + /* + * SRP Success flag, set by srp success interrupt in FS I2C mode + */ + u8 srp_success; + u8 srp_timer_started; + + /* Common configuration information */ + /* Power and Clock Gating Control Register */ + ulong pcgcctl; +#define DWC_OTG_PCGCCTL_OFFSET 0xE00 + + /* Push/pop addresses for endpoints or host channels. */ + ulong data_fifo[MAX_EPS_CHANNELS]; +#define DWC_OTG_DATA_FIFO_OFFSET 0x1000 +#define DWC_OTG_DATA_FIFO_SIZE 0x1000 + + /* Total RAM for FIFOs (Bytes) */ + u16 total_fifo_size; + /* Size of Rx FIFO (Bytes) */ + u16 rx_fifo_size; + /* Size of Non-periodic Tx FIFO (Bytes) */ + u16 nperio_tx_fifo_size; + + /* 1 if DMA is enabled, 0 otherwise. */ + u8 dma_enable; + + /* 1 if dedicated Tx FIFOs are enabled, 0 otherwise. */ + u8 en_multiple_tx_fifo; + + /* + * Set to 1 if multiple packets of a high-bandwidth transfer is in + * process of being queued + */ + u8 queuing_high_bandwidth; + + /* Hardware Configuration -- stored here for convenience. */ + ulong hwcfg1; + ulong hwcfg2; + ulong hwcfg3; + ulong hwcfg4; + + /* HCD callbacks */ + /* include/linux/usb/otg.h */ + + /* HCD callbacks */ + struct cil_callbacks *hcd_cb; + /* PCD callbacks */ + struct cil_callbacks *pcd_cb; + + /* Device mode Periodic Tx FIFO Mask */ + u32 p_tx_msk; + /* Device mode Periodic Tx FIFO Mask */ + u32 tx_msk; + + /* Features of various DWC implementation */ + u32 features; + + /* Added to support PLB DMA : phys-virt mapping */ + resource_size_t phys_addr; + + struct delayed_work usb_port_wakeup; + struct work_struct usb_port_otg; + struct otg_transceiver *xceiv; +}; + +/* + * The following functions support initialization of the CIL driver component + * and the DWC_otg controller. + */ +extern void dwc_otg_core_init(struct core_if *core_if); +extern void init_fslspclksel(struct core_if *core_if); +extern void dwc_otg_core_dev_init(struct core_if *core_if); +extern void dwc_otg_enable_global_interrupts(struct core_if *core_if); +extern void dwc_otg_disable_global_interrupts(struct core_if *core_if); +extern void dwc_otg_enable_common_interrupts(struct core_if *core_if); + +/** + * This function Reads HPRT0 in preparation to modify. It keeps the WC bits 0 + * so that if they are read as 1, they won't clear when you write it back + */ +static inline u32 dwc_otg_read_hprt0(struct core_if *core_if) +{ + u32 hprt0 = 0; + hprt0 = dwc_reg_read(core_if->host_if->hprt0, 0); + hprt0 = DWC_HPRT0_PRT_ENA_RW(hprt0, 0); + hprt0 = DWC_HPRT0_PRT_CONN_DET_RW(hprt0, 0); + hprt0 = DWC_HPRT0_PRT_ENA_DIS_CHG_RW(hprt0, 0); + hprt0 = DWC_HPRT0_PRT_OVRCURR_ACT_RW(hprt0, 0); + return hprt0; +} + +/* + * The following functions support managing the DWC_otg controller in either + * device or host mode. + */ +extern void dwc_otg_read_packet(struct core_if *core_if, u8 * dest, u16 bytes); +extern void dwc_otg_flush_tx_fifo(struct core_if *core_if, const int _num); +extern void dwc_otg_flush_rx_fifo(struct core_if *core_if); + +#define NP_TXFIFO_EMPTY -1 +#define MAX_NP_TXREQUEST_Q_SLOTS 8 + +/** + * This function returns the Core Interrupt register. + */ +static inline u32 dwc_otg_read_core_intr(struct core_if *core_if) +{ + u32 global_regs = (u32) core_if->core_global_regs; + return dwc_reg_read(global_regs, DWC_GINTSTS) & + dwc_reg_read(global_regs, DWC_GINTMSK); +} + +/** + * This function returns the mode of the operation, host or device. + */ +static inline u32 dwc_otg_mode(struct core_if *core_if) +{ + u32 global_regs = (u32) core_if->core_global_regs; + return dwc_reg_read(global_regs, DWC_GINTSTS) & 0x1; +} + +static inline u8 dwc_otg_is_device_mode(struct core_if *core_if) +{ + return dwc_otg_mode(core_if) != DWC_HOST_MODE; +} +static inline u8 dwc_otg_is_host_mode(struct core_if *core_if) +{ + return dwc_otg_mode(core_if) == DWC_HOST_MODE; +} + +extern int dwc_otg_handle_common_intr(struct core_if *core_if); + +/* + * DWC_otg CIL callback structure. This structure allows the HCD and PCD to + * register functions used for starting and stopping the PCD and HCD for role + * change on for a DRD. + */ +struct cil_callbacks { + /* Start function for role change */ + int (*start) (void *_p); + /* Stop Function for role change */ + int (*stop) (void *_p); + /* Disconnect Function for role change */ + int (*disconnect) (void *_p); + /* Resume/Remote wakeup Function */ + int (*resume_wakeup) (void *_p); + /* Suspend function */ + int (*suspend) (void *_p); + /* Session Start (SRP) */ + int (*session_start) (void *_p); + /* Pointer passed to start() and stop() */ + void *p; +}; + +extern void dwc_otg_cil_register_pcd_callbacks(struct core_if *core_if, + struct cil_callbacks *cb, + void *p); +extern void dwc_otg_cil_register_hcd_callbacks(struct core_if *core_if, + struct cil_callbacks *cb, + void *p); + +#define DWC_LIMITED_XFER 0x00000000 +#define DWC_DEVICE_ONLY 0x00000000 +#define DWC_HOST_ONLY 0x00000000 + +#ifdef DWC_LIMITED_XFER_SIZE +#undef DWC_LIMITED_XFER +#define DWC_LIMITED_XFER 0x00000001 +#endif + +#ifdef CONFIG_DWC_DEVICE_ONLY +#undef DWC_DEVICE_ONLY +#define DWC_DEVICE_ONLY 0x00000002 +static inline void dwc_otg_hcd_remove(struct device *dev) +{ +} +static inline int dwc_otg_hcd_init(struct device *_dev, + struct dwc_otg_device *dwc_dev) +{ + return 0; +} +#else +extern int dwc_otg_hcd_init(struct device *_dev, + struct dwc_otg_device *dwc_dev); +extern void dwc_otg_hcd_remove(struct device *_dev); +#endif + +#ifdef CONFIG_DWC_HOST_ONLY +#undef DWC_HOST_ONLY +#define DWC_HOST_ONLY 0x00000004 +static inline void dwc_otg_pcd_remove(struct device *dev) +{ +} +static inline int dwc_otg_pcd_init(struct device *dev) +{ + return 0; +} +#else +extern void dwc_otg_pcd_remove(struct device *dev); +extern int dwc_otg_pcd_init(struct device *dev); +#endif + +extern void dwc_otg_cil_remove(struct core_if *core_if); +extern struct core_if *dwc_otg_cil_init(const __iomem u32 * base, + struct core_params *params); + +static inline void dwc_set_feature(struct core_if *core_if) +{ + core_if->features = DWC_LIMITED_XFER | DWC_DEVICE_ONLY | DWC_HOST_ONLY; +} + +static inline int dwc_has_feature(struct core_if *core_if, + unsigned long feature) +{ + return core_if->features & feature; +} +extern struct core_params dwc_otg_module_params; +extern int check_parameters(struct core_if *core_if); +#endif diff --git a/drivers/usb/dwc/cil_intr.c b/drivers/usb/dwc/cil_intr.c new file mode 100644 index 0000000..a42b0e6 --- /dev/null +++ b/drivers/usb/dwc/cil_intr.c @@ -0,0 +1,616 @@ +/* + * DesignWare HS OTG controller driver + * Copyright (C) 2006 Synopsys, Inc. + * Portions Copyright (C) 2010 Applied Micro Circuits Corporation. + * + * This program 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. + * + * 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 version 2 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see http://www.gnu.org/licenses + * or write to the Free Software Foundation, Inc., 51 Franklin Street, + * Suite 500, Boston, MA 02110-1335 USA. + * + * Based on Synopsys driver version 2.60a + * Modified by Mark Miesfeld <mmiesfeld@apm.com> + * Modified by Stefan Roese <sr@denx.de>, DENX Software Engineering + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL SYNOPSYS, INC. BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES + * (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/* + * The Core Interface Layer provides basic services for accessing and + * managing the DWC_otg hardware. These services are used by both the + * Host Controller Driver and the Peripheral Controller Driver. + * + * This file contains the Common Interrupt handlers. + */ +#include <linux/delay.h> + +#include "cil.h" + +/** + * This function will log a debug message + */ +static int dwc_otg_handle_mode_mismatch_intr(struct core_if *core_if) +{ + u32 gintsts = 0; + ulong global_regs = core_if->core_global_regs; + + pr_warning("Mode Mismatch Interrupt: currently in %s mode\n", + dwc_otg_mode(core_if) ? "Host" : "Device"); + + /* Clear interrupt */ + gintsts |= DWC_INTSTS_MODE_MISMTC; + dwc_reg_write(global_regs, DWC_GINTSTS, gintsts); + + return 1; +} + +/** + * Start the HCD. Helper function for using the HCD callbacks. + */ +static inline void hcd_start(struct core_if *core_if) +{ + if (core_if->hcd_cb && core_if->hcd_cb->start) + core_if->hcd_cb->start(core_if->hcd_cb->p); +} + +/** + * Stop the HCD. Helper function for using the HCD callbacks. + */ +static inline void hcd_stop(struct core_if *core_if) +{ + if (core_if->hcd_cb && core_if->hcd_cb->stop) + core_if->hcd_cb->stop(core_if->hcd_cb->p); +} + +/** + * Disconnect the HCD. Helper function for using the HCD callbacks. + */ +static inline void hcd_disconnect(struct core_if *core_if) +{ + if (core_if->hcd_cb && core_if->hcd_cb->disconnect) + core_if->hcd_cb->disconnect(core_if->hcd_cb->p); +} + +/** + * Inform the HCD the a New Session has begun. Helper function for using the + * HCD callbacks. + */ +static inline void hcd_session_start(struct core_if *core_if) +{ + if (core_if->hcd_cb && core_if->hcd_cb->session_start) + core_if->hcd_cb->session_start(core_if->hcd_cb->p); +} + +/** + * Start the PCD. Helper function for using the PCD callbacks. + */ +static inline void pcd_start(struct core_if *core_if) +{ + if (core_if->pcd_cb && core_if->pcd_cb->start) { + struct dwc_pcd *pcd; + + pcd = (struct dwc_pcd *)core_if->pcd_cb->p; + spin_lock(&pcd->lock); + core_if->pcd_cb->start(core_if->pcd_cb->p); + spin_unlock(&pcd->lock); + } +} + +/** + * Stop the PCD. Helper function for using the PCD callbacks. + */ +static inline void pcd_stop(struct core_if *core_if) +{ + if (core_if->pcd_cb && core_if->pcd_cb->stop) { + struct dwc_pcd *pcd; + + pcd = (struct dwc_pcd *)core_if->pcd_cb->p; + spin_lock(&pcd->lock); + core_if->pcd_cb->stop(core_if->pcd_cb->p); + spin_unlock(&pcd->lock); + } +} + +/** + * Suspend the PCD. Helper function for using the PCD callbacks. + */ +static inline void pcd_suspend(struct core_if *core_if) +{ + if (core_if->pcd_cb && core_if->pcd_cb->suspend) { + struct dwc_pcd *pcd; + + pcd = (struct dwc_pcd *)core_if->pcd_cb->p; + spin_lock(&pcd->lock); + core_if->pcd_cb->suspend(core_if->pcd_cb->p); + spin_unlock(&pcd->lock); + } +} + +/** + * Resume the PCD. Helper function for using the PCD callbacks. + */ +static inline void pcd_resume(struct core_if *core_if) +{ + if (core_if->pcd_cb && core_if->pcd_cb->resume_wakeup) { + struct dwc_pcd *pcd; + + pcd = (struct dwc_pcd *)core_if->pcd_cb->p; + spin_lock(&pcd->lock); + core_if->pcd_cb->resume_wakeup(core_if->pcd_cb->p); + spin_unlock(&pcd->lock); + } +} + +/** + * This function handles the OTG Interrupts. It reads the OTG + * Interrupt Register (GOTGINT) to determine what interrupt has + * occurred. + */ +static int dwc_otg_handle_otg_intr(struct core_if *core_if) +{ + ulong global_regs = core_if->core_global_regs; + u32 gotgint; + u32 gotgctl; + u32 gintmsk; + + gotgint = dwc_reg_read(global_regs, DWC_GOTGINT); + if (gotgint & DWC_GINT_SES_ENDDET) { + gotgctl = dwc_reg_read(global_regs, DWC_GOTGCTL); + if (core_if->xceiv->state == OTG_STATE_B_HOST) { + pcd_start(core_if); + core_if->xceiv->state = OTG_STATE_B_PERIPHERAL; + } else { + /* + * If not B_HOST and Device HNP still set. HNP did not + * succeed + */ + if (gotgctl & DWC_GCTL_DEV_HNP_ENA) + pr_err("Device Not Connected / " + "Responding\n"); + /* + * If Session End Detected the B-Cable has been + * disconnected. Reset PCD and Gadget driver to a + * clean state. + */ + pcd_stop(core_if); + } + gotgctl = 0; + gotgctl |= DWC_GCTL_DEV_HNP_ENA; + dwc_reg_modify(global_regs, DWC_GOTGCTL, gotgctl, 0); + } + if (gotgint & DWC_GINT_SES_REQSUC) { + gotgctl = dwc_reg_read(global_regs, DWC_GOTGCTL); + if (gotgctl & DWC_GCTL_SES_REQ_SUCCESS) { + if (core_if->core_params->phy_type == + DWC_PHY_TYPE_PARAM_FS && + core_if->core_params->i2c_enable) { + core_if->srp_success = 1; + } else { + pcd_resume(core_if); + + /* Clear Session Request */ + gotgctl = 0; + gotgctl |= DWC_GCTL_SES_REQ; + dwc_reg_modify(global_regs, DWC_GOTGCTL, + gotgctl, 0); + } + } + } + if (gotgint & DWC_GINT_HST_NEGSUC) { + /* + * Print statements during the HNP interrupt handling can cause + * it to fail. + */ + gotgctl = dwc_reg_read(global_regs, DWC_GOTGCTL); + if (gotgctl & DWC_GCTL_HOST_NEG_SUCCES) { + if (dwc_otg_is_host_mode(core_if)) { + core_if->xceiv->state = OTG_STATE_B_HOST; + /* + * Need to disable SOF interrupt immediately. + * When switching from device to host, the PCD + * interrupt handler won't handle the + * interrupt if host mode is already set. The + * HCD interrupt handler won't get called if + * the HCD state is HALT. This means that the + * interrupt does not get handled and Linux + * complains loudly. + */ + gintmsk = 0; + gintmsk |= DWC_INTMSK_STRT_OF_FRM; + dwc_reg_modify(global_regs, DWC_GINTMSK, + gintmsk, 0); + pcd_stop(core_if); + /* Initialize the Core for Host mode. */ + hcd_start(core_if); + core_if->xceiv->state = OTG_STATE_B_HOST; + } + } else { + gotgctl = 0; + gotgctl |= DWC_GCTL_HNP_REQ; + gotgctl |= DWC_GCTL_DEV_HNP_ENA; + dwc_reg_modify(global_regs, DWC_GOTGCTL, gotgctl, 0); + + pr_err("Device Not Connected / Responding\n"); + } + } + if (gotgint & DWC_GINT_HST_NEGDET) { + /* + * The disconnect interrupt is set at the same time as + * Host Negotiation Detected. During the mode + * switch all interrupts are cleared so the disconnect + * interrupt handler will not get executed. + */ + if (dwc_otg_is_device_mode(core_if)) { + hcd_disconnect(core_if); + pcd_start(core_if); + core_if->xceiv->state = OTG_STATE_A_PERIPHERAL; + } else { + /* + * Need to disable SOF interrupt immediately. When + * switching from device to host, the PCD interrupt + * handler won't handle the interrupt if host mode is + * already set. The HCD interrupt handler won't get + * called if the HCD state is HALT. This means that + * the interrupt does not get handled and Linux + * complains loudly. + */ + gintmsk = 0; + gintmsk |= DWC_INTMSK_STRT_OF_FRM; + dwc_reg_modify(global_regs, DWC_GINTMSK, gintmsk, 0); + pcd_stop(core_if); + hcd_start(core_if); + core_if->xceiv->state = OTG_STATE_A_HOST; + } + } + if (gotgint & DWC_GINT_DEVTOUT) + pr_info(" ++OTG Interrupt: A-Device Timeout " "Change++\n"); + if (gotgint & DWC_GINT_DEBDONE) + pr_info(" ++OTG Interrupt: Debounce Done++\n"); + + /* Clear GOTGINT */ + dwc_reg_write(global_regs, DWC_GOTGINT, gotgint); + return 1; +} + +/* + * Wakeup Workqueue implementation + */ +static void port_otg_wqfunc(struct work_struct *work) +{ + struct core_if *core_if = container_of(work, struct core_if, + usb_port_otg); + ulong global_regs = core_if->core_global_regs; + u32 count = 0; + u32 gotgctl; + + pr_info("%s\n", __func__); + + gotgctl = dwc_reg_read(global_regs, DWC_GOTGCTL); + if (gotgctl & DWC_GCTL_CONN_ID_STATUS) { + /* + * B-Device connector (device mode) wait for switch to device + * mode. + */ + while (!dwc_otg_is_device_mode(core_if) && ++count <= 10000) { + pr_info("Waiting for Peripheral Mode, " + "Mode=%s\n", dwc_otg_is_host_mode(core_if) ? + "Host" : "Peripheral"); + msleep(100); + } + BUG_ON(count > 10000); + core_if->xceiv->state = OTG_STATE_B_PERIPHERAL; + dwc_otg_core_init(core_if); + dwc_otg_enable_global_interrupts(core_if); + pcd_start(core_if); + } else { + /* + * A-Device connector (host mode) wait for switch to host + * mode. + */ + while (!dwc_otg_is_host_mode(core_if) && ++count <= 10000) { + pr_info("Waiting for Host Mode, Mode=%s\n", + dwc_otg_is_host_mode(core_if) ? + "Host" : "Peripheral"); + msleep(100); + } + BUG_ON(count > 10000); + core_if->xceiv->state = OTG_STATE_A_HOST; + dwc_otg_core_init(core_if); + dwc_otg_enable_global_interrupts(core_if); + hcd_start(core_if); + } +} + +/** + * This function handles the Connector ID Status Change Interrupt. It + * reads the OTG Interrupt Register (GOTCTL) to determine whether this + * is a Device to Host Mode transition or a Host Mode to Device + * Transition. + * + * This only occurs when the cable is connected/removed from the PHY + * connector. + */ +static int dwc_otg_handle_conn_id_status_change_intr(struct core_if *core_if) +{ + u32 gintsts = 0; + u32 gintmsk = 0; + ulong global_regs = core_if->core_global_regs; + + /* + * Need to disable SOF interrupt immediately. If switching from device + * to host, the PCD interrupt handler won't handle the interrupt if + * host mode is already set. The HCD interrupt handler won't get + * called if the HCD state is HALT. This means that the interrupt does + * not get handled and Linux complains loudly. + */ + gintmsk |= DWC_INTSTS_STRT_OF_FRM; + dwc_reg_modify(global_regs, DWC_GINTMSK, gintmsk, 0); + + INIT_WORK(&core_if->usb_port_otg, port_otg_wqfunc); + schedule_work(&core_if->usb_port_otg); + + /* Set flag and clear interrupt */ + gintsts |= DWC_INTSTS_CON_ID_STS_CHG; + dwc_reg_write(global_regs, DWC_GINTSTS, gintsts); + return 1; +} + +/** + * This interrupt indicates that a device is initiating the Session + * Request Protocol to request the host to turn on bus power so a new + * session can begin. The handler responds by turning on bus power. If + * the DWC_otg controller is in low power mode, the handler brings the + * controller out of low power mode before turning on bus power. + */ +static int dwc_otg_handle_session_req_intr(struct core_if *core_if) +{ + u32 gintsts = 0; + ulong global_regs = core_if->core_global_regs; + + if (!dwc_has_feature(core_if, DWC_HOST_ONLY)) { + u32 hprt0; + + if (dwc_otg_is_device_mode(core_if)) { + pr_info("SRP: Device mode\n"); + } else { + pr_info("SRP: Host mode\n"); + + /* Turn on the port power bit. */ + hprt0 = dwc_otg_read_hprt0(core_if); + hprt0 = DWC_HPRT0_PRT_PWR_RW(hprt0, 1); + dwc_reg_write(core_if->host_if->hprt0, 0, hprt0); + + /* + * Start the Connection timer. + * A message can be displayed, + * if connect does not occur within 10 seconds. + */ + hcd_session_start(core_if); + } + } + /* Clear interrupt */ + gintsts |= DWC_INTSTS_NEW_SES_DET; + dwc_reg_write(global_regs, DWC_GINTSTS, gintsts); + return 1; +} + +/** + * This interrupt indicates that the DWC_otg controller has detected a + * resume or remote wakeup sequence. If the DWC_otg controller is in + * low power mode, the handler must brings the controller out of low + * power mode. The controller automatically begins resume + * signaling. The handler schedules a time to stop resume signaling. + */ +static int dwc_otg_handle_wakeup_detected_intr(struct core_if *core_if) +{ + u32 gintsts = 0; + struct device_if *dev_if = core_if->dev_if; + ulong global_regs = core_if->core_global_regs; + + if (dwc_otg_is_device_mode(core_if)) { + u32 dctl = 0; + + /* Clear the Remote Wakeup Signalling */ + dctl = DEC_DCTL_REMOTE_WAKEUP_SIG(dctl, 1); + dwc_reg_modify(dev_if->dev_global_regs, DWC_DCTL, dctl, 0); + + if (core_if->pcd_cb && core_if->pcd_cb->resume_wakeup) + core_if->pcd_cb->resume_wakeup(core_if->pcd_cb->p); + } else { + u32 pcgcctl = 0; + + /* Restart the Phy Clock */ + pcgcctl = DWC_PCGCCTL_STOP_CLK_SET(pcgcctl); + dwc_reg_modify(core_if->pcgcctl, 0, pcgcctl, 0); + schedule_delayed_work(&core_if->usb_port_wakeup, 10); + } + + /* Clear interrupt */ + gintsts |= DWC_INTSTS_WKP; + dwc_reg_write(global_regs, DWC_GINTSTS, gintsts); + return 1; +} + +/** + * This interrupt indicates that a device has been disconnected from + * the root port. + */ +static int dwc_otg_handle_disconnect_intr(struct core_if *core_if) +{ + u32 gintsts = 0; + ulong global_regs = core_if->core_global_regs; + + if (!dwc_has_feature(core_if, DWC_HOST_ONLY)) { + if (core_if->xceiv->state == OTG_STATE_B_HOST) { + hcd_disconnect(core_if); + pcd_start(core_if); + core_if->xceiv->state = OTG_STATE_B_PERIPHERAL; + } else if (dwc_otg_is_device_mode(core_if)) { + u32 gotgctl; + + gotgctl = dwc_reg_read(global_regs, DWC_GOTGCTL); + + /* + * If HNP is in process, do nothing. + * The OTG "Host Negotiation Detected" + * interrupt will do the mode switch. + * Otherwise, since we are in device mode, + * disconnect and stop the HCD, + * then start the PCD. + */ + if ((gotgctl) & DWC_GCTL_DEV_HNP_ENA) { + hcd_disconnect(core_if); + pcd_start(core_if); + core_if->xceiv->state = OTG_STATE_B_PERIPHERAL; + } + } else if (core_if->xceiv->state == OTG_STATE_A_HOST) { + /* A-Cable still connected but device disconnected. */ + hcd_disconnect(core_if); + } + } + gintsts |= DWC_INTSTS_SES_DISCON_DET; + dwc_reg_write(global_regs, DWC_GINTSTS, gintsts); + return 1; +} + +/** + * This interrupt indicates that SUSPEND state has been detected on + * the USB. + * + * For HNP the USB Suspend interrupt signals the change from + * "a_peripheral" to "a_host". + * + * When power management is enabled the core will be put in low power + * mode. + */ +static int dwc_otg_handle_usb_suspend_intr(struct core_if *core_if) +{ + u32 dsts = 0; + u32 gintsts = 0; + ulong global_regs = core_if->core_global_regs; + struct device_if *dev_if = core_if->dev_if; + + if (dwc_otg_is_device_mode(core_if)) { + /* + * Check the Device status register to determine if the Suspend + * state is active. + */ + dsts = dwc_reg_read(dev_if->dev_global_regs, DWC_DSTS); + /* PCD callback for suspend. */ + pcd_suspend(core_if); + } else { + if (core_if->xceiv->state == OTG_STATE_A_PERIPHERAL) { + /* Clear the a_peripheral flag, back to a_host. */ + pcd_stop(core_if); + hcd_start(core_if); + core_if->xceiv->state = OTG_STATE_A_HOST; + } + } + + /* Clear interrupt */ + gintsts |= DWC_INTMSK_USB_SUSP; + dwc_reg_write(global_regs, DWC_GINTSTS, gintsts); + return 1; +} + +/** + * This function returns the Core Interrupt register. + * + * Although the Host Port interrupt (portintr) is documented as host mode + * only, it appears to occur in device mode when Port Enable / Disable Changed + * bit in HPRT0 is set. The code in dwc_otg_handle_common_intr checks if in + * device mode and just clears the interrupt. + */ +static inline u32 dwc_otg_read_common_intr(struct core_if *core_if) +{ + u32 gintsts; + u32 gintmsk; + u32 gintmsk_common = 0; + ulong global_regs = core_if->core_global_regs; + + gintmsk_common |= DWC_INTMSK_WKP; + gintmsk_common |= DWC_INTMSK_NEW_SES_DET; + gintmsk_common |= DWC_INTMSK_CON_ID_STS_CHG; + gintmsk_common |= DWC_INTMSK_OTG; + gintmsk_common |= DWC_INTMSK_MODE_MISMTC; + gintmsk_common |= DWC_INTMSK_SES_DISCON_DET; + gintmsk_common |= DWC_INTMSK_USB_SUSP; + gintmsk_common |= DWC_INTMSK_HST_PORT; + + gintsts = dwc_reg_read(global_regs, DWC_GINTSTS); + gintmsk = dwc_reg_read(global_regs, DWC_GINTMSK); + + return (gintsts & gintmsk) & gintmsk_common; +} + +/** + * Common interrupt handler. + * + * The common interrupts are those that occur in both Host and Device mode. + * This handler handles the following interrupts: + * - Mode Mismatch Interrupt + * - Disconnect Interrupt + * - OTG Interrupt + * - Connector ID Status Change Interrupt + * - Session Request Interrupt. + * - Resume / Remote Wakeup Detected Interrupt. + * + * - Host Port Interrupt. Although this interrupt is documented as only + * occurring in Host mode, it also occurs in Device mode when Port Enable / + * Disable Changed bit in HPRT0 is set. If it is seen here, while in Device + * mode, the interrupt is just cleared. + * + */ +int dwc_otg_handle_common_intr(struct core_if *core_if) +{ + int retval = 0; + u32 gintsts; + ulong global_regs = core_if->core_global_regs; + + gintsts = dwc_otg_read_common_intr(core_if); + + if (gintsts & DWC_INTSTS_MODE_MISMTC) + retval |= dwc_otg_handle_mode_mismatch_intr(core_if); + if (gintsts & DWC_INTSTS_OTG) + retval |= dwc_otg_handle_otg_intr(core_if); + if (gintsts & DWC_INTSTS_CON_ID_STS_CHG) + retval |= dwc_otg_handle_conn_id_status_change_intr(core_if); + if (gintsts & DWC_INTSTS_SES_DISCON_DET) + retval |= dwc_otg_handle_disconnect_intr(core_if); + if (gintsts & DWC_INTSTS_NEW_SES_DET) + retval |= dwc_otg_handle_session_req_intr(core_if); + if (gintsts & DWC_INTSTS_WKP) + retval |= dwc_otg_handle_wakeup_detected_intr(core_if); + if (gintsts & DWC_INTMSK_USB_SUSP) + retval |= dwc_otg_handle_usb_suspend_intr(core_if); + + if ((gintsts & DWC_INTSTS_HST_PORT) && + dwc_otg_is_device_mode(core_if)) { + gintsts = 0; + gintsts |= DWC_INTSTS_HST_PORT; + dwc_reg_write(global_regs, DWC_GINTSTS, gintsts); + retval |= 1; + pr_info("RECEIVED PORTINT while in Device mode\n"); + } + + return retval; +} diff --git a/drivers/usb/dwc/driver.h b/drivers/usb/dwc/driver.h new file mode 100644 index 0000000..a86532b --- /dev/null +++ b/drivers/usb/dwc/driver.h @@ -0,0 +1,76 @@ +/* + * DesignWare HS OTG controller driver + * Copyright (C) 2006 Synopsys, Inc. + * Portions Copyright (C) 2010 Applied Micro Circuits Corporation. + * + * This program 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. + * + * 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 version 2 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see http://www.gnu.org/licenses + * or write to the Free Software Foundation, Inc., 51 Franklin Street, + * Suite 500, Boston, MA 02110-1335 USA. + * + * Based on Synopsys driver version 2.60a + * Modified by Mark Miesfeld <mmiesfeld@apm.com> + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL SYNOPSYS, INC. BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES + * (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#if !defined(__DWC_OTG_DRIVER_H__) +#define __DWC_OTG_DRIVER_H__ + +/* + * This file contains the interface to the Linux driver. + */ +#include "cil.h" + +/* + * This structure is a wrapper that encapsulates the driver components used to + * manage a single DWC_otg controller. + */ +struct dwc_otg_device { + /* Base address returned from ioremap() */ + __iomem void *base; + + /* Pointer to the core interface structure. */ + struct core_if *core_if; + + /* Pointer to the PCD structure. */ + struct dwc_pcd *pcd; + + /* Pointer to the HCD structure. */ + struct dwc_hcd *hcd; + + /* Flag to indicate whether the common IRQ handler is installed. */ + u8 common_irq_installed; + + /* Interrupt request number. */ + unsigned int irq; + + /* + * Physical address of Control and Status registers, used by + * release_mem_region(). + */ + resource_size_t phys_addr; + + /* Length of memory region, used by release_mem_region(). */ + unsigned long base_len; +}; +#endif diff --git a/drivers/usb/dwc/hcd.c b/drivers/usb/dwc/hcd.c new file mode 100644 index 0000000..2718b95 --- /dev/null +++ b/drivers/usb/dwc/hcd.c @@ -0,0 +1,2473 @@ +/* + * DesignWare HS OTG controller driver + * Copyright (C) 2006 Synopsys, Inc. + * Portions Copyright (C) 2010 Applied Micro Circuits Corporation. + * + * This program 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. + * + * 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 version 2 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see http://www.gnu.org/licenses + * or write to the Free Software Foundation, Inc., 51 Franklin Street, + * Suite 500, Boston, MA 02110-1335 USA. + * + * Based on Synopsys driver version 2.60a + * Modified by Mark Miesfeld <mmiesfeld@apm.com> + * Modified by Stefan Roese <sr@denx.de>, DENX Software Engineering + * Modified by Chuck Meade <chuck@theptrgroup.com> + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL SYNOPSYS, INC. BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES + * (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/* + * This file contains the implementation of the HCD. In Linux, the HCD + * implements the hc_driver API. + */ + +#include <asm/unaligned.h> +#include <linux/dma-mapping.h> + +#include "hcd.h" + +static const char dwc_otg_hcd_name[] = "dwc_otg_hcd"; + +/** + * Clears the transfer state for a host channel. This function is normally + * called after a transfer is done and the host channel is being released. It + * clears the channel interrupt enables and any unhandled channel interrupt + * conditions. + */ +void dwc_otg_hc_cleanup(struct core_if *core_if, struct dwc_hc *hc) +{ + ulong regs; + + hc->xfer_started = 0; + regs = core_if->host_if->hc_regs[hc->hc_num]; + dwc_reg_write(regs, DWC_HCINTMSK, 0); + dwc_reg_write(regs, DWC_HCINT, 0xFFFFFFFF); +} + +/** + * This function enables the Host mode interrupts. + */ +static void dwc_otg_enable_host_interrupts(struct core_if *core_if) +{ + ulong global_regs = core_if->core_global_regs; + u32 intr_mask = 0; + + /* Disable all interrupts. */ + dwc_reg_write(global_regs, DWC_GINTMSK, 0); + + /* Clear any pending interrupts. */ + dwc_reg_write(global_regs, DWC_GINTSTS, 0xFFFFFFFF); + + /* Enable the common interrupts */ + dwc_otg_enable_common_interrupts(core_if); + + /* + * Enable host mode interrupts without disturbing common + * interrupts. + */ + intr_mask |= DWC_INTMSK_STRT_OF_FRM; + intr_mask |= DWC_INTMSK_HST_PORT; + intr_mask |= DWC_INTMSK_HST_CHAN; + dwc_reg_modify(global_regs, DWC_GINTMSK, intr_mask, intr_mask); +} + +/** + * This function initializes the DWC_otg controller registers for + * host mode. + * + * This function flushes the Tx and Rx FIFOs and it flushes any entries in the + * request queues. Host channels are reset to ensure that they are ready for + * performing transfers. + */ +static void dwc_otg_core_host_init(struct core_if *core_if) +{ + ulong global_regs = core_if->core_global_regs; + struct dwc_host_if *host_if = core_if->host_if; + struct core_params *params = core_if->core_params; + u32 hprt0 = 0; + u32 nptxfifosize = 0; + u32 ptxfifosize = 0; + u32 i; + u32 hcchar; + ulong hcfg; + ulong hc_regs; + int num_channels; + u32 gotgctl = 0; + + /* Restart the Phy Clock */ + dwc_reg_write(core_if->pcgcctl, 0, 0); + + /* Initialize Host Configuration Register */ + init_fslspclksel(core_if); + if (core_if->core_params->speed == DWC_SPEED_PARAM_FULL) { + hcfg = dwc_reg_read(host_if->host_global_regs, DWC_HCFG); + hcfg = DWC_HCFG_FSLSUPP_RW(hcfg, 1); + dwc_reg_write(host_if->host_global_regs, DWC_HCFG, hcfg); + } + + /* Configure data FIFO sizes */ + if (DWC_HWCFG2_DYN_FIFO_RD(core_if->hwcfg2) + && params->enable_dynamic_fifo) { + /* Rx FIFO */ + dwc_reg_write(global_regs, DWC_GRXFSIZ, + params->host_rx_fifo_size); + + /* Non-periodic Tx FIFO */ + nptxfifosize = DWC_RX_FIFO_DEPTH_WR(nptxfifosize, + params-> + host_nperio_tx_fifo_size); + nptxfifosize = + DWC_RX_FIFO_START_ADDR_WR(nptxfifosize, + params->host_rx_fifo_size); + dwc_reg_write(global_regs, DWC_GNPTXFSIZ, nptxfifosize); + + /* Periodic Tx FIFO */ + ptxfifosize = DWC_RX_FIFO_DEPTH_WR(ptxfifosize, + params-> + host_perio_tx_fifo_size); + ptxfifosize = + DWC_RX_FIFO_START_ADDR_WR(ptxfifosize, + (DWC_RX_FIFO_START_ADDR_RD + (nptxfifosize) + + DWC_RX_FIFO_DEPTH_RD + (nptxfifosize))); + dwc_reg_write(global_regs, DWC_HPTXFSIZ, ptxfifosize); + } + + /* Clear Host Set HNP Enable in the OTG Control Register */ + gotgctl |= DWC_GCTL_HOST_HNP_ENA; + dwc_reg_modify(global_regs, DWC_GOTGCTL, gotgctl, 0); + + /* Make sure the FIFOs are flushed. */ + dwc_otg_flush_tx_fifo(core_if, DWC_GRSTCTL_TXFNUM_ALL); + dwc_otg_flush_rx_fifo(core_if); + + /* Flush out any leftover queued requests. */ + num_channels = core_if->core_params->host_channels; + for (i = 0; i < num_channels; i++) { + hc_regs = core_if->host_if->hc_regs[i]; + hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR); + hcchar = DWC_HCCHAR_ENA_RW(hcchar, 0); + hcchar = DWC_HCCHAR_DIS_RW(hcchar, 1); + hcchar = DWC_HCCHAR_EPDIR_RW(hcchar, 0); + dwc_reg_write(hc_regs, DWC_HCCHAR, hcchar); + } + + /* Halt all channels to put them into a known state. */ + for (i = 0; i < num_channels; i++) { + int count = 0; + + hc_regs = core_if->host_if->hc_regs[i]; + hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR); + hcchar = DWC_HCCHAR_ENA_RW(hcchar, 1); + hcchar = DWC_HCCHAR_DIS_RW(hcchar, 1); + hcchar = DWC_HCCHAR_EPDIR_RW(hcchar, 0); + dwc_reg_write(hc_regs, DWC_HCCHAR, hcchar); + + do { + hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR); + if (++count > 200) { + pr_err("%s: Unable to clear halt on " + "channel %d\n", __func__, i); + break; + } + udelay(100); + } while (DWC_HCCHAR_ENA_RD(hcchar)); + } + + /* Turn on the vbus power. */ + pr_info("Init: Port Power? op_state=%s\n", + otg_state_string(core_if->xceiv->state)); + + if (core_if->xceiv->state == OTG_STATE_A_HOST) { + hprt0 = dwc_otg_read_hprt0(core_if); + pr_info("Init: Power Port (%d)\n", DWC_HPRT0_PRT_PWR_RD(hprt0)); + if (DWC_HPRT0_PRT_PWR_RD(hprt0) == 0) { + hprt0 = DWC_HPRT0_PRT_PWR_RW(hprt0, 1); + dwc_reg_write(host_if->hprt0, 0, hprt0); + } + } + dwc_otg_enable_host_interrupts(core_if); +} + +/** + * Initializes dynamic portions of the DWC_otg HCD state. + */ +static void hcd_reinit(struct dwc_hcd *hcd) +{ + struct list_head *item; + int num_channels; + u32 i; + struct dwc_hc *channel; + + hcd->flags.d32 = 0; + hcd->non_periodic_qh_ptr = &hcd->non_periodic_sched_active; + hcd->available_host_channels = hcd->core_if->core_params->host_channels; + + /* + * Put all channels in the free channel list and clean up channel + * states. + */ + item = hcd->free_hc_list.next; + while (item != &hcd->free_hc_list) { + list_del(item); + item = hcd->free_hc_list.next; + } + + num_channels = hcd->core_if->core_params->host_channels; + for (i = 0; i < num_channels; i++) { + channel = hcd->hc_ptr_array[i]; + list_add_tail(&channel->hc_list_entry, &hcd->free_hc_list); + dwc_otg_hc_cleanup(hcd->core_if, channel); + } + + /* Initialize the DWC core for host mode operation. */ + dwc_otg_core_host_init(hcd->core_if); +} + +/* Gets the dwc_hcd from a struct usb_hcd */ +static inline struct dwc_hcd *hcd_to_dwc_otg_hcd(struct usb_hcd *hcd) +{ + return (struct dwc_hcd *)hcd->hcd_priv; +} + +/** + * Initializes the DWC_otg controller and its root hub and prepares it for host + * mode operation. Activates the root port. Returns 0 on success and a negative + * error code on failure. +*/ +static int dwc_otg_hcd_start(struct usb_hcd *hcd) +{ + struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd); + struct usb_bus *bus = hcd_to_bus(hcd); + + hcd->state = HC_STATE_RUNNING; + + /* Inform the HUB driver to resume. */ + if (bus->root_hub) + usb_hcd_resume_root_hub(hcd); + + hcd_reinit(dwc_hcd); + return 0; +} + +/** + * Work queue function for starting the HCD when A-Cable is connected. + * The dwc_otg_hcd_start() must be called in a process context. + */ +static void hcd_start_func(struct work_struct *work) +{ + struct dwc_hcd *priv = container_of(work, struct dwc_hcd, start_work); + struct usb_hcd *usb_hcd = (struct usb_hcd *)priv->_p; + + if (usb_hcd) + dwc_otg_hcd_start(usb_hcd); +} + +/** + * HCD Callback function for starting the HCD when A-Cable is + * connected. + */ +static int dwc_otg_hcd_start_cb(void *_p) +{ + struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(_p); + struct core_if *core_if = dwc_hcd->core_if; + u32 hprt0; + + if (core_if->xceiv->state == OTG_STATE_B_HOST) { + /* + * Reset the port. During a HNP mode switch the reset + * needs to occur within 1ms and have a duration of at + * least 50ms. + */ + hprt0 = dwc_otg_read_hprt0(core_if); + hprt0 = DWC_HPRT0_PRT_RST_RW(hprt0, 1); + dwc_reg_write(core_if->host_if->hprt0, 0, hprt0); + ((struct usb_hcd *)_p)->self.is_b_host = 1; + } else { + ((struct usb_hcd *)_p)->self.is_b_host = 0; + } + + /* Need to start the HCD in a non-interrupt context. */ + dwc_hcd->_p = _p; + schedule_work(&dwc_hcd->start_work); + return 1; +} + +/** + * This function disables the Host Mode interrupts. + */ +static void dwc_otg_disable_host_interrupts(struct core_if *core_if) +{ + u32 global_regs = core_if->core_global_regs; + u32 intr_mask = 0; + + /* + * Disable host mode interrupts without disturbing common + * interrupts. + */ + intr_mask |= DWC_INTMSK_STRT_OF_FRM; + intr_mask |= DWC_INTMSK_HST_PORT; + intr_mask |= DWC_INTMSK_HST_CHAN; + intr_mask |= DWC_INTMSK_P_TXFIFO_EMPTY; + intr_mask |= DWC_INTMSK_NP_TXFIFO_EMPT; + dwc_reg_modify(global_regs, DWC_GINTMSK, intr_mask, 0); +} + +/** + * Halts the DWC_otg host mode operations in a clean manner. USB transfers are + * stopped. + */ +static void dwc_otg_hcd_stop(struct usb_hcd *hcd) +{ + struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd); + u32 hprt0 = 0; + + /* Turn off all host-specific interrupts. */ + spin_lock(&dwc_hcd->lock); + dwc_otg_disable_host_interrupts(dwc_hcd->core_if); + spin_unlock(&dwc_hcd->lock); + + /* + * The root hub should be disconnected before this function is called. + * The disconnect will clear the QTD lists (via ..._hcd_urb_dequeue) + * and the QH lists (via ..._hcd_endpoint_disable). + */ + + /* Turn off the vbus power */ + pr_info("PortPower off\n"); + hprt0 = DWC_HPRT0_PRT_PWR_RW(hprt0, 0); + dwc_reg_write(dwc_hcd->core_if->host_if->hprt0, 0, hprt0); +} + +/** + * HCD Callback function for stopping the HCD. + */ +static int dwc_otg_hcd_stop_cb(void *_p) +{ + struct usb_hcd *usb_hcd = (struct usb_hcd *)_p; + + dwc_otg_hcd_stop(usb_hcd); + return 1; +} + +static void del_timers(struct dwc_hcd *hcd) +{ + del_timer_sync(&hcd->conn_timer); +} + +/** + * Processes all the URBs in a single list of QHs. Completes them with + * -ETIMEDOUT and frees the QTD. + */ +static void kill_urbs_in_qh_list(struct dwc_hcd *hcd, struct list_head *qh_list) +{ + struct list_head *qh_item, *q; + + qh_item = qh_list->next; + list_for_each_safe(qh_item, q, qh_list) { + struct dwc_qh *qh; + struct list_head *qtd_item; + struct dwc_qtd *qtd; + + qh = list_entry(qh_item, struct dwc_qh, qh_list_entry); + qtd_item = qh->qtd_list.next; + qtd = list_entry(qtd_item, struct dwc_qtd, qtd_list_entry); + if (qtd->urb != NULL) { + spin_lock(&hcd->lock); + dwc_otg_hcd_complete_urb(hcd, qtd->urb, -ETIMEDOUT); + dwc_otg_hcd_qtd_remove_and_free(qtd); + spin_unlock(&hcd->lock); + } + } +} + +/** + * Responds with an error status of ETIMEDOUT to all URBs in the non-periodic + * and periodic schedules. The QTD associated with each URB is removed from + * the schedule and freed. This function may be called when a disconnect is + * detected or when the HCD is being stopped. + */ +static void kill_all_urbs(struct dwc_hcd *hcd) +{ + kill_urbs_in_qh_list(hcd, &hcd->non_periodic_sched_deferred); + kill_urbs_in_qh_list(hcd, &hcd->non_periodic_sched_inactive); + kill_urbs_in_qh_list(hcd, &hcd->non_periodic_sched_active); + kill_urbs_in_qh_list(hcd, &hcd->periodic_sched_inactive); + kill_urbs_in_qh_list(hcd, &hcd->periodic_sched_ready); + kill_urbs_in_qh_list(hcd, &hcd->periodic_sched_assigned); + kill_urbs_in_qh_list(hcd, &hcd->periodic_sched_queued); +} + +/** + * HCD Callback function for disconnect of the HCD. + */ +static int dwc_otg_hcd_disconnect_cb(void *_p) +{ + u32 intr; + struct dwc_hcd *hcd = hcd_to_dwc_otg_hcd(_p); + struct core_if *core_if = hcd->core_if; + + /* Set status flags for the hub driver. */ + hcd->flags.b.port_connect_status_change = 1; + hcd->flags.b.port_connect_status = 0; + + /* + * Shutdown any transfers in process by clearing the Tx FIFO Empty + * interrupt mask and status bits and disabling subsequent host + * channel interrupts. + */ + intr = 0; + intr |= DWC_INTMSK_NP_TXFIFO_EMPT; + intr |= DWC_INTMSK_P_TXFIFO_EMPTY; + intr |= DWC_INTMSK_HST_CHAN; + spin_lock(&hcd->lock); + dwc_reg_modify(gintmsk_reg(hcd), 0, intr, 0); + dwc_reg_modify(gintsts_reg(hcd), 0, intr, 0); + spin_unlock(&hcd->lock); + + del_timers(hcd); + + /* + * Turn off the vbus power only if the core has transitioned to device + * mode. If still in host mode, need to keep power on to detect a + * reconnection. + */ + if (dwc_otg_is_device_mode(core_if)) { + if (core_if->xceiv->state != OTG_STATE_A_SUSPEND) { + u32 hprt0 = 0; + + pr_info("Disconnect: PortPower off\n"); + hprt0 = DWC_HPRT0_PRT_PWR_RW(hprt0, 0); + dwc_reg_write(core_if->host_if->hprt0, 0, hprt0); + } + dwc_otg_disable_host_interrupts(core_if); + } + + /* Respond with an error status to all URBs in the schedule. */ + kill_all_urbs(hcd); + if (dwc_otg_is_host_mode(core_if)) { + /* Clean up any host channels that were in use. */ + int num_channels; + u32 i; + struct dwc_hc *channel; + ulong regs; + u32 hcchar; + + num_channels = core_if->core_params->host_channels; + if (!core_if->dma_enable) { + /* Flush out any channel requests in slave mode. */ + for (i = 0; i < num_channels; i++) { + channel = hcd->hc_ptr_array[i]; + if (list_empty(&channel->hc_list_entry)) { + regs = + core_if->host_if->hc_regs[i]; + hcchar = dwc_reg_read(regs, DWC_HCCHAR); + + if (DWC_HCCHAR_ENA_RD(hcchar)) { + hcchar = + DWC_HCCHAR_ENA_RW(hcchar, + 0); + hcchar = + DWC_HCCHAR_DIS_RW(hcchar, + 1); + hcchar = + DWC_HCCHAR_EPDIR_RW(hcchar, + 0); + dwc_reg_write(regs, DWC_HCCHAR, + hcchar); + } + } + } + } + + for (i = 0; i < num_channels; i++) { + channel = hcd->hc_ptr_array[i]; + if (list_empty(&channel->hc_list_entry)) { + regs = core_if->host_if->hc_regs[i]; + hcchar = dwc_reg_read(regs, DWC_HCCHAR); + + if (DWC_HCCHAR_ENA_RD(hcchar)) { + /* Halt the channel. */ + hcchar = DWC_HCCHAR_DIS_RW(hcchar, 1); + dwc_reg_write(regs, DWC_HCCHAR, hcchar); + } + dwc_otg_hc_cleanup(core_if, channel); + list_add_tail(&channel->hc_list_entry, + &hcd->free_hc_list); + } + } + } + + /* + * A disconnect will end the session so the B-Device is no + * longer a B-host. + */ + ((struct usb_hcd *)_p)->self.is_b_host = 0; + return 1; +} + +/** + * Connection timeout function. An OTG host is required to display a + * message if the device does not connect within 10 seconds. + */ +static void dwc_otg_hcd_connect_timeout(unsigned long _ptr) +{ + pr_info("Connect Timeout\n"); + pr_err("Device Not Connected/Responding\n"); +} + +/** + * Start the connection timer. An OTG host is required to display a + * message if the device does not connect within 10 seconds. The + * timer is deleted if a port connect interrupt occurs before the + * timer expires. + */ +static void dwc_otg_hcd_start_connect_timer(struct dwc_hcd *hcd) +{ + init_timer(&hcd->conn_timer); + hcd->conn_timer.function = dwc_otg_hcd_connect_timeout; + hcd->conn_timer.data = (unsigned long)0; + hcd->conn_timer.expires = jiffies + (HZ * 10); + add_timer(&hcd->conn_timer); +} + +/** + * HCD Callback function for disconnect of the HCD. + */ +static int dwc_otg_hcd_session_start_cb(void *_p) +{ + struct dwc_hcd *hcd = hcd_to_dwc_otg_hcd(_p); + + dwc_otg_hcd_start_connect_timer(hcd); + return 1; +} + +/* HCD Callback structure for handling mode switching. */ +static struct cil_callbacks hcd_cil_callbacks = { + .start = dwc_otg_hcd_start_cb, + .stop = dwc_otg_hcd_stop_cb, + .disconnect = dwc_otg_hcd_disconnect_cb, + .session_start = dwc_otg_hcd_session_start_cb, + .p = NULL, +}; + +/* + * Reset Workqueue implementation + */ +static void port_reset_wqfunc(struct work_struct *work) +{ + struct dwc_hcd *hcd = container_of(work, struct dwc_hcd, + usb_port_reset); + struct core_if *core_if = hcd->core_if; + u32 hprt0 = 0; + unsigned long flags; + + pr_info("%s\n", __func__); + spin_lock_irqsave(&hcd->lock, flags); + hprt0 = dwc_otg_read_hprt0(core_if); + hprt0 = DWC_HPRT0_PRT_RST_RW(hprt0, 1); + dwc_reg_write(core_if->host_if->hprt0, 0, hprt0); + spin_unlock_irqrestore(&hcd->lock, flags); + msleep(60); + spin_lock_irqsave(&hcd->lock, flags); + hprt0 = DWC_HPRT0_PRT_RST_RW(hprt0, 0); + dwc_reg_write(core_if->host_if->hprt0, 0, hprt0); + hcd->flags.b.port_reset_change = 1; + spin_unlock_irqrestore(&hcd->lock, flags); +} + +/* + * Wakeup Workqueue implementation + */ +static void port_wakeup_wqfunc(struct work_struct *work) +{ + struct core_if *core_if = container_of(to_delayed_work(work), + struct core_if, usb_port_wakeup); + u32 hprt0; + + pr_info("%s\n", __func__); + /* Now wait for 70 ms. */ + hprt0 = dwc_otg_read_hprt0(core_if); + msleep(70); + hprt0 = DWC_HPRT0_PRT_RES_RW(hprt0, 0); + dwc_reg_write(core_if->host_if->hprt0, 0, hprt0); +} + +/** + * Starts processing a USB transfer request specified by a USB Request Block + * (URB). mem_flags indicates the type of memory allocation to use while + * processing this URB. + */ +static int dwc_otg_hcd_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, + gfp_t _mem_flags) +{ + int retval; + unsigned long flags; + struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd); + struct dwc_qtd *qtd; + + if (!dwc_hcd->flags.b.port_connect_status) { + /* No longer connected. */ + retval = -ENODEV; + goto err_enq; + } + + qtd = dwc_otg_hcd_qtd_create(urb, _mem_flags); + if (!qtd) { + pr_err("DWC OTG HCD URB Enqueue failed creating " "QTD\n"); + retval = -ENOMEM; + goto err_enq; + } + + spin_lock_irqsave(&dwc_hcd->lock, flags); + retval = usb_hcd_link_urb_to_ep(hcd, urb); + if (unlikely(retval)) + goto fail; + + retval = dwc_otg_hcd_qtd_add(qtd, dwc_hcd); + if (retval < 0) { + pr_err("DWC OTG HCD URB Enqueue failed adding QTD. " + "Error status %d\n", retval); + usb_hcd_unlink_urb_from_ep(hcd, urb); + goto fail; + } + +fail: + if (retval) + dwc_otg_hcd_qtd_free(qtd); + + spin_unlock_irqrestore(&dwc_hcd->lock, flags); +err_enq: + + return retval; +} + +/** + * Attempts to halt a host channel. This function should only be called in + * Slave mode or to abort a transfer in either Slave mode or DMA mode. Under + * normal circumstances in DMA mode, the controller halts the channel when the + * transfer is complete or a condition occurs that requires application + * intervention. + * + * In slave mode, checks for a free request queue entry, then sets the Channel + * Enable and Channel Disable bits of the Host Channel Characteristics + * register of the specified channel to intiate the halt. If there is no free + * request queue entry, sets only the Channel Disable bit of the HCCHARn + * register to flush requests for this channel. In the latter case, sets a + * flag to indicate that the host channel needs to be halted when a request + * queue slot is open. + * + * In DMA mode, always sets the Channel Enable and Channel Disable bits of the + * HCCHARn register. The controller ensures there is space in the request + * queue before submitting the halt request. + * + * Some time may elapse before the core flushes any posted requests for this + * host channel and halts. The Channel Halted interrupt handler completes the + * deactivation of the host channel. + */ +void dwc_otg_hc_halt(struct core_if *core_if, struct dwc_hc *hc, + enum dwc_halt_status hlt_sts) +{ + u32 nptxsts; + u32 hptxsts = 0; + u32 hcchar; + ulong hc_regs; + ulong global_regs = core_if->core_global_regs; + ulong host_global_regs; + + hc_regs = core_if->host_if->hc_regs[hc->hc_num]; + host_global_regs = core_if->host_if->host_global_regs; + + WARN_ON(hlt_sts == DWC_OTG_HC_XFER_NO_HALT_STATUS); + + if (hlt_sts == DWC_OTG_HC_XFER_URB_DEQUEUE || + hlt_sts == DWC_OTG_HC_XFER_AHB_ERR) { + /* + * Disable all channel interrupts except Ch Halted. The QTD + * and QH state associated with this transfer has been cleared + * (in the case of URB_DEQUEUE), so the channel needs to be + * shut down carefully to prevent crashes. + */ + u32 hcintmsk; + hcintmsk = 0; + hcintmsk = DWC_HCINTMSK_CHAN_HALTED_RW(hcintmsk, 1); + dwc_reg_write(hc_regs, DWC_HCINTMSK, hcintmsk); + + /* + * Make sure no other interrupts besides halt are currently + * pending. Handling another interrupt could cause a crash due + * to the QTD and QH state. + */ + dwc_reg_write(hc_regs, DWC_HCINT, ~hcintmsk); + + /* + * Make sure the halt status is set to URB_DEQUEUE or AHB_ERR + * even if the channel was already halted for some other reason. + */ + hc->halt_status = hlt_sts; + + /* + * If the channel is not enabled, the channel is either already + * halted or it hasn't started yet. In DMA mode, the transfer + * may halt if it finishes normally or a condition occurs that + * requires driver intervention. Don't want to halt the channel + * again. In either Slave or DMA mode, it's possible that the + * transfer has been assigned to a channel, but not started yet + * when an URB is dequeued. Don't want to halt a channel that + * hasn't started yet. + */ + hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR); + if (!DWC_HCCHAR_ENA_RD(hcchar)) + return; + } + + if (hc->halt_pending) + /* + * A halt has already been issued for this channel. This might + * happen when a transfer is aborted by a higher level in + * the stack. + */ + return; + + hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR); + hcchar = DWC_HCCHAR_ENA_RW(hcchar, 1); + hcchar = DWC_HCCHAR_DIS_RW(hcchar, 1); + if (!core_if->dma_enable) { + /* Check for space in the request queue to issue the halt. */ + if (hc->ep_type == DWC_OTG_EP_TYPE_CONTROL || + hc->ep_type == DWC_OTG_EP_TYPE_BULK) { + nptxsts = dwc_reg_read(global_regs, DWC_GNPTXSTS); + + if (!DWC_GNPTXSTS_NPTXQSPCAVAIL_RD(nptxsts)) + hcchar = DWC_HCCHAR_ENA_RW(hcchar, 0); + } else { + hptxsts = + dwc_reg_read(host_global_regs, DWC_HPTXSTS); + + if (!DWC_HPTXSTS_PTXSPC_AVAIL_RD(hptxsts) || + core_if->queuing_high_bandwidth) + hcchar = DWC_HCCHAR_ENA_RW(hcchar, 0); + } + } + dwc_reg_write(hc_regs, DWC_HCCHAR, hcchar); + + hc->halt_status = hlt_sts; + if (DWC_HCCHAR_ENA_RD(hcchar)) { + hc->halt_pending = 1; + hc->halt_on_queue = 0; + } else { + hc->halt_on_queue = 1; + } +} + +/** + * Aborts/cancels a USB transfer request. Always returns 0 to indicate + * success. + */ +static int dwc_otg_hcd_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, + int status) +{ + unsigned long flags; + struct dwc_hcd *dwc_hcd; + struct dwc_qtd *urb_qtd; + struct dwc_qh *qh; + int retval; + + urb_qtd = (struct dwc_qtd *)urb->hcpriv; + if (!urb_qtd) + return -EINVAL; + qh = (struct dwc_qh *)urb_qtd->qtd_qh_ptr; + if (!qh) + return -EINVAL; + + dwc_hcd = hcd_to_dwc_otg_hcd(hcd); + spin_lock_irqsave(&dwc_hcd->lock, flags); + + retval = usb_hcd_check_unlink_urb(hcd, urb, status); + if (retval) { + spin_unlock_irqrestore(&dwc_hcd->lock, flags); + return retval; + } + + if (urb_qtd == qh->qtd_in_process) { + /* The QTD is in process (it has been assigned to a channel). */ + if (dwc_hcd->flags.b.port_connect_status) { + /* + * If still connected (i.e. in host mode), halt the + * channel so it can be used for other transfers. If + * no longer connected, the host registers can't be + * written to halt the channel since the core is in + * device mode. + */ + dwc_otg_hc_halt(dwc_hcd->core_if, qh->channel, + DWC_OTG_HC_XFER_URB_DEQUEUE); + } + } + + /* + * Free the QTD and clean up the associated QH. Leave the QH in the + * schedule if it has any remaining QTDs. + */ + dwc_otg_hcd_qtd_remove_and_free(urb_qtd); + if (qh && urb_qtd == qh->qtd_in_process) { + dwc_otg_hcd_qh_deactivate(dwc_hcd, qh, 0); + qh->channel = NULL; + qh->qtd_in_process = NULL; + } else if (qh && list_empty(&qh->qtd_list)) { + dwc_otg_hcd_qh_remove(dwc_hcd, qh); + } + + urb->hcpriv = NULL; + usb_hcd_unlink_urb_from_ep(hcd, urb); + spin_unlock_irqrestore(&dwc_hcd->lock, flags); + + /* Higher layer software sets URB status. */ + usb_hcd_giveback_urb(hcd, urb, status); + + return 0; +} + +/* Remove and free a QH */ +static inline void dwc_otg_hcd_qh_remove_and_free(struct dwc_hcd *hcd, + struct dwc_qh *qh) +{ + dwc_otg_hcd_qh_remove(hcd, qh); + dwc_otg_hcd_qh_free(qh); +} + +static void qh_list_free(struct dwc_hcd *hcd, struct list_head *_qh_list) +{ + struct list_head *item, *tmp; + struct dwc_qh *qh; + + /* If the list hasn't been initialized yet, return. */ + if (_qh_list->next == NULL) + return; + + /* Ensure there are no QTDs or URBs left. */ + kill_urbs_in_qh_list(hcd, _qh_list); + + list_for_each_safe(item, tmp, _qh_list) { + qh = list_entry(item, struct dwc_qh, qh_list_entry); + dwc_otg_hcd_qh_remove_and_free(hcd, qh); + } +} + +/** + * Frees resources in the DWC_otg controller related to a given endpoint. Also + * clears state in the HCD related to the endpoint. Any URBs for the endpoint + * must already be dequeued. + */ +static void dwc_otg_hcd_endpoint_disable(struct usb_hcd *hcd, + struct usb_host_endpoint *ep) +{ + struct dwc_qh *qh; + struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd); + unsigned long flags; + + spin_lock_irqsave(&dwc_hcd->lock, flags); + qh = (struct dwc_qh *)ep->hcpriv; + if (qh) { + dwc_otg_hcd_qh_remove_and_free(dwc_hcd, qh); + ep->hcpriv = NULL; + } + spin_unlock_irqrestore(&dwc_hcd->lock, flags); +} + +/** + * Creates Status Change bitmap for the root hub and root port. The bitmap is + * returned in buf. Bit 0 is the status change indicator for the root hub. Bit 1 + * is the status change indicator for the single root port. Returns 1 if either + * change indicator is 1, otherwise returns 0. + */ +static int dwc_otg_hcd_hub_status_data(struct usb_hcd *_hcd, char *buf) +{ + struct dwc_hcd *hcd = hcd_to_dwc_otg_hcd(_hcd); + + buf[0] = 0; + buf[0] |= (hcd->flags.b.port_connect_status_change + || hcd->flags.b.port_reset_change + || hcd->flags.b.port_enable_change + || hcd->flags.b.port_suspend_change + || hcd->flags.b.port_over_current_change) << 1; + + return (buf[0] != 0); +} + +/* Handles the hub class-specific ClearPortFeature request.*/ +static int do_clear_port_feature(struct dwc_hcd *hcd, u16 val) +{ + struct core_if *core_if = hcd->core_if; + u32 hprt0 = 0; + unsigned long flags; + + spin_lock_irqsave(&hcd->lock, flags); + switch (val) { + case USB_PORT_FEAT_ENABLE: + hprt0 = dwc_otg_read_hprt0(core_if); + hprt0 = DWC_HPRT0_PRT_ENA_RW(hprt0, 1); + dwc_reg_write(core_if->host_if->hprt0, 0, hprt0); + break; + case USB_PORT_FEAT_SUSPEND: + hprt0 = dwc_otg_read_hprt0(core_if); + hprt0 = DWC_HPRT0_PRT_RES_RW(hprt0, 1); + dwc_reg_write(core_if->host_if->hprt0, 0, hprt0); + + /* Clear Resume bit */ + spin_unlock_irqrestore(&hcd->lock, flags); + msleep(100); + spin_lock_irqsave(&hcd->lock, flags); + hprt0 = DWC_HPRT0_PRT_RES_RW(hprt0, 0); + dwc_reg_write(core_if->host_if->hprt0, 0, hprt0); + break; + case USB_PORT_FEAT_POWER: + hprt0 = dwc_otg_read_hprt0(core_if); + hprt0 = DWC_HPRT0_PRT_PWR_RW(hprt0, 0); + dwc_reg_write(core_if->host_if->hprt0, 0, hprt0); + break; + case USB_PORT_FEAT_INDICATOR: + /* Port inidicator not supported */ + break; + case USB_PORT_FEAT_C_CONNECTION: + /* Clears drivers internal connect status change flag */ + hcd->flags.b.port_connect_status_change = 0; + break; + case USB_PORT_FEAT_C_RESET: + /* Clears driver's internal Port Reset Change flag */ + hcd->flags.b.port_reset_change = 0; + break; + case USB_PORT_FEAT_C_ENABLE: + /* Clears driver's internal Port Enable/Disable Change flag */ + hcd->flags.b.port_enable_change = 0; + break; + case USB_PORT_FEAT_C_SUSPEND: + /* + * Clears the driver's internal Port Suspend + * Change flag, which is set when resume signaling on + * the host port is complete + */ + hcd->flags.b.port_suspend_change = 0; + break; + case USB_PORT_FEAT_C_OVER_CURRENT: + hcd->flags.b.port_over_current_change = 0; + break; + default: + pr_err("DWC OTG HCD - ClearPortFeature request %xh " + "unknown or unsupported\n", val); + spin_unlock_irqrestore(&hcd->lock, flags); + return -EINVAL; + } + spin_unlock_irqrestore(&hcd->lock, flags); + return 0; +} + +/* Handles the hub class-specific SetPortFeature request.*/ +static int do_set_port_feature(struct usb_hcd *hcd, u16 val, u16 index) +{ + struct core_if *core_if = hcd_to_dwc_otg_hcd(hcd)->core_if; + u32 hprt0 = 0; + struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd); + unsigned long flags; + u32 pcgcctl = 0; + + spin_lock_irqsave(&dwc_hcd->lock, flags); + + switch (val) { + case USB_PORT_FEAT_SUSPEND: + if (hcd->self.otg_port == index && hcd->self.b_hnp_enable) { + u32 gotgctl = 0; + gotgctl |= DWC_GCTL_HOST_HNP_ENA; + dwc_reg_modify(core_if->core_global_regs, + DWC_GOTGCTL, 0, gotgctl); + core_if->xceiv->state = OTG_STATE_A_SUSPEND; + } + + hprt0 = dwc_otg_read_hprt0(core_if); + hprt0 = DWC_HPRT0_PRT_SUS_RW(hprt0, 1); + dwc_reg_write(core_if->host_if->hprt0, 0, hprt0); + + /* Suspend the Phy Clock */ + pcgcctl = DWC_PCGCCTL_STOP_CLK_SET(pcgcctl); + dwc_reg_write(core_if->pcgcctl, 0, pcgcctl); + + /* For HNP the bus must be suspended for at least 200ms. */ + if (hcd->self.b_hnp_enable) { + spin_unlock_irqrestore(&dwc_hcd->lock, flags); + msleep(200); + spin_lock_irqsave(&dwc_hcd->lock, flags); + } + break; + case USB_PORT_FEAT_POWER: + hprt0 = dwc_otg_read_hprt0(core_if); + hprt0 = DWC_HPRT0_PRT_PWR_RW(hprt0, 1); + dwc_reg_write(core_if->host_if->hprt0, 0, hprt0); + break; + case USB_PORT_FEAT_RESET: + hprt0 = dwc_otg_read_hprt0(core_if); + + /* + * When B-Host the Port reset bit is set in the Start HCD + * Callback function, so that the reset is started within 1ms + * of the HNP success interrupt. + */ + if (!hcd->self.is_b_host) { + hprt0 = DWC_HPRT0_PRT_RST_RW(hprt0, 1); + dwc_reg_write(core_if->host_if->hprt0, 0, hprt0); + } + + /* Clear reset bit in 10ms (FS/LS) or 50ms (HS) */ + spin_unlock_irqrestore(&dwc_hcd->lock, flags); + msleep(60); + spin_lock_irqsave(&dwc_hcd->lock, flags); + hprt0 = DWC_HPRT0_PRT_RST_RW(hprt0, 0); + dwc_reg_write(core_if->host_if->hprt0, 0, hprt0); + break; + case USB_PORT_FEAT_INDICATOR: + /* Not supported */ + break; + default: + pr_err("DWC OTG HCD - " + "SetPortFeature request %xh " + "unknown or unsupported\n", val); + spin_unlock_irqrestore(&dwc_hcd->lock, flags); + return -EINVAL; + } + spin_unlock_irqrestore(&dwc_hcd->lock, flags); + return 0; +} + +/* Handles hub class-specific requests.*/ +static int dwc_otg_hcd_hub_control(struct usb_hcd *hcd, u16 req_type, u16 val, + u16 index, char *buf, u16 len) +{ + int retval = 0; + struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd); + struct core_if *core_if = hcd_to_dwc_otg_hcd(hcd)->core_if; + struct usb_hub_descriptor *desc; + u32 hprt0 = 0; + u32 port_status; + unsigned long flags; + + spin_lock_irqsave(&dwc_hcd->lock, flags); + switch (req_type) { + case ClearHubFeature: + switch (val) { + case C_HUB_LOCAL_POWER: + case C_HUB_OVER_CURRENT: + /* Nothing required here */ + break; + default: + retval = -EINVAL; + pr_err("DWC OTG HCD - ClearHubFeature request" + " %xh unknown\n", val); + } + break; + case ClearPortFeature: + if (!index || index > 1) + goto error; + spin_unlock_irqrestore(&dwc_hcd->lock, flags); + retval = do_clear_port_feature(dwc_hcd, val); + spin_lock_irqsave(&dwc_hcd->lock, flags); + break; + case GetHubDescriptor: + desc = (struct usb_hub_descriptor *)buf; + desc->bDescLength = 9; + desc->bDescriptorType = 0x29; + desc->bNbrPorts = 1; + desc->wHubCharacteristics = 0x08; + desc->bPwrOn2PwrGood = 1; + desc->bHubContrCurrent = 0; + break; + case GetHubStatus: + memset(buf, 0, 4); + break; + case GetPortStatus: + if (!index || index > 1) + goto error; + + port_status = 0; + if (dwc_hcd->flags.b.port_connect_status_change) + port_status |= (1 << USB_PORT_FEAT_C_CONNECTION); + if (dwc_hcd->flags.b.port_enable_change) + port_status |= (1 << USB_PORT_FEAT_C_ENABLE); + if (dwc_hcd->flags.b.port_suspend_change) + port_status |= (1 << USB_PORT_FEAT_C_SUSPEND); + if (dwc_hcd->flags.b.port_reset_change) + port_status |= (1 << USB_PORT_FEAT_C_RESET); + if (dwc_hcd->flags.b.port_over_current_change) { + pr_err("Device Not Supported\n"); + port_status |= (1 << USB_PORT_FEAT_C_OVER_CURRENT); + } + if (!dwc_hcd->flags.b.port_connect_status) { + /* + * The port is disconnected, which means the core is + * either in device mode or it soon will be. Just + * return 0's for the remainder of the port status + * since the port register can't be read if the core + * is in device mode. + */ + *((__le32 *) buf) = cpu_to_le32(port_status); + break; + } + + hprt0 = dwc_reg_read(core_if->host_if->hprt0, 0); + + if (DWC_HPRT0_PRT_STS_RD(hprt0)) + port_status |= USB_PORT_STAT_CONNECTION; + if (DWC_HPRT0_PRT_ENA_RD(hprt0)) + port_status |= USB_PORT_STAT_ENABLE; + if (DWC_HPRT0_PRT_SUS_RD(hprt0)) + port_status |= USB_PORT_STAT_SUSPEND; + if (DWC_HPRT0_PRT_OVRCURR_ACT_RD(hprt0)) + port_status |= USB_PORT_STAT_OVERCURRENT; + if (DWC_HPRT0_PRT_RST_RD(hprt0)) + port_status |= USB_PORT_STAT_RESET; + if (DWC_HPRT0_PRT_PWR_RD(hprt0)) + port_status |= USB_PORT_STAT_POWER; + + if (DWC_HPRT0_PRT_SPD_RD(hprt0) == DWC_HPRT0_PRTSPD_HIGH_SPEED) + port_status |= USB_PORT_STAT_HIGH_SPEED; + else if (DWC_HPRT0_PRT_SPD_RD(hprt0) == + DWC_HPRT0_PRTSPD_LOW_SPEED) + port_status |= USB_PORT_STAT_LOW_SPEED; + + if (DWC_HPRT0_PRT_TST_CTL_RD(hprt0)) + port_status |= (1 << USB_PORT_FEAT_TEST); + + /* USB_PORT_FEAT_INDICATOR unsupported always 0 */ + *((__le32 *) buf) = cpu_to_le32(port_status); + break; + case SetHubFeature: + /* No HUB features supported */ + break; + case SetPortFeature: + if (val != USB_PORT_FEAT_TEST && (!index || index > 1)) + goto error; + + if (!dwc_hcd->flags.b.port_connect_status) { + /* + * The port is disconnected, which means the core is + * either in device mode or it soon will be. Just + * return without doing anything since the port + * register can't be written if the core is in device + * mode. + */ + break; + } + spin_unlock_irqrestore(&dwc_hcd->lock, flags); + retval = do_set_port_feature(hcd, val, index); + spin_lock_irqsave(&dwc_hcd->lock, flags); + break; + default: +error: + retval = -EINVAL; + pr_warning("DWC OTG HCD - Unknown hub control request" + " type or invalid req_type: %xh index: %xh " + "val: %xh\n", req_type, index, val); + break; + } + spin_unlock_irqrestore(&dwc_hcd->lock, flags); + return retval; +} + +/** + * Handles host mode interrupts for the DWC_otg controller. Returns IRQ_NONE if + * there was no interrupt to handle. Returns IRQ_HANDLED if there was a valid + * interrupt. + * + * This function is called by the USB core when an interrupt occurs + */ +static irqreturn_t dwc_otg_hcd_irq(struct usb_hcd *hcd) +{ + struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd); + + return IRQ_RETVAL(dwc_otg_hcd_handle_intr(dwc_hcd)); +} + +static const struct hc_driver dwc_otg_hc_driver = { + .description = dwc_otg_hcd_name, + .product_desc = "DWC OTG Controller", + .hcd_priv_size = sizeof(struct dwc_hcd), + .irq = dwc_otg_hcd_irq, + .flags = HCD_MEMORY | HCD_USB2, + .start = dwc_otg_hcd_start, + .stop = dwc_otg_hcd_stop, + .urb_enqueue = dwc_otg_hcd_urb_enqueue, + .urb_dequeue = dwc_otg_hcd_urb_dequeue, + .endpoint_disable = dwc_otg_hcd_endpoint_disable, + .get_frame_number = dwc_otg_hcd_get_frame_number, + .hub_status_data = dwc_otg_hcd_hub_status_data, + .hub_control = dwc_otg_hcd_hub_control, +}; + +/** + * Frees secondary storage associated with the dwc_hcd structure contained + * in the struct usb_hcd field. + */ +static void dwc_otg_hcd_free(struct usb_hcd *hcd) +{ + struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd); + u32 i; + + del_timers(dwc_hcd); + + /* Free memory for QH/QTD lists */ + qh_list_free(dwc_hcd, &dwc_hcd->non_periodic_sched_inactive); + qh_list_free(dwc_hcd, &dwc_hcd->non_periodic_sched_deferred); + qh_list_free(dwc_hcd, &dwc_hcd->non_periodic_sched_active); + qh_list_free(dwc_hcd, &dwc_hcd->periodic_sched_inactive); + qh_list_free(dwc_hcd, &dwc_hcd->periodic_sched_ready); + qh_list_free(dwc_hcd, &dwc_hcd->periodic_sched_assigned); + qh_list_free(dwc_hcd, &dwc_hcd->periodic_sched_queued); + + /* Free memory for the host channels. */ + for (i = 0; i < MAX_EPS_CHANNELS; i++) { + struct dwc_hc *hc = dwc_hcd->hc_ptr_array[i]; + + kfree(hc); + } + if (dwc_hcd->core_if->dma_enable) { + if (dwc_hcd->status_buf_dma) + dma_free_coherent(hcd->self.controller, + DWC_OTG_HCD_STATUS_BUF_SIZE, + dwc_hcd->status_buf, + dwc_hcd->status_buf_dma); + } else { + kfree(dwc_hcd->status_buf); + } + +} + +/** + * Initializes the HCD. This function allocates memory for and initializes the + * static parts of the usb_hcd and dwc_hcd structures. It also registers the + * USB bus with the core and calls the hc_driver->start() function. It returns + * a negative error on failure. + */ +int dwc_otg_hcd_init(struct device *_dev, + struct dwc_otg_device *dwc_otg_device) +{ + struct usb_hcd *hcd; + struct dwc_hcd *dwc_hcd; + struct dwc_otg_device *otg_dev = dev_get_drvdata(_dev); + int num_channels; + u32 i; + struct dwc_hc *channel; + int retval = 0; + + /* + * Allocate memory for the base HCD plus the DWC OTG HCD. + * Initialize the base HCD. + */ + hcd = usb_create_hcd(&dwc_otg_hc_driver, _dev, dwc_otg_hcd_name); + if (!hcd) { + retval = -ENOMEM; + goto error1; + } + dev_set_drvdata(_dev, dwc_otg_device); + hcd->regs = otg_dev->base; + hcd->rsrc_start = otg_dev->phys_addr; + hcd->rsrc_len = otg_dev->base_len; + hcd->self.otg_port = 1; + + /* Initialize the DWC OTG HCD. */ + dwc_hcd = hcd_to_dwc_otg_hcd(hcd); + dwc_hcd->core_if = otg_dev->core_if; + spin_lock_init(&dwc_hcd->lock); + otg_dev->hcd = dwc_hcd; + + /* Register the HCD CIL Callbacks */ + dwc_otg_cil_register_hcd_callbacks(otg_dev->core_if, &hcd_cil_callbacks, + hcd); + + /* Initialize the non-periodic schedule. */ + INIT_LIST_HEAD(&dwc_hcd->non_periodic_sched_inactive); + INIT_LIST_HEAD(&dwc_hcd->non_periodic_sched_active); + INIT_LIST_HEAD(&dwc_hcd->non_periodic_sched_deferred); + + /* Initialize the periodic schedule. */ + INIT_LIST_HEAD(&dwc_hcd->periodic_sched_inactive); + INIT_LIST_HEAD(&dwc_hcd->periodic_sched_ready); + INIT_LIST_HEAD(&dwc_hcd->periodic_sched_assigned); + INIT_LIST_HEAD(&dwc_hcd->periodic_sched_queued); + + /* + * Create a host channel descriptor for each host channel implemented + * in the controller. Initialize the channel descriptor array. + */ + INIT_LIST_HEAD(&dwc_hcd->free_hc_list); + num_channels = dwc_hcd->core_if->core_params->host_channels; + + for (i = 0; i < num_channels; i++) { + channel = kzalloc(sizeof(struct dwc_hc), GFP_KERNEL); + if (!channel) { + retval = -ENOMEM; + pr_err("%s: host channel allocation failed\n", + __func__); + goto error2; + } + + channel->hc_num = i; + dwc_hcd->hc_ptr_array[i] = channel; + } + + /* Initialize the Connection timeout timer. */ + init_timer(&dwc_hcd->conn_timer); + + /* Initialize workqueue */ + INIT_WORK(&dwc_hcd->usb_port_reset, port_reset_wqfunc); + INIT_WORK(&dwc_hcd->start_work, hcd_start_func); + INIT_WORK(&dwc_hcd->core_if->usb_port_otg, NULL); + INIT_DELAYED_WORK(&dwc_hcd->core_if->usb_port_wakeup, + port_wakeup_wqfunc); + + /* Set device flags indicating whether the HCD supports DMA. */ + if (otg_dev->core_if->dma_enable) { + static u64 dummy_mask = DMA_BIT_MASK(32); + + pr_info("Using DMA mode\n"); + _dev->dma_mask = (void *)&dummy_mask; + _dev->coherent_dma_mask = ~0; + } else { + pr_info("Using Slave mode\n"); + _dev->dma_mask = (void *)0; + _dev->coherent_dma_mask = 0; + } + + init_hcd_usecs(dwc_hcd); + /* + * Finish generic HCD initialization and start the HCD. This function + * allocates the DMA buffer pool, registers the USB bus, requests the + * IRQ line, and calls dwc_otg_hcd_start method. + */ + printk("before hcd\n");msleep(100); + retval = usb_add_hcd(hcd, otg_dev->irq, IRQF_SHARED); + printk("after hcd\n");msleep(100); + if (retval < 0) + goto error2; + hcd->rsrc_start = otg_dev->phys_addr; + hcd->rsrc_len = otg_dev->base_len; + + /* + * Allocate space for storing data on status transactions. Normally no + * data is sent, but this space acts as a bit bucket. This must be + * done after usb_add_hcd since that function allocates the DMA buffer + * pool. + */ + if (otg_dev->core_if->dma_enable) { + dwc_hcd->status_buf = + dma_alloc_coherent(_dev, DWC_OTG_HCD_STATUS_BUF_SIZE, + &dwc_hcd->status_buf_dma, + GFP_KERNEL | GFP_DMA); + } else { + dwc_hcd->status_buf = kmalloc(DWC_OTG_HCD_STATUS_BUF_SIZE, + GFP_KERNEL); + } + if (!dwc_hcd->status_buf) { + retval = -ENOMEM; + pr_err("%s: status_buf allocation failed\n", __func__); + goto error3; + } + return 0; + +error3: + usb_remove_hcd(hcd); +error2: + dwc_otg_hcd_free(hcd); + usb_put_hcd(hcd); +error1: + return retval; +} + +/** + * Removes the HCD. + * Frees memory and resources associated with the HCD and deregisters the bus. + */ +void __devexit dwc_otg_hcd_remove(struct device *_dev) +{ + struct dwc_otg_device *otg_dev = dev_get_drvdata(_dev); + struct dwc_hcd *dwc_hcd = otg_dev->hcd; + struct usb_hcd *hcd = dwc_otg_hcd_to_hcd(dwc_hcd); + + /* Turn off all interrupts */ + dwc_reg_write(gintmsk_reg(dwc_hcd), 0, 0); + spin_lock(&dwc_hcd->lock); + dwc_reg_modify(gahbcfg_reg(dwc_hcd), 0, 1, 0); + spin_unlock(&dwc_hcd->lock); + + cancel_work_sync(&dwc_hcd->start_work); + cancel_work_sync(&dwc_hcd->usb_port_reset); + cancel_work_sync(&dwc_hcd->core_if->usb_port_otg); + + usb_remove_hcd(hcd); + dwc_otg_hcd_free(hcd); + usb_put_hcd(hcd); +} + +/** Returns the current frame number. */ +int dwc_otg_hcd_get_frame_number(struct usb_hcd *hcd) +{ + struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd); + u32 hfnum = 0; + + hfnum = dwc_reg_read(dwc_hcd->core_if->host_if-> + host_global_regs, DWC_HFNUM); + + return DWC_HFNUM_FRNUM_RD(hfnum); +} + +/** + * Prepares a host channel for transferring packets to/from a specific + * endpoint. The HCCHARn register is set up with the characteristics specified + * in _hc. Host channel interrupts that may need to be serviced while this + * transfer is in progress are enabled. + */ +static void dwc_otg_hc_init(struct core_if *core_if, struct dwc_hc *hc) +{ + u32 intr_enable; + ulong global_regs = core_if->core_global_regs; + u32 hc_intr_mask = 0; + u32 gintmsk = 0; + u32 hcchar; + u32 hcsplt; + u8 hc_num = hc->hc_num; + struct dwc_host_if *host_if = core_if->host_if; + ulong hc_regs = host_if->hc_regs[hc_num]; + + /* Clear old interrupt conditions for this host channel. */ + hc_intr_mask = 0x3FF; + dwc_reg_write(hc_regs, DWC_HCINT, hc_intr_mask); + + /* Enable channel interrupts required for this transfer. */ + hc_intr_mask = 0; + hc_intr_mask = DWC_HCINTMSK_CHAN_HALTED_RW(hc_intr_mask, 1); + if (core_if->dma_enable) { + hc_intr_mask = DWC_HCINTMSK_AHB_ERR_RW(hc_intr_mask, 1); + + if (hc->error_state && !hc->do_split && + hc->ep_type != DWC_OTG_EP_TYPE_ISOC) { + hc_intr_mask = + DWC_HCINTMSK_ACK_RESP_REC_RW(hc_intr_mask, 1); + if (hc->ep_is_in) { + hc_intr_mask = + DWC_HCINTMSK_DATA_TOG_ERR_RW(hc_intr_mask, + 1); + if (hc->ep_type != DWC_OTG_EP_TYPE_INTR) + hc_intr_mask = + DWC_HCINTMSK_NAK_RESP_REC_RW + (hc_intr_mask, 1); + } + } + } else { + switch (hc->ep_type) { + case DWC_OTG_EP_TYPE_CONTROL: + case DWC_OTG_EP_TYPE_BULK: + hc_intr_mask = + DWC_HCINTMSK_TXFER_CMPL_RW(hc_intr_mask, 1); + hc_intr_mask = + DWC_HCINTMSK_STALL_RESP_REC_RW(hc_intr_mask, 1); + hc_intr_mask = + DWC_HCINTMSK_TRANS_ERR_RW(hc_intr_mask, 1); + hc_intr_mask = + DWC_HCINTMSK_DATA_TOG_ERR_RW(hc_intr_mask, 1); + + if (hc->ep_is_in) { + hc_intr_mask = + DWC_HCINTMSK_BBL_ERR_RW(hc_intr_mask, 1); + } else { + hc_intr_mask = + DWC_HCINTMSK_NAK_RESP_REC_RW(hc_intr_mask, + 1); + hc_intr_mask = + DWC_HCINTMSK_NYET_RESP_REC_RW(hc_intr_mask, + 1); + if (hc->do_ping) + hc_intr_mask = + DWC_HCINTMSK_ACK_RESP_REC_RW + (hc_intr_mask, 1); + } + + if (hc->do_split) { + hc_intr_mask = + DWC_HCINTMSK_NAK_RESP_REC_RW(hc_intr_mask, + 1); + if (hc->complete_split) + hc_intr_mask = + DWC_HCINTMSK_NYET_RESP_REC_RW + (hc_intr_mask, 1); + else + hc_intr_mask = + DWC_HCINTMSK_ACK_RESP_REC_RW + (hc_intr_mask, 1); + } + + if (hc->error_state) + hc_intr_mask = + DWC_HCINTMSK_ACK_RESP_REC_RW(hc_intr_mask, + 1); + break; + case DWC_OTG_EP_TYPE_INTR: + hc_intr_mask = + DWC_HCINTMSK_TXFER_CMPL_RW(hc_intr_mask, 1); + hc_intr_mask = + DWC_HCINTMSK_NAK_RESP_REC_RW(hc_intr_mask, 1); + hc_intr_mask = + DWC_HCINTMSK_STALL_RESP_REC_RW(hc_intr_mask, 1); + hc_intr_mask = + DWC_HCINTMSK_TRANS_ERR_RW(hc_intr_mask, 1); + hc_intr_mask = + DWC_HCINTMSK_DATA_TOG_ERR_RW(hc_intr_mask, 1); + hc_intr_mask = + DWC_HCINTMSK_FRAME_OVERN_ERR_RW(hc_intr_mask, 1); + + if (hc->ep_is_in) + hc_intr_mask = + DWC_HCINTMSK_BBL_ERR_RW(hc_intr_mask, 1); + if (hc->error_state) + hc_intr_mask = + DWC_HCINTMSK_ACK_RESP_REC_RW(hc_intr_mask, + 1); + + if (hc->do_split) { + if (hc->complete_split) + hc_intr_mask = + DWC_HCINTMSK_NYET_RESP_REC_RW + (hc_intr_mask, 1); + else + hc_intr_mask = + DWC_HCINTMSK_ACK_RESP_REC_RW + (hc_intr_mask, 1); + } + break; + case DWC_OTG_EP_TYPE_ISOC: + hc_intr_mask = + DWC_HCINTMSK_TXFER_CMPL_RW(hc_intr_mask, 1); + hc_intr_mask = + DWC_HCINTMSK_FRAME_OVERN_ERR_RW(hc_intr_mask, 1); + hc_intr_mask = + DWC_HCINTMSK_ACK_RESP_REC_RW(hc_intr_mask, 1); + + if (hc->ep_is_in) { + hc_intr_mask = + DWC_HCINTMSK_TRANS_ERR_RW(hc_intr_mask, 1); + hc_intr_mask = + DWC_HCINTMSK_BBL_ERR_RW(hc_intr_mask, 1); + } + break; + } + } + dwc_reg_write(hc_regs, DWC_HCINTMSK, hc_intr_mask); + + /* Enable the top level host channel interrupt. */ + intr_enable = (1 << hc_num); + dwc_reg_modify(host_if->host_global_regs, DWC_HAINTMSK, 0, + intr_enable); + + /* Make sure host channel interrupts are enabled. */ + gintmsk |= DWC_INTMSK_HST_CHAN; + dwc_reg_modify(global_regs, DWC_GINTMSK, 0, gintmsk); + + /* + * Program the HCCHARn register with the endpoint characteristics for + * the current transfer. + */ + hcchar = 0; + hcchar = DWC_HCCHAR_DEV_ADDR_RW(hcchar, hc->dev_addr); + hcchar = DWC_HCCHAR_EP_NUM_RW(hcchar, hc->ep_num); + hcchar = DWC_HCCHAR_EPDIR_RW(hcchar, hc->ep_is_in); + hcchar = DWC_HCCHAR_LSP_DEV_RW(hcchar, (hc->speed == + DWC_OTG_EP_SPEED_LOW)); + hcchar = DWC_HCCHAR_EPTYPE_RW(hcchar, hc->ep_type); + hcchar = DWC_HCCHAR_MPS_RW(hcchar, hc->max_packet); + dwc_reg_write(host_if->hc_regs[hc_num], DWC_HCCHAR, hcchar); + + /* Program the HCSPLIT register for SPLITs */ + hcsplt = 0; + if (hc->do_split) { + hcsplt = DWC_HCSPLT_COMP_SPLT_RW(hcsplt, hc->complete_split); + hcsplt = DWC_HCSPLT_TRANS_POS_RW(hcsplt, hc->xact_pos); + hcsplt = DWC_HCSPLT_HUB_ADDR_RW(hcsplt, hc->hub_addr); + hcsplt = DWC_HCSPLT_PRT_ADDR_RW(hcsplt, hc->port_addr); + } + dwc_reg_write(host_if->hc_regs[hc_num], DWC_HCSPLT, hcsplt); +} + +/** + * Assigns transactions from a QTD to a free host channel and initializes the + * host channel to perform the transactions. The host channel is removed from + * the free list. + */ +static void assign_and_init_hc(struct dwc_hcd *hcd, struct dwc_qh *qh) +{ + struct dwc_hc *hc; + struct dwc_qtd *qtd; + struct urb *urb; + struct usb_iso_packet_descriptor *frame_desc; + + hc = list_entry(hcd->free_hc_list.next, struct dwc_hc, hc_list_entry); + + /* Remove the host channel from the free list. */ + list_del_init(&hc->hc_list_entry); + qtd = list_entry(qh->qtd_list.next, struct dwc_qtd, qtd_list_entry); + urb = qtd->urb; + qh->channel = hc; + qh->qtd_in_process = qtd; + + /* + * Use usb_pipedevice to determine device address. This address is + * 0 before the SET_ADDRESS command and the correct address afterward. + */ + hc->dev_addr = usb_pipedevice(urb->pipe); + hc->ep_num = usb_pipeendpoint(urb->pipe); + + if (urb->dev->speed == USB_SPEED_LOW) + hc->speed = DWC_OTG_EP_SPEED_LOW; + else if (urb->dev->speed == USB_SPEED_FULL) + hc->speed = DWC_OTG_EP_SPEED_FULL; + else + hc->speed = DWC_OTG_EP_SPEED_HIGH; + + hc->max_packet = dwc_max_packet(qh->maxp); + hc->xfer_started = 0; + hc->halt_status = DWC_OTG_HC_XFER_NO_HALT_STATUS; + hc->error_state = (qtd->error_count > 0); + hc->halt_on_queue = 0; + hc->halt_pending = 0; + hc->requests = 0; + + /* + * The following values may be modified in the transfer type section + * below. The xfer_len value may be reduced when the transfer is + * started to accommodate the max widths of the XferSize and PktCnt + * fields in the HCTSIZn register. + */ + hc->do_ping = qh->ping_state; + hc->ep_is_in = (usb_pipein(urb->pipe) != 0); + hc->data_pid_start = qh->data_toggle; + hc->multi_count = 1; + + if (hcd->core_if->dma_enable) + hc->xfer_buff = urb->transfer_dma + (u8 *) urb->actual_length; + else + hc->xfer_buff = (u8 *) urb->transfer_buffer + + urb->actual_length; + + hc->xfer_len = urb->transfer_buffer_length - urb->actual_length; + hc->xfer_count = 0; + + /* + * Set the split attributes + */ + hc->do_split = 0; + if (qh->do_split) { + hc->do_split = 1; + hc->xact_pos = qtd->isoc_split_pos; + hc->complete_split = qtd->complete_split; + hc->hub_addr = urb->dev->tt->hub->devnum; + hc->port_addr = urb->dev->ttport; + } + + switch (usb_pipetype(urb->pipe)) { + case PIPE_CONTROL: + hc->ep_type = DWC_OTG_EP_TYPE_CONTROL; + + switch (qtd->control_phase) { + case DWC_OTG_CONTROL_SETUP: + hc->do_ping = 0; + hc->ep_is_in = 0; + hc->data_pid_start = DWC_OTG_HC_PID_SETUP; + + if (hcd->core_if->dma_enable) + hc->xfer_buff = (u8 *) (u32) urb->setup_dma; + else + hc->xfer_buff = (u8 *) urb->setup_packet; + + hc->xfer_len = 8; + break; + case DWC_OTG_CONTROL_DATA: + hc->data_pid_start = qtd->data_toggle; + break; + case DWC_OTG_CONTROL_STATUS: + /* + * Direction is opposite of data direction or IN if no + * data. + */ + if (urb->transfer_buffer_length == 0) + hc->ep_is_in = 1; + else + hc->ep_is_in = (usb_pipein(urb->pipe) != + USB_DIR_IN); + + if (hc->ep_is_in) + hc->do_ping = 0; + + hc->data_pid_start = DWC_OTG_HC_PID_DATA1; + hc->xfer_len = 0; + if (hcd->core_if->dma_enable) + hc->xfer_buff = + (u8 *) (u32) hcd->status_buf_dma; + else + hc->xfer_buff = (u8 *) hcd->status_buf; + break; + } + break; + case PIPE_BULK: + hc->ep_type = DWC_OTG_EP_TYPE_BULK; + break; + case PIPE_INTERRUPT: + hc->ep_type = DWC_OTG_EP_TYPE_INTR; + break; + case PIPE_ISOCHRONOUS: + frame_desc = &urb->iso_frame_desc[qtd->isoc_frame_index]; + hc->ep_type = DWC_OTG_EP_TYPE_ISOC; + + if (hcd->core_if->dma_enable) + hc->xfer_buff = (u8 *) (u32) urb->transfer_dma; + else + hc->xfer_buff = (u8 *) urb->transfer_buffer; + + hc->xfer_buff += frame_desc->offset + qtd->isoc_split_offset; + hc->xfer_len = frame_desc->length - qtd->isoc_split_offset; + + if (hc->xact_pos == DWC_HCSPLIT_XACTPOS_ALL) { + if (hc->xfer_len <= 188) + hc->xact_pos = DWC_HCSPLIT_XACTPOS_ALL; + else + hc->xact_pos = DWC_HCSPLIT_XACTPOS_BEGIN; + } + break; + } + + if (hc->ep_type == DWC_OTG_EP_TYPE_INTR || + hc->ep_type == DWC_OTG_EP_TYPE_ISOC) + /* + * This value may be modified when the transfer is started to + * reflect the actual transfer length. + */ + hc->multi_count = dwc_hb_mult(qh->maxp); + + dwc_otg_hc_init(hcd->core_if, hc); + hc->qh = qh; +} + +/** + * This function selects transactions from the HCD transfer schedule and + * assigns them to available host channels. It is called from HCD interrupt + * handler functions. + */ +enum dwc_transaction_type dwc_otg_hcd_select_transactions(struct dwc_hcd *hcd) +{ + struct list_head *qh_ptr; + struct dwc_qh *qh; + int num_channels; + enum dwc_transaction_type ret_val = DWC_OTG_TRANSACTION_NONE; + + /* Process entries in the periodic ready list. */ + num_channels = hcd->core_if->core_params->host_channels; + qh_ptr = hcd->periodic_sched_ready.next; + while (qh_ptr != &hcd->periodic_sched_ready && + !list_empty(&hcd->free_hc_list)) { + /* Leave one channel for non periodic transactions. */ + if (hcd->available_host_channels <= 1) + break; + hcd->available_host_channels--; + qh = list_entry(qh_ptr, struct dwc_qh, qh_list_entry); + assign_and_init_hc(hcd, qh); + /* + * Move the QH from the periodic ready schedule to the + * periodic assigned schedule. + */ + qh_ptr = qh_ptr->next; + list_move(&qh->qh_list_entry, &hcd->periodic_sched_assigned); + ret_val = DWC_OTG_TRANSACTION_PERIODIC; + } + + /* + * Process entries in the deferred portion of the non-periodic list. + * A NAK put them here and, at the right time, they need to be + * placed on the sched_inactive list. + */ + qh_ptr = hcd->non_periodic_sched_deferred.next; + while (qh_ptr != &hcd->non_periodic_sched_deferred) { + u16 frame_number = + dwc_otg_hcd_get_frame_number(dwc_otg_hcd_to_hcd(hcd)); + qh = list_entry(qh_ptr, struct dwc_qh, qh_list_entry); + qh_ptr = qh_ptr->next; + + if (dwc_frame_num_le(qh->sched_frame, frame_number)) + /* + * Move the QH from the non periodic deferred schedule + * to the non periodic inactive schedule. + */ + list_move(&qh->qh_list_entry, + &hcd->non_periodic_sched_inactive); + } + + /* + * Process entries in the inactive portion of the non-periodic + * schedule. Some free host channels may not be used if they are + * reserved for periodic transfers. + */ + qh_ptr = hcd->non_periodic_sched_inactive.next; + num_channels = hcd->core_if->core_params->host_channels; + + while (qh_ptr != &hcd->non_periodic_sched_inactive + && !list_empty(&hcd->free_hc_list)) { + if (hcd->available_host_channels < 1) + break; + hcd->available_host_channels--; + qh = list_entry(qh_ptr, struct dwc_qh, qh_list_entry); + assign_and_init_hc(hcd, qh); + /* + * Move the QH from the non-periodic inactive schedule to the + * non-periodic active schedule. + */ + qh_ptr = qh_ptr->next; + list_move(&qh->qh_list_entry, &hcd->non_periodic_sched_active); + if (ret_val == DWC_OTG_TRANSACTION_NONE) + ret_val = DWC_OTG_TRANSACTION_NON_PERIODIC; + else + ret_val = DWC_OTG_TRANSACTION_ALL; + + } + return ret_val; +} + +/** + * Sets the channel property that indicates in which frame a periodic transfer + * should occur. This is always set to the _next_ frame. This function has no + * effect on non-periodic transfers. + */ +static inline void hc_set_even_odd_frame(struct core_if *core_if, + struct dwc_hc *hc, u32 * hcchar) +{ + if (hc->ep_type == DWC_OTG_EP_TYPE_INTR || + hc->ep_type == DWC_OTG_EP_TYPE_ISOC) { + u32 hfnum = 0; + + hfnum = dwc_reg_read(core_if->host_if->host_global_regs, + DWC_HFNUM); + + /* 1 if _next_ frame is odd, 0 if it's even */ + *hcchar = DWC_HCCHAR_ODD_FRAME_RW(*hcchar, + ((DWC_HFNUM_FRNUM_RD(hfnum) & + 0x1) ? 0 : 1)); + } +} + +static void set_initial_xfer_pid(struct dwc_hc *hc) +{ + if (hc->speed == DWC_OTG_EP_SPEED_HIGH) { + if (hc->ep_is_in) { + if (hc->multi_count == 1) + hc->data_pid_start = DWC_OTG_HC_PID_DATA0; + else if (hc->multi_count == 2) + hc->data_pid_start = DWC_OTG_HC_PID_DATA1; + else + hc->data_pid_start = DWC_OTG_HC_PID_DATA2; + } else { + if (hc->multi_count == 1) + hc->data_pid_start = DWC_OTG_HC_PID_DATA0; + else + hc->data_pid_start = DWC_OTG_HC_PID_MDATA; + } + } else { + hc->data_pid_start = DWC_OTG_HC_PID_DATA0; + } +} + +/** + * Starts a PING transfer. This function should only be called in Slave mode. + * The Do Ping bit is set in the HCTSIZ register, then the channel is enabled. + */ +static void dwc_otg_hc_do_ping(struct core_if *core_if, struct dwc_hc *hc) +{ + u32 hcchar; + u32 hctsiz = 0; + + ulong hc_regs = core_if->host_if->hc_regs[hc->hc_num]; + + hctsiz = 0; + hctsiz = DWC_HCTSIZ_DO_PING_PROTO_RW(hctsiz, 1); + hctsiz = DWC_HCTSIZ_PKT_CNT_RW(hctsiz, 1); + dwc_reg_write(hc_regs, DWC_HCTSIZ, hctsiz); + + hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR); + hcchar = DWC_HCCHAR_ENA_RW(hcchar, 1); + hcchar = DWC_HCCHAR_DIS_RW(hcchar, 0); + dwc_reg_write(hc_regs, DWC_HCCHAR, hcchar); +} + +/** + * This function writes a packet into the Tx FIFO associated with the Host + * Channel. For a channel associated with a non-periodic EP, the non-periodic + * Tx FIFO is written. For a channel associated with a periodic EP, the + * periodic Tx FIFO is written. This function should only be called in Slave + * mode. + * + * Upon return the xfer_buff and xfer_count fields in hc are incremented by + * then number of bytes written to the Tx FIFO. + */ +static void dwc_otg_hc_write_packet(struct core_if *core_if, struct dwc_hc *hc) +{ + u32 i; + u32 remaining_count; + u32 byte_count; + u32 dword_count; + u32 *data_buff = (u32 *) (hc->xfer_buff); + u32 data_fifo = core_if->data_fifo[hc->hc_num]; + + remaining_count = hc->xfer_len - hc->xfer_count; + if (remaining_count > hc->max_packet) + byte_count = hc->max_packet; + else + byte_count = remaining_count; + + dword_count = (byte_count + 3) / 4; + + if (((unsigned long)data_buff) & 0x3) + /* xfer_buff is not DWORD aligned. */ + for (i = 0; i < dword_count; i++, data_buff++) + dwc_write_fifo32(data_fifo, + get_unaligned(data_buff)); + else + /* xfer_buff is DWORD aligned. */ + for (i = 0; i < dword_count; i++, data_buff++) + dwc_write_fifo32(data_fifo, *data_buff); + + hc->xfer_count += byte_count; + hc->xfer_buff += byte_count; +} + +/** + * This function does the setup for a data transfer for a host channel and + * starts the transfer. May be called in either Slave mode or DMA mode. In + * Slave mode, the caller must ensure that there is sufficient space in the + * request queue and Tx Data FIFO. + * + * For an OUT transfer in Slave mode, it loads a data packet into the + * appropriate FIFO. If necessary, additional data packets will be loaded in + * the Host ISR. + * + * For an IN transfer in Slave mode, a data packet is requested. The data + * packets are unloaded from the Rx FIFO in the Host ISR. If necessary, + * additional data packets are requested in the Host ISR. + * + * For a PING transfer in Slave mode, the Do Ping bit is set in the HCTSIZ + * register along with a packet count of 1 and the channel is enabled. This + * causes a single PING transaction to occur. Other fields in HCTSIZ are + * simply set to 0 since no data transfer occurs in this case. + * + * For a PING transfer in DMA mode, the HCTSIZ register is initialized with + * all the information required to perform the subsequent data transfer. In + * addition, the Do Ping bit is set in the HCTSIZ register. In this case, the + * controller performs the entire PING protocol, then starts the data + * transfer. + */ +static void dwc_otg_hc_start_transfer(struct core_if *core_if, + struct dwc_hc *hc) +{ + u32 hcchar; + u32 hctsiz = 0; + u16 num_packets; + u32 max_hc_xfer_size = core_if->core_params->max_transfer_size; + u16 max_hc_pkt_count = core_if->core_params->max_packet_count; + ulong hc_regs = core_if->host_if->hc_regs[hc->hc_num]; + hctsiz = 0; + + if (hc->do_ping) { + if (!core_if->dma_enable) { + dwc_otg_hc_do_ping(core_if, hc); + hc->xfer_started = 1; + return; + } else { + hctsiz = DWC_HCTSIZ_DO_PING_PROTO_RW(hctsiz, 1); + } + } + + if (hc->do_split) { + num_packets = 1; + + if (hc->complete_split && !hc->ep_is_in) + /* + * For CSPLIT OUT Transfer, set the size to 0 so the + * core doesn't expect any data written to the FIFO + */ + hc->xfer_len = 0; + else if (hc->ep_is_in || (hc->xfer_len > hc->max_packet)) + hc->xfer_len = hc->max_packet; + else if (!hc->ep_is_in && (hc->xfer_len > 188)) + hc->xfer_len = 188; + + hctsiz = DWC_HCTSIZ_XFER_SIZE_RW(hctsiz, hc->xfer_len); + } else { + /* + * Ensure that the transfer length and packet count will fit + * in the widths allocated for them in the HCTSIZn register. + */ + if (hc->ep_type == DWC_OTG_EP_TYPE_INTR || + hc->ep_type == DWC_OTG_EP_TYPE_ISOC) { + u32 max_len = hc->multi_count * hc->max_packet; + + /* + * Make sure the transfer size is no larger than one + * (micro)frame's worth of data. (A check was done + * when the periodic transfer was accepted to ensure + * that a (micro)frame's worth of data can be + * programmed into a channel.) + */ + if (hc->xfer_len > max_len) + hc->xfer_len = max_len; + } else if (hc->xfer_len > max_hc_xfer_size) { + /* + * Make sure that xfer_len is a multiple of max packet + * size. + */ + hc->xfer_len = max_hc_xfer_size - hc->max_packet + 1; + } + if (hc->xfer_len > 0) { + num_packets = (hc->xfer_len + hc->max_packet - 1) / + hc->max_packet; + if (num_packets > max_hc_pkt_count) { + num_packets = max_hc_pkt_count; + hc->xfer_len = num_packets * hc->max_packet; + } + } else { + /* Need 1 packet for transfer length of 0. */ + num_packets = 1; + } + + if (hc->ep_is_in) + /* + * Always program an integral # of max packets for IN + * transfers. + */ + hc->xfer_len = num_packets * hc->max_packet; + + if (hc->ep_type == DWC_OTG_EP_TYPE_INTR || + hc->ep_type == DWC_OTG_EP_TYPE_ISOC) + /* + * Make sure that the multi_count field matches the + * actual transfer length. + */ + hc->multi_count = num_packets; + + /* Set up the initial PID for the transfer. */ + if (hc->ep_type == DWC_OTG_EP_TYPE_ISOC) + set_initial_xfer_pid(hc); + + hctsiz = DWC_HCTSIZ_XFER_SIZE_RW(hctsiz, hc->xfer_len); + } + + hc->start_pkt_count = num_packets; + hctsiz = DWC_HCTSIZ_PKT_CNT_RW(hctsiz, num_packets); + hctsiz = DWC_HCTSIZ_PKT_PID_RW(hctsiz, hc->data_pid_start); + dwc_reg_write(hc_regs, DWC_HCTSIZ, hctsiz); + + if (core_if->dma_enable) + dwc_reg_write(hc_regs, DWC_HCDMA, (u32) hc->xfer_buff); + + /* Start the split */ + if (hc->do_split) { + u32 hcsplt; + + hcsplt = dwc_reg_read(hc_regs, DWC_HCSPLT); + hcsplt = DWC_HCSPLT_COMP_SPLT_RW(hcsplt, 1); + dwc_reg_write(hc_regs, DWC_HCSPLT, hcsplt); + } + + hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR); + hcchar = DWC_HCCHAR_MULTI_CNT_RW(hcchar, hc->multi_count); + hc_set_even_odd_frame(core_if, hc, &hcchar); + + /* Set host channel enable after all other setup is complete. */ + hcchar = DWC_HCCHAR_ENA_RW(hcchar, 1); + hcchar = DWC_HCCHAR_DIS_RW(hcchar, 0); + dwc_reg_write(hc_regs, DWC_HCCHAR, hcchar); + + hc->xfer_started = 1; + hc->requests++; + if (!core_if->dma_enable && !hc->ep_is_in && hc->xfer_len > 0) + /* Load OUT packet into the appropriate Tx FIFO. */ + dwc_otg_hc_write_packet(core_if, hc); +} + +/** + * This function continues a data transfer that was started by previous call + * to dwc_otg_hc_start_transfer</code>. The caller must ensure there is + * sufficient space in the request queue and Tx Data FIFO. This function + * should only be called in Slave mode. In DMA mode, the controller acts + * autonomously to complete transfers programmed to a host channel. + * + * For an OUT transfer, a new data packet is loaded into the appropriate FIFO + * if there is any data remaining to be queued. For an IN transfer, another + * data packet is always requested. For the SETUP phase of a control transfer, + * this function does nothing. + */ +static int dwc_otg_hc_continue_transfer(struct core_if *core_if, + struct dwc_hc *hc) +{ + if (hc->do_split) { + /* SPLITs always queue just once per channel */ + return 0; + } else if (hc->data_pid_start == DWC_OTG_HC_PID_SETUP) { + /* SETUPs are queued only once since they can't be NAKed. */ + return 0; + } else if (hc->ep_is_in) { + /* + * Always queue another request for other IN transfers. If + * back-to-back INs are issued and NAKs are received for both, + * the driver may still be processing the first NAK when the + * second NAK is received. When the interrupt handler clears + * the NAK interrupt for the first NAK, the second NAK will + * not be seen. So we can't depend on the NAK interrupt + * handler to requeue a NAKed request. Instead, IN requests + * are issued each time this function is called. When the + * transfer completes, the extra requests for the channel will + * be flushed. + */ + u32 hcchar; + ulong hc_regs = core_if->host_if->hc_regs[hc->hc_num]; + + hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR); + hc_set_even_odd_frame(core_if, hc, &hcchar); + + hcchar = DWC_HCCHAR_ENA_RW(hcchar, 1); + hcchar = DWC_HCCHAR_DIS_RW(hcchar, 0); + dwc_reg_write(hc_regs, DWC_HCCHAR, hcchar); + + hc->requests++; + return 1; + } else { + /* OUT transfers. */ + if (hc->xfer_count < hc->xfer_len) { + if (hc->ep_type == DWC_OTG_EP_TYPE_INTR || + hc->ep_type == DWC_OTG_EP_TYPE_ISOC) { + u32 hcchar; + u32 hc_regs; + + hc_regs = + core_if->host_if->hc_regs[hc->hc_num]; + hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR); + hc_set_even_odd_frame(core_if, hc, &hcchar); + } + + /* Load OUT packet into the appropriate Tx FIFO. */ + dwc_otg_hc_write_packet(core_if, hc); + hc->requests++; + return 1; + } else { + return 0; + } + } +} + +/** + * This function writes a packet into the Tx FIFO associated with the Host + * Channel. For a channel associated with a non-periodic EP, the non-periodic + * Tx FIFO is written. For a channel associated with a periodic EP, the + * periodic Tx FIFO is written. This function should only be called in Slave + * mode. + * + * Upon return the xfer_buff and xfer_count fields in hc are incremented by + * then number of bytes written to the Tx FIFO. + */ + +/** + * Attempts to queue a single transaction request for a host channel + * associated with either a periodic or non-periodic transfer. This function + * assumes that there is space available in the appropriate request queue. For + * an OUT transfer or SETUP transaction in Slave mode, it checks whether space + * is available in the appropriate Tx FIFO. + */ +static int queue_transaction(struct dwc_hcd *hcd, struct dwc_hc *hc, + u16 _fifo_dwords_avail) +{ + int retval; + + if (hcd->core_if->dma_enable) { + if (!hc->xfer_started) { + dwc_otg_hc_start_transfer(hcd->core_if, hc); + hc->qh->ping_state = 0; + } + retval = 0; + } else if (hc->halt_pending) { + /* Don't queue a request if the channel has been halted. */ + retval = 0; + } else if (hc->halt_on_queue) { + dwc_otg_hc_halt(hcd->core_if, hc, hc->halt_status); + retval = 0; + } else if (hc->do_ping) { + if (!hc->xfer_started) + dwc_otg_hc_start_transfer(hcd->core_if, hc); + retval = 0; + } else if (!hc->ep_is_in || hc->data_pid_start == + DWC_OTG_HC_PID_SETUP) { + if ((_fifo_dwords_avail * 4) >= hc->max_packet) { + if (!hc->xfer_started) { + dwc_otg_hc_start_transfer(hcd->core_if, hc); + retval = 1; + } else { + retval = + dwc_otg_hc_continue_transfer(hcd->core_if, + hc); + } + } else { + retval = -1; + } + } else { + if (!hc->xfer_started) { + dwc_otg_hc_start_transfer(hcd->core_if, hc); + retval = 1; + } else { + retval = dwc_otg_hc_continue_transfer(hcd->core_if, hc); + } + } + return retval; +} + +/** + * Processes active non-periodic channels and queues transactions for these + * channels to the DWC_otg controller. After queueing transactions, the NP Tx + * FIFO Empty interrupt is enabled if there are more transactions to queue as + * NP Tx FIFO or request queue space becomes available. Otherwise, the NP Tx + * FIFO Empty interrupt is disabled. + */ +static void process_non_periodic_channels(struct dwc_hcd *hcd) +{ + u32 tx_status = 0; + struct list_head *orig_qh_ptr; + struct dwc_qh *qh; + int status; + int no_queue_space = 0; + int no_fifo_space = 0; + int more_to_do = 0; + ulong regs = hcd->core_if->core_global_regs; + + /* + * Keep track of the starting point. Skip over the start-of-list + * entry. + */ + if (hcd->non_periodic_qh_ptr == &hcd->non_periodic_sched_active) + hcd->non_periodic_qh_ptr = hcd->non_periodic_qh_ptr->next; + orig_qh_ptr = hcd->non_periodic_qh_ptr; + + /* + * Process once through the active list or until no more space is + * available in the request queue or the Tx FIFO. + */ + do { + tx_status = dwc_reg_read(regs, DWC_GNPTXSTS); + if (!hcd->core_if->dma_enable && + DWC_GNPTXSTS_NPTXQSPCAVAIL_RD(tx_status) == 0) { + no_queue_space = 1; + break; + } + + qh = list_entry(hcd->non_periodic_qh_ptr, struct dwc_qh, + qh_list_entry); + status = queue_transaction(hcd, qh->channel, + DWC_GNPTXSTS_NPTXFSPCAVAIL_RD + (tx_status)); + + if (status > 0) { + more_to_do = 1; + } else if (status < 0) { + no_fifo_space = 1; + break; + } + + /* Advance to next QH, skipping start-of-list entry. */ + hcd->non_periodic_qh_ptr = hcd->non_periodic_qh_ptr->next; + if (hcd->non_periodic_qh_ptr == &hcd->non_periodic_sched_active) + hcd->non_periodic_qh_ptr = + hcd->non_periodic_qh_ptr->next; + } while (hcd->non_periodic_qh_ptr != orig_qh_ptr); + + if (!hcd->core_if->dma_enable) { + u32 intr_mask = 0; + + intr_mask |= DWC_INTMSK_NP_TXFIFO_EMPT; + if (more_to_do || no_queue_space || no_fifo_space) { + /* + * May need to queue more transactions as the request + * queue or Tx FIFO empties. Enable the non-periodic + * Tx FIFO empty interrupt. (Always use the half-empty + * level to ensure that new requests are loaded as + * soon as possible.) + */ + dwc_reg_modify(gintmsk_reg(hcd), 0, 0, intr_mask); + } else { + /* + * Disable the Tx FIFO empty interrupt since there are + * no more transactions that need to be queued right + * now. This function is called from interrupt + * handlers to queue more transactions as transfer + * states change. + */ + dwc_reg_modify(gintmsk_reg(hcd), 0, intr_mask, 0); + } + } +} + +/** + * Processes periodic channels for the next frame and queues transactions for + * these channels to the DWC_otg controller. After queueing transactions, the + * Periodic Tx FIFO Empty interrupt is enabled if there are more transactions + * to queue as Periodic Tx FIFO or request queue space becomes available. + * Otherwise, the Periodic Tx FIFO Empty interrupt is disabled. + */ +static void process_periodic_channels(struct dwc_hcd *hcd) +{ + u32 tx_status = 0; + struct list_head *qh_ptr; + struct dwc_qh *qh; + int status; + int no_queue_space = 0; + int no_fifo_space = 0; + ulong host_regs; + + host_regs = hcd->core_if->host_if->host_global_regs; + + qh_ptr = hcd->periodic_sched_assigned.next; + while (qh_ptr != &hcd->periodic_sched_assigned) { + tx_status = dwc_reg_read(host_regs, DWC_HPTXSTS); + if (DWC_HPTXSTS_PTXSPC_AVAIL_RD(tx_status) == 0) { + no_queue_space = 1; + break; + } + + qh = list_entry(qh_ptr, struct dwc_qh, qh_list_entry); + + /* + * Set a flag if we're queuing high-bandwidth in slave mode. + * The flag prevents any halts to get into the request queue in + * the middle of multiple high-bandwidth packets getting queued. + */ + if (!hcd->core_if->dma_enable && qh->channel->multi_count > 1) + hcd->core_if->queuing_high_bandwidth = 1; + + status = queue_transaction(hcd, qh->channel, + DWC_HPTXSTS_PTXFSPC_AVAIL_RD + (tx_status)); + if (status < 0) { + no_fifo_space = 1; + break; + } + + /* + * In Slave mode, stay on the current transfer until there is + * nothing more to do or the high-bandwidth request count is + * reached. In DMA mode, only need to queue one request. The + * controller automatically handles multiple packets for + * high-bandwidth transfers. + */ + if (hcd->core_if->dma_enable || (status == 0 || + qh->channel->requests == + qh->channel->multi_count)) { + qh_ptr = qh_ptr->next; + + /* + * Move the QH from the periodic assigned schedule to + * the periodic queued schedule. + */ + list_move(&qh->qh_list_entry, + &hcd->periodic_sched_queued); + + /* done queuing high bandwidth */ + hcd->core_if->queuing_high_bandwidth = 0; + } + } + + if (!hcd->core_if->dma_enable) { + u32 intr_mask = 0; + + intr_mask |= DWC_INTMSK_NP_TXFIFO_EMPT; + + if (!list_empty(&hcd->periodic_sched_assigned) || + no_queue_space || no_fifo_space) + /* + * May need to queue more transactions as the request + * queue or Tx FIFO empties. Enable the periodic Tx + * FIFO empty interrupt. (Always use the half-empty + * level to ensure that new requests are loaded as + * soon as possible.) + */ + dwc_reg_modify(gintmsk_reg(hcd), 0, 0, intr_mask); + else + /* + * Disable the Tx FIFO empty interrupt since there are + * no more transactions that need to be queued right + * now. This function is called from interrupt + * handlers to queue more transactions as transfer + * states change. + */ + dwc_reg_modify(gintmsk_reg(hcd), 0, intr_mask, 0); + } +} + +/** + * This function processes the currently active host channels and queues + * transactions for these channels to the DWC_otg controller. It is called + * from HCD interrupt handler functions. + */ +void dwc_otg_hcd_queue_transactions(struct dwc_hcd *hcd, + enum dwc_transaction_type tr_type) +{ + /* Process host channels associated with periodic transfers. */ + if ((tr_type == DWC_OTG_TRANSACTION_PERIODIC || + tr_type == DWC_OTG_TRANSACTION_ALL) && + !list_empty(&hcd->periodic_sched_assigned)) + process_periodic_channels(hcd); + + /* Process host channels associated with non-periodic transfers. */ + if (tr_type == DWC_OTG_TRANSACTION_NON_PERIODIC || + tr_type == DWC_OTG_TRANSACTION_ALL) { + if (!list_empty(&hcd->non_periodic_sched_active)) { + process_non_periodic_channels(hcd); + } else { + /* + * Ensure NP Tx FIFO empty interrupt is disabled when + * there are no non-periodic transfers to process. + */ + u32 gintmsk = 0; + gintmsk |= DWC_INTMSK_NP_TXFIFO_EMPT; + dwc_reg_modify(gintmsk_reg(hcd), 0, gintmsk, 0); + } + } +} + +/** + * Sets the final status of an URB and returns it to the device driver. Any + * required cleanup of the URB is performed. + */ +void dwc_otg_hcd_complete_urb(struct dwc_hcd *hcd, struct urb *urb, int status) +__releases(hcd->lock) __acquires(hcd->lock) +{ + urb->hcpriv = NULL; + usb_hcd_unlink_urb_from_ep(dwc_otg_hcd_to_hcd(hcd), urb); + + spin_unlock(&hcd->lock); + usb_hcd_giveback_urb(dwc_otg_hcd_to_hcd(hcd), urb, status); + spin_lock(&hcd->lock); +} diff --git a/drivers/usb/dwc/hcd.h b/drivers/usb/dwc/hcd.h new file mode 100644 index 0000000..2d25abc --- /dev/null +++ b/drivers/usb/dwc/hcd.h @@ -0,0 +1,416 @@ +/* + * DesignWare HS OTG controller driver + * Copyright (C) 2006 Synopsys, Inc. + * Portions Copyright (C) 2010 Applied Micro Circuits Corporation. + * + * This program 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. + * + * 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 version 2 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see http://www.gnu.org/licenses + * or write to the Free Software Foundation, Inc., 51 Franklin Street, + * Suite 500, Boston, MA 02110-1335 USA. + * + * Based on Synopsys driver version 2.60a + * Modified by Mark Miesfeld <mmiesfeld@apm.com> + * Modified by Stefan Roese <sr@denx.de>, DENX Software Engineering + * Modified by Chuck Meade <chuck@theptrgroup.com> + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL SYNOPSYS, INC. BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES + * (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#if !defined(__DWC_HCD_H__) +#define __DWC_HCD_H__ + +#include <linux/usb.h> +#include <linux/usb/hcd.h> + +#include "driver.h" + +/* + * This file contains the structures, constants, and interfaces for + * the Host Contoller Driver (HCD). + * + * The Host Controller Driver (HCD) is responsible for translating requests + * from the USB Driver into the appropriate actions on the DWC_otg controller. + * It isolates the USBD from the specifics of the controller by providing an + * API to the USBD. + */ + +/* Phases for control transfers. */ +enum dwc_control_phase { + DWC_OTG_CONTROL_SETUP, + DWC_OTG_CONTROL_DATA, + DWC_OTG_CONTROL_STATUS +}; + +/* Transaction types. */ +enum dwc_transaction_type { + DWC_OTG_TRANSACTION_NONE, + DWC_OTG_TRANSACTION_PERIODIC, + DWC_OTG_TRANSACTION_NON_PERIODIC, + DWC_OTG_TRANSACTION_ALL +}; + +/* + * A Queue Transfer Descriptor (QTD) holds the state of a bulk, control, + * interrupt, or isochronous transfer. A single QTD is created for each URB + * (of one of these types) submitted to the HCD. The transfer associated with + * a QTD may require one or multiple transactions. + * + * A QTD is linked to a Queue Head, which is entered in either the + * non-periodic or periodic schedule for execution. When a QTD is chosen for + * execution, some or all of its transactions may be executed. After + * execution, the state of the QTD is updated. The QTD may be retired if all + * its transactions are complete or if an error occurred. Otherwise, it + * remains in the schedule so more transactions can be executed later. + */ +struct dwc_qtd { + /* + * Determines the PID of the next data packet for the data phase of + * control transfers. Ignored for other transfer types. + * One of the following values: + * - DWC_OTG_HC_PID_DATA0 + * - DWC_OTG_HC_PID_DATA1 + */ + u8 data_toggle; + + /* Current phase for control transfers (Setup, Data, or Status). */ + enum dwc_control_phase control_phase; + + /* + * Keep track of the current split type + * for FS/LS endpoints on a HS Hub + */ + u8 complete_split; + + /* How many bytes transferred during SSPLIT OUT */ + u32 ssplit_out_xfer_count; + + /* + * Holds the number of bus errors that have occurred for a transaction + * within this transfer. + */ + u8 error_count; + + /* + * Index of the next frame descriptor for an isochronous transfer. A + * frame descriptor describes the buffer position and length of the + * data to be transferred in the next scheduled (micro)frame of an + * isochronous transfer. It also holds status for that transaction. + * The frame index starts at 0. + */ + int isoc_frame_index; + + /* Position of the ISOC split on full/low speed */ + u8 isoc_split_pos; + + /* Position of the ISOC split in the buffer for the current frame */ + u16 isoc_split_offset; + + /* URB for this transfer */ + struct urb *urb; + + /* This list of QTDs */ + struct list_head qtd_list_entry; + + /* Field to track the qh pointer */ + struct dwc_qh *qtd_qh_ptr; +}; + +/* + * A Queue Head (QH) holds the static characteristics of an endpoint and + * maintains a list of transfers (QTDs) for that endpoint. A QH structure may + * be entered in either the non-periodic or periodic schedule. + */ +struct dwc_qh { + /* + * Endpoint type. + * One of the following values: + * - USB_ENDPOINT_XFER_CONTROL + * - USB_ENDPOINT_XFER_ISOC + * - USB_ENDPOINT_XFER_BULK + * - USB_ENDPOINT_XFER_INT + */ + u8 ep_type; + u8 ep_is_in; + + /* wMaxPacketSize Field of Endpoint Descriptor. */ + u16 maxp; + + /* + * Determines the PID of the next data packet for non-control + * transfers. Ignored for control transfers. + * One of the following values: + * - DWC_OTG_HC_PID_DATA0 + * - DWC_OTG_HC_PID_DATA1 + */ + u8 data_toggle; + + /* Ping state if 1. */ + u8 ping_state; + + /* List of QTDs for this QH. */ + struct list_head qtd_list; + + /* Host channel currently processing transfers for this QH. */ + struct dwc_hc *channel; + + /* QTD currently assigned to a host channel for this QH. */ + struct dwc_qtd *qtd_in_process; + + /* Full/low speed endpoint on high-speed hub requires split. */ + u8 do_split; + + /* Periodic schedule information */ + + /* Bandwidth in microseconds per (micro)frame. */ + u8 usecs; + + /* Interval between transfers in (micro)frames. */ + u16 interval; + + /* + * (micro)frame to initialize a periodic transfer. The transfer + * executes in the following (micro)frame. + */ + u16 sched_frame; + + /* (micro)frame at which last start split was initialized. */ + u16 start_split_frame; + + u16 speed; + u16 frame_usecs[8]; + + /* Entry for QH in either the periodic or non-periodic schedule. */ + struct list_head qh_list_entry; +}; + +/* Gets the struct usb_hcd that contains a struct dwc_hcd. */ +static inline struct usb_hcd *dwc_otg_hcd_to_hcd(struct dwc_hcd *dwc_hcd) +{ + return container_of((void *)dwc_hcd, struct usb_hcd, hcd_priv); +} + +/* HCD Create/Destroy Functions */ +extern int dwc_otg_hcd_init(struct device *_dev, + struct dwc_otg_device *dwc_dev); +extern void dwc_otg_hcd_remove(struct device *_dev); + +/* + * The following functions support managing the DWC_otg controller in host + * mode. + */ +extern int dwc_otg_hcd_get_frame_number(struct usb_hcd *hcd); +extern void dwc_otg_hc_cleanup(struct core_if *core_if, struct dwc_hc *hc); +extern void dwc_otg_hc_halt(struct core_if *core_if, struct dwc_hc *hc, + enum dwc_halt_status _halt_status); + +/* Transaction Execution Functions */ +extern enum dwc_transaction_type dwc_otg_hcd_select_transactions(struct dwc_hcd + *hcd); +extern void dwc_otg_hcd_queue_transactions(struct dwc_hcd *hcd, + enum dwc_transaction_type tr_type); +extern void dwc_otg_hcd_complete_urb(struct dwc_hcd *_hcd, struct urb *urb, + int status); + +/* Interrupt Handler Functions */ +extern int dwc_otg_hcd_handle_intr(struct dwc_hcd *hcd); + +/* Schedule Queue Functions */ +extern int init_hcd_usecs(struct dwc_hcd *hcd); +extern void dwc_otg_hcd_qh_free(struct dwc_qh *qh); +extern void dwc_otg_hcd_qh_remove(struct dwc_hcd *hcd, struct dwc_qh *qh); +extern void dwc_otg_hcd_qh_deactivate(struct dwc_hcd *hcd, struct dwc_qh *qh, + int sched_csplit); +extern int dwc_otg_hcd_qh_deferr(struct dwc_hcd *hcd, struct dwc_qh *qh, + int delay); +extern struct dwc_qtd *dwc_otg_hcd_qtd_create(struct urb *urb, + gfp_t _mem_flags); +extern int dwc_otg_hcd_qtd_add(struct dwc_qtd *qtd, struct dwc_hcd *dwc_hcd); + +/* + * Frees the memory for a QTD structure. QTD should already be removed from + * list. + */ +static inline void dwc_otg_hcd_qtd_free(struct dwc_qtd *_qtd) +{ + kfree(_qtd); +} + +/* Removes a QTD from list. */ +static inline void dwc_otg_hcd_qtd_remove(struct dwc_qtd *_qtd) +{ + list_del(&_qtd->qtd_list_entry); +} + +/* Remove and free a QTD */ +static inline void dwc_otg_hcd_qtd_remove_and_free(struct dwc_qtd *_qtd) +{ + dwc_otg_hcd_qtd_remove(_qtd); + dwc_otg_hcd_qtd_free(_qtd); +} + +struct dwc_qh *dwc_urb_to_qh(struct urb *_urb); + +/* Gets the usb_host_endpoint associated with an URB. */ +static inline struct usb_host_endpoint *dwc_urb_to_endpoint(struct urb *_urb) +{ + struct usb_device *dev = _urb->dev; + int ep_num = usb_pipeendpoint(_urb->pipe); + + if (usb_pipein(_urb->pipe)) + return dev->ep_in[ep_num]; + else + return dev->ep_out[ep_num]; +} + +/* + * Gets the endpoint number from a _bEndpointAddress argument. The endpoint is + * qualified with its direction (possible 32 endpoints per device). + */ +#define dwc_ep_addr_to_endpoint(_bEndpointAddress_) \ + ((_bEndpointAddress_ & USB_ENDPOINT_NUMBER_MASK) | \ + ((_bEndpointAddress_ & USB_DIR_IN) != 0) << 4) + +/* Gets the QH that contains the list_head */ +#define dwc_list_to_qh(_list_head_ptr_) \ + (container_of(_list_head_ptr_, struct dwc_qh, qh_list_entry)) + +/* Gets the QTD that contains the list_head */ +#define dwc_list_to_qtd(_list_head_ptr_) \ + (container_of(_list_head_ptr_, struct dwc_qtd, qtd_list_entry)) + +/* Check if QH is non-periodic */ +#define dwc_qh_is_non_per(_qh_ptr_) \ + ((_qh_ptr_->ep_type == USB_ENDPOINT_XFER_BULK) || \ + (_qh_ptr_->ep_type == USB_ENDPOINT_XFER_CONTROL)) + +/* High bandwidth multiplier as encoded in highspeed endpoint descriptors */ +#define dwc_hb_mult(wMaxPacketSize) (1 + (((wMaxPacketSize) >> 11) & 0x03)) + +/* Packet size for any kind of endpoint descriptor */ +#define dwc_max_packet(wMaxPacketSize) ((wMaxPacketSize) & 0x07ff) + +/* + * Returns true if _frame1 is less than or equal to _frame2. The comparison is + * done modulo DWC_HFNUM_MAX_FRNUM. This accounts for the rollover of the + * frame number when the max frame number is reached. + */ +static inline int dwc_frame_num_le(u16 _frame1, u16 _frame2) +{ + return ((_frame2 - _frame1) & DWC_HFNUM_MAX_FRNUM) <= + (DWC_HFNUM_MAX_FRNUM >> 1); +} + +/* + * Returns true if _frame1 is greater than _frame2. The comparison is done + * modulo DWC_HFNUM_MAX_FRNUM. This accounts for the rollover of the frame + * number when the max frame number is reached. + */ +static inline int dwc_frame_num_gt(u16 _frame1, u16 _frame2) +{ + return (_frame1 != _frame2) && + (((_frame1 - _frame2) & + DWC_HFNUM_MAX_FRNUM) < (DWC_HFNUM_MAX_FRNUM >> 1)); +} + +/* + * Increments _frame by the amount specified by _inc. The addition is done + * modulo DWC_HFNUM_MAX_FRNUM. Returns the incremented value. + */ +static inline u16 dwc_frame_num_inc(u16 _frame, u16 _inc) +{ + return (_frame + _inc) & DWC_HFNUM_MAX_FRNUM; +} + +static inline u16 dwc_full_frame_num(u16 _frame) +{ + return ((_frame) & DWC_HFNUM_MAX_FRNUM) >> 3; +} + +static inline u16 dwc_micro_frame_num(u16 _frame) +{ + return (_frame) & 0x7; +} + +static inline ulong gintsts_reg(struct dwc_hcd *hcd) +{ + ulong global_regs = hcd->core_if->core_global_regs; + return global_regs + DWC_GINTSTS; +} + +static inline ulong gintmsk_reg(struct dwc_hcd *hcd) +{ + ulong global_regs = hcd->core_if->core_global_regs; + return global_regs + DWC_GINTMSK; +} + +static inline ulong gahbcfg_reg(struct dwc_hcd *hcd) +{ + ulong global_regs = hcd->core_if->core_global_regs; + return global_regs + DWC_GAHBCFG; +} + +static inline const char *pipetype_str(unsigned int pipe) +{ + switch (usb_pipetype(pipe)) { + case PIPE_CONTROL: + return "control"; + case PIPE_BULK: + return "bulk"; + case PIPE_INTERRUPT: + return "interrupt"; + case PIPE_ISOCHRONOUS: + return "isochronous"; + default: + return "unknown"; + } +} + +static inline const char *dev_speed_str(enum usb_device_speed speed) +{ + switch (speed) { + case USB_SPEED_HIGH: + return "high"; + case USB_SPEED_FULL: + return "full"; + case USB_SPEED_LOW: + return "low"; + default: + return "unknown"; + } +} + +static inline const char *ep_type_str(u8 type) +{ + switch (type) { + case USB_ENDPOINT_XFER_ISOC: + return "isochronous"; + case USB_ENDPOINT_XFER_INT: + return "interrupt"; + case USB_ENDPOINT_XFER_CONTROL: + return "control"; + case USB_ENDPOINT_XFER_BULK: + return "bulk"; + default: + return "?"; + } +} +#endif diff --git a/drivers/usb/dwc/hcd_intr.c b/drivers/usb/dwc/hcd_intr.c new file mode 100644 index 0000000..b16934d --- /dev/null +++ b/drivers/usb/dwc/hcd_intr.c @@ -0,0 +1,1477 @@ +/* + * DesignWare HS OTG controller driver + * Copyright (C) 2006 Synopsys, Inc. + * Portions Copyright (C) 2010 Applied Micro Circuits Corporation. + * + * This program 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. + * + * 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 version 2 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see http://www.gnu.org/licenses + * or write to the Free Software Foundation, Inc., 51 Franklin Street, + * Suite 500, Boston, MA 02110-1335 USA. + * + * Based on Synopsys driver version 2.60a + * Modified by Mark Miesfeld <mmiesfeld@apm.com> + * Modified by Stefan Roese <sr@denx.de>, DENX Software Engineering + * Modified by Chuck Meade <chuck@theptrgroup.com> + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL SYNOPSYS, INC. BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES + * (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "hcd.h" + +/* This file contains the implementation of the HCD Interrupt handlers. */ +static const int erratum_usb09_patched; +static const int deferral_on = 1; +static const int nak_deferral_delay = 8; +static const int nyet_deferral_delay = 1; + +/** + * Handles the start-of-frame interrupt in host mode. Non-periodic + * transactions may be queued to the DWC_otg controller for the current + * (micro)frame. Periodic transactions may be queued to the controller for the + * next (micro)frame. + */ +static int dwc_otg_hcd_handle_sof_intr(struct dwc_hcd *hcd) +{ + u32 hfnum = 0; + struct list_head *qh_entry; + struct dwc_qh *qh; + enum dwc_transaction_type tr_type; + u32 gintsts = 0; + + hfnum = + dwc_reg_read(hcd->core_if->host_if->host_global_regs, + DWC_HFNUM); + + hcd->frame_number = DWC_HFNUM_FRNUM_RD(hfnum); + + /* Determine whether any periodic QHs should be executed. */ + qh_entry = hcd->periodic_sched_inactive.next; + while (qh_entry != &hcd->periodic_sched_inactive) { + qh = list_entry(qh_entry, struct dwc_qh, qh_list_entry); + qh_entry = qh_entry->next; + + /* + * If needed, move QH to the ready list to be executed next + * (micro)frame. + */ + if (dwc_frame_num_le(qh->sched_frame, hcd->frame_number)) + list_move(&qh->qh_list_entry, + &hcd->periodic_sched_ready); + } + + tr_type = dwc_otg_hcd_select_transactions(hcd); + if (tr_type != DWC_OTG_TRANSACTION_NONE) + dwc_otg_hcd_queue_transactions(hcd, tr_type); + + /* Clear interrupt */ + gintsts |= DWC_INTMSK_STRT_OF_FRM; + dwc_reg_write(gintsts_reg(hcd), 0, gintsts); + return 1; +} + +/** + * Handles the Rx Status Queue Level Interrupt, which indicates that there is at + * least one packet in the Rx FIFO. The packets are moved from the FIFO to + * memory if the DWC_otg controller is operating in Slave mode. + */ +static int dwc_otg_hcd_handle_rx_status_q_level_intr(struct dwc_hcd *hcd) +{ + u32 grxsts; + struct dwc_hc *hc; + + grxsts = dwc_reg_read(hcd->core_if->core_global_regs, DWC_GRXSTSP); + hc = hcd->hc_ptr_array[grxsts & DWC_HM_RXSTS_CHAN_NUM_RD(grxsts)]; + + /* Packet Status */ + switch (DWC_HM_RXSTS_PKT_STS_RD(grxsts)) { + case DWC_GRXSTS_PKTSTS_IN: + /* Read the data into the host buffer. */ + if (DWC_HM_RXSTS_BYTE_CNT_RD(grxsts) > 0) { + dwc_otg_read_packet(hcd->core_if, hc->xfer_buff, + DWC_HM_RXSTS_BYTE_CNT_RD(grxsts)); + /* Update the HC fields for the next packet received. */ + hc->xfer_count += DWC_HM_RXSTS_BYTE_CNT_RD(grxsts); + hc->xfer_buff += DWC_HM_RXSTS_BYTE_CNT_RD(grxsts); + } + case DWC_GRXSTS_PKTSTS_IN_XFER_COMP: + case DWC_GRXSTS_PKTSTS_DATA_TOGGLE_ERR: + case DWC_GRXSTS_PKTSTS_CH_HALTED: + /* Handled in interrupt, just ignore data */ + break; + default: + pr_err("RX_STS_Q Interrupt: Unknown status %d\n", + DWC_HM_RXSTS_PKT_STS_RD(grxsts)); + break; + } + return 1; +} + +/** + * This interrupt occurs when the non-periodic Tx FIFO is half-empty. More + * data packets may be written to the FIFO for OUT transfers. More requests + * may be written to the non-periodic request queue for IN transfers. This + * interrupt is enabled only in Slave mode. + */ +static int dwc_otg_hcd_handle_np_tx_fifo_empty_intr(struct dwc_hcd *hcd) +{ + dwc_otg_hcd_queue_transactions(hcd, DWC_OTG_TRANSACTION_NON_PERIODIC); + return 1; +} + +/** + * This interrupt occurs when the periodic Tx FIFO is half-empty. More data + * packets may be written to the FIFO for OUT transfers. More requests may be + * written to the periodic request queue for IN transfers. This interrupt is + * enabled only in Slave mode. + */ +static int dwc_otg_hcd_handle_perio_tx_fifo_empty_intr(struct dwc_hcd *hcd) +{ + dwc_otg_hcd_queue_transactions(hcd, DWC_OTG_TRANSACTION_PERIODIC); + return 1; +} + +/** + * When the port changes to enabled it may be necessary to adjust the phy clock + * speed. + */ +static int adjusted_phy_clock_speed(struct dwc_hcd *hcd, u32 hprt0) +{ + int adjusted = 0; + u32 usbcfg; + ulong global_regs = hcd->core_if->core_global_regs; + struct core_params *params = hcd->core_if->core_params; + ulong h_regs = hcd->core_if->host_if->host_global_regs; + + usbcfg = dwc_reg_read(global_regs, DWC_GUSBCFG); + + if (DWC_HPRT0_PRT_SPD_RD(hprt0) == DWC_HPRT0_PRTSPD_LOW_SPEED || + DWC_HPRT0_PRT_SPD_RD(hprt0) == DWC_HPRT0_PRTSPD_FULL_SPEED) { + /* Low power */ + u32 hcfg; + + if (!(usbcfg & DWC_USBCFG_PHYLPWRCLKSEL)) { + /* Set PHY low power clock select for FS/LS devices */ + usbcfg |= DWC_USBCFG_PHYLPWRCLKSEL; + dwc_reg_write(global_regs, DWC_GUSBCFG, usbcfg); + adjusted = 1; + } + + hcfg = dwc_reg_read(h_regs, DWC_HCFG); + if (DWC_HPRT0_PRT_SPD_RD(hprt0) == DWC_HPRT0_PRTSPD_LOW_SPEED && + params->host_ls_low_power_phy_clk == + DWC_HOST_LS_LOW_POWER_PHY_CLK_PARAM_6MHZ) { + /* 6 MHZ, check for 6 MHZ clock select */ + if (DWC_HCFG_FSLSP_CLK_RD(hcfg) != DWC_HCFG_6_MHZ) { + hcfg = DWC_HCFG_FSLSP_CLK_RW(hcfg, + DWC_HCFG_6_MHZ); + dwc_reg_write(h_regs, DWC_HCFG, hcfg); + adjusted = 1; + } + } else if (DWC_HCFG_FSLSP_CLK_RD(hcfg) != DWC_HCFG_48_MHZ) { + /* 48 MHZ and clock select is not 48 MHZ */ + hcfg = DWC_HCFG_FSLSP_CLK_RW(hcfg, DWC_HCFG_48_MHZ); + dwc_reg_write(h_regs, DWC_HCFG, hcfg); + adjusted = 1; + } + } else if (usbcfg & DWC_USBCFG_PHYLPWRCLKSEL) { + usbcfg &= ~((u32) DWC_USBCFG_PHYLPWRCLKSEL); + dwc_reg_write(global_regs, DWC_GUSBCFG, usbcfg); + adjusted = 1; + } + if (adjusted) + schedule_work(&hcd->usb_port_reset); + + return adjusted; +} + +/** + * Helper function to handle the port enable changed interrupt when the port + * becomes enabled. Checks if we need to adjust the PHY clock speed for low + * power and adjusts it if needed. + */ +static void port_enabled(struct dwc_hcd *hcd, u32 hprt0) +{ + if (hcd->core_if->core_params->host_support_fs_ls_low_power) + if (!adjusted_phy_clock_speed(hcd, hprt0)) + hcd->flags.b.port_reset_change = 1; +} + +/** + * There are multiple conditions that can cause a port interrupt. This function + * determines which interrupt conditions have occurred and handles them + * appropriately. + */ +static int dwc_otg_hcd_handle_port_intr(struct dwc_hcd *hcd) +{ + int retval = 0; + u32 hprt0; + u32 hprt0_modify; + + hprt0 = dwc_reg_read(hcd->core_if->host_if->hprt0, 0); + hprt0_modify = dwc_reg_read(hcd->core_if->host_if->hprt0, 0); + + /* + * Clear appropriate bits in HPRT0 to clear the interrupt bit in + * GINTSTS + */ + hprt0_modify = DWC_HPRT0_PRT_ENA_RW(hprt0_modify, 0); + hprt0_modify = DWC_HPRT0_PRT_CONN_DET_RW(hprt0_modify, 0); + hprt0_modify = DWC_HPRT0_PRT_ENA_DIS_CHG_RW(hprt0_modify, 0); + hprt0_modify = DWC_HPRT0_PRT_OVRCURR_CHG_RW(hprt0_modify, 0); + + /* Port connect detected interrupt */ + if (DWC_HPRT0_PRT_CONN_DET_RD(hprt0)) { + /* Set the status flags and clear interrupt */ + hcd->flags.b.port_connect_status_change = 1; + hcd->flags.b.port_connect_status = 1; + hprt0_modify = DWC_HPRT0_PRT_CONN_DET_RW(hprt0_modify, 1); + + /* B-Device has connected, Delete the connection timer. */ + del_timer_sync(&hcd->conn_timer); + + /* + * The Hub driver asserts a reset when it sees port connect + * status change flag + */ + retval |= 1; + } + + /* Port enable changed interrupt */ + if (DWC_HPRT0_PRT_ENA_DIS_CHG_RD(hprt0)) { + /* Set the internal flag if the port was disabled */ + if (DWC_HPRT0_PRT_ENA_RD(hprt0)) + port_enabled(hcd, hprt0); + else + hcd->flags.b.port_enable_change = 1; + + /* Clear the interrupt */ + hprt0_modify = DWC_HPRT0_PRT_ENA_DIS_CHG_RW(hprt0_modify, 1); + retval |= 1; + } + + /* Overcurrent change interrupt */ + if (DWC_HPRT0_PRT_OVRCURR_CHG_RD(hprt0)) { + hcd->flags.b.port_over_current_change = 1; + hprt0_modify = DWC_HPRT0_PRT_OVRCURR_CHG_RW(hprt0_modify, 1); + retval |= 1; + } + + /* Clear the port interrupts */ + dwc_reg_write(hcd->core_if->host_if->hprt0, 0, hprt0_modify); + return retval; +} + +/** + * Gets the actual length of a transfer after the transfer halts. halt_status + * holds the reason for the halt. + * + * For IN transfers where halt_status is DWC_OTG_HC_XFER_COMPLETE, _short_read + * is set to 1 upon return if less than the requested number of bytes were + * transferred. Otherwise, _short_read is set to 0 upon return. _short_read may + * also be NULL on entry, in which case it remains unchanged. + */ +static u32 get_actual_xfer_length(struct dwc_hc *hc, ulong regs, + struct dwc_qtd *qtd, + enum dwc_halt_status halt_status, + int *_short_read) +{ + u32 hctsiz = 0; + u32 length; + + if (_short_read) + *_short_read = 0; + + hctsiz = dwc_reg_read(regs, DWC_HCTSIZ); + if (halt_status == DWC_OTG_HC_XFER_COMPLETE) { + if (hc->ep_is_in) { + length = hc->xfer_len - DWC_HCTSIZ_XFER_SIZE_RD(hctsiz); + if (_short_read) + *_short_read = + (DWC_HCTSIZ_XFER_SIZE_RD(hctsiz) != 0); + } else if (hc->qh->do_split) { + length = qtd->ssplit_out_xfer_count; + } else { + length = hc->xfer_len; + } + } else { + /* + * Must use the hctsiz.pktcnt field to determine how much data + * has been transferred. This field reflects the number of + * packets that have been transferred via the USB. This is + * always an integral number of packets if the transfer was + * halted before its normal completion. (Can't use the + * hctsiz.xfersize field because that reflects the number of + * bytes transferred via the AHB, not the USB). + */ + length = (hc->start_pkt_count - DWC_HCTSIZ_PKT_CNT_RD(hctsiz)) * + hc->max_packet; + } + return length; +} + +/** + * Updates the state of the URB after a Transfer Complete interrupt on the + * host channel. Updates the actual_length field of the URB based on the + * number of bytes transferred via the host channel. Sets the URB status + * if the data transfer is finished. + */ +static int update_urb_state_xfer_comp(struct dwc_hc *hc, + ulong regs, struct urb *urb, + struct dwc_qtd *qtd, int *status) +{ + int xfer_done = 0; + int short_read = 0; + + urb->actual_length += get_actual_xfer_length(hc, regs, qtd, + DWC_OTG_HC_XFER_COMPLETE, + &short_read); + + if (short_read || urb->actual_length == urb->transfer_buffer_length) { + xfer_done = 1; + if (short_read && (urb->transfer_flags & URB_SHORT_NOT_OK)) + *status = -EREMOTEIO; + else + *status = 0; + } + return xfer_done; +} + +/* + * Save the starting data toggle for the next transfer. The data toggle is + * saved in the QH for non-control transfers and it's saved in the QTD for + * control transfers. + */ +static void save_data_toggle(struct dwc_hc *hc, ulong regs, struct dwc_qtd *qtd) +{ + u32 hctsiz = 0; + hctsiz = dwc_reg_read(regs, DWC_HCTSIZ); + + if (hc->ep_type != DWC_OTG_EP_TYPE_CONTROL) { + struct dwc_qh *qh = hc->qh; + + if (DWC_HCTSIZ_PKT_PID_RD(hctsiz) == DWC_HCTSIZ_DATA0) + qh->data_toggle = DWC_OTG_HC_PID_DATA0; + else + qh->data_toggle = DWC_OTG_HC_PID_DATA1; + } else { + if (DWC_HCTSIZ_PKT_PID_RD(hctsiz) == DWC_HCTSIZ_DATA0) + qtd->data_toggle = DWC_OTG_HC_PID_DATA0; + else + qtd->data_toggle = DWC_OTG_HC_PID_DATA1; + } +} + +/** + * Frees the first QTD in the QH's list if free_qtd is 1. For non-periodic + * QHs, removes the QH from the active non-periodic schedule. If any QTDs are + * still linked to the QH, the QH is added to the end of the inactive + * non-periodic schedule. For periodic QHs, removes the QH from the periodic + * schedule if no more QTDs are linked to the QH. + */ +static void deactivate_qh(struct dwc_hcd *hcd, struct dwc_qh *qh, int free_qtd) +{ + int continue_split = 0; + struct dwc_qtd *qtd; + + qtd = list_entry(qh->qtd_list.next, struct dwc_qtd, qtd_list_entry); + if (qtd->complete_split) + continue_split = 1; + else if (qtd->isoc_split_pos == DWC_HCSPLIT_XACTPOS_MID || + qtd->isoc_split_pos == DWC_HCSPLIT_XACTPOS_END) + continue_split = 1; + + if (free_qtd) { + dwc_otg_hcd_qtd_remove(qtd); + continue_split = 0; + } + + qh->channel = NULL; + qh->qtd_in_process = NULL; + dwc_otg_hcd_qh_deactivate(hcd, qh, continue_split); +} + +/** + * Updates the state of an Isochronous URB when the transfer is stopped for + * any reason. The fields of the current entry in the frame descriptor array + * are set based on the transfer state and the input status. Completes the + * Isochronous URB if all the URB frames have been completed. + */ +static enum dwc_halt_status update_isoc_urb_state(struct dwc_hcd *hcd, + struct dwc_hc *hc, u32 regs, + struct dwc_qtd *qtd, + enum dwc_halt_status status) +{ + struct urb *urb = qtd->urb; + enum dwc_halt_status ret_val = status; + struct usb_iso_packet_descriptor *frame_desc; + frame_desc = &urb->iso_frame_desc[qtd->isoc_frame_index]; + + switch (status) { + case DWC_OTG_HC_XFER_COMPLETE: + frame_desc->status = 0; + frame_desc->actual_length = + get_actual_xfer_length(hc, regs, qtd, status, NULL); + break; + case DWC_OTG_HC_XFER_FRAME_OVERRUN: + urb->error_count++; + if (hc->ep_is_in) + frame_desc->status = -ENOSR; + else + frame_desc->status = -ECOMM; + + frame_desc->actual_length = 0; + break; + case DWC_OTG_HC_XFER_BABBLE_ERR: + /* Don't need to update actual_length in this case. */ + urb->error_count++; + frame_desc->status = -EOVERFLOW; + break; + case DWC_OTG_HC_XFER_XACT_ERR: + urb->error_count++; + frame_desc->status = -EPROTO; + frame_desc->actual_length = + get_actual_xfer_length(hc, regs, qtd, status, NULL); + default: + pr_err("%s: Unhandled halt_status (%d)\n", __func__, status); + BUG(); + break; + } + + if (++qtd->isoc_frame_index == urb->number_of_packets) { + /* + * urb->status is not used for isoc transfers. + * The individual frame_desc statuses are used instead. + */ + dwc_otg_hcd_complete_urb(hcd, urb, 0); + ret_val = DWC_OTG_HC_XFER_URB_COMPLETE; + } else { + ret_val = DWC_OTG_HC_XFER_COMPLETE; + } + return ret_val; +} + +/** + * Releases a host channel for use by other transfers. Attempts to select and + * queue more transactions since at least one host channel is available. + */ +static void release_channel(struct dwc_hcd *hcd, struct dwc_hc *hc, + struct dwc_qtd *qtd, + enum dwc_halt_status halt_status, int *must_free) +{ + enum dwc_transaction_type tr_type; + int free_qtd; + int deact = 1; + struct dwc_qh *qh; + int retry_delay = 1; + + switch (halt_status) { + case DWC_OTG_HC_XFER_NYET: + case DWC_OTG_HC_XFER_NAK: + if (halt_status == DWC_OTG_HC_XFER_NYET) + retry_delay = nyet_deferral_delay; + else + retry_delay = nak_deferral_delay; + free_qtd = 0; + if (deferral_on && hc->do_split) { + qh = hc->qh; + if (qh) + deact = dwc_otg_hcd_qh_deferr(hcd, qh, + retry_delay); + } + break; + case DWC_OTG_HC_XFER_URB_COMPLETE: + free_qtd = 1; + break; + case DWC_OTG_HC_XFER_AHB_ERR: + case DWC_OTG_HC_XFER_STALL: + case DWC_OTG_HC_XFER_BABBLE_ERR: + free_qtd = 1; + break; + case DWC_OTG_HC_XFER_XACT_ERR: + if (qtd->error_count >= 3) { + free_qtd = 1; + dwc_otg_hcd_complete_urb(hcd, qtd->urb, -EPROTO); + } else { + free_qtd = 0; + } + break; + case DWC_OTG_HC_XFER_URB_DEQUEUE: + /* + * The QTD has already been removed and the QH has been + * deactivated. Don't want to do anything except release the + * host channel and try to queue more transfers. + */ + goto cleanup; + case DWC_OTG_HC_XFER_NO_HALT_STATUS: + pr_err("%s: No halt_status, channel %d\n", __func__, + hc->hc_num); + free_qtd = 0; + break; + default: + free_qtd = 0; + break; + } + if (free_qtd) + /* must_free pre-initialized to zero */ + *must_free = 1; + if (deact) + deactivate_qh(hcd, hc->qh, free_qtd); + +cleanup: + /* + * Release the host channel for use by other transfers. The cleanup + * function clears the channel interrupt enables and conditions, so + * there's no need to clear the Channel Halted interrupt separately. + */ + dwc_otg_hc_cleanup(hcd->core_if, hc); + list_add_tail(&hc->hc_list_entry, &hcd->free_hc_list); + hcd->available_host_channels++; + /* Try to queue more transfers now that there's a free channel. */ + if (!erratum_usb09_patched) { + tr_type = dwc_otg_hcd_select_transactions(hcd); + if (tr_type != DWC_OTG_TRANSACTION_NONE) + dwc_otg_hcd_queue_transactions(hcd, tr_type); + } +} + +/** + * Halts a host channel. If the channel cannot be halted immediately because + * the request queue is full, this function ensures that the FIFO empty + * interrupt for the appropriate queue is enabled so that the halt request can + * be queued when there is space in the request queue. + * + * This function may also be called in DMA mode. In that case, the channel is + * simply released since the core always halts the channel automatically in + * DMA mode. + */ +static void halt_channel(struct dwc_hcd *hcd, struct dwc_hc *hc, + struct dwc_qtd *qtd, enum dwc_halt_status halt_status, + int *must_free) +{ + if (hcd->core_if->dma_enable) { + release_channel(hcd, hc, qtd, halt_status, must_free); + return; + } + + /* Slave mode processing... */ + dwc_otg_hc_halt(hcd->core_if, hc, halt_status); + if (hc->halt_on_queue) { + u32 gintmsk = 0; + + if (hc->ep_type == DWC_OTG_EP_TYPE_CONTROL || + hc->ep_type == DWC_OTG_EP_TYPE_BULK) { + /* + * Make sure the Non-periodic Tx FIFO empty interrupt + * is enabled so that the non-periodic schedule will + * be processed. + */ + gintmsk |= DWC_INTMSK_NP_TXFIFO_EMPT; + dwc_reg_modify(gintmsk_reg(hcd), 0, 0, gintmsk); + } else { + /* + * Move the QH from the periodic queued schedule to + * the periodic assigned schedule. This allows the + * halt to be queued when the periodic schedule is + * processed. + */ + list_move(&hc->qh->qh_list_entry, + &hcd->periodic_sched_assigned); + + /* + * Make sure the Periodic Tx FIFO Empty interrupt is + * enabled so that the periodic schedule will be + * processed. + */ + gintmsk |= DWC_INTMSK_P_TXFIFO_EMPTY; + dwc_reg_modify(gintmsk_reg(hcd), 0, 0, gintmsk); + } + } +} + +/** + * Performs common cleanup for non-periodic transfers after a Transfer + * Complete interrupt. This function should be called after any endpoint type + * specific handling is finished to release the host channel. + */ +static void complete_non_periodic_xfer(struct dwc_hcd *hcd, struct dwc_hc *hc, + ulong regs, struct dwc_qtd *qtd, + enum dwc_halt_status halt_status, + int *must_free) +{ + u32 hcint; + + qtd->error_count = 0; + hcint = dwc_reg_read(regs, DWC_HCINT); + if (DWC_HCINT_NYET_RESP_REC_RD(hcint)) { + u32 hcint_clear = 0; + + hcint_clear = DWC_HCINT_NYET_RESP_REC_RW(hcint_clear, 1); + /* + * Got a NYET on the last transaction of the transfer. This + * means that the endpoint should be in the PING state at the + * beginning of the next transfer. + */ + hc->qh->ping_state = 1; + dwc_reg_write(regs, DWC_HCINT, hcint_clear); + } + + /* + * Always halt and release the host channel to make it available for + * more transfers. There may still be more phases for a control + * transfer or more data packets for a bulk transfer at this point, + * but the host channel is still halted. A channel will be reassigned + * to the transfer when the non-periodic schedule is processed after + * the channel is released. This allows transactions to be queued + * properly via dwc_otg_hcd_queue_transactions, which also enables the + * Tx FIFO Empty interrupt if necessary. + * + * IN transfers in Slave mode require an explicit disable to + * halt the channel. (In DMA mode, this call simply releases + * the channel.) + * + * The channel is automatically disabled by the core for OUT + * transfers in Slave mode. + */ + if (hc->ep_is_in) + halt_channel(hcd, hc, qtd, halt_status, must_free); + else + release_channel(hcd, hc, qtd, halt_status, must_free); +} + +/** + * Performs common cleanup for periodic transfers after a Transfer Complete + * interrupt. This function should be called after any endpoint type specific + * handling is finished to release the host channel. + */ +static void complete_periodic_xfer(struct dwc_hcd *hcd, struct dwc_hc *hc, + ulong regs, struct dwc_qtd *qtd, + enum dwc_halt_status halt_status, + int *must_free) +{ + u32 hctsiz = 0; + + hctsiz = dwc_reg_read(regs, DWC_HCTSIZ); + qtd->error_count = 0; + + /* + * For OUT transfers and 0 packet count, the Core halts the channel, + * otherwise, Flush any outstanding requests from the Tx queue. + */ + if (!hc->ep_is_in || (DWC_HCTSIZ_PKT_CNT_RD(hctsiz) == 0)) + release_channel(hcd, hc, qtd, halt_status, must_free); + else + halt_channel(hcd, hc, qtd, halt_status, must_free); +} + +/** + * Handles a host channel Transfer Complete interrupt. This handler may be + * called in either DMA mode or Slave mode. + */ +static int handle_hc_xfercomp_intr(struct dwc_hcd *hcd, struct dwc_hc *hc, + ulong regs, struct dwc_qtd *qtd, + int *must_free) +{ + int urb_xfer_done; + enum dwc_halt_status halt_status = DWC_OTG_HC_XFER_COMPLETE; + struct urb *urb = qtd->urb; + int pipe_type = usb_pipetype(urb->pipe); + int status = -EINPROGRESS; + u32 hcintmsk = 0; + + /* Handle xfer complete on CSPLIT. */ + if (hc->qh->do_split) + qtd->complete_split = 0; + + /* Update the QTD and URB states. */ + switch (pipe_type) { + case PIPE_CONTROL: + switch (qtd->control_phase) { + case DWC_OTG_CONTROL_SETUP: + if (urb->transfer_buffer_length > 0) + qtd->control_phase = DWC_OTG_CONTROL_DATA; + else + qtd->control_phase = DWC_OTG_CONTROL_STATUS; + halt_status = DWC_OTG_HC_XFER_COMPLETE; + break; + case DWC_OTG_CONTROL_DATA: + urb_xfer_done = update_urb_state_xfer_comp(hc, regs, + urb, qtd, + &status); + if (urb_xfer_done) + qtd->control_phase = DWC_OTG_CONTROL_STATUS; + else + save_data_toggle(hc, regs, qtd); + halt_status = DWC_OTG_HC_XFER_COMPLETE; + break; + case DWC_OTG_CONTROL_STATUS: + if (status == -EINPROGRESS) + status = 0; + dwc_otg_hcd_complete_urb(hcd, urb, status); + halt_status = DWC_OTG_HC_XFER_URB_COMPLETE; + break; + } + complete_non_periodic_xfer(hcd, hc, regs, qtd, + halt_status, must_free); + break; + case PIPE_BULK: + urb_xfer_done = update_urb_state_xfer_comp(hc, regs, urb, qtd, + &status); + if (urb_xfer_done) { + dwc_otg_hcd_complete_urb(hcd, urb, status); + halt_status = DWC_OTG_HC_XFER_URB_COMPLETE; + } else { + halt_status = DWC_OTG_HC_XFER_COMPLETE; + } + + save_data_toggle(hc, regs, qtd); + complete_non_periodic_xfer(hcd, hc, regs, qtd, + halt_status, must_free); + break; + case PIPE_INTERRUPT: + update_urb_state_xfer_comp(hc, regs, urb, qtd, &status); + /* + * Interrupt URB is done on the first transfer complete + * interrupt. + */ + dwc_otg_hcd_complete_urb(hcd, urb, status); + save_data_toggle(hc, regs, qtd); + complete_periodic_xfer(hcd, hc, regs, qtd, + DWC_OTG_HC_XFER_URB_COMPLETE, must_free); + break; + case PIPE_ISOCHRONOUS: + if (qtd->isoc_split_pos == DWC_HCSPLIT_XACTPOS_ALL) { + halt_status = update_isoc_urb_state(hcd, hc, regs, qtd, + DWC_OTG_HC_XFER_COMPLETE); + } + complete_periodic_xfer(hcd, hc, regs, qtd, + halt_status, must_free); + break; + } + + /* disable xfercompl */ + hcintmsk = DWC_HCINTMSK_TXFER_CMPL_RW(hcintmsk, 1); + dwc_reg_modify(regs, DWC_HCINTMSK, hcintmsk, 0); + + return 1; +} + +/** + * Handles a host channel STALL interrupt. This handler may be called in + * either DMA mode or Slave mode. + */ +static int handle_hc_stall_intr(struct dwc_hcd *hcd, struct dwc_hc *hc, + u32 regs, struct dwc_qtd *qtd, int *must_free) +{ + struct urb *urb = qtd->urb; + int pipe_type = usb_pipetype(urb->pipe); + u32 hcintmsk = 0; + + if (pipe_type == PIPE_CONTROL) + dwc_otg_hcd_complete_urb(hcd, qtd->urb, -EPIPE); + + if (pipe_type == PIPE_BULK || pipe_type == PIPE_INTERRUPT) { + dwc_otg_hcd_complete_urb(hcd, qtd->urb, -EPIPE); + /* + * USB protocol requires resetting the data toggle for bulk + * and interrupt endpoints when a CLEAR_FEATURE(ENDPOINT_HALT) + * setup command is issued to the endpoint. Anticipate the + * CLEAR_FEATURE command since a STALL has occurred and reset + * the data toggle now. + */ + hc->qh->data_toggle = 0; + } + + halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_STALL, must_free); + /* disable stall */ + hcintmsk = DWC_HCINTMSK_STALL_RESP_REC_RW(hcintmsk, 1); + dwc_reg_modify(regs, DWC_HCINTMSK, hcintmsk, 0); + + return 1; +} + +/** + * Updates the state of the URB when a transfer has been stopped due to an + * abnormal condition before the transfer completes. Modifies the + * actual_length field of the URB to reflect the number of bytes that have + * actually been transferred via the host channel. + */ +static void update_urb_state_xfer_intr(struct dwc_hc *hc, + u32 regs, struct urb *urb, + struct dwc_qtd *qtd, + enum dwc_halt_status sts) +{ + u32 xfr_len = get_actual_xfer_length(hc, regs, qtd, sts, NULL); + urb->actual_length += xfr_len; +} + +/** + * Handles a host channel NAK interrupt. This handler may be called in either + * DMA mode or Slave mode. + */ +static int handle_hc_nak_intr(struct dwc_hcd *hcd, struct dwc_hc *hc, + u32 regs, struct dwc_qtd *qtd, int *must_free) +{ + u32 hcintmsk = 0; + + /* + * Handle NAK for IN/OUT SSPLIT/CSPLIT transfers, bulk, control, and + * interrupt. Re-start the SSPLIT transfer. + */ + if (hc->do_split) { + if (hc->complete_split) + qtd->error_count = 0; + + qtd->complete_split = 0; + halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NAK, must_free); + goto handle_nak_done; + } + switch (usb_pipetype(qtd->urb->pipe)) { + case PIPE_CONTROL: + case PIPE_BULK: + if (hcd->core_if->dma_enable && hc->ep_is_in) { + /* + * NAK interrupts are enabled on bulk/control IN + * transfers in DMA mode for the sole purpose of + * resetting the error count after a transaction error + * occurs. The core will continue transferring data. + */ + qtd->error_count = 0; + goto handle_nak_done; + } + + /* + * NAK interrupts normally occur during OUT transfers in DMA + * or Slave mode. For IN transfers, more requests will be + * queued as request queue space is available. + */ + qtd->error_count = 0; + if (!hc->qh->ping_state) { + update_urb_state_xfer_intr(hc, regs, qtd->urb, qtd, + DWC_OTG_HC_XFER_NAK); + + save_data_toggle(hc, regs, qtd); + if (qtd->urb->dev->speed == USB_SPEED_HIGH) + hc->qh->ping_state = 1; + } + + /* + * Halt the channel so the transfer can be re-started from + * the appropriate point or the PING protocol will + * start/continue. + */ + halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NAK, must_free); + break; + case PIPE_INTERRUPT: + qtd->error_count = 0; + halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NAK, must_free); + break; + case PIPE_ISOCHRONOUS: + /* Should never get called for isochronous transfers. */ + BUG(); + break; + } + +handle_nak_done: + /* disable nak */ + hcintmsk = DWC_HCINTMSK_NAK_RESP_REC_RW(hcintmsk, 1); + dwc_reg_modify(regs, DWC_HCINTMSK, hcintmsk, 0); + + return 1; +} + +/** + * Helper function for handle_hc_ack_intr(). Sets the split values for an ACK + * on SSPLIT for ISOC OUT. + */ +static void set_isoc_out_vals(struct dwc_hc *hc, struct dwc_qtd *qtd) +{ + struct usb_iso_packet_descriptor *frame_desc; + + switch (hc->xact_pos) { + case DWC_HCSPLIT_XACTPOS_ALL: + break; + case DWC_HCSPLIT_XACTPOS_END: + qtd->isoc_split_pos = DWC_HCSPLIT_XACTPOS_ALL; + qtd->isoc_split_offset = 0; + break; + case DWC_HCSPLIT_XACTPOS_BEGIN: + case DWC_HCSPLIT_XACTPOS_MID: + /* + * For BEGIN or MID, calculate the length for the next + * microframe to determine the correct SSPLIT token, either MID + * or END. + */ + frame_desc = &qtd->urb->iso_frame_desc[qtd->isoc_frame_index]; + qtd->isoc_split_offset += 188; + + if ((frame_desc->length - qtd->isoc_split_offset) <= 188) + qtd->isoc_split_pos = DWC_HCSPLIT_XACTPOS_END; + else + qtd->isoc_split_pos = DWC_HCSPLIT_XACTPOS_MID; + + break; + } +} + +/** + * Handles a host channel ACK interrupt. This interrupt is enabled when + * performing the PING protocol in Slave mode, when errors occur during + * either Slave mode or DMA mode, and during Start Split transactions. + */ +static int handle_hc_ack_intr(struct dwc_hcd *hcd, struct dwc_hc *hc, + u32 regs, struct dwc_qtd *qtd, int *must_free) +{ + u32 hcintmsk = 0; + + if (hc->do_split) { + /* Handle ACK on SSPLIT. ACK should not occur in CSPLIT. */ + if (!hc->ep_is_in && hc->data_pid_start != DWC_OTG_HC_PID_SETUP) + qtd->ssplit_out_xfer_count = hc->xfer_len; + + /* Don't need complete for isochronous out transfers. */ + if (!(hc->ep_type == DWC_OTG_EP_TYPE_ISOC && !hc->ep_is_in)) + qtd->complete_split = 1; + + if (hc->ep_type == DWC_OTG_EP_TYPE_ISOC && !hc->ep_is_in) + set_isoc_out_vals(hc, qtd); + else + halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_ACK, + must_free); + } else { + qtd->error_count = 0; + if (hc->qh->ping_state) { + hc->qh->ping_state = 0; + + /* + * Halt the channel so the transfer can be re-started + * from the appropriate point. This only happens in + * Slave mode. In DMA mode, the ping_state is cleared + * when the transfer is started because the core + * automatically executes the PING, then the transfer. + */ + halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_ACK, + must_free); + } + } + + /* + * If the ACK occurred when _not_ in the PING state, let the channel + * continue transferring data after clearing the error count. + */ + /* disable ack */ + hcintmsk = DWC_HCINTMSK_ACK_RESP_REC_RW(hcintmsk, 1); + dwc_reg_modify(regs, DWC_HCINTMSK, hcintmsk, 0); + + return 1; +} + +/** + * Handles a host channel NYET interrupt. This interrupt should only occur on + * Bulk and Control OUT endpoints and for complete split transactions. If a + * NYET occurs at the same time as a Transfer Complete interrupt, it is + * handled in the xfercomp interrupt handler, not here. This handler may be + * called in either DMA mode or Slave mode. + */ +static int handle_hc_nyet_intr(struct dwc_hcd *hcd, struct dwc_hc *hc, + u32 regs, struct dwc_qtd *qtd, int *must_free) +{ + u32 hcintmsk = 0; + u32 hcint_clear = 0; + + /* + * NYET on CSPLIT + * re-do the CSPLIT immediately on non-periodic + */ + if (hc->do_split && hc->complete_split) { + if (hc->ep_type == DWC_OTG_EP_TYPE_INTR || + hc->ep_type == DWC_OTG_EP_TYPE_ISOC) { + int frnum = + dwc_otg_hcd_get_frame_number(dwc_otg_hcd_to_hcd + (hcd)); + if (dwc_full_frame_num(frnum) != + dwc_full_frame_num(hc->qh->sched_frame)) { + qtd->complete_split = 0; + halt_channel(hcd, hc, qtd, + DWC_OTG_HC_XFER_XACT_ERR, + must_free); + goto handle_nyet_done; + } + } + halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NYET, must_free); + goto handle_nyet_done; + } + hc->qh->ping_state = 1; + qtd->error_count = 0; + update_urb_state_xfer_intr(hc, regs, qtd->urb, qtd, + DWC_OTG_HC_XFER_NYET); + save_data_toggle(hc, regs, qtd); + /* + * Halt the channel and re-start the transfer so the PING + * protocol will start. + */ + halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NYET, must_free); + +handle_nyet_done: + /* disable nyet */ + hcintmsk = DWC_HCINTMSK_NYET_RESP_REC_RW(hcintmsk, 1); + dwc_reg_modify(regs, DWC_HCINTMSK, hcintmsk, 0); + /* clear nyet */ + hcint_clear = DWC_HCINT_NYET_RESP_REC_RW(hcint_clear, 1); + dwc_reg_write(regs, DWC_HCINT, hcint_clear); + return 1; +} + +/** + * Handles a host channel babble interrupt. This handler may be called in + * either DMA mode or Slave mode. + */ +static int handle_hc_babble_intr(struct dwc_hcd *hcd, struct dwc_hc *hc, + u32 regs, struct dwc_qtd *qtd, int *must_free) +{ + u32 hcintmsk = 0; + + if (hc->ep_type != DWC_OTG_EP_TYPE_ISOC) { + dwc_otg_hcd_complete_urb(hcd, qtd->urb, -EOVERFLOW); + halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_BABBLE_ERR, + must_free); + } else { + enum dwc_halt_status halt_status; + halt_status = update_isoc_urb_state(hcd, hc, regs, qtd, + DWC_OTG_HC_XFER_BABBLE_ERR); + halt_channel(hcd, hc, qtd, halt_status, must_free); + } + /* disable bblerr */ + hcintmsk = DWC_HCINTMSK_BBL_ERR_RW(hcintmsk, 1); + dwc_reg_modify(regs, DWC_HCINTMSK, hcintmsk, 0); + return 1; +} + +/** + * Handles a host channel AHB error interrupt. This handler is only called in + * DMA mode. + */ +static int handle_hc_ahberr_intr(struct dwc_hcd *hcd, struct dwc_hc *hc, + u32 regs, struct dwc_qtd *qtd) +{ + u32 hcchar; + u32 hcsplt; + u32 hctsiz = 0; + u32 hcdma; + struct urb *urb = qtd->urb; + u32 hcintmsk = 0; + + hcchar = dwc_reg_read(regs, DWC_HCCHAR); + hcsplt = dwc_reg_read(regs, DWC_HCSPLT); + hctsiz = dwc_reg_read(regs, DWC_HCTSIZ); + hcdma = dwc_reg_read(regs, DWC_HCDMA); + + pr_err("AHB ERROR, Channel %d\n", hc->hc_num); + pr_err(" hcchar 0x%08x, hcsplt 0x%08x\n", hcchar, hcsplt); + pr_err(" hctsiz 0x%08x, hcdma 0x%08x\n", hctsiz, hcdma); + + pr_err(" Device address: %d\n", usb_pipedevice(urb->pipe)); + pr_err(" Endpoint: %d, %s\n", usb_pipeendpoint(urb->pipe), + (usb_pipein(urb->pipe) ? "IN" : "OUT")); + + pr_err(" Endpoint type: %s\n", pipetype_str(urb->pipe)); + pr_err(" Speed: %s\n", dev_speed_str(urb->dev->speed)); + pr_err(" Max packet size: %d\n", + usb_maxpacket(urb->dev, urb->pipe, usb_pipeout(urb->pipe))); + pr_err(" Data buffer length: %d\n", urb->transfer_buffer_length); + pr_err(" Transfer buffer: %p, Transfer DMA: %p\n", + urb->transfer_buffer, (void *)(u32) urb->transfer_dma); + pr_err(" Setup buffer: %p, Setup DMA: %p\n", + urb->setup_packet, (void *)(u32) urb->setup_dma); + pr_err(" Interval: %d\n", urb->interval); + + dwc_otg_hcd_complete_urb(hcd, urb, -EIO); + + /* + * Force a channel halt. Don't call halt_channel because that won't + * write to the HCCHARn register in DMA mode to force the halt. + */ + dwc_otg_hc_halt(hcd->core_if, hc, DWC_OTG_HC_XFER_AHB_ERR); + /* disable ahberr */ + hcintmsk = DWC_HCINTMSK_AHB_ERR_RW(hcintmsk, 1); + dwc_reg_modify(regs, DWC_HCINTMSK, hcintmsk, 0); + + return 1; +} + +/** + * Handles a host channel transaction error interrupt. This handler may be + * called in either DMA mode or Slave mode. + */ +static int handle_hc_xacterr_intr(struct dwc_hcd *hcd, struct dwc_hc *hc, + u32 regs, struct dwc_qtd *qtd, int *must_free) +{ + enum dwc_halt_status status = DWC_OTG_HC_XFER_XACT_ERR; + u32 hcintmsk = 0; + + switch (usb_pipetype(qtd->urb->pipe)) { + case PIPE_CONTROL: + case PIPE_BULK: + qtd->error_count++; + if (!hc->qh->ping_state) { + update_urb_state_xfer_intr(hc, regs, qtd->urb, qtd, + status); + save_data_toggle(hc, regs, qtd); + + if (!hc->ep_is_in && qtd->urb->dev->speed == + USB_SPEED_HIGH) + hc->qh->ping_state = 1; + } + /* + * Halt the channel so the transfer can be re-started from + * the appropriate point or the PING protocol will start. + */ + halt_channel(hcd, hc, qtd, status, must_free); + break; + case PIPE_INTERRUPT: + qtd->error_count++; + if (hc->do_split && hc->complete_split) + qtd->complete_split = 0; + + halt_channel(hcd, hc, qtd, status, must_free); + break; + case PIPE_ISOCHRONOUS: + status = update_isoc_urb_state(hcd, hc, regs, qtd, status); + halt_channel(hcd, hc, qtd, status, must_free); + break; + } + /* Disable xacterr */ + hcintmsk = DWC_HCINTMSK_TRANS_ERR_RW(hcintmsk, 1); + dwc_reg_modify(regs, DWC_HCINTMSK, hcintmsk, 0); + + return 1; +} + +/** + * Handles a host channel frame overrun interrupt. This handler may be called + * in either DMA mode or Slave mode. + */ +static int handle_hc_frmovrun_intr(struct dwc_hcd *hcd, struct dwc_hc *hc, + u32 regs, struct dwc_qtd *qtd, + int *must_free) +{ + enum dwc_halt_status status = DWC_OTG_HC_XFER_FRAME_OVERRUN; + u32 hcintmsk = 0; + + switch (usb_pipetype(qtd->urb->pipe)) { + case PIPE_CONTROL: + case PIPE_BULK: + break; + case PIPE_INTERRUPT: + halt_channel(hcd, hc, qtd, status, must_free); + break; + case PIPE_ISOCHRONOUS: + status = update_isoc_urb_state(hcd, hc, regs, qtd, status); + halt_channel(hcd, hc, qtd, status, must_free); + break; + } + /* Disable frmovrun */ + hcintmsk = DWC_HCINTMSK_FRAME_OVERN_ERR_RW(hcintmsk, 1); + dwc_reg_modify(regs, DWC_HCINTMSK, hcintmsk, 0); + + return 1; +} + +/** + * Handles a host channel data toggle error interrupt. This handler may be + * called in either DMA mode or Slave mode. + */ +static int handle_hc_datatglerr_intr(struct dwc_hcd *hcd, struct dwc_hc *hc, + u32 regs, struct dwc_qtd *qtd) +{ + u32 hcintmsk = 0; + + if (hc->ep_is_in) + qtd->error_count = 0; + else + pr_err("Data Toggle Error on OUT transfer, channel " + "%d\n", hc->hc_num); + + /* disable datatglerr */ + hcintmsk = DWC_HCINTMSK_DATA_TOG_ERR_RW(hcintmsk, 1); + dwc_reg_modify(regs, DWC_HCINTMSK, hcintmsk, 0); + + return 1; +} + +/** + * Handles a host Channel Halted interrupt in DMA mode. This handler + * determines the reason the channel halted and proceeds accordingly. + */ +static void handle_hc_chhltd_intr_dma(struct dwc_hcd *hcd, struct dwc_hc *hc, + ulong regs, struct dwc_qtd *qtd, + int *must_free) +{ + u32 hcint; + u32 hcintmsk = 0; + + if (hc->halt_status == DWC_OTG_HC_XFER_URB_DEQUEUE || + hc->halt_status == DWC_OTG_HC_XFER_AHB_ERR) { + /* + * Just release the channel. A dequeue can happen on a + * transfer timeout. In the case of an AHB Error, the channel + * was forced to halt because there's no way to gracefully + * recover. + */ + release_channel(hcd, hc, qtd, hc->halt_status, must_free); + return; + } + + /* Read the HCINTn register to determine the cause for the halt. */ + hcint = dwc_reg_read(regs, DWC_HCINT); + hcintmsk = dwc_reg_read(regs, DWC_HCINTMSK); + if (DWC_HCINT_TXFER_CMPL_RD(hcint)) { + /* + * This is here because of a possible hardware bug. Spec + * says that on SPLIT-ISOC OUT transfers in DMA mode that a HALT + * interrupt w/ACK bit set should occur, but I only see the + * XFERCOMP bit, even with it masked out. This is a workaround + * for that behavior. Should fix this when hardware is fixed. + */ + if (hc->ep_type == DWC_OTG_EP_TYPE_ISOC && !hc->ep_is_in) + handle_hc_ack_intr(hcd, hc, regs, qtd, must_free); + + handle_hc_xfercomp_intr(hcd, hc, regs, qtd, must_free); + } else if (DWC_HCINT_STALL_RESP_REC_RD(hcint)) { + handle_hc_stall_intr(hcd, hc, regs, qtd, must_free); + } else if (DWC_HCINT_TRANS_ERR_RD(hcint)) { + /* + * Must handle xacterr before nak or ack. Could get a xacterr + * at the same time as either of these on a BULK/CONTROL OUT + * that started with a PING. The xacterr takes precedence. + */ + handle_hc_xacterr_intr(hcd, hc, regs, qtd, must_free); + } else if (DWC_HCINT_NYET_RESP_REC_RD(hcint)) { + /* + * Must handle nyet before nak or ack. Could get a nyet at the + * same time as either of those on a BULK/CONTROL OUT that + * started with a PING. The nyet takes precedence. + */ + handle_hc_nyet_intr(hcd, hc, regs, qtd, must_free); + } else if (DWC_HCINT_BBL_ERR_RD(hcint)) { + handle_hc_babble_intr(hcd, hc, regs, qtd, must_free); + } else if (DWC_HCINT_FRAME_OVERN_ERR_RD(hcint)) { + handle_hc_frmovrun_intr(hcd, hc, regs, qtd, must_free); + } else if (DWC_HCINT_DATA_TOG_ERR_RD(hcint)) { + handle_hc_datatglerr_intr(hcd, hc, regs, qtd); + hc->qh->data_toggle = 0; + halt_channel(hcd, hc, qtd, hc->halt_status, must_free); + } else if (DWC_HCINT_NAK_RESP_REC_RD(hcint) && + !DWC_HCINTMSK_NAK_RESP_REC_RD(hcintmsk)) { + /* + * If nak is not masked, it's because a non-split IN transfer + * is in an error state. In that case, the nak is handled by + * the nak interrupt handler, not here. Handle nak here for + * BULK/CONTROL OUT transfers, which halt on a NAK to allow + * rewinding the buffer pointer. + */ + handle_hc_nak_intr(hcd, hc, regs, qtd, must_free); + } else if (DWC_HCINT_ACK_RESP_REC_RD(hcint) && + !DWC_HCINTMSK_ACK_RESP_REC_RD(hcintmsk)) { + /* + * If ack is not masked, it's because a non-split IN transfer + * is in an error state. In that case, the ack is handled by + * the ack interrupt handler, not here. Handle ack here for + * split transfers. Start splits halt on ACK. + */ + handle_hc_ack_intr(hcd, hc, regs, qtd, must_free); + } else { + if (hc->ep_type == DWC_OTG_EP_TYPE_INTR || + hc->ep_type == DWC_OTG_EP_TYPE_ISOC) { + /* + * A periodic transfer halted with no other channel + * interrupts set. Assume it was halted by the core + * because it could not be completed in its scheduled + * (micro)frame. + */ + halt_channel(hcd, hc, qtd, + DWC_OTG_HC_XFER_PERIODIC_INCOMPLETE, + must_free); + } else { + pr_err("%s: Channel %d, DMA Mode -- ChHltd " + "set, but reason for halting is unknown, " + "hcint 0x%08x, intsts 0x%08x\n", + __func__, hc->hc_num, hcint, + dwc_reg_read(gintsts_reg(hcd), 0)); + } + } +} + +/** + * Handles a host channel Channel Halted interrupt. + * + * In slave mode, this handler is called only when the driver specifically + * requests a halt. This occurs during handling other host channel interrupts + * (e.g. nak, xacterr, stall, nyet, etc.). + * + * In DMA mode, this is the interrupt that occurs when the core has finished + * processing a transfer on a channel. Other host channel interrupts (except + * ahberr) are disabled in DMA mode. + */ +static int handle_hc_chhltd_intr(struct dwc_hcd *hcd, struct dwc_hc *hc, + ulong regs, struct dwc_qtd *qtd, int *must_free) +{ + if (hcd->core_if->dma_enable) + handle_hc_chhltd_intr_dma(hcd, hc, regs, qtd, must_free); + else + release_channel(hcd, hc, qtd, hc->halt_status, must_free); + + return 1; +} + +/* Handles interrupt for a specific Host Channel */ +static int dwc_otg_hcd_handle_hc_n_intr(struct dwc_hcd *hcd, u32 num) +{ + int must_free = 0; + int retval = 0; + u32 hcint; + u32 hcintmsk = 0; + struct dwc_hc *hc; + ulong hc_regs; + struct dwc_qtd *qtd; + + hc = hcd->hc_ptr_array[num]; + hc_regs = hcd->core_if->host_if->hc_regs[num]; + qtd = list_entry(hc->qh->qtd_list.next, struct dwc_qtd, qtd_list_entry); + + hcint = dwc_reg_read(hc_regs, DWC_HCINT); + hcintmsk = dwc_reg_read(hc_regs, DWC_HCINTMSK); + + hcint = hcint & hcintmsk; + if (!hcd->core_if->dma_enable && DWC_HCINT_CHAN_HALTED_RD(hcint) + && hcint != 0x2) + hcint = DWC_HCINT_CHAN_HALTED_RW(hcint, 0); + + if (DWC_HCINT_TXFER_CMPL_RD(hcint)) { + retval |= handle_hc_xfercomp_intr(hcd, hc, hc_regs, + qtd, &must_free); + /* + * If NYET occurred at same time as Xfer Complete, the NYET is + * handled by the Xfer Complete interrupt handler. Don't want + * to call the NYET interrupt handler in this case. + */ + hcint = DWC_HCINT_NYET_RESP_REC_RW(hcint, 0); + } + + if (DWC_HCINT_CHAN_HALTED_RD(hcint)) + retval |= handle_hc_chhltd_intr(hcd, hc, hc_regs, + qtd, &must_free); + if (DWC_HCINT_AHB_ERR_RD(hcint)) + retval |= handle_hc_ahberr_intr(hcd, hc, hc_regs, qtd); + if (DWC_HCINT_STALL_RESP_REC_RD(hcint)) + retval |= handle_hc_stall_intr(hcd, hc, hc_regs, + qtd, &must_free); + if (DWC_HCINT_NAK_RESP_REC_RD(hcint)) + retval |= handle_hc_nak_intr(hcd, hc, hc_regs, qtd, &must_free); + if (DWC_HCINT_ACK_RESP_REC_RD(hcint)) + retval |= handle_hc_ack_intr(hcd, hc, hc_regs, qtd, &must_free); + if (DWC_HCINT_NYET_RESP_REC_RD(hcint)) + retval |= handle_hc_nyet_intr(hcd, hc, hc_regs, + qtd, &must_free); + if (DWC_HCINT_TRANS_ERR_RD(hcint)) + retval |= handle_hc_xacterr_intr(hcd, hc, hc_regs, + qtd, &must_free); + if (DWC_HCINT_BBL_ERR_RD(hcint)) + retval |= handle_hc_babble_intr(hcd, hc, hc_regs, + qtd, &must_free); + if (DWC_HCINT_FRAME_OVERN_ERR_RD(hcint)) + retval |= handle_hc_frmovrun_intr(hcd, hc, hc_regs, + qtd, &must_free); + if (DWC_HCINT_DATA_TOG_ERR_RD(hcint)) + retval |= handle_hc_datatglerr_intr(hcd, hc, hc_regs, qtd); + + if (must_free) + /* Free the qtd here now that we are done using it. */ + dwc_otg_hcd_qtd_free(qtd); + return retval; +} + +/** + * This function returns the Host All Channel Interrupt register + */ +static inline u32 dwc_otg_read_host_all_channels_intr(struct core_if + *core_if) +{ + return dwc_reg_read(core_if->host_if->host_global_regs, DWC_HAINT); +} + +/** + * This interrupt indicates that one or more host channels has a pending + * interrupt. There are multiple conditions that can cause each host channel + * interrupt. This function determines which conditions have occurred for each + * host channel interrupt and handles them appropriately. + */ +static int dwc_otg_hcd_handle_hc_intr(struct dwc_hcd *hcd) +{ + u32 i; + int retval = 0; + u32 haint; + + /* + * Clear appropriate bits in HCINTn to clear the interrupt bit in + * GINTSTS + */ + haint = dwc_otg_read_host_all_channels_intr(hcd->core_if); + for (i = 0; i < hcd->core_if->core_params->host_channels; i++) + if (DWC_HAINT_RD(haint) & (1 << i)) + retval |= dwc_otg_hcd_handle_hc_n_intr(hcd, i); + + return retval; +} + +/* This function handles interrupts for the HCD.*/ +int dwc_otg_hcd_handle_intr(struct dwc_hcd *hcd) +{ + int ret = 0; + struct core_if *core_if = hcd->core_if; + u32 gintsts; + + /* Check if HOST Mode */ + if (dwc_otg_is_host_mode(core_if)) { + spin_lock(&hcd->lock); + gintsts = dwc_otg_read_core_intr(core_if); + if (!gintsts) { + spin_unlock(&hcd->lock); + return IRQ_NONE; + } + + if (gintsts & DWC_INTMSK_STRT_OF_FRM) + ret |= dwc_otg_hcd_handle_sof_intr(hcd); + if (gintsts & DWC_INTMSK_RXFIFO_NOT_EMPT) + ret |= dwc_otg_hcd_handle_rx_status_q_level_intr(hcd); + if (gintsts & DWC_INTMSK_NP_TXFIFO_EMPT) + ret |= dwc_otg_hcd_handle_np_tx_fifo_empty_intr(hcd); + if (gintsts & DWC_INTMSK_HST_PORT) + ret |= dwc_otg_hcd_handle_port_intr(hcd); + if (gintsts & DWC_INTMSK_HST_CHAN) + ret |= dwc_otg_hcd_handle_hc_intr(hcd); + if (gintsts & DWC_INTMSK_P_TXFIFO_EMPTY) + ret |= dwc_otg_hcd_handle_perio_tx_fifo_empty_intr(hcd); + + spin_unlock(&hcd->lock); + } + return ret; +} diff --git a/drivers/usb/dwc/hcd_queue.c b/drivers/usb/dwc/hcd_queue.c new file mode 100644 index 0000000..67f0409 --- /dev/null +++ b/drivers/usb/dwc/hcd_queue.c @@ -0,0 +1,696 @@ +/* + * DesignWare HS OTG controller driver + * Copyright (C) 2006 Synopsys, Inc. + * Portions Copyright (C) 2010 Applied Micro Circuits Corporation. + * + * This program 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. + * + * 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 version 2 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see http://www.gnu.org/licenses + * or write to the Free Software Foundation, Inc., 51 Franklin Street, + * Suite 500, Boston, MA 02110-1335 USA. + * + * Based on Synopsys driver version 2.60a + * Modified by Mark Miesfeld <mmiesfeld@apm.com> + * Modified by Stefan Roese <sr@denx.de>, DENX Software Engineering + * Modified by Chuck Meade <chuck@theptrgroup.com> + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL SYNOPSYS, INC. BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES + * (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/* + * This file contains the functions to manage Queue Heads and Queue + * Transfer Descriptors. + */ + +#include "hcd.h" + +static inline int is_fs_ls(enum usb_device_speed speed) +{ + return speed == USB_SPEED_FULL || speed == USB_SPEED_LOW; +} + +/* Allocates memory for a QH structure. */ +static inline struct dwc_qh *dwc_otg_hcd_qh_alloc(void) +{ + return kmalloc(sizeof(struct dwc_qh), GFP_ATOMIC); +} + +/** + * Initializes a QH structure to initialize the QH. + */ +#define SCHEDULE_SLOP 10 +static void dwc_otg_hcd_qh_init(struct dwc_hcd *hcd, struct dwc_qh *qh, + struct urb *urb) +{ + memset(qh, 0, sizeof(struct dwc_qh)); + + /* Initialize QH */ + switch (usb_pipetype(urb->pipe)) { + case PIPE_CONTROL: + qh->ep_type = USB_ENDPOINT_XFER_CONTROL; + break; + case PIPE_BULK: + qh->ep_type = USB_ENDPOINT_XFER_BULK; + break; + case PIPE_ISOCHRONOUS: + qh->ep_type = USB_ENDPOINT_XFER_ISOC; + break; + case PIPE_INTERRUPT: + qh->ep_type = USB_ENDPOINT_XFER_INT; + break; + } + + qh->ep_is_in = usb_pipein(urb->pipe) ? 1 : 0; + qh->data_toggle = DWC_OTG_HC_PID_DATA0; + qh->maxp = usb_maxpacket(urb->dev, urb->pipe, !(usb_pipein(urb->pipe))); + + INIT_LIST_HEAD(&qh->qtd_list); + INIT_LIST_HEAD(&qh->qh_list_entry); + + qh->channel = NULL; + qh->speed = urb->dev->speed; + + /* + * FS/LS Enpoint on HS Hub NOT virtual root hub + */ + qh->do_split = 0; + if (is_fs_ls(urb->dev->speed) && urb->dev->tt && urb->dev->tt->hub && + urb->dev->tt->hub->devnum != 1) + qh->do_split = 1; + + if (qh->ep_type == USB_ENDPOINT_XFER_INT || + qh->ep_type == USB_ENDPOINT_XFER_ISOC) { + /* Compute scheduling parameters once and save them. */ + u32 hprt; + int bytecount = dwc_hb_mult(qh->maxp) * + dwc_max_packet(qh->maxp); + + qh->usecs = NS_TO_US(usb_calc_bus_time(urb->dev->speed, + usb_pipein(urb->pipe), + (qh->ep_type == + USB_ENDPOINT_XFER_ISOC), + bytecount)); + + /* Start in a slightly future (micro)frame. */ + qh->sched_frame = dwc_frame_num_inc(hcd->frame_number, + SCHEDULE_SLOP); + qh->interval = urb->interval; + + hprt = dwc_reg_read(hcd->core_if->host_if->hprt0, 0); + if (DWC_HPRT0_PRT_SPD_RD(hprt) == DWC_HPRT0_PRTSPD_HIGH_SPEED && + is_fs_ls(urb->dev->speed)) { + qh->interval *= 8; + qh->sched_frame |= 0x7; + qh->start_split_frame = qh->sched_frame; + } + } +} + +/** + * This function allocates and initializes a QH. + */ +static struct dwc_qh *dwc_otg_hcd_qh_create(struct dwc_hcd *hcd, + struct urb *urb) +{ + struct dwc_qh *qh; + + /* Allocate memory */ + qh = dwc_otg_hcd_qh_alloc(); + if (qh == NULL) + return NULL; + + dwc_otg_hcd_qh_init(hcd, qh, urb); + return qh; +} + +/** + * Free each QTD in the QH's QTD-list then free the QH. QH should already be + * removed from a list. QTD list should already be empty if called from URB + * Dequeue. + */ +void dwc_otg_hcd_qh_free(struct dwc_qh *qh) +{ + struct dwc_qtd *qtd; + struct list_head *pos, *temp; + + /* Free each QTD in the QTD list */ + list_for_each_safe(pos, temp, &qh->qtd_list) { + list_del(pos); + qtd = dwc_list_to_qtd(pos); + dwc_otg_hcd_qtd_free(qtd); + } + kfree(qh); +} + +/** + * Microframe scheduler + * track the total use in hcd->frame_usecs + * keep each qh use in qh->frame_usecs + * when surrendering the qh then donate the time back + */ +static const u16 max_uframe_usecs[] = { 100, 100, 100, 100, 100, 100, 30, 0 }; + +/* + * called from dwc_otg_hcd.c:dwc_otg_hcd_init + */ +int init_hcd_usecs(struct dwc_hcd *hcd) +{ + int i; + + for (i = 0; i < 8; i++) + hcd->frame_usecs[i] = max_uframe_usecs[i]; + + return 0; +} + +static int find_single_uframe(struct dwc_hcd *hcd, struct dwc_qh *qh) +{ + int i; + u16 utime; + int t_left; + int ret; + int done; + + ret = -1; + utime = qh->usecs; + t_left = utime; + i = 0; + done = 0; + while (done == 0) { + /* At the start hcd->frame_usecs[i] = max_uframe_usecs[i]; */ + if (utime <= hcd->frame_usecs[i]) { + hcd->frame_usecs[i] -= utime; + qh->frame_usecs[i] += utime; + t_left -= utime; + ret = i; + done = 1; + return ret; + } else { + i++; + if (i == 8) { + done = 1; + ret = -1; + } + } + } + return ret; +} + +/* + * use this for FS apps that can span multiple uframes + */ +static int find_multi_uframe(struct dwc_hcd *hcd, struct dwc_qh *qh) +{ + int i; + int j; + u16 utime; + int t_left; + int ret; + int done; + u16 xtime; + + ret = -1; + utime = qh->usecs; + t_left = utime; + i = 0; + done = 0; +loop: + while (done == 0) { + if (hcd->frame_usecs[i] <= 0) { + i++; + if (i == 8) { + done = 1; + ret = -1; + } + goto loop; + } + + /* + * We need n consequtive slots so use j as a start slot. + * j plus j+1 must be enough time (for now) + */ + xtime = hcd->frame_usecs[i]; + for (j = i + 1; j < 8; j++) { + /* + * if we add this frame remaining time to xtime we may + * be OK, if not we need to test j for a complete frame. + */ + if ((xtime + hcd->frame_usecs[j]) < utime) { + if (hcd->frame_usecs[j] < max_uframe_usecs[j]) { + j = 8; + ret = -1; + continue; + } + } + if (xtime >= utime) { + ret = i; + j = 8; /* stop loop with a good value ret */ + continue; + } + /* add the frame time to x time */ + xtime += hcd->frame_usecs[j]; + /* we must have a fully available next frame or break */ + if ((xtime < utime) && + (hcd->frame_usecs[j] == max_uframe_usecs[j])) { + ret = -1; + j = 8; /* stop loop with a bad value ret */ + continue; + } + } + if (ret >= 0) { + t_left = utime; + for (j = i; (t_left > 0) && (j < 8); j++) { + t_left -= hcd->frame_usecs[j]; + if (t_left <= 0) { + qh->frame_usecs[j] += + hcd->frame_usecs[j] + t_left; + hcd->frame_usecs[j] = -t_left; + ret = i; + done = 1; + } else { + qh->frame_usecs[j] += + hcd->frame_usecs[j]; + hcd->frame_usecs[j] = 0; + } + } + } else { + i++; + if (i == 8) { + done = 1; + ret = -1; + } + } + } + return ret; +} + +static int find_uframe(struct dwc_hcd *hcd, struct dwc_qh *qh) +{ + int ret = -1; + + if (qh->speed == USB_SPEED_HIGH) + /* if this is a hs transaction we need a full frame */ + ret = find_single_uframe(hcd, qh); + else + /* FS transaction may need a sequence of frames */ + ret = find_multi_uframe(hcd, qh); + + return ret; +} + +/** + * Checks that the max transfer size allowed in a host channel is large enough + * to handle the maximum data transfer in a single (micro)frame for a periodic + * transfer. + */ +static int check_max_xfer_size(struct dwc_hcd *hcd, struct dwc_qh *qh) +{ + int status = 0; + u32 max_xfer_size; + u32 max_channel_xfer_size; + + max_xfer_size = dwc_max_packet(qh->maxp) * dwc_hb_mult(qh->maxp); + max_channel_xfer_size = hcd->core_if->core_params->max_transfer_size; + + if (max_xfer_size > max_channel_xfer_size) { + pr_notice("%s: Periodic xfer length %d > max xfer " + "length for channel %d\n", __func__, max_xfer_size, + max_channel_xfer_size); + status = -ENOSPC; + } + + return status; +} + +/** + * Schedules an interrupt or isochronous transfer in the periodic schedule. + */ +static int schedule_periodic(struct dwc_hcd *hcd, struct dwc_qh *qh) +{ + int status; + struct usb_bus *bus = hcd_to_bus(dwc_otg_hcd_to_hcd(hcd)); + int frame; + + status = find_uframe(hcd, qh); + frame = -1; + if (status == 0) { + frame = 7; + } else { + if (status > 0) + frame = status - 1; + } + /* Set the new frame up */ + if (frame > -1) { + qh->sched_frame &= ~0x7; + qh->sched_frame |= (frame & 7); + } + if (status != -1) + status = 0; + if (status) { + pr_notice("%s: Insufficient periodic bandwidth for " + "periodic transfer.\n", __func__); + return status; + } + status = check_max_xfer_size(hcd, qh); + if (status) { + pr_notice("%s: Channel max transfer size too small " + "for periodic transfer.\n", __func__); + return status; + } + /* Always start in the inactive schedule. */ + list_add_tail(&qh->qh_list_entry, &hcd->periodic_sched_inactive); + + /* Update claimed usecs per (micro)frame. */ + hcd->periodic_usecs += qh->usecs; + + /* + * Update average periodic bandwidth claimed and # periodic reqs for + * usbfs. + */ + bus->bandwidth_allocated += qh->usecs / qh->interval; + + if (qh->ep_type == USB_ENDPOINT_XFER_INT) + bus->bandwidth_int_reqs++; + else + bus->bandwidth_isoc_reqs++; + + return status; +} + +/** + * This function adds a QH to either the non periodic or periodic schedule if + * it is not already in the schedule. If the QH is already in the schedule, no + * action is taken. + */ +static int dwc_otg_hcd_qh_add(struct dwc_hcd *hcd, struct dwc_qh *qh) +{ + int status = 0; + + /* QH may already be in a schedule. */ + if (!list_empty(&qh->qh_list_entry)) + goto done; + /* + * Add the new QH to the appropriate schedule. For non-periodic, always + * start in the inactive schedule. + */ + if (dwc_qh_is_non_per(qh)) + list_add_tail(&qh->qh_list_entry, + &hcd->non_periodic_sched_inactive); + else + status = schedule_periodic(hcd, qh); + +done: + return status; +} + +/** + * This function adds a QH to the non periodic deferred schedule. + * + * @return 0 if successful, negative error code otherwise. + */ +static int dwc_otg_hcd_qh_add_deferred(struct dwc_hcd *hcd, struct dwc_qh *qh) +{ + if (!list_empty(&qh->qh_list_entry)) + /* QH already in a schedule. */ + goto done; + + /* Add the new QH to the non periodic deferred schedule */ + if (dwc_qh_is_non_per(qh)) + list_add_tail(&qh->qh_list_entry, + &hcd->non_periodic_sched_deferred); +done: + return 0; +} + +/** + * Removes an interrupt or isochronous transfer from the periodic schedule. + */ +static void deschedule_periodic(struct dwc_hcd *hcd, struct dwc_qh *qh) +{ + struct usb_bus *bus = hcd_to_bus(dwc_otg_hcd_to_hcd(hcd)); + int i; + + list_del_init(&qh->qh_list_entry); + /* Update claimed usecs per (micro)frame. */ + hcd->periodic_usecs -= qh->usecs; + for (i = 0; i < 8; i++) { + hcd->frame_usecs[i] += qh->frame_usecs[i]; + qh->frame_usecs[i] = 0; + } + /* + * Update average periodic bandwidth claimed and # periodic reqs for + * usbfs. + */ + bus->bandwidth_allocated -= qh->usecs / qh->interval; + + if (qh->ep_type == USB_ENDPOINT_XFER_INT) + bus->bandwidth_int_reqs--; + else + bus->bandwidth_isoc_reqs--; +} + +/** + * Removes a QH from either the non-periodic or periodic schedule. Memory is + * not freed. + */ +void dwc_otg_hcd_qh_remove(struct dwc_hcd *hcd, struct dwc_qh *qh) +{ + /* Do nothing if QH is not in a schedule */ + if (list_empty(&qh->qh_list_entry)) + return; + + if (dwc_qh_is_non_per(qh)) { + if (hcd->non_periodic_qh_ptr == &qh->qh_list_entry) + hcd->non_periodic_qh_ptr = + hcd->non_periodic_qh_ptr->next; + list_del_init(&qh->qh_list_entry); + } else { + deschedule_periodic(hcd, qh); + } +} + +/** + * Defers a QH. For non-periodic QHs, removes the QH from the active + * non-periodic schedule. The QH is added to the deferred non-periodic + * schedule if any QTDs are still attached to the QH. + */ +int dwc_otg_hcd_qh_deferr(struct dwc_hcd *hcd, struct dwc_qh *qh, int delay) +{ + int deact = 1; + + if (dwc_qh_is_non_per(qh)) { + qh->sched_frame = dwc_frame_num_inc(hcd->frame_number, delay); + qh->channel = NULL; + qh->qtd_in_process = NULL; + deact = 0; + dwc_otg_hcd_qh_remove(hcd, qh); + if (!list_empty(&qh->qtd_list)) + /* Add back to deferred non-periodic schedule. */ + dwc_otg_hcd_qh_add_deferred(hcd, qh); + } + return deact; +} + +/** + * Schedule the next continuing periodic split transfer + */ +static void sched_next_per_split_xfr(struct dwc_qh *qh, u16 fr_num, + int sched_split) +{ + if (sched_split) { + qh->sched_frame = fr_num; + if (dwc_frame_num_le(fr_num, + dwc_frame_num_inc(qh->start_split_frame, + 1))) { + /* + * Allow one frame to elapse after start split + * microframe before scheduling complete split, but DONT + * if we are doing the next start split in the + * same frame for an ISOC out. + */ + if (qh->ep_type != USB_ENDPOINT_XFER_ISOC || + qh->ep_is_in) + qh->sched_frame = + dwc_frame_num_inc(qh->sched_frame, 1); + } + } else { + qh->sched_frame = dwc_frame_num_inc(qh->start_split_frame, + qh->interval); + + if (dwc_frame_num_le(qh->sched_frame, fr_num)) + qh->sched_frame = fr_num; + qh->sched_frame |= 0x7; + qh->start_split_frame = qh->sched_frame; + } +} + +/** + * Deactivates a periodic QH. The QH is removed from the periodic queued + * schedule. If there are any QTDs still attached to the QH, the QH is added to + * either the periodic inactive schedule or the periodic ready schedule and its + * next scheduled frame is calculated. The QH is placed in the ready schedule if + * the scheduled frame has been reached already. Otherwise it's placed in the + * inactive schedule. If there are no QTDs attached to the QH, the QH is + * completely removed from the periodic schedule. + */ +static void deactivate_periodic_qh(struct dwc_hcd *hcd, struct dwc_qh *qh, + int sched_next_split) +{ + /* unsigned long flags; */ + u16 fr_num = dwc_otg_hcd_get_frame_number(dwc_otg_hcd_to_hcd(hcd)); + + if (qh->do_split) { + sched_next_per_split_xfr(qh, fr_num, sched_next_split); + } else { + qh->sched_frame = dwc_frame_num_inc(qh->sched_frame, + qh->interval); + if (dwc_frame_num_le(qh->sched_frame, fr_num)) + qh->sched_frame = fr_num; + } + + if (list_empty(&qh->qtd_list)) { + dwc_otg_hcd_qh_remove(hcd, qh); + } else { + /* + * Remove from periodic_sched_queued and move to appropriate + * queue. + */ + if (qh->sched_frame == fr_num) + list_move(&qh->qh_list_entry, + &hcd->periodic_sched_ready); + else + list_move(&qh->qh_list_entry, + &hcd->periodic_sched_inactive); + } +} + +/** + * Deactivates a non-periodic QH. Removes the QH from the active non-periodic + * schedule. The QH is added to the inactive non-periodic schedule if any QTDs + * are still attached to the QH. + */ +static void deactivate_non_periodic_qh(struct dwc_hcd *hcd, struct dwc_qh *qh) +{ + dwc_otg_hcd_qh_remove(hcd, qh); + if (!list_empty(&qh->qtd_list)) + dwc_otg_hcd_qh_add(hcd, qh); +} + +/** + * Deactivates a QH. Determines if the QH is periodic or non-periodic and takes + * the appropriate action. + */ +void dwc_otg_hcd_qh_deactivate(struct dwc_hcd *hcd, struct dwc_qh *qh, + int sched_next_periodic_split) +{ + if (dwc_qh_is_non_per(qh)) + deactivate_non_periodic_qh(hcd, qh); + else + deactivate_periodic_qh(hcd, qh, sched_next_periodic_split); +} + +/** + * Initializes a QTD structure. + */ +static void dwc_otg_hcd_qtd_init(struct dwc_qtd *qtd, struct urb *urb) +{ + memset(qtd, 0, sizeof(struct dwc_qtd)); + qtd->urb = urb; + + if (usb_pipecontrol(urb->pipe)) { + /* + * The only time the QTD data toggle is used is on the data + * phase of control transfers. This phase always starts with + * DATA1. + */ + qtd->data_toggle = DWC_OTG_HC_PID_DATA1; + qtd->control_phase = DWC_OTG_CONTROL_SETUP; + } + + /* start split */ + qtd->complete_split = 0; + qtd->isoc_split_pos = DWC_HCSPLIT_XACTPOS_ALL; + qtd->isoc_split_offset = 0; + + /* Store the qtd ptr in the urb to reference what QTD. */ + urb->hcpriv = qtd; + + INIT_LIST_HEAD(&qtd->qtd_list_entry); + return; +} + +/* Allocates memory for a QTD structure. */ +static inline struct dwc_qtd *dwc_otg_hcd_qtd_alloc(gfp_t _mem_flags) +{ + return kmalloc(sizeof(struct dwc_qtd), _mem_flags); +} + +/** + * This function allocates and initializes a QTD. + */ +struct dwc_qtd *dwc_otg_hcd_qtd_create(struct urb *urb, gfp_t _mem_flags) +{ + struct dwc_qtd *qtd = dwc_otg_hcd_qtd_alloc(_mem_flags); + + if (!qtd) + return NULL; + + dwc_otg_hcd_qtd_init(qtd, urb); + return qtd; +} + +/** + * This function adds a QTD to the QTD-list of a QH. It will find the correct + * QH to place the QTD into. If it does not find a QH, then it will create a + * new QH. If the QH to which the QTD is added is not currently scheduled, it + * is placed into the proper schedule based on its EP type. + * + */ +int dwc_otg_hcd_qtd_add(struct dwc_qtd *qtd, struct dwc_hcd *hcd) +{ + struct usb_host_endpoint *ep; + struct dwc_qh *qh; + int retval = 0; + struct urb *urb = qtd->urb; + + /* + * Get the QH which holds the QTD-list to insert to. Create QH if it + * doesn't exist. + */ + ep = dwc_urb_to_endpoint(urb); + + qh = (struct dwc_qh *)ep->hcpriv; + if (!qh) { + qh = dwc_otg_hcd_qh_create(hcd, urb); + if (!qh) { + retval = -1; + goto done; + } + ep->hcpriv = qh; + } + qtd->qtd_qh_ptr = qh; + retval = dwc_otg_hcd_qh_add(hcd, qh); + if (!retval) + list_add_tail(&qtd->qtd_list_entry, &qh->qtd_list); + +done: + return retval; +} diff --git a/drivers/usb/dwc/param.c b/drivers/usb/dwc/param.c new file mode 100644 index 0000000..0dc73d2 --- /dev/null +++ b/drivers/usb/dwc/param.c @@ -0,0 +1,180 @@ +/* + * DesignWare HS OTG controller driver + * Copyright (C) 2006 Synopsys, Inc. + * Portions Copyright (C) 2010 Applied Micro Circuits Corporation. + * + * This program 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. + * + * 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 version 2 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see http://www.gnu.org/licenses + * or write to the Free Software Foundation, Inc., 51 Franklin Street, + * Suite 500, Boston, MA 02110-1335 USA. + * + * Based on Synopsys driver version 2.60a + * Modified by Mark Miesfeld <mmiesfeld@apm.com> + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL SYNOPSYS, INC. BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES + * (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/* + * This file provides dwc_otg driver parameter and parameter checking. + */ + +#include "cil.h" + +/* + * Encapsulate the module parameter settings + */ +struct core_params dwc_otg_module_params = { + .otg_cap = -1, + .dma_enable = -1, + .dma_burst_size = -1, + .speed = -1, + .host_support_fs_ls_low_power = -1, + .host_ls_low_power_phy_clk = -1, + .enable_dynamic_fifo = -1, + .dev_rx_fifo_size = -1, + .dev_nperio_tx_fifo_size = -1, + .dev_perio_tx_fifo_size = {-1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1}, /* 15 */ + .host_rx_fifo_size = -1, + .host_nperio_tx_fifo_size = -1, + .host_perio_tx_fifo_size = -1, + .max_transfer_size = -1, + .max_packet_count = -1, + .host_channels = -1, + .dev_endpoints = -1, + .phy_type = -1, + .phy_utmi_width = -1, + .phy_ulpi_ddr = -1, + .phy_ulpi_ext_vbus = -1, + .i2c_enable = -1, + .ulpi_fs_ls = -1, + .ts_dline = -1, + .en_multiple_tx_fifo = -1, + .dev_tx_fifo_size = {-1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1}, /* 15 */ + .thr_ctl = -1, + .tx_thr_length = -1, + .rx_thr_length = -1, +}; + +/** + * Checks that parameter settings for the periodic Tx FIFO sizes are correct + * according to the hardware configuration. Sets the size to the hardware + * configuration if an incorrect size is detected. + */ +static int set_valid_perio_tx_fifo_sizes(struct core_if *core_if) +{ + ulong regs = (u32) core_if->core_global_regs; + u32 *param_size = &dwc_otg_module_params.dev_perio_tx_fifo_size[0]; + u32 i, size; + + for (i = 0; i < MAX_PERIO_FIFOS; i++, param_size++) { + size = dwc_reg_read(regs, DWC_DPTX_FSIZ_DIPTXF(i)); + *param_size = size; + } + return 0; +} + +/** + * Checks that parameter settings for the Tx FIFO sizes are correct according to + * the hardware configuration. Sets the size to the hardware configuration if + * an incorrect size is detected. + */ +static int set_valid_tx_fifo_sizes(struct core_if *core_if) +{ + ulong regs = (u32) core_if->core_global_regs; + u32 *param_size = &dwc_otg_module_params.dev_tx_fifo_size[0]; + u32 i, size; + + for (i = 0; i < MAX_TX_FIFOS; i++, param_size) { + size = dwc_reg_read(regs, DWC_DPTX_FSIZ_DIPTXF(i)); + *param_size = size; + } + return 0; +} + +/** + * This function is called during module intialization to verify that + * the module parameters are in a valid state. + */ +int check_parameters(struct core_if *core_if) +{ + /* Default values */ + dwc_otg_module_params.otg_cap = dwc_param_otg_cap_default; + dwc_otg_module_params.dma_enable = dwc_param_dma_enable_default; + dwc_otg_module_params.speed = dwc_param_speed_default; + dwc_otg_module_params.host_support_fs_ls_low_power = + dwc_param_host_support_fs_ls_low_power_default; + dwc_otg_module_params.host_ls_low_power_phy_clk = + dwc_param_host_ls_low_power_phy_clk_default; + dwc_otg_module_params.phy_type = dwc_param_phy_type_default; + dwc_otg_module_params.phy_ulpi_ddr = dwc_param_phy_ulpi_ddr_default; + dwc_otg_module_params.phy_ulpi_ext_vbus = + dwc_param_phy_ulpi_ext_vbus_default; + dwc_otg_module_params.i2c_enable = dwc_param_i2c_enable_default; + dwc_otg_module_params.ulpi_fs_ls = dwc_param_ulpi_fs_ls_default; + dwc_otg_module_params.ts_dline = dwc_param_ts_dline_default; + + dwc_otg_module_params.dma_burst_size = dwc_param_dma_burst_size_default; + dwc_otg_module_params.phy_utmi_width = dwc_param_phy_utmi_width_default; + dwc_otg_module_params.thr_ctl = dwc_param_thr_ctl_default; + dwc_otg_module_params.tx_thr_length = dwc_param_tx_thr_length_default; + dwc_otg_module_params.rx_thr_length = dwc_param_rx_thr_length_default; + + /* + * Hardware configurations of the OTG core. + */ + dwc_otg_module_params.enable_dynamic_fifo = + DWC_HWCFG2_DYN_FIFO_RD(core_if->hwcfg2); + dwc_otg_module_params.dev_rx_fifo_size = + dwc_reg_read(core_if->core_global_regs, DWC_GRXFSIZ); + dwc_otg_module_params.dev_nperio_tx_fifo_size = + dwc_reg_read(core_if->core_global_regs, DWC_GNPTXFSIZ) >> 16; + + dwc_otg_module_params.host_rx_fifo_size = + dwc_reg_read(core_if->core_global_regs, DWC_GRXFSIZ); + dwc_otg_module_params.host_nperio_tx_fifo_size = + dwc_reg_read(core_if->core_global_regs, DWC_GNPTXFSIZ) >> 16; + dwc_otg_module_params.host_perio_tx_fifo_size = + dwc_reg_read(core_if->core_global_regs, DWC_HPTXFSIZ) >> 16; + dwc_otg_module_params.max_transfer_size = + (1 << (DWC_HWCFG3_XFERSIZE_CTR_WIDTH_RD(core_if->hwcfg3) + 11)) + - 1; + dwc_otg_module_params.max_packet_count = + (1 << (DWC_HWCFG3_PKTSIZE_CTR_WIDTH_RD(core_if->hwcfg3) + 4)) + - 1; + + dwc_otg_module_params.host_channels = + DWC_HWCFG2_NO_HST_CHAN_RD(core_if->hwcfg2) + 1; + dwc_otg_module_params.dev_endpoints = + DWC_HWCFG2_NO_DEV_EP_RD(core_if->hwcfg2); + dwc_otg_module_params.en_multiple_tx_fifo = + (DWC_HWCFG4_DED_FIFO_ENA_RD(core_if->hwcfg4) == 0) + ? 0 : 1, 0; + set_valid_perio_tx_fifo_sizes(core_if); + set_valid_tx_fifo_sizes(core_if); + + return 0; +} + +module_param_named(dma_enable, dwc_otg_module_params.dma_enable, bool, 0444); +MODULE_PARM_DESC(dma_enable, "DMA Mode 0=Slave 1=DMA enabled"); diff --git a/drivers/usb/dwc/pcd.c b/drivers/usb/dwc/pcd.c new file mode 100644 index 0000000..cfcb0ba --- /dev/null +++ b/drivers/usb/dwc/pcd.c @@ -0,0 +1,1791 @@ +/* + * DesignWare HS OTG controller driver + * Copyright (C) 2006 Synopsys, Inc. + * Portions Copyright (C) 2010 Applied Micro Circuits Corporation. + * + * This program 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. + * + * 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 version 2 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see http://www.gnu.org/licenses + * or write to the Free Software Foundation, Inc., 51 Franklin Street, + * Suite 500, Boston, MA 02110-1335 USA. + * + * Based on Synopsys driver version 2.60a + * Modified by Mark Miesfeld <mmiesfeld@apm.com> + * Modified by Stefan Roese <sr@denx.de>, DENX Software Engineering + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL SYNOPSYS, INC. BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES + * (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/* + * This file implements the Peripheral Controller Driver. + * + * The Peripheral Controller Driver (PCD) is responsible for + * translating requests from the Function Driver into the appropriate + * actions on the DWC_otg controller. It isolates the Function Driver + * from the specifics of the controller by providing an API to the + * Function Driver. + * + * The Peripheral Controller Driver for Linux will implement the + * Gadget API, so that the existing Gadget drivers can be used. + * (Gadget Driver is the Linux terminology for a Function Driver.) + * + * The Linux Gadget API is defined in the header file linux/usb/gadget.h. The + * USB EP operations API is defined in the structure usb_ep_ops and the USB + * Controller API is defined in the structure usb_gadget_ops + * + * An important function of the PCD is managing interrupts generated + * by the DWC_otg controller. The implementation of the DWC_otg device + * mode interrupt service routines is in dwc_otg_pcd_intr.c. + */ + +#include <linux/dma-mapping.h> +#include <linux/delay.h> + +#include "pcd.h" + +/* + * Static PCD pointer for use in usb_gadget_register_driver and + * usb_gadget_unregister_driver. Initialized in dwc_otg_pcd_init. + */ +static struct dwc_pcd *s_pcd; + +static inline int need_stop_srp_timer(struct core_if *core_if) +{ + if (core_if->core_params->phy_type != DWC_PHY_TYPE_PARAM_FS || + !core_if->core_params->i2c_enable) + return core_if->srp_timer_started ? 1 : 0; + return 0; +} + +/** + * Tests if the module is set to FS or if the PHY_TYPE is FS. If so, then the + * gadget should not report as dual-speed capable. + */ +static inline int check_is_dual_speed(struct core_if *core_if) +{ + if (core_if->core_params->speed == DWC_SPEED_PARAM_FULL || + (DWC_HWCFG2_HS_PHY_TYPE_RD(core_if->hwcfg2) == 2 && + DWC_HWCFG2_P_2_P_RD(core_if->hwcfg2) == 1 && + core_if->core_params->ulpi_fs_ls)) + return 0; + return 1; +} + +/** + * Tests if driver is OTG capable. + */ +static inline int check_is_otg(struct core_if *core_if) +{ + if (DWC_HWCFG2_OP_MODE_RD(core_if->hwcfg2) == + DWC_HWCFG2_OP_MODE_NO_SRP_CAPABLE_DEVICE || + DWC_HWCFG2_OP_MODE_RD(core_if->hwcfg2) == + DWC_HWCFG2_OP_MODE_NO_SRP_CAPABLE_HOST || + DWC_HWCFG2_OP_MODE_RD(core_if->hwcfg2) == + DWC_HWCFG2_OP_MODE_SRP_CAPABLE_DEVICE || + DWC_HWCFG2_OP_MODE_RD(core_if->hwcfg2) == + DWC_HWCFG2_OP_MODE_SRP_CAPABLE_HOST) + return 0; + return 1; +} + +/** + * This function completes a request. It calls the request call back. + */ +void request_done(struct pcd_ep *ep, struct pcd_request *req, int status) +{ + unsigned stopped = ep->stopped; + + list_del_init(&req->queue); + if (req->req.status == -EINPROGRESS) + req->req.status = status; + else + status = req->req.status; + + if (GET_CORE_IF(ep->pcd)->dma_enable) { + if (req->mapped) { + dma_unmap_single(ep->pcd->gadget.dev.parent, + req->req.dma, req->req.length, + ep->dwc_ep.is_in ? DMA_TO_DEVICE : + DMA_FROM_DEVICE); + req->req.dma = DMA_ADDR_INVALID; + req->mapped = 0; + } else { + dma_sync_single_for_cpu(ep->pcd->gadget.dev.parent, + req->req.dma, req->req.length, + ep->dwc_ep. + is_in ? DMA_TO_DEVICE : + DMA_FROM_DEVICE); + } + } + + /* don't modify queue heads during completion callback */ + ep->stopped = 1; + spin_unlock(&ep->pcd->lock); + req->req.complete(&ep->ep, &req->req); + spin_lock(&ep->pcd->lock); + + if (ep->pcd->request_pending > 0) + --ep->pcd->request_pending; + ep->stopped = stopped; + + /* + * Added-sr: 2007-07-26 + * + * Finally, when the current request is done, mark this endpoint + * as not active, so that new requests can be processed. + */ + if (dwc_has_feature(GET_CORE_IF(ep->pcd), DWC_LIMITED_XFER)) + ep->dwc_ep.active = 0; +} + +/** + * This function terminates all the requsts in the EP request queue. + */ +void request_nuke(struct pcd_ep *ep) +{ + struct pcd_request *req; + + ep->stopped = 1; + + /* called with irqs blocked?? */ + while (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, struct pcd_request, queue); + request_done(ep, req, -ESHUTDOWN); + } +} + +/* + * The following sections briefly describe the behavior of the Gadget + * API endpoint operations implemented in the DWC_otg driver + * software. Detailed descriptions of the generic behavior of each of + * these functions can be found in the Linux header file + * include/linux/usb_gadget.h. + * + * The Gadget API provides wrapper functions for each of the function + * pointers defined in usb_ep_ops. The Gadget Driver calls the wrapper + * function, which then calls the underlying PCD function. The + * following sections are named according to the wrapper + * functions. Within each section, the corresponding DWC_otg PCD + * function name is specified. + * + */ + +/** + * This function assigns periodic Tx FIFO to an periodic EP in shared Tx FIFO + * mode + */ +static u32 assign_perio_tx_fifo(struct core_if *core_if) +{ + u32 mask = 1; + u32 i; + + for (i = 0; i < DWC_HWCFG4_NUM_DEV_PERIO_IN_EP_RD(core_if->hwcfg4); + ++i) { + if (!(mask & core_if->p_tx_msk)) { + core_if->p_tx_msk |= mask; + return i + 1; + } + mask <<= 1; + } + return 0; +} + +/** + * This function releases periodic Tx FIFO in shared Tx FIFO mode + */ +static void release_perio_tx_fifo(struct core_if *core_if, u32 fifo_num) +{ + core_if->p_tx_msk = (core_if->p_tx_msk & (1 << (fifo_num - 1))) + ^ core_if->p_tx_msk; +} + +/** + * This function assigns periodic Tx FIFO to an periodic EP in shared Tx FIFO + * mode + */ +static u32 assign_tx_fifo(struct core_if *core_if) +{ + u32 mask = 1; + u32 i; + + for (i = 0; i < DWC_HWCFG4_NUM_IN_EPS_RD(core_if->hwcfg4); ++i) { + if (!(mask & core_if->tx_msk)) { + core_if->tx_msk |= mask; + return i + 1; + } + mask <<= 1; + } + return 0; +} + +/** + * This function releases periodic Tx FIFO in shared Tx FIFO mode + */ +static void release_tx_fifo(struct core_if *core_if, u32 fifo_num) +{ + core_if->tx_msk = (core_if->tx_msk & (1 << (fifo_num - 1))) + ^ core_if->tx_msk; +} + +/** + * Sets an in endpoint's tx fifo based on the hardware configuration. + */ +static void set_in_ep_tx_fifo(struct dwc_pcd *pcd, struct pcd_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + if (pcd->otg_dev->core_if->en_multiple_tx_fifo) { + ep->dwc_ep.tx_fifo_num = assign_tx_fifo(pcd->otg_dev->core_if); + } else { + ep->dwc_ep.tx_fifo_num = 0; + + /* If ISOC EP then assign a Periodic Tx FIFO. */ + if ((desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == + USB_ENDPOINT_XFER_ISOC) + ep->dwc_ep.tx_fifo_num = + assign_perio_tx_fifo(pcd->otg_dev->core_if); + } +} + +/** + * This function activates an EP. The Device EP control register for + * the EP is configured as defined in the ep structure. Note: This function is + * not used for EP0. + */ +void dwc_otg_ep_activate(struct core_if *core_if, struct dwc_ep *ep) +{ + struct device_if *dev_if = core_if->dev_if; + u32 depctl = 0; + ulong addr; + u32 daintmsk = 0; + + /* Read DEPCTLn register */ + if (ep->is_in == 1) { + addr = dev_if->in_ep_regs[ep->num] + DWC_DIEPCTL; + daintmsk = DWC_DAINTMSK_IN_EP_RW(daintmsk, ep->num); + } else { + addr = dev_if->out_ep_regs[ep->num] + DWC_DOEPCTL; + daintmsk = DWC_DAINTMSK_OUT_EP_RW(daintmsk, ep->num); + } + + /* If the EP is already active don't change the EP Control register */ + depctl = dwc_reg_read(addr, 0); + if (!DWC_DEPCTL_ACT_EP_RD(depctl)) { + depctl = DWC_DEPCTL_MPS_RW(depctl, ep->maxpacket); + depctl = DWC_DEPCTL_EP_TYPE_RW(depctl, ep->type); + depctl = DWC_DEPCTL_TX_FIFO_NUM_RW(depctl, ep->tx_fifo_num); + depctl = DWC_DEPCTL_SET_DATA0_PID_RW(depctl, 1); + depctl = DWC_DEPCTL_ACT_EP_RW(depctl, 1); + dwc_reg_write(addr, 0, depctl); + } + + /* Enable the Interrupt for this EP */ + dwc_reg_modify(dev_if->dev_global_regs, DWC_DAINTMSK, 0, daintmsk); + + ep->stall_clear_flag = 0; +} + +/** + * This function is called by the Gadget Driver for each EP to be + * configured for the current configuration (SET_CONFIGURATION). + * + * This function initializes the dwc_otg_ep_t data structure, and then + * calls dwc_otg_ep_activate. + */ +static int dwc_otg_pcd_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct pcd_ep *ep; + struct dwc_pcd *pcd; + unsigned long flags; + + ep = container_of(_ep, struct pcd_ep, ep); + if (!_ep || !desc || ep->desc || desc->bDescriptorType != + USB_DT_ENDPOINT) { + pr_warning("%s, bad ep or descriptor\n", __func__); + return -EINVAL; + } + + if (ep == &ep->pcd->ep0) { + pr_warning("%s, bad ep(0)\n", __func__); + return -EINVAL; + } + + /* Check FIFO size */ + if (!desc->wMaxPacketSize) { + pr_warning("%s, bad %s maxpacket\n", __func__, _ep->name); + return -ERANGE; + } + + pcd = ep->pcd; + if (!pcd->driver || pcd->gadget.speed == USB_SPEED_UNKNOWN) { + pr_warning("%s, bogus device state\n", __func__); + return -ESHUTDOWN; + } + + spin_lock_irqsave(&pcd->lock, flags); + ep->desc = desc; + ep->ep.maxpacket = le16_to_cpu(desc->wMaxPacketSize); + + /* Activate the EP */ + ep->stopped = 0; + ep->wedged = 0; + ep->dwc_ep.is_in = (USB_DIR_IN & desc->bEndpointAddress) != 0; + ep->dwc_ep.maxpacket = ep->ep.maxpacket; + ep->dwc_ep.type = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + + if (ep->dwc_ep.is_in) + set_in_ep_tx_fifo(pcd, ep, desc); + + /* Set initial data PID. */ + if ((desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == + USB_ENDPOINT_XFER_BULK) + ep->dwc_ep.data_pid_start = 0; + + dwc_otg_ep_activate(GET_CORE_IF(pcd), &ep->dwc_ep); + spin_unlock_irqrestore(&pcd->lock, flags); + return 0; +} + +/** + * This function deactivates an EP. This is done by clearing the USB Active EP + * bit in the Device EP control register. Note: This function is not used for + * EP0. EP0 cannot be deactivated. + */ +static void dwc_otg_ep_deactivate(struct core_if *core_if, struct dwc_ep *ep) +{ + u32 depctl = 0; + ulong addr; + u32 daintmsk = 0; + + /* Read DEPCTLn register */ + if (ep->is_in == 1) { + addr = core_if->dev_if->in_ep_regs[ep->num] + DWC_DIEPCTL; + daintmsk = DWC_DAINTMSK_IN_EP_RW(daintmsk, ep->num); + } else { + addr = + core_if->dev_if->out_ep_regs[ep->num] + DWC_DOEPCTL; + daintmsk = DWC_DAINTMSK_OUT_EP_RW(daintmsk, ep->num); + } + + depctl = DWC_DEPCTL_ACT_EP_RW(depctl, 0); + dwc_reg_write(addr, 0, depctl); + + /* Disable the Interrupt for this EP */ + dwc_reg_modify(core_if->dev_if->dev_global_regs, DWC_DAINTMSK, + daintmsk, 0); +} + +/** + * This function is called when an EP is disabled due to disconnect or + * change in configuration. Any pending requests will terminate with a + * status of -ESHUTDOWN. + * + * This function modifies the dwc_otg_ep_t data structure for this EP, + * and then calls dwc_otg_ep_deactivate. + */ +static int dwc_otg_pcd_ep_disable(struct usb_ep *_ep) +{ + struct pcd_ep *ep; + struct core_if *core_if; + unsigned long flags; + + ep = container_of(_ep, struct pcd_ep, ep); + if (!_ep || !ep->desc) + return -EINVAL; + + core_if = ep->pcd->otg_dev->core_if; + + spin_lock_irqsave(&ep->pcd->lock, flags); + + request_nuke(ep); + dwc_otg_ep_deactivate(core_if, &ep->dwc_ep); + + ep->desc = NULL; + ep->stopped = 1; + if (ep->dwc_ep.is_in) { + release_perio_tx_fifo(core_if, ep->dwc_ep.tx_fifo_num); + release_tx_fifo(core_if, ep->dwc_ep.tx_fifo_num); + } + + spin_unlock_irqrestore(&ep->pcd->lock, flags); + + return 0; +} + +/** + * This function allocates a request object to use with the specified + * endpoint. + */ +static struct usb_request *dwc_otg_pcd_alloc_request(struct usb_ep *_ep, + gfp_t gfp_flags) +{ + struct pcd_request *req; + + if (!_ep) { + pr_warning("%s() Invalid EP\n", __func__); + return NULL; + } + + req = kzalloc(sizeof(struct pcd_request), gfp_flags); + if (!req) { + pr_warning("%s() request allocation failed\n", __func__); + return NULL; + } + + req->req.dma = DMA_ADDR_INVALID; + INIT_LIST_HEAD(&req->queue); + + return &req->req; +} + +/** + * This function frees a request object. + */ +static void dwc_otg_pcd_free_request(struct usb_ep *_ep, + struct usb_request *_req) +{ + struct pcd_request *req; + + if (!_ep || !_req) { + pr_warning("%s() nvalid ep or req argument\n", __func__); + return; + } + + req = container_of(_req, struct pcd_request, req); + kfree(req); +} + +/* + * In dedicated Tx FIFO mode, enable the Non-Periodic Tx FIFO empty interrupt. + * Otherwise, enable the Tx FIFO epmty interrupt. The data will be written into + * the fifo by the ISR. + */ +static void enable_tx_fifo_empty_intr(struct core_if *c_if, struct dwc_ep *ep) +{ + u32 intr_mask = 0; + struct device_if *d_if = c_if->dev_if; + ulong global_regs = c_if->core_global_regs; + + if (!c_if->en_multiple_tx_fifo) { + intr_mask |= DWC_INTMSK_NP_TXFIFO_EMPT; + dwc_reg_modify(global_regs, DWC_GINTSTS, intr_mask, 0); + dwc_reg_modify(global_regs, DWC_GINTMSK, intr_mask, intr_mask); + } else if (ep->xfer_len) { + /* Enable the Tx FIFO Empty Interrupt for this EP */ + u32 fifoemptymsk = 1 << ep->num; + dwc_reg_modify(d_if->dev_global_regs, + DWC_DTKNQR4FIFOEMPTYMSK, 0, fifoemptymsk); + } +} + +static void set_next_ep(struct device_if *dev_if, u8 num) +{ + u32 depctl = 0; + + depctl = dwc_reg_read(dev_if->in_ep_regs[0], 0) + DWC_DIEPCTL; + depctl = DWC_DEPCTL_NXT_EP_RW(depctl, num); + + dwc_reg_write((dev_if->in_ep_regs[0]), DWC_DIEPCTL, depctl); +} + +/** + * This function does the setup for a data transfer for an EP and + * starts the transfer. For an IN transfer, the packets will be loaded into the + * appropriate Tx FIFO in the ISR. For OUT transfers, the packets are unloaded + * from the Rx FIFO in the ISR. + * + */ +void dwc_otg_ep_start_transfer(struct core_if *c_if, struct dwc_ep *ep) +{ + u32 depctl = 0; + u32 deptsiz = 0; + struct device_if *d_if = c_if->dev_if; + ulong glbl_regs = c_if->core_global_regs; + + if (ep->is_in) { + ulong in_regs = d_if->in_ep_regs[ep->num]; + u32 gtxstatus; + + gtxstatus = dwc_reg_read(glbl_regs, DWC_GNPTXSTS); + if (!c_if->en_multiple_tx_fifo + && !DWC_GNPTXSTS_NPTXQSPCAVAIL_RD(gtxstatus)) + return; + + depctl = dwc_reg_read(in_regs, DWC_DIEPCTL); + deptsiz = dwc_reg_read(in_regs, DWC_DIEPTSIZ); + + /* Zero Length Packet? */ + if (!ep->xfer_len) { + deptsiz = DWC_DEPTSIZ_XFER_SIZ_RW(deptsiz, 0); + deptsiz = DWC_DEPTSIZ_PKT_CNT_RW(deptsiz, 1); + } else { + /* + * Program the transfer size and packet count as + * follows: + * + * xfersize = N * maxpacket + short_packet + * pktcnt = N + (short_packet exist ? 1 : 0) + */ + + /* + * Added-sr: 2007-07-26 + * + * Since the 405EZ (Ultra) only support 2047 bytes as + * max transfer size, we have to split up bigger + * transfers into multiple transfers of 1024 bytes sized + * messages. I happens often, that transfers of 4096 + * bytes are required (zero-gadget, + * file_storage-gadget). + */ + if (dwc_has_feature(c_if, DWC_LIMITED_XFER)) { + if (ep->xfer_len > MAX_XFER_LEN) { + ep->bytes_pending = ep->xfer_len + - MAX_XFER_LEN; + ep->xfer_len = MAX_XFER_LEN; + } + } + + deptsiz = + DWC_DEPTSIZ_XFER_SIZ_RW(deptsiz, ep->xfer_len); + deptsiz = + DWC_DEPTSIZ_PKT_CNT_RW(deptsiz, + ((ep->xfer_len - 1 + + ep->maxpacket) / + ep->maxpacket)); + } + dwc_reg_write(in_regs, DWC_DIEPTSIZ, deptsiz); + + if (c_if->dma_enable) + dwc_reg_write(in_regs, DWC_DIEPDMA, ep->dma_addr); + else if (ep->type != DWC_OTG_EP_TYPE_ISOC) + enable_tx_fifo_empty_intr(c_if, ep); + + /* EP enable, IN data in FIFO */ + depctl = DWC_DEPCTL_CLR_NAK_RW(depctl, 1); + depctl = DWC_DEPCTL_EPENA_RW(depctl, 1); + dwc_reg_write(in_regs, DWC_DIEPCTL, depctl); + + if (c_if->dma_enable) + set_next_ep(d_if, ep->num); + } else { + u32 out_regs = d_if->out_ep_regs[ep->num]; + + depctl = dwc_reg_read(out_regs, DWC_DOEPCTL); + deptsiz = dwc_reg_read(out_regs, DWC_DOEPTSIZ); + + /* + * Program the transfer size and packet count as follows: + * + * pktcnt = N + * xfersize = N * maxpacket + */ + if (!ep->xfer_len) { + deptsiz = + DWC_DEPTSIZ_XFER_SIZ_RW(deptsiz, ep->maxpacket); + deptsiz = DWC_DEPTSIZ_PKT_CNT_RW(deptsiz, 1); + } else { + deptsiz = DWC_DEPTSIZ_PKT_CNT_RW(deptsiz, + ((ep->xfer_len + + ep->maxpacket - + 1) / ep->maxpacket)); + deptsiz = + DWC_DEPTSIZ_XFER_SIZ_RW(deptsiz, + DWC_DEPTSIZ_PKT_CNT_RD + (deptsiz) * ep->maxpacket); + } + dwc_reg_write(out_regs, DWC_DOEPTSIZ, deptsiz); + + if (c_if->dma_enable) + dwc_reg_write(out_regs, DWC_DOEPDMA, ep->dma_addr); + + if (ep->type == DWC_OTG_EP_TYPE_ISOC) { + if (ep->even_odd_frame) + depctl = DWC_DEPCTL_SET_DATA1_PID_RW(depctl, 1); + else + depctl = DWC_DEPCTL_SET_DATA0_PID_RW(depctl, 1); + } + + /* EP enable */ + depctl = DWC_DEPCTL_CLR_NAK_RW(depctl, 1); + depctl = DWC_DEPCTL_EPENA_RW(depctl, 1); + dwc_reg_write(out_regs, DWC_DOEPCTL, depctl); + } +} + +/** + * This function does the setup for a data transfer for EP0 and starts + * the transfer. For an IN transfer, the packets will be loaded into + * the appropriate Tx FIFO in the ISR. For OUT transfers, the packets are + * unloaded from the Rx FIFO in the ISR. + */ +void dwc_otg_ep0_start_transfer(struct core_if *c_if, struct dwc_ep *ep) +{ + u32 depctl = 0; + u32 deptsiz = 0; + struct device_if *d_if = c_if->dev_if; + ulong glbl_regs = c_if->core_global_regs; + + ep->total_len = ep->xfer_len; + + if (ep->is_in) { + ulong in_regs = d_if->in_ep_regs[0]; + u32 gtxstatus; + + gtxstatus = dwc_reg_read(glbl_regs, DWC_GNPTXSTS); + + if (!c_if->en_multiple_tx_fifo + && !DWC_GNPTXSTS_NPTXQSPCAVAIL_RD(gtxstatus)) + return; + + depctl = dwc_reg_read(in_regs, DWC_DIEPCTL); + deptsiz = dwc_reg_read(in_regs, DWC_DIEPTSIZ); + + /* Zero Length Packet? */ + if (!ep->xfer_len) { + deptsiz = DWC_DEPTSIZ0_XFER_SIZ_RW(deptsiz, 0); + deptsiz = DWC_DEPTSIZ0_PKT_CNT_RW(deptsiz, 1); + } else { + /* + * Program the transfer size and packet count as + * follows: + * + * xfersize = N * maxpacket + short_packet + * pktcnt = N + (short_packet exist ? 1 : 0) + */ + if (ep->xfer_len > ep->maxpacket) { + ep->xfer_len = ep->maxpacket; + deptsiz = DWC_DEPTSIZ0_XFER_SIZ_RW(deptsiz, + ep-> + maxpacket); + } else { + deptsiz = DWC_DEPTSIZ0_XFER_SIZ_RW(deptsiz, + ep-> + xfer_len); + } + deptsiz = DWC_DEPTSIZ0_PKT_CNT_RW(deptsiz, 1); + } + dwc_reg_write(in_regs, DWC_DIEPTSIZ, deptsiz); + + if (c_if->dma_enable) + dwc_reg_write(in_regs, DWC_DIEPDMA, ep->dma_addr); + + /* EP enable, IN data in FIFO */ + depctl = DWC_DEPCTL_CLR_NAK_RW(depctl, 1); + depctl = DWC_DEPCTL_EPENA_RW(depctl, 1); + dwc_reg_write(in_regs, DWC_DIEPCTL, depctl); + + if (!c_if->dma_enable) + enable_tx_fifo_empty_intr(c_if, ep); + } else { + ulong out_regs = d_if->out_ep_regs[ep->num]; + + depctl = dwc_reg_read(out_regs, DWC_DOEPCTL); + deptsiz = dwc_reg_read(out_regs, DWC_DOEPTSIZ); + + /* + * Program the transfer size and packet count as follows: + * + * xfersize = N * (maxpacket + 4 - (maxpacket % 4)) + * pktcnt = N + */ + if (!ep->xfer_len) { + deptsiz = DWC_DEPTSIZ0_XFER_SIZ_RW(deptsiz, + ep->maxpacket); + deptsiz = DWC_DEPTSIZ0_PKT_CNT_RW(deptsiz, 1); + } else { + deptsiz = DWC_DEPTSIZ0_PKT_CNT_RW(deptsiz, + (ep->xfer_len + + ep->maxpacket - + 1) / ep->maxpacket); + deptsiz = + DWC_DEPTSIZ0_XFER_SIZ_RW(deptsiz, + DWC_DEPTSIZ_PKT_CNT_RD + (deptsiz) * ep->maxpacket); + } + dwc_reg_write(out_regs, DWC_DOEPTSIZ, deptsiz); + + if (c_if->dma_enable) + dwc_reg_write(out_regs, DWC_DOEPDMA, ep->dma_addr); + + /* EP enable */ + depctl = DWC_DEPCTL_CLR_NAK_RW(depctl, 1); + depctl = DWC_DEPCTL_EPENA_RW(depctl, 1); + dwc_reg_write(out_regs, DWC_DOEPCTL, depctl); + } +} + +/** + * This function is used to submit an I/O Request to an EP. + * + * - When the request completes the request's completion callback + * is called to return the request to the driver. + * - An EP, except control EPs, may have multiple requests + * pending. + * - Once submitted the request cannot be examined or modified. + * - Each request is turned into one or more packets. + * - A BULK EP can queue any amount of data; the transfer is + * packetized. + * - Zero length Packets are specified with the request 'zero' + * flag. + */ +static int dwc_otg_pcd_ep_queue(struct usb_ep *_ep, struct usb_request *_req, + gfp_t gfp_flags) +{ + int prevented = 0; + struct pcd_request *req; + struct pcd_ep *ep; + struct dwc_pcd *pcd; + struct core_if *core_if; + unsigned long flags = 0; + + req = container_of(_req, struct pcd_request, req); + if (!_req || !_req->complete || !_req->buf || + !list_empty(&req->queue)) { + pr_warning("%s, bad params\n", __func__); + return -EINVAL; + } + + ep = container_of(_ep, struct pcd_ep, ep); + if (!_ep || (!ep->desc && ep->dwc_ep.num != 0)) { + pr_warning("%s, bad ep\n", __func__); + return -EINVAL; + } + + pcd = ep->pcd; + if (!pcd->driver || pcd->gadget.speed == USB_SPEED_UNKNOWN) { + pr_warning("%s, bogus device state\n", __func__); + return -ESHUTDOWN; + } + core_if = pcd->otg_dev->core_if; + + if (GET_CORE_IF(pcd)->dma_enable) { + if (_req->dma == DMA_ADDR_INVALID) { + _req->dma = dma_map_single(pcd->gadget.dev.parent, + _req->buf, _req->length, + ep->dwc_ep. + is_in ? DMA_TO_DEVICE : + DMA_FROM_DEVICE); + req->mapped = 1; + } else { + dma_sync_single_for_device(pcd->gadget.dev.parent, + _req->dma, _req->length, + ep->dwc_ep. + is_in ? DMA_TO_DEVICE : + DMA_FROM_DEVICE); + req->mapped = 0; + } + } + + spin_lock_irqsave(&ep->pcd->lock, flags); + + _req->status = -EINPROGRESS; + _req->actual = 0; + + /* Start the transfer */ + if (list_empty(&ep->queue) && !ep->stopped) { + /* EP0 Transfer? */ + if (ep->dwc_ep.num == 0) { + switch (pcd->ep0state) { + case EP0_IN_DATA_PHASE: + break; + case EP0_OUT_DATA_PHASE: + if (pcd->request_config) { + /* Complete STATUS PHASE */ + ep->dwc_ep.is_in = 1; + pcd->ep0state = EP0_STATUS; + } + break; + default: + spin_unlock_irqrestore(&pcd->lock, flags); + return -EL2HLT; + } + + ep->dwc_ep.dma_addr = _req->dma; + ep->dwc_ep.start_xfer_buff = _req->buf; + ep->dwc_ep.xfer_buff = _req->buf; + ep->dwc_ep.xfer_len = _req->length; + ep->dwc_ep.xfer_count = 0; + ep->dwc_ep.sent_zlp = 0; + ep->dwc_ep.total_len = ep->dwc_ep.xfer_len; + + dwc_otg_ep0_start_transfer(core_if, &ep->dwc_ep); + } else { + /* Setup and start the Transfer */ + ep->dwc_ep.dma_addr = _req->dma; + ep->dwc_ep.start_xfer_buff = _req->buf; + ep->dwc_ep.xfer_buff = _req->buf; + ep->dwc_ep.xfer_len = _req->length; + ep->dwc_ep.xfer_count = 0; + ep->dwc_ep.sent_zlp = 0; + ep->dwc_ep.total_len = ep->dwc_ep.xfer_len; + + dwc_otg_ep_start_transfer(core_if, &ep->dwc_ep); + } + } + + if (req || prevented) { + ++pcd->request_pending; + list_add_tail(&req->queue, &ep->queue); + + if (ep->dwc_ep.is_in && ep->stopped && !core_if->dma_enable) { + /* + * Device IN endpoint interrupt mask register is laid + * out exactly the same as the device IN endpoint + * interrupt register. + */ + u32 diepmsk = 0; + diepmsk = DWC_DIEPMSK_IN_TKN_TX_EMPTY_RW(diepmsk, 1); + + dwc_reg_modify(core_if->dev_if->dev_global_regs, + DWC_DIEPMSK, 0, diepmsk); + } + } + + spin_unlock_irqrestore(&pcd->lock, flags); + return 0; +} + +/** + * This function cancels an I/O request from an EP. + */ +static int dwc_otg_pcd_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct pcd_request *req; + struct pcd_ep *ep; + struct dwc_pcd *pcd; + unsigned long flags; + + ep = container_of(_ep, struct pcd_ep, ep); + if (!_ep || !_req || (!ep->desc && ep->dwc_ep.num != 0)) { + pr_warning("%s, bad argument\n", __func__); + return -EINVAL; + } + + pcd = ep->pcd; + if (!pcd->driver || pcd->gadget.speed == USB_SPEED_UNKNOWN) { + pr_warning("%s, bogus device state\n", __func__); + return -ESHUTDOWN; + } + + spin_lock_irqsave(&pcd->lock, flags); + + /* make sure it's actually queued on this endpoint */ + list_for_each_entry(req, &ep->queue, queue) + if (&req->req == _req) + break; + + if (&req->req != _req) { + spin_unlock_irqrestore(&pcd->lock, flags); + return -EINVAL; + } + + if (!list_empty(&req->queue)) + request_done(ep, req, -ECONNRESET); + else + req = NULL; + + spin_unlock_irqrestore(&pcd->lock, flags); + + return req ? 0 : -EOPNOTSUPP; +} + +/** + * Set the EP STALL. + */ +void dwc_otg_ep_set_stall(struct core_if *core_if, struct dwc_ep *ep) +{ + u32 depctl = 0; + ulong depctl_addr; + + if (ep->is_in) { + depctl_addr = + (core_if->dev_if->in_ep_regs[ep->num]) + DWC_DIEPCTL; + depctl = dwc_reg_read(depctl_addr, 0); + + /* set the disable and stall bits */ + if (DWC_DEPCTL_EPENA_RD(depctl)) + depctl = DWC_DEPCTL_EPDIS_RW(depctl, 1); + depctl = DWC_DEPCTL_STALL_HNDSHK_RW(depctl, 1); + dwc_reg_write(depctl_addr, 0, depctl); + } else { + depctl_addr = + (core_if->dev_if->out_ep_regs[ep->num] + DWC_DOEPCTL); + depctl = dwc_reg_read(depctl_addr, 0); + + /* set the stall bit */ + depctl = DWC_DEPCTL_STALL_HNDSHK_RW(depctl, 1); + dwc_reg_write(depctl_addr, 0, depctl); + } +} + +/** + * Clear the EP STALL. + */ +void dwc_otg_ep_clear_stall(struct core_if *core_if, struct dwc_ep *ep) +{ + u32 depctl = 0; + ulong depctl_addr; + + if (ep->is_in == 1) + depctl_addr = + (core_if->dev_if->in_ep_regs[ep->num]) + DWC_DIEPCTL; + else + depctl_addr = + (core_if->dev_if->out_ep_regs[ep->num]) + DWC_DOEPCTL; + + depctl = dwc_reg_read(depctl_addr, 0); + + /* clear the stall bits */ + depctl = DWC_DEPCTL_STALL_HNDSHK_RW(depctl, 0); + + /* + * USB Spec 9.4.5: For endpoints using data toggle, regardless + * of whether an endpoint has the Halt feature set, a + * ClearFeature(ENDPOINT_HALT) request always results in the + * data toggle being reinitialized to DATA0. + */ + if (ep->type == DWC_OTG_EP_TYPE_INTR || + ep->type == DWC_OTG_EP_TYPE_BULK) + depctl = DWC_DEPCTL_SET_DATA0_PID_RW(depctl, 1); + + dwc_reg_write(depctl_addr, 0, depctl); +} + +/** + * usb_ep_set_halt stalls an endpoint. + * + * usb_ep_clear_halt clears an endpoint halt and resets its data + * toggle. + * + * Both of these functions are implemented with the same underlying + * function. The behavior depends on the val argument: + * - 0 means clear_halt. + * - 1 means set_halt, + * - 2 means clear stall lock flag. + * - 3 means set stall lock flag. + */ +static int dwc_otg_pcd_ep_set_halt_wedge(struct usb_ep *_ep, + int val, int wedged) +{ + int retval = 0; + unsigned long flags; + struct pcd_ep *ep; + + ep = container_of(_ep, struct pcd_ep, ep); + if (!_ep || (!ep->desc && ep != &ep->pcd->ep0) || + ep->desc->bmAttributes == USB_ENDPOINT_XFER_ISOC) { + pr_warning("%s, bad ep\n", __func__); + return -EINVAL; + } + + spin_lock_irqsave(&ep->pcd->lock, flags); + + if (ep->dwc_ep.is_in && !list_empty(&ep->queue)) { + pr_warning("%s() %s XFer In process\n", __func__, _ep->name); + retval = -EAGAIN; + } else if (val == 0) { + ep->wedged = 0; + dwc_otg_ep_clear_stall(ep->pcd->otg_dev->core_if, &ep->dwc_ep); + } else if (val == 1) { + if (ep->dwc_ep.num == 0) + ep->pcd->ep0state = EP0_STALL; + if (wedged) + ep->wedged = 1; + + ep->stopped = 1; + dwc_otg_ep_set_stall(ep->pcd->otg_dev->core_if, &ep->dwc_ep); + } else if (val == 2) { + ep->dwc_ep.stall_clear_flag = 0; + } else if (val == 3) { + ep->dwc_ep.stall_clear_flag = 1; + } + + spin_unlock_irqrestore(&ep->pcd->lock, flags); + return retval; +} +static int dwc_otg_pcd_ep_set_halt(struct usb_ep *_ep, int val) +{ + return dwc_otg_pcd_ep_set_halt_wedge(_ep, val, 0); +} +static int dwc_otg_pcd_ep_set_wedge(struct usb_ep *_ep) +{ + return dwc_otg_pcd_ep_set_halt_wedge(_ep, 1, 1); +} + +static struct usb_ep_ops dwc_otg_pcd_ep_ops = { + .enable = dwc_otg_pcd_ep_enable, + .disable = dwc_otg_pcd_ep_disable, + .alloc_request = dwc_otg_pcd_alloc_request, + .free_request = dwc_otg_pcd_free_request, + .queue = dwc_otg_pcd_ep_queue, + .dequeue = dwc_otg_pcd_ep_dequeue, + .set_halt = dwc_otg_pcd_ep_set_halt, + .set_wedge = dwc_otg_pcd_ep_set_wedge, + .fifo_status = NULL, + .fifo_flush = NULL, +}; + +/** + * Gets the current USB frame number from the DTS register. This is the frame + * number from the last SOF packet. + */ +static u32 dwc_otg_get_frame_number(struct core_if *core_if) +{ + u32 dsts; + + dsts = dwc_reg_read(core_if->dev_if->dev_global_regs, DWC_DSTS); + return DWC_DSTS_SOFFN_RD(dsts); +} + +/** + * The following gadget operations will be implemented in the DWC_otg + * PCD. Functions in the API that are not described below are not + * implemented. + * + * The Gadget API provides wrapper functions for each of the function + * pointers defined in usb_gadget_ops. The Gadget Driver calls the + * wrapper function, which then calls the underlying PCD function. The + * following sections are named according to the wrapper functions + * (except for ioctl, which doesn't have a wrapper function). Within + * each section, the corresponding DWC_otg PCD function name is + * specified. + * + */ + +/** + *Gets the USB Frame number of the last SOF. + */ +static int dwc_otg_pcd_get_frame(struct usb_gadget *_gadget) +{ + if (!_gadget) { + return -ENODEV; + } else { + struct dwc_pcd *pcd; + + pcd = container_of(_gadget, struct dwc_pcd, gadget); + dwc_otg_get_frame_number(GET_CORE_IF(pcd)); + } + + return 0; +} + +/** + * This function is called when the SRP timer expires. The SRP should complete + * within 6 seconds. + */ +static void srp_timeout(unsigned long data) +{ + u32 gotgctl; + struct dwc_pcd *pcd = (struct dwc_pcd *)data; + struct core_if *core_if = pcd->otg_dev->core_if; + ulong addr = otg_ctl_reg(pcd); + + gotgctl = dwc_reg_read(addr, 0); + core_if->srp_timer_started = 0; + + if (core_if->core_params->phy_type == DWC_PHY_TYPE_PARAM_FS && + core_if->core_params->i2c_enable) { + pr_info("SRP Timeout\n"); + + if (core_if->srp_success && (gotgctl & + DWC_GCTL_BSESSION_VALID)) { + if (core_if->pcd_cb && core_if->pcd_cb->resume_wakeup) + core_if->pcd_cb->resume_wakeup(core_if->pcd_cb-> + p); + + /* Clear Session Request */ + gotgctl = 0; + gotgctl |= DWC_GCTL_SES_REQ; + dwc_reg_modify(addr, 0, gotgctl, 0); + + core_if->srp_success = 0; + } else { + pr_err("Device not connected/responding\n"); + gotgctl &= ~DWC_GCTL_SES_REQ; + dwc_reg_write(addr, 0, gotgctl); + } + } else if (gotgctl & DWC_GCTL_SES_REQ) { + pr_info("SRP Timeout\n"); + pr_err("Device not connected/responding\n"); + + gotgctl &= ~DWC_GCTL_SES_REQ; + dwc_reg_write(addr, 0, gotgctl); + } else { + pr_info(" SRP GOTGCTL=%0x\n", gotgctl); + } +} + +/** + * Start the SRP timer to detect when the SRP does not complete within + * 6 seconds. + */ +static void dwc_otg_pcd_start_srp_timer(struct dwc_pcd *pcd) +{ + struct timer_list *srp_timer = &pcd->srp_timer; + + GET_CORE_IF(pcd)->srp_timer_started = 1; + init_timer(srp_timer); + srp_timer->function = srp_timeout; + srp_timer->data = (unsigned long)pcd; + srp_timer->expires = jiffies + (HZ * 6); + + add_timer(srp_timer); +} + +static void dwc_otg_pcd_initiate_srp(struct dwc_pcd *pcd) +{ + u32 mem; + u32 val; + ulong addr = otg_ctl_reg(pcd); + + val = dwc_reg_read(addr, 0); + if (val & DWC_GCTL_SES_REQ) { + pr_err("Session Request Already active!\n"); + return; + } + + pr_notice("Session Request Initated\n"); + mem = dwc_reg_read(addr, 0); + mem |= DWC_GCTL_SES_REQ; + dwc_reg_write(addr, 0, mem); + + /* Start the SRP timer */ + dwc_otg_pcd_start_srp_timer(pcd); + return; +} + +static void dwc_otg_pcd_remote_wakeup(struct dwc_pcd *pcd, int set) +{ + u32 dctl = 0; + ulong addr = dev_ctl_reg(pcd); + + if (dwc_otg_is_device_mode(GET_CORE_IF(pcd))) { + if (pcd->remote_wakeup_enable) { + if (set) { + dctl = DEC_DCTL_REMOTE_WAKEUP_SIG(dctl, 1); + dwc_reg_modify(addr, 0, 0, dctl); + msleep(20); + dwc_reg_modify(addr, 0, dctl, 0); + } + } + } +} + +/** + * Initiates Session Request Protocol (SRP) to wakeup the host if no + * session is in progress. If a session is already in progress, but + * the device is suspended, remote wakeup signaling is started. + * + */ +static int dwc_otg_pcd_wakeup(struct usb_gadget *_gadget) +{ + unsigned long flags; + struct dwc_pcd *pcd; + u32 dsts; + u32 gotgctl; + + if (!_gadget) + return -ENODEV; + else + pcd = container_of(_gadget, struct dwc_pcd, gadget); + + spin_lock_irqsave(&pcd->lock, flags); + + /* + * This function starts the Protocol if no session is in progress. If + * a session is already in progress, but the device is suspended, + * remote wakeup signaling is started. + */ + + /* Check if valid session */ + gotgctl = dwc_reg_read(otg_ctl_reg(pcd), 0); + if (gotgctl & DWC_GCTL_BSESSION_VALID) { + /* Check if suspend state */ + dsts = dwc_reg_read(dev_sts_reg(pcd), 0); + if (DWC_DSTS_SUSP_STS_RD(dsts)) + dwc_otg_pcd_remote_wakeup(pcd, 1); + } else { + dwc_otg_pcd_initiate_srp(pcd); + } + + spin_unlock_irqrestore(&pcd->lock, flags); + return 0; +} + +static int dwc_gadget_vbus_draw(struct usb_gadget *gadget, unsigned mA) +{ + struct dwc_pcd *pcd = container_of(gadget, struct dwc_pcd, gadget); + + if (pcd->otg_dev->core_if->xceiv) + return -EOPNOTSUPP; + + return otg_set_power(pcd->otg_dev->core_if->xceiv, mA); + +} +static int dwc_gadget_start(struct usb_gadget_driver *driver, + int (*bind) (struct usb_gadget *)); +static int dwc_gadget_stop(struct usb_gadget_driver *driver); + +static const struct usb_gadget_ops dwc_otg_pcd_ops = { + .get_frame = dwc_otg_pcd_get_frame, + .wakeup = dwc_otg_pcd_wakeup, + .start = dwc_gadget_start, + .stop = dwc_gadget_stop, + .vbus_draw = dwc_gadget_vbus_draw, +}; + +/** + * This function updates the otg values in the gadget structure. + */ +void dwc_otg_pcd_update_otg(struct dwc_pcd *pcd, const unsigned reset) +{ + if (!pcd->gadget.is_otg) + return; + + if (reset) { + pcd->b_hnp_enable = 0; + pcd->a_hnp_support = 0; + pcd->a_alt_hnp_support = 0; + } + + pcd->gadget.b_hnp_enable = pcd->b_hnp_enable; + pcd->gadget.a_hnp_support = pcd->a_hnp_support; + pcd->gadget.a_alt_hnp_support = pcd->a_alt_hnp_support; +} + +/** + * This function is the top level PCD interrupt handler. + */ +static irqreturn_t dwc_otg_pcd_irq(int _irq, void *dev) +{ + struct dwc_pcd *pcd = dev; + int retval; + + retval = dwc_otg_pcd_handle_intr(pcd); + return IRQ_RETVAL(retval); +} + +/** + * PCD Callback function for initializing the PCD when switching to + * device mode. + */ +static int dwc_otg_pcd_start_cb(void *_p) +{ + struct dwc_pcd *pcd = (struct dwc_pcd *)_p; + + /* Initialize the Core for Device mode. */ + if (dwc_otg_is_device_mode(GET_CORE_IF(pcd))) + dwc_otg_core_dev_init(GET_CORE_IF(pcd)); + + return 1; +} + +/** + * PCD Callback function for stopping the PCD when switching to Host + * mode. + */ +static int dwc_otg_pcd_stop_cb(void *_p) +{ + dwc_otg_pcd_stop((struct dwc_pcd *)_p); + return 1; +} + +/** + * PCD Callback function for notifying the PCD when resuming from + * suspend. + * + * @param _p void pointer to the <code>struct dwc_pcd</code> + */ +static int dwc_otg_pcd_suspend_cb(void *_p) +{ + struct dwc_pcd *pcd = (struct dwc_pcd *)_p; + + if (pcd->driver && pcd->driver->suspend) { + spin_unlock(&pcd->lock); + pcd->driver->suspend(&pcd->gadget); + spin_lock(&pcd->lock); + } + return 1; +} + +/** + * PCD Callback function for notifying the PCD when resuming from + * suspend. + */ +static int dwc_otg_pcd_resume_cb(void *_p) +{ + struct dwc_pcd *pcd = (struct dwc_pcd *)_p; + struct core_if *core_if = pcd->otg_dev->core_if; + + if (pcd->driver && pcd->driver->resume) { + spin_unlock(&pcd->lock); + pcd->driver->resume(&pcd->gadget); + spin_lock(&pcd->lock); + } + + /* Maybe stop the SRP timeout timer. */ + if (need_stop_srp_timer(core_if)) { + core_if->srp_timer_started = 0; + del_timer_sync(&pcd->srp_timer); + } + return 1; +} + +/** + * PCD Callback structure for handling mode switching. + */ +static struct cil_callbacks pcd_callbacks = { + .start = dwc_otg_pcd_start_cb, + .stop = dwc_otg_pcd_stop_cb, + .suspend = dwc_otg_pcd_suspend_cb, + .resume_wakeup = dwc_otg_pcd_resume_cb, + .p = NULL, /* Set at registration */ +}; + +/** + * Tasklet + * + */ +static void start_xfer_tasklet_func(unsigned long data) +{ + struct dwc_pcd *pcd = (struct dwc_pcd *)data; + u32 diepctl = 0; + int num = pcd->otg_dev->core_if->dev_if->num_in_eps; + u32 i; + unsigned long flags; + + spin_lock_irqsave(&pcd->lock, flags); + diepctl = dwc_reg_read(in_ep_ctl_reg(pcd, 0), 0); + + if (pcd->ep0.queue_sof) { + pcd->ep0.queue_sof = 0; + dwc_start_next_request(&pcd->ep0); + } + + for (i = 0; i < num; i++) { + u32 diepctl = 0; + + diepctl = dwc_reg_read(in_ep_ctl_reg(pcd, i), 0); + if (pcd->in_ep[i].queue_sof) { + pcd->in_ep[i].queue_sof = 0; + dwc_start_next_request(&pcd->in_ep[i]); + } + } + spin_unlock_irqrestore(&pcd->lock, flags); +} + +static struct tasklet_struct start_xfer_tasklet = { + .next = NULL, + .state = 0, + .count = ATOMIC_INIT(0), + .func = start_xfer_tasklet_func, + .data = 0, +}; + +/** + * This function initialized the pcd Dp structures to there default + * state. + */ +static void dwc_otg_pcd_reinit(struct dwc_pcd *pcd) +{ + static const char *names[] = { + "ep0", "ep1in", "ep2in", "ep3in", "ep4in", "ep5in", + "ep6in", "ep7in", "ep8in", "ep9in", "ep10in", "ep11in", + "ep12in", "ep13in", "ep14in", "ep15in", "ep1out", "ep2out", + "ep3out", "ep4out", "ep5out", "ep6out", "ep7out", "ep8out", + "ep9out", "ep10out", "ep11out", "ep12out", "ep13out", + "ep14out", "ep15out" + }; + u32 i; + int in_ep_cntr, out_ep_cntr; + u32 hwcfg1; + u32 num_in_eps = (GET_CORE_IF(pcd))->dev_if->num_in_eps; + u32 num_out_eps = (GET_CORE_IF(pcd))->dev_if->num_out_eps; + struct pcd_ep *ep; + + INIT_LIST_HEAD(&pcd->gadget.ep_list); + pcd->gadget.ep0 = &pcd->ep0.ep; + pcd->gadget.speed = USB_SPEED_UNKNOWN; + INIT_LIST_HEAD(&pcd->gadget.ep0->ep_list); + + /* Initialize the EP0 structure. */ + ep = &pcd->ep0; + + /* Init EP structure */ + ep->desc = NULL; + ep->pcd = pcd; + ep->stopped = 1; + + /* Init DWC ep structure */ + ep->dwc_ep.num = 0; + ep->dwc_ep.active = 0; + ep->dwc_ep.tx_fifo_num = 0; + + /* Control until ep is actvated */ + ep->dwc_ep.type = DWC_OTG_EP_TYPE_CONTROL; + ep->dwc_ep.maxpacket = MAX_PACKET_SIZE; + ep->dwc_ep.dma_addr = 0; + ep->dwc_ep.start_xfer_buff = NULL; + ep->dwc_ep.xfer_buff = NULL; + ep->dwc_ep.xfer_len = 0; + ep->dwc_ep.xfer_count = 0; + ep->dwc_ep.sent_zlp = 0; + ep->dwc_ep.total_len = 0; + ep->queue_sof = 0; + + /* Init the usb_ep structure. */ + ep->ep.name = names[0]; + ep->ep.ops = &dwc_otg_pcd_ep_ops; + + ep->ep.maxpacket = MAX_PACKET_SIZE; + list_add_tail(&ep->ep.ep_list, &pcd->gadget.ep_list); + INIT_LIST_HEAD(&ep->queue); + + /* Initialize the EP structures. */ + in_ep_cntr = 0; + hwcfg1 = (GET_CORE_IF(pcd))->hwcfg1 >> 3; + + for (i = 1; in_ep_cntr < num_in_eps; i++) { + if (!(hwcfg1 & 0x1)) { + struct pcd_ep *ep = &pcd->in_ep[in_ep_cntr]; + + in_ep_cntr++; + /* Init EP structure */ + ep->desc = NULL; + ep->pcd = pcd; + ep->stopped = 1; + + /* Init DWC ep structure */ + ep->dwc_ep.is_in = 1; + ep->dwc_ep.num = i; + ep->dwc_ep.active = 0; + ep->dwc_ep.tx_fifo_num = 0; + + /* Control until ep is actvated */ + ep->dwc_ep.type = DWC_OTG_EP_TYPE_CONTROL; + ep->dwc_ep.maxpacket = MAX_PACKET_SIZE; + ep->dwc_ep.dma_addr = 0; + ep->dwc_ep.start_xfer_buff = NULL; + ep->dwc_ep.xfer_buff = NULL; + ep->dwc_ep.xfer_len = 0; + ep->dwc_ep.xfer_count = 0; + ep->dwc_ep.sent_zlp = 0; + ep->dwc_ep.total_len = 0; + ep->queue_sof = 0; + + ep->ep.name = names[i]; + ep->ep.ops = &dwc_otg_pcd_ep_ops; + + ep->ep.maxpacket = MAX_PACKET_SIZE; + list_add_tail(&ep->ep.ep_list, &pcd->gadget.ep_list); + INIT_LIST_HEAD(&ep->queue); + } + hwcfg1 >>= 2; + } + + out_ep_cntr = 0; + hwcfg1 = (GET_CORE_IF(pcd))->hwcfg1 >> 2; + for (i = 1; out_ep_cntr < num_out_eps; i++) { + if (!(hwcfg1 & 0x1)) { + struct pcd_ep *ep = &pcd->out_ep[out_ep_cntr]; + + out_ep_cntr++; + /* Init EP structure */ + ep->desc = NULL; + ep->pcd = pcd; + ep->stopped = 1; + + /* Init DWC ep structure */ + ep->dwc_ep.is_in = 0; + ep->dwc_ep.num = i; + ep->dwc_ep.active = 0; + ep->dwc_ep.tx_fifo_num = 0; + + /* Control until ep is actvated */ + ep->dwc_ep.type = DWC_OTG_EP_TYPE_CONTROL; + ep->dwc_ep.maxpacket = MAX_PACKET_SIZE; + ep->dwc_ep.dma_addr = 0; + ep->dwc_ep.start_xfer_buff = NULL; + ep->dwc_ep.xfer_buff = NULL; + ep->dwc_ep.xfer_len = 0; + ep->dwc_ep.xfer_count = 0; + ep->dwc_ep.sent_zlp = 0; + ep->dwc_ep.total_len = 0; + ep->queue_sof = 0; + + ep->ep.name = names[15 + i]; + ep->ep.ops = &dwc_otg_pcd_ep_ops; + + ep->ep.maxpacket = MAX_PACKET_SIZE; + list_add_tail(&ep->ep.ep_list, &pcd->gadget.ep_list); + INIT_LIST_HEAD(&ep->queue); + } + hwcfg1 >>= 2; + } + + /* remove ep0 from the list. There is a ep0 pointer. */ + list_del_init(&pcd->ep0.ep.ep_list); + + pcd->ep0state = EP0_DISCONNECT; + pcd->ep0.ep.maxpacket = MAX_EP0_SIZE; + pcd->ep0.dwc_ep.maxpacket = MAX_EP0_SIZE; + pcd->ep0.dwc_ep.type = DWC_OTG_EP_TYPE_CONTROL; +} + +/** + * This function releases the Gadget device. + * required by device_unregister(). + */ +static void dwc_otg_pcd_gadget_release(struct device *dev) +{ + pr_info("%s(%p)\n", __func__, dev); +} + +/** + * Allocates the buffers for the setup packets when the PCD portion of the + * driver is first initialized. + */ +static int init_pkt_buffs(struct device *dev, struct dwc_pcd *pcd) +{ + if (pcd->otg_dev->core_if->dma_enable) { + pcd->dwc_pool = dma_pool_create("dwc_otg_pcd", dev, + sizeof(*pcd->setup_pkt) * 5, 32, + 0); + if (!pcd->dwc_pool) + return -ENOMEM; + pcd->setup_pkt = dma_pool_alloc(pcd->dwc_pool, GFP_KERNEL, + &pcd->setup_pkt_dma_handle); + if (!pcd->setup_pkt) + goto error; + pcd->status_buf = dma_pool_alloc(pcd->dwc_pool, GFP_KERNEL, + &pcd->status_buf_dma_handle); + if (!pcd->status_buf) + goto error1; + } else { + pcd->setup_pkt = kmalloc(sizeof(*pcd->setup_pkt) * 5, + GFP_KERNEL); + if (!pcd->setup_pkt) + return -ENOMEM; + pcd->status_buf = kmalloc(sizeof(u16), GFP_KERNEL); + if (!pcd->status_buf) { + kfree(pcd->setup_pkt); + return -ENOMEM; + } + } + return 0; + +error1: + dma_pool_free(pcd->dwc_pool, pcd->setup_pkt, pcd->setup_pkt_dma_handle); +error: + dma_pool_destroy(pcd->dwc_pool); + return -ENOMEM; +} + +/** + * This function initializes the PCD portion of the driver. + */ +int dwc_otg_pcd_init(struct device *dev) +{ + static const char pcd_name[] = "dwc_otg_pcd"; + struct dwc_pcd *pcd; + struct dwc_otg_device *otg_dev = dev_get_drvdata(dev); + struct core_if *core_if = otg_dev->core_if; + int retval; + + /* Allocate PCD structure */ + pcd = kzalloc(sizeof(*pcd), GFP_KERNEL); + if (!pcd) { + retval = -ENOMEM; + goto err; + } + + spin_lock_init(&pcd->lock); + + otg_dev->pcd = pcd; + s_pcd = pcd; + pcd->gadget.name = pcd_name; + + dev_set_name(&pcd->gadget.dev, "gadget"); + pcd->otg_dev = otg_dev; + pcd->gadget.dev.parent = dev; + pcd->gadget.dev.release = dwc_otg_pcd_gadget_release; + pcd->gadget.ops = &dwc_otg_pcd_ops; + + if (DWC_HWCFG4_DED_FIFO_ENA_RD(core_if->hwcfg4)) + pr_info("Dedicated Tx FIFOs mode\n"); + else + pr_info("Shared Tx FIFO mode\n"); + + pcd->gadget.is_dualspeed = check_is_dual_speed(core_if); + pcd->gadget.is_otg = check_is_otg(core_if); + + /* Register the gadget device */ + retval = device_register(&pcd->gadget.dev); + if (retval) + goto unreg_device; + + + /* hook up the gadget*/ + retval = usb_add_gadget_udc(dev, &pcd->gadget); + if (retval) + goto unreg_device; + + /* Initialized the Core for Device mode. */ + if (dwc_otg_is_device_mode(core_if)) + dwc_otg_core_dev_init(core_if); + + /* Initialize EP structures */ + dwc_otg_pcd_reinit(pcd); + + /* Register the PCD Callbacks. */ + dwc_otg_cil_register_pcd_callbacks(core_if, &pcd_callbacks, pcd); + + /* Setup interupt handler */ + retval = request_irq(otg_dev->irq, dwc_otg_pcd_irq, IRQF_SHARED, + pcd->gadget.name, pcd); + if (retval) { + pr_err("request of irq%d failed\n", otg_dev->irq); + retval = -EBUSY; + goto err_cleanup; + } + + /* Initialize the DMA buffer for SETUP packets */ + retval = init_pkt_buffs(dev, pcd); + if (retval) + goto err_cleanup; + + /* Initialize tasklet */ + start_xfer_tasklet.data = (unsigned long)pcd; + pcd->start_xfer_tasklet = &start_xfer_tasklet; + return 0; + +err_cleanup: + kfree(pcd); + otg_dev->pcd = NULL; + s_pcd = NULL; + +unreg_device: + device_unregister(&pcd->gadget.dev); + +err: + return retval; +} + +/** + * Cleanup the PCD. + */ +void __devexit dwc_otg_pcd_remove(struct device *dev) +{ + struct dwc_otg_device *otg_dev = dev_get_drvdata(dev); + struct dwc_pcd *pcd = otg_dev->pcd; + + /* Free the IRQ */ + free_irq(otg_dev->irq, pcd); + + /* start with the driver above us */ + if (pcd->driver) { + /* should have been done already by driver model core */ + pr_warning("driver '%s' is still registered\n", + pcd->driver->driver.name); + usb_gadget_unregister_driver(pcd->driver); + } + if (pcd->start_xfer_tasklet) + tasklet_kill(pcd->start_xfer_tasklet); + tasklet_kill(&pcd->test_mode_tasklet); + + device_unregister(&pcd->gadget.dev); + if (GET_CORE_IF(pcd)->dma_enable) { + dma_pool_free(pcd->dwc_pool, pcd->setup_pkt, + pcd->setup_pkt_dma_handle); + dma_pool_free(pcd->dwc_pool, pcd->status_buf, + pcd->status_buf_dma_handle); + dma_pool_destroy(pcd->dwc_pool); + } else { + kfree(pcd->setup_pkt); + kfree(pcd->status_buf); + } + kfree(pcd); + otg_dev->pcd = NULL; +} + +/** + * This function registers a gadget driver with the PCD. + * + * When a driver is successfully registered, it will receive control + * requests including set_configuration(), which enables non-control + * requests. then usb traffic follows until a disconnect is reported. + * then a host may connect again, or the driver might get unbound. + */ +static int dwc_gadget_start(struct usb_gadget_driver *driver, + int (*bind) (struct usb_gadget *)) +{ + int retval; + + if (!driver || driver->speed == USB_SPEED_UNKNOWN || !bind || + !driver->unbind || !driver->disconnect || !driver->setup) + return -EINVAL; + + if (s_pcd == NULL) + return -ENODEV; + + if (s_pcd->driver != NULL) + return -EBUSY; + + /* hook up the driver */ + s_pcd->driver = driver; + s_pcd->gadget.dev.driver = &driver->driver; + + retval = bind(&s_pcd->gadget); + if (retval) { + struct core_if *core_if; + + pr_err("bind to driver %s --> error %d\n", + driver->driver.name, retval); + core_if = s_pcd->otg_dev->core_if; + otg_set_peripheral(core_if->xceiv, &s_pcd->gadget); + s_pcd->driver = NULL; + s_pcd->gadget.dev.driver = NULL; + return retval; + } + return 0; +} + +/** + * This function unregisters a gadget driver + */ +static int dwc_gadget_stop(struct usb_gadget_driver *driver) +{ + struct core_if *core_if; + + if (!s_pcd) + return -ENODEV; + if (!driver || driver != s_pcd->driver) + return -EINVAL; + + core_if = s_pcd->otg_dev->core_if; + core_if->xceiv->state = OTG_STATE_UNDEFINED; + otg_set_peripheral(core_if->xceiv, NULL); + + driver->unbind(&s_pcd->gadget); + s_pcd->driver = NULL; + + return 0; +} diff --git a/drivers/usb/dwc/pcd.h b/drivers/usb/dwc/pcd.h new file mode 100644 index 0000000..0000446 --- /dev/null +++ b/drivers/usb/dwc/pcd.h @@ -0,0 +1,139 @@ +/* + * DesignWare HS OTG controller driver + * Copyright (C) 2006 Synopsys, Inc. + * Portions Copyright (C) 2010 Applied Micro Circuits Corporation. + * + * This program 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. + * + * 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 version 2 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see http://www.gnu.org/licenses + * or write to the Free Software Foundation, Inc., 51 Franklin Street, + * Suite 500, Boston, MA 02110-1335 USA. + * + * Based on Synopsys driver version 2.60a + * Modified by Mark Miesfeld <mmiesfeld@apm.com> + * Modified by Stefan Roese <sr@denx.de>, DENX Software Engineering + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL SYNOPSYS, INC. BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES + * (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#if !defined(__DWC_PCD_H__) +#define __DWC_PCD_H__ + +#include "driver.h" + +/* + * This file contains the structures, constants, and interfaces for + * the Perpherial Contoller Driver (PCD). + * + * The Peripheral Controller Driver (PCD) for Linux will implement the + * Gadget API, so that the existing Gadget drivers can be used. For + * the Mass Storage Function driver the File-backed USB Storage Gadget + * (FBS) driver will be used. The FBS driver supports the + * Control-Bulk (CB), Control-Bulk-Interrupt (CBI), and Bulk-Only + * transports. + * + */ + +/* Invalid DMA Address */ +#define DMA_ADDR_INVALID (~(dma_addr_t) 0) +/* Maxpacket size for EP0 */ +#define MAX_EP0_SIZE 64 +/* Maxpacket size for any EP */ +#define MAX_PACKET_SIZE 1024 + +/* + * Get the pointer to the core_if from the pcd pointer. + */ +#define GET_CORE_IF(_pcd) (_pcd->otg_dev->core_if) + +/* + * DWC_otg request structure. + * This structure is a list of requests. + */ +struct pcd_request { + struct usb_request req; /* USB Request. */ + struct list_head queue; /* queue of these requests. */ + unsigned mapped:1; +}; + +static inline ulong in_ep_int_reg(struct dwc_pcd *pd, int i) +{ + return GET_CORE_IF(pd)->dev_if->in_ep_regs[i] + DWC_DIEPINT; +} +static inline ulong out_ep_int_reg(struct dwc_pcd *pd, int i) +{ + return GET_CORE_IF(pd)->dev_if->out_ep_regs[i] + DWC_DOEPINT; +} +static inline ulong in_ep_ctl_reg(struct dwc_pcd *pd, int i) +{ + return GET_CORE_IF(pd)->dev_if->in_ep_regs[i] + DWC_DIEPCTL; +} + +static inline ulong out_ep_ctl_reg(struct dwc_pcd *pd, int i) +{ + return GET_CORE_IF(pd)->dev_if->out_ep_regs[i] + DWC_DOEPCTL; +} + +static inline ulong dev_ctl_reg(struct dwc_pcd *pd) +{ + return GET_CORE_IF(pd)->dev_if->dev_global_regs + + DWC_DCTL; +} + +static inline ulong dev_diepmsk_reg(struct dwc_pcd *pd) +{ + return GET_CORE_IF(pd)->dev_if->dev_global_regs + + DWC_DIEPMSK; +} + +static inline ulong dev_sts_reg(struct dwc_pcd *pd) +{ + return GET_CORE_IF(pd)->dev_if->dev_global_regs + + DWC_DSTS; +} + +static inline ulong otg_ctl_reg(struct dwc_pcd *pd) +{ + return GET_CORE_IF(pd)->core_global_regs + DWC_GOTGCTL; +} + +extern int dwc_otg_pcd_init(struct device *dev); + +/* + * The following functions support managing the DWC_otg controller in device + * mode. + */ +extern void dwc_otg_ep_activate(struct core_if *core_if, struct dwc_ep *ep); +extern void dwc_otg_ep_start_transfer(struct core_if *_if, struct dwc_ep *ep); +extern void dwc_otg_ep_set_stall(struct core_if *core_if, struct dwc_ep *ep); +extern void dwc_otg_ep_clear_stall(struct core_if *core_if, struct dwc_ep *ep); +extern void dwc_otg_pcd_remove(struct device *dev); +extern int dwc_otg_pcd_handle_intr(struct dwc_pcd *pcd); +extern void dwc_otg_pcd_stop(struct dwc_pcd *pcd); +extern void request_nuke(struct pcd_ep *ep); +extern void dwc_otg_pcd_update_otg(struct dwc_pcd *pcd, const unsigned reset); +extern void dwc_otg_ep0_start_transfer(struct core_if *_if, struct dwc_ep *ep); + +extern void request_done(struct pcd_ep *ep, struct pcd_request *req, + int _status); + +extern void dwc_start_next_request(struct pcd_ep *ep); +#endif diff --git a/drivers/usb/dwc/pcd_intr.c b/drivers/usb/dwc/pcd_intr.c new file mode 100644 index 0000000..ea0ce78 --- /dev/null +++ b/drivers/usb/dwc/pcd_intr.c @@ -0,0 +1,2312 @@ +/* + * DesignWare HS OTG controller driver + * Copyright (C) 2006 Synopsys, Inc. + * Portions Copyright (C) 2010 Applied Micro Circuits Corporation. + * + * This program 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. + * + * 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 version 2 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see http://www.gnu.org/licenses + * or write to the Free Software Foundation, Inc., 51 Franklin Street, + * Suite 500, Boston, MA 02110-1335 USA. + * + * Based on Synopsys driver version 2.60a + * Modified by Mark Miesfeld <mmiesfeld@apm.com> + * Modified by Stefan Roese <sr@denx.de>, DENX Software Engineering + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL SYNOPSYS, INC. BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES + * (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "driver.h" +#include "pcd.h" + +/** + * This function returns pointer to in ep struct with number num + */ +static struct pcd_ep *get_in_ep(struct dwc_pcd *pcd, u32 num) +{ + if (num == 0) { + return &pcd->ep0; + } else { + u32 i; + int num_in_eps = GET_CORE_IF(pcd)->dev_if->num_in_eps; + + for (i = 0; i < num_in_eps; ++i) { + if (pcd->in_ep[i].dwc_ep.num == num) + return &pcd->in_ep[i]; + } + } + return NULL; +} + +/** + * This function returns pointer to out ep struct with number num + */ +static struct pcd_ep *get_out_ep(struct dwc_pcd *pcd, u32 num) +{ + if (num == 0) { + return &pcd->ep0; + } else { + u32 i; + int num_out_eps = GET_CORE_IF(pcd)->dev_if->num_out_eps; + + for (i = 0; i < num_out_eps; ++i) { + if (pcd->out_ep[i].dwc_ep.num == num) + return &pcd->out_ep[i]; + } + } + return NULL; +} + +/** + * This functions gets a pointer to an EP from the wIndex address + * value of the control request. + */ +static struct pcd_ep *get_ep_by_addr(struct dwc_pcd *pcd, u16 index) +{ + struct pcd_ep *ep; + + if (!(index & USB_ENDPOINT_NUMBER_MASK)) + return &pcd->ep0; + + list_for_each_entry(ep, &pcd->gadget.ep_list, ep.ep_list) { + u8 bEndpointAddress; + + if (!ep->desc) + continue; + + bEndpointAddress = ep->desc->bEndpointAddress; + if ((index ^ bEndpointAddress) & USB_DIR_IN) + continue; + + if ((index & 0x0f) == (bEndpointAddress & 0x0f)) + return ep; + } + return NULL; +} + +/** + * This function checks the EP request queue, if the queue is not + * empty the next request is started. + */ +void dwc_start_next_request(struct pcd_ep *ep) +{ + if (!list_empty(&ep->queue)) { + struct pcd_request *req; + + req = list_entry(ep->queue.next, struct pcd_request, queue); + + /* Setup and start the Transfer */ + ep->dwc_ep.start_xfer_buff = req->req.buf; + ep->dwc_ep.xfer_buff = req->req.buf; + ep->dwc_ep.xfer_len = req->req.length; + ep->dwc_ep.xfer_count = 0; + ep->dwc_ep.dma_addr = req->req.dma; + ep->dwc_ep.sent_zlp = 0; + ep->dwc_ep.total_len = ep->dwc_ep.xfer_len; + + /* + * Added-sr: 2007-07-26 + * + * When a new transfer will be started, mark this + * endpoint as active. This way it will be blocked + * for further transfers, until the current transfer + * is finished. + */ + if (dwc_has_feature(GET_CORE_IF(ep->pcd), DWC_LIMITED_XFER)) + ep->dwc_ep.active = 1; + + dwc_otg_ep_start_transfer(GET_CORE_IF(ep->pcd), &ep->dwc_ep); + } +} + +/** + * This function handles the SOF Interrupts. At this time the SOF + * Interrupt is disabled. + */ +static int dwc_otg_pcd_handle_sof_intr(struct dwc_pcd *pcd) +{ + struct core_if *core_if = GET_CORE_IF(pcd); + u32 gintsts; + + /* Clear interrupt */ + gintsts = 0; + gintsts |= DWC_INTMSK_STRT_OF_FRM; + dwc_reg_write((core_if->core_global_regs), DWC_GINTSTS, gintsts); + return 1; +} + +/** + * This function reads the 8 bytes of the setup packet from the Rx FIFO into the + * destination buffer. It is called from the Rx Status Queue Level (RxStsQLvl) + * interrupt routine when a SETUP packet has been received in Slave mode. + */ +static void dwc_otg_read_setup_packet(struct core_if *core_if, u32 * dest) +{ + dest[0] = dwc_read_fifo32(core_if->data_fifo[0]); + dest[1] = dwc_read_fifo32(core_if->data_fifo[0]); +} + +/** + * This function handles the Rx Status Queue Level Interrupt, which + * indicates that there is a least one packet in the Rx FIFO. The + * packets are moved from the FIFO to memory, where they will be + * processed when the Endpoint Interrupt Register indicates Transfer + * Complete or SETUP Phase Done. + * + * Repeat the following until the Rx Status Queue is empty: + * -# Read the Receive Status Pop Register (GRXSTSP) to get Packet + * info + * -# If Receive FIFO is empty then skip to step Clear the interrupt + * and exit + * -# If SETUP Packet call dwc_otg_read_setup_packet to copy the + * SETUP data to the buffer + * -# If OUT Data Packet call dwc_otg_read_packet to copy the data + * to the destination buffer + */ +static int dwc_otg_pcd_handle_rx_status_q_level_intr(struct dwc_pcd *pcd) +{ + struct core_if *core_if = GET_CORE_IF(pcd); + ulong global_regs = core_if->core_global_regs; + u32 gintmask = 0; + u32 grxsts; + struct pcd_ep *ep; + u32 gintsts; + + /* Disable the Rx Status Queue Level interrupt */ + gintmask |= DWC_INTMSK_RXFIFO_NOT_EMPT; + dwc_reg_modify(global_regs, DWC_GINTMSK, gintmask, 0); + + /* Get the Status from the top of the FIFO */ + grxsts = dwc_reg_read(global_regs, DWC_GRXSTSP); + + /* Get pointer to EP structure */ + ep = get_out_ep(pcd, DWC_DM_RXSTS_CHAN_NUM_RD(grxsts)); + + switch (DWC_DM_RXSTS_PKT_STS_RD(grxsts)) { + case DWC_DSTS_GOUT_NAK: + break; + case DWC_STS_DATA_UPDT: + if ((grxsts & DWC_DM_RXSTS_BYTE_CNT) && ep->dwc_ep.xfer_buff) { + dwc_otg_read_packet(core_if, ep->dwc_ep.xfer_buff, + DWC_DM_RXSTS_BYTE_CNT_RD(grxsts)); + ep->dwc_ep.xfer_count += + DWC_DM_RXSTS_BYTE_CNT_RD(grxsts); + ep->dwc_ep.xfer_buff += + DWC_DM_RXSTS_BYTE_CNT_RD(grxsts); + } + break; + case DWC_STS_XFER_COMP: + break; + case DWC_DSTS_SETUP_COMP: + break; + case DWC_DSTS_SETUP_UPDT: + dwc_otg_read_setup_packet(core_if, pcd->setup_pkt->d32); + ep->dwc_ep.xfer_count += DWC_DM_RXSTS_BYTE_CNT_RD(grxsts); + break; + default: + pr_err("RX_STS_Q Interrupt: Unknown status %d\n", + DWC_HM_RXSTS_PKT_STS_RD(grxsts)); + break; + } + + /* Enable the Rx Status Queue Level interrupt */ + dwc_reg_modify(global_regs, DWC_GINTMSK, 0, gintmask); + + /* Clear interrupt */ + gintsts = 0; + gintsts |= DWC_INTSTS_RXFIFO_NOT_EMPT; + dwc_reg_write(global_regs, DWC_GINTSTS, gintsts); + + return 1; +} + +/** + * This function examines the Device IN Token Learning Queue to + * determine the EP number of the last IN token received. This + * implementation is for the Mass Storage device where there are only + * 2 IN EPs (Control-IN and BULK-IN). + * + * The EP numbers for the first six IN Tokens are in DTKNQR1 and there + * are 8 EP Numbers in each of the other possible DTKNQ Registers. + */ +static int get_ep_of_last_in_token(struct core_if *core_if) +{ + ulong regs = core_if->dev_if->dev_global_regs; + const u32 TOKEN_Q_DEPTH = + DWC_HWCFG2_DEV_TKN_Q_DEPTH_RD(core_if->hwcfg2); + /* Number of Token Queue Registers */ + const int DTKNQ_REG_CNT = (TOKEN_Q_DEPTH + 7) / 8; + u32 dtknqr1 = 0; + u32 in_tkn_epnums[4]; + int ndx; + u32 i; + u32 addr = regs + DWC_DTKNQR1; + int epnum = 0; + + /* Read the DTKNQ Registers */ + for (i = 0; i <= DTKNQ_REG_CNT; i++) { + in_tkn_epnums[i] = dwc_reg_read(addr, 0); + + if (addr == (regs + DWC_DVBUSDIS)) + addr = regs + DWC_DTKNQR3_DTHRCTL; + else + ++addr; + } + + /* Copy the DTKNQR1 data to the bit field. */ + dtknqr1 = in_tkn_epnums[0]; + + /* Get the EP numbers */ + in_tkn_epnums[0] = DWC_DTKNQR1_EP_TKN_NO_RD(dtknqr1); + ndx = DWC_DTKNQR1_INT_TKN_Q_WR_PTR_RD(dtknqr1) - 1; + + if (ndx == -1) { + /* + * Calculate the max queue position. + */ + int cnt = TOKEN_Q_DEPTH; + + if (TOKEN_Q_DEPTH <= 6) + cnt = TOKEN_Q_DEPTH - 1; + else if (TOKEN_Q_DEPTH <= 14) + cnt = TOKEN_Q_DEPTH - 7; + else if (TOKEN_Q_DEPTH <= 22) + cnt = TOKEN_Q_DEPTH - 15; + else + cnt = TOKEN_Q_DEPTH - 23; + + epnum = (in_tkn_epnums[DTKNQ_REG_CNT - 1] >> (cnt * 4)) & 0xF; + } else { + if (ndx <= 5) { + epnum = (in_tkn_epnums[0] >> (ndx * 4)) & 0xF; + } else if (ndx <= 13) { + ndx -= 6; + epnum = (in_tkn_epnums[1] >> (ndx * 4)) & 0xF; + } else if (ndx <= 21) { + ndx -= 14; + epnum = (in_tkn_epnums[2] >> (ndx * 4)) & 0xF; + } else if (ndx <= 29) { + ndx -= 22; + epnum = (in_tkn_epnums[3] >> (ndx * 4)) & 0xF; + } + } + + return epnum; +} + +static inline int count_dwords(struct pcd_ep *ep, u32 len) +{ + if (len > ep->dwc_ep.maxpacket) + len = ep->dwc_ep.maxpacket; + return (len + 3) / 4; +} + +/** + * This function writes a packet into the Tx FIFO associated with the EP. For + * non-periodic EPs the non-periodic Tx FIFO is written. For periodic EPs the + * periodic Tx FIFO associated with the EP is written with all packets for the + * next micro-frame. + * + * The buffer is padded to DWORD on a per packet basis in + * slave/dma mode if the MPS is not DWORD aligned. The last packet, if + * short, is also padded to a multiple of DWORD. + * + * ep->xfer_buff always starts DWORD aligned in memory and is a + * multiple of DWORD in length + * + * ep->xfer_len can be any number of bytes + * + * ep->xfer_count is a multiple of ep->maxpacket until the last packet + * + * FIFO access is DWORD + */ +static void dwc_otg_ep_write_packet(struct core_if *core_if, struct dwc_ep *ep, + int dma) +{ + u32 i; + u32 byte_count; + u32 dword_count; + u32 fifo; + u32 *data_buff = (u32 *) ep->xfer_buff; + + if (ep->xfer_count >= ep->xfer_len) + return; + + /* Find the byte length of the packet either short packet or MPS */ + if ((ep->xfer_len - ep->xfer_count) < ep->maxpacket) + byte_count = ep->xfer_len - ep->xfer_count; + else + byte_count = ep->maxpacket; + + /* + * Find the DWORD length, padded by extra bytes as neccessary if MPS + * is not a multiple of DWORD + */ + dword_count = (byte_count + 3) / 4; + + fifo = core_if->data_fifo[ep->num]; + + if (!dma) + for (i = 0; i < dword_count; i++, data_buff++) + dwc_write_fifo32(fifo, *data_buff); + + ep->xfer_count += byte_count; + ep->xfer_buff += byte_count; + ep->dma_addr += byte_count; +} + +/** + * This interrupt occurs when the non-periodic Tx FIFO is half-empty. + * The active request is checked for the next packet to be loaded into + * the non-periodic Tx FIFO. + */ +static int dwc_otg_pcd_handle_np_tx_fifo_empty_intr(struct dwc_pcd *pcd) +{ + struct core_if *core_if = GET_CORE_IF(pcd); + ulong global_regs = core_if->core_global_regs; + u32 txstatus = 0; + u32 gintsts = 0; + int epnum; + struct pcd_ep *ep; + u32 len; + int dwords; + + /* Get the epnum from the IN Token Learning Queue. */ + epnum = get_ep_of_last_in_token(core_if); + ep = get_in_ep(pcd, epnum); + + txstatus = dwc_reg_read(global_regs, DWC_GNPTXSTS); + + /* + * While there is space in the queue, space in the FIFO, and data to + * tranfer, write packets to the Tx FIFO + */ + len = ep->dwc_ep.xfer_len - ep->dwc_ep.xfer_count; + dwords = count_dwords(ep, len); + while ((DWC_GNPTXSTS_NPTXQSPCAVAIL_RD(txstatus) > 0) && + (DWC_GNPTXSTS_NPTXFSPCAVAIL_RD(txstatus) > dwords) && + ep->dwc_ep.xfer_count < ep->dwc_ep.xfer_len) { + /* + * Added-sr: 2007-07-26 + * + * When a new transfer will be started, mark this + * endpoint as active. This way it will be blocked + * for further transfers, until the current transfer + * is finished. + */ + if (dwc_has_feature(core_if, DWC_LIMITED_XFER)) + ep->dwc_ep.active = 1; + + dwc_otg_ep_write_packet(core_if, &ep->dwc_ep, 0); + len = ep->dwc_ep.xfer_len - ep->dwc_ep.xfer_count; + dwords = count_dwords(ep, len); + txstatus = dwc_reg_read(global_regs, DWC_GNPTXSTS); + } + + /* Clear nptxfempty interrupt */ + gintsts |= DWC_INTMSK_RXFIFO_NOT_EMPT; + dwc_reg_write(global_regs, DWC_GINTSTS, gintsts); + + /* Re-enable tx-fifo empty interrupt, if packets are stil pending */ + if (len) + dwc_reg_modify(global_regs, DWC_GINTSTS, 0, gintsts); + return 1; +} + +/** + * This function is called when dedicated Tx FIFO Empty interrupt occurs. + * The active request is checked for the next packet to be loaded into + * apropriate Tx FIFO. + */ +static int write_empty_tx_fifo(struct dwc_pcd *pcd, u32 epnum) +{ + struct core_if *core_if = GET_CORE_IF(pcd); + ulong regs; + u32 txstatus = 0; + struct pcd_ep *ep; + u32 len; + int dwords; + u32 diepint = 0; + + ep = get_in_ep(pcd, epnum); + regs = core_if->dev_if->in_ep_regs[epnum]; + txstatus = dwc_reg_read(regs, DWC_DTXFSTS); + + /* + * While there is space in the queue, space in the FIFO and data to + * tranfer, write packets to the Tx FIFO + */ + len = ep->dwc_ep.xfer_len - ep->dwc_ep.xfer_count; + dwords = count_dwords(ep, len); + while (DWC_DTXFSTS_TXFSSPC_AVAI_RD(txstatus) > dwords + && ep->dwc_ep.xfer_count < ep->dwc_ep.xfer_len + && ep->dwc_ep.xfer_len != 0) { + dwc_otg_ep_write_packet(core_if, &ep->dwc_ep, 0); + len = ep->dwc_ep.xfer_len - ep->dwc_ep.xfer_count; + dwords = count_dwords(ep, len); + txstatus = dwc_reg_read(regs, DWC_DTXFSTS); + } + /* Clear emptyintr */ + diepint = DWC_DIEPINT_TXFIFO_EMPTY_RW(diepint, 1); + dwc_reg_write(in_ep_int_reg(pcd, epnum), 0, diepint); + return 1; +} + +/** + * This function is called when the Device is disconnected. It stops any active + * requests and informs the Gadget driver of the disconnect. + */ +void dwc_otg_pcd_stop(struct dwc_pcd *pcd) +{ + int i, num_in_eps, num_out_eps; + struct pcd_ep *ep; + u32 intr_mask = 0; + ulong global_regs = GET_CORE_IF(pcd)->core_global_regs; + + num_in_eps = GET_CORE_IF(pcd)->dev_if->num_in_eps; + num_out_eps = GET_CORE_IF(pcd)->dev_if->num_out_eps; + + /* Don't disconnect drivers more than once */ + if (pcd->ep0state == EP0_DISCONNECT) + return; + pcd->ep0state = EP0_DISCONNECT; + + /* Reset the OTG state. */ + dwc_otg_pcd_update_otg(pcd, 1); + + /* Disable the NP Tx Fifo Empty Interrupt. */ + intr_mask |= DWC_INTMSK_NP_TXFIFO_EMPT; + dwc_reg_modify(global_regs, DWC_GINTMSK, intr_mask, 0); + + /* Flush the FIFOs */ + dwc_otg_flush_tx_fifo(GET_CORE_IF(pcd), 0); + dwc_otg_flush_rx_fifo(GET_CORE_IF(pcd)); + + /* Prevent new request submissions, kill any outstanding requests */ + ep = &pcd->ep0; + request_nuke(ep); + + /* Prevent new request submissions, kill any outstanding requests */ + for (i = 0; i < num_in_eps; i++) + request_nuke((struct pcd_ep *)&pcd->in_ep[i]); + + /* Prevent new request submissions, kill any outstanding requests */ + for (i = 0; i < num_out_eps; i++) + request_nuke((struct pcd_ep *)&pcd->out_ep[i]); + + /* Report disconnect; the driver is already quiesced */ + if (pcd->driver && pcd->driver->disconnect) { + spin_unlock(&pcd->lock); + pcd->driver->disconnect(&pcd->gadget); + spin_lock(&pcd->lock); + } +} + +/** + * This interrupt indicates that ... + */ +static int dwc_otg_pcd_handle_i2c_intr(struct dwc_pcd *pcd) +{ + u32 intr_mask = 0; + u32 gintsts; + + pr_info("Interrupt handler not implemented for i2cintr\n"); + + /* Turn off and clean the interrupt */ + intr_mask |= DWC_INTMSK_I2C_INTR; + dwc_reg_modify((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTMSK, + intr_mask, 0); + + gintsts = 0; + gintsts |= DWC_INTSTS_I2C_INTR; + dwc_reg_write((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTSTS, + gintsts); + + return 1; +} + +/** + * This interrupt indicates that ... + */ +static int dwc_otg_pcd_handle_early_suspend_intr(struct dwc_pcd *pcd) +{ + u32 intr_mask = 0; + u32 gintsts; + + pr_info("Early Suspend Detected\n"); + + /* Turn off and clean the interrupt */ + intr_mask |= DWC_INTMSK_EARLY_SUSP; + dwc_reg_modify((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTMSK, + intr_mask, 0); + + gintsts = 0; + gintsts |= DWC_INTSTS_EARLY_SUSP; + dwc_reg_write((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTSTS, + gintsts); + + return 1; +} + +/** + * This function configures EPO to receive SETUP packets. + * + * Program the following fields in the endpoint specific registers for Control + * OUT EP 0, in order to receive a setup packet: + * + * - DOEPTSIZ0.Packet Count = 3 (To receive up to 3 back to back setup packets) + * + * - DOEPTSIZE0.Transfer Size = 24 Bytes (To receive up to 3 back to back setup + * packets) + * + * In DMA mode, DOEPDMA0 Register with a memory address to store any setup + * packets received + */ +static void ep0_out_start(struct core_if *core_if, struct dwc_pcd *pcd) +{ + struct device_if *dev_if = core_if->dev_if; + u32 doeptsize0 = 0; + + doeptsize0 = DWC_DEPTSIZ0_SUPCNT_RW(doeptsize0, 3); + doeptsize0 = DWC_DEPTSIZ0_PKT_CNT_RW(doeptsize0, 1); + doeptsize0 = DWC_DEPTSIZ0_XFER_SIZ_RW(doeptsize0, 8 * 3); + dwc_reg_write(dev_if->out_ep_regs[0], DWC_DOEPTSIZ, doeptsize0); + + if (core_if->dma_enable) { + u32 doepctl = 0; + + dwc_reg_write(dev_if->out_ep_regs[0], DWC_DOEPDMA, + pcd->setup_pkt_dma_handle); + doepctl = DWC_DEPCTL_EPENA_RW(doepctl, 1); + doepctl = DWC_DEPCTL_ACT_EP_RW(doepctl, 1); + dwc_reg_write(out_ep_ctl_reg(pcd, 0), 0, doepctl); + } +} + +/** + * This interrupt occurs when a USB Reset is detected. When the USB Reset + * Interrupt occurs the device state is set to DEFAULT and the EP0 state is set + * to IDLE. + * + * Set the NAK bit for all OUT endpoints (DOEPCTLn.SNAK = 1) + * + * Unmask the following interrupt bits: + * - DAINTMSK.INEP0 = 1 (Control 0 IN endpoint) + * - DAINTMSK.OUTEP0 = 1 (Control 0 OUT endpoint) + * - DOEPMSK.SETUP = 1 + * - DOEPMSK.XferCompl = 1 + * - DIEPMSK.XferCompl = 1 + * - DIEPMSK.TimeOut = 1 + * + * Program the following fields in the endpoint specific registers for Control + * OUT EP 0, in order to receive a setup packet + * - DOEPTSIZ0.Packet Count = 3 (To receive up to 3 back to back setup packets) + * - DOEPTSIZE0.Transfer Size = 24 Bytes (To receive up to 3 back to back setup + * packets) + * + * - In DMA mode, DOEPDMA0 Register with a memory address to store any setup + * packets received + * + * At this point, all the required initialization, except for enabling + * the control 0 OUT endpoint is done, for receiving SETUP packets. + * + * Note that the bits in the Device IN endpoint mask register (diepmsk) are laid + * out exactly the same as the Device IN endpoint interrupt register (diepint.) + * Likewise for Device OUT endpoint mask / interrupt registers (doepmsk / + * doepint.) + */ +static int dwc_otg_pcd_handle_usb_reset_intr(struct dwc_pcd *pcd) +{ + struct core_if *core_if = GET_CORE_IF(pcd); + struct device_if *dev_if = core_if->dev_if; + u32 doepctl = 0; + u32 daintmsk = 0; + u32 doepmsk = 0; + u32 diepmsk = 0; + u32 dcfg = 0; + u32 resetctl = 0; + u32 dctl = 0; + u32 i; + u32 gintsts = 0; + + pr_info("USB RESET\n"); + + /* reset the HNP settings */ + dwc_otg_pcd_update_otg(pcd, 1); + + /* Clear the Remote Wakeup Signalling */ + dctl = DEC_DCTL_REMOTE_WAKEUP_SIG(dctl, 1); + dwc_reg_modify(dev_ctl_reg(pcd), 0, dctl, 0); + + /* Set NAK for all OUT EPs */ + doepctl = DWC_DEPCTL_SET_NAK_RW(doepctl, 1); + for (i = 0; i <= dev_if->num_out_eps; i++) + dwc_reg_write(out_ep_ctl_reg(pcd, i), 0, doepctl); + + /* Flush the NP Tx FIFO */ + dwc_otg_flush_tx_fifo(core_if, 0); + + /* Flush the Learning Queue */ + resetctl |= DWC_RSTCTL_TKN_QUE_FLUSH; + dwc_reg_write(core_if->core_global_regs, DWC_GRSTCTL, resetctl); + + daintmsk |= DWC_DAINT_INEP00; + daintmsk |= DWC_DAINT_OUTEP00; + dwc_reg_write(dev_if->dev_global_regs, DWC_DAINTMSK, daintmsk); + + doepmsk = DWC_DOEPMSK_SETUP_DONE_RW(doepmsk, 1); + doepmsk = DWC_DOEPMSK_AHB_ERROR_RW(doepmsk, 1); + doepmsk = DWC_DOEPMSK_EP_DISA_RW(doepmsk, 1); + doepmsk = DWC_DOEPMSK_TX_COMPL_RW(doepmsk, 1); + dwc_reg_write(dev_if->dev_global_regs, DWC_DOEPMSK, doepmsk); + + diepmsk = DWC_DIEPMSK_TX_CMPL_RW(diepmsk, 1); + diepmsk = DWC_DIEPMSK_TOUT_COND_RW(diepmsk, 1); + diepmsk = DWC_DIEPMSK_EP_DISA_RW(diepmsk, 1); + diepmsk = DWC_DIEPMSK_AHB_ERROR_RW(diepmsk, 1); + diepmsk = DWC_DIEPMSK_IN_TKN_TX_EMPTY_RW(diepmsk, 1); + dwc_reg_write(dev_if->dev_global_regs, DWC_DIEPMSK, diepmsk); + + /* Reset Device Address */ + dcfg = dwc_reg_read(dev_if->dev_global_regs, DWC_DCFG); + dcfg = DWC_DCFG_DEV_ADDR_WR(dcfg, 0); + dwc_reg_write(dev_if->dev_global_regs, DWC_DCFG, dcfg); + + /* setup EP0 to receive SETUP packets */ + ep0_out_start(core_if, pcd); + + /* Clear interrupt */ + gintsts = 0; + gintsts |= DWC_INTSTS_USB_RST; + dwc_reg_write((core_if->core_global_regs), DWC_GINTSTS, gintsts); + + return 1; +} + +/** + * Get the device speed from the device status register and convert it + * to USB speed constant. + */ +static int get_device_speed(struct dwc_pcd *pcd) +{ + u32 dsts = 0; + enum usb_device_speed speed = USB_SPEED_UNKNOWN; + + dsts = dwc_reg_read(dev_sts_reg(pcd), 0); + + switch (DWC_DSTS_ENUM_SPEED_RD(dsts)) { + case DWC_DSTS_ENUMSPD_HS_PHY_30MHZ_OR_60MHZ: + speed = USB_SPEED_HIGH; + break; + case DWC_DSTS_ENUMSPD_FS_PHY_30MHZ_OR_60MHZ: + case DWC_DSTS_ENUMSPD_FS_PHY_48MHZ: + speed = USB_SPEED_FULL; + break; + case DWC_DSTS_ENUMSPD_LS_PHY_6MHZ: + speed = USB_SPEED_LOW; + break; + } + return speed; +} + +/** + * This function enables EP0 OUT to receive SETUP packets and configures EP0 + * IN for transmitting packets. It is normally called when the "Enumeration + * Done" interrupt occurs. + */ +static void dwc_otg_ep0_activate(struct core_if *core_if, struct dwc_ep *ep) +{ + struct device_if *dev_if = core_if->dev_if; + u32 dsts; + u32 diepctl = 0; + u32 doepctl = 0; + u32 dctl = 0; + + /* Read the Device Status and Endpoint 0 Control registers */ + dsts = dwc_reg_read(dev_if->dev_global_regs, DWC_DSTS); + diepctl = dwc_reg_read(dev_if->in_ep_regs[0], DWC_DIEPCTL); + doepctl = dwc_reg_read(dev_if->out_ep_regs[0], DWC_DOEPCTL); + + /* Set the MPS of the IN EP based on the enumeration speed */ + switch (DWC_DSTS_ENUM_SPEED_RD(dsts)) { + case DWC_DSTS_ENUMSPD_HS_PHY_30MHZ_OR_60MHZ: + case DWC_DSTS_ENUMSPD_FS_PHY_30MHZ_OR_60MHZ: + case DWC_DSTS_ENUMSPD_FS_PHY_48MHZ: + diepctl = DWC_DEPCTL_MPS_RW(diepctl, DWC_DEP0CTL_MPS_64); + break; + case DWC_DSTS_ENUMSPD_LS_PHY_6MHZ: + diepctl = DWC_DEPCTL_MPS_RW(diepctl, DWC_DEP0CTL_MPS_8); + break; + } + dwc_reg_write(dev_if->in_ep_regs[0], DWC_DIEPCTL, diepctl); + + /* Enable OUT EP for receive */ + doepctl = DWC_DEPCTL_EPENA_RW(doepctl, 1); + dwc_reg_write(dev_if->out_ep_regs[0], DWC_DOEPCTL, doepctl); + + dctl = DWC_DCTL_CLR_CLBL_NP_IN_NAK(dctl, 1); + dwc_reg_modify(dev_if->dev_global_regs, DWC_DCTL, dctl, dctl); +} + +/** + * Read the device status register and set the device speed in the + * data structure. + * Set up EP0 to receive SETUP packets by calling dwc_ep0_activate. + */ +static int dwc_otg_pcd_handle_enum_done_intr(struct dwc_pcd *pcd) +{ + struct pcd_ep *ep0 = &pcd->ep0; + u32 gintsts; + u32 gusbcfg; + struct core_if *core_if = GET_CORE_IF(pcd); + ulong global_regs = core_if->core_global_regs; + u32 gsnpsid = global_regs + DWC_GSNPSID; + u8 utmi16b, utmi8b; + + if (gsnpsid >= (u32) 0x4f54260a) { + utmi16b = 5; + utmi8b = 9; + } else { + utmi16b = 4; + utmi8b = 8; + } + dwc_otg_ep0_activate(GET_CORE_IF(pcd), &ep0->dwc_ep); + + pcd->ep0state = EP0_IDLE; + ep0->stopped = 0; + pcd->gadget.speed = get_device_speed(pcd); + + gusbcfg = dwc_reg_read(global_regs, DWC_GUSBCFG); + + /* Set USB turnaround time based on device speed and PHY interface. */ + if (pcd->gadget.speed == USB_SPEED_HIGH) { + switch (DWC_HWCFG2_HS_PHY_TYPE_RD(core_if->hwcfg2)) { + case DWC_HWCFG2_HS_PHY_TYPE_ULPI: + gusbcfg = + (gusbcfg & (~((u32) DWC_USBCFG_TRN_TIME(0xf)))) | + DWC_USBCFG_TRN_TIME(9); + break; + case DWC_HWCFG2_HS_PHY_TYPE_UTMI: + if (DWC_HWCFG4_UTMI_PHY_DATA_WIDTH_RD(core_if->hwcfg4) + == 0) + gusbcfg = + (gusbcfg & + (~((u32) DWC_USBCFG_TRN_TIME(0xf)))) | + DWC_USBCFG_TRN_TIME(utmi8b); + else if (DWC_HWCFG4_UTMI_PHY_DATA_WIDTH_RD + (core_if->hwcfg4) == 1) + gusbcfg = + (gusbcfg & + (~((u32) DWC_USBCFG_TRN_TIME(0xf)))) | + DWC_USBCFG_TRN_TIME(utmi16b); + else if (core_if->core_params->phy_utmi_width == 8) + gusbcfg = + (gusbcfg & + (~((u32) DWC_USBCFG_TRN_TIME(0xf)))) | + DWC_USBCFG_TRN_TIME(utmi8b); + else + gusbcfg = + (gusbcfg & + (~((u32) DWC_USBCFG_TRN_TIME(0xf)))) | + DWC_USBCFG_TRN_TIME(utmi16b); + break; + case DWC_HWCFG2_HS_PHY_TYPE_UTMI_ULPI: + if (gusbcfg & DWC_USBCFG_ULPI_UTMI_SEL) { + gusbcfg = + (gusbcfg & + (~((u32) DWC_USBCFG_TRN_TIME(0xf)))) | + DWC_USBCFG_TRN_TIME(9); + } else { + if (core_if->core_params->phy_utmi_width == 16) + gusbcfg = + (gusbcfg & + (~ + ((u32) DWC_USBCFG_TRN_TIME(0xf)))) + | DWC_USBCFG_TRN_TIME(utmi16b); + else + gusbcfg = + (gusbcfg & + (~ + ((u32) DWC_USBCFG_TRN_TIME(0xf)))) + | DWC_USBCFG_TRN_TIME(utmi8b); + } + break; + } + } else { + /* Full or low speed */ + gusbcfg = (gusbcfg & (~((u32) DWC_USBCFG_TRN_TIME(0xf)))) | + DWC_USBCFG_TRN_TIME(9); + } + dwc_reg_write(global_regs, DWC_GUSBCFG, gusbcfg); + + /* Clear interrupt */ + gintsts = 0; + gintsts |= DWC_INTSTS_ENUM_DONE; + dwc_reg_write((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTSTS, + gintsts); + + return 1; +} + +/** + * This interrupt indicates that the ISO OUT Packet was dropped due to + * Rx FIFO full or Rx Status Queue Full. If this interrupt occurs + * read all the data from the Rx FIFO. + */ +static int dwc_otg_pcd_handle_isoc_out_packet_dropped_intr(struct dwc_pcd *pcd) +{ + u32 intr_mask = 0; + u32 gintsts; + + pr_info("Interrupt Handler not implemented for ISOC Out " "Dropped\n"); + + /* Turn off and clear the interrupt */ + intr_mask |= DWC_INTMSK_ISYNC_OUTPKT_DRP; + dwc_reg_modify((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTMSK, + intr_mask, 0); + + gintsts = 0; + gintsts |= DWC_INTSTS_ISYNC_OUTPKT_DRP; + dwc_reg_write((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTSTS, + gintsts); + + return 1; +} + +/** + * This interrupt indicates the end of the portion of the micro-frame + * for periodic transactions. If there is a periodic transaction for + * the next frame, load the packets into the EP periodic Tx FIFO. + */ +static int dwc_otg_pcd_handle_end_periodic_frame_intr(struct dwc_pcd *pcd) +{ + u32 intr_mask = 0; + u32 gintsts; + + pr_info("Interrupt handler not implemented for End of " + "Periodic Portion of Micro-Frame Interrupt"); + + /* Turn off and clear the interrupt */ + intr_mask |= DWC_INTMSK_END_OF_PFRM; + dwc_reg_modify((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTMSK, + intr_mask, 0); + + gintsts = 0; + gintsts |= DWC_INTSTS_END_OF_PFRM; + dwc_reg_write((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTSTS, + gintsts); + + return 1; +} + +/** + * This interrupt indicates that EP of the packet on the top of the + * non-periodic Tx FIFO does not match EP of the IN Token received. + * + * The "Device IN Token Queue" Registers are read to determine the + * order the IN Tokens have been received. The non-periodic Tx FIFO is flushed, + * so it can be reloaded in the order seen in the IN Token Queue. + */ +static int dwc_otg_pcd_handle_ep_mismatch_intr(struct core_if *core_if) +{ + u32 intr_mask = 0; + u32 gintsts; + + pr_info("Interrupt handler not implemented for End Point " + "Mismatch\n"); + + /* Turn off and clear the interrupt */ + intr_mask |= DWC_INTMSK_ENDP_MIS_MTCH; + dwc_reg_modify((core_if->core_global_regs), DWC_GINTMSK, + intr_mask, 0); + + gintsts = 0; + gintsts |= DWC_INTSTS_ENDP_MIS_MTCH; + dwc_reg_write((core_if->core_global_regs), DWC_GINTSTS, gintsts); + return 1; +} + +/** + * This funcion stalls EP0. + */ +static void ep0_do_stall(struct dwc_pcd *pcd, const int val) +{ + struct pcd_ep *ep0 = &pcd->ep0; + struct usb_ctrlrequest *ctrl = &pcd->setup_pkt->req; + + pr_warning("req %02x.%02x protocol STALL; err %d\n", + ctrl->bRequestType, ctrl->bRequest, val); + + ep0->dwc_ep.is_in = 1; + dwc_otg_ep_set_stall(pcd->otg_dev->core_if, &ep0->dwc_ep); + + pcd->ep0.stopped = 1; + pcd->ep0state = EP0_IDLE; + ep0_out_start(GET_CORE_IF(pcd), pcd); +} + +/** + * This functions delegates the setup command to the gadget driver. + */ +static void do_gadget_setup(struct dwc_pcd *pcd, struct usb_ctrlrequest *ctrl) +{ + if (pcd->driver && pcd->driver->setup) { + int ret; + + spin_unlock(&pcd->lock); + ret = pcd->driver->setup(&pcd->gadget, ctrl); + spin_lock(&pcd->lock); + + if (ret < 0) + ep0_do_stall(pcd, ret); + + /** This is a g_file_storage gadget driver specific + * workaround: a DELAYED_STATUS result from the fsg_setup + * routine will result in the gadget queueing a EP0 IN status + * phase for a two-stage control transfer. + * + * Exactly the same as a SET_CONFIGURATION/SET_INTERFACE except + * that this is a class specific request. Need a generic way to + * know when the gadget driver will queue the status phase. + * + * Can we assume when we call the gadget driver setup() function + * that it will always queue and require the following flag? + * Need to look into this. + */ + if (ret == 256 + 999) + pcd->request_config = 1; + } +} + +/** + * This function starts the Zero-Length Packet for the IN status phase + * of a 2 stage control transfer. + */ +static void do_setup_in_status_phase(struct dwc_pcd *pcd) +{ + struct pcd_ep *ep0 = &pcd->ep0; + + if (pcd->ep0state == EP0_STALL) + return; + + pcd->ep0state = EP0_STATUS; + + ep0->dwc_ep.xfer_len = 0; + ep0->dwc_ep.xfer_count = 0; + ep0->dwc_ep.is_in = 1; + ep0->dwc_ep.dma_addr = pcd->setup_pkt_dma_handle; + dwc_otg_ep0_start_transfer(GET_CORE_IF(pcd), &ep0->dwc_ep); + + /* Prepare for more SETUP Packets */ + ep0_out_start(GET_CORE_IF(pcd), pcd); +} + +/** + * This function starts the Zero-Length Packet for the OUT status phase + * of a 2 stage control transfer. + */ +static void do_setup_out_status_phase(struct dwc_pcd *pcd) +{ + struct pcd_ep *ep0 = &pcd->ep0; + + if (pcd->ep0state == EP0_STALL) + return; + pcd->ep0state = EP0_STATUS; + + ep0->dwc_ep.xfer_len = 0; + ep0->dwc_ep.xfer_count = 0; + ep0->dwc_ep.is_in = 0; + ep0->dwc_ep.dma_addr = pcd->setup_pkt_dma_handle; + dwc_otg_ep0_start_transfer(GET_CORE_IF(pcd), &ep0->dwc_ep); + + /* Prepare for more SETUP Packets */ + ep0_out_start(GET_CORE_IF(pcd), pcd); +} + +/** + * Clear the EP halt (STALL) and if pending requests start the + * transfer. + */ +static void pcd_clear_halt(struct dwc_pcd *pcd, struct pcd_ep *ep) +{ + struct core_if *core_if = GET_CORE_IF(pcd); + + if (!ep->dwc_ep.stall_clear_flag) + dwc_otg_ep_clear_stall(core_if, &ep->dwc_ep); + + /* Reactive the EP */ + dwc_otg_ep_activate(core_if, &ep->dwc_ep); + + if (ep->stopped) { + ep->stopped = 0; + /* If there is a request in the EP queue start it */ + + /* + * dwc_start_next_request(), outside of interpt contxt at some + * time after the current time, after a clear-halt setup packet. + * Still need to implement ep mismatch in the future if a gadget + * ever uses more than one endpoint at once + */ + if (core_if->dma_enable) { + ep->queue_sof = 1; + tasklet_schedule(pcd->start_xfer_tasklet); + } else { + /* + * Added-sr: 2007-07-26 + * + * To re-enable this endpoint it's important to + * set this next_ep number. Otherwise the endpoint + * will not get active again after stalling. + */ + if (dwc_has_feature(core_if, DWC_LIMITED_XFER)) + dwc_start_next_request(ep); + } + } + + /* Start Control Status Phase */ + do_setup_in_status_phase(pcd); +} + +/** + * This function is called when the SET_FEATURE TEST_MODE Setup packet is sent + * from the host. The Device Control register is written with the Test Mode + * bits set to the specified Test Mode. This is done as a tasklet so that the + * "Status" phase of the control transfer completes before transmitting the TEST + * packets. + * + */ +static void do_test_mode(unsigned long data) +{ + u32 dctl = 0; + struct dwc_pcd *pcd = (struct dwc_pcd *)data; + int test_mode = pcd->test_mode; + + dctl = dwc_reg_read(dev_ctl_reg(pcd), 0); + switch (test_mode) { + case 1: /* TEST_J */ + dctl = DWC_DCTL_TST_CTL(dctl, 1); + break; + case 2: /* TEST_K */ + dctl = DWC_DCTL_TST_CTL(dctl, 2); + break; + case 3: /* TEST_SE0_NAK */ + dctl = DWC_DCTL_TST_CTL(dctl, 3); + break; + case 4: /* TEST_PACKET */ + dctl = DWC_DCTL_TST_CTL(dctl, 4); + break; + case 5: /* TEST_FORCE_ENABLE */ + dctl = DWC_DCTL_TST_CTL(dctl, 5); + break; + } + dwc_reg_write(dev_ctl_reg(pcd), 0, dctl); +} + +/** + * This function process the SET_FEATURE Setup Commands. + */ +static void do_set_feature(struct dwc_pcd *pcd) +{ + struct core_if *core_if = GET_CORE_IF(pcd); + ulong regs = core_if->core_global_regs; + struct usb_ctrlrequest ctrl = pcd->setup_pkt->req; + int otg_cap = core_if->core_params->otg_cap; + u32 gotgctl = 0; + + switch (ctrl.bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + switch (__le16_to_cpu(ctrl.wValue)) { + case USB_DEVICE_REMOTE_WAKEUP: + pcd->remote_wakeup_enable = 1; + break; + case USB_DEVICE_TEST_MODE: + /* + * Setup the Test Mode tasklet to do the Test + * Packet generation after the SETUP Status + * phase has completed. + */ + + pcd->test_mode_tasklet.next = NULL; + pcd->test_mode_tasklet.state = 0; + atomic_set(&pcd->test_mode_tasklet.count, 0); + + pcd->test_mode_tasklet.func = do_test_mode; + pcd->test_mode_tasklet.data = (unsigned long)pcd; + pcd->test_mode = __le16_to_cpu(ctrl.wIndex) >> 8; + tasklet_schedule(&pcd->test_mode_tasklet); + + break; + case USB_DEVICE_B_HNP_ENABLE: + /* dev may initiate HNP */ + if (otg_cap == DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE) { + pcd->b_hnp_enable = 1; + dwc_otg_pcd_update_otg(pcd, 0); + /* + * gotgctl.devhnpen cleared by a + * USB Reset? + */ + gotgctl |= DWC_GCTL_DEV_HNP_ENA; + gotgctl |= DWC_GCTL_HNP_REQ; + dwc_reg_write(regs, DWC_GOTGCTL, gotgctl); + } else { + ep0_do_stall(pcd, -EOPNOTSUPP); + } + break; + case USB_DEVICE_A_HNP_SUPPORT: + /* RH port supports HNP */ + if (otg_cap == DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE) { + pcd->a_hnp_support = 1; + dwc_otg_pcd_update_otg(pcd, 0); + } else { + ep0_do_stall(pcd, -EOPNOTSUPP); + } + break; + case USB_DEVICE_A_ALT_HNP_SUPPORT: + /* other RH port does */ + if (otg_cap == DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE) { + pcd->a_alt_hnp_support = 1; + dwc_otg_pcd_update_otg(pcd, 0); + } else { + ep0_do_stall(pcd, -EOPNOTSUPP); + } + break; + } + do_setup_in_status_phase(pcd); + break; + case USB_RECIP_INTERFACE: + do_gadget_setup(pcd, &ctrl); + break; + case USB_RECIP_ENDPOINT: + if (__le16_to_cpu(ctrl.wValue) == USB_ENDPOINT_HALT) { + struct pcd_ep *ep; + + ep = get_ep_by_addr(pcd, __le16_to_cpu(ctrl.wIndex)); + + if (ep == NULL) { + ep0_do_stall(pcd, -EOPNOTSUPP); + return; + } + + ep->stopped = 1; + dwc_otg_ep_set_stall(core_if, &ep->dwc_ep); + } + do_setup_in_status_phase(pcd); + break; + } +} + +/** + * This function process the CLEAR_FEATURE Setup Commands. + */ +static void do_clear_feature(struct dwc_pcd *pcd) +{ + struct usb_ctrlrequest ctrl = pcd->setup_pkt->req; + struct pcd_ep *ep; + + switch (ctrl.bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + switch (__le16_to_cpu(ctrl.wValue)) { + case USB_DEVICE_REMOTE_WAKEUP: + pcd->remote_wakeup_enable = 0; + break; + case USB_DEVICE_TEST_MODE: + /* Add CLEAR_FEATURE for TEST modes. */ + break; + } + do_setup_in_status_phase(pcd); + break; + case USB_RECIP_ENDPOINT: + ep = get_ep_by_addr(pcd, __le16_to_cpu(ctrl.wIndex)); + if (ep == NULL) { + ep0_do_stall(pcd, -EOPNOTSUPP); + return; + } + + pcd_clear_halt(pcd, ep); + break; + } +} + +/** + * This function processes SETUP commands. In Linux, the USB Command processing + * is done in two places - the first being the PCD and the second in the Gadget + * Driver (for example, the File-Backed Storage Gadget Driver). + * + * GET_STATUS: Command is processed as defined in chapter 9 of the USB 2.0 + * Specification chapter 9 + * + * CLEAR_FEATURE: The Device and Endpoint requests are the ENDPOINT_HALT feature + * is procesed, all others the interface requests are ignored. + * + * SET_FEATURE: The Device and Endpoint requests are processed by the PCD. + * Interface requests are passed to the Gadget Driver. + * + * SET_ADDRESS: PCD, Program the DCFG reg, with device address received + * + * GET_DESCRIPTOR: Gadget Driver, Return the requested descriptor + * + * SET_DESCRIPTOR: Gadget Driver, Optional - not implemented by any of the + * existing Gadget Drivers. + * + * SET_CONFIGURATION: Gadget Driver, Disable all EPs and enable EPs for new + * configuration. + * + * GET_CONFIGURATION: Gadget Driver, Return the current configuration + * + * SET_INTERFACE: Gadget Driver, Disable all EPs and enable EPs for new + * configuration. + * + * GET_INTERFACE: Gadget Driver, Return the current interface. + * + * SYNC_FRAME: Display debug message. + * + * When the SETUP Phase Done interrupt occurs, the PCD SETUP commands are + * processed by pcd_setup. Calling the Function Driver's setup function from + * pcd_setup processes the gadget SETUP commands. + */ +static void pcd_setup(struct dwc_pcd *pcd) +{ + struct core_if *core_if = GET_CORE_IF(pcd); + struct device_if *dev_if = core_if->dev_if; + struct usb_ctrlrequest ctrl = pcd->setup_pkt->req; + struct pcd_ep *ep; + struct pcd_ep *ep0 = &pcd->ep0; + u16 *status = pcd->status_buf; + u32 doeptsize0 = 0; + + doeptsize0 = dwc_reg_read(dev_if->out_ep_regs[0], DWC_DOEPTSIZ); + + /* handle > 1 setup packet , assert error for now */ + if (core_if->dma_enable && (DWC_DEPTSIZ0_SUPCNT_RD(doeptsize0) < 2)) + pr_err("\n\n CANNOT handle > 1 setup packet in " + "DMA mode\n\n"); + + /* Clean up the request queue */ + request_nuke(ep0); + ep0->stopped = 0; + + if (ctrl.bRequestType & USB_DIR_IN) { + ep0->dwc_ep.is_in = 1; + pcd->ep0state = EP0_IN_DATA_PHASE; + } else { + ep0->dwc_ep.is_in = 0; + pcd->ep0state = EP0_OUT_DATA_PHASE; + } + + if ((ctrl.bRequestType & USB_TYPE_MASK) != USB_TYPE_STANDARD) { + /* + * Handle non-standard (class/vendor) requests in the gadget + * driver + */ + do_gadget_setup(pcd, &ctrl); + return; + } + + switch (ctrl.bRequest) { + case USB_REQ_GET_STATUS: + switch (ctrl.bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + *status = 0x1; /* Self powered */ + *status |= pcd->remote_wakeup_enable << 1; + break; + case USB_RECIP_INTERFACE: + *status = 0; + break; + case USB_RECIP_ENDPOINT: + ep = get_ep_by_addr(pcd, __le16_to_cpu(ctrl.wIndex)); + if (ep == NULL || __le16_to_cpu(ctrl.wLength) > 2) { + ep0_do_stall(pcd, -EOPNOTSUPP); + return; + } + *status = ep->stopped; + break; + } + + *status = __cpu_to_le16(*status); + + pcd->ep0_pending = 1; + ep0->dwc_ep.start_xfer_buff = (u8 *) status; + ep0->dwc_ep.xfer_buff = (u8 *) status; + ep0->dwc_ep.dma_addr = pcd->status_buf_dma_handle; + ep0->dwc_ep.xfer_len = 2; + ep0->dwc_ep.xfer_count = 0; + ep0->dwc_ep.total_len = ep0->dwc_ep.xfer_len; + dwc_otg_ep0_start_transfer(GET_CORE_IF(pcd), &ep0->dwc_ep); + break; + case USB_REQ_CLEAR_FEATURE: + do_clear_feature(pcd); + break; + case USB_REQ_SET_FEATURE: + do_set_feature(pcd); + break; + case USB_REQ_SET_ADDRESS: + if (ctrl.bRequestType == USB_RECIP_DEVICE) { + u32 dcfg = 0; + + dcfg = DWC_DCFG_DEV_ADDR_WR(dcfg, + __le16_to_cpu(ctrl.wValue)); + dwc_reg_modify(dev_if->dev_global_regs, DWC_DCFG, + 0, dcfg); + do_setup_in_status_phase(pcd); + return; + } + break; + case USB_REQ_SET_INTERFACE: + case USB_REQ_SET_CONFIGURATION: + pcd->request_config = 1; /* Configuration changed */ + do_gadget_setup(pcd, &ctrl); + break; + case USB_REQ_SYNCH_FRAME: + do_gadget_setup(pcd, &ctrl); + break; + default: + /* Call the Gadget Driver's setup functions */ + do_gadget_setup(pcd, &ctrl); + break; + } +} + +/** + * This function completes the ep0 control transfer. + */ +static int ep0_complete_request(struct pcd_ep *ep) +{ + struct core_if *core_if = GET_CORE_IF(ep->pcd); + struct device_if *dev_if = core_if->dev_if; + ulong in_regs = dev_if->in_ep_regs[ep->dwc_ep.num]; + u32 deptsiz = 0; + struct pcd_request *req; + int is_last = 0; + struct dwc_pcd *pcd = ep->pcd; + + if (pcd->ep0_pending && list_empty(&ep->queue)) { + if (ep->dwc_ep.is_in) + do_setup_out_status_phase(pcd); + else + do_setup_in_status_phase(pcd); + + pcd->ep0_pending = 0; + pcd->ep0state = EP0_STATUS; + return 1; + } + + if (list_empty(&ep->queue)) + return 0; + + req = list_entry(ep->queue.next, struct pcd_request, queue); + + if (pcd->ep0state == EP0_STATUS) { + is_last = 1; + } else if (ep->dwc_ep.is_in) { + deptsiz = dwc_reg_read(in_regs, DWC_DIEPTSIZ); + + if (DWC_DEPTSIZ0_XFER_SIZ_RD(deptsiz) == 0) { + req->req.actual = ep->dwc_ep.xfer_count; + do_setup_out_status_phase(pcd); + } + } else { + /* This is ep0-OUT */ + req->req.actual = ep->dwc_ep.xfer_count; + do_setup_in_status_phase(pcd); + } + + /* Complete the request */ + if (is_last) { + request_done(ep, req, 0); + ep->dwc_ep.start_xfer_buff = NULL; + ep->dwc_ep.xfer_buff = NULL; + ep->dwc_ep.xfer_len = 0; + return 1; + } + return 0; +} + +/** + * This function completes the request for the EP. If there are additional + * requests for the EP in the queue they will be started. + */ +static void complete_ep(struct pcd_ep *ep) +{ + struct core_if *core_if = GET_CORE_IF(ep->pcd); + struct device_if *dev_if = core_if->dev_if; + ulong in_ep_regs = dev_if->in_ep_regs[ep->dwc_ep.num]; + u32 deptsiz = 0; + struct pcd_request *req = NULL; + int is_last = 0; + + /* Get any pending requests */ + if (!list_empty(&ep->queue)) + req = list_entry(ep->queue.next, struct pcd_request, queue); + + if (ep->dwc_ep.is_in) { + deptsiz = dwc_reg_read(in_ep_regs, DWC_DIEPTSIZ); + + if (core_if->dma_enable && !DWC_DEPTSIZ_XFER_SIZ_RD(deptsiz)) + ep->dwc_ep.xfer_count = ep->dwc_ep.xfer_len; + + if (DWC_DEPTSIZ_XFER_SIZ_RD(deptsiz) == 0 && + DWC_DEPTSIZ_PKT_CNT_RD(deptsiz) == 0 && + ep->dwc_ep.xfer_count == ep->dwc_ep.xfer_len) + is_last = 1; + else + pr_warning("Incomplete transfer (%s-%s " + "[siz=%d pkt=%d])\n", ep->ep.name, + ep->dwc_ep.is_in ? "IN" : "OUT", + DWC_DEPTSIZ_XFER_SIZ_RD(deptsiz), + DWC_DEPTSIZ_PKT_CNT_RD(deptsiz)); + } else { + ulong out_ep_regs = dev_if->out_ep_regs[ep->dwc_ep.num]; + + deptsiz = dwc_reg_read(out_ep_regs, DWC_DOEPTSIZ); + is_last = 1; + } + + /* Complete the request */ + if (is_last) { + /* + * Added-sr: 2007-07-26 + * + * Since the 405EZ (Ultra) only support 2047 bytes as + * max transfer size, we have to split up bigger transfers + * into multiple transfers of 1024 bytes sized messages. + * I happens often, that transfers of 4096 bytes are + * required (zero-gadget, file_storage-gadget). + */ + if ((dwc_has_feature(core_if, DWC_LIMITED_XFER)) && + ep->dwc_ep.bytes_pending) { + ulong in_regs = + core_if->dev_if->in_ep_regs[ep->dwc_ep.num]; + u32 intr_mask = 0; + + ep->dwc_ep.xfer_len = ep->dwc_ep.bytes_pending; + if (ep->dwc_ep.xfer_len > MAX_XFER_LEN) { + ep->dwc_ep.bytes_pending = ep->dwc_ep.xfer_len - + MAX_XFER_LEN; + ep->dwc_ep.xfer_len = MAX_XFER_LEN; + } else { + ep->dwc_ep.bytes_pending = 0; + } + + /* + * Restart the current transfer with the next "chunk" + * of data. + */ + ep->dwc_ep.xfer_count = 0; + + deptsiz = dwc_reg_read(in_regs, DWC_DIEPTSIZ); + deptsiz = + DWC_DEPTSIZ_XFER_SIZ_RW(deptsiz, + ep->dwc_ep.xfer_len); + deptsiz = + DWC_DEPTSIZ_PKT_CNT_RW(deptsiz, + ((ep->dwc_ep.xfer_len - 1 + + ep->dwc_ep.maxpacket) / + ep->dwc_ep.maxpacket)); + dwc_reg_write(in_regs, DWC_DIEPTSIZ, deptsiz); + + intr_mask |= DWC_INTSTS_NP_TXFIFO_EMPT; + dwc_reg_modify((core_if->core_global_regs), + DWC_GINTSTS, intr_mask, 0); + dwc_reg_modify((core_if->core_global_regs), + DWC_GINTMSK, intr_mask, intr_mask); + + /* + * Just return here if message was not completely + * transferred. + */ + return; + } + if (core_if->dma_enable) + req->req.actual = ep->dwc_ep.xfer_len - + DWC_DEPTSIZ_XFER_SIZ_RD(deptsiz); + else + req->req.actual = ep->dwc_ep.xfer_count; + + request_done(ep, req, 0); + ep->dwc_ep.start_xfer_buff = NULL; + ep->dwc_ep.xfer_buff = NULL; + ep->dwc_ep.xfer_len = 0; + + /* If there is a request in the queue start it. */ + dwc_start_next_request(ep); + } +} + +/** + * This function continues control IN transfers started by + * dwc_otg_ep0_start_transfer, when the transfer does not fit in a + * single packet. NOTE: The DIEPCTL0/DOEPCTL0 registers only have one + * bit for the packet count. + */ +static void dwc_otg_ep0_continue_transfer(struct core_if *c_if, + struct dwc_ep *ep) +{ + if (ep->is_in) { + u32 depctl = 0; + u32 deptsiz = 0; + struct device_if *d_if = c_if->dev_if; + ulong in_regs = d_if->in_ep_regs[0]; + u32 tx_status = 0; + ulong glbl_regs = c_if->core_global_regs; + + tx_status = dwc_reg_read(glbl_regs, DWC_GNPTXSTS); + + depctl = dwc_reg_read(in_regs, DWC_DIEPCTL); + deptsiz = dwc_reg_read(in_regs, DWC_DIEPTSIZ); + + /* + * Program the transfer size and packet count as follows: + * xfersize = N * maxpacket + short_packet + * pktcnt = N + (short_packet exist ? 1 : 0) + */ + if (ep->total_len - ep->xfer_count > ep->maxpacket) + deptsiz = DWC_DEPTSIZ0_XFER_SIZ_RW(deptsiz, + ep->maxpacket); + else + deptsiz = DWC_DEPTSIZ0_XFER_SIZ_RW(deptsiz, + (ep->total_len - + ep->xfer_count)); + + deptsiz = DWC_DEPTSIZ0_PKT_CNT_RW(deptsiz, 1); + ep->xfer_len += DWC_DEPTSIZ0_XFER_SIZ_RD(deptsiz); + dwc_reg_write(in_regs, DWC_DIEPTSIZ, deptsiz); + + /* Write the DMA register */ + if (DWC_HWCFG2_ARCH_RD(c_if->hwcfg2) == DWC_INT_DMA_ARCH) + dwc_reg_write(in_regs, DWC_DIEPDMA, ep->dma_addr); + + /* EP enable, IN data in FIFO */ + depctl = DWC_DEPCTL_CLR_NAK_RW(depctl, 1); + depctl = DWC_DEPCTL_EPENA_RW(depctl, 1); + dwc_reg_write(in_regs, DWC_DIEPCTL, depctl); + + /* + * Enable the Non-Periodic Tx FIFO empty interrupt, the + * data will be written into the fifo by the ISR. + */ + if (!c_if->dma_enable) { + u32 intr_mask = 0; + + /* First clear it from GINTSTS */ + intr_mask |= DWC_INTMSK_NP_TXFIFO_EMPT; + dwc_reg_write(glbl_regs, DWC_GINTSTS, intr_mask); + + /* To avoid spurious NPTxFEmp intr */ + dwc_reg_modify(glbl_regs, DWC_GINTMSK, intr_mask, + intr_mask); + } + } +} + +/** + * This function handles EP0 Control transfers. + * + * The state of the control tranfers are tracked in ep0state + */ +static void handle_ep0(struct dwc_pcd *pcd) +{ + struct core_if *core_if = GET_CORE_IF(pcd); + struct pcd_ep *ep0 = &pcd->ep0; + + switch (pcd->ep0state) { + case EP0_DISCONNECT: + break; + case EP0_IDLE: + pcd->request_config = 0; + pcd_setup(pcd); + break; + case EP0_IN_DATA_PHASE: + if (core_if->dma_enable) + /* + * For EP0 we can only program 1 packet at a time so we + * need to do the calculations after each complete. + * Call write_packet to make the calculations, as in + * slave mode, and use those values to determine if we + * can complete. + */ + dwc_otg_ep_write_packet(core_if, &ep0->dwc_ep, 1); + else + dwc_otg_ep_write_packet(core_if, &ep0->dwc_ep, 0); + + if (ep0->dwc_ep.xfer_count < ep0->dwc_ep.total_len) + dwc_otg_ep0_continue_transfer(core_if, &ep0->dwc_ep); + else + ep0_complete_request(ep0); + break; + case EP0_OUT_DATA_PHASE: + ep0_complete_request(ep0); + break; + case EP0_STATUS: + ep0_complete_request(ep0); + pcd->ep0state = EP0_IDLE; + ep0->stopped = 1; + ep0->dwc_ep.is_in = 0; /* OUT for next SETUP */ + + /* Prepare for more SETUP Packets */ + if (core_if->dma_enable) { + ep0_out_start(core_if, pcd); + } else { + int i; + u32 diepctl = 0; + + diepctl = dwc_reg_read(in_ep_ctl_reg(pcd, 0), 0); + if (pcd->ep0.queue_sof) { + pcd->ep0.queue_sof = 0; + dwc_start_next_request(&pcd->ep0); + } + + diepctl = dwc_reg_read(in_ep_ctl_reg(pcd, 0), 0); + if (pcd->ep0.queue_sof) { + pcd->ep0.queue_sof = 0; + dwc_start_next_request(&pcd->ep0); + } + + for (i = 0; i < core_if->dev_if->num_in_eps; i++) { + diepctl = dwc_reg_read(in_ep_ctl_reg(pcd, i), + 0); + + if (pcd->in_ep[i].queue_sof) { + pcd->in_ep[i].queue_sof = 0; + dwc_start_next_request(&pcd->in_ep[i]); + } + } + } + break; + case EP0_STALL: + pr_err("EP0 STALLed, should not get here handle_ep0()\n"); + break; + } +} + +/** + * Restart transfer + */ +static void restart_transfer(struct dwc_pcd *pcd, const u32 ep_num) +{ + struct core_if *core_if = GET_CORE_IF(pcd); + struct device_if *dev_if = core_if->dev_if; + u32 dieptsiz = 0; + struct pcd_ep *ep; + + dieptsiz = dwc_reg_read(dev_if->in_ep_regs[ep_num], DWC_DIEPTSIZ); + ep = get_in_ep(pcd, ep_num); + + /* + * If pktcnt is not 0, and xfersize is 0, and there is a buffer, + * resend the last packet. + */ + if (DWC_DEPTSIZ_PKT_CNT_RD(dieptsiz) && + !DWC_DEPTSIZ_XFER_SIZ_RD(dieptsiz) && ep->dwc_ep.start_xfer_buff) { + if (ep->dwc_ep.xfer_len <= ep->dwc_ep.maxpacket) { + ep->dwc_ep.xfer_count = 0; + ep->dwc_ep.xfer_buff = ep->dwc_ep.start_xfer_buff; + } else { + ep->dwc_ep.xfer_count -= ep->dwc_ep.maxpacket; + + /* convert packet size to dwords. */ + ep->dwc_ep.xfer_buff -= ep->dwc_ep.maxpacket; + } + ep->stopped = 0; + + if (!ep_num) + dwc_otg_ep0_start_transfer(core_if, &ep->dwc_ep); + else + dwc_otg_ep_start_transfer(core_if, &ep->dwc_ep); + } +} + +/** + * Handle the IN EP Transfer Complete interrupt. + * + * If dedicated fifos are enabled, then the Tx FIFO empty interrupt for the EP + * is disabled. Otherwise the NP Tx FIFO empty interrupt is disabled. + */ +static void handle_in_ep_xfr_complete_intr(struct dwc_pcd *pcd, + struct pcd_ep *ep, u32 num) +{ + struct core_if *c_if = GET_CORE_IF(pcd); + struct device_if *d_if = c_if->dev_if; + struct dwc_ep *dwc_ep = &ep->dwc_ep; + u32 diepint = 0; + + if (c_if->en_multiple_tx_fifo) { + u32 fifoemptymsk = 0x1 << dwc_ep->num; + dwc_reg_modify(d_if->dev_global_regs, + DWC_DTKNQR4FIFOEMPTYMSK, fifoemptymsk, 0); + } else { + u32 intr_mask = 0; + + intr_mask |= DWC_INTMSK_NP_TXFIFO_EMPT; + dwc_reg_modify((c_if->core_global_regs), DWC_GINTMSK, + intr_mask, 0); + } + + /* Clear the interrupt, then complete the transfer */ + diepint = DWC_DIEPINT_TX_CMPL_RW(diepint, 1); + dwc_reg_write(d_if->in_ep_regs[num], DWC_DIEPINT, diepint); + + if (!num) + handle_ep0(pcd); + else + complete_ep(ep); +} + +/** + * Handle the IN EP disable interrupt. + */ +static void handle_in_ep_disable_intr(struct dwc_pcd *pcd, const u32 ep_num) +{ + struct core_if *core_if = GET_CORE_IF(pcd); + struct device_if *dev_if = core_if->dev_if; + u32 dieptsiz = 0; + u32 dctl = 0; + struct pcd_ep *ep; + struct dwc_ep *dwc_ep; + u32 diepint = 0; + + ep = get_in_ep(pcd, ep_num); + dwc_ep = &ep->dwc_ep; + + dieptsiz = dwc_reg_read(dev_if->in_ep_regs[ep_num], DWC_DIEPTSIZ); + + if (ep->stopped) { + /* Flush the Tx FIFO */ + dwc_otg_flush_tx_fifo(core_if, dwc_ep->tx_fifo_num); + + /* Clear the Global IN NP NAK */ + dctl = 0; + dctl = DWC_DCTL_CLR_CLBL_NP_IN_NAK(dctl, 1); + dwc_reg_modify(dev_ctl_reg(pcd), 0, dctl, 0); + + if (DWC_DEPTSIZ_PKT_CNT_RD(dieptsiz) || + DWC_DEPTSIZ_XFER_SIZ_RD(dieptsiz)) + restart_transfer(pcd, ep_num); + } else { + if (DWC_DEPTSIZ_PKT_CNT_RD(dieptsiz) || + DWC_DEPTSIZ_XFER_SIZ_RD(dieptsiz)) + restart_transfer(pcd, ep_num); + } + /* Clear epdisabled */ + diepint = DWC_DIEPINT_EP_DISA_RW(diepint, 1); + dwc_reg_write(in_ep_int_reg(pcd, ep_num), 0, diepint); + +} + +/** + * Handler for the IN EP timeout handshake interrupt. + */ +static void handle_in_ep_timeout_intr(struct dwc_pcd *pcd, const u32 ep_num) +{ + struct core_if *core_if = GET_CORE_IF(pcd); + struct pcd_ep *ep; + u32 dctl = 0; + u32 intr_mask = 0; + u32 diepint = 0; + + ep = get_in_ep(pcd, ep_num); + + /* Disable the NP Tx Fifo Empty Interrrupt */ + if (!core_if->dma_enable) { + intr_mask |= DWC_INTMSK_NP_TXFIFO_EMPT; + dwc_reg_modify((core_if->core_global_regs), DWC_GINTMSK, + intr_mask, 0); + } + + /* Non-periodic EP */ + /* Enable the Global IN NAK Effective Interrupt */ + intr_mask |= DWC_INTMSK_GLBL_IN_NAK; + dwc_reg_modify((core_if->core_global_regs), DWC_GINTMSK, 0, + intr_mask); + + /* Set Global IN NAK */ + dctl = DWC_DCTL_CLR_CLBL_NP_IN_NAK(dctl, 1); + dwc_reg_modify(dev_ctl_reg(pcd), 0, dctl, dctl); + ep->stopped = 1; + + /* Clear timeout */ + diepint = DWC_DIEPINT_TOUT_COND_RW(diepint, 1); + dwc_reg_write(in_ep_int_reg(pcd, ep_num), 0, diepint); +} + +/** + * Handles the IN Token received with TxF Empty interrupt. + * + * For the 405EZ, only start the next transfer, when currently no other transfer + * is active on this endpoint. + * + * Note that the bits in the Device IN endpoint mask register are laid out + * exactly the same as the Device IN endpoint interrupt register. + */ +static void handle_in_ep_tx_fifo_empty_intr(struct dwc_pcd *pcd, + struct pcd_ep *ep, u32 num) +{ + u32 diepint = 0; + + if (!ep->stopped && num) { + u32 diepmsk = 0; + + diepmsk = DWC_DIEPMSK_IN_TKN_TX_EMPTY_RW(diepmsk, 1); + dwc_reg_modify(dev_diepmsk_reg(pcd), 0, diepmsk, 0); + + if (dwc_has_feature(GET_CORE_IF(pcd), DWC_LIMITED_XFER)) { + if (!ep->dwc_ep.active) + dwc_start_next_request(ep); + } else { + dwc_start_next_request(ep); + } + } + /* Clear intktxfemp */ + diepint = DWC_DIEPMSK_IN_TKN_TX_EMPTY_RW(diepint, 1); + dwc_reg_write(in_ep_int_reg(pcd, num), 0, diepint); +} + +static void handle_in_ep_nak_effective_intr(struct dwc_pcd *pcd, + struct pcd_ep *ep, u32 num) +{ + u32 diepctl = 0; + u32 diepint = 0; + + /* Periodic EP */ + if (ep->disabling) { + diepctl = 0; + diepctl = DWC_DEPCTL_SET_NAK_RW(diepctl, 1); + diepctl = DWC_DEPCTL_DPID_RW(diepctl, 1); + dwc_reg_modify(in_ep_ctl_reg(pcd, num), 0, diepctl, diepctl); + } + /* Clear inepnakeff */ + diepint = DWC_DIEPINT_IN_EP_NAK_RW(diepint, 1); + dwc_reg_write(in_ep_int_reg(pcd, num), 0, diepint); + +} + +/** + * This function returns the Device IN EP Interrupt register + */ +static inline u32 dwc_otg_read_diep_intr(struct core_if *core_if, + struct dwc_ep *ep) +{ + struct device_if *dev_if = core_if->dev_if; + u32 v, msk, emp; + + msk = dwc_reg_read(dev_if->dev_global_regs, DWC_DIEPMSK); + emp = + dwc_reg_read(dev_if->dev_global_regs, DWC_DTKNQR4FIFOEMPTYMSK); + msk |= ((emp >> ep->num) & 0x1) << 7; + v = dwc_reg_read(dev_if->in_ep_regs[ep->num], DWC_DIEPINT) & msk; + return v; +} + +/** + * This function reads the Device All Endpoints Interrupt register and + * returns the IN endpoint interrupt bits. + */ +static inline u32 dwc_otg_read_dev_all_in_ep_intr(struct core_if *_if) +{ + u32 v; + + v = dwc_reg_read(_if->dev_if->dev_global_regs, DWC_DAINT) & + dwc_reg_read(_if->dev_if->dev_global_regs, DWC_DAINTMSK); + return v & 0xffff; +} + +/** + * This interrupt indicates that an IN EP has a pending Interrupt. + * The sequence for handling the IN EP interrupt is shown below: + * + * - Read the Device All Endpoint Interrupt register + * - Repeat the following for each IN EP interrupt bit set (from LSB to MSB). + * + * - Read the Device Endpoint Interrupt (DIEPINTn) register + * - If "Transfer Complete" call the request complete function + * - If "Endpoint Disabled" complete the EP disable procedure. + * - If "AHB Error Interrupt" log error + * - If "Time-out Handshake" log error + * - If "IN Token Received when TxFIFO Empty" write packet to Tx FIFO. + * - If "IN Token EP Mismatch" (disable, this is handled by EP Mismatch + * Interrupt) + */ +static int dwc_otg_pcd_handle_in_ep_intr(struct dwc_pcd *pcd) +{ + struct core_if *core_if = GET_CORE_IF(pcd); + u32 diepint = 0; + u32 ep_intr; + u32 epnum = 0; + struct pcd_ep *ep; + struct dwc_ep *dwc_ep; + + /* Read in the device interrupt bits */ + ep_intr = dwc_otg_read_dev_all_in_ep_intr(core_if); + + /* Service the Device IN interrupts for each endpoint */ + while (ep_intr) { + if (ep_intr & 0x1) { + u32 c_diepint; + + /* Get EP pointer */ + ep = get_in_ep(pcd, epnum); + dwc_ep = &ep->dwc_ep; + + diepint = dwc_otg_read_diep_intr(core_if, dwc_ep); + + /* Transfer complete */ + if (DWC_DIEPINT_TX_CMPL_RD(diepint)) + handle_in_ep_xfr_complete_intr(pcd, ep, epnum); + + /* Endpoint disable */ + if (DWC_DIEPINT_EP_DISA_RD(diepint)) + handle_in_ep_disable_intr(pcd, epnum); + + /* AHB Error */ + if (DWC_DIEPINT_AHB_ERROR_RD(diepint)) { + /* Clear ahberr */ + c_diepint = 0; + c_diepint = + DWC_DIEPINT_AHB_ERROR_RW(c_diepint, 1); + dwc_reg_write(in_ep_int_reg(pcd, epnum), 0, + c_diepint); + } + + /* TimeOUT Handshake (non-ISOC IN EPs) */ + if (DWC_DIEPINT_TOUT_COND_RD(diepint)) + handle_in_ep_timeout_intr(pcd, epnum); + + /* IN Token received with TxF Empty */ + if (DWC_DIEPINT_IN_TKN_TX_EMPTY_RD(diepint)) + handle_in_ep_tx_fifo_empty_intr(pcd, ep, epnum); + + /* IN Token Received with EP mismatch */ + if (DWC_DIEPINT_IN_TKN_EP_MISS_RD(diepint)) { + /* Clear intknepmis */ + c_diepint = 0; + c_diepint = + DWC_DIEPINT_IN_TKN_EP_MISS_RW(c_diepint, 1); + dwc_reg_write(in_ep_int_reg(pcd, epnum), 0, + c_diepint); + } + + /* IN Endpoint NAK Effective */ + if (DWC_DIEPINT_IN_EP_NAK_RD(diepint)) + handle_in_ep_nak_effective_intr(pcd, ep, epnum); + + /* IN EP Tx FIFO Empty Intr */ + if (DWC_DIEPINT_TXFIFO_EMPTY_RD(diepint)) + write_empty_tx_fifo(pcd, epnum); + } + epnum++; + ep_intr >>= 1; + } + return 1; +} + +/** + * This function reads the Device All Endpoints Interrupt register and + * returns the OUT endpoint interrupt bits. + */ +static inline u32 dwc_otg_read_dev_all_out_ep_intr(struct core_if *_if) +{ + u32 v; + + v = dwc_reg_read(_if->dev_if->dev_global_regs, DWC_DAINT) & + dwc_reg_read(_if->dev_if->dev_global_regs, DWC_DAINTMSK); + return (v & 0xffff0000) >> 16; +} + +/** + * This function returns the Device OUT EP Interrupt register + */ +static inline u32 dwc_otg_read_doep_intr(struct core_if *core_if, + struct dwc_ep *ep) +{ + struct device_if *dev_if = core_if->dev_if; + u32 v; + + v = dwc_reg_read(dev_if->out_ep_regs[ep->num], DWC_DOEPINT) & + dwc_reg_read(dev_if->dev_global_regs, DWC_DOEPMSK); + return v; +} + +/** + * This interrupt indicates that an OUT EP has a pending Interrupt. + * The sequence for handling the OUT EP interrupt is shown below: + * + * - Read the Device All Endpoint Interrupt register. + * - Repeat the following for each OUT EP interrupt bit set (from LSB to MSB). + * + * - Read the Device Endpoint Interrupt (DOEPINTn) register + * - If "Transfer Complete" call the request complete function + * - If "Endpoint Disabled" complete the EP disable procedure. + * - If "AHB Error Interrupt" log error + * - If "Setup Phase Done" process Setup Packet (See Standard USB Command + * Processing) + */ +static int dwc_otg_pcd_handle_out_ep_intr(struct dwc_pcd *pcd) +{ + struct core_if *core_if = GET_CORE_IF(pcd); + u32 ep_intr; + u32 doepint = 0; + u32 epnum = 0; + struct dwc_ep *dwc_ep; + + /* Read in the device interrupt bits */ + ep_intr = dwc_otg_read_dev_all_out_ep_intr(core_if); + while (ep_intr) { + if (ep_intr & 0x1) { + u32 c_doepint = 0; + + dwc_ep = &((get_out_ep(pcd, epnum))->dwc_ep); + doepint = dwc_otg_read_doep_intr(core_if, dwc_ep); + + /* Transfer complete */ + if (DWC_DOEPINT_TX_COMPL_RD(doepint)) { + /* Clear xfercompl */ + c_doepint = 0; + c_doepint = + DWC_DOEPMSK_TX_COMPL_RW(c_doepint, 1); + dwc_reg_write(out_ep_int_reg(pcd, epnum), 0, + c_doepint); + if (epnum == 0) + handle_ep0(pcd); + else + complete_ep(get_out_ep(pcd, epnum)); + } + + /* Endpoint disable */ + if (DWC_DOEPINT_EP_DISA_RD(doepint)) { + /* Clear epdisabled */ + c_doepint = 0; + c_doepint = + DWC_DOEPMSK_EP_DISA_RW(c_doepint, 1); + dwc_reg_write(out_ep_int_reg(pcd, epnum), 0, + c_doepint); + } + + /* AHB Error */ + if (DWC_DOEPINT_AHB_ERROR_RD(doepint)) { + c_doepint = 0; + c_doepint = + DWC_DOEPMSK_AHB_ERROR_RW(c_doepint, 1); + dwc_reg_write(out_ep_int_reg(pcd, epnum), 0, + c_doepint); + } + + /* Setup Phase Done (control EPs) */ + if (DWC_DOEPINT_SETUP_DONE_RD(doepint)) { + c_doepint = 0; + c_doepint = + DWC_DOEPMSK_SETUP_DONE_RW(c_doepint, 1); + dwc_reg_write(out_ep_int_reg(pcd, epnum), 0, + c_doepint); + handle_ep0(pcd); + } + } + epnum++; + ep_intr >>= 1; + } + return 1; +} + +/** + * Incomplete ISO IN Transfer Interrupt. This interrupt indicates one of the + * following conditions occurred while transmitting an ISOC transaction. + * + * - Corrupted IN Token for ISOC EP. + * - Packet not complete in FIFO. + * + * The follow actions should be taken: + * - Determine the EP + * - Set incomplete flag in dwc_ep structure + * - Disable EP. When "Endpoint Disabled" interrupt is received Flush FIFO + */ +static int dwc_otg_pcd_handle_incomplete_isoc_in_intr(struct dwc_pcd *pcd) +{ + u32 intr_mask = 0; + u32 gintsts = 0; + + pr_info("Interrupt handler not implemented for IN ISOC " + "Incomplete\n"); + + /* Turn off and clear the interrupt */ + intr_mask |= DWC_INTMSK_INCMP_IN_ATX; + dwc_reg_modify((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTMSK, + intr_mask, 0); + + gintsts |= DWC_INTSTS_INCMP_IN_ATX; + dwc_reg_write((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTSTS, + gintsts); + return 1; +} + +/** + * Incomplete ISO OUT Transfer Interrupt. This interrupt indicates that the + * core has dropped an ISO OUT packet. The following conditions can be the + * cause: + * + * - FIFO Full, the entire packet would not fit in the FIFO. + * - CRC Error + * - Corrupted Token + * + * The follow actions should be taken: + * - Determine the EP + * - Set incomplete flag in dwc_ep structure + * - Read any data from the FIFO + * - Disable EP. When "Endpoint Disabled" interrupt is received re-enable EP. + */ +static int dwc_otg_pcd_handle_incomplete_isoc_out_intr(struct dwc_pcd *pcd) +{ + u32 intr_mask = 0; + u32 gintsts = 0; + + pr_info("Interrupt handler not implemented for OUT ISOC " + "Incomplete\n"); + + /* Turn off and clear the interrupt */ + intr_mask |= DWC_INTMSK_INCMP_OUT_PTX; + dwc_reg_modify((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTMSK, + intr_mask, 0); + + gintsts |= DWC_INTSTS_INCMP_OUT_PTX; + dwc_reg_write((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTSTS, + gintsts); + return 1; +} + +/** + * This function handles the Global IN NAK Effective interrupt. + */ +static int dwc_otg_pcd_handle_in_nak_effective(struct dwc_pcd *pcd) +{ + struct device_if *dev_if = GET_CORE_IF(pcd)->dev_if; + u32 diepctl = 0; + u32 diepctl_rd = 0; + u32 intr_mask = 0; + u32 gintsts = 0; + u32 i; + + /* Disable all active IN EPs */ + diepctl = DWC_DEPCTL_DPID_RW(diepctl, 1); + diepctl = DWC_DEPCTL_SET_NAK_RW(diepctl, 1); + for (i = 0; i <= dev_if->num_in_eps; i++) { + diepctl_rd = dwc_reg_read(in_ep_ctl_reg(pcd, i), 0); + if (DWC_DEPCTL_EPENA_RD(diepctl_rd)) + dwc_reg_write(in_ep_ctl_reg(pcd, i), 0, diepctl); + } + + /* Disable the Global IN NAK Effective Interrupt */ + intr_mask |= DWC_INTMSK_GLBL_IN_NAK; + dwc_reg_modify((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTMSK, + intr_mask, 0); + + /* Clear interrupt */ + gintsts |= DWC_INTSTS_GLBL_IN_NAK; + dwc_reg_write((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTSTS, + gintsts); + return 1; +} + +/** + * This function handles the Global OUT NAK Effective interrupt. + */ +static int dwc_otg_pcd_handle_out_nak_effective(struct dwc_pcd *pcd) +{ + u32 intr_mask = 0; + u32 gintsts = 0; + + pr_info("Interrupt handler not implemented for Global IN " + "NAK Effective\n"); + + /* Turn off and clear the interrupt */ + intr_mask |= DWC_INTMSK_GLBL_OUT_NAK; + dwc_reg_modify((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTMSK, + intr_mask, 0); + + /* Clear goutnakeff */ + gintsts |= DWC_INTSTS_GLBL_OUT_NAK; + dwc_reg_write((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTSTS, + gintsts); + return 1; +} + +/** + * PCD interrupt handler. + * + * The PCD handles the device interrupts. Many conditions can cause a + * device interrupt. When an interrupt occurs, the device interrupt + * service routine determines the cause of the interrupt and + * dispatches handling to the appropriate function. These interrupt + * handling functions are described below. + * + * All interrupt registers are processed from LSB to MSB. + * + */ +int dwc_otg_pcd_handle_intr(struct dwc_pcd *pcd) +{ + struct core_if *core_if = GET_CORE_IF(pcd); + u32 gintr_status; + int ret = 0; + + if (dwc_otg_is_device_mode(core_if)) { + spin_lock(&pcd->lock); + + gintr_status = dwc_otg_read_core_intr(core_if); + if (!gintr_status) { + spin_unlock(&pcd->lock); + return 0; + } + + if (gintr_status & DWC_INTSTS_STRT_OF_FRM) + ret |= dwc_otg_pcd_handle_sof_intr(pcd); + if (gintr_status & DWC_INTSTS_RXFIFO_NOT_EMPT) + ret |= dwc_otg_pcd_handle_rx_status_q_level_intr(pcd); + if (gintr_status & DWC_INTSTS_NP_TXFIFO_EMPT) + ret |= dwc_otg_pcd_handle_np_tx_fifo_empty_intr(pcd); + if (gintr_status & DWC_INTSTS_GLBL_IN_NAK) + ret |= dwc_otg_pcd_handle_in_nak_effective(pcd); + if (gintr_status & DWC_INTSTS_GLBL_OUT_NAK) + ret |= dwc_otg_pcd_handle_out_nak_effective(pcd); + if (gintr_status & DWC_INTSTS_I2C_INTR) + ret |= dwc_otg_pcd_handle_i2c_intr(pcd); + if (gintr_status & DWC_INTSTS_EARLY_SUSP) + ret |= dwc_otg_pcd_handle_early_suspend_intr(pcd); + if (gintr_status & DWC_INTSTS_USB_RST) + ret |= dwc_otg_pcd_handle_usb_reset_intr(pcd); + if (gintr_status & DWC_INTSTS_ENUM_DONE) + ret |= dwc_otg_pcd_handle_enum_done_intr(pcd); + if (gintr_status & DWC_INTSTS_ISYNC_OUTPKT_DRP) + ret |= + dwc_otg_pcd_handle_isoc_out_packet_dropped_intr + (pcd); + if (gintr_status & DWC_INTSTS_END_OF_PFRM) + ret |= dwc_otg_pcd_handle_end_periodic_frame_intr(pcd); + if (gintr_status & DWC_INTSTS_ENDP_MIS_MTCH) + ret |= dwc_otg_pcd_handle_ep_mismatch_intr(core_if); + if (gintr_status & DWC_INTSTS_IN_ENDP) + ret |= dwc_otg_pcd_handle_in_ep_intr(pcd); + if (gintr_status & DWC_INTSTS_OUT_ENDP) + ret |= dwc_otg_pcd_handle_out_ep_intr(pcd); + if (gintr_status & DWC_INTSTS_INCMP_IN_ATX) + ret |= dwc_otg_pcd_handle_incomplete_isoc_in_intr(pcd); + if (gintr_status & DWC_INTSTS_INCMP_OUT_PTX) + ret |= dwc_otg_pcd_handle_incomplete_isoc_out_intr(pcd); + + spin_unlock(&pcd->lock); + } + return ret; +} diff --git a/drivers/usb/dwc/regs.h b/drivers/usb/dwc/regs.h new file mode 100644 index 0000000..d1748f1 --- /dev/null +++ b/drivers/usb/dwc/regs.h @@ -0,0 +1,1326 @@ +/* + * DesignWare HS OTG controller driver + * Copyright (C) 2006 Synopsys, Inc. + * Portions Copyright (C) 2010 Applied Micro Circuits Corporation. + * + * This program 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. + * + * 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 version 2 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see http://www.gnu.org/licenses + * or write to the Free Software Foundation, Inc., 51 Franklin Street, + * Suite 500, Boston, MA 02110-1335 USA. + * + * Based on Synopsys driver version 2.60a + * Modified by Mark Miesfeld <mmiesfeld@apm.com> + * + * Revamped register difinitions by Tirumala R Marri(tmarri@apm.com) + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL SYNOPSYS, INC. BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES + * (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef __DWC_OTG_REGS_H__ +#define __DWC_OTG_REGS_H__ + +#include <linux/types.h> +/*Bit fields in the Device EP Transfer Size Register is 11 bits */ +#undef DWC_LIMITED_XFER_SIZE +/* + * This file contains the Macro defintions for accessing the DWC_otg core + * registers. + * + * The application interfaces with the HS OTG core by reading from and + * writing to the Control and Status Register (CSR) space through the + * AHB Slave interface. These registers are 32 bits wide, and the + * addresses are 32-bit-block aligned. + * CSRs are classified as follows: + * - Core Global Registers + * - Device Mode Registers + * - Device Global Registers + * - Device Endpoint Specific Registers + * - Host Mode Registers + * - Host Global Registers + * - Host Port CSRs + * - Host Channel Specific Registers + * + * Only the Core Global registers can be accessed in both Device and + * Host modes. When the HS OTG core is operating in one mode, either + * Device or Host, the application must not access registers from the + * other mode. When the core switches from one mode to another, the + * registers in the new mode of operation must be reprogrammed as they + * would be after a power-on reset. + */ + +/* + * DWC_otg Core registers. The core_global_regs structure defines the + * size and relative field offsets for the Core Global registers. + */ +#define DWC_GOTGCTL 0x000 +#define DWC_GOTGINT 0x004 +#define DWC_GAHBCFG 0x008 +#define DWC_GUSBCFG 0x00C +#define DWC_GRSTCTL 0x010 +#define DWC_GINTSTS 0x014 +#define DWC_GINTMSK 0x018 +#define DWC_GRXSTSR 0x01C +#define DWC_GRXSTSP 0x020 +#define DWC_GRXFSIZ 0x024 +#define DWC_GNPTXFSIZ 0x028 +#define DWC_GNPTXSTS 0x02C +#define DWC_GI2CCTL 0x030 +#define DWC_VDCTL 0x034 +#define DWC_GGPIO 0x038 +#define DWC_GUID 0x03C +#define DWC_GSNPSID 0x040 +#define DWC_GHWCFG1 0x044 +#define DWC_GHWCFG2 0x048 +#define DWC_GHWCFG3 0x04c +#define DWC_GHWCFG4 0x050 +#define DWC_HPTXFSIZ 0x100 +#define DWC_DPTX_FSIZ_DIPTXF(x) (0x104 + x * 4) /* 15 <= x > 1 */ + +#define DWC_GLBINTRMASK 0x0001 +#define DWC_DMAENABLE 0x0020 +#define DWC_NPTXEMPTYLVL_EMPTY 0x0080 +#define DWC_NPTXEMPTYLVL_HALFEMPTY 0x0000 +#define DWC_PTXEMPTYLVL_EMPTY 0x0100 +#define DWC_PTXEMPTYLVL_HALFEMPTY 0x0000 + +#define DWC_SLAVE_ONLY_ARCH 0 +#define DWC_EXT_DMA_ARCH 1 +#define DWC_INT_DMA_ARCH 2 + +#define DWC_MODE_HNP_SRP_CAPABLE 0 +#define DWC_MODE_SRP_ONLY_CAPABLE 1 +#define DWC_MODE_NO_HNP_SRP_CAPABLE 2 +#define DWC_MODE_SRP_CAPABLE_DEVICE 3 +#define DWC_MODE_NO_SRP_CAPABLE_DEVICE 4 +#define DWC_MODE_SRP_CAPABLE_HOST 5 +#define DWC_MODE_NO_SRP_CAPABLE_HOST 6 + +/* + * These Macros represents the bit fields of the Core OTG Controland Status + * Register (GOTGCTL). Set the bits using the bit fields then write the u32 + * value to the register. + */ +#define DWC_GCTL_BSESSION_VALID (1 << 19) +#define DWC_GCTL_CSESSION_VALID (1 << 18) +#define DWC_GCTL_DEBOUNCE (1 << 17) +#define DWC_GCTL_CONN_ID_STATUS (1 << 16) +#define DWC_GCTL_DEV_HNP_ENA (1 << 11) +#define DWC_GCTL_HOST_HNP_ENA (1 << 10) +#define DWC_GCTL_HNP_REQ (1 << 9) +#define DWC_GCTL_HOST_NEG_SUCCES (1 << 8) +#define DWC_GCTL_SES_REQ (1 << 1) +#define DWC_GCTL_SES_REQ_SUCCESS (1 << 0) + +#define DWC_GCTL_BSESSION_VALID_RD(reg) (((reg) & (0x001 << 19)) >> 19) +#define DWC_GCTL_CSESSION_VALID_RD(reg) (((reg) & (0x001 << 18)) >> 18) +#define DWC_GCTL_DEBOUNCE_RD(reg) (((reg) & (0x001 << 17)) >> 17) +#define DWC_GCTL_CONN_ID_STATUS_RD(reg) (((reg) & (0x001 << 16)) >> 16) +#define DWC_GCTL_DEV_HNP_ENA_RD(reg) (((reg) & (0x001 << 11)) >> 11) +#define DWC_GCTL_HOST_HNP_ENA_RD(reg) (((reg) & (0x001 << 10)) >> 10) +#define DWC_GCTL_HNP_REQ_RD(reg) (((reg) & (0x001 << 9)) >> 9) +#define DWC_GCTL_HOST_NEG_SUCCES_RD(reg) (((reg) & (0x001 << 8)) >> 8) +#define DWC_GCTL_SES_REQ_RD(reg) (((reg) & (0x001 << 1)) >> 1) +#define DWC_GCTL_SES_REQ_SUCCESS_RD(reg) (((reg) & (0x001 << 0)) >> 0) + +#define DWC_GCTL_BSESSION_VALID_RW(reg, x) \ + (((reg) & (~((u32)0x01 << 19))) | ((x) << 19)) +#define DWC_GCTL_CSESSION_VALID_RW(reg, x) \ + (((reg) & (~((u32)0x01 << 18))) | ((x) << 18)) +#define DWC_GCTL_DEBOUNCE_RW(reg, x) \ + (((reg) & (~((u32)0x01 << 17))) | ((x) << 17)) +#define DWC_GCTL_CONN_ID_STATUS_RW(reg, x) \ + (((reg) & (~((u32)0x01 << 16))) | ((x) << 16)) +#define DWC_GCTL_DEV_HNP_ENA_RW (reg, x) \ + (((reg) & (~((u32)0x01 << 11))) | ((x) << 11)) +#define DWC_GCTL_HOST_HNP_ENA_RW(reg, x) \ + (((reg) & (~((u32)0x01 << 10))) | ((x) << 10)) +#define DWC_GCTL_HNP_REQ_RW(reg, x) \ + (((reg) & (~((u32)0x01 << 9))) | ((x) << 9)) +#define DWC_GCTL_HOST_NEG_SUCCES_RW(reg, x) \ + (((reg) & (~((u32)0x01 << 8))) | ((x) << 8)) +#define DWC_GCTL_SES_REQ_RW(reg, x) \ + (((reg) & (~((u32)0x01 << 1))) | ((x) << 1)) +#define DWC_GCTL_SES_REQ_SUCCESS_RW(reg, x) \ + (((reg) & (~((u32)0x01 << 0))) | ((x) << 0)) +/* + * These Macros represents the bit fields of the Core OTG Interrupt Register + * (GOTGINT). Set/clear the bits using the bit fields then write the u32 + * value to the register. + */ +#define DWC_GINT_DEBDONE (1 << 19) +#define DWC_GINT_DEVTOUT (1 << 18) +#define DWC_GINT_HST_NEGDET (1 << 17) +#define DWC_GINT_HST_NEGSUC (1 << 9) +#define DWC_GINT_SES_REQSUC (1 << 8) +#define DWC_GINT_SES_ENDDET (1 << 2) + +/* + * These Macros represents the bit fields of the Core AHB Configuration Register + * (GAHBCFG). Set/clear the bits using the bit fields then write the u32 value + * to the register. + */ +#define DWC_AHBCFG_FIFO_EMPTY (1 << 8) +#define DWC_AHBCFG_NPFIFO_EMPTY (1 << 7) +#define DWC_AHBCFG_DMA_ENA (1 << 5) +#define DWC_AHBCFG_BURST_LEN(x) (x << 1) +#define DWC_AHBCFG_GLBL_INT_MASK (1 << 0) + +#define DWC_GAHBCFG_TXFEMPTYLVL_EMPTY 1 +#define DWC_GAHBCFG_TXFEMPTYLVL_HALFEMPTY 0 +#define DWC_GAHBCFG_DMAENABLE 1 +#define DWC_GAHBCFG_INT_DMA_BURST_SINGLE 0 +#define DWC_GAHBCFG_INT_DMA_BURST_INCR 1 +#define DWC_GAHBCFG_INT_DMA_BURST_INCR4 3 +#define DWC_GAHBCFG_INT_DMA_BURST_INCR8 5 +#define DWC_GAHBCFG_INT_DMA_BURST_INCR16 7 + +/* + + * (GUSBCFG). Set the bits using the bit fields then write the u32 value to the + * register. + */ +#define DWC_USBCFG_CORR_PKT (1 << 31) +#define DWC_USBCFG_FRC_DEV_MODE (1 << 30) +#define DWC_USBCFG_FRC_HST_MODE (1 << 29) +#define DWC_USBCFG_TERM_SEL_DL_PULSE (1 << 22) +#define DWC_USBCFG_ULPI_INTVBUS_INDICATOR (1 << 21) +#define DWC_USBCFG_ULPI_EXT_VBUS_DRV (1 << 20) +#define DWC_USBCFG_ULPI_CLK_SUS_M (1 << 19) +#define DWC_USBCFG_ULPI_AUTO_RES (1 << 18) +#define DWC_USBCFG_ULPI_FSLS (1 << 17) +#define DWC_USBCFG_OTGUTMIFSSEL (1 << 16) +#define DWC_USBCFG_PHYLPWRCLKSEL (1 << 15) +#define DWC_USBCFG_NPTXFRWNDEN (1 << 14) +#define DWC_USBCFG_TRN_TIME(x) (x << 10) +#define DWC_USBCFG_HNP_CAP (1 << 9) +#define DWC_USBCFG_SRP_CAP (1 << 8) +#define DWC_USBCFG_DDRSEL (1 << 7) +#define DWC_USBCFG_USB_2_11 (1 << 6) +#define DWC_USBCFG_FSINTF (1 << 5) +#define DWC_USBCFG_ULPI_UTMI_SEL (1 << 4) +#define DWC_USBCFG_PHYIF (1 << 3) +#define DWC_USBCFG_TOUT_CAL(x) (x << 0) + +/* + * These Macros represents the bit fields of the Core Reset Register (GRSTCTL). + * Set/clear the bits using the bit fields then write the u32 value to the + * register. + */ +#define DWC_RSTCTL_AHB_IDLE (1 << 31) +#define DWC_RSTCTL_DMA_REQ (1 << 30) +#define DWC_RSTCTL_TX_FIFO_NUM(reg, x) \ + (((reg) & (~((u32)0x1f << 6))) | ((x) << 6)) +#define DWC_RSTCTL_TX_FIFO_FLUSH (1 << 5) +#define DWC_RSTCTL_RX_FIFO_FLUSH (1 << 4) +#define DWC_RSTCTL_TKN_QUE_FLUSH (1 << 3) +#define DWC_RSTCTL_HSTFRM_CNTR_RST (1 << 2) +#define DWC_RSTCTL_HCLK_SFT_RST (1 << 1) +#define DWC_RSTCTL_SFT_RST (1 << 1) +#define DWC_GRSTCTL_TXFNUM_ALL 0x10 + +/* + * These Macros represents the bit fields of the Core Interrupt Mask Register + * (GINTMSK). Set/clear the bits using the bit fields then write the u32 value + * to the register. + */ +#define DWC_INTMSK_WKP (1 << 31) +#define DWC_INTMSK_NEW_SES_DET (1 << 30) +#define DWC_INTMSK_SES_DISCON_DET (1 << 29) +#define DWC_INTMSK_CON_ID_STS_CHG (1 << 28) +#define DWC_INTMSK_P_TXFIFO_EMPTY (1 << 26) +#define DWC_INTMSK_HST_CHAN (1 << 25) +#define DWC_INTMSK_HST_PORT (1 << 24) +#define DWC_INTMSK_DATA_FETCH_SUS (1 << 23) +#define DWC_INTMSK_INCMP_PTX (1 << 22) +#define DWC_INTMSK_INCMP_OUT_PTX (1 << 21) +#define DWC_INTMSK_INCMP_IN_ATX (1 << 20) +#define DWC_INTMSK_OUT_ENDP (1 << 19) +#define DWC_INTMSK_IN_ENDP (1 << 18) +#define DWC_INTMSK_ENDP_MIS_MTCH (1 << 17) +#define DWC_INTMSK_END_OF_PFRM (1 << 15) +#define DWC_INTMSK_ISYNC_OUTPKT_DRP (1 << 14) +#define DWC_INTMSK_ENUM_DONE (1 << 13) +#define DWC_INTMSK_USB_RST (1 << 12) +#define DWC_INTMSK_USB_SUSP (1 << 11) +#define DWC_INTMSK_EARLY_SUSP (1 << 10) +#define DWC_INTMSK_I2C_INTR (1 << 9) +#define DWC_INTMSK_GLBL_OUT_NAK (1 << 7) +#define DWC_INTMSK_GLBL_IN_NAK (1 << 6) +#define DWC_INTMSK_NP_TXFIFO_EMPT (1 << 5) +#define DWC_INTMSK_RXFIFO_NOT_EMPT (1 << 4) +#define DWC_INTMSK_STRT_OF_FRM (1 << 3) +#define DWC_INTMSK_OTG (1 << 2) +#define DWC_INTMSK_MODE_MISMTC (1 << 1) +/* + * These Macros represents the bit fields of the Core Interrupt Register + * (GINTSTS). Set/clear the bits using the bit fields then write the u32 value + * to the register. + */ +#define DWC_INTSTS_WKP (1 << 31) +#define DWC_INTSTS_NEW_SES_DET (1 << 30) +#define DWC_INTSTS_SES_DISCON_DET (1 << 29) +#define DWC_INTSTS_CON_ID_STS_CHG (1 << 28) +#define DWC_INTSTS_P_TXFIFO_EMPTY (1 << 26) +#define DWC_INTSTS_HST_CHAN (1 << 25) +#define DWC_INTSTS_HST_PORT (1 << 24) +#define DWC_INTSTS_DATA_FETCH_SUS (1 << 23) +#define DWC_INTSTS_INCMP_PTX (1 << 22) +#define DWC_INTSTS_INCMP_OUT_PTX (1 << 21) +#define DWC_INTSTS_INCMP_IN_ATX (1 << 20) +#define DWC_INTSTS_OUT_ENDP (1 << 19) +#define DWC_INTSTS_IN_ENDP (1 << 18) +#define DWC_INTSTS_ENDP_MIS_MTCH (1 << 17) +#define DWC_INTSTS_END_OF_PFRM (1 << 15) +#define DWC_INTSTS_ISYNC_OUTPKT_DRP (1 << 14) +#define DWC_INTSTS_ENUM_DONE (1 << 13) +#define DWC_INTSTS_USB_RST (1 << 12) +#define DWC_INTSTS_USB_SUSP (1 << 11) +#define DWC_INTSTS_EARLY_SUSP (1 << 10) +#define DWC_INTSTS_I2C_INTR (1 << 9) +#define DWC_INTSTS_GLBL_OUT_NAK (1 << 7) +#define DWC_INTSTS_GLBL_IN_NAK (1 << 6) +#define DWC_INTSTS_NP_TXFIFO_EMPT (1 << 5) +#define DWC_INTSTS_RXFIFO_NOT_EMPT (1 << 4) +#define DWC_INTSTS_STRT_OF_FRM (1 << 3) +#define DWC_INTSTS_OTG (1 << 2) +#define DWC_INTSTS_MODE_MISMTC (1 << 1) +#define DWC_INTSTS_CURR_MODE (1 << 0) +#define DWC_SOF_INTR_MASK 0x0008 +#define DWC_HOST_MODE 1 + +/* + * These Macros represents the bit fields in the Device Receive Status Read and + * Pop Registers (GRXSTSR, GRXSTSP) Read the register into the u32 + * element then read out the bits using the bit elements. + */ +#define DWC_DM_RXSTS_PKT_STS (0x01f << 17) +#define DWC_DM_RXSTS_PKT_DPID (0x003 << 15) +#define DWC_DM_RXSTS_BYTE_CNT (0x7ff << 4) +#define DWC_DM_RXSTS_CHAN_NUM (0x00f << 0) + +#define DWC_DM_RXSTS_PKT_STS_RD(reg) (((reg) & (0x00f << 17)) >> 17) +#define DWC_DM_RXSTS_PKT_DPID_RD(reg) (((reg) & (0x003 << 15)) >> 15) +#define DWC_DM_RXSTS_BYTE_CNT_RD(reg) (((reg) & (0x7ff << 04)) >> 04) +#define DWC_DM_RXSTS_CHAN_NUM_RD(reg) ((reg) & 0x00f) + +#define DWC_STS_DATA_UPDT 0x2 /* OUT Data Packet */ +#define DWC_STS_XFER_COMP 0x3 /* OUT Data Transfer Complete */ +#define DWC_DSTS_GOUT_NAK 0x1 /* Global OUT NAK */ +#define DWC_DSTS_SETUP_COMP 0x4 /* Setup Phase Complete */ +#define DWC_DSTS_SETUP_UPDT 0x6 /* SETUP Packet */ + +/* + * These Macros represents the bit fields in the Host Receive Status Read and + * Pop Registers (GRXSTSR, GRXSTSP) Read the register into the u32 + * element then read out the bits using the bit elements. + */ +#define DWC_HM_RXSTS_FRM_NUM (0x00f << 21) +#define DWC_HM_RXSTS_PKT_STS (0x01f << 17) +#define DWC_HM_RXSTS_PKT_DPID (0x003 << 15) +#define DWC_HM_RXSTS_BYTE_CNT (0x7ff << 4) +#define DWC_HM_RXSTS_CHAN_NUM (0x00f << 0) + +#define DWC_HM_RXSTS_PKT_STS_RD(reg) (((reg) & (0x00f << 17)) >> 17) +#define DWC_HM_RXSTS_PKT_DPID_RD(reg) (((reg) & (0x003 << 15)) >> 15) +#define DWC_HM_RXSTS_BYTE_CNT_RD(reg) (((reg) & (0x7ff << 04)) >> 04) +#define DWC_HM_RXSTS_CHAN_NUM_RD(reg) ((reg) & 0x00f) + +#define DWC_GRXSTS_PKTSTS_IN 0x2 +#define DWC_GRXSTS_PKTSTS_IN_XFER_COMP 0x3 +#define DWC_GRXSTS_PKTSTS_DATA_TOGGLE_ERR 0x5 +#define DWC_GRXSTS_PKTSTS_CH_HALTED 0x7 + +/* + * These Macros represents the bit fields in the FIFO Size Registers (HPTXFSIZ, + * GNPTXFSIZ, DPTXFSIZn). Read the register into the u32 element then + * read out the bits using the bit elements. + */ +#define DWC_RX_FIFO_DEPTH_RD(reg) (((reg) & ((u32)0xffff << 16)) >> 16) +#define DWC_RX_FIFO_DEPTH_WR(reg, x) \ + (((reg) & (~((u32)0xffff << 16))) | ((x) << 16)) +#define DWC_RX_FIFO_START_ADDR_RD(reg) ((reg) & 0xffff) +#define DWC_RX_FIFO_START_ADDR_WR(reg, x) \ + (((reg) & (~((u32)0xffff))) | (x)) + +/* + * These Macros represents the bit fields in the Non-Periodic Tx FIFO/Queue + * Status Register (GNPTXSTS). Read the register into the u32 element then read + * out the bits using the bit elements. + */ +#define DWC_GNPTXSTS_NPTXQTOP_CHNEP_RD(x) (((x) & (0x3f << 26)) >> 26) +#define DWC_GNPTXSTS_NPTXQTOP_TKN_RD(x) (((x) & (0x03 << 24)) >> 24) +#define DWC_GNPTXSTS_NPTXQSPCAVAIL_RD(x) (((x) & (0xff << 16)) >> 16) +#define DWC_GNPTXSTS_NPTXFSPCAVAIL_RD(x) (0xffff & (x)) + +/* + * These Macros represents the bit fields in the Transmit FIFO Status Register + * (DTXFSTS). Read the register into the u32 element then read out the bits + * using the bit elements. + */ +#define DWC_DTXFSTS_TXFSSPC_AVAI_RD(x) ((x) & 0xffff) + +/* + * These Macros represents the bit fields in the I2C Control Register (I2CCTL). + * Read the register into the u32 element then read out the bits using the bit + * elements. + */ +#define DWC_I2CCTL_BSYDNE (1 << 31) +#define DWC_I2CCTL_RW (1 << 30) +#define DWC_I2CCTL_I2CDEVADDR(x) ((x) << 27) +#define DWC_I2CCTL_I2CSUSCTL (1 << 25) +#define DWC_I2CCTL_ACK (1 << 24) +#define DWC_I2CCTL_I2CEN (1 << 23) +#define DWC_I2CCTL_ADDR (1 << 22) +#define DWC_I2CCTL_REGADDR(x) ((x) << 14) +#define DWC_I2CCTL_RWDATA(x) ((x) << 6) + +/* + * These Macros represents the bit fields in the User HW Config1 Register. Read + * the register into the u32 element then read out the bits using the bit + * elements. + */ +#define DWC_HWCFG1_EPDIR15(x) ((x) << 30) +#define DWC_HWCFG1_EPDIR14(x) ((x) << 28) +#define DWC_HWCFG1_EPDIR13(x) ((x) << 26) +#define DWC_HWCFG1_EPDIR12(x) ((x) << 24) +#define DWC_HWCFG1_EPDIR11(x) ((x) << 22) +#define DWC_HWCFG1_EPDIR10(x) ((x) << 20) +#define DWC_HWCFG1_EPDIR9(x) ((x) << 18) +#define DWC_HWCFG1_EPDIR8(x) ((x) << 16) +#define DWC_HWCFG1_EPDIR7(x) ((x) << 14) +#define DWC_HWCFG1_EPDIR6(x) ((x) << 13) +#define DWC_HWCFG1_EPDIR5(x) ((x) << 10) +#define DWC_HWCFG1_EPDIR4(x) ((x) << 8) +#define DWC_HWCFG1_EPDIR3(x) ((x) << 6) +#define DWC_HWCFG1_EPDIR2(x) ((x) << 4) +#define DWC_HWCFG1_EPDIR1(x) ((x) << 2) +#define DWC_HWCFG1_EPDIR0(x) ((x) << 0) + +/* + * These Macros represents the bit fields in the User HW Config2 Register. Read + * the register into the u32 element then read out the bits using the bit + * elements. + */ +#define DWC_HWCFG2_DEV_TKN_Q_DEPTH_RD(x) (((x) & (0x1F << 26)) >> 26) +#define DWC_HWCFG2_HOST_PERIO_Q_DEPTH_RD(x) (((x) & (0x3 << 24)) >> 24) +#define DWC_HWCFG2_NP_TX_Q_DEPTH_RD(x) (((x) & (0x3 << 22)) >> 22) +#define DWC_HWCFG2_RX_STS_Q_DEPTH_RD(x) (((x) & (0x3 << 20)) >> 20) +#define DWC_HWCFG2_DYN_FIFO_RD(x) (((x) & (0x1 << 19)) >> 19) +#define DWC_HWCFG2_PERIO_EP_SUPP_RD(x) (((x) & (0x1 << 18)) >> 18) +#define DWC_HWCFG2_NO_HST_CHAN_RD(x) (((x) & (0xf << 14)) >> 14) +#define DWC_HWCFG2_NO_DEV_EP_RD(x) (((x) & (0xf << 10)) >> 10) +#define DWC_HWCFG2_FS_PHY_TYPE_RD(x) (((x) & (0x3 << 8)) >> 8) +#define DWC_HWCFG2_HS_PHY_TYPE_RD(x) (((x) & (0x3 << 6)) >> 6) +#define DWC_HWCFG2_P_2_P_RD(x) (((x) & (0x1 << 5)) >> 5) +#define DWC_HWCFG2_ARCH_RD(x) (((x) & (0x3 << 3)) >> 3) +#define DWC_HWCFG2_OP_MODE_RD(x) ((x) & 0x7) + +#define DWC_HWCFG2_HS_PHY_TYPE_NOT_SUPPORTED 0 +#define DWC_HWCFG2_HS_PHY_TYPE_UTMI 1 +#define DWC_HWCFG2_HS_PHY_TYPE_ULPI 2 +#define DWC_HWCFG2_HS_PHY_TYPE_UTMI_ULPI 3 +#define DWC_HWCFG2_OP_MODE_HNP_SRP_CAPABLE_OTG 0 +#define DWC_HWCFG2_OP_MODE_SRP_ONLY_CAPABLE_OTG 1 +#define DWC_HWCFG2_OP_MODE_NO_HNP_SRP_CAPABLE_OTG 2 +#define DWC_HWCFG2_OP_MODE_SRP_CAPABLE_DEVICE 3 +#define DWC_HWCFG2_OP_MODE_NO_SRP_CAPABLE_DEVICE 4 +#define DWC_HWCFG2_OP_MODE_SRP_CAPABLE_HOST 5 +#define DWC_HWCFG2_OP_MODE_NO_SRP_CAPABLE_HOST 6 + +/* + * These Macros represents the bit fields in the User HW Config3 Register. ead + * the register into the u32 element then read out the bits using the bit + * elements. + */ +#define DWC_HWCFG3_DFIFO_DEPTH_RD(x) (((x) & (0xffff << 16)) >> 16) +#define DWC_HWCFG3_AHB_PHY_CLK_SYNC_RD(x) (((x) & (0x1 << 12)) >> 12) +#define DWC_HWCFG3_SYNC_RST_TYPE_RD(x) (((x) & (0x1 << 11)) >> 11) +#define DWC_HWCFG3_OPT_FEATURES_RD(x) (((x) & (0x1 << 10)) >> 10) +#define DWC_HWCFG3_VEND_CTRL_IF_RD(x) (((x) & (0x1 << 9)) >> 9) +#define DWC_HWCFG3_I2C_RD(x) (((x) & (0x1 << 8)) >> 8) +#define DWC_HWCFG3_OTG_FUNC_RD(x) (((x) & (0x1 << 07)) >> 07) +#define DWC_HWCFG3_PKTSIZE_CTR_WIDTH_RD(x) (((x) & (0x7 << 04)) >> 04) +#define DWC_HWCFG3_XFERSIZE_CTR_WIDTH_RD(x) ((x) & 0xf) + +/* + * These Macros represents the bit fields in the User HW Config4 Register. Read + * the register into the u32 element then read out the bits using the bit + * elements. + */ +#define DWC_HWCFG4_NUM_IN_EPS_RD(x) (((x) & (0xF << 26)) >> 26) +#define DWC_HWCFG4_DED_FIFO_ENA_RD(x) (((x) & (0x1 << 25)) >> 25) +#define DWC_HWCFG4_SES_END_FILT_EN_RD(x) (((x) & (0x1 << 24)) >> 24) +#define DWC_HWCFG4_BVALID_FILT_EN_RD(x) (((x) & (0x1 << 23)) >> 23) +#define DWC_HWCFG4_AVALID_FILT_EN_RD(x) (((x) & (0x1 << 22)) >> 22) +#define DWC_HWCFG4_VBUS_VALID_FILT_EN_RD(x) (((x) & (0x1 << 21)) >> 21) +#define DWC_HWCFG4_IDDIG_FILT_EN_RD(x) (((x) & (0x1 << 20)) >> 20) +#define DWC_HWCFG4_NUM_DEV_MODE_CTRL_EP_RD(x) (((x) & (0xF << 16)) >> 16) +#define DWC_HWCFG4_UTMI_PHY_DATA_WIDTH_RD(x) (((x) & (0x3 << 14)) >> 14) +#define DWC_HWCFG4_MIN_AHB_FREQ_RD(x) (((x) & (0x1 << 05)) >> 05) +#define DWC_HWCFG4_POWER_OPT_RD(x) (((x) & (0x1 << 04)) >> 04) +#define DWC_HWCFG4_NUM_DEV_PERIO_IN_EP_RD(x) ((x) & 0xf) + +/* + * Device Global Registers. Offsets 800h-BFFh + * + * The following structures define the size and relative field offsets for the + * Device Mode Registers. + * + * These registers are visible only in Device mode and must not be accessed in + * Host mode, as the results are unknown. + */ +#define DWC_DCFG 0x000 +#define DWC_DCTL 0x004 +#define DWC_DSTS 0x008 +#define DWC_DIEPMSK 0x010 +#define DWC_DOEPMSK 0x014 +#define DWC_DAINT 0x018 +#define DWC_DAINTMSK 0x01C +#define DWC_DTKNQR1 0x020 +#define DWC_DTKNQR2 0x024 +#define DWC_DVBUSDIS 0x028 +#define DWC_DVBUSPULSE 0x02C +#define DWC_DTKNQR3_DTHRCTL 0x030 +#define DWC_DTKNQR4FIFOEMPTYMSK 0x034 + +/* + * These Macros represents the bit fields in the Device Configuration + * Register. Read the register into the u32 member then + * set/clear the bits using the bit elements. Write the + * u32 member to the dcfg register. +*/ +#define DWC_DCFG_IN_EP_MISMATCH_CNT_RD(x) (((x) & (0x1f << 18)) >> 18) +#define DWC_DCFG_P_FRM_INTRVL_RD(x) (((x) & (0x03 << 11)) >> 11) +#define DWC_DCFG_DEV_ADDR_RD(x) (((x) & (0x3f << 04)) >> 04) +#define DWC_DCFG_NGL_STS_OUT_RD(x) (((x) & (0x1 << 2)) >> 2) +#define DWC_DCFG_DEV_SPEED_RD(x) ((x) & 0x3) + +#define DWC_DCFG_IN_EP_MISMATCH_CNT_WR(reg, x) \ + (((reg) & (~((u32)0x1f << 18))) | ((x) << 18)) +#define DWC_DCFG_P_FRM_INTRVL_WR(reg, x) \ + (((reg) & (~((u32)0x03 << 11))) | ((x) << 11)) +#define DWC_DCFG_DEV_ADDR_WR(reg, x) \ + (((reg) & (~((u32)0x3f << 04))) | ((x) << 04)) +#define DWC_DCFG_NGL_STS_OUT_WR(reg, x) \ + (((reg) & (~((u32)0x1 << 2))) | ((x) << 2)) +#define DWC_DCFG_DEV_SPEED_WR(reg, x) \ + (((reg) & (~(u32)0x3)) | (x)) + +#define DWC_DCFG_FRAME_INTERVAL_80 0 +#define DWC_DCFG_FRAME_INTERVAL_85 1 +#define DWC_DCFG_FRAME_INTERVAL_90 2 +#define DWC_DCFG_FRAME_INTERVAL_95 3 + +/* + * These Macros represents the bit fields in the Device Control Register. Read + * the register into the u32 member then set/clear the bits using the bit + * elements. + */ +#define DWC_DCTL_PWR_ON_PROG_DONE_RD(x) (((x) & (1 << 11)) >> 11) + +#define DWC_DCTL_PWR_ON_PROG_DONE_WR(reg, x) \ + (((reg) & (~((u32)0x01 << 11))) | ((x) << 11)) +#define DWC_DCTL_CLR_GLBL_OUT_NAK_WR(reg, x) \ + (((reg) & (~((u32)0x01 << 10))) | ((x) << 10)) +#define DWC_DCTL_SET_GLBL_OUT_NAL(reg, x) \ + (((reg) & (~((u32)0x01 << 9))) | ((x) << 9)) +#define DWC_DCTL_CLR_CLBL_NP_IN_NAK(reg, x) \ + (((reg) & (~((u32)0x01 << 8))) | ((x) << 8)) +#define DWC_DCTL_SET_GLBL_NP_IN_NAK(reg, x) \ + (((reg) & (~((u32)0x01 << 07))) | ((x) << 07)) +#define DWC_DCTL_TST_CTL(reg, x) \ + (((reg) & (~((u32)0x07 << 04))) | ((x) << 04)) +#define DWC_DCTL_GLBL_OUT_NAK_STS(reg, x) \ + (((reg) & (~((u32)0x01 << 03))) | ((x) << 03)) +#define DWC_DCTL_GLBL_NP_IN_NAK(reg, x) \ + (((reg) & (~((u32)0x01 << 02))) | ((x) << 02)) +#define DWC_DCTL_SFT_DISCONNECT(reg, x) \ + (((reg) & (~((u32)0x01 << 01))) | ((x) << 01)) +#define DEC_DCTL_REMOTE_WAKEUP_SIG(reg, x) \ + (((reg) & (~((u32)0x01 << 00))) | ((x) << 00)) + +/* + * These Macros represents the bit fields in the Dev Status Register. Read the + * register into the u32 member then set/clear the bits using the bit elements. + */ +#define DWC_DSTS_SOFFN_RD(x) (((x) & (0x3fff << 8)) >> 8) +#define DWC_DSTS_ERRTICERR_RD(x) (((x) & (0x0001 << 3)) >> 3) +#define DWC_DSTS_ENUM_SPEED_RD(x) (((x) & (0x0003 << 1)) >> 1) +#define DWC_DSTS_SUSP_STS_RD(x) ((x) & 1) + +#define DWC_DSTS_ENUMSPD_HS_PHY_30MHZ_OR_60MHZ 0 +#define DWC_DSTS_ENUMSPD_FS_PHY_30MHZ_OR_60MHZ 1 +#define DWC_DSTS_ENUMSPD_LS_PHY_6MHZ 2 +#define DWC_DSTS_ENUMSPD_FS_PHY_48MHZ 3 + +/* + * These Macros represents the bit fields in the Device IN EP Interrupt Register + * and the Device IN EP Common Mask Register. + * + * Read the register into the u32 member then set/clear the bits using the bit + * elements. + */ +#define DWC_DIEPINT_TXFIFO_UNDERN_RD(x) (((x) & (0x1 << 8)) >> 8) +#define DWC_DIEPINT_TXFIFO_EMPTY_RD(x) (((x) & (0x1 << 7)) >> 7) +#define DWC_DIEPINT_IN_EP_NAK_RD(x) (((x) & (0x1 << 6)) >> 6) +#define DWC_DIEPINT_IN_TKN_EP_MISS_RD(x) (((x) & (0x1 << 5)) >> 5) +#define DWC_DIEPINT_IN_TKN_TX_EMPTY_RD(x) (((x) & (0x1 << 4)) >> 4) +#define DWC_DIEPINT_TOUT_COND_RD(x) (((x) & (0x1 << 3)) >> 3) +#define DWC_DIEPINT_AHB_ERROR_RD(x) (((x) & (0x1 << 2)) >> 2) +#define DWC_DIEPINT_EP_DISA_RD(x) (((x) & (0x1 << 1)) >> 1) +#define DWC_DIEPINT_TX_CMPL_RD(x) ((x) & 0x1) + +#define DWC_DIEPINT_TXFIFO_UNDERN_RW(reg, x) \ + (((reg) & (~((u32)0x01 << 8))) | ((x) << 8)) +#define DWC_DIEPINT_TXFIFO_EMPTY_RW(reg, x) \ + (((reg) & (~((u32)0x01 << 7))) | ((x) << 7)) +#define DWC_DIEPINT_IN_EP_NAK_RW(reg, x) \ + (((reg) & (~((u32)0x01 << 6))) | ((x) << 6)) +#define DWC_DIEPINT_IN_TKN_EP_MISS_RW(reg, x) \ + (((reg) & (~((u32)0x01 << 5))) | ((x) << 5)) +#define DWC_DIEPINT_IN_TKN_TX_EMPTY_RW(reg, x) \ + (((reg) & (~((u32)0x01 << 4))) | ((x) << 4)) +#define DWC_DIEPINT_TOUT_COND_RW(reg, x) \ + (((reg) & (~((u32)0x01 << 3))) | ((x) << 3)) +#define DWC_DIEPINT_AHB_ERROR_RW(reg, x) \ + (((reg) & (~((u32)0x01 << 2))) | ((x) << 2)) +#define DWC_DIEPINT_EP_DISA_RW(reg, x) \ + (((reg) & (~((u32)0x01 << 1))) | ((x) << 1)) +#define DWC_DIEPINT_TX_CMPL_RW(reg, x) \ + (((reg) & (~((u32)0x01 << 0))) | ((x) << 0)) + +#define DWC_DIEPMSK_TXFIFO_UNDERN_RW(reg, x) \ + (((reg) & (~((u32)0x01 << 8))) | ((x) << 8)) +#define DWC_DIEPMSK_TXFIFO_EMPTY_RW(reg, x) \ + (((reg) & (~((u32)0x01 << 7))) | ((x) << 7)) +#define DWC_DIEPMSK_IN_EP_NAK_RW(reg, x) \ + (((reg) & (~((u32)0x01 << 6))) | ((x) << 6)) +#define DWC_DIEPMSK_IN_TKN_EP_MISS_RW(reg, x) \ + (((reg) & (~((u32)0x01 << 5))) | ((x) << 5)) +#define DWC_DIEPMSK_IN_TKN_TX_EMPTY_RW(reg, x) \ + (((reg) & (~((u32)0x01 << 4))) | ((x) << 4)) +#define DWC_DIEPMSK_TOUT_COND_RW(reg, x) \ + (((reg) & (~((u32)0x01 << 3))) | ((x) << 3)) +#define DWC_DIEPMSK_AHB_ERROR_RW(reg, x) \ + (((reg) & (~((u32)0x01 << 2))) | ((x) << 2)) +#define DWC_DIEPMSK_EP_DISA_RW(reg, x) \ + (((reg) & (~((u32)0x01 << 1))) | ((x) << 1)) +#define DWC_DIEPMSK_TX_CMPL_RW(reg, x) \ + (((reg) & (~((u32)0x01 << 0))) | ((x) << 0)) + +/* + * These Macros represents the bit fields in the Device OUT EP Itr Register + * and Device OUT EP Common Interrupt Mask Register. + * + * Read the register into the u32 member then set/clear the bits using the bit + * elements. + */ +#define DWC_DOEPINT_OUTPKT_ERR_RD(x) (((x) & (0x1 << 8)) >> 8) +#define DWC_DOEPINT_B2B_PKTS_RD(x) (((x) & (0x1 << 6)) >> 6) +#define DWC_DOEPINT_OUT_TKN_RD(x) (((x) & (0x1 << 4)) >> 4) +#define DWC_DOEPINT_SETUP_DONE_RD(x) (((x) & (0x1 << 3)) >> 3) +#define DWC_DOEPINT_AHB_ERROR_RD(x) (((x) & (0x1 << 2)) >> 2) +#define DWC_DOEPINT_EP_DISA_RD(x) (((x) & (0x1 << 1)) >> 1) +#define DWC_DOEPINT_TX_COMPL_RD(x) (((x) & (0x1 << 0)) >> 0) + +#define DWC_DOEPMSK_OUTPKT_ERR_RW(reg, x) \ + (((reg) & (~((u32)0x01 << 8))) | ((x) << 8)) +#define DWC_DOEPMSK_B2B_PKTS_RW(reg, x) \ + (((reg) & (~((u32)0x01 << 6))) | ((x) << 6)) +#define DWC_DOEPMSK_OUT_TKN_RW(reg, x) \ + (((reg) & (~((u32)0x01 << 4))) | ((x) << 4)) +#define DWC_DOEPMSK_SETUP_DONE_RW(reg, x) \ + (((reg) & (~((u32)0x01 << 3))) | ((x) << 3)) +#define DWC_DOEPMSK_AHB_ERROR_RW(reg, x) \ + (((reg) & (~((u32)0x01 << 2))) | ((x) << 2)) +#define DWC_DOEPMSK_EP_DISA_RW(reg, x) \ + (((reg) & (~((u32)0x01 << 1))) | ((x) << 1)) +#define DWC_DOEPMSK_TX_COMPL_RW(reg, x) \ + (((reg) & (~((u32)0x01 << 0))) | ((x) << 0)) + +/* + * These Macros represents the bit fields in the Device All EP Intr and Mask + * Registers. Read the register into the u32 member then set/clear the bits + * using the bit elements. + */ +#define DWC_DAINT_OUT_EP_RD(reg, ep) \ + (((reg) & (1 << (ep + 16))) >> (ep + 16)) +#define DWC_DAINTMSK_OUT_EP_RW(reg, ep) \ + (((reg) & (~(u32)(1 << (ep + 16)))) | (1 << (ep + 16))) +#define DWC_DAINT_IN_EP_RD(reg, ep) (((reg) & (1 << ep)) >> ep) +#define DWC_DAINTMSK_IN_EP_RW(reg, ep) \ + (((reg) & (~(u32)(1 << ep))) | (1 << ep)) +#define DWC_DAINT_OUTEP15 (1 << 31) +#define DWC_DAINT_OUTEP14 (1 << 30) +#define DWC_DAINT_OUTEP13 (1 << 29) +#define DWC_DAINT_OUTEP12 (1 << 28) +#define DWC_DAINT_OUTEP11 (1 << 27) +#define DWC_DAINT_OUTEP10 (1 << 26) +#define DWC_DAINT_OUTEP09 (1 << 25) +#define DWC_DAINT_OUTEP08 (1 << 24) +#define DWC_DAINT_OUTEP07 (1 << 23) +#define DWC_DAINT_OUTEP06 (1 << 22) +#define DWC_DAINT_OUTEP05 (1 << 21) +#define DWC_DAINT_OUTEP04 (1 << 20) +#define DWC_DAINT_OUTEP03 (1 << 19) +#define DWC_DAINT_OUTEP02 (1 << 18) +#define DWC_DAINT_OUTEP01 (1 << 17) +#define DWC_DAINT_OUTEP00 (1 << 16) +#define DWC_DAINT_INEP15 (1 << 15) +#define DWC_DAINT_INEP14 (1 << 14) +#define DWC_DAINT_INEP13 (1 << 13) +#define DWC_DAINT_INEP12 (1 << 12) +#define DWC_DAINT_INEP11 (1 << 11) +#define DWC_DAINT_INEP10 (1 << 10) +#define DWC_DAINT_INEP09 (1 << 9) +#define DWC_DAINT_INEP08 (1 << 8) +#define DWC_DAINT_INEP07 (1 << 07) +#define DWC_DAINT_INEP06 (1 << 06) +#define DWC_DAINT_INEP05 (1 << 05) +#define DWC_DAINT_INEP04 (1 << 04) +#define DWC_DAINT_INEP03 (1 << 03) +#define DWC_DAINT_INEP02 (1 << 02) +#define DWC_DAINT_INEP01 (1 << 01) +#define DWC_DAINT_INEP00 (1 << 00) + +/* + * These Macros represents the bit fields in the Device IN Token Queue Read + * Registers. Read the register into the u32 member. READ-ONLY Register + */ +#define DWC_DTKNQR1_EP_TKN_NO_RD(x) (((x) & (0xffffff << 8)) >> 8) +#define DWC_DTKNQR1_WRAP_BIT_RD(x) (((x) & (1 << 7)) >> 7) +#define DWC_DTKNQR1_INT_TKN_Q_WR_PTR_RD(x) ((x) & 0x1f) + +/* + * These Macros represents Threshold control Register. Read and wr the register + * into the u32 member. READ-WRITABLE Register + */ +#define DWC_DTHCTRL_RX_ARB_PARK_EN_RD(x) (((x) & (0x001 << 27)) >> 27) +#define DWC_DTHCTRL_RX_THR_LEN_RD(x) (((x) & (0x1ff << 17)) >> 17) +#define DWC_DTHCTRL_RX_THR_EN_RD(x) (((x) & (0x001 << 16)) >> 16) +#define DWC_DTHCTRL_TX_THR_LEN_RD(x) (((x) & (0x1ff << 02)) >> 02) +#define DWC_DTHCTRL_ISO_THR_EN(x) (((x) & (0x001 << 01)) >> 01) +#define DWC_DTHCTRL_NON_ISO_THR_ENA_RD(x) (((x) & (0x001 << 00)) >> 00) + +#define DWC_DTHCTRL_RX_ARB_PARK_EN_RW(reg, x) \ + (((reg) & (~((u32)0x001 << 27))) | ((x) << 27)) +#define DWC_DTHCTRL_RX_THR_LEN_RW(reg, x) \ + (((reg) & (~((u32)0x1ff << 17))) | ((x) << 17)) +#define DWC_DTHCTRL_RX_THR_EN_RW(reg, x) \ + (((reg) & (~((u32)0x001 << 16))) | ((x) << 16)) +#define DWC_DTHCTRL_TX_THR_LEN_RW(reg, x) \ + (((reg) & (~((u32)0x1ff << 02))) | ((x) << 02)) +#define DWC_DTHCTRL_ISO_THR_EN_RW(reg, x) \ + (((reg) & (~((u32)0x001 << 01))) | ((x) << 01)) +#define DWC_DTHCTRL_NON_ISO_THR_ENA_RW(reg, x) \ + (((reg) & (~((u32)0x001 << 00))) | ((x) << 00)) + +/* + * Device Logical IN Endpoint-Specific Registers. Offsets 900h-AFCh + * + * There will be one set of endpoint registers per logical endpoint implemented. + * + * These registers are visible only in Device mode and must not be accessed in + * Host mode, as the results are unknown. + */ +#define DWC_DIEPCTL 0x00 +#define DWC_DIEPINT 0x08 +#define DWC_DIEPTSIZ 0x10 +#define DWC_DIEPDMA 0x14 +#define DWC_DTXFSTS 0x18 + +/* + * Device Logical OUT Endpoint-Specific Registers. Offsets: B00h-CFCh + * + * There will be one set of endpoint registers per logical endpoint implemented. + * + * These registers are visible only in Device mode and must not be accessed in + * Host mode, as the results are unknown. + */ +#define DWC_DOEPCTL 0x00 +#define DWC_DOEPFN 0x04 +#define DWC_DOEPINT 0x08 +#define DWC_DOEPTSIZ 0x10 +#define DWC_DOEPDMA 0x14 + +/* + * These Macros represents the bit fields in the Device EP Ctrl Register. Read + * the register into the u32 member then set/clear the bits using the bit + * elements. + */ +#define DWC_DEP0CTL_MPS_64 0 +#define DWC_DEP0CTL_MPS_32 1 +#define DWC_DEP0CTL_MPS_16 2 +#define DWC_DEP0CTL_MPS_8 3 + +#define DWC_DEPCTL_EPENA_RD(x) (((x) & (0x1 << 31)) >> 31) +#define DWC_DEPCTL_EPDIS_RD(x) (((x) & (0x1 << 30)) >> 30) +#define DWC_DEPCTL_SET_DATA1_PID_RD(x) (((x) & (0x1 << 29)) >> 29) +#define DWC_DEPCTL_SET_DATA0_PID_RD(x) (((x) & (0x1 << 28)) >> 28) +#define DWC_DEPCTL_SET_NAK_RD(x) (((x) & (0x1 << 27)) >> 27) +#define DWC_DEPCTL_CLR_NAK_RD(x) (((x) & (0x1 << 26)) >> 26) +#define DWC_DEPCTL_TX_FIFO_NUM_RD(x) (((x) & (0xf << 22)) >> 22) +#define DWC_DEPCTL_STALL_HNDSHK _RD(x) (((x) & (0x1 << 21)) >> 21) +#define DWC_DEPCTL_SNP_MODE_RD(x) (((x) & (0x1 << 20)) >> 20) +#define DWC_DEPCTL_EP_TYPE_RD(x) (((x) & (0x3 << 18)) >> 18) +#define DWC_DEPCTL_NKASTS_RD(x) (((x) & (0x1 << 17)) >> 17) +#define DWC_DEPCTL_DPID _RD(x) (((x) & (0x1 << 16)) >> 16) +#define DWC_DEPCTL_ACT_EP_RD(x) (((x) & (0x1 << 15)) >> 15) +#define DWC_DEPCTL_NXT_EP_RD(x) (((x) & (0xf << 11)) >> 11) +#define DWC_DEPCTL_MPS_RD(x) (((x) & (0x7ff << 00)) >> 00) + +#define DWC_DEPCTL_EPENA_RW(reg, x) \ + (((reg) & (~((u32)0x001 << 31))) | ((x) << 31)) +#define DWC_DEPCTL_EPDIS_RW(reg, x) \ + (((reg) & (~((u32)0x001 << 30))) | ((x) << 30)) +#define DWC_DEPCTL_SET_DATA1_PID_RW(reg, x) \ + (((reg) & (~((u32)0x001 << 29))) | ((x) << 29)) +#define DWC_DEPCTL_SET_DATA0_PID_RW(reg, x) \ + (((reg) & (~((u32)0x001 << 28))) | ((x) << 28)) +#define DWC_DEPCTL_SET_NAK_RW(reg, x) \ + (((reg) & (~((u32)0x001 << 27))) | ((x) << 27)) +#define DWC_DEPCTL_CLR_NAK_RW(reg, x) \ + (((reg) & (~((u32)0x001 << 26))) | ((x) << 26)) +#define DWC_DEPCTL_TX_FIFO_NUM_RW(reg, x) \ + (((reg) & (~((u32)0x00f << 22))) | ((x) << 22)) +#define DWC_DEPCTL_STALL_HNDSHK_RW(reg, x) \ + (((reg) & (~((u32)0x001 << 21))) | ((x) << 21)) +#define DWC_DEPCTL_SNP_MODE_RW(reg, x) \ + (((reg) & (~((u32)0x001 << 20))) | ((x) << 20)) +#define DWC_DEPCTL_EP_TYPE_RW(reg, x) \ + (((reg) & (~((u32)0x003 << 18))) | ((x) << 18)) +#define DWC_DEPCTL_NKASTS_RW(reg, x) \ + (((reg) & (~((u32)0x001 << 17))) | ((x) << 17)) +#define DWC_DEPCTL_DPID_RW(reg, x) \ + (((reg) & (~((u32)0x001 << 16))) | ((x) << 16)) +#define DWC_DEPCTL_ACT_EP_RW(reg, x) \ + (((reg) & (~((u32)0x001 << 15))) | ((x) << 15)) +#define DWC_DEPCTL_NXT_EP_RW(reg, x) \ + (((reg) & (~((u32)0x00f << 11))) | ((x) << 11)) +#define DWC_DEPCTL_MPS_RW(reg, x) \ + (((reg) & (~((u32)0x7ff << 00))) | ((x) << 00)) + +/* + * These Macros represents the bit fields in the Device EP Txfer Size Register. + * Read the register into the u32 member then set/clear the bits using the bit + * elements. + */ +#if defined(DWC_LIMITED_XFER_SIZE) +#define DWC_DEPTSIZ_MCOUNT_RD(x) (((x) & (0x003 << 29)) >> 29) +#define DWC_DEPTSIZ_PKT_CNT_RD(x) (((x) & (0x01f << 19)) >> 19) +#define DWC_DEPTSIZ_XFER_SIZ_RD(x) (((x) & (0x7ff << 00)) >> 00) +#define DWC_DEPTSIZ_MCOUNT_RW(reg, x) \ + (((reg) & (~((u32)0x003 << 29))) | ((x) << 29)) +#define DWC_DEPTSIZ_PKT_CNT_RW(reg, x) \ + (((reg) & (~((u32)0x01f << 19))) | ((x) << 19)) +#define DWC_DEPTSIZ_XFER_SIZ_RW(reg, x) \ + (((reg) & (~((u32)0x7ff << 00))) | ((x) << 00)) +#else +#define DWC_DEPTSIZ_MCOUNT_RD(x) \ + (((x) & (0x003 << 29)) >> 29) +#define DWC_DEPTSIZ_PKT_CNT_RD(x) \ + (((x) & (0x3ff << 19)) >> 19) +#define DWC_DEPTSIZ_XFER_SIZ_RD(x) \ + (((x) & (0x7ffff << 00)) >> 00) +#define DWC_DEPTSIZ_MCOUNT_RW(reg, x) \ + (((reg) & (~((u32)0x003 << 29))) | ((x) << 29)) +#define DWC_DEPTSIZ_PKT_CNT_RW(reg, x) \ + (((reg) & (~((u32)0x7ff << 19))) | ((x) << 19)) +#define DWC_DEPTSIZ_XFER_SIZ_RW(reg, x) \ + (((reg) & (~((u32)0x7ffff << 00))) | ((x) << 00)) +#endif + +/* + * These Macros represents the bit fields in the Device EP 0 Transfer Size + * Register. Read the register into the u32 member then set/clear the bits + * using the bit elements. + */ +#define DWC_DEPTSIZ0_SUPCNT_RD(x) (((x) & (0x003 << 29)) >> 29) +#define DWC_DEPTSIZ0_PKT_CNT_RD(x) (((x) & (0x001 << 19)) >> 19) +#define DWC_DEPTSIZ0_XFER_SIZ_RD(x) (((x) & (0x07f << 00)) >> 00) +#define DWC_DEPTSIZ0_SUPCNT_RW(reg, x) \ + (((reg) & (~((u32)0x003 << 29))) | ((x) << 29)) +#define DWC_DEPTSIZ0_PKT_CNT_RW(reg, x) \ + (((reg) & (~((u32)0x001 << 19))) | ((x) << 19)) +#define DWC_DEPTSIZ0_XFER_SIZ_RW(reg, x) \ + (((reg) & (~((u32)0x07f << 00))) | ((x) << 00)) + +#define MAX_PERIO_FIFOS 15 /* Max periodic FIFOs */ +#define MAX_TX_FIFOS 15 /* Max non-periodic FIFOs */ + +/* Maximum number of Endpoints/HostChannels */ +#define MAX_EPS_CHANNELS 12 /* This come from device tree or defconfig */ + +/* + * The device_if structure contains information needed to manage the DWC_otg + * controller acting in device mode. It represents the programming view of the + * device-specific aspects of the controller. + */ +struct device_if { + /* Device Global Registers starting at offset 800h */ + ulong dev_global_regs; +#define DWC_DEV_GLOBAL_REG_OFFSET 0x800 + + /* Device Logical IN Endpoint-Specific Registers 900h-AFCh */ + ulong in_ep_regs[MAX_EPS_CHANNELS]; +#define DWC_DEV_IN_EP_REG_OFFSET 0x900 +#define DWC_EP_REG_OFFSET 0x20 + + /* Device Logical OUT Endpoint-Specific Registers B00h-CFCh */ + ulong out_ep_regs[MAX_EPS_CHANNELS]; +#define DWC_DEV_OUT_EP_REG_OFFSET 0xB00 + + /* Device configuration information */ + /* Device Speed 0: Unknown, 1: LS, 2:FS, 3: HS */ + u8 speed; + /* Number # of Tx EP range: 0-15 exept ep0 */ + u8 num_in_eps; + /* Number # of Rx EP range: 0-15 exept ep 0 */ + u8 num_out_eps; + + /* Size of periodic FIFOs (Bytes) */ + u16 perio_tx_fifo_size[MAX_PERIO_FIFOS]; + + /* Size of Tx FIFOs (Bytes) */ + u16 tx_fifo_size[MAX_TX_FIFOS]; + + /* Thresholding enable flags and length varaiables */ + u16 rx_thr_en; + u16 iso_tx_thr_en; + u16 non_iso_tx_thr_en; + u16 rx_thr_length; + u16 tx_thr_length; +}; + +/* + * These Macros represents the bit fields in the Power and Clock Gating Control + * Register. Read the register into the u32 member then set/clear the + * bits using the bit elements. + */ +#define DWC_PCGCCTL_PHY_SUS_RD(x) (((x) & (0x001 << 4)) >> 4) +#define DWC_PCGCCTL_RSTP_DWN_RD(x) (((x) & (0x001 << 3)) >> 3) +#define DWC_PCGCCTL_PWR_CLAMP_RD(x) (((x) & (0x001 << 2)) >> 2) +#define DWC_PCGCCTL_GATE_HCLK_RD(x) (((x) & (0x001 << 1)) >> 1) +#define DWC_PCGCCTL_STOP_CLK_RD(x) (((x) & (0x001 << 0)) >> 0) + +#define DWC_PCGCCTL_RSTP_DWN_RW(reg, x) \ + (((reg) & (~((u32)0x001 << 3))) | ((x) << 3)) +#define DWC_PCGCCTL_PWR_CLAMP_RW(reg, x) \ + (((reg) & (~((u32)0x001 << 2))) | ((x) << 2)) +#define DWC_PCGCCTL_GATE_HCLK_RW(reg, x) \ + (((reg) & (~((u32)0x001 << 1))) | ((x) << 1)) +#define DWC_PCGCCTL_STOP_CLK_SET(reg) \ + (((reg) | 1)) +#define DWC_PCGCCTL_STOP_CLK_CLR(reg) \ + (((reg) & (~((u32)0x001 << 0)))) + +/* + * Host Mode Register Structures + */ + +/* + * The Host Global Registers structure defines the size and relative field + * offsets for the Host Mode Global Registers. Host Global Registers offsets + * 400h-7FFh. +*/ +#define DWC_HCFG 0x00 +#define DWC_HFIR 0x04 +#define DWC_HFNUM 0x08 +#define DWC_HPTXSTS 0x10 +#define DWC_HAINT 0x14 +#define DWC_HAINTMSK 0x18 + +/* + * These Macros represents the bit fields in the Host Configuration Register. + * Read the register into the u32 member then set/clear the bits using the bit + * elements. Write the u32 member to the hcfg register. + */ +#define DWC_HCFG_FSLSUPP_RD(x) (((x) & (0x001 << 2)) >> 2) +#define DWC_HCFG_FSLSP_CLK_RD(x) (((x) & (0x003 << 0)) >> 0) +#define DWC_HCFG_FSLSUPP_RW(reg, x) \ + (((reg) & (~((u32)0x001 << 2))) | ((x) << 2)) +#define DWC_HCFG_FSLSP_CLK_RW(reg, x) \ + (((reg) & (~((u32)0x003 << 0))) | ((x) << 0)) + +#define DWC_HCFG_30_60_MHZ 0 +#define DWC_HCFG_48_MHZ 1 +#define DWC_HCFG_6_MHZ 2 + +/* + * These Macros represents the bit fields in the Host Frame Remaing/Number + * Register. + */ +#define DWC_HFIR_FRINT_RD(x) (((x) & (0xffff << 0)) >> 0) +#define DWC_HFIR_FRINT_RW(reg, x) \ + (((reg) & (~((u32)0xffff << 0))) | ((x) << 0)) + +/* + * These Macros represents the bit fields in the Host Frame Remaing/Number + * Register. + */ +#define DWC_HFNUM_FRREM_RD(x) (((x) & (0xffff << 16)) >> 16) +#define DWC_HFNUM_FRNUM_RD(x) (((x) & (0xffff << 0)) >> 0) +#define DWC_HFNUM_FRREM_RW(reg, x) \ + (((reg) & (~((u32)0xffff << 16))) | ((x) << 16)) +#define DWC_HFNUM_FRNUM_RW(reg, x) \ + (((reg) & (~((u32)0xffff << 0))) | ((x) << 0)) +#define DWC_HFNUM_MAX_FRNUM 0x3FFF +#define DWC_HFNUM_MAX_FRNUM 0x3FFF + +#define DWC_HPTXSTS_PTXQTOP_ODD_RD(x) (((x) & (0x01 << 31)) >> 31) +#define DWC_HPTXSTS_PTXQTOP_CHNUM_RD(x) (((x) & (0x0f << 27)) >> 27) +#define DWC_HPTXSTS_PTXQTOP_TKN_RD(x) (((x) & (0x03 << 25)) >> 25) +#define DWC_HPTXSTS_PTXQTOP_TERM_RD(x) (((x) & (0x01 << 24)) >> 24) +#define DWC_HPTXSTS_PTXSPC_AVAIL_RD(x) (((x) & (0xff << 16)) >> 16) +#define DWC_HPTXSTS_PTXFSPC_AVAIL_RD(x) (((x) & (0xffff << 00)) >> 00) + +/* + * These Macros represents the bit fields in the Host Port Control and Status + * Register. Read the register into the u32 member then set/clear the bits using + * the bit elements. Write the u32 member to the hprt0 register. + */ +#define DWC_HPRT0_PRT_SPD_RD(x) (((x) & (0x3 << 17)) >> 17) +#define DWC_HPRT0_PRT_TST_CTL_RD(x) (((x) & (0xf << 13)) >> 13) +#define DWC_HPRT0_PRT_PWR_RD(x) (((x) & (0x1 << 12)) >> 12) +#define DWC_HPRT0_PRT_LSTS_RD(x) (((x) & (0x3 << 10)) >> 10) +#define DWC_HPRT0_PRT_RST_RD(x) (((x) & (0x1 << 8)) >> 8) +#define DWC_HPRT0_PRT_SUS_RD(x) (((x) & (0x1 << 7)) >> 7) +#define DWC_HPRT0_PRT_RES_RD(x) (((x) & (0x1 << 6)) >> 6) +#define DWC_HPRT0_PRT_OVRCURR_CHG_RD(x) (((x) & (0x1 << 5)) >> 5) +#define DWC_HPRT0_PRT_OVRCURR_ACT_RD(x) (((x) & (0x1 << 4)) >> 4) +#define DWC_HPRT0_PRT_ENA_DIS_CHG_RD(x) (((x) & (0x1 << 3)) >> 3) +#define DWC_HPRT0_PRT_ENA_RD(x) (((x) & (0x1 << 2)) >> 2) +#define DWC_HPRT0_PRT_CONN_DET_RD(x) (((x) & (0x1 << 1)) >> 1) +#define DWC_HPRT0_PRT_STS_RD(x) (((x) & (0x1 << 0)) >> 0) + +#define DWC_HPRT0_PRT_SPD_RW(reg, x) \ + (((reg) & (~((u32)0x3 << 17))) | ((x) << 17)) +#define DWC_HPRT0_PRT_TST_CTL_RW(reg, x) \ + (((reg) & (~((u32)0xf << 13))) | ((x) << 13)) +#define DWC_HPRT0_PRT_PWR_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 12))) | ((x) << 12)) +#define DWC_HPRT0_PRT_LSTS_RW(reg, x) \ + (((reg) & (~((u32)0x3 << 10))) | ((x) << 10)) +#define DWC_HPRT0_PRT_RST_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 8))) | ((x) << 8)) +#define DWC_HPRT0_PRT_SUS_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 7))) | ((x) << 7)) +#define DWC_HPRT0_PRT_RES_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 6))) | ((x) << 6)) +#define DWC_HPRT0_PRT_OVRCURR_CHG_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 5))) | ((x) << 5)) +#define DWC_HPRT0_PRT_OVRCURR_ACT_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 4))) | ((x) << 4)) +#define DWC_HPRT0_PRT_ENA_DIS_CHG_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 3))) | ((x) << 3)) +#define DWC_HPRT0_PRT_ENA_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 2))) | ((x) << 2)) +#define DWC_HPRT0_PRT_CONN_DET_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 1))) | ((x) << 1)) +#define DWC_HPRT0_PRT_STS_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 0))) | ((x) << 0)) + +#define DWC_HPRT0_PRTSPD_HIGH_SPEED 0 +#define DWC_HPRT0_PRTSPD_FULL_SPEED 1 +#define DWC_HPRT0_PRTSPD_LOW_SPEED 2 + +/* + * These Macros represents the bit fields in the Host All Interrupt Register. + */ +#define DWC_HAINT_CH15_RD(x) (((x) & (0x1 << 15)) >> 15) +#define DWC_HAINT_CH14_RD(x) (((x) & (0x1 << 14)) >> 14) +#define DWC_HAINT_CH13_RD(x) (((x) & (0x1 << 13)) >> 13) +#define DWC_HAINT_CH12_RD(x) (((x) & (0x1 << 12)) >> 12) +#define DWC_HAINT_CH11_RD(x) (((x) & (0x1 << 11)) >> 11) +#define DWC_HAINT_CH10_RD(x) (((x) & (0x1 << 10)) >> 10) +#define DWC_HAINT_CH09_RD(x) (((x) & (0x1 << 9)) >> 9) +#define DWC_HAINT_CH08_RD(x) (((x) & (0x1 << 8)) >> 8) +#define DWC_HAINT_CH07_RD(x) (((x) & (0x1 << 7)) >> 7) +#define DWC_HAINT_CH06_RD(x) (((x) & (0x1 << 6)) >> 6) +#define DWC_HAINT_CH05_RD(x) (((x) & (0x1 << 5)) >> 5) +#define DWC_HAINT_CH04_RD(x) (((x) & (0x1 << 4)) >> 4) +#define DWC_HAINT_CH03_RD(x) (((x) & (0x1 << 3)) >> 3) +#define DWC_HAINT_CH02_RD(x) (((x) & (0x1 << 2)) >> 2) +#define DWC_HAINT_CH01_RD(x) (((x) & (0x1 << 1)) >> 1) +#define DWC_HAINT_CH00_RD(x) (((x) & (0x1 << 0)) >> 0) + +#define DWC_HAINT_RD(x) (((x) & (0xffff << 0)) >> 0) + +/* + * These Macros represents the bit fields in the Host All Interrupt Register. + */ +#define DWC_HAINTMSK_CH15_RD(x) (((x) & (0x1 << 15)) >> 15) +#define DWC_HAINTMSK_CH14_RD(x) (((x) & (0x1 << 14)) >> 14) +#define DWC_HAINTMSK_CH13_RD(x) (((x) & (0x1 << 13)) >> 13) +#define DWC_HAINTMSK_CH12_RD(x) (((x) & (0x1 << 12)) >> 12) +#define DWC_HAINTMSK_CH11_RD(x) (((x) & (0x1 << 11)) >> 11) +#define DWC_HAINTMSK_CH10_RD(x) (((x) & (0x1 << 10)) >> 10) +#define DWC_HAINTMSK_CH09_RD(x) (((x) & (0x1 << 9)) >> 9) +#define DWC_HAINTMSK_CH08_RD(x) (((x) & (0x1 << 8)) >> 8) +#define DWC_HAINTMSK_CH07_RD(x) (((x) & (0x1 << 7)) >> 7) +#define DWC_HAINTMSK_CH06_RD(x) (((x) & (0x1 << 6)) >> 6) +#define DWC_HAINTMSK_CH05_RD(x) (((x) & (0x1 << 5)) >> 5) +#define DWC_HAINTMSK_CH04_RD(x) (((x) & (0x1 << 4)) >> 4) +#define DWC_HAINTMSK_CH03_RD(x) (((x) & (0x1 << 3)) >> 3) +#define DWC_HAINTMSK_CH02_RD(x) (((x) & (0x1 << 2)) >> 2) +#define DWC_HAINTMSK_CH01_RD(x) (((x) & (0x1 << 1)) >> 1) +#define DWC_HAINTMSK_CH00_RD(x) (((x) & (0x1 << 0)) >> 0) +#define DWC_HAINTMSK_RD(x) ((x) & 0xffff) + +#define DWC_HAINTMSK_CH15_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 15))) | ((x) << 15)) +#define DWC_HAINTMSK_CH14_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 14))) | ((x) << 14)) +#define DWC_HAINTMSK_CH13_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 13))) | ((x) << 13)) +#define DWC_HAINTMSK_CH12_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 12))) | ((x) << 12)) +#define DWC_HAINTMSK_CH11_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 11))) | ((x) << 11)) +#define DWC_HAINTMSK_CH10_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 10))) | ((x) << 10)) +#define DWC_HAINTMSK_CH09_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 9))) | ((x) << 9)) +#define DWC_HAINTMSK_CH08_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 8))) | ((x) << 8)) +#define DWC_HAINTMSK_CH07_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 7))) | ((x) << 7)) +#define DWC_HAINTMSK_CH06_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 6))) | ((x) << 6)) +#define DWC_HAINTMSK_CH05_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 5))) | ((x) << 5)) +#define DWC_HAINTMSK_CH04_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 4))) | ((x) << 4)) +#define DWC_HAINTMSK_CH03_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 3))) | ((x) << 3)) +#define DWC_HAINTMSK_CH02_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 2))) | ((x) << 2)) +#define DWC_HAINTMSK_CH01_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 1))) | ((x) << 1)) +#define DWC_HAINTMSK_CH00_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 0))) | ((x) << 0)) +#define DWC_HAINTMSK_RW(reg, x) \ + (((reg) & (~((u32)0xffff))) | x) + +/* + * Host Channel Specific Registers. 500h-5FCh + */ +#define DWC_HCCHAR 0x00 +#define DWC_HCSPLT 0x04 +#define DWC_HCINT 0x08 +#define DWC_HCINTMSK 0x0C +#define DWC_HCTSIZ 0x10 +#define DWC_HCDMA 0x14 + +/* + * These Macros represents the bit fields in the Host Channel Characteristics + * Register. Read the register into the u32 member then set/clear the bits using + * the bit elements. Write the u32 member to the hcchar register. + */ +#define DWC_HCCHAR_ENA_RD(x) (((x) & (0x001 << 31)) >> 31) +#define DWC_HCCHAR_DIS_RD(x) (((x) & (0x001 << 30)) >> 30) +#define DWC_HCCHAR_ODD_FRAME_RD(x) (((x) & (0x001 << 29)) >> 29) +#define DWC_HCCHAR_DEV_ADDR_RD(x) (((x) & (0x07f << 22)) >> 22) +#define DWC_HCCHAR_MULTI_CNT_RD(x) (((x) & (0x003 << 20)) >> 20) +#define DWC_HCCHAR_EPTYPE_RD(x) (((x) & (0x003 << 18)) >> 18) +#define DWC_HCCHAR_LSP_DEV_RD(x) (((x) & (0x001 << 17)) >> 17) +#define DWC_HCCHAR_EPDIR_RD(x) (((x) & (0x001 << 15)) >> 15) +#define DWC_HCCHAR_EP_NUM_RD(x) (((x) & (0x00f << 11)) >> 11) +#define DWC_HCCHAR_MPS_RD(x) (((x) & (0x7ff << 0)) >> 0) + +#define DWC_HCCHAR_ENA_RW(reg, x) \ + (((reg) & (~((u32)0x001 << 31))) | ((x) << 31)) +#define DWC_HCCHAR_DIS_RW(reg, x) \ + (((reg) & (~((u32)0x001 << 30))) | ((x) << 30)) +#define DWC_HCCHAR_ODD_FRAME_RW(reg, x) \ + (((reg) & (~((u32)0x001 << 29))) | ((x) << 29)) +#define DWC_HCCHAR_DEV_ADDR_RW(reg, x) \ + (((reg) & (~((u32)0x07f << 22))) | ((x) << 22)) +#define DWC_HCCHAR_MULTI_CNT_RW(reg, x) \ + (((reg) & (~((u32)0x003 << 20))) | ((x) << 20)) +#define DWC_HCCHAR_EPTYPE_RW(reg, x) \ + (((reg) & (~((u32)0x003 << 18))) | ((x) << 18)) +#define DWC_HCCHAR_LSP_DEV_RW(reg, x) \ + (((reg) & (~((u32)0x001 << 17))) | ((x) << 17)) +#define DWC_HCCHAR_EPDIR_RW(reg, x) \ + (((reg) & (~((u32)0x001 << 15))) | ((x) << 15)) +#define DWC_HCCHAR_EP_NUM_RW(reg, x) \ + (((reg) & (~((u32)0x00f << 11))) | ((x) << 11)) +#define DWC_HCCHAR_MPS_RW(reg, x) \ + (((reg) & (~((u32)0x7ff << 0))) | ((x) << 0)) + +#define DWC_HCSPLT_ENA_RD(x) (((x) & (0x01 << 31)) >> 31) +#define DWC_HCSPLT_COMP_SPLT_RD(x) (((x) & (0x01 << 16)) >> 16) +#define DWC_HCSPLT_TRANS_POS_RD(x) (((x) & (0x03 << 14)) >> 14) +#define DWC_HCSPLT_HUB_ADDR_RD(x) (((x) & (0x7f << 7)) >> 7) +#define DWC_HCSPLT_PRT_ADDR_RD(x) (((x) & (0x7f << 0)) >> 0) + +#define DWC_HCSPLT_ENA_RW(reg, x) \ + (((reg) & (~((u32)0x01 << 31))) | ((x) << 31)) +#define DWC_HCSPLT_COMP_SPLT_RW(reg, x) \ + (((reg) & (~((u32)0x01 << 16))) | ((x) << 16)) +#define DWC_HCSPLT_TRANS_POS_RW(reg, x) \ + (((reg) & (~((u32)0x03 << 14))) | ((x) << 14)) +#define DWC_HCSPLT_HUB_ADDR_RW(reg, x) \ + (((reg) & (~((u32)0x7f << 7))) | ((x) << 7)) +#define DWC_HCSPLT_PRT_ADDR_RW(reg, x) \ + (((reg) & (~((u32)0x7f << 0))) | ((x) << 0)) + +#define DWC_HCSPLIT_XACTPOS_MID 0 +#define DWC_HCSPLIT_XACTPOS_END 1 +#define DWC_HCSPLIT_XACTPOS_BEGIN 2 +#define DWC_HCSPLIT_XACTPOS_ALL 3 + +/* + * These Macros represents the bit fields in the Host All Interrupt + * Register. + */ +#define DWC_HCINT_DATA_TOG_ERR_RD(x) (((x) & (0x1 << 10)) >> 10) +#define DWC_HCINT_FRAME_OVERN_ERR_RD(x) (((x) & (0x1 << 9)) >> 9) +#define DWC_HCINT_BBL_ERR_RD(x) (((x) & (0x1 << 8)) >> 8) +#define DWC_HCINT_TRANS_ERR_RD(x) (((x) & (0x1 << 7)) >> 7) +#define DWC_HCINT_NYET_RESP_REC_RD(x) (((x) & (0x1 << 6)) >> 6) +#define DWC_HCINT_ACK_RESP_REC_RD(x) (((x) & (0x1 << 5)) >> 5) +#define DWC_HCINT_NAK_RESP_REC_RD(x) (((x) & (0x1 << 4)) >> 4) +#define DWC_HCINT_STALL_RESP_REC_RD(x) (((x) & (0x1 << 3)) >> 3) +#define DWC_HCINT_AHB_ERR_RD(x) (((x) & (0x1 << 2)) >> 2) +#define DWC_HCINT_CHAN_HALTED_RD(x) (((x) & (0x1 << 1)) >> 1) +#define DWC_HCINT_TXFER_CMPL_RD(x) (((x) & (0x1 << 0)) >> 0) + +#define DWC_HCINT_DATA_TOG_ERR_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 10))) | ((x) << 10)) +#define DWC_HCINT_FRAME_OVERN_ERR_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 9))) | ((x) << 9)) +#define DWC_HCINT_BBL_ERR_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 8))) | ((x) << 8)) +#define DWC_HCINT_TRANS_ERR_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 7))) | ((x) << 7)) +#define DWC_HCINT_NYET_RESP_REC_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 6))) | ((x) << 6)) +#define DWC_HCINT_ACK_RESP_REC_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 5))) | ((x) << 5)) +#define DWC_HCINT_NAK_RESP_REC_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 4))) | ((x) << 4)) +#define DWC_HCINT_STALL_RESP_REC_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 3))) | ((x) << 3)) +#define DWC_HCINT_AHB_ERR_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 2))) | ((x) << 2)) +#define DWC_HCINT_CHAN_HALTED_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 1))) | ((x) << 1)) +#define DWC_HCINT_TXFER_CMPL_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 0))) | ((x) << 0)) + +/* + * These Macros represents the bit fields in the Host Channel Transfer Size + * Register. Read the register into the u32 member then set/clear the bits + * using the bit elements. Write the u32 member to the hcchar register. + */ +#define DWC_HCTSIZ_DO_PING_PROTO_RD(x) (((x) & (0x00001 << 31)) >> 31) +#define DWC_HCTSIZ_PKT_PID_RD(x) (((x) & (0x00003 << 29)) >> 29) +#define DWC_HCTSIZ_PKT_CNT_RD(x) (((x) & (0x003ff << 19)) >> 19) +#define DWC_HCTSIZ_XFER_SIZE_RD(x) (((x) & (0x7ffff << 00)) >> 00) + +#define DWC_HCTSIZ_DO_PING_PROTO_RW(reg, x) \ + (((reg) & (~((u32)0x00001 << 31))) | ((x) << 31)) +#define DWC_HCTSIZ_PKT_PID_RW(reg, x) \ + (((reg) & (~((u32)0x00003 << 29))) | ((x) << 29)) +#define DWC_HCTSIZ_PKT_CNT_RW(reg, x) \ + (((reg) & (~((u32)0x003ff << 19))) | ((x) << 19)) +#define DWC_HCTSIZ_XFER_SIZE_RW(reg, x) \ + (((reg) & (~((u32)0x7ffff << 00))) | ((x) << 00)) + +#define DWC_HCTSIZ_DATA0 0 +#define DWC_HCTSIZ_DATA1 2 +#define DWC_HCTSIZ_DATA2 1 +#define DWC_HCTSIZ_MDATA 3 +#define DWC_HCTSIZ_SETUP 3 + +/* + * These Macros represents the bit fields in the Host Channel Interrupt Mask + * Register. Read the register into the u32 member then set/clear the bits using + * the bit elements. Write the u32 member to the hcintmsk register. + */ +#define DWC_HCINTMSK_DATA_TOG_ERR_RD(x) (((x) & (0x1 << 10)) >> 10) +#define DWC_HCINTMSK_FRAME_OVERN_ERR_RD(x) (((x) & (0x1 << 9)) >> 9) +#define DWC_HCINTMSK_BBL_ERR_RD(x) (((x) & (0x1 << 8)) >> 8) +#define DWC_HCINTMSK_TRANS_ERR_RD(x) (((x) & (0x1 << 7)) >> 7) +#define DWC_HCINTMSK_NYET_RESP_REC_RD(x) (((x) & (0x1 << 6)) >> 6) +#define DWC_HCINTMSK_ACK_RESP_REC_RD(x) (((x) & (0x1 << 5)) >> 5) +#define DWC_HCINTMSK_NAK_RESP_REC_RD(x) (((x) & (0x1 << 4)) >> 4) +#define DWC_HCINTMSK_STALL_RESP_REC_RD(x) (((x) & (0x1 << 3)) >> 3) +#define DWC_HCINTMSK_AHB_ERR_RD(x) (((x) & (0x1 << 2)) >> 2) +#define DWC_HCINTMSK_CHAN_HALTED_RD(x) (((x) & (0x1 << 1)) >> 1) +#define DWC_HCINTMSK_TXFER_CMPL_RD(x) (((x) & (0x1 << 0)) >> 0) + +#define DWC_HCINTMSK_DATA_TOG_ERR_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 10))) | ((x) << 10)) +#define DWC_HCINTMSK_FRAME_OVERN_ERR_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 9))) | ((x) << 9)) +#define DWC_HCINTMSK_BBL_ERR_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 8))) | ((x) << 8)) +#define DWC_HCINTMSK_TRANS_ERR_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 7))) | ((x) << 7)) +#define DWC_HCINTMSK_NYET_RESP_REC_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 6))) | ((x) << 6)) +#define DWC_HCINTMSK_ACK_RESP_REC_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 5))) | ((x) << 5)) +#define DWC_HCINTMSK_NAK_RESP_REC_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 4))) | ((x) << 4)) +#define DWC_HCINTMSK_STALL_RESP_REC_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 3))) | ((x) << 3)) +#define DWC_HCINTMSK_AHB_ERR_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 2))) | ((x) << 2)) +#define DWC_HCINTMSK_CHAN_HALTED_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 1))) | ((x) << 1)) +#define DWC_HCINTMSK_TXFER_CMPL_RW(reg, x) \ + (((reg) & (~((u32)0x1 << 0))) | ((x) << 0)) + +/* + * OTG Host Interface Structure. + * + * The OTG Host Interface Structure structure contains information needed to + * manage the DWC_otg controller acting in host mode. It represents the + * programming view of the host-specific aspects of the controller. + */ +struct dwc_host_if { /* CONFIG_DWC_OTG_REG_LE */ + /* Host Global Registers starting at offset 400h. */ + ulong host_global_regs; +#define DWC_OTG_HOST_GLOBAL_REG_OFFSET 0x400 + + /* Host Port 0 Control and Status Register */ + ulong hprt0; +#define DWC_OTG_HOST_PORT_REGS_OFFSET 0x440 + + /* Host Channel Specific Registers at offsets 500h-5FCh. */ + ulong hc_regs[MAX_EPS_CHANNELS]; +#define DWC_OTG_HOST_CHAN_REGS_OFFSET 0x500 +#define DWC_OTG_CHAN_REGS_OFFSET 0x20 + + /* Host configuration information */ + /* Number of Host Channels (range: 1-16) */ + u8 num_host_channels; + /* Periodic EPs supported (0: no, 1: yes) */ + u8 perio_eps_supported; + /* Periodic Tx FIFO Size (Only 1 host periodic Tx FIFO) */ + u16 perio_tx_fifo_size; +}; +#endif diff --git a/drivers/usb/early/ehci-dbgp.c b/drivers/usb/early/ehci-dbgp.c index 1fc8f12..347bb05 100644 --- a/drivers/usb/early/ehci-dbgp.c +++ b/drivers/usb/early/ehci-dbgp.c @@ -450,7 +450,7 @@ static int dbgp_ehci_startup(void) writel(FLAG_CF, &ehci_regs->configured_flag); /* Wait until the controller is no longer halted */ - loop = 10; + loop = 1000; do { status = readl(&ehci_regs->status); if (!(status & STS_HALT)) diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index aca160e..9f30cf5 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -112,6 +112,7 @@ config USB_GADGET_SELECTED choice prompt "USB Peripheral Controller" depends on USB_GADGET + default USB_GADGET_S3C_OTGD if (ARCH_S5PV210) help A USB device uses a controller to talk to its host. Systems should have only one such upstream link. @@ -373,6 +374,20 @@ config USB_S3C_HSUDC default USB_GADGET select USB_GADGET_SELECTED +config USB_GADGET_S3C_OTGD + boolean "S3C HS USB OTG Device" + depends on (ARCH_S5PV210) + help + Samsung's S3C64XX processors include high speed USB OTG2.0 + controller. It has 15 configurable endpoints, as well as + endpoint zero (for control transfers). + + This driver has been tested on the S3C6410, S5P6440, S5PC100 processor. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "s3c-udc-otg" and force all + gadget drivers to also be dynamically linked. + config USB_GADGET_PXA_U2O boolean "PXA9xx Processor USB2.0 controller" select USB_GADGET_DUALSPEED @@ -633,6 +648,43 @@ config USB_DUMMY_HCD endchoice +comment "NOTE: S3C OTG device role enables the controller driver below" + depends on USB_GADGET_S3C_OTGD + +config USB_S3C_OTGD + tristate "S3C high speed(2.0, dual-speed) USB OTG device" + depends on USB_GADGET && USB_GADGET_S3C_OTGD + default y + default USB_GADGET + select USB_GADGET_SELECTED + select USB_GADGET_DUALSPEED + help + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "s3c-udc-otg-hs" and force all + gadget drivers to also be dynamically linked. + +choice + prompt "S3C OTGD transfer mode" + depends on USB_S3C_OTGD + default y + help + S3C USB OTG conteroller supports DMA mode and Slave mode + for the dat transfer. You must slect one for the core + operation mode. + +config USB_GADGET_S3C_OTGD_DMA_MODE + bool "enabled DMA MODE" + depends on USB_GADGET_S3C_OTGD + help + S3C USB OTG core operates in DMA mode. + +config USB_GADGET_S3C_OTGD_SLAVE_MODE + bool "enabled Slave MODE" + depends on USB_GADGET_S3C_OTGD + help + S3C USB OTG core operates in Slave mode. +endchoice + # Selected by UDC drivers that support high-speed operation. config USB_GADGET_DUALSPEED bool @@ -944,6 +996,12 @@ config USB_G_ANDROID The functions can be configured via a board file and may be enabled and disabled dynamically. +config USB_ANDROID_RNDIS_DWORD_ALIGNED + boolean "Use double word aligned" + depends on USB_G_ANDROID + help + Provides dword aligned for DMA controller. + config USB_CDC_COMPOSITE tristate "CDC Composite Device (Ethernet and ACM)" depends on NET diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index ab17a4c..0cb5b62 100644..100755 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_USB_IMX) += imx_udc.o obj-$(CONFIG_USB_GOKU) += goku_udc.o obj-$(CONFIG_USB_OMAP) += omap_udc.o obj-$(CONFIG_USB_S3C2410) += s3c2410_udc.o +obj-$(CONFIG_USB_S3C_OTGD) += s3c_udc_otg.o obj-$(CONFIG_USB_AT91) += at91_udc.o obj-$(CONFIG_USB_ATMEL_USBA) += atmel_usba_udc.o obj-$(CONFIG_USB_FSL_USB2) += fsl_usb2_udc.o diff --git a/drivers/usb/gadget/epautoconf.c b/drivers/usb/gadget/epautoconf.c index 9b7360f..431cc35 100644 --- a/drivers/usb/gadget/epautoconf.c +++ b/drivers/usb/gadget/epautoconf.c @@ -290,6 +290,60 @@ struct usb_ep *usb_ep_autoconfig ( if (ep && ep_matches (gadget, ep, desc)) return ep; #endif + } else if (gadget_is_s3c(gadget)) { + if (USB_ENDPOINT_XFER_INT == type) { + /* single buffering is enough */ + ep = find_ep (gadget, "ep3-int"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + ep = find_ep (gadget, "ep6-int"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + ep = find_ep (gadget, "ep9-int"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + ep = find_ep (gadget, "ep12-int"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + } else if (USB_ENDPOINT_XFER_BULK == type + && (USB_DIR_IN & desc->bEndpointAddress)) { + ep = find_ep (gadget, "ep2-bulk"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + ep = find_ep (gadget, "ep5-bulk"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + ep = find_ep (gadget, "ep8-bulk"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + ep = find_ep (gadget, "ep11-bulk"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + ep = find_ep (gadget, "ep14-bulk"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + } else if (USB_ENDPOINT_XFER_BULK == type + && !(USB_DIR_IN & desc->bEndpointAddress)) { + ep = find_ep (gadget, "ep1-bulk"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + ep = find_ep (gadget, "ep4-bulk"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + ep = find_ep (gadget, "ep7-bulk"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + ep = find_ep (gadget, "ep10-bulk"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + ep = find_ep (gadget, "ep13-bulk"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + } else if (USB_ENDPOINT_XFER_ISOC == type) { + ep = find_ep(gadget, "ep15-iso"); + if (ep && ep_matches(gadget, ep, desc)) + return ep; + } } /* Second, look at endpoints until an unclaimed one looks usable */ diff --git a/drivers/usb/gadget/f_mass_storage.c b/drivers/usb/gadget/f_mass_storage.c index bc2f624..7405647 100644 --- a/drivers/usb/gadget/f_mass_storage.c +++ b/drivers/usb/gadget/f_mass_storage.c @@ -908,11 +908,13 @@ static int do_write(struct fsg_common *common) curlun->sense_data = SS_INVALID_FIELD_IN_CDB; return -EINVAL; } +#ifndef CONFIG_USB_ANDROID_MASS_STORAGE if (!curlun->nofua && (common->cmnd[1] & 0x08)) { /* FUA */ spin_lock(&curlun->filp->f_lock); curlun->filp->f_flags |= O_SYNC; spin_unlock(&curlun->filp->f_lock); } +#endif } if (lba >= curlun->num_sectors) { curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; diff --git a/drivers/usb/gadget/fsl_udc_core.c b/drivers/usb/gadget/fsl_udc_core.c index 5308381..44d789d 100644 --- a/drivers/usb/gadget/fsl_udc_core.c +++ b/drivers/usb/gadget/fsl_udc_core.c @@ -717,6 +717,8 @@ static void fsl_queue_td(struct fsl_ep *ep, struct fsl_req *req) lastreq = list_entry(ep->queue.prev, struct fsl_req, queue); lastreq->tail->next_td_ptr = cpu_to_hc32(req->head->td_dma & DTD_ADDR_MASK); + /* Ensure dTD's next dtd pointer to be updated */ + wmb(); /* Read prime bit, if 1 goto done */ if (fsl_readl(&dr_regs->endpointprime) & bitmask) goto out; diff --git a/drivers/usb/gadget/gadget_chips.h b/drivers/usb/gadget/gadget_chips.h index bcdac7c..839c9a3 100644 --- a/drivers/usb/gadget/gadget_chips.h +++ b/drivers/usb/gadget/gadget_chips.h @@ -70,6 +70,12 @@ #define gadget_is_s3c2410(g) 0 #endif +#if CONFIG_USB_GADGET_S3C_OTGD +#define gadget_is_s3c(g) !strcmp("s3c-udc", (g)->name) +#else +#define gadget_is_s3c(g) 0 +#endif + #ifdef CONFIG_USB_GADGET_AT91 #define gadget_is_at91(g) !strcmp("at91_udc", (g)->name) #else @@ -223,6 +229,8 @@ static inline int usb_gadget_controller_number(struct usb_gadget *gadget) return 0x29; else if (gadget_is_s3c_hsudc(gadget)) return 0x30; + else if (gadget_is_s3c(gadget)) + return 0x31; return -ENOENT; } diff --git a/drivers/usb/gadget/s3c_udc.h b/drivers/usb/gadget/s3c_udc.h new file mode 100644 index 0000000..a776167 --- /dev/null +++ b/drivers/usb/gadget/s3c_udc.h @@ -0,0 +1,164 @@ +/* + * drivers/usb/gadget/s3c_udc.h + * Samsung S3C on-chip full/high speed USB device controllers + * Copyright (C) 2005 for Samsung Electronics + * + * 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 __S3C_USB_GADGET +#define __S3C_USB_GADGET + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/ioport.h> +#include <linux/types.h> +#include <linux/version.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/timer.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/proc_fs.h> +#include <linux/mm.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> + +#include <asm/byteorder.h> +#include <asm/dma.h> +#include <linux/io.h> +#include <asm/irq.h> +#include <asm/system.h> +#include <asm/unaligned.h> +/* #include <asm/hardware.h> */ + +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> + +/* Max packet size */ +#if defined(CONFIG_USB_GADGET_S3C_FS) +#define EP0_FIFO_SIZE 8 +#define EP_FIFO_SIZE 64 +#define S3C_MAX_ENDPOINTS 5 +#elif defined(CONFIG_USB_GADGET_S3C_HS) || defined(CONFIG_ARCH_S5PV210) +#define EP0_FIFO_SIZE 64 +#define EP_FIFO_SIZE 512 +#define EP_FIFO_SIZE2 1024 +#define S3C_MAX_ENDPOINTS 16 +#define DED_TX_FIFO 1 /* Dedicated NPTx fifo for s5p6440 */ +#else +#define EP0_FIFO_SIZE 64 +#define EP_FIFO_SIZE 512 +#define EP_FIFO_SIZE2 1024 +#define S3C_MAX_ENDPOINTS 16 +#endif + +#define WAIT_FOR_SETUP 0 +#define DATA_STATE_XMIT 1 +#define DATA_STATE_NEED_ZLP 2 +#define WAIT_FOR_OUT_STATUS 3 +#define DATA_STATE_RECV 4 +#define RegReadErr 5 +#define FAIL_TO_SETUP 6 + +#define TEST_J_SEL 0x1 +#define TEST_K_SEL 0x2 +#define TEST_SE0_NAK_SEL 0x3 +#define TEST_PACKET_SEL 0x4 +#define TEST_FORCE_ENABLE_SEL 0x5 + + +typedef enum ep_type { + ep_control, ep_bulk_in, ep_bulk_out, ep_interrupt, ep_isochronous +} ep_type_t; + +struct s3c_ep { + struct usb_ep ep; + struct s3c_udc *dev; + + const struct usb_endpoint_descriptor *desc; + struct list_head queue; + unsigned long pio_irqs; + + u8 stopped; + u8 bEndpointAddress; + u8 bmAttributes; + + ep_type_t ep_type; + u32 fifo; +#ifdef CONFIG_USB_GADGET_S3C_FS + u32 csr1; + u32 csr2; +#endif +}; + +struct s3c_request { + struct usb_request req; + struct list_head queue; + unsigned char mapped; +}; + +struct s3c_udc { + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + /* struct device *dev; */ + struct platform_device *dev; + spinlock_t lock; + u16 status; + int ep0state; + struct s3c_ep ep[S3C_MAX_ENDPOINTS]; + + unsigned char usb_address; + + unsigned req_pending:1, req_std:1, req_config:1; + + struct regulator *udc_vcc_d, *udc_vcc_a; + int udc_enabled; + int soft_disconnected; +}; + +extern struct s3c_udc *the_controller; +extern void otg_phy_init(void); +extern void otg_phy_off(void); +extern struct usb_ctrlrequest usb_ctrl; +extern struct i2c_driver fsa9480_i2c_driver; + +#define ep_is_in(EP) (((EP)->bEndpointAddress&USB_DIR_IN) == USB_DIR_IN) +#define ep_index(EP) ((EP)->bEndpointAddress&0xF) +#define ep_maxpacket(EP) ((EP)->ep.maxpacket) + +#if defined CONFIG_USB_S3C_OTG_HOST || defined CONFIG_USB_DWC_OTG +#define USB_OTG_DRIVER_S3CHS 1 +#define USB_OTG_DRIVER_S3CFSLS 2 +#define USB_OTG_DRIVER_S3C USB_OTG_DRIVER_S3CHS | USB_OTG_DRIVER_S3CFSLS +#define USB_OTG_DRIVER_DWC 4 +#endif +extern atomic_t g_OtgHostMode; // actual mode: client (0) or host (1) +extern atomic_t g_OtgOperationMode; // operation mode: 'c'lient, 'h'ost, 'o'tg or 'a'uto-host +extern atomic_t g_OtgLastCableState; // last cable state: detached (0), client attached (1), otg attached (2) +extern atomic_t g_OtgDriver; // driver to use: 0: S3C High-speed, 1: S3C Low-speed/Full-speed, 2: DWC +#ifdef CONFIG_USB_S3C_OTG_HOST +extern struct platform_driver s5pc110_otg_driver; +#endif +#ifdef CONFIG_USB_DWC_OTG +extern struct platform_driver dwc_otg_driver; +#endif +extern int s3c_is_otgmode(void); +extern int s3c_get_drivermode(void); +#endif diff --git a/drivers/usb/gadget/s3c_udc_otg.c b/drivers/usb/gadget/s3c_udc_otg.c new file mode 100644 index 0000000..7bbcedc --- /dev/null +++ b/drivers/usb/gadget/s3c_udc_otg.c @@ -0,0 +1,1547 @@ +/* + * drivers/usb/gadget/s3c_udc_otg.c + * Samsung S3C on-chip full/high speed USB OTG 2.0 device controllers + * + * Copyright (C) 2008 for Samsung Electronics + * + * + * 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 "s3c_udc.h" +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <mach/map.h> +#include <plat/regs-otg.h> +#include <linux/i2c.h> +#include <linux/regulator/consumer.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/vmalloc.h> +#include <linux/proc_fs.h> +#include <asm/uaccess.h> +#if defined(CONFIG_USB_GADGET_S3C_OTGD_DMA_MODE) /* DMA mode */ +#define OTG_DMA_MODE 1 + +#elif defined(CONFIG_USB_GADGET_S3C_OTGD_SLAVE_MODE) /* Slave mode */ +#define OTG_DMA_MODE 0 +#error " Slave Mode is not implemented to do later" +#else +#error " Unknown S3C OTG operation mode, Select a correct operation mode" +#endif + +#define DEBUG_S3C_UDC_SETUP +#undef DEBUG_S3C_UDC_EP0 +#define DEBUG_S3C_UDC_ISR +#undef DEBUG_S3C_UDC_OUT_EP +#undef DEBUG_S3C_UDC_IN_EP +#undef DEBUG_S3C_UDC + +#define EP0_CON 0 +#define EP1_OUT 1 +#define EP2_IN 2 +#define EP3_IN 3 +#define EP_MASK 0xF + +#if defined(DEBUG_S3C_UDC_SETUP) || defined(DEBUG_S3C_UDC_ISR)\ + || defined(DEBUG_S3C_UDC_OUT_EP) + +static char *state_names[] = { + "WAIT_FOR_SETUP", + "DATA_STATE_XMIT", + "DATA_STATE_NEED_ZLP", + "WAIT_FOR_OUT_STATUS", + "DATA_STATE_RECV", + }; +#endif + +#ifdef DEBUG_S3C_UDC_SETUP +#define DEBUG_SETUP(fmt, args...) pr_debug(fmt, ##args) +#else +#define DEBUG_SETUP(fmt, args...) do {} while (0) +#endif + +#ifdef DEBUG_S3C_UDC_EP0 +#define DEBUG_EP0(fmt, args...) printk(fmt, ##args) +#else +#define DEBUG_EP0(fmt, args...) do {} while (0) +#endif + +#ifdef DEBUG_S3C_UDC +#define DEBUG(fmt, args...) printk(fmt, ##args) +#else +#define DEBUG(fmt, args...) do {} while (0) +#endif + +#ifdef DEBUG_S3C_UDC_ISR +#define DEBUG_ISR(fmt, args...) pr_debug(fmt, ##args) +#else +#define DEBUG_ISR(fmt, args...) do {} while (0) +#endif + +#ifdef DEBUG_S3C_UDC_OUT_EP +#define DEBUG_OUT_EP(fmt, args...) printk(fmt, ##args) +#else +#define DEBUG_OUT_EP(fmt, args...) do {} while (0) +#endif + +#ifdef DEBUG_S3C_UDC_IN_EP +#define DEBUG_IN_EP(fmt, args...) printk(fmt, ##args) +#else +#define DEBUG_IN_EP(fmt, args...) do {} while (0) +#endif + + +#define DRIVER_DESC "S3C HS USB Device Controller Driver, (c) 2008-2009 Samsung Electronics" +#define DRIVER_VERSION "15 March 2009" + +struct s3c_udc *the_controller; + +static struct clk *otg_clock; +static const char driver_name[] = "s3c-udc"; +static const char driver_desc[] = DRIVER_DESC; +static const char ep0name[] = "ep0-control"; + +/* Max packet size*/ +static unsigned int ep0_fifo_size = 64; +static unsigned int ep_fifo_size = 512; +static unsigned int ep_fifo_size2 = 1024; +static int reset_available = 1; + +/* + Local declarations. +*/ +static int s3c_ep_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *); +static int s3c_ep_disable(struct usb_ep *ep); +static struct usb_request *s3c_alloc_request(struct usb_ep *ep, + gfp_t gfp_flags); +static void s3c_free_request(struct usb_ep *ep, struct usb_request *); + +static int s3c_queue(struct usb_ep *ep, + struct usb_request *, gfp_t gfp_flags); +static int s3c_dequeue(struct usb_ep *ep, struct usb_request *); +static int s3c_fifo_status(struct usb_ep *ep); +static void s3c_fifo_flush(struct usb_ep *ep); +static void s3c_ep0_read(struct s3c_udc *dev); +static void s3c_ep0_kick(struct s3c_udc *dev, struct s3c_ep *ep); +static void s3c_handle_ep0(struct s3c_udc *dev); +static int s3c_ep0_write(struct s3c_udc *dev); +static int write_fifo_ep0(struct s3c_ep *ep, struct s3c_request *req); +static void done(struct s3c_ep *ep, struct s3c_request *req, int status); +static void stop_activity(struct s3c_udc *dev, + struct usb_gadget_driver *driver); +static int udc_enable(struct s3c_udc *dev); +static void udc_set_address(struct s3c_udc *dev, unsigned char address); +static void reconfig_usbd(void); +static void set_max_pktsize(struct s3c_udc *dev, enum usb_device_speed speed); +static void nuke(struct s3c_ep *ep, int status); +static int s3c_udc_set_halt(struct usb_ep *_ep, int value); + +static struct usb_ep_ops s3c_ep_ops = { + .enable = s3c_ep_enable, + .disable = s3c_ep_disable, + + .alloc_request = s3c_alloc_request, + .free_request = s3c_free_request, + + .queue = s3c_queue, + .dequeue = s3c_dequeue, + + .set_halt = s3c_udc_set_halt, + .fifo_status = s3c_fifo_status, + .fifo_flush = s3c_fifo_flush, +}; + +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + +static const char proc_node_name[] = "driver/udc"; + +static int +udc_proc_read(char *page, char **start, off_t off, int count, + int *eof, void *_dev) +{ + char *buf = page; + struct s3c_udc *dev = _dev; + char *next = buf; + unsigned size = count; + unsigned long flags; + int t; + + if (off != 0) + return 0; + + local_irq_save(flags); + + /* basic device status */ + t = scnprintf(next, size, + DRIVER_DESC "\n" + "%s version: %s\n" + "Gadget driver: %s\n" + "\n", + driver_name, DRIVER_VERSION, + dev->driver ? dev->driver->driver.name : "(none)"); + size -= t; + next += t; + + local_irq_restore(flags); + *eof = 1; + return count - size; +} + +#define create_proc_files() \ + create_proc_read_entry(proc_node_name, 0, NULL, udc_proc_read, dev) +#define remove_proc_files() \ + remove_proc_entry(proc_node_name, NULL) + +#else /* !CONFIG_USB_GADGET_DEBUG_FILES */ + +#define create_proc_files() do {} while (0) +#define remove_proc_files() do {} while (0) + +#endif /* CONFIG_USB_GADGET_DEBUG_FILES */ + +#if OTG_DMA_MODE /* DMA Mode */ +#include "s3c_udc_otg_xfer_dma.c" + +#else /* Slave Mode */ +#include "s3c_udc_otg_xfer_slave.c" +#endif + +/* + * udc_disable - disable USB device controller + */ +static void udc_disable(struct s3c_udc *dev) +{ + DEBUG_SETUP("%s: %p\n", __func__, dev); + + udc_set_address(dev, 0); + + dev->ep0state = WAIT_FOR_SETUP; + dev->gadget.speed = USB_SPEED_UNKNOWN; + dev->usb_address = 0; + + otg_phy_off(); +} + +/* + * udc_reinit - initialize software state + */ +static void udc_reinit(struct s3c_udc *dev) +{ + unsigned int i; + + DEBUG_SETUP("%s: %p\n", __func__, dev); + + /* device/ep0 records init */ + INIT_LIST_HEAD(&dev->gadget.ep_list); + INIT_LIST_HEAD(&dev->gadget.ep0->ep_list); + dev->ep0state = WAIT_FOR_SETUP; + + /* basic endpoint records init */ + for (i = 0; i < S3C_MAX_ENDPOINTS; i++) { + struct s3c_ep *ep = &dev->ep[i]; + + if (i != 0) + list_add_tail(&ep->ep.ep_list, &dev->gadget.ep_list); + + ep->desc = 0; + ep->stopped = 0; + INIT_LIST_HEAD(&ep->queue); + ep->pio_irqs = 0; + } + + /* the rest was statically initialized, and is read-only */ +} + +#define BYTES2MAXP(x) (x / 8) +#define MAXP2BYTES(x) (x * 8) + +/* until it's enabled, this UDC should be completely invisible + * to any USB host. + */ +static int udc_enable(struct s3c_udc *dev) +{ + unsigned long flags; + + DEBUG_SETUP("%s: %p\n", __func__, dev); + + otg_phy_init(); + + spin_lock_irqsave(&dev->lock, flags); + reconfig_usbd(); + spin_unlock_irqrestore(&dev->lock, flags); + + DEBUG_SETUP("S3C USB 2.0 OTG Controller Core Initialized : 0x%x\n", + readl(S3C_UDC_OTG_GINTMSK)); + + dev->gadget.speed = USB_SPEED_UNKNOWN; + + return 0; +} + +/* + Register entry point for the peripheral controller driver. +*/ +int usb_gadget_probe_driver(struct usb_gadget_driver *driver, + int (*bind)(struct usb_gadget *)) +{ + struct s3c_udc *dev = the_controller; + int retval; + + DEBUG_SETUP("%s: %s\n", __func__, driver->driver.name); + +/* + * adb composite fail to !driver->unbind in composite.c as below + * static struct usb_gadget_driver composite_driver = { + * .speed = USB_SPEED_HIGH, + * .bind = composite_bind, + * .unbind = __exit_p(composite_unbind), + */ + if (!driver + || (driver->speed != USB_SPEED_FULL && driver->speed != USB_SPEED_HIGH) + || !bind + || !driver->disconnect || !driver->setup) + return -EINVAL; + if (!dev) + return -ENODEV; + if (dev->driver) + return -EBUSY; + + /* first hook up the driver ... */ + dev->driver = driver; + /* initialize device status as self-powered.*/ + dev->status = 1 << USB_DEVICE_SELF_POWERED; + dev->gadget.dev.driver = &driver->driver; + retval = device_add(&dev->gadget.dev); + + if (retval) { + printk(KERN_ERR "target device_add failed, error %d\n", retval); + return retval; + } + + retval = bind(&dev->gadget); + if (retval) { + printk(KERN_ERR "%s: bind to driver %s --> error %d\n", + dev->gadget.name, driver->driver.name, retval); + device_del(&dev->gadget.dev); + + dev->driver = 0; + dev->gadget.dev.driver = 0; + return retval; + } + + enable_irq(IRQ_OTG); + + retval = usb_gadget_vbus_connect(&dev->gadget); + if (retval < 0) + dev_dbg(&dev->gadget.dev, "%s vbus connect %d\n", + __func__, retval); + + if (!(readl(S3C_UDC_OTG_GOTGCTL) & B_SESSION_VALID)) { + retval = usb_gadget_vbus_disconnect(&dev->gadget); + if (retval < 0) { + dev_dbg(&dev->gadget.dev, "%s vbus disconnect %d\n", + __func__, retval); + } + } + + printk(KERN_DEBUG "Registered gadget driver '%s'\n", + driver->driver.name); + + return 0; +} +EXPORT_SYMBOL(usb_gadget_probe_driver); + +/* + Unregister entry point for the peripheral controller driver. +*/ +int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) +{ + struct s3c_udc *dev = the_controller; + unsigned long flags; + + if (!dev) + return -ENODEV; + if (!driver || driver != dev->driver) + return -EINVAL; + + spin_lock_irqsave(&dev->lock, flags); + dev->driver = 0; + stop_activity(dev, driver); + spin_unlock_irqrestore(&dev->lock, flags); + + driver->unbind(&dev->gadget); + device_del(&dev->gadget.dev); + + disable_irq(IRQ_OTG); + + printk(KERN_DEBUG "Unregistered gadget driver '%s'\n", + driver->driver.name); + + udc_disable(dev); + + return 0; +} +EXPORT_SYMBOL(usb_gadget_unregister_driver); + +static int s3c_udc_power(struct s3c_udc *dev, char en) +{ + pr_debug("%s : %s\n", __func__, en ? "ON" : "OFF"); + + if (en) { + regulator_enable(dev->udc_vcc_d); + regulator_enable(dev->udc_vcc_a); + } else { + regulator_disable(dev->udc_vcc_d); + regulator_disable(dev->udc_vcc_a); + } + + return 0; +} + +int s3c_vbus_enable(struct usb_gadget *gadget, int enable) +{ + unsigned long flags; + struct s3c_udc *dev = the_controller; + + if (dev->udc_enabled != enable) { + dev->udc_enabled = enable; + if (!enable) { + spin_lock_irqsave(&dev->lock, flags); + stop_activity(dev, dev->driver); + spin_unlock_irqrestore(&dev->lock, flags); + udc_disable(dev); + clk_disable(otg_clock); + s3c_udc_power(dev, 0); + } else { + s3c_udc_power(dev, 1); + clk_enable(otg_clock); + udc_reinit(dev); + udc_enable(dev); + } + } else + dev_dbg(&dev->gadget.dev, "%s, udc : %d, en : %d\n", + __func__, dev->udc_enabled, enable); + + return 0; +} + +/* + * done - retire a request; caller blocked irqs + */ +static void done(struct s3c_ep *ep, struct s3c_request *req, int status) +{ + unsigned int stopped = ep->stopped; + struct device *dev = &the_controller->dev->dev; + DEBUG("%s: %s %p, req = %p, stopped = %d\n", + __func__, ep->ep.name, ep, &req->req, stopped); + + list_del_init(&req->queue); + + if (likely(req->req.status == -EINPROGRESS)) + req->req.status = status; + else + status = req->req.status; + + if (req->mapped) { + dma_unmap_single(dev, req->req.dma, req->req.length, + (ep->bEndpointAddress & USB_DIR_IN) ? + DMA_TO_DEVICE : DMA_FROM_DEVICE); + req->req.dma = DMA_ADDR_INVALID; + req->mapped = 0; + } + if (status && status != -ESHUTDOWN) { + DEBUG("complete %s req %p stat %d len %u/%u\n", + ep->ep.name, &req->req, status, + req->req.actual, req->req.length); + } + + /* don't modify queue heads during completion callback */ + ep->stopped = 1; + + spin_unlock(&ep->dev->lock); + req->req.complete(&ep->ep, &req->req); + spin_lock(&ep->dev->lock); + + ep->stopped = stopped; +} + +/* + * nuke - dequeue ALL requests + */ +static void nuke(struct s3c_ep *ep, int status) +{ + struct s3c_request *req; + + DEBUG("%s: %s %p\n", __func__, ep->ep.name, ep); + + /* called with irqs blocked */ + while (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, struct s3c_request, queue); + done(ep, req, status); + } +} + +static void stop_activity(struct s3c_udc *dev, + struct usb_gadget_driver *driver) +{ + int i; + + /* don't disconnect drivers more than once */ + if (dev->gadget.speed == USB_SPEED_UNKNOWN) + driver = 0; + dev->gadget.speed = USB_SPEED_UNKNOWN; + + /* prevent new request submissions, kill any outstanding requests */ + for (i = 0; i < S3C_MAX_ENDPOINTS; i++) { + struct s3c_ep *ep = &dev->ep[i]; + ep->stopped = 1; + nuke(ep, -ESHUTDOWN); + } + + /* report disconnect; the driver is already quiesced */ + if (driver) { + spin_unlock(&dev->lock); + driver->disconnect(&dev->gadget); + spin_lock(&dev->lock); + } + + /* re-init driver-visible data structures */ + udc_reinit(dev); +} + +static void reconfig_usbd(void) +{ + struct s3c_udc *dev = the_controller; + /* 2. Soft-reset OTG Core and then unreset again. */ +#ifdef DED_TX_FIFO + int i; +#endif + unsigned int uTemp; + writel(CORE_SOFT_RESET, S3C_UDC_OTG_GRSTCTL); + + writel(0<<15 /* PHY Low Power Clock sel*/ + |1<<14 /* Non-Periodic TxFIFO Rewind Enable*/ + |0x5<<10 /* Turnaround time*/ + |0<<9|0<<8 /* [0:HNP disable, 1:HNP enable][ 0:SRP disable, 1:SRP enable] H1= 1,1*/ + |0<<7 /* Ulpi DDR sel*/ + |0<<6 /* 0: high speed utmi+, 1: full speed serial*/ + |0<<4 /* 0: utmi+, 1:ulpi*/ + |1<<3 /* phy i/f 0:8bit, 1:16bit*/ + |0x7<<0, /* HS/FS Timeout**/ + S3C_UDC_OTG_GUSBCFG); + + /* 3. Put the OTG device core in the disconnected state.*/ + uTemp = readl(S3C_UDC_OTG_DCTL); + uTemp |= SOFT_DISCONNECT; + writel(uTemp, S3C_UDC_OTG_DCTL); + + udelay(20); + + /* 4. Make the OTG device core exit from the disconnected state.*/ + if (!dev->soft_disconnected) { + uTemp = readl(S3C_UDC_OTG_DCTL); + uTemp = uTemp & ~SOFT_DISCONNECT; + writel(uTemp, S3C_UDC_OTG_DCTL); + } + + /* 5. Configure OTG Core to initial settings of device mode.*/ + /* [][1: full speed(30Mhz) 0:high speed]*/ + writel(1<<18|0x0<<0, S3C_UDC_OTG_DCFG); + + mdelay(1); + + /* 6. Unmask the core interrupts*/ + writel(GINTMSK_INIT, S3C_UDC_OTG_GINTMSK); + + /* 7. Set NAK bit of EP0, EP1, EP2*/ + writel(DEPCTL_EPDIS|DEPCTL_SNAK|(0<<0), S3C_UDC_OTG_DOEPCTL(EP0_CON)); + writel(DEPCTL_EPDIS|DEPCTL_SNAK|(0<<0), S3C_UDC_OTG_DIEPCTL(EP0_CON)); + + /* 8. Unmask EPO interrupts*/ + writel(((1<<EP0_CON)<<DAINT_OUT_BIT)|(1<<EP0_CON), + S3C_UDC_OTG_DAINTMSK); + + /* 9. Unmask device OUT EP common interrupts*/ + writel(DOEPMSK_INIT, S3C_UDC_OTG_DOEPMSK); + + /* 10. Unmask device IN EP common interrupts*/ + writel(DIEPMSK_INIT, S3C_UDC_OTG_DIEPMSK); + + /* 11. Set Rx FIFO Size (in 32-bit words) */ + writel(RX_FIFO_SIZE, S3C_UDC_OTG_GRXFSIZ); + + /* 12. Set Non Periodic Tx FIFO Size*/ + writel((NPTX_FIFO_SIZE) << 16 | (NPTX_FIFO_START_ADDR) << 0, + S3C_UDC_OTG_GNPTXFSIZ); + +#ifdef DED_TX_FIFO + for (i = 1; i < S3C_MAX_ENDPOINTS; i++) + writel((PTX_FIFO_SIZE) << 16 | + (NPTX_FIFO_START_ADDR + NPTX_FIFO_SIZE + PTX_FIFO_SIZE*(i-1)) << 0, + S3C_UDC_OTG_DIEPTXF(i)); +#endif + + /* Flush the RX FIFO */ + writel(0x10, S3C_UDC_OTG_GRSTCTL); + while (readl(S3C_UDC_OTG_GRSTCTL) & 0x10) + ; + + /* Flush all the Tx FIFO's */ + writel(0x10<<6, S3C_UDC_OTG_GRSTCTL); + writel((0x10<<6)|0x20, S3C_UDC_OTG_GRSTCTL); + while (readl(S3C_UDC_OTG_GRSTCTL) & 0x20) + ; + + /* 13. Initialize OTG Link Core.*/ + writel(GAHBCFG_INIT, S3C_UDC_OTG_GAHBCFG); +} + +static void set_max_pktsize(struct s3c_udc *dev, enum usb_device_speed speed) +{ + unsigned int ep_ctrl; + int i; + if (speed == USB_SPEED_HIGH) { + ep0_fifo_size = 64; + ep_fifo_size = 512; + ep_fifo_size2 = 1024; + dev->gadget.speed = USB_SPEED_HIGH; + } else { + ep0_fifo_size = 64; + ep_fifo_size = 64; + ep_fifo_size2 = 64; + dev->gadget.speed = USB_SPEED_FULL; + } + + dev->ep[0].ep.maxpacket = ep0_fifo_size; + for (i = 1; i < S3C_MAX_ENDPOINTS; i++) { + /* fullspeed limitations don't apply to isochronous endpoints */ + if (dev->ep[i].bmAttributes != USB_ENDPOINT_XFER_ISOC) + dev->ep[i].ep.maxpacket = ep_fifo_size; + } + + /* EP0 - Control IN (64 bytes)*/ + ep_ctrl = readl(S3C_UDC_OTG_DIEPCTL(EP0_CON)); + writel(ep_ctrl|(0<<0), S3C_UDC_OTG_DIEPCTL(EP0_CON)); + + /* EP0 - Control OUT (64 bytes)*/ + ep_ctrl = readl(S3C_UDC_OTG_DOEPCTL(EP0_CON)); + writel(ep_ctrl|(0<<0), S3C_UDC_OTG_DOEPCTL(EP0_CON)); +} + +static int s3c_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct s3c_ep *ep; + struct s3c_udc *dev; + unsigned long flags; + + DEBUG("%s: %p\n", __func__, _ep); + + ep = container_of(_ep, struct s3c_ep, ep); + if (!_ep || !desc || ep->desc || _ep->name == ep0name + || desc->bDescriptorType != USB_DT_ENDPOINT + || ep->bEndpointAddress != desc->bEndpointAddress + || ep_maxpacket(ep) < le16_to_cpu(desc->wMaxPacketSize)) { + + DEBUG("%s: bad ep or descriptor\n", __func__); + return -EINVAL; + } + + /* xfer types must match, except that interrupt ~= bulk */ + if (ep->bmAttributes != + (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) + && ep->bmAttributes != USB_ENDPOINT_XFER_BULK + && desc->bmAttributes != USB_ENDPOINT_XFER_INT) { + + DEBUG("%s: %s type mismatch\n", __func__, _ep->name); + return -EINVAL; + } + + /* hardware _could_ do smaller, but driver doesn't */ + if ((desc->bmAttributes == USB_ENDPOINT_XFER_BULK + && le16_to_cpu(desc->wMaxPacketSize) != ep_maxpacket(ep)) + || !desc->wMaxPacketSize) { + + DEBUG("%s: bad %s maxpacket\n", __func__, _ep->name); + return -ERANGE; + } + + dev = ep->dev; + if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) { + + DEBUG("%s: bogus device state\n", __func__); + return -ESHUTDOWN; + } + + ep->stopped = 0; + ep->desc = desc; + ep->pio_irqs = 0; + ep->ep.maxpacket = le16_to_cpu(desc->wMaxPacketSize); + + /* Reset halt state */ + s3c_udc_set_halt(_ep, 0); + + spin_lock_irqsave(&ep->dev->lock, flags); + s3c_udc_ep_activate(ep); + spin_unlock_irqrestore(&ep->dev->lock, flags); + + DEBUG("%s: enabled %s, stopped = %d, maxpacket = %d\n", + __func__, _ep->name, ep->stopped, ep->ep.maxpacket); + return 0; +} + +/** Disable EP + */ +static int s3c_ep_disable(struct usb_ep *_ep) +{ + struct s3c_ep *ep; + unsigned long flags; + + DEBUG("%s: %p\n", __func__, _ep); + + ep = container_of(_ep, struct s3c_ep, ep); + if (!_ep || !ep->desc) { + DEBUG("%s: %s not enabled\n", __func__, + _ep ? ep->ep.name : NULL); + return -EINVAL; + } + + spin_lock_irqsave(&ep->dev->lock, flags); + + /* Nuke all pending requests */ + nuke(ep, -ESHUTDOWN); + + ep->desc = 0; + ep->stopped = 1; + + spin_unlock_irqrestore(&ep->dev->lock, flags); + + DEBUG("%s: disabled %s\n", __func__, _ep->name); + return 0; +} + +static struct usb_request *s3c_alloc_request(struct usb_ep *ep, + gfp_t gfp_flags) +{ + struct s3c_request *req; + + DEBUG("%s: %s %p\n", __func__, ep->name, ep); + + req = kzalloc(sizeof(*req), gfp_flags); + if (req) + INIT_LIST_HEAD(&req->queue); + + return &req->req; +} + +static void s3c_free_request(struct usb_ep *ep, struct usb_request *_req) +{ + struct s3c_request *req = container_of(_req, struct s3c_request, req); + DEBUG("%s: %p\n", __func__, ep); + kfree(req); +} + +/* dequeue JUST ONE request */ +static int s3c_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct s3c_ep *ep; + struct s3c_request *req; + unsigned long flags; + + DEBUG("%s: %p\n", __func__, _ep); + + ep = container_of(_ep, struct s3c_ep, ep); + if (!_ep || ep->ep.name == ep0name) + return -EINVAL; + + spin_lock_irqsave(&ep->dev->lock, flags); + + /* make sure it's actually queued on this endpoint */ + list_for_each_entry(req, &ep->queue, queue) { + if (&req->req == _req) + break; + } + if (&req->req != _req) { + spin_unlock_irqrestore(&ep->dev->lock, flags); + return -EINVAL; + } + + done(ep, req, -ECONNRESET); + + spin_unlock_irqrestore(&ep->dev->lock, flags); + return 0; +} + +/** Return bytes in EP FIFO + */ +static int s3c_fifo_status(struct usb_ep *_ep) +{ + int count = 0; + struct s3c_ep *ep; + + ep = container_of(_ep, struct s3c_ep, ep); + if (!_ep) { + DEBUG("%s: bad ep\n", __func__); + return -ENODEV; + } + + DEBUG("%s: %d\n", __func__, ep_index(ep)); + + /* LPD can't report unclaimed bytes from IN fifos */ + if (ep_is_in(ep)) + return -EOPNOTSUPP; + + return count; +} + +/** Flush EP FIFO + */ +static void s3c_fifo_flush(struct usb_ep *_ep) +{ + struct s3c_ep *ep; + + ep = container_of(_ep, struct s3c_ep, ep); + if (unlikely(!_ep || (!ep->desc && ep->ep.name != ep0name))) { + DEBUG("%s: bad ep\n", __func__); + return; + } + + DEBUG("%s: %d\n", __func__, ep_index(ep)); +} + +/* --------------------------------------------------------------------------- + * device-scoped parts of the api to the usb controller hardware + * --------------------------------------------------------------------------- + */ + +static int s3c_udc_get_frame(struct usb_gadget *_gadget) +{ + unsigned int frame; + DEBUG("%s: %p\n", __func__, _gadget); + /*fram count number [21:8]*/ + frame = readl(S3C_UDC_OTG_DSTS); + frame &= SOFFN_MASK; + frame >>= SOFFN_SHIFT; + return frame; +} + +static int s3c_udc_wakeup(struct usb_gadget *_gadget) +{ + unsigned int dev_gctl, dev_dctl, dev_status; + unsigned long flags; + int retVal = -EINVAL; + struct s3c_udc *dev = the_controller; + + DEBUG("%s: %p\n", __func__, _gadget); + + if (!_gadget) + return -ENODEV; + + spin_lock_irqsave(&dev->lock, flags); + + if (!(dev->status & (1 << USB_DEVICE_REMOTE_WAKEUP))) { + DEBUG_SETUP("%s::Remote Wakeup is not set\n", __func__); + goto wakeup_exit; + } + /* check for session */ + dev_gctl = readl(S3C_UDC_OTG_GOTGCTL); + if (dev_gctl & B_SESSION_VALID) { + /* check for suspend state */ + dev_status = readl(S3C_UDC_OTG_DSTS); + if (dev_status & USB_SUSPEND) { + DEBUG_SETUP("%s:: Set Remote wakeup\n", __func__); + dev_dctl = readl(S3C_UDC_OTG_DCTL); + dev_dctl |= REMOTE_WAKEUP; + writel(dev_dctl, S3C_UDC_OTG_DCTL); + mdelay(1); + DEBUG_SETUP("%s:: Clear Remote Wakeup\n", __func__); + dev_dctl = readl(S3C_UDC_OTG_DCTL); + dev_dctl &= (~REMOTE_WAKEUP); + writel(dev_dctl, S3C_UDC_OTG_DCTL); + } else { + DEBUG_SETUP("%s:: already woke up\n", __func__); + } + + } else if (dev_gctl & SESSION_REQ) { + DEBUG_SETUP("%s:: Session request already active\n", __func__); + goto wakeup_exit; + } + + retVal = 0; +wakeup_exit: + spin_unlock_irqrestore(&dev->lock, flags); + return retVal; +} + +void s3c_udc_soft_connect(void) +{ + struct s3c_udc *dev = the_controller; + unsigned long flags; + u32 uTemp; + + DEBUG("[%s]\n", __func__); + + spin_lock_irqsave(&dev->lock, flags); + + uTemp = readl(S3C_UDC_OTG_DCTL); + uTemp = uTemp & ~SOFT_DISCONNECT; + writel(uTemp, S3C_UDC_OTG_DCTL); + udelay(20); + + reset_available = 1; + + /* Unmask the core interrupt */ + writel(readl(S3C_UDC_OTG_GINTSTS), S3C_UDC_OTG_GINTSTS); + writel(GINTMSK_INIT, S3C_UDC_OTG_GINTMSK); + + dev->soft_disconnected = 0; + spin_unlock_irqrestore(&dev->lock, flags); +} + +void s3c_udc_soft_disconnect(void) +{ + u32 uTemp; + struct s3c_udc *dev = the_controller; + unsigned long flags; + + DEBUG("[%s]\n", __func__); + + spin_lock_irqsave(&dev->lock, flags); + + /* Mask the core interrupt */ + writel(0, S3C_UDC_OTG_GINTMSK); + + uTemp = readl(S3C_UDC_OTG_DCTL); + uTemp |= SOFT_DISCONNECT; + writel(uTemp, S3C_UDC_OTG_DCTL); + + stop_activity(dev, dev->driver); + + dev->soft_disconnected = 1; + spin_unlock_irqrestore(&dev->lock, flags); +} + +static int s3c_udc_pullup(struct usb_gadget *gadget, int is_on) +{ + if (is_on) + s3c_udc_soft_connect(); + else + s3c_udc_soft_disconnect(); + return 0; +} + + +static const struct usb_gadget_ops s3c_udc_ops = { + .get_frame = s3c_udc_get_frame, + .wakeup = s3c_udc_wakeup, + /* current versions must always be self-powered */ + .pullup = s3c_udc_pullup, + .vbus_session = s3c_vbus_enable, +}; + +static void nop_release(struct device *dev) +{ + DEBUG("%s\n", __func__); +} + +static struct s3c_udc memory = { + .usb_address = 0, + + .gadget = { + .ops = &s3c_udc_ops, + .ep0 = &memory.ep[0].ep, + .name = driver_name, + .dev = { + .release = nop_release, + }, + }, + + /* control endpoint */ + .ep[0] = { + .ep = { + .name = ep0name, + .ops = &s3c_ep_ops, + .maxpacket = EP0_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = 0, + .bmAttributes = 0, + + .ep_type = ep_control, + .fifo = (unsigned int) S3C_UDC_OTG_EP0_FIFO, + }, + + /* first group of endpoints */ + .ep[1] = { + .ep = { + .name = "ep1-bulk", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_OUT | 1, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + + .ep_type = ep_bulk_out, + .fifo = (unsigned int) S3C_UDC_OTG_EP1_FIFO, + }, + + .ep[2] = { + .ep = { + .name = "ep2-bulk", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_IN | 2, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + + .ep_type = ep_bulk_in, + .fifo = (unsigned int) S3C_UDC_OTG_EP2_FIFO, + }, + + .ep[3] = { + .ep = { + .name = "ep3-int", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_IN | 3, + .bmAttributes = USB_ENDPOINT_XFER_INT, + + .ep_type = ep_interrupt, + .fifo = (unsigned int) S3C_UDC_OTG_EP3_FIFO, + }, + .ep[4] = { + .ep = { + .name = "ep4-bulk", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_OUT | 4, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + + .ep_type = ep_bulk_out, + .fifo = (unsigned int) S3C_UDC_OTG_EP4_FIFO, + }, + .ep[5] = { + .ep = { + .name = "ep5-bulk", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_IN | 5, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + + .ep_type = ep_bulk_in, + .fifo = (unsigned int) S3C_UDC_OTG_EP5_FIFO, + }, + .ep[6] = { + .ep = { + .name = "ep6-int", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_IN | 6, + .bmAttributes = USB_ENDPOINT_XFER_INT, + + .ep_type = ep_interrupt, + .fifo = (unsigned int) S3C_UDC_OTG_EP6_FIFO, + }, + .ep[7] = { + .ep = { + .name = "ep7-bulk", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_OUT | 7, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + + .ep_type = ep_bulk_out, + .fifo = (unsigned int) S3C_UDC_OTG_EP7_FIFO, + }, + .ep[8] = { + .ep = { + .name = "ep8-bulk", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_IN | 8, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + + .ep_type = ep_bulk_in, + .fifo = (unsigned int) S3C_UDC_OTG_EP8_FIFO, + }, + .ep[9] = { + .ep = { + .name = "ep9-int", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_IN | 9, + .bmAttributes = USB_ENDPOINT_XFER_INT, + + .ep_type = ep_interrupt, + .fifo = (unsigned int) S3C_UDC_OTG_EP9_FIFO, + }, + .ep[10] = { + .ep = { + .name = "ep10-bulk", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_OUT | 0xa, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + + .ep_type = ep_bulk_out, + .fifo = (unsigned int) S3C_UDC_OTG_EP10_FIFO, + }, + .ep[11] = { + .ep = { + .name = "ep11-bulk", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_IN | 0xb, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + + .ep_type = ep_bulk_in, + .fifo = (unsigned int) S3C_UDC_OTG_EP11_FIFO, + }, + .ep[12] = { + .ep = { + .name = "ep12-int", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_IN | 0xc, + .bmAttributes = USB_ENDPOINT_XFER_INT, + + .ep_type = ep_interrupt, + .fifo = (unsigned int) S3C_UDC_OTG_EP12_FIFO, + }, + .ep[13] = { + .ep = { + .name = "ep13-bulk", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_OUT | 0xd, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + + .ep_type = ep_bulk_out, + .fifo = (unsigned int) S3C_UDC_OTG_EP13_FIFO, + }, + .ep[14] = { + .ep = { + .name = "ep14-bulk", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_IN | 0xe, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + + .ep_type = ep_bulk_in, + .fifo = (unsigned int) S3C_UDC_OTG_EP14_FIFO, + }, + .ep[15] = { + .ep = { + .name = "ep15-iso", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_IN | 0xf, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + + .ep_type = ep_isochronous, + .fifo = (unsigned int) S3C_UDC_OTG_EP15_FIFO, + }, +}; + +#if defined CONFIG_USB_S3C_OTG_HOST || defined CONFIG_USB_DWC_OTG +atomic_t g_OtgHostMode; // actual mode: client (0) or host (1) +atomic_t g_OtgOperationMode; // operation mode: 'c'lient, 'h'ost, 'o'tg or 'a'uto-host +atomic_t g_OtgLastCableState; // last cable state: detached (0), client attached (1), otg attached (2) +atomic_t g_OtgDriver; // driver to use: 0: S3C High-speed, 1: S3C Low-speed/Full-speed, 2: DWC + +int s3c_is_otgmode(void) +{ + printk("otgmode = %d\n", atomic_read(&g_OtgHostMode)); + return atomic_read(&g_OtgHostMode); +} +EXPORT_SYMBOL(s3c_is_otgmode); + +int s3c_get_drivermode(void) +{ + printk("drivermode = %d\n", atomic_read(&g_OtgDriver)); + return atomic_read(&g_OtgDriver); +} +EXPORT_SYMBOL(s3c_get_drivermode); + +void set_otghost_mode(int mode) { +//sztupy: +// this function will determine what to do with the new information according to the current mode of operation +// mode: 0: cable detached, 1: client cable attached, 2: otg cable attached, -1: operation config changed +// modes of operation: c: always client, h: always host, g: otg mode (host if otg cable), a: automatic mode (host if cable plugged in) + + struct s3c_udc *dev = the_controller; + int enable = 0; + int rval; + char opmode = atomic_read(&g_OtgOperationMode); + if (mode==-1) mode = atomic_read(&g_OtgLastCableState); + atomic_set(&g_OtgLastCableState, mode); + switch (opmode) { + case 'h': enable = 1; break; + case 'o': enable = (mode==2); break; + case 'a': enable = (mode>0); break; + default: atomic_set(&g_OtgOperationMode,'c'); enable = 0; break; + } + + if (enable && !atomic_read(&g_OtgHostMode)) { + printk("Setting OTG host mode\n"); + free_irq(IRQ_OTG, dev); + s3c_vbus_enable(&dev->gadget, 1); + +#if defined CONFIG_USB_S3C_OTG_HOST && defined CONFIG_USB_DWC_OTG + if (atomic_read(&g_OtgDriver) & USB_OTG_DRIVER_DWC) { + rval = platform_driver_register(&dwc_otg_driver); + } else { + rval = platform_driver_register(&s5pc110_otg_driver); + } + if (rval < 0) +#elif defined CONFIG_USB_DWC_OTG + if (platform_driver_register(&dwc_otg_driver) < 0) +#elif defined CONFIG_USB_S3C_OTG_HOST + if (platform_driver_register(&s5pc110_otg_driver) < 0) +#endif + { + printk("platform_driver_register failed...\n"); + atomic_set(&g_OtgHostMode , 0); + } else { + printk("platform_driver_registered\n"); + atomic_set(&g_OtgHostMode , atomic_read(&g_OtgDriver)+1); + } + } else if (!enable && atomic_read(&g_OtgHostMode)) { +// sztupy: also handle the disabling here + printk("Disabling OTG host mode\n"); + s3c_vbus_enable(&dev->gadget, 0); +#if defined CONFIG_USB_S3C_OTG_HOST && defined CONFIG_USB_DWC_OTG + if ((atomic_read(&g_OtgHostMode)-1) & USB_OTG_DRIVER_DWC) { + platform_driver_unregister(&dwc_otg_driver); + } else { + platform_driver_unregister(&s5pc110_otg_driver); + } +#elif defined CONFIG_USB_S3C_OTG_HOST + platform_driver_unregister(&s5pc110_otg_driver); +#elif defined CONFIG_USB_DWC_OTG + platform_driver_unregister(&dwc_otg_driver); +#endif + printk("platform_driver_unregistered\n"); + /* irq setup after old hardware state is cleaned up */ + if (request_irq(IRQ_OTG, s3c_udc_irq, 0, driver_name, dev)) { + printk("Warning: Could not request IRQ for USB gadget!\n"); + } + atomic_set(&g_OtgHostMode , 0); + } else { + printk("OTG: no changes needed\n"); + } +} + +EXPORT_SYMBOL(set_otghost_mode); + +static ssize_t usbmode_read(struct device *dev, struct device_attribute *attr, char *buf) +{ + const char *msg = "client"; + const char *msg2 = "disconnected"; + const char *msg3 = "gadget"; + switch(atomic_read(&g_OtgOperationMode)) { + case 'o': msg = "otg"; break; + case 'h': msg = "host"; break; + case 'a': msg = "auto-host"; break; + } + switch(atomic_read(&g_OtgLastCableState)) { + case 1: msg2 = "usb connected"; break; + case 2: msg2 = "otg connected"; break; + } + switch(atomic_read(&g_OtgHostMode)) { + case USB_OTG_DRIVER_S3CHS: msg3 = "host (S3C/HS)"; break; + case USB_OTG_DRIVER_S3CFSLS: msg3 = "host (S3C/FSLS)"; break; + case USB_OTG_DRIVER_DWC: msg3 = "host (DWC)"; break; + } + + return sprintf(buf,"%s (cable: %s; state: %s)\n", msg, msg2, msg3); +} + +static ssize_t usbmode_write(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) +{ + char c; + printk("input data --> %s", buf); + c = buf[0]; + switch(c) { + case 'a': atomic_set(&g_OtgOperationMode, 'a'); set_otghost_mode(-1);break; + case 'h': atomic_set(&g_OtgOperationMode, 'h'); set_otghost_mode(-1);break; + case 'c': atomic_set(&g_OtgOperationMode, 'c'); set_otghost_mode(-1);break; + case 'o': atomic_set(&g_OtgOperationMode, 'o'); set_otghost_mode(-1);break; + default: printk("Invalid input data\n"); + } + return size; +} + +static ssize_t usbdriver_read(struct device *dev, struct device_attribute *attr, char *buf) { + const char *msg = "h:S3C driver (high-speed)"; + switch(atomic_read(&g_OtgDriver)) { + case USB_OTG_DRIVER_S3CFSLS: msg = "l:S3C driver (low-speed / full-speed)";break; + case USB_OTG_DRIVER_DWC: msg = "d:DWC driver";break; + } + return sprintf(buf,"%s\n",msg); +} + +static ssize_t usbdriver_write(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { + char c; + printk("input data --> %s", buf); + c = buf[0]; + switch(c) { +#if defined CONFIG_USB_S3C_OTG_HOST + case 'h': atomic_set(&g_OtgDriver, USB_OTG_DRIVER_S3CHS); break; + case 'l': atomic_set(&g_OtgDriver, USB_OTG_DRIVER_S3CFSLS); break; +#endif +#if defined CONFIG_USB_DWC_OTG + case 'd': atomic_set(&g_OtgDriver, USB_OTG_DRIVER_DWC); break; +#endif + default: printk("Invalid input data\n"); + } + return size; + +} + +static ssize_t usbversion_read(struct device *dev, struct device_attribute *attr, char *buf) { + return sprintf(buf,"version: build 5\ndrivers:%s%s\n", +#if defined CONFIG_USB_S3C_OTG_HOST + " S3CHS S3CFSLS" +#else + "" +#endif + , +#if defined CONFIG_USB_DWC_OTG + " DWC" +#else + "" +#endif + ); +} + +// kevinh - Allow changing USB host/target modes on S3C android devices without a special cable +static DEVICE_ATTR(opmode, S_IRUGO | S_IWUSR, usbmode_read, usbmode_write); +// sztupy - add some more attributes +static DEVICE_ATTR(hostdriver, S_IRUGO | S_IWUSR, usbdriver_read, usbdriver_write); +static DEVICE_ATTR(version, S_IRUGO, usbversion_read, NULL); +#endif + +/* + * probe - binds to the platform device + */ +static int s3c_udc_probe(struct platform_device *pdev) +{ + struct s3c_udc *dev = &memory; + int retval; + DEBUG("%s: %p\n", __func__, pdev); + + spin_lock_init(&dev->lock); + dev->dev = pdev; + + device_initialize(&dev->gadget.dev); + dev->gadget.dev.parent = &pdev->dev; + + dev->gadget.is_dualspeed = 1; /* Hack only*/ + dev->gadget.is_otg = 0; + dev->gadget.is_a_peripheral = 0; + dev->gadget.b_hnp_enable = 0; + dev->gadget.a_hnp_support = 0; + dev->gadget.a_alt_hnp_support = 0; + + dev->udc_vcc_d = regulator_get(&pdev->dev, "pd_io"); + dev->udc_vcc_a = regulator_get(&pdev->dev, "pd_core"); + if (IS_ERR(dev->udc_vcc_d) || IS_ERR(dev->udc_vcc_a)) { + printk(KERN_ERR "failed to find udc vcc source\n"); + return -ENOENT; + } + + the_controller = dev; + platform_set_drvdata(pdev, dev); + + dev_set_name(&dev->gadget.dev, "gadget"); + otg_clock = clk_get(&pdev->dev, "otg"); + if (otg_clock == NULL) { + printk(KERN_ERR "failed to find otg clock source\n"); + return -ENOENT; + } + + udc_reinit(dev); + + /* irq setup after old hardware state is cleaned up */ + retval = + request_irq(IRQ_OTG, s3c_udc_irq, 0, driver_name, dev); + + if (retval != 0) { + DEBUG(KERN_ERR "%s: can't get irq %i, err %d\n", driver_name, + IRQ_OTG, retval); + return -EBUSY; + } + + disable_irq(IRQ_OTG); + create_proc_files(); +#if defined CONFIG_USB_S3C_OTG_HOST || defined CONFIG_USB_DWC_OTG + if (device_create_file(&pdev->dev, &dev_attr_opmode) < 0) + printk("Failed to create device file(%s)!\n", dev_attr_opmode.attr.name); + if (device_create_file(&pdev->dev, &dev_attr_hostdriver) < 0) + printk("Failed to create device file(%s)!\n", dev_attr_hostdriver.attr.name); + if (device_create_file(&pdev->dev, &dev_attr_version) < 0) + printk("Failed to create device file(%s)!\n", dev_attr_version.attr.name); +#if !defined CONFIG_USB_S3C_OTG_HOST + atomic_set(&g_OtgDriver, USB_OTG_DRIVER_DWC); +#endif +#endif + return retval; +} + +static int s3c_udc_remove(struct platform_device *pdev) +{ + struct s3c_udc *dev = platform_get_drvdata(pdev); + + DEBUG("%s: %p\n", __func__, pdev); + + if (otg_clock != NULL) { + clk_disable(otg_clock); + clk_put(otg_clock); + otg_clock = NULL; + } + + remove_proc_files(); + usb_gadget_unregister_driver(dev->driver); + + free_irq(IRQ_OTG, dev); + + platform_set_drvdata(pdev, 0); + + the_controller = 0; + + return 0; +} + +#ifdef CONFIG_PM +static int s3c_udc_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct s3c_udc *dev = the_controller; + + disable_irq(IRQ_OTG); + + if (dev->driver && dev->driver->suspend) + dev->driver->suspend(&dev->gadget); + + if (dev->udc_enabled) + usb_gadget_vbus_disconnect(&dev->gadget); + + return 0; +} + +static int s3c_udc_resume(struct platform_device *pdev) +{ + struct s3c_udc *dev = the_controller; + + if (dev->driver && dev->driver->resume) + dev->driver->resume(&dev->gadget); + + enable_irq(IRQ_OTG); + + return 0; +} +#else +#define s3c_udc_suspend NULL +#define s3c_udc_resume NULL +#endif /* CONFIG_PM */ + +/*-------------------------------------------------------------------------*/ +static struct platform_driver s3c_udc_driver = { + .probe = s3c_udc_probe, + .remove = s3c_udc_remove, + .suspend = s3c_udc_suspend, + .resume = s3c_udc_resume, + .driver = { + .owner = THIS_MODULE, + .name = "s3c-usbgadget", + }, +}; + +static int __init udc_init(void) +{ + int ret; + + ret = platform_driver_register(&s3c_udc_driver); + if (!ret) + printk(KERN_INFO "%s : %s\n" + "%s : version %s %s\n", + driver_name, DRIVER_DESC, + driver_name, DRIVER_VERSION, OTG_DMA_MODE ? + "(DMA Mode)" : "(Slave Mode)"); + + return ret; +} + +static void __exit udc_exit(void) +{ + platform_driver_unregister(&s3c_udc_driver); + printk(KERN_INFO "Unloaded %s version %s\n", + driver_name, DRIVER_VERSION); +} + +module_init(udc_init); +module_exit(udc_exit); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Samsung"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/s3c_udc_otg_xfer_dma.c b/drivers/usb/gadget/s3c_udc_otg_xfer_dma.c new file mode 100644 index 0000000..70cf602 --- /dev/null +++ b/drivers/usb/gadget/s3c_udc_otg_xfer_dma.c @@ -0,0 +1,1387 @@ +/* + * drivers/usb/gadget/s3c_udc_otg_xfer_dma.c + * Samsung S3C on-chip full/high speed USB OTG 2.0 device controllers + * + * Copyright (C) 2009 for Samsung Electronics + * + * + * 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 GINTMSK_INIT (INT_OUT_EP|INT_IN_EP|INT_RESUME|INT_ENUMDONE|INT_RESET|INT_SUSPEND) +#define DOEPMSK_INIT (CTRL_OUT_EP_SETUP_PHASE_DONE|AHB_ERROR|TRANSFER_DONE) +#define DIEPMSK_INIT (NON_ISO_IN_EP_TIMEOUT|AHB_ERROR|TRANSFER_DONE) +#define GAHBCFG_INIT (PTXFE_HALF|NPTXFE_HALF|MODE_DMA|BURST_INCR4|GBL_INT_UNMASK) + +#define DMA_ADDR_INVALID (~(dma_addr_t)0) + +static u8 clear_feature_num; +static int clear_feature_flag; +static int set_conf_done; + +/* Bulk-Only Mass Storage Reset (class-specific request) */ +#define GET_MAX_LUN_REQUEST 0xFE +#define BOT_RESET_REQUEST 0xFF + +/* TEST MODE in set_feature request */ +#define TEST_SELECTOR_MASK 0xFF +#define TEST_PKT_SIZE 53 + +static u8 test_pkt[TEST_PKT_SIZE] __attribute__((aligned(8))) = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* JKJKJKJK x 9 */ + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, /* JJKKJJKK x 8 */ + 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, /* JJJJKKKK x 8 */ + 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* JJJJJJJKKKKKKK x 8 - '1' */ + 0x7F, 0xBF, 0xDF, 0xEF, 0xF7, 0xFB, 0xFD, /* '1' + JJJJJJJK x 8 */ + 0xFC, 0x7E, 0xBF, 0xDF, 0xEF, 0xF7, 0xFB, 0xFD, 0x7E /* {JKKKKKKK x 10}, JK */ +}; + +void s3c_udc_ep_set_stall(struct s3c_ep *ep); + +#if defined(CONFIG_MACH_SMDKC110) || defined(CONFIG_MACH_SMDKV210) +extern void s3c_cable_check_status(int flag); + +void s3c_udc_cable_connect(struct s3c_udc *dev) +{ + s3c_cable_check_status(1); +} + +void s3c_udc_cable_disconnect(struct s3c_udc *dev) +{ + s3c_cable_check_status(0); +} +#endif + +static inline void s3c_udc_ep0_zlp(void) +{ + u32 ep_ctrl; + + writel(virt_to_phys(&usb_ctrl), S3C_UDC_OTG_DIEPDMA(EP0_CON)); + writel((1<<19 | 0<<0), S3C_UDC_OTG_DIEPTSIZ(EP0_CON)); + + ep_ctrl = readl(S3C_UDC_OTG_DIEPCTL(EP0_CON)); + writel(ep_ctrl|DEPCTL_EPENA|DEPCTL_CNAK, S3C_UDC_OTG_DIEPCTL(EP0_CON)); + + DEBUG_EP0("%s:EP0 ZLP DIEPCTL0 = 0x%x\n", + __func__, readl(S3C_UDC_OTG_DIEPCTL(EP0_CON))); +} + +static inline void s3c_udc_pre_setup(void) +{ + u32 ep_ctrl; + + DEBUG_IN_EP("%s : Prepare Setup packets.\n", __func__); + + writel((1 << 19)|sizeof(struct usb_ctrlrequest), S3C_UDC_OTG_DOEPTSIZ(EP0_CON)); + writel(virt_to_phys(&usb_ctrl), S3C_UDC_OTG_DOEPDMA(EP0_CON)); + + ep_ctrl = readl(S3C_UDC_OTG_DOEPCTL(EP0_CON)); + writel(ep_ctrl|DEPCTL_EPENA|DEPCTL_CNAK, S3C_UDC_OTG_DOEPCTL(EP0_CON)); +} + +static int setdma_rx(struct s3c_ep *ep, struct s3c_request *req) +{ + u32 *buf, ctrl; + u32 length, pktcnt; + u32 ep_num = ep_index(ep); + + struct device *dev = &the_controller->dev->dev; + buf = req->req.buf + req->req.actual; + prefetchw(buf); + + length = req->req.length - req->req.actual; + req->req.dma = dma_map_single(dev, buf, + length, DMA_FROM_DEVICE); + req->mapped = 1; + + if (length == 0) + pktcnt = 1; + else + pktcnt = (length - 1)/(ep->ep.maxpacket) + 1; + + ctrl = readl(S3C_UDC_OTG_DOEPCTL(ep_num)); + + writel(virt_to_phys(buf), S3C_UDC_OTG_DOEPDMA(ep_num)); + writel((pktcnt<<19)|(length<<0), S3C_UDC_OTG_DOEPTSIZ(ep_num)); + writel(DEPCTL_EPENA|DEPCTL_CNAK|ctrl, S3C_UDC_OTG_DOEPCTL(ep_num)); + + DEBUG_OUT_EP("%s: EP%d RX DMA start : DOEPDMA = 0x%x, DOEPTSIZ = 0x%x, DOEPCTL = 0x%x\n" + "\tbuf = 0x%p, pktcnt = %d, xfersize = %d\n", + __func__, ep_num, + readl(S3C_UDC_OTG_DOEPDMA(ep_num)), + readl(S3C_UDC_OTG_DOEPTSIZ(ep_num)), + readl(S3C_UDC_OTG_DOEPCTL(ep_num)), + buf, pktcnt, length); + return 0; + +} + +static int setdma_tx(struct s3c_ep *ep, struct s3c_request *req) +{ + u32 *buf, ctrl = 0; + u32 length, pktcnt; + u32 ep_num = ep_index(ep); + struct device *dev = &the_controller->dev->dev; + + buf = req->req.buf + req->req.actual; + prefetch(buf); + length = req->req.length - req->req.actual; + + if (ep_num == EP0_CON) + length = min(length, (u32)ep_maxpacket(ep)); + + req->req.actual += length; + req->req.dma = dma_map_single(dev, buf, + length, DMA_TO_DEVICE); + req->mapped = 1; + + if (length == 0) + pktcnt = 1; + else + pktcnt = (length - 1)/(ep->ep.maxpacket) + 1; + +#ifdef DED_TX_FIFO + /* Write the FIFO number to be used for this endpoint */ + ctrl = readl(S3C_UDC_OTG_DIEPCTL(ep_num)); + ctrl &= ~DEPCTL_TXFNUM_MASK;; + ctrl |= (ep_num << DEPCTL_TXFNUM_BIT); + writel(ctrl , S3C_UDC_OTG_DIEPCTL(ep_num)); +#endif + + writel(virt_to_phys(buf), S3C_UDC_OTG_DIEPDMA(ep_num)); + writel((pktcnt<<19)|(length<<0), S3C_UDC_OTG_DIEPTSIZ(ep_num)); + ctrl = readl(S3C_UDC_OTG_DIEPCTL(ep_num)); + if (ep->bmAttributes == USB_ENDPOINT_XFER_ISOC) + ctrl |= DEPCTL_SET_ODD_FRM; + writel(DEPCTL_EPENA|DEPCTL_CNAK|ctrl, S3C_UDC_OTG_DIEPCTL(ep_num)); + + DEBUG_IN_EP("%s:EP%d TX DMA start : DIEPDMA0 = 0x%x, DIEPTSIZ0 = 0x%x, DIEPCTL0 = 0x%x\n" + "\tbuf = 0x%p, pktcnt = %d, xfersize = %d\n", + __func__, ep_num, + readl(S3C_UDC_OTG_DIEPDMA(ep_num)), + readl(S3C_UDC_OTG_DIEPTSIZ(ep_num)), + readl(S3C_UDC_OTG_DIEPCTL(ep_num)), + buf, pktcnt, length); + + return length; +} + +static void complete_rx(struct s3c_udc *dev, u8 ep_num) +{ + struct s3c_ep *ep = &dev->ep[ep_num]; + struct s3c_request *req = NULL; + u32 ep_tsr = 0, xfer_size = 0, xfer_length, is_short = 0; + + if (list_empty(&ep->queue)) { + DEBUG_OUT_EP("%s: RX DMA done : NULL REQ on OUT EP-%d\n", + __func__, ep_num); + return; + + } + + req = list_entry(ep->queue.next, struct s3c_request, queue); + + ep_tsr = readl(S3C_UDC_OTG_DOEPTSIZ(ep_num)); + + if (ep_num == EP0_CON) + xfer_size = (ep_tsr & 0x7f); + + else + xfer_size = (ep_tsr & 0x7fff); + + __dma_single_cpu_to_dev(req->req.buf, req->req.length, DMA_FROM_DEVICE); + xfer_length = req->req.length - xfer_size; + req->req.actual += min(xfer_length, req->req.length - req->req.actual); + is_short = (xfer_length < ep->ep.maxpacket); + + DEBUG_OUT_EP("%s: RX DMA done : ep = %d, rx bytes = %d/%d, " + "is_short = %d, DOEPTSIZ = 0x%x, remained bytes = %d\n", + __func__, ep_num, req->req.actual, req->req.length, + is_short, ep_tsr, xfer_size); + + if (is_short || req->req.actual == xfer_length) { + if (ep_num == EP0_CON && dev->ep0state == DATA_STATE_RECV) { + DEBUG_OUT_EP(" => Send ZLP\n"); + dev->ep0state = WAIT_FOR_SETUP; + s3c_udc_ep0_zlp(); + + } else { + done(ep, req, 0); + + if (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, struct s3c_request, queue); + DEBUG_OUT_EP("%s: Next Rx request start...\n", __func__); + setdma_rx(ep, req); + } + } + } +} + +static void complete_tx(struct s3c_udc *dev, u8 ep_num) +{ + struct s3c_ep *ep = &dev->ep[ep_num]; + struct s3c_request *req; + u32 ep_tsr = 0, xfer_size = 0, xfer_length, is_short = 0; + u32 last; + + if (list_empty(&ep->queue)) { + DEBUG_IN_EP("%s: TX DMA done : NULL REQ on IN EP-%d\n", + __func__, ep_num); + return; + + } + + req = list_entry(ep->queue.next, struct s3c_request, queue); + + if (dev->ep0state == DATA_STATE_XMIT) { + DEBUG_IN_EP("%s: ep_num = %d, ep0stat == DATA_STATE_XMIT\n", + __func__, ep_num); + + last = write_fifo_ep0(ep, req); + + if (last) + dev->ep0state = WAIT_FOR_SETUP; + + return; + } + + ep_tsr = readl(S3C_UDC_OTG_DIEPTSIZ(ep_num)); + + if (ep_num == EP0_CON) + xfer_size = (ep_tsr & 0x7f); + else + xfer_size = (ep_tsr & 0x7fff); + + req->req.actual = req->req.length - xfer_size; + xfer_length = req->req.length - xfer_size; + req->req.actual += min(xfer_length, req->req.length - req->req.actual); + is_short = (xfer_length < ep->ep.maxpacket); + + DEBUG_IN_EP("%s: TX DMA done : ep = %d, tx bytes = %d/%d, " + "is_short = %d, DIEPTSIZ = 0x%x, remained bytes = %d\n", + __func__, ep_num, req->req.actual, req->req.length, + is_short, ep_tsr, xfer_size); + + if (req->req.actual == req->req.length) { + done(ep, req, 0); + + if (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, struct s3c_request, queue); + DEBUG_IN_EP("%s: Next Tx request start...\n", __func__); + setdma_tx(ep, req); + } + } +} +static inline void s3c_udc_check_tx_queue(struct s3c_udc *dev, u8 ep_num) +{ + struct s3c_ep *ep = &dev->ep[ep_num]; + struct s3c_request *req; + + DEBUG_IN_EP("%s: Check queue, ep_num = %d\n", __func__, ep_num); + + if (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, struct s3c_request, queue); + DEBUG_IN_EP("%s: Next Tx request(0x%p) start...\n", __func__, req); + + if (ep_is_in(ep)) + setdma_tx(ep, req); + else + setdma_rx(ep, req); + } else { + DEBUG_IN_EP("%s: NULL REQ on IN EP-%d\n", __func__, ep_num); + + return; + } + +} + +static void process_ep_in_intr(struct s3c_udc *dev) +{ + u32 ep_intr, ep_intr_status; + u8 ep_num = 0; + + ep_intr = readl(S3C_UDC_OTG_DAINT); + DEBUG_IN_EP("*** %s: EP In interrupt : DAINT = 0x%x\n", + __func__, ep_intr); + + ep_intr &= DAINT_MASK; + + while (ep_intr) { + if (ep_intr & 0x1) { + ep_intr_status = readl(S3C_UDC_OTG_DIEPINT(ep_num)); + DEBUG_IN_EP("\tEP%d-IN : DIEPINT = 0x%x\n", + ep_num, ep_intr_status); + + /* Interrupt Clear */ + writel(ep_intr_status, S3C_UDC_OTG_DIEPINT(ep_num)); + + if (ep_intr_status & TRANSFER_DONE) { + complete_tx(dev, ep_num); + + if (ep_num == 0) { + if (dev->ep0state == WAIT_FOR_SETUP) + s3c_udc_pre_setup(); + + /* continue transfer after set_clear_halt for DMA mode */ + if (clear_feature_flag == 1) { + s3c_udc_check_tx_queue(dev, clear_feature_num); + clear_feature_flag = 0; + } + } + } + } + ep_num++; + ep_intr >>= 1; + } + +} + +static void process_ep_out_intr(struct s3c_udc *dev) +{ + u32 ep_intr, ep_intr_status, ep_ctrl; + u8 ep_num = 0; + + ep_intr = readl(S3C_UDC_OTG_DAINT); + DEBUG_OUT_EP("*** %s: EP OUT interrupt : DAINT = 0x%x\n", + __func__, ep_intr); + + ep_intr = (ep_intr >> DAINT_OUT_BIT) & DAINT_MASK; + + while (ep_intr) { + if (ep_intr & 0x1) { + ep_intr_status = readl(S3C_UDC_OTG_DOEPINT(ep_num)); + DEBUG_OUT_EP("\tEP%d-OUT : DOEPINT = 0x%x\n", + ep_num, ep_intr_status); + + /* Interrupt Clear */ + writel(ep_intr_status, S3C_UDC_OTG_DOEPINT(ep_num)); + + if (ep_num == 0) { + if (ep_intr_status & CTRL_OUT_EP_SETUP_PHASE_DONE) { + DEBUG_OUT_EP("\tSETUP packet(transaction) arrived\n"); + s3c_handle_ep0(dev); + } + + if (ep_intr_status & TRANSFER_DONE) { + complete_rx(dev, ep_num); + + writel((3<<29)|(1<<19)|sizeof(struct usb_ctrlrequest), + S3C_UDC_OTG_DOEPTSIZ(EP0_CON)); + writel(virt_to_phys(&usb_ctrl), + S3C_UDC_OTG_DOEPDMA(EP0_CON)); + + ep_ctrl = readl(S3C_UDC_OTG_DOEPCTL(EP0_CON)); + writel(ep_ctrl|DEPCTL_EPENA|DEPCTL_SNAK, + S3C_UDC_OTG_DOEPCTL(EP0_CON)); + } + + } else { + if (ep_intr_status & TRANSFER_DONE) + complete_rx(dev, ep_num); + } + } + ep_num++; + ep_intr >>= 1; + } +} + +/* + * usb client interrupt handler. + */ +static irqreturn_t s3c_udc_irq(int irq, void *_dev) +{ + struct s3c_udc *dev = _dev; + u32 intr_status; + u32 usb_status, gintmsk; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + + intr_status = readl(S3C_UDC_OTG_GINTSTS); + gintmsk = readl(S3C_UDC_OTG_GINTMSK); + + DEBUG_ISR("\n*** %s : GINTSTS=0x%x(on state %s), GINTMSK : 0x%x, DAINT : 0x%x, DAINTMSK : 0x%x\n", + __func__, intr_status, state_names[dev->ep0state], gintmsk, + readl(S3C_UDC_OTG_DAINT), readl(S3C_UDC_OTG_DAINTMSK)); + + if (!intr_status) { + spin_unlock_irqrestore(&dev->lock, flags); + return IRQ_HANDLED; + } + + if (intr_status & INT_ENUMDONE) { + DEBUG_ISR("\tSpeed Detection interrupt\n"); + + writel(INT_ENUMDONE, S3C_UDC_OTG_GINTSTS); + usb_status = (readl(S3C_UDC_OTG_DSTS) & 0x6); + + if (usb_status & (USB_FULL_30_60MHZ | USB_FULL_48MHZ)) { + DEBUG_ISR("\t\tFull Speed Detection\n"); + set_max_pktsize(dev, USB_SPEED_FULL); + + } else { + DEBUG_ISR("\t\tHigh Speed Detection : 0x%x\n", usb_status); + set_max_pktsize(dev, USB_SPEED_HIGH); + } + } + + if (intr_status & INT_EARLY_SUSPEND) { + DEBUG_ISR("\tEarly suspend interrupt\n"); + writel(INT_EARLY_SUSPEND, S3C_UDC_OTG_GINTSTS); + } + + if (intr_status & INT_SUSPEND) { + usb_status = readl(S3C_UDC_OTG_DSTS); + DEBUG_ISR("\tSuspend interrupt :(DSTS):0x%x\n", usb_status); + writel(INT_SUSPEND, S3C_UDC_OTG_GINTSTS); + + if (dev->gadget.speed != USB_SPEED_UNKNOWN + && dev->driver + && dev->driver->suspend) { + spin_unlock(&dev->lock); + dev->driver->suspend(&dev->gadget); + spin_lock(&dev->lock); + } + if (dev->status & (1 << USB_DEVICE_REMOTE_WAKEUP)) { + DEBUG_ISR("device is under remote wakeup\n"); + spin_unlock_irqrestore(&dev->lock, flags); + return IRQ_HANDLED; + } + if (dev->driver) { + spin_unlock(&dev->lock); + dev->driver->disconnect(&dev->gadget); + spin_lock(&dev->lock); + } +#if defined(CONFIG_MACH_SMDKC110) || defined(CONFIG_MACH_SMDKV210) + s3c_udc_cable_disconnect(dev); +#endif + } + + if (intr_status & INT_RESUME) { + DEBUG_ISR("\tResume interrupt\n"); + writel(INT_RESUME, S3C_UDC_OTG_GINTSTS); + + if (dev->gadget.speed != USB_SPEED_UNKNOWN + && dev->driver + && dev->driver->resume) { + + dev->driver->resume(&dev->gadget); + } + } + + if (intr_status & INT_RESET) { + usb_status = readl(S3C_UDC_OTG_GOTGCTL); + DEBUG_ISR("\tReset interrupt - (GOTGCTL):0x%x\n", usb_status); + writel(INT_RESET, S3C_UDC_OTG_GINTSTS); + + set_conf_done = 0; + + if ((usb_status & 0xc0000) == (0x3 << 18)) { + if (reset_available) { + DEBUG_ISR("\t\tOTG core got reset (%d)!!\n", reset_available); + stop_activity(dev, dev->driver); + reconfig_usbd(); + dev->ep0state = WAIT_FOR_SETUP; + reset_available = 0; + s3c_udc_pre_setup(); + } + } else if (!(usb_status & B_SESSION_VALID)) { + reset_available = 1; + if (dev->udc_enabled) { + DEBUG_ISR("Reset without B_SESSION\n"); + if (dev->driver) { + spin_unlock(&dev->lock); + dev->driver->disconnect(&dev->gadget); + spin_lock(&dev->lock); + } + } + } else { + reset_available = 1; + DEBUG_ISR("\t\tRESET handling skipped\n"); + } + } + + if (intr_status & INT_IN_EP) + process_ep_in_intr(dev); + + if (intr_status & INT_OUT_EP) + process_ep_out_intr(dev); + + spin_unlock_irqrestore(&dev->lock, flags); + + return IRQ_HANDLED; +} + +/** Queue one request + * Kickstart transfer if needed + */ +static int s3c_queue(struct usb_ep *_ep, struct usb_request *_req, + gfp_t gfp_flags) +{ + struct s3c_request *req; + struct s3c_ep *ep; + struct s3c_udc *dev; + unsigned long flags; + u32 ep_num, gintsts; + + req = container_of(_req, struct s3c_request, req); + if (unlikely(!_req || !_req->complete || !_req->buf || !list_empty(&req->queue))) { + + DEBUG("%s: bad params\n", __func__); + return -EINVAL; + } + + ep = container_of(_ep, struct s3c_ep, ep); + + if (unlikely(!_ep || (!ep->desc && ep->ep.name != ep0name))) { + + DEBUG("%s: bad ep\n", __func__); + return -EINVAL; + } + + ep_num = ep_index(ep); + dev = ep->dev; + if (unlikely(!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN)) { + + DEBUG("%s: bogus device state %p\n", __func__, dev->driver); + return -ESHUTDOWN; + } + + spin_lock_irqsave(&dev->lock, flags); + + _req->status = -EINPROGRESS; + _req->actual = 0; + + /* kickstart this i/o queue? */ + DEBUG("\n*** %s: %s-%s req = %p, len = %d, buf = %p" + "Q empty = %d, stopped = %d\n", + __func__, _ep->name, ep_is_in(ep) ? "in" : "out", + _req, _req->length, _req->buf, + list_empty(&ep->queue), ep->stopped); + + if (list_empty(&ep->queue) && !ep->stopped) { + + if (ep_num == 0) { + /* EP0 */ + list_add_tail(&req->queue, &ep->queue); + s3c_ep0_kick(dev, ep); + req = 0; + + } else if (ep_is_in(ep)) { + gintsts = readl(S3C_UDC_OTG_GINTSTS); + DEBUG_IN_EP("%s: ep_is_in, S3C_UDC_OTG_GINTSTS=0x%x\n", + __func__, gintsts); + + if (set_conf_done == 1) { + setdma_tx(ep, req); + } else { + done(ep, req, 0); + DEBUG("%s: Not yet Set_configureation, ep_num = %d, req = %p\n", + __func__, ep_num, req); + req = 0; + } + + } else { + gintsts = readl(S3C_UDC_OTG_GINTSTS); + DEBUG_OUT_EP("%s: ep_is_out, S3C_UDC_OTG_GINTSTS=0x%x\n", + __func__, gintsts); + + setdma_rx(ep, req); + } + } + + /* pio or dma irq handler advances the queue. */ + if (likely(req != 0)) + list_add_tail(&req->queue, &ep->queue); + + spin_unlock_irqrestore(&dev->lock, flags); + + return 0; +} + +/****************************************************************/ +/* End Point 0 related functions */ +/****************************************************************/ + +/* return: 0 = still running, 1 = completed, negative = errno */ +static int write_fifo_ep0(struct s3c_ep *ep, struct s3c_request *req) +{ + u32 max; + unsigned count; + int is_last; + + max = ep_maxpacket(ep); + + DEBUG_EP0("%s: max = %d\n", __func__, max); + + count = setdma_tx(ep, req); + + /* last packet is usually short (or a zlp) */ + if (likely(count != max)) + is_last = 1; + else { + if (likely(req->req.length != req->req.actual) || req->req.zero) + is_last = 0; + else + is_last = 1; + } + + DEBUG_EP0("%s: wrote %s %d bytes%s %d left %p\n", __func__, + ep->ep.name, count, + is_last ? "/L" : "", req->req.length - req->req.actual, req); + + /* requests complete when all IN data is in the FIFO */ + if (is_last) { + ep->dev->ep0state = WAIT_FOR_SETUP; + return 1; + } + + return 0; +} + +static inline int s3c_fifo_read(struct s3c_ep *ep, u32 *cp, int max) +{ + u32 bytes; + + bytes = sizeof(struct usb_ctrlrequest); + __dma_single_cpu_to_dev(&usb_ctrl, bytes, DMA_FROM_DEVICE); + DEBUG_EP0("%s: bytes=%d, ep_index=%d\n", __func__, bytes, ep_index(ep)); + + return bytes; +} + +/** + * udc_set_address - set the USB address for this device + * @address: + * + * Called from control endpoint function + * after it decodes a set address setup packet. + */ +static void udc_set_address(struct s3c_udc *dev, unsigned char address) +{ + u32 ctrl = readl(S3C_UDC_OTG_DCFG); + writel(address << 4 | ctrl, S3C_UDC_OTG_DCFG); + + s3c_udc_ep0_zlp(); + + DEBUG_EP0("%s: USB OTG 2.0 Device address=%d, DCFG=0x%x\n", + __func__, address, readl(S3C_UDC_OTG_DCFG)); + + dev->usb_address = address; +} + +static inline void s3c_udc_ep0_set_stall(struct s3c_ep *ep) +{ + struct s3c_udc *dev; + u32 ep_ctrl = 0; + + dev = ep->dev; + ep_ctrl = readl(S3C_UDC_OTG_DIEPCTL(EP0_CON)); + + /* set the disable and stall bits */ + if (ep_ctrl & DEPCTL_EPENA) + ep_ctrl |= DEPCTL_EPDIS; + + ep_ctrl |= DEPCTL_STALL; + + writel(ep_ctrl, S3C_UDC_OTG_DIEPCTL(EP0_CON)); + + DEBUG_EP0("%s: set ep%d stall, DIEPCTL0 = 0x%x\n", + __func__, ep_index(ep), readl(S3C_UDC_OTG_DIEPCTL(EP0_CON))); + /* + * The application can only set this bit, and the core clears it, + * when a SETUP token is received for this endpoint + */ + dev->ep0state = WAIT_FOR_SETUP; + + s3c_udc_pre_setup(); +} + +static void s3c_ep0_read(struct s3c_udc *dev) +{ + struct s3c_request *req; + struct s3c_ep *ep = &dev->ep[0]; + int ret; + + if (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, struct s3c_request, queue); + + } else { + DEBUG("%s: ---> BUG\n", __func__); + BUG(); + return; + } + + DEBUG_EP0("%s: req = %p, req.length = 0x%x, req.actual = 0x%x\n", + __func__, req, req->req.length, req->req.actual); + + if (req->req.length == 0) { + /* zlp for Set_configuration, Set_interface, + * or Bulk-Only mass storge reset */ + + dev->ep0state = WAIT_FOR_SETUP; + set_conf_done = 1; + s3c_udc_ep0_zlp(); + done(ep, req, 0); + DEBUG_EP0("%s: req.length = 0, bRequest = %d\n", __func__, usb_ctrl.bRequest); + return; + } + + ret = setdma_rx(ep, req); +} + +/* + * DATA_STATE_XMIT + */ +static int s3c_ep0_write(struct s3c_udc *dev) +{ + struct s3c_request *req; + struct s3c_ep *ep = &dev->ep[0]; + int ret, need_zlp = 0; + + if (list_empty(&ep->queue)) + req = 0; + else + req = list_entry(ep->queue.next, struct s3c_request, queue); + + if (!req) { + DEBUG_EP0("%s: NULL REQ\n", __func__); + return 0; + } + + DEBUG_EP0("%s: req = %p, req.length = 0x%x, req.actual = 0x%x\n", + __func__, req, req->req.length, req->req.actual); + + if (req->req.length - req->req.actual == ep0_fifo_size) { + /* Next write will end with the packet size, */ + /* so we need Zero-length-packet */ + need_zlp = 1; + } + + ret = write_fifo_ep0(ep, req); + + if ((ret == 1) && !need_zlp) { + /* Last packet */ + dev->ep0state = WAIT_FOR_SETUP; + DEBUG_EP0("%s: finished, waiting for status\n", __func__); + + } else { + dev->ep0state = DATA_STATE_XMIT; + DEBUG_EP0("%s: not finished\n", __func__); + } + + if (need_zlp) { + dev->ep0state = DATA_STATE_NEED_ZLP; + DEBUG_EP0("%s: Need ZLP!\n", __func__); + } + + return 1; +} + +u16 g_status __attribute__((aligned(8))); + +static int s3c_udc_get_status(struct s3c_udc *dev, + struct usb_ctrlrequest *crq) +{ + u8 ep_num = crq->wIndex & 0x7F; + u32 ep_ctrl; + + DEBUG_SETUP("%s: *** USB_REQ_GET_STATUS\n", __func__); + + switch (crq->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_INTERFACE: + g_status = 0; + DEBUG_SETUP("\tGET_STATUS: USB_RECIP_INTERFACE, g_stauts = %d\n", g_status); + break; + + case USB_RECIP_DEVICE: + /* update device status */ + g_status = dev->status; + DEBUG_SETUP("\tGET_STATUS: USB_RECIP_DEVICE, g_stauts = %d\n", g_status); + break; + + case USB_RECIP_ENDPOINT: + if (crq->wLength > 2) { + DEBUG_SETUP("\tGET_STATUS: Not support EP or wLength\n"); + return 1; + } + + g_status = dev->ep[ep_num].stopped; + DEBUG_SETUP("\tGET_STATUS: USB_RECIP_ENDPOINT, g_stauts = %d\n", g_status); + + break; + + default: + return 1; + } + __dma_single_cpu_to_dev(&g_status, 2, DMA_TO_DEVICE); + + writel(virt_to_phys(&g_status), S3C_UDC_OTG_DIEPDMA(EP0_CON)); + writel((1<<19)|(2<<0), S3C_UDC_OTG_DIEPTSIZ(EP0_CON)); + + ep_ctrl = readl(S3C_UDC_OTG_DIEPCTL(EP0_CON)); + writel(ep_ctrl|DEPCTL_EPENA|DEPCTL_CNAK, S3C_UDC_OTG_DIEPCTL(EP0_CON)); + dev->ep0state = WAIT_FOR_SETUP; + + return 0; +} + +void s3c_udc_ep_set_stall(struct s3c_ep *ep) +{ + u8 ep_num; + u32 ep_ctrl = 0; + + ep_num = ep_index(ep); + DEBUG("%s: ep_num = %d, ep_type = %d\n", __func__, ep_num, ep->ep_type); + + if (ep_is_in(ep)) { + ep_ctrl = readl(S3C_UDC_OTG_DIEPCTL(ep_num)); + + /* set the disable and stall bits */ + if (ep_ctrl & DEPCTL_EPENA) + ep_ctrl |= DEPCTL_EPDIS; + + ep_ctrl |= DEPCTL_STALL; + + writel(ep_ctrl, S3C_UDC_OTG_DIEPCTL(ep_num)); + DEBUG("%s: set stall, DIEPCTL%d = 0x%x\n", + __func__, ep_num, readl(S3C_UDC_OTG_DIEPCTL(ep_num))); + + } else { + ep_ctrl = readl(S3C_UDC_OTG_DOEPCTL(ep_num)); + + /* set the stall bit */ + ep_ctrl |= DEPCTL_STALL; + + writel(ep_ctrl, S3C_UDC_OTG_DOEPCTL(ep_num)); + DEBUG("%s: set stall, DOEPCTL%d = 0x%x\n", + __func__, ep_num, readl(S3C_UDC_OTG_DOEPCTL(ep_num))); + } + + return; +} + +void s3c_udc_ep_clear_stall(struct s3c_ep *ep) +{ + u8 ep_num; + u32 ep_ctrl = 0; + + ep_num = ep_index(ep); + DEBUG("%s: ep_num = %d, ep_type = %d\n", __func__, ep_num, ep->ep_type); + + if (ep_is_in(ep)) { + ep_ctrl = readl(S3C_UDC_OTG_DIEPCTL(ep_num)); + + /* clear stall bit */ + ep_ctrl &= ~DEPCTL_STALL; + + /* + * USB Spec 9.4.5: For endpoints using data toggle, regardless + * of whether an endpoint has the Halt feature set, a + * ClearFeature(ENDPOINT_HALT) request always results in the + * data toggle being reinitialized to DATA0. + */ + if (ep->bmAttributes == USB_ENDPOINT_XFER_INT + || ep->bmAttributes == USB_ENDPOINT_XFER_BULK) { + ep_ctrl |= DEPCTL_SETD0PID; /* DATA0 */ + } + + writel(ep_ctrl, S3C_UDC_OTG_DIEPCTL(ep_num)); + DEBUG("%s: cleared stall, DIEPCTL%d = 0x%x\n", + __func__, ep_num, readl(S3C_UDC_OTG_DIEPCTL(ep_num))); + + } else { + ep_ctrl = readl(S3C_UDC_OTG_DOEPCTL(ep_num)); + + /* clear stall bit */ + ep_ctrl &= ~DEPCTL_STALL; + + if (ep->bmAttributes == USB_ENDPOINT_XFER_INT + || ep->bmAttributes == USB_ENDPOINT_XFER_BULK) { + ep_ctrl |= DEPCTL_SETD0PID; /* DATA0 */ + } + + writel(ep_ctrl, S3C_UDC_OTG_DOEPCTL(ep_num)); + DEBUG("%s: cleared stall, DOEPCTL%d = 0x%x\n", + __func__, ep_num, readl(S3C_UDC_OTG_DOEPCTL(ep_num))); + } + + return; +} + +static int s3c_udc_set_halt(struct usb_ep *_ep, int value) +{ + struct s3c_ep *ep; + struct s3c_udc *dev; + unsigned long flags; + u8 ep_num; + + ep = container_of(_ep, struct s3c_ep, ep); + ep_num = ep_index(ep); + + if (unlikely(!_ep || !ep->desc || ep_num == EP0_CON || + ep->desc->bmAttributes == USB_ENDPOINT_XFER_ISOC)) { + DEBUG("%s: %s bad ep or descriptor\n", __func__, ep->ep.name); + return -EINVAL; + } + + /* Attempt to halt IN ep will fail if any transfer requests + * are still queue */ + if (value && ep_is_in(ep) && !list_empty(&ep->queue)) { + DEBUG("%s: %s queue not empty, req = %p\n", + __func__, ep->ep.name, + list_entry(ep->queue.next, struct s3c_request, queue)); + + return -EAGAIN; + } + + dev = ep->dev; + DEBUG("%s: ep_num = %d, value = %d\n", __func__, ep_num, value); + + spin_lock_irqsave(&dev->lock, flags); + + if (value == 0) { + ep->stopped = 0; + s3c_udc_ep_clear_stall(ep); + } else { + ep->stopped = 1; + s3c_udc_ep_set_stall(ep); + } + + spin_unlock_irqrestore(&dev->lock, flags); + + return 0; +} + +void s3c_udc_ep_activate(struct s3c_ep *ep) +{ + u8 ep_num; + u32 ep_ctrl = 0, daintmsk = 0; + + ep_num = ep_index(ep); + + /* Read DEPCTLn register */ + if (ep_is_in(ep)) { + ep_ctrl = readl(S3C_UDC_OTG_DIEPCTL(ep_num)); + daintmsk = 1 << ep_num; + } else { + ep_ctrl = readl(S3C_UDC_OTG_DOEPCTL(ep_num)); + daintmsk = (1 << ep_num) << DAINT_OUT_BIT; + } + + DEBUG("%s: EPCTRL%d = 0x%x, ep_is_in = %d\n", + __func__, ep_num, ep_ctrl, ep_is_in(ep)); + + /* If the EP is already active don't change the EP Control + * register. */ + if (!(ep_ctrl & DEPCTL_USBACTEP)) { + ep_ctrl = (ep_ctrl & ~DEPCTL_TYPE_MASK) | (ep->bmAttributes << DEPCTL_TYPE_BIT); + ep_ctrl = (ep_ctrl & ~DEPCTL_MPS_MASK) | (ep->ep.maxpacket << DEPCTL_MPS_BIT); + ep_ctrl |= (DEPCTL_SETD0PID | DEPCTL_USBACTEP); + + if (ep_is_in(ep)) { + writel(ep_ctrl, S3C_UDC_OTG_DIEPCTL(ep_num)); + DEBUG("%s: USB Ative EP%d, DIEPCTRL%d = 0x%x\n", + __func__, ep_num, ep_num, readl(S3C_UDC_OTG_DIEPCTL(ep_num))); + } else { + writel(ep_ctrl, S3C_UDC_OTG_DOEPCTL(ep_num)); + DEBUG("%s: USB Ative EP%d, DOEPCTRL%d = 0x%x\n", + __func__, ep_num, ep_num, readl(S3C_UDC_OTG_DOEPCTL(ep_num))); + } + } + + /* Unmask EP Interrtupt */ + writel(readl(S3C_UDC_OTG_DAINTMSK)|daintmsk, S3C_UDC_OTG_DAINTMSK); + DEBUG("%s: DAINTMSK = 0x%x\n", __func__, readl(S3C_UDC_OTG_DAINTMSK)); + +} + +static int s3c_udc_clear_feature(struct usb_ep *_ep) +{ + struct s3c_ep *ep; + u8 ep_num; + struct s3c_udc *dev = the_controller; + ep = container_of(_ep, struct s3c_ep, ep); + ep_num = ep_index(ep); + + DEBUG_SETUP("%s: ep_num = %d, is_in = %d, clear_feature_flag = %d\n", + __func__, ep_num, ep_is_in(ep), clear_feature_flag); + + if (usb_ctrl.wLength != 0) { + DEBUG_SETUP("\tCLEAR_FEATURE: wLength is not zero.....\n"); + return 1; + } + + switch (usb_ctrl.bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + switch (usb_ctrl.wValue) { + case USB_DEVICE_REMOTE_WAKEUP: + DEBUG_SETUP("\tCLEAR_FEATURE: USB_DEVICE_REMOTE_WAKEUP\n"); + printk(KERN_INFO "%s:: USB_DEVICE_REMOTE_WAKEUP\n", __func__); + dev->status &= ~(1 << USB_DEVICE_REMOTE_WAKEUP); + break; + + case USB_DEVICE_TEST_MODE: + DEBUG_SETUP("\tCLEAR_FEATURE: USB_DEVICE_TEST_MODE\n"); + /** @todo Add CLEAR_FEATURE for TEST modes. */ + break; + } + + s3c_udc_ep0_zlp(); + break; + + case USB_RECIP_ENDPOINT: + DEBUG_SETUP("\tCLEAR_FEATURE: USB_RECIP_ENDPOINT, wValue = %d\n", + usb_ctrl.wValue); + + if (usb_ctrl.wValue == USB_ENDPOINT_HALT) { + if (ep_num == 0) { + s3c_udc_ep0_set_stall(ep); + return 0; + } + + s3c_udc_ep0_zlp(); + + s3c_udc_ep_clear_stall(ep); + s3c_udc_ep_activate(ep); + ep->stopped = 0; + + clear_feature_num = ep_num; + clear_feature_flag = 1; + } + break; + } + + return 0; +} + +/* Set into the test mode for Test Mode set_feature request */ +static inline void set_test_mode(void) +{ + u32 ep_ctrl, dctl; + u8 test_selector = (usb_ctrl.wIndex>>8) & TEST_SELECTOR_MASK; + + if (test_selector > 0 && test_selector < 6) { + ep_ctrl = readl(S3C_UDC_OTG_DIEPCTL(EP0_CON)); + + writel(1<<19 | 0<<0, S3C_UDC_OTG_DIEPTSIZ(EP0_CON)); + writel(ep_ctrl|DEPCTL_EPENA|DEPCTL_CNAK|EP0_CON<<DEPCTL_NEXT_EP_BIT , S3C_UDC_OTG_DIEPCTL(EP0_CON)); + } + + switch (test_selector) { + case TEST_J_SEL: + /* some delay is necessary like printk() or udelay() */ + printk(KERN_INFO "Test mode selector in set_feature request is TEST J\n"); + + dctl = readl(S3C_UDC_OTG_DCTL); + writel((dctl&~(TEST_CONTROL_MASK))|TEST_J_MODE, S3C_UDC_OTG_DCTL); + break; + case TEST_K_SEL: + /* some delay is necessary like printk() or udelay() */ + printk(KERN_INFO "Test mode selector in set_feature request is TEST K\n"); + + dctl = readl(S3C_UDC_OTG_DCTL); + writel((dctl&~(TEST_CONTROL_MASK))|TEST_K_MODE, S3C_UDC_OTG_DCTL); + break; + case TEST_SE0_NAK_SEL: + /* some delay is necessary like printk() or udelay() */ + printk(KERN_INFO "Test mode selector in set_feature request is TEST SE0 NAK\n"); + + dctl = readl(S3C_UDC_OTG_DCTL); + writel((dctl&~(TEST_CONTROL_MASK))|TEST_SE0_NAK_MODE, S3C_UDC_OTG_DCTL); + break; + case TEST_PACKET_SEL: + /* some delay is necessary like printk() or udelay() */ + printk(KERN_INFO "Test mode selector in set_feature request is TEST PACKET\n"); + __dma_single_cpu_to_dev(test_pkt, TEST_PKT_SIZE, DMA_TO_DEVICE); + writel(virt_to_phys(test_pkt), S3C_UDC_OTG_DIEPDMA(EP0_CON)); + + ep_ctrl = readl(S3C_UDC_OTG_DIEPCTL(EP0_CON)); + + writel(1<<19 | TEST_PKT_SIZE<<0, S3C_UDC_OTG_DIEPTSIZ(EP0_CON)); + writel(ep_ctrl|DEPCTL_EPENA|DEPCTL_CNAK|EP0_CON<<DEPCTL_NEXT_EP_BIT, S3C_UDC_OTG_DIEPCTL(EP0_CON)); + + dctl = readl(S3C_UDC_OTG_DCTL); + writel((dctl&~(TEST_CONTROL_MASK))|TEST_PACKET_MODE, S3C_UDC_OTG_DCTL); + break; + case TEST_FORCE_ENABLE_SEL: + /* some delay is necessary like printk() or udelay() */ + printk(KERN_INFO "Test mode selector in set_feature request is TEST FORCE ENABLE\n"); + + dctl = readl(S3C_UDC_OTG_DCTL); + writel((dctl&~(TEST_CONTROL_MASK))|TEST_FORCE_ENABLE_MODE, S3C_UDC_OTG_DCTL); + break; + } +} + +static int s3c_udc_set_feature(struct usb_ep *_ep) +{ + struct s3c_ep *ep; + u8 ep_num; + struct s3c_udc *dev = the_controller; + ep = container_of(_ep, struct s3c_ep, ep); + ep_num = ep_index(ep); + + DEBUG_SETUP("%s: *** USB_REQ_SET_FEATURE , ep_num = %d\n", __func__, ep_num); + + if (usb_ctrl.wLength != 0) { + DEBUG_SETUP("\tSET_FEATURE: wLength is not zero.....\n"); + return 1; + } + + switch (usb_ctrl.bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + switch (usb_ctrl.wValue) { + case USB_DEVICE_REMOTE_WAKEUP: + DEBUG_SETUP("\tSET_FEATURE: USB_DEVICE_REMOTE_WAKEUP\n"); + printk(KERN_INFO "%s:: USB_DEVICE_REMOTE_WAKEUP\n", __func__); + dev->status |= (1 << USB_DEVICE_REMOTE_WAKEUP); + break; + + case USB_DEVICE_TEST_MODE: + DEBUG_SETUP("\tSET_FEATURE: USB_DEVICE_TEST_MODE\n"); + set_test_mode(); + break; + + case USB_DEVICE_B_HNP_ENABLE: + DEBUG_SETUP("\tSET_FEATURE: USB_DEVICE_B_HNP_ENABLE\n"); + break; + + case USB_DEVICE_A_HNP_SUPPORT: + /* RH port supports HNP */ + DEBUG_SETUP("\tSET_FEATURE: USB_DEVICE_A_HNP_SUPPORT\n"); + break; + + case USB_DEVICE_A_ALT_HNP_SUPPORT: + /* other RH port does */ + DEBUG_SETUP("\tSET_FEATURE: USB_DEVICE_A_ALT_HNP_SUPPORT\n"); + break; + } + + s3c_udc_ep0_zlp(); + return 0; + + case USB_RECIP_INTERFACE: + DEBUG_SETUP("\tSET_FEATURE: USB_RECIP_INTERFACE\n"); + break; + + case USB_RECIP_ENDPOINT: + DEBUG_SETUP("\tSET_FEATURE: USB_RECIP_ENDPOINT\n"); + if (usb_ctrl.wValue == USB_ENDPOINT_HALT) { + if (ep_num == 0) { + s3c_udc_ep0_set_stall(ep); + return 0; + } + ep->stopped = 1; + s3c_udc_ep_set_stall(ep); + } + + s3c_udc_ep0_zlp(); + return 0; + } + + return 1; +} + +/* + * WAIT_FOR_SETUP (OUT_PKT_RDY) + */ +static void s3c_ep0_setup(struct s3c_udc *dev) +{ + struct s3c_ep *ep = &dev->ep[0]; + int i, bytes, is_in; + u8 ep_num; + + /* Nuke all previous transfers */ + nuke(ep, -EPROTO); + + /* read control req from fifo (8 bytes) */ + bytes = s3c_fifo_read(ep, (u32 *)&usb_ctrl, 8); + + DEBUG_SETUP("%s: bRequestType = 0x%x(%s), bRequest = 0x%x" + "\twLength = 0x%x, wValue = 0x%x, wIndex= 0x%x\n", + __func__, usb_ctrl.bRequestType, + (usb_ctrl.bRequestType & USB_DIR_IN) ? "IN" : "OUT", usb_ctrl.bRequest, + usb_ctrl.wLength, usb_ctrl.wValue, usb_ctrl.wIndex); + + if (usb_ctrl.bRequest == GET_MAX_LUN_REQUEST && usb_ctrl.wLength != 1) { + DEBUG_SETUP("\t%s:GET_MAX_LUN_REQUEST:invalid wLength = %d, setup returned\n", + __func__, usb_ctrl.wLength); + + s3c_udc_ep0_set_stall(ep); + dev->ep0state = WAIT_FOR_SETUP; + + return; + } else if (usb_ctrl.bRequest == BOT_RESET_REQUEST && usb_ctrl.wLength != 0) { + /* Bulk-Only *mass storge reset of class-specific request */ + DEBUG_SETUP("\t%s:BOT Rest:invalid wLength = %d, setup returned\n", + __func__, usb_ctrl.wLength); + + s3c_udc_ep0_set_stall(ep); + dev->ep0state = WAIT_FOR_SETUP; + + return; + } + + /* Set direction of EP0 */ + if (likely(usb_ctrl.bRequestType & USB_DIR_IN)) { + ep->bEndpointAddress |= USB_DIR_IN; + is_in = 1; + + } else { + ep->bEndpointAddress &= ~USB_DIR_IN; + is_in = 0; + } + /* cope with automagic for some standard requests. */ + dev->req_std = (usb_ctrl.bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD; + dev->req_config = 0; + dev->req_pending = 1; + + /* Handle some SETUP packets ourselves */ + switch (usb_ctrl.bRequest) { + case USB_REQ_SET_ADDRESS: + DEBUG_SETUP("%s: *** USB_REQ_SET_ADDRESS (%d)\n", + __func__, usb_ctrl.wValue); + + if (usb_ctrl.bRequestType + != (USB_TYPE_STANDARD | USB_RECIP_DEVICE)) + break; + + udc_set_address(dev, usb_ctrl.wValue); + return; + + case USB_REQ_SET_CONFIGURATION: + DEBUG_SETUP("============================================\n"); + DEBUG_SETUP("%s: USB_REQ_SET_CONFIGURATION (%d)\n", + __func__, usb_ctrl.wValue); + + if (usb_ctrl.bRequestType == USB_RECIP_DEVICE) { + reset_available = 1; + dev->req_config = 1; + } + +#if defined(CONFIG_MACH_SMDKC110) || defined(CONFIG_MACH_SMDKV210) + s3c_udc_cable_connect(dev); +#endif + break; + + case USB_REQ_GET_DESCRIPTOR: + DEBUG_SETUP("%s: *** USB_REQ_GET_DESCRIPTOR\n", __func__); + break; + + case USB_REQ_SET_INTERFACE: + DEBUG_SETUP("%s: *** USB_REQ_SET_INTERFACE (%d)\n", + __func__, usb_ctrl.wValue); + + if (usb_ctrl.bRequestType == USB_RECIP_INTERFACE) { + reset_available = 1; + dev->req_config = 1; + } + break; + + case USB_REQ_GET_CONFIGURATION: + DEBUG_SETUP("%s: *** USB_REQ_GET_CONFIGURATION\n", __func__); + break; + + case USB_REQ_GET_STATUS: + if (dev->req_std) { + if (!s3c_udc_get_status(dev, &usb_ctrl)) + return; + } + break; + + case USB_REQ_CLEAR_FEATURE: + ep_num = usb_ctrl.wIndex & 0x7f; + + if (!s3c_udc_clear_feature(&dev->ep[ep_num].ep)) + return; + break; + + case USB_REQ_SET_FEATURE: + ep_num = usb_ctrl.wIndex & 0x7f; + + if (!s3c_udc_set_feature(&dev->ep[ep_num].ep)) + return; + break; + + default: + DEBUG_SETUP("%s: *** Default of usb_ctrl.bRequest=0x%x happened.\n", + __func__, usb_ctrl.bRequest); + break; + } + + if (likely(dev->driver)) { + /* device-2-host (IN) or no data setup command, + * process immediately */ + DEBUG_SETUP("%s: usb_ctrlrequest will be passed to fsg_setup()\n", __func__); + + spin_unlock(&dev->lock); + i = dev->driver->setup(&dev->gadget, &usb_ctrl); + spin_lock(&dev->lock); + + if (i < 0) { + if (dev->req_config) { + DEBUG_SETUP("\tconfig change 0x%02x fail %d?\n", + (u32)&usb_ctrl.bRequest, i); + return; + } + + /* setup processing failed, force stall */ + s3c_udc_ep0_set_stall(ep); + dev->ep0state = WAIT_FOR_SETUP; + + DEBUG_SETUP("\tdev->driver->setup failed (%d), bRequest = %d\n", + i, usb_ctrl.bRequest); + + + } else if (dev->req_pending) { + dev->req_pending = 0; + DEBUG_SETUP("\tdev->req_pending...\n"); + } + + DEBUG_SETUP("\tep0state = %s\n", state_names[dev->ep0state]); + + } +} + +/* + * handle ep0 interrupt + */ +static void s3c_handle_ep0(struct s3c_udc *dev) +{ + if (dev->ep0state == WAIT_FOR_SETUP) { + DEBUG_OUT_EP("%s: WAIT_FOR_SETUP\n", __func__); + s3c_ep0_setup(dev); + + } else { + DEBUG_OUT_EP("%s: strange state!!(state = %s)\n", + __func__, state_names[dev->ep0state]); + } +} + +static void s3c_ep0_kick(struct s3c_udc *dev, struct s3c_ep *ep) +{ + DEBUG_EP0("%s: ep_is_in = %d\n", __func__, ep_is_in(ep)); + if (ep_is_in(ep)) { + dev->ep0state = DATA_STATE_XMIT; + s3c_ep0_write(dev); + + } else { + dev->ep0state = DATA_STATE_RECV; + s3c_ep0_read(dev); + } +} + diff --git a/drivers/usb/gadget/u_ether.c b/drivers/usb/gadget/u_ether.c index b5a30fe..f30c307 100644 --- a/drivers/usb/gadget/u_ether.c +++ b/drivers/usb/gadget/u_ether.c @@ -249,11 +249,13 @@ rx_submit(struct eth_dev *dev, struct usb_request *req, gfp_t gfp_flags) goto enomem; } +#ifndef CONFIG_USB_ANDROID_RNDIS_DWORD_ALIGNED /* Some platforms perform better when IP packets are aligned, * but on at least one, checksumming fails otherwise. Note: * RNDIS headers involve variable numbers of LE32 values. */ skb_reserve(skb, NET_IP_ALIGN); +#endif req->buf = skb->data; req->length = size; @@ -483,7 +485,10 @@ static void tx_complete(struct usb_ep *ep, struct usb_request *req) list_add(&req->list, &dev->tx_reqs); spin_unlock(&dev->req_lock); dev_kfree_skb_any(skb); - +#ifdef CONFIG_USB_ANDROID_RNDIS_DWORD_ALIGNED + if (req->buf != skb->data) + kfree(req->buf); +#endif atomic_dec(&dev->tx_qlen); if (netif_carrier_ok(dev->net)) netif_wake_queue(dev->net); @@ -577,7 +582,21 @@ static netdev_tx_t eth_start_xmit(struct sk_buff *skb, length = skb->len; } + +#ifdef CONFIG_USB_ANDROID_RNDIS_DWORD_ALIGNED + if ((int)skb->data & 3) { + req->buf = kmalloc(skb->len, GFP_ATOMIC); + if (!req->buf) + goto drop; + memcpy((void *)req->buf, (void *)skb->data, skb->len); + } + else { + req->buf = skb->data; + } +#else req->buf = skb->data; +#endif + req->context = skb; req->complete = tx_complete; @@ -618,6 +637,10 @@ static netdev_tx_t eth_start_xmit(struct sk_buff *skb, dev_kfree_skb_any(skb); drop: dev->net->stats.tx_dropped++; +#ifdef CONFIG_USB_ANDROID_RNDIS_DWORD_ALIGNED + if (req->buf != skb->data) + kfree(req->buf); +#endif spin_lock_irqsave(&dev->req_lock, flags); if (list_empty(&dev->tx_reqs)) netif_start_queue(net); @@ -823,12 +846,6 @@ int gether_setup_name(struct usb_gadget *g, u8 ethaddr[ETH_ALEN], SET_ETHTOOL_OPS(net, &ops); - /* two kinds of host-initiated state changes: - * - iff DATA transfer is active, carrier is "on" - * - tx queueing enabled if open *and* carrier is "on" - */ - netif_carrier_off(net); - dev->gadget = g; SET_NETDEV_DEV(net, &g->dev); SET_NETDEV_DEVTYPE(net, &gadget_type); @@ -842,6 +859,12 @@ int gether_setup_name(struct usb_gadget *g, u8 ethaddr[ETH_ALEN], INFO(dev, "HOST MAC %pM\n", dev->host_mac); the_dev = dev; + + /* two kinds of host-initiated state changes: + * - iff DATA transfer is active, carrier is "on" + * - tx queueing enabled if open *and* carrier is "on" + */ + netif_carrier_off(net); } return status; diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index ab085f1..67c452d 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -578,3 +578,19 @@ config USB_OCTEON_OHCI config USB_OCTEON2_COMMON bool default y if USB_OCTEON_EHCI || USB_OCTEON_OHCI + +config USB_S3C_OTG_HOST + tristate "S3C USB OTG Host support" + depends on USB && (PLAT_S3C64XX || PLAT_S5P) + help + Samsung's S3C64XX processors include high speed USB OTG2.0 + controller. It has 15 configurable endpoints, as well as + endpoint zero (for control transfers). + + This driver support only OTG Host role. If you want to use + OTG Device role, select USB Gadget support and S3C OTG Device. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "s3c_otg_hcd" and force all + drivers to also be dynamically linked. + diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index 624a362..de59025 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -15,6 +15,7 @@ xhci-hcd-y := xhci.o xhci-mem.o xhci-pci.o xhci-hcd-y += xhci-ring.o xhci-hub.o xhci-dbg.o obj-$(CONFIG_USB_WHCI_HCD) += whci/ +obj-$(CONFIG_USB_S3C_OTG_HOST) += s3c-otg/ obj-$(CONFIG_PCI) += pci-quirks.o diff --git a/drivers/usb/host/ehci-pci.c b/drivers/usb/host/ehci-pci.c index 3940d28..f768314 100644 --- a/drivers/usb/host/ehci-pci.c +++ b/drivers/usb/host/ehci-pci.c @@ -144,14 +144,6 @@ static int ehci_pci_setup(struct usb_hcd *hcd) hcd->has_tt = 1; tdi_reset(ehci); } - if (pdev->subsystem_vendor == PCI_VENDOR_ID_ASUSTEK) { - /* EHCI #1 or #2 on 6 Series/C200 Series chipset */ - if (pdev->device == 0x1c26 || pdev->device == 0x1c2d) { - ehci_info(ehci, "broken D3 during system sleep on ASUS\n"); - hcd->broken_pci_sleep = 1; - device_set_wakeup_capable(&pdev->dev, false); - } - } break; case PCI_VENDOR_ID_TDI: if (pdev->device == PCI_DEVICE_ID_TDI_EHCI) { @@ -365,7 +357,9 @@ static bool usb_is_intel_switchable_ehci(struct pci_dev *pdev) { return pdev->class == PCI_CLASS_SERIAL_USB_EHCI && pdev->vendor == PCI_VENDOR_ID_INTEL && - pdev->device == 0x1E26; + (pdev->device == 0x1E26 || + pdev->device == 0x8C2D || + pdev->device == 0x8C26); } static void ehci_enable_xhci_companion(void) diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c index e4dd26a..08fdcfa 100644 --- a/drivers/usb/host/ehci-q.c +++ b/drivers/usb/host/ehci-q.c @@ -130,9 +130,17 @@ qh_refresh (struct ehci_hcd *ehci, struct ehci_qh *qh) else { qtd = list_entry (qh->qtd_list.next, struct ehci_qtd, qtd_list); - /* first qtd may already be partially processed */ - if (cpu_to_hc32(ehci, qtd->qtd_dma) == qh->hw->hw_current) + /* + * first qtd may already be partially processed. + * If we come here during unlink, the QH overlay region + * might have reference to the just unlinked qtd. The + * qtd is updated in qh_completions(). Update the QH + * overlay here. + */ + if (cpu_to_hc32(ehci, qtd->qtd_dma) == qh->hw->hw_current) { + qh->hw->hw_qtd_next = qtd->hw_next; qtd = NULL; + } } if (qtd) diff --git a/drivers/usb/host/pci-quirks.c b/drivers/usb/host/pci-quirks.c index 7fec8bd..3f623fb 100644 --- a/drivers/usb/host/pci-quirks.c +++ b/drivers/usb/host/pci-quirks.c @@ -73,7 +73,9 @@ #define NB_PIF0_PWRDOWN_1 0x01100013 #define USB_INTEL_XUSB2PR 0xD0 +#define USB_INTEL_USB2PRM 0xD4 #define USB_INTEL_USB3_PSSEN 0xD8 +#define USB_INTEL_USB3PRM 0xDC static struct amd_chipset_info { struct pci_dev *nb_dev; @@ -541,7 +543,14 @@ static const struct dmi_system_id __devinitconst ehci_dmi_nohandoff_table[] = { /* Pegatron Lucid (Ordissimo AIRIS) */ .matches = { DMI_MATCH(DMI_BOARD_NAME, "M11JB"), - DMI_MATCH(DMI_BIOS_VERSION, "Lucid-GE-133"), + DMI_MATCH(DMI_BIOS_VERSION, "Lucid-"), + }, + }, + { + /* Pegatron Lucid (Ordissimo) */ + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "Ordissimo"), + DMI_MATCH(DMI_BIOS_VERSION, "Lucid-"), }, }, { } @@ -711,12 +720,28 @@ static int handshake(void __iomem *ptr, u32 mask, u32 done, return -ETIMEDOUT; } -bool usb_is_intel_switchable_xhci(struct pci_dev *pdev) +#define PCI_DEVICE_ID_INTEL_LYNX_POINT_XHCI 0x8C31 + +bool usb_is_intel_ppt_switchable_xhci(struct pci_dev *pdev) { return pdev->class == PCI_CLASS_SERIAL_USB_XHCI && pdev->vendor == PCI_VENDOR_ID_INTEL && pdev->device == PCI_DEVICE_ID_INTEL_PANTHERPOINT_XHCI; } + +/* The Intel Lynx Point chipset also has switchable ports. */ +bool usb_is_intel_lpt_switchable_xhci(struct pci_dev *pdev) +{ + return pdev->class == PCI_CLASS_SERIAL_USB_XHCI && + pdev->vendor == PCI_VENDOR_ID_INTEL && + pdev->device == PCI_DEVICE_ID_INTEL_LYNX_POINT_XHCI; +} + +bool usb_is_intel_switchable_xhci(struct pci_dev *pdev) +{ + return usb_is_intel_ppt_switchable_xhci(pdev) || + usb_is_intel_lpt_switchable_xhci(pdev); +} EXPORT_SYMBOL_GPL(usb_is_intel_switchable_xhci); /* @@ -739,12 +764,21 @@ EXPORT_SYMBOL_GPL(usb_is_intel_switchable_xhci); */ void usb_enable_xhci_ports(struct pci_dev *xhci_pdev) { +#if defined(CONFIG_USB_XHCI_HCD) || defined(CONFIG_USB_XHCI_HCD_MODULE) u32 ports_available; - ports_available = 0xffffffff; + /* Read USB3PRM, the USB 3.0 Port Routing Mask Register + * Indicate the ports that can be changed from OS. + */ + pci_read_config_dword(xhci_pdev, USB_INTEL_USB3PRM, + &ports_available); + + dev_dbg(&xhci_pdev->dev, "Configurable ports to enable SuperSpeed: 0x%x\n", + ports_available); + /* Write USB3_PSSEN, the USB 3.0 Port SuperSpeed Enable - * Register, to turn on SuperSpeed terminations for all - * available ports. + * Register, to turn on SuperSpeed terminations for the + * switchable ports. */ pci_write_config_dword(xhci_pdev, USB_INTEL_USB3_PSSEN, cpu_to_le32(ports_available)); @@ -754,7 +788,16 @@ void usb_enable_xhci_ports(struct pci_dev *xhci_pdev) dev_dbg(&xhci_pdev->dev, "USB 3.0 ports that are now enabled " "under xHCI: 0x%x\n", ports_available); - ports_available = 0xffffffff; + /* Read XUSB2PRM, xHCI USB 2.0 Port Routing Mask Register + * Indicate the USB 2.0 ports to be controlled by the xHCI host. + */ + + pci_read_config_dword(xhci_pdev, USB_INTEL_USB2PRM, + &ports_available); + + dev_dbg(&xhci_pdev->dev, "Configurable USB 2.0 ports to hand over to xCHI: 0x%x\n", + ports_available); + /* Write XUSB2PR, the xHC USB 2.0 Port Routing Register, to * switch the USB 2.0 power and data lines over to the xHCI * host. @@ -766,9 +809,28 @@ void usb_enable_xhci_ports(struct pci_dev *xhci_pdev) &ports_available); dev_dbg(&xhci_pdev->dev, "USB 2.0 ports that are now switched over " "to xHCI: 0x%x\n", ports_available); +#else + /* Don't switchover the ports if the user hasn't compiled the xHCI + * driver. Otherwise they will see "dead" USB ports that don't power + * the devices. + */ + dev_warn(&xhci_pdev->dev, + "CONFIG_USB_XHCI_HCD is turned off, " + "defaulting to EHCI.\n"); + dev_warn(&xhci_pdev->dev, + "USB 3.0 devices will work at USB 2.0 speeds.\n"); +#endif /* CONFIG_USB_XHCI_HCD || CONFIG_USB_XHCI_HCD_MODULE */ + } EXPORT_SYMBOL_GPL(usb_enable_xhci_ports); +void usb_disable_xhci_ports(struct pci_dev *xhci_pdev) +{ + pci_write_config_dword(xhci_pdev, USB_INTEL_USB3_PSSEN, 0x0); + pci_write_config_dword(xhci_pdev, USB_INTEL_XUSB2PR, 0x0); +} +EXPORT_SYMBOL_GPL(usb_disable_xhci_ports); + /** * PCI Quirks for xHCI. * @@ -784,12 +846,12 @@ static void __devinit quirk_usb_handoff_xhci(struct pci_dev *pdev) void __iomem *op_reg_base; u32 val; int timeout; + int len = pci_resource_len(pdev, 0); if (!mmio_resource_enabled(pdev, 0)) return; - base = ioremap_nocache(pci_resource_start(pdev, 0), - pci_resource_len(pdev, 0)); + base = ioremap_nocache(pci_resource_start(pdev, 0), len); if (base == NULL) return; @@ -799,9 +861,17 @@ static void __devinit quirk_usb_handoff_xhci(struct pci_dev *pdev) */ ext_cap_offset = xhci_find_next_cap_offset(base, XHCI_HCC_PARAMS_OFFSET); do { + if ((ext_cap_offset + sizeof(val)) > len) { + /* We're reading garbage from the controller */ + dev_warn(&pdev->dev, + "xHCI controller failing to respond"); + return; + } + if (!ext_cap_offset) /* We've reached the end of the extended capabilities */ goto hc_init; + val = readl(base + ext_cap_offset); if (XHCI_EXT_CAPS_ID(val) == XHCI_EXT_CAPS_LEGACY) break; @@ -832,9 +902,10 @@ static void __devinit quirk_usb_handoff_xhci(struct pci_dev *pdev) /* Disable any BIOS SMIs and clear all SMI events*/ writel(val, base + ext_cap_offset + XHCI_LEGACY_CONTROL_OFFSET); +hc_init: if (usb_is_intel_switchable_xhci(pdev)) usb_enable_xhci_ports(pdev); -hc_init: + op_reg_base = base + XHCI_HC_LENGTH(readl(base)); /* Wait for the host controller to be ready before writing any diff --git a/drivers/usb/host/pci-quirks.h b/drivers/usb/host/pci-quirks.h index b1002a8..7f69a39 100644 --- a/drivers/usb/host/pci-quirks.h +++ b/drivers/usb/host/pci-quirks.h @@ -10,10 +10,12 @@ void usb_amd_quirk_pll_disable(void); void usb_amd_quirk_pll_enable(void); bool usb_is_intel_switchable_xhci(struct pci_dev *pdev); void usb_enable_xhci_ports(struct pci_dev *xhci_pdev); +void usb_disable_xhci_ports(struct pci_dev *xhci_pdev); #else static inline void usb_amd_quirk_pll_disable(void) {} static inline void usb_amd_quirk_pll_enable(void) {} static inline void usb_amd_dev_put(void) {} +static inline void usb_disable_xhci_ports(struct pci_dev *xhci_pdev) {} #endif /* CONFIG_PCI */ #endif /* __LINUX_USB_PCI_QUIRKS_H */ diff --git a/drivers/usb/host/s3c-otg/Makefile b/drivers/usb/host/s3c-otg/Makefile new file mode 100644 index 0000000..37674c8 --- /dev/null +++ b/drivers/usb/host/s3c-otg/Makefile @@ -0,0 +1,18 @@ +# +# Makefile for USB OTG Host Controller Drivers +# + +obj-$(CONFIG_USB_S3C_OTG_HOST) += s3c_otg_hcd.o + +s3c_otg_hcd-objs += s3c-otg-hcdi-driver.o s3c-otg-hcdi-hcd.o +s3c_otg_hcd-objs += s3c-otg-transfer-common.o s3c-otg-transfer-nonperiodic.o \ + s3c-otg-transfer-periodic.o +s3c_otg_hcd-objs += s3c-otg-scheduler-ischeduler.o s3c-otg-scheduler-scheduler.o \ + s3c-otg-scheduler-readyq.o +s3c_otg_hcd-objs += s3c-otg-oci.o +s3c_otg_hcd-objs += s3c-otg-transferchecker-common.o \ + s3c-otg-transferchecker-control.o \ + s3c-otg-transferchecker-bulk.o \ + s3c-otg-transferchecker-interrupt.o +s3c_otg_hcd-objs += s3c-otg-isr.o +s3c_otg_hcd-objs += s3c-otg-roothub.o diff --git a/drivers/usb/host/s3c-otg/debug-mem.c b/drivers/usb/host/s3c-otg/debug-mem.c new file mode 100644 index 0000000..de85853 --- /dev/null +++ b/drivers/usb/host/s3c-otg/debug-mem.c @@ -0,0 +1,55 @@ +#include "s3c-otg-hcdi-hcd.h" +#include "debug-mem.h" + +#define MAX_ALLOCS 1024 + +typedef void *ElemType; + +static ElemType alloced[MAX_ALLOCS]; +static int numalloced = 0; + +#define fail(args...) ( printk(args), dump_stack() ) + +void debug_alloc(void *addr) { + ElemType *freeloc = NULL; + ElemType *c = alloced; + int i; + + if(!addr) + fail("MEMD alloc of NULL"); + + for(i = 0; i < numalloced; i++, c++) { + if(*c == NULL && freeloc == NULL) + freeloc = c; + else if(*c == addr) + fail("MEMD multiple allocs of %p", addr); + } + + if(freeloc) + *freeloc = addr; + else { + if(numalloced >= MAX_ALLOCS) + fail("MEMD too many allocs"); + else { + alloced[numalloced++] = addr; + } + } +} + +void debug_free(void *addr) { + ElemType *c = alloced; + int i; + + if(!addr) + fail("free of NULL"); + + for(i = 0; i < numalloced; i++, c++) { + if(*c == addr) { + *c = NULL; + return; + } + } + + fail("MEMD freed addr %p was never alloced\n", addr); +} + diff --git a/drivers/usb/host/s3c-otg/debug-mem.h b/drivers/usb/host/s3c-otg/debug-mem.h new file mode 100644 index 0000000..4164793 --- /dev/null +++ b/drivers/usb/host/s3c-otg/debug-mem.h @@ -0,0 +1,9 @@ +#ifndef __DEBUGMEM_H +#define __DEBUGMEM_H + +// kevinh quick hack to see if the s3c stuff is doing memory properly + +void debug_alloc(void *addr); +void debug_free(void *addr); + +#endif diff --git a/drivers/usb/host/s3c-otg/s3c-otg-common-common.h b/drivers/usb/host/s3c-otg/s3c-otg-common-common.h new file mode 100644 index 0000000..720154e --- /dev/null +++ b/drivers/usb/host/s3c-otg/s3c-otg-common-common.h @@ -0,0 +1,49 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * @file s3c-otg-common-common.h + * @brief it includes common header files for all modules \n + * @version + * ex)-# Jun 11,2008 v1.0 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Creating the initial version of this code \n + * @see None + ****************************************************************************/ +/**************************************************************************** + * 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 _S3C_OTG_COMMON_COMMON_H_ +#define _S3C_OTG_COMMON_COMMON_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +//#include "s3c-otg-common-typedef.h" +#include "s3c-otg-common-errorcode.h" +#include <linux/errno.h> +#include <linux/usb.h> + +//Define OS +#define LINUX 1 + +//Kernel Version +#define KERNEL_2_6_21 + +#ifdef __cplusplus +} +#endif +#endif /* _S3C_OTG_COMMON_COMMON_H_ */ diff --git a/drivers/usb/host/s3c-otg/s3c-otg-common-const.h b/drivers/usb/host/s3c-otg/s3c-otg-common-const.h new file mode 100644 index 0000000..f0f5cb9 --- /dev/null +++ b/drivers/usb/host/s3c-otg/s3c-otg-common-const.h @@ -0,0 +1,167 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : s3c-otg-common-const.h + * [Description] : The Header file defines constants to be used at sub-modules of S3C6400HCD. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2008/06/03 + * [Revision History] + * (1) 2008/06/03 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created s3c-otg-common-const.h file and defines some constants. + * + ****************************************************************************/ +/**************************************************************************** + * 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 _CONST_TYPE_DEF_H_ +#define _CONST_TYPE_DEF_H_ + +/* +// ---------------------------------------------------------------------------- +// Include files : None. +// ---------------------------------------------------------------------------- +*/ + +#include "s3c-otg-common-common.h" + +//#include "s3c-otg-common-regdef.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @def OTG_PORT_NUMBER + * + * @brief write~ description + * + * describe in detail + */ +#define OTG_PORT_NUMBER 0 + + + +//Defines Stages of Control Transfer +#define SETUP_STAGE 1 +#define DATA_STAGE 2 +#define STATUS_STAGE 3 +#define COMPLETE_STAGE 4 + + +//Defines Direction of Endpoint +#define EP_IN 1 +#define EP_OUT 0 + +//Define speed of USB Device +#define LOW_SPEED_OTG 2 +#define FULL_SPEED_OTG 1 +#define HIGH_SPEED_OTG 0 +#define SUPER_SPEED_OTG 3 + +//Define multiple count of packet in periodic transfer. +#define MULTI_COUNT_ZERO 0 +#define MULTI_COUNT_ONE 1 +#define MULTI_COUNT_TWO 2 + +//Define USB Transfer Types. +#define CONTROL_TRANSFER 0 +#define ISOCH_TRANSFER 1 +#define BULK_TRANSFER 2 +#define INT_TRANSFER 3 + +#define BULK_TIMEOUT 300 + +//Defines PID +#define DATA0 0 +#define DATA1 2 +#define DATA2 1 +#define MDATA 3 +#define SETUP 3 + +//Defines USB Transfer Request Size on USB2.0 +#define USB_20_STAND_DEV_REQUEST_SIZE 8 +//Define Max Channel Number +#define MAX_CH_NUMBER 16 +//Define Channel Number +#define CH_0 0 +#define CH_1 1 +#define CH_2 2 +#define CH_3 3 +#define CH_4 4 +#define CH_5 5 +#define CH_6 6 +#define CH_7 7 +#define CH_8 8 +#define CH_9 9 +#define CH_10 10 +#define CH_11 11 +#define CH_12 12 +#define CH_13 13 +#define CH_14 14 +#define CH_15 15 +#define CH_NONE 20 + + +// define the Constant for result of processing the USB Transfer. +#define RE_TRANSMIT 1 +#define RE_SCHEDULE 2 +#define DE_ALLOCATE 3 +#define NO_ACTION 4 + +//define the threshold value to retransmit USB Transfer +#define RETRANSMIT_THRESHOLD 2 + +//define the maximum size of data to be tranferred through channel. +#define MAX_CH_TRANSFER_SIZE 65536//65535 + +//define Max Frame Number which Synopsys OTG suppports. +#define MAX_FRAME_NUMBER 0x3FFF +// Channel Interrupt Status +#define CH_STATUS_DataTglErr (0x1<<10) +#define CH_STATUS_FrmOvrun (0x1<<9) +#define CH_STATUS_BblErr (0x1<<8) +#define CH_STATUS_XactErr (0x1<<7) +#define CH_STATUS_NYET (0x1<<6) +#define CH_STATUS_ACK (0x1<<5) +#define CH_STATUS_NAK (0x1<<4) +#define CH_STATUS_STALL (0x1<<3) +#define CH_STATUS_AHBErr (0x1<<2) +#define CH_STATUS_ChHltd (0x1<<1) +#define CH_STATUS_XferCompl (0x1<<0) +#define CH_STATUS_ALL 0x7FF + + +//Define USB Transfer Flag.. +//typedef URB_SHORT_NOT_OK USB_TRANS_FLAG_NOT_SHORT; +//typedef URB_ISO_ASAP USB_TRANS_FLAG_ISO_ASYNCH; + +#define USB_TRANS_FLAG_NOT_SHORT URB_SHORT_NOT_OK +#define USB_TRANS_FLAG_ISO_ASYNCH URB_ISO_ASAP + + +#define HFNUM_MAX_FRNUM 0x3FFF +#define SCHEDULE_SLOT 10 + +#ifdef __cplusplus +} +#endif + + +#endif + + diff --git a/drivers/usb/host/s3c-otg/s3c-otg-common-datastruct.h b/drivers/usb/host/s3c-otg/s3c-otg-common-datastruct.h new file mode 100644 index 0000000..c31e8ab --- /dev/null +++ b/drivers/usb/host/s3c-otg/s3c-otg-common-datastruct.h @@ -0,0 +1,811 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : s3c-otg-common-datastruct.h + * [Description] : The Header file defines Data Structures to be used at sub-modules of S3C6400HCD. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2008/06/03 + * [Revision History] + * (1) 2008/06/03 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and defines Data Structure to be managed by Transfer. + * (2) 2008/08/18 by SeungSoo Yang ( ss1.yang@samsung.com ) + * - modifying ED structure + * + ****************************************************************************/ +/**************************************************************************** + * 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 _DATA_STRUCT_DEF_H +#define _DATA_STRUCT_DEF_H + +/* +// ---------------------------------------------------------------------------- +// Include files : None. +// ---------------------------------------------------------------------------- +*/ + +#include <linux/wakelock.h> +#include <plat/s5p-otghost.h> +//#include "s3c-otg-common-typedef.h" +#include "s3c-otg-hcdi-list.h" + +//#include "s3c-otg-common-regdef.h" +//#include "s3c-otg-common-errorcode.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +typedef union _hcintmsk_t +{ + // raw register data + u32 d32; + + // register bits + struct + { + unsigned xfercompl : 1; + unsigned chhltd : 1; + unsigned ahberr : 1; + unsigned stall : 1; + unsigned nak : 1; + unsigned ack : 1; + unsigned nyet : 1; + unsigned xacterr : 1; + unsigned bblerr : 1; + unsigned frmovrun : 1; + unsigned datatglerr : 1; + unsigned reserved : 21; + } b; +} hcintmsk_t; + +typedef union _hcintn_t +{ + u32 d32; + struct + { + u32 xfercompl :1; + u32 chhltd :1; + u32 abherr :1; + u32 stall :1; + u32 nak :1; + u32 ack :1; + u32 nyet :1; + u32 xacterr :1; + u32 bblerr :1; + u32 frmovrun :1; + u32 datatglerr :1; + u32 reserved :21; + }b; +}hcintn_t; + + +typedef union _pcgcctl_t +{ + /** raw register data */ + u32 d32; + /** register bits */ + struct + { + unsigned stoppclk :1; + unsigned gatehclk :1; + unsigned pwrclmp :1; + unsigned rstpdwnmodule :1; + unsigned physuspended :1; + unsigned Reserved5_31 :27; + }b; +}pcgcctl_t; + + +typedef struct isoch_packet_desc +{ + u32 isoch_packiet_start_addr;// start address of buffer is buffer address + uiOffsert. + u32 buf_size; + u32 transferred_szie; + u32 isoch_status; +}isoch_packet_desc_t;//, *isoch_packet_desc_t *,**isoch_packet_desc_t **; + + +typedef struct standard_dev_req_info +{ + bool is_data_stage; + u8 conrol_transfer_stage; + u32 vir_standard_dev_req_addr; + u32 phy_standard_dev_req_addr; +}standard_dev_req_info_t; + + +typedef struct control_data_tgl_t +{ + u8 setup_tgl; + u8 data_tgl; + u8 status_tgl; +}control_data_tgl_t; + + + +typedef struct ed_status +{ + u8 data_tgl; + control_data_tgl_t control_data_tgl; + bool is_ping_enable; + bool is_in_transfer_ready_q; + bool is_in_transferring; + u32 in_transferring_td; + bool is_alloc_resource_for_ed; + bool is_complete_split; +// sztupy: split transaction support +#define ED_STATUS_SPLIT_POS_ALL 3 +#define ED_STATUS_SPLIT_POS_BEGIN 2 +#define ED_STATUS_SPLIT_POS_MID 1 +#define ED_STATUS_SPLIT_POS_END 0 + u8 split_pos; + u32 split_offset; +}ed_status_t;//, *ed_status_t *,**ed_status_t **; + + +typedef struct ed_desc +{ + u8 device_addr; + u8 endpoint_num; + bool is_ep_in; + u8 dev_speed; + u8 endpoint_type; + u16 max_packet_size; + u8 mc; + u8 interval; + u32 sched_frame; + u32 used_bus_time; + u8 hub_addr; + u8 hub_port; + bool is_do_split; +}ed_dest_t;//, *ed_dest_t *,**ed_dest_t **; + + +//Defines the Data Structures of Transfer. +typedef struct hc_reg +{ + + hcintmsk_t hc_int_msk; + hcintn_t hc_int; + u32 dma_addr; + +}hc_reg_t;//, *hc_reg_t *, **hc_reg_t **; + + +typedef struct stransfer +{ + u32 stransfer_id; + u32 parent_td; + ed_dest_t *ed_desc_p; + ed_status_t *ed_status_p; + u32 start_vir_buf_addr; + u32 start_phy_buf_addr; + u32 buf_size; + u32 packet_cnt; + u8 alloc_chnum; + hc_reg_t hc_reg; +}stransfer_t;//, *stransfer_t *,**stransfer_t **; + + +typedef struct ed +{ + u32 ed_id; + bool is_halted; + bool is_need_to_insert_scheduler; + ed_dest_t ed_desc; + ed_status_t ed_status; + otg_list_head ed_list_entry; + otg_list_head td_list_entry; + otg_list_head trans_ready_q_list_entry; + u32 num_td; + void *ed_private; +}ed_t;//, *ed_t *, **ed_t **; + + + +typedef struct td +{ + u32 td_id; + ed_t *parent_ed_p; + void *call_back_func_p; + void *call_back_func_param_p; + bool is_transferring; + bool is_transfer_done; + u32 transferred_szie; + bool is_standard_dev_req; + standard_dev_req_info_t standard_dev_req_info; + u32 vir_buf_addr; + u32 phy_buf_addr; + u32 buf_size; + u32 transfer_flag; + stransfer_t cur_stransfer; + USB_ERROR_CODE error_code; + u32 err_cnt; + otg_list_head td_list_entry; + + //Isochronous Transfer Specific + u32 isoch_packet_num; + isoch_packet_desc_t *isoch_packet_desc_p; + u32 isoch_packet_index; + u32 isoch_packet_position; + u32 sched_frame; + u32 interval; + u32 used_total_bus_time; + + // the private data can be used by S3C6400Interface. + void *td_private; +}td_t;//, *td_t *,**td_t **; + + +//Define Data Structures of Scheduler. +typedef struct trans_ready_q +{ + bool is_periodic_transfer; + otg_list_head trans_ready_q_list_head; + u32 trans_ready_entry_num; + + //In case of Periodic Transfer + u32 total_perio_bus_bandwidth; + u8 total_alloc_chnum; +}trans_ready_q_t;//, *trans_ready_q_t *,**trans_ready_q_t **; + + +//Define USB OTG Reg Data Structure by Kyuhyeok. + +#define MAX_COUNT 10000 +#define INT_ALL 0xffffffff + +typedef union _haint_t +{ + u32 d32; + struct + { + u32 channel_intr_0 :1; + u32 channel_intr_1 :1; + u32 channel_intr_2 :1; + u32 channel_intr_3 :1; + u32 channel_intr_4 :1; + u32 channel_intr_5 :1; + u32 channel_intr_6 :1; + u32 channel_intr_7 :1; + u32 channel_intr_8 :1; + u32 channel_intr_9 :1; + u32 channel_intr_10 :1; + u32 channel_intr_11 :1; + u32 channel_intr_12 :1; + u32 channel_intr_13 :1; + u32 channel_intr_14 :1; + u32 channel_intr_15 :1; + u32 reserved1 :16; + }b; +}haint_t; + +typedef union _gresetctl_t +{ + /** raw register data */ + u32 d32; + /** register bits */ + struct + { + unsigned csftrst : 1; + unsigned hsftrst : 1; + unsigned hstfrm : 1; + unsigned intknqflsh : 1; + unsigned rxfflsh : 1; + unsigned txfflsh : 1; + unsigned txfnum : 5; + unsigned reserved11_29 : 19; + unsigned dmareq : 1; + unsigned ahbidle : 1; + } b; +} gresetctl_t; + + +typedef union _gahbcfg_t +{ + /** raw register data */ + u32 d32; + /** register bits */ + struct + { + unsigned glblintrmsk : 1; +#define GAHBCFG_GLBINT_ENABLE 1 + unsigned hburstlen : 4; +#define INT_DMA_MODE_SINGLE 00 +#define INT_DMA_MODE_INCR 01 +#define INT_DMA_MODE_INCR4 03 +#define INT_DMA_MODE_INCR8 05 +#define INT_DMA_MODE_INCR16 07 + unsigned dmaenable : 1; +#define GAHBCFG_DMAENABLE 1 + unsigned reserved : 1; + unsigned nptxfemplvl : 1; + unsigned ptxfemplvl : 1; +#define GAHBCFG_TXFEMPTYLVL_EMPTY 1 +#define GAHBCFG_TXFEMPTYLVL_HALFEMPTY 0 + unsigned reserved9_31 : 22; + } b; +} gahbcfg_t; + +typedef union _gusbcfg_t +{ + /** raw register data */ + u32 d32; + /** register bits */ + struct + { + unsigned toutcal : 3; + unsigned phyif : 1; + unsigned ulpi_utmi_sel : 1; + unsigned fsintf : 1; + unsigned physel : 1; + unsigned ddrsel : 1; + unsigned srpcap : 1; + unsigned hnpcap : 1; + unsigned usbtrdtim : 4; + unsigned nptxfrwnden : 1; + unsigned phylpwrclksel : 1; + unsigned reserved : 13; + unsigned forcehstmode : 1; + unsigned reserved2 : 2; + } b; +} gusbcfg_t; + + +typedef union _ghwcfg2_t +{ + /** raw register data */ + u32 d32; + /** register bits */ + struct { + /* GHWCFG2 */ + unsigned op_mode : 3; +#define MODE_HNP_SRP_CAPABLE 0 +#define MODE_SRP_ONLY_CAPABLE 1 +#define MODE_NO_HNP_SRP_CAPABLE 2 +#define MODE_SRP_CAPABLE_DEVICE 3 +#define MODE_NO_SRP_CAPABLE_DEVICE 4 +#define MODE_SRP_CAPABLE_HOST 5 +#define MODE_NO_SRP_CAPABLE_HOST 6 + + unsigned architecture : 2; +#define HWCFG2_ARCH_SLAVE_ONLY 0x00 +#define HWCFG2_ARCH_EXT_DMA 0x01 +#define HWCFG2_ARCH_INT_DMA 0x02 + + unsigned point2point : 1; + unsigned hs_phy_type : 2; + unsigned fs_phy_type : 2; + unsigned num_dev_ep : 4; + unsigned num_host_chan : 4; + unsigned perio_ep_supported : 1; + unsigned dynamic_fifo : 1; + unsigned rx_status_q_depth : 2; + unsigned nonperio_tx_q_depth : 2; + unsigned host_perio_tx_q_depth : 2; + unsigned dev_token_q_depth : 5; + unsigned reserved31 : 1; + } b; +} ghwcfg2_t; + +typedef union _gintsts_t +{ + /** raw register data */ + u32 d32; +#define SOF_INTR_MASK 0x0008 + /** register bits */ + struct + { +#define HOST_MODE 1 +#define DEVICE_MODE 0 + unsigned curmode : 1; +#define OTG_HOST_MODE 1 +#define OTG_DEVICE_MODE 0 + + unsigned modemismatch : 1; + unsigned otgintr : 1; + unsigned sofintr : 1; + unsigned rxstsqlvl : 1; + unsigned nptxfempty : 1; + unsigned ginnakeff : 1; + unsigned goutnakeff : 1; + unsigned reserved8 : 1; + unsigned i2cintr : 1; + unsigned erlysuspend : 1; + unsigned usbsuspend : 1; + unsigned usbreset : 1; + unsigned enumdone : 1; + unsigned isooutdrop : 1; + unsigned eopframe : 1; + unsigned intokenrx : 1; + unsigned epmismatch : 1; + unsigned inepint : 1; + unsigned outepintr : 1; + unsigned incompisoin : 1; + unsigned incompisoout : 1; + unsigned reserved22_23 : 2; + unsigned portintr : 1; + unsigned hcintr : 1; + unsigned ptxfempty : 1; + unsigned reserved27 : 1; + unsigned conidstschng : 1; + unsigned disconnect : 1; + unsigned sessreqintr : 1; + unsigned wkupintr : 1; + } b; +} gintsts_t; + + +typedef union _hcfg_t +{ + /** raw register data */ + u32 d32; + + /** register bits */ + struct + { + /** FS/LS Phy Clock Select */ + unsigned fslspclksel : 2; +#define HCFG_30_60_MHZ 0 +#define HCFG_48_MHZ 1 +#define HCFG_6_MHZ 2 + + /** FS/LS Only Support */ + unsigned fslssupp : 1; + unsigned reserved3_31 : 29; + } b; +} hcfg_t; + +typedef union _hprt_t +{ + /** raw register data */ + u32 d32; + /** register bits */ + struct + { + unsigned prtconnsts : 1; + unsigned prtconndet : 1; + unsigned prtena : 1; + unsigned prtenchng : 1; + unsigned prtovrcurract : 1; + unsigned prtovrcurrchng : 1; + unsigned prtres : 1; + unsigned prtsusp : 1; + unsigned prtrst : 1; + unsigned reserved9 : 1; + unsigned prtlnsts : 2; + unsigned prtpwr : 1; + unsigned prttstctl : 4; + unsigned prtspd : 2; +#define HPRT0_PRTSPD_HIGH_SPEED 0 +#define HPRT0_PRTSPD_FULL_SPEED 1 +#define HPRT0_PRTSPD_LOW_SPEED 2 + unsigned reserved19_31 : 13; + } b; +} hprt_t; + + +typedef union _gintmsk_t +{ + /** raw register data */ + u32 d32; + /** register bits */ + struct + { + unsigned reserved0 : 1; + unsigned modemismatch : 1; + unsigned otgintr : 1; + unsigned sofintr : 1; + unsigned rxstsqlvl : 1; + unsigned nptxfempty : 1; + unsigned ginnakeff : 1; + unsigned goutnakeff : 1; + unsigned reserved8 : 1; + unsigned i2cintr : 1; + unsigned erlysuspend : 1; + unsigned usbsuspend : 1; + unsigned usbreset : 1; + unsigned enumdone : 1; + unsigned isooutdrop : 1; + unsigned eopframe : 1; + unsigned reserved16 : 1; + unsigned epmismatch : 1; + unsigned inepintr : 1; + unsigned outepintr : 1; + unsigned incompisoin : 1; + unsigned incompisoout : 1; + unsigned reserved22_23 : 2; + unsigned portintr : 1; + unsigned hcintr : 1; + unsigned ptxfempty : 1; + unsigned reserved27 : 1; + unsigned conidstschng : 1; + unsigned disconnect : 1; + unsigned sessreqintr : 1; + unsigned wkupintr : 1; + } b; +} gintmsk_t; + + +typedef struct _hc_t +{ + + u8 hc_num; // Host channel number used for register address lookup + + unsigned dev_addr : 7; // Device to access + unsigned ep_is_in : 1; // EP direction; 0: OUT, 1: IN + + unsigned ep_num : 4; // EP to access + unsigned low_speed : 1; // 1: Low speed, 0: Not low speed + unsigned ep_type : 2; // Endpoint type. + // One of the following values: + // - OTG_EP_TYPE_CONTROL: 0 + // - OTG_EP_TYPE_ISOC: 1 + // - OTG_EP_TYPE_BULK: 2 + // - OTG_EP_TYPE_INTR: 3 + + unsigned rsvdb1 : 1; // 8 bit padding + + u8 rsvd2; // 4 byte boundary + + unsigned max_packet : 12; // Max packet size in bytes + + unsigned data_pid_start : 2; +#define OTG_HC_PID_DATA0 0 +#define OTG_HC_PID_DATA2 1 +#define OTG_HC_PID_DATA1 2 +#define OTG_HC_PID_MDATA 3 +#define OTG_HC_PID_SETUP 3 + + unsigned multi_count : 2; // Number of periodic transactions per (micro)frame + + + // Flag to indicate whether the transfer has been started. Set to 1 if + // it has been started, 0 otherwise. + u8 xfer_started; + + + // Set to 1 to indicate that a PING request should be issued on this + // channel. If 0, process normally. + u8 do_ping; + + // Set to 1 to indicate that the error count for this transaction is + // non-zero. Set to 0 if the error count is 0. + u8 error_state; + u32 *xfer_buff; // Pointer to the current transfer buffer position. + u16 start_pkt_count; // Packet count at start of transfer. + + u32 xfer_len; // Total number of bytes to transfer. + u32 xfer_count; // Number of bytes transferred so far. + + + // Set to 1 if the host channel has been halted, but the core is not + // finished flushing queued requests. Otherwise 0. + u8 halt_pending; + u8 halt_status; // Reason for halting the host channel + u8 short_read; // Set when the host channel does a short read. + u8 rsvd3; // 4 byte boundary + +} hc_t; + + +// Port status for the HC +#define HCD_DRIVE_RESET 0x0001 +#define HCD_SEND_SETUP 0x0002 + +#define HC_MAX_PKT_COUNT 511 +#define HC_MAX_TRANSFER_SIZE 65535 +#define MAXP_SIZE_64BYTE 64 +#define MAXP_SIZE_512BYTE 512 +#define MAXP_SIZE_1024BYTE 1024 + +typedef union _hcchar_t +{ + // raw register data + u32 d32; + + // register bits + struct + { + // Maximum packet size in bytes + unsigned mps : 11; + + // Endpoint number + unsigned epnum : 4; + + // 0: OUT, 1: IN + unsigned epdir : 1; +#define HCDIR_OUT 0 +#define HCDIR_IN 1 + + unsigned reserved : 1; + + // 0: Full/high speed device, 1: Low speed device + unsigned lspddev : 1; + + // 0: Control, 1: Isoc, 2: Bulk, 3: Intr + unsigned eptype : 2; +#define OTG_EP_TYPE_CONTROL 0 +#define OTG_EP_TYPE_ISOC 1 +#define OTG_EP_TYPE_BULK 2 +#define OTG_EP_TYPE_INTR 3 + + // Packets per frame for periodic transfers. 0 is reserved. + unsigned multicnt : 2; + + // Device address + unsigned devaddr : 7; + + // Frame to transmit periodic transaction. + // 0: even, 1: odd + unsigned oddfrm : 1; + + // Channel disable + unsigned chdis : 1; + + // Channel enable + unsigned chen : 1; + } b; +} hcchar_t; + +// sztupy: adding struct to handle HCSPLT register +typedef union _hcsplt_t { + // raw register data + u32 d32; + + // register bits + struct + { + unsigned prtaddr : 7; + unsigned hubaddr : 7; + unsigned xactpos : 2; + unsigned compsplt : 1; + unsigned reserved : 14; + unsigned spltena : 1; + } b; + +} hcsplt_t; + +typedef union _hctsiz_t +{ + // raw register data + u32 d32; + + // register bits + struct + { + // Total transfer size in bytes + unsigned xfersize : 19; + + // Data packets to transfer + unsigned pktcnt : 10; + + // Packet ID for next data packet + // 0: DATA0 + // 1: DATA2 + // 2: DATA1 + // 3: MDATA (non-Control), SETUP (Control) + unsigned pid : 2; +#define HCTSIZ_DATA0 0 +#define HCTSIZ_DATA1 2 +#define HCTSIZ_DATA2 1 +#define HCTSIZ_MDATA 3 +#define HCTSIZ_SETUP 3 + + // Do PING protocol when 1 + unsigned dopng : 1; + } b; +} hctsiz_t; + + + +typedef union _grxstsr_t +{ + // raw register data + u32 d32; + + // register bits + struct + { + unsigned chnum : 4; + unsigned bcnt : 11; + unsigned dpid : 2; + unsigned pktsts : 4; + unsigned Reserved : 11; + } b; +} grxstsr_t; + +typedef union _hfir_t +{ + // raw register data + u32 d32; + + // register bits + struct + { + unsigned frint : 16; + unsigned Reserved : 16; + } b; +} hfir_t; + +typedef union _hfnum_t +{ + // raw register data + u32 d32; + + // register bits + struct + { + unsigned frnum : 16; +#define HFNUM_MAX_FRNUM 0x3FFF + unsigned frrem : 16; + } b; +} hfnum_t; + +typedef union grstctl_t +{ + /** raw register data */ + u32 d32; + /** register bits */ + struct + { + unsigned csftrst : 1; + unsigned hsftrst : 1; + unsigned hstfrm : 1; + unsigned intknqflsh : 1; + unsigned rxfflsh : 1; + unsigned txfflsh : 1; + unsigned txfnum : 5; + unsigned reserved11_29 : 19; + unsigned dmareq : 1; + unsigned ahbidle : 1; + } b; +} grstctl_t; + +typedef struct hc_info +{ + hcintmsk_t hc_int_msk; + hcintn_t hc_int; + u32 dma_addr; + hcchar_t hc_char; + hctsiz_t hc_size; +}hc_info_t;//, *hc_info_t *, **hc_info_t **; + +#ifndef USB_MAXCHILDREN + #define USB_MAXCHILDREN (31) +#endif + +typedef struct _usb_hub_descriptor_t +{ + u8 desc_length; + u8 desc_type; + u8 port_number; + u16 hub_characteristics; + u8 power_on_to_power_good; + u8 hub_control_current; + /* add 1 bit for hub status change; round to bytes */ + u8 DeviceRemovable[(USB_MAXCHILDREN + 1 + 7) / 8]; + u8 port_pwr_ctrl_mask[(USB_MAXCHILDREN + 1 + 7) / 8]; +}usb_hub_descriptor_t; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/drivers/usb/host/s3c-otg/s3c-otg-common-errorcode.h b/drivers/usb/host/s3c-otg/s3c-otg-common-errorcode.h new file mode 100644 index 0000000..b2b3bee --- /dev/null +++ b/drivers/usb/host/s3c-otg/s3c-otg-common-errorcode.h @@ -0,0 +1,115 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : s3c-otg-common-errorcode.h + * [Description] : The Header file defines Error Codes to be used at sub-modules of S3C6400HCD. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2008/06/03 + * [Revision History] + * (1) 2008/06/03 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file. + * (2) 2008/08/18 by SeungSoo Yang ( ss1.yang@samsung.com ) + * - add HCD error code + * + ****************************************************************************/ +/**************************************************************************** + * 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 _ERROR_CODE_DEF_H +#define _ERROR_CODE_DEF_H + +/* +// ---------------------------------------------------------------------------- +// Include files : None. +// ---------------------------------------------------------------------------- +*/ + +//#include "s3c-otg-common-typedef.h" +#include "s3c-otg-common-common.h" + + +#ifdef __cplusplus +extern "C" +{ +#endif + + +typedef int USB_ERROR_CODE; + +//General USB Error Code. +#define USB_ERR_SUCCESS 0 +#define USB_ERR_FAIL -1 + +#define USB_ERR_NO 1 + +#define USB_ERR_NO_ENTITY -2 + +//S3CTransfer Error Code +#define USB_ERR_NODEV -ENODEV +#define USB_ERR_NOMEM -ENOMEM +#define USB_ERR_NOSPACE -ENOSPC +#define USB_ERR_NOIO -EIO + +//OTG-HCD error code +#define USB_ERR_NOELEMENT -ENOENT +#define USB_ERR_ESHUTDOWN -ESHUTDOWN /* unplug */ +#define USB_ERR_DEQUEUED -ECONNRESET /* unlink */ + + +//S3CScheduler Error Code +#define USB_ERR_ALREADY_EXIST -1 +#define USB_ERR_NO_RESOURCE -2 +#define USB_ERR_NO_CHANNEL -3 +#define USB_ERR_NO_BANDWIDTH -4 +#define USB_ERR_ALL_RESROUCE -5 + + + + +/************************************************ + *Defines the USB Error Status Code of USB Transfer. + ************************************************/ + +//#ifdef LINUX + +#define USB_ERR_STATUS_COMPLETE 0 +#define USB_ERR_STATUS_INPROGRESS -EINPROGRESS +#define USB_ERR_STATUS_CRC -EILSEQ +#define USB_ERR_STATUS_XACTERR -EPROTO +#define USB_ERR_STATUS_STALL -EPIPE +#define USB_ERR_STATUS_BBLERR -EOVERFLOW +#define USB_ERR_STATUS_AHBERR -EIO +#define USB_ERR_STATUS_FRMOVRUN_OUT -ENOSR +#define USB_ERR_STATUS_FRMOVRUN_IN -ECOMM +#define USB_ERR_STATUS_SHORTREAD -EREMOTEIO + +//#else + +//#endif + + + + + + +#ifdef __cplusplus +} +#endif + + +#endif + diff --git a/drivers/usb/host/s3c-otg/s3c-otg-common-regdef.h b/drivers/usb/host/s3c-otg/s3c-otg-common-regdef.h new file mode 100644 index 0000000..ed119d7 --- /dev/null +++ b/drivers/usb/host/s3c-otg/s3c-otg-common-regdef.h @@ -0,0 +1,302 @@ +/**************************************************************************** +* (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved +* +* [File Name] : s3c-otg-common-regdef.h +* [Description] : +* +* [Author] : Kyu Hyeok Jang { kyuhyeok.jang@samsung.com } +* [Department] : System LSI Division/Embedded Software Center +* [Created Date]: 2007/12/15 +* [Revision History] +* (1) 2007/12/15 by Kyu Hyeok Jang { kyuhyeok.jang@samsung.com } +* - Created +* +****************************************************************************/ +/**************************************************************************** + * 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 _OTG_REG_DEF_H +#define _OTG_REG_DEF_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +typedef struct { + u32 OPHYPWR; + u32 OPHYCLK; + u32 ORSTCON; +}OTG_PHY_REG, *PS_OTG_PHY_REG; + +#define GOTGCTL 0x000 // OTG Control & Status +#define GOTGINT 0x004 // OTG Interrupt +#define GAHBCFG 0x008 // Core AHB Configuration +#define GUSBCFG 0x00C // Core USB Configuration +#define GRSTCTL 0x010 // Core Reset +#define GINTSTS 0x014 // Core Interrupt +#define GINTMSK 0x018 // Core Interrupt Mask +#define GRXSTSR 0x01C // Receive Status Debug Read/Status Read +#define GRXSTSP 0x020 // Receive Status Debug Pop/Status Pop +#define GRXFSIZ 0x024 // Receive FIFO Size +#define GNPTXFSIZ 0x028 // Non-Periodic Transmit FIFO Size +#define GNPTXSTS 0x02C // Non-Periodic Transmit FIFO/Queue Status +#define GPVNDCTL 0x034 // PHY Vendor Control +#define GGPIO 0x038 // General Purpose I/O +#define GUID 0x03C // User ID +#define GSNPSID 0x040 // Synopsys ID +#define GHWCFG1 0x044 // User HW Config1 +#define GHWCFG2 0x048 // User HW Config2 +#define GHWCFG3 0x04C // User HW Config3 +#define GHWCFG4 0x050 // User HW Config4 + +#define HPTXFSIZ 0x100 // Host Periodic Transmit FIFO Size +#define DPTXFSIZ1 0x104 // Device Periodic Transmit FIFO-1 Size +#define DPTXFSIZ2 0x108 // Device Periodic Transmit FIFO-2 Size +#define DPTXFSIZ3 0x10C // Device Periodic Transmit FIFO-3 Size +#define DPTXFSIZ4 0x110 // Device Periodic Transmit FIFO-4 Size +#define DPTXFSIZ5 0x114 // Device Periodic Transmit FIFO-5 Size +#define DPTXFSIZ6 0x118 // Device Periodic Transmit FIFO-6 Size +#define DPTXFSIZ7 0x11C // Device Periodic Transmit FIFO-7 Size +#define DPTXFSIZ8 0x120 // Device Periodic Transmit FIFO-8 Size +#define DPTXFSIZ9 0x124 // Device Periodic Transmit FIFO-9 Size +#define DPTXFSIZ10 0x128 // Device Periodic Transmit FIFO-10 Size +#define DPTXFSIZ11 0x12C // Device Periodic Transmit FIFO-11 Size +#define DPTXFSIZ12 0x130 // Device Periodic Transmit FIFO-12 Size +#define DPTXFSIZ13 0x134 // Device Periodic Transmit FIFO-13 Size +#define DPTXFSIZ14 0x138 // Device Periodic Transmit FIFO-14 Size +#define DPTXFSIZ15 0x13C // Device Periodic Transmit FIFO-15 Size + +//********************************************************************* +// Host Mode Registers +//********************************************************************* +// Host Global Registers + +// Channel specific registers +#define HCCHAR_ADDR 0x500 +#define HCCHAR(n) 0x500 + ((n)*0x20) +#define HCSPLT(n) 0x504 + ((n)*0x20) +#define HCINT(n) 0x508 + ((n)*0x20) +#define HCINTMSK(n) 0x50C + ((n)*0x20) +#define HCTSIZ(n) 0x510 + ((n)*0x20) +#define HCDMA(n) 0x514 + ((n)*0x20) + +#define HCFG 0x400 // Host Configuration +#define HFIR 0x404 // Host Frame Interval +#define HFNUM 0x408 // Host Frame Number/Frame Time Remaining +#define HPTXSTS 0x410 // Host Periodic Transmit FIFO/Queue Status +#define HAINT 0x414 // Host All Channels Interrupt +#define HAINTMSK 0x418 // Host All Channels Interrupt Mask + +// Host Port Control & Status Registers + +#define HPRT 0x440 // Host Port Control & Status + +// Device Logical Endpoints-Specific Registers + +#define DIEPCTL 0x900 // Device IN Endpoint 0 Control +#define DOEPCTL(n) 0xB00 + ((n)*0x20)) // Device OUT Endpoint 0 Control +#define DIEPINT(n) 0x908 + ((n)*0x20)) // Device IN Endpoint 0 Interrupt +#define DOEPINT(n) 0xB08 + ((n)*0x20)) // Device OUT Endpoint 0 Interrupt +#define DIEPTSIZ(n) 0x910 + ((n)*0x20)) // Device IN Endpoint 0 Transfer Size +#define DOEPTSIZ(n) 0xB10 + ((n)*0x20)) // Device OUT Endpoint 0 Transfer Size +#define DIEPDMA(n) 0x914 + ((n)*0x20)) // Device IN Endpoint 0 DMA Address +#define DOEPDMA(n) 0xB14 + ((n)*0x20)) // Device OUT Endpoint 0 DMA Address + +#define EP_FIFO(n) 0x1000 + ((n)*0x1000)) + +#define PCGCCTL 0x0E00 + +// +#define BASE_REGISTER_OFFSET 0x0 +#define REGISTER_SET_SIZE 0x200 + +// Power Reg Bits +#define USB_RESET 0x8 +#define MCU_RESUME 0x4 +#define SUSPEND_MODE 0x2 +#define SUSPEND_MODE_ENABLE_CTRL 0x1 + +// EP0 CSR +#define EP0_OUT_PACKET_RDY 0x1 +#define EP0_IN_PACKET_RDY 0x2 +#define EP0_SENT_STALL 0x4 +#define DATA_END 0x8 +#define SETUP_END 0x10 +#define EP0_SEND_STALL 0x20 +#define SERVICED_OUT_PKY_RDY 0x40 +#define SERVICED_SETUP_END 0x80 + +// IN_CSR1_REG Bit definitions +#define IN_PACKET_READY 0x1 +#define UNDER_RUN 0x4 // Iso Mode Only +#define FLUSH_IN_FIFO 0x8 +#define IN_SEND_STALL 0x10 +#define IN_SENT_STALL 0x20 +#define IN_CLR_DATA_TOGGLE 0x40 + +// OUT_CSR1_REG Bit definitions +#define OUT_PACKET_READY 0x1 +#define FLUSH_OUT_FIFO 0x10 +#define OUT_SEND_STALL 0x20 +#define OUT_SENT_STALL 0x40 +#define OUT_CLR_DATA_TOGGLE 0x80 + +// IN_CSR2_REG Bit definitions +#define IN_DMA_INT_DISABLE 0x10 +#define SET_MODE_IN 0x20 + +#define EPTYPE (0x3<<18) +#define SET_TYPE_CONTROL (0x0<<18) +#define SET_TYPE_ISO (0x1<<18) +#define SET_TYPE_BULK (0x2<<18) +#define SET_TYPE_INTERRUPT (0x3<<18) + +#define AUTO_MODE 0x80 + +// OUT_CSR2_REG Bit definitions +#define AUTO_CLR 0x40 +#define OUT_DMA_INT_DISABLE 0x20 + +// Can be used for Interrupt and Interrupt Enable Reg - common bit def +#define EP0_IN_INT (0x1<<0) +#define EP1_IN_INT (0x1<<1) +#define EP2_IN_INT (0x1<<2) +#define EP3_IN_INT (0x1<<3) +#define EP4_IN_INT (0x1<<4) +#define EP5_IN_INT (0x1<<5) +#define EP6_IN_INT (0x1<<6) +#define EP7_IN_INT (0x1<<7) +#define EP8_IN_INT (0x1<<8) +#define EP9_IN_INT (0x1<<9) +#define EP10_IN_INT (0x1<<10) +#define EP11_IN_INT (0x1<<11) +#define EP12_IN_INT (0x1<<12) +#define EP13_IN_INT (0x1<<13) +#define EP14_IN_INT (0x1<<14) +#define EP15_IN_INT (0x1<<15) +#define EP0_OUT_INT (0x1<<16) +#define EP1_OUT_INT (0x1<<17) +#define EP2_OUT_INT (0x1<<18) +#define EP3_OUT_INT (0x1<<19) +#define EP4_OUT_INT (0x1<<20) +#define EP5_OUT_INT (0x1<<21) +#define EP6_OUT_INT (0x1<<22) +#define EP7_OUT_INT (0x1<<23) +#define EP8_OUT_INT (0x1<<24) +#define EP9_OUT_INT (0x1<<25) +#define EP10_OUT_INT (0x1<<26) +#define EP11_OUT_INT (0x1<<27) +#define EP12_OUT_INT (0x1<<28) +#define EP13_OUT_INT (0x1<<29) +#define EP14_OUT_INT (0x1<<30) +#define EP15_OUT_INT (0x1<<31) + +// GOTGINT +#define SesEndDet (0x1<<2) + +// GRSTCTL +#define TxFFlsh (0x1<<5) +#define RxFFlsh (0x1<<4) +#define INTknQFlsh (0x1<<3) +#define FrmCntrRst (0x1<<2) +#define HSftRst (0x1<<1) +#define CSftRst (0x1<<0) + +#define CLEAR_ALL_EP_INTRS 0xffffffff + +#define EP_INTERRUPT_DISABLE_ALL 0x0 // Bits to write to EP_INT_EN_REG - Use CLEAR + +// DMA control register bit definitions +#define RUN_OB 0x80 +#define STATE 0x70 +#define DEMAND_MODE 0x8 +#define OUT_DMA_RUN 0x4 +#define IN_DMA_RUN 0x2 +#define DMA_MODE_EN 0x1 + + +#define REAL_PHYSICAL_ADDR_EP0_FIFO (0x520001c0) //Endpoint 0 FIFO +#define REAL_PHYSICAL_ADDR_EP1_FIFO (0x520001c4) //Endpoint 1 FIFO +#define REAL_PHYSICAL_ADDR_EP2_FIFO (0x520001c8) //Endpoint 2 FIFO +#define REAL_PHYSICAL_ADDR_EP3_FIFO (0x520001cc) //Endpoint 3 FIFO +#define REAL_PHYSICAL_ADDR_EP4_FIFO (0x520001d0) //Endpoint 4 FIFO + +// GAHBCFG +#define MODE_DMA (1<<5) +#define MODE_SLAVE (0<<5) +#define BURST_SINGLE (0<<1) +#define BURST_INCR (1<<1) +#define BURST_INCR4 (3<<1) +#define BURST_INCR8 (5<<1) +#define BURST_INCR16 (7<<1) +#define GBL_INT_MASK (0<<0) +#define GBL_INT_UNMASK (1<<0) + +// For USB DMA +//BOOL InitUsbdDriverGlobals(void); //:-) +//void UsbdDeallocateVm(void); //:-) +//BOOL UsbdAllocateVm(void); //:-) +//void UsbdInitDma(int epnum, int bufIndex,int bufOffset); //:-) + + +//by Kevin + +////////////////////////////////////////////////////////////////////////// + +/* +inline u32 ReadReg( + u32 uiOffset + ) +{ + volatile u32 *pbReg = ctrlr_base_reg_addr(uiOffset); + u32 uiValue = (u32) *pbReg; + return uiValue; +} + +inline void WriteReg( + u32 uiOffset, + u32 bValue + ) +{ + volatile ULONG *pbReg = ctrlr_base_reg_addr(uiOffset); + *pbReg = (ULONG) bValue; +} + +inline void UpdateReg( + u32 uiOffset, + u32 bValue + ) +{ + WriteReg(uiOffset, (ReadReg(uiOffset) | bValue)); +}; + +inline void ClearReg( + u32 uiOffeset, + u32 bValue + ) +{ + WriteReg(uiOffeset, (ReadReg(uiOffeset) & ~bValue)); +}; +*/ + +#ifdef __cplusplus +} +#endif + +#endif + + diff --git a/drivers/usb/host/s3c-otg/s3c-otg-hcdi-debug.h b/drivers/usb/host/s3c-otg/s3c-otg-hcdi-debug.h new file mode 100644 index 0000000..20416b1 --- /dev/null +++ b/drivers/usb/host/s3c-otg/s3c-otg-hcdi-debug.h @@ -0,0 +1,88 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * @file s3c-otg-hcdi-debug.c + * @brief It provides debug functions for display message \n + * @version + * -# Jun 9,2008 v1.0 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Creating the initial version of this code \n + * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Optimizing for performance \n + * @see None + ****************************************************************************/ +/**************************************************************************** + * 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 _S3C_OTG_HCDI_DEBUG_H_ +#define _S3C_OTG_HCDI_DEBUG_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#define OTG_DEBUG + +#ifdef OTG_DEBUG +#if 0 +#include <linux/stddef.h> +#endif + +#define OTG_DBG_OTGHCDI_DRIVER true +#define OTG_DBG_OTGHCDI_HCD false +#define OTG_DBG_OTGHCDI_KAL false +#define OTG_DBG_OTGHCDI_LIST false +#define OTG_DBG_OTGHCDI_MEM false + +#define OTG_DBG_TRANSFER false +#define OTG_DBG_SCHEDULE false +#define OTG_DBG_OCI false +#define OTG_DBG_DONETRASF false +#define OTG_DBG_ISR false +#define OTG_DBG_ROOTHUB false + + +#include <linux/kernel.h> //for printk + +#define otg_err(is_active, msg...) \ + do{ if (/*(is_active) == */true)\ + {\ + pr_err("otg_err: in %s()::%05d ", __func__ , __LINE__); \ + pr_err("=> " msg); \ + }\ + }while(0) + +#define otg_dbg(is_active, msg...) \ + do{ if ((is_active) == true)\ + {\ + pr_info("otg_dbg: in %s()::%05d ", __func__, __LINE__); \ + pr_info("=> " msg); \ + }\ + }while(0) + +#else //OTG_DEBUG + +# define otg_err(is_active, msg...) do{}while(0) +# define otg_dbg(is_active, msg...) do{}while(0) + +#endif + + +#ifdef __cplusplus +} +#endif + +#endif /* _S3C_OTG_HCDI_DEBUG_H_ */ diff --git a/drivers/usb/host/s3c-otg/s3c-otg-hcdi-driver.c b/drivers/usb/host/s3c-otg/s3c-otg-hcdi-driver.c new file mode 100644 index 0000000..f99261f --- /dev/null +++ b/drivers/usb/host/s3c-otg/s3c-otg-hcdi-driver.c @@ -0,0 +1,386 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * @file s3c-otg-hcdi-driver.c + * @brief It provides functions related with module for OTGHCD driver. \n + * @version + * -# Jun 9,2008 v1.0 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Creating the initial version of this code \n + * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Optimizing for performance \n + * -# Aug 18,2008 v1.3 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Modifying for successful rmmod & disconnecting \n + * @see None + * + ****************************************************************************/ +/**************************************************************************** + * 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 "s3c-otg-hcdi-driver.h" +#include "../../gadget/s3c_udc.h" +extern void otg_phy_off(void); + +/** + * static int s5pc110_otg_drv_probe (struct platform_device *pdev) + * + * @brief probe function of OTG hcd platform_driver + * + * @param [in] pdev : pointer of platform_device of otg hcd platform_driver + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If fail \n + * @remark + * it allocates resources of it and call other modules' init function. + * then call usb_create_hcd, usb_add_hcd, s5pc110_otghcd_start functions + */ + +struct usb_hcd* g_pUsbHcd = NULL; + +static void otg_power_work(struct work_struct *work) +{ + struct sec_otghost *otghost = container_of(work, + struct sec_otghost, work); + struct sec_otghost_data *hdata = otghost->otg_data; + + if (hdata && hdata->set_pwr_cb) { + hdata->set_pwr_cb(0); +#ifdef CONFIG_USB_HOST_NOTIFY + if (g_pUsbHcd) + host_state_notify(&g_pUsbHcd->ndev, NOTIFY_HOST_OVERCURRENT); +#endif + } else { + otg_err(true, "invalid otghost data\n"); + } +} + +static int s5pc110_otg_drv_probe (struct platform_device *pdev) +{ + int ret_val = 0; + u32 reg_val = 0; + struct sec_otghost *otghost = NULL; + struct sec_otghost_data *otg_data = dev_get_platdata(&pdev->dev); + + otg_dbg(OTG_DBG_OTGHCDI_DRIVER, "s3c_otg_drv_probe\n"); + + reset_scheduler_numbers(); + /*init for host mode*/ + /** + Allocate memory for the base HCD & Initialize the base HCD. + */ + g_pUsbHcd = usb_create_hcd(&s5pc110_otg_hc_driver, &pdev->dev, + "s3cotg");/*pdev->dev.bus_id*/ + if (g_pUsbHcd == NULL) { + ret_val = -ENOMEM; + otg_err(OTG_DBG_OTGHCDI_DRIVER, + "failed to usb_create_hcd\n"); + goto err_out_clk; + } + + /* mapping hcd resource & device resource*/ + + g_pUsbHcd->rsrc_start = pdev->resource[0].start; + g_pUsbHcd->rsrc_len = pdev->resource[0].end - + pdev->resource[0].start + 1; + + if (!request_mem_region(g_pUsbHcd->rsrc_start, g_pUsbHcd->rsrc_len, + gHcdName)) { + otg_err(OTG_DBG_OTGHCDI_DRIVER, + "failed to request_mem_region\n"); + ret_val = -EBUSY; + goto err_out_create_hcd; + } + + + /* Physical address => Virtual address */ + g_pUsbHcd->regs = S3C_VA_OTG; + g_pUsbHcd->self.otg_port = 1; + + g_pUDCBase = (u8 *)g_pUsbHcd->regs; + + otghost = hcd_to_sec_otghost(g_pUsbHcd); + + if (otghost == NULL) { + otg_err(true, "failed to get otghost hcd\n"); + ret_val = USB_ERR_FAIL; + goto err_out_create_hcd; + } + otghost->otg_data = otg_data; + + if ((s3c_get_drivermode()) & USB_OTG_DRIVER_S3CFSLS) { + otghost->is_hs = 0; // force USB 1.x mode + } else { + otghost->is_hs = 1; + } + + INIT_WORK(&otghost->work, otg_power_work); + otghost->wq = create_singlethread_workqueue("sec_otghostd"); + + /* call others' init() */ + ret_val = otg_hcd_init_modules(otghost); + if (ret_val != USB_ERR_SUCCESS) { + otg_err(OTG_DBG_OTGHCDI_DRIVER, + "failed to otg_hcd_init_modules\n"); + ret_val = USB_ERR_FAIL; + goto err_out_create_hcd; + } + + /** + * Attempt to ensure this device is really a s5pc110 USB-OTG Controller. + * Read and verify the SNPSID register contents. The value should be + * 0x45F42XXX, which corresponds to "OT2", as in "OTG version 2.XX". + */ + reg_val = read_reg_32(0x40); + if ((reg_val & 0xFFFFF000) != 0x4F542000) { + otg_err(OTG_DBG_OTGHCDI_DRIVER, + "Bad value for SNPSID: 0x%x\n", reg_val); + ret_val = -EINVAL; + goto err_out_create_hcd_init; + } +#ifdef CONFIG_USB_HOST_NOTIFY + if (otg_data->host_notify) { + g_pUsbHcd->host_notify = otg_data->host_notify; + g_pUsbHcd->ndev.name = dev_name(&pdev->dev); + ret_val = host_notify_dev_register(&g_pUsbHcd->ndev); + if (ret_val) { + otg_err(OTG_DBG_OTGHCDI_DRIVER, + "Failed to host_notify_dev_register\n"); + goto err_out_create_hcd_init; + } + } +#endif +#ifdef CONFIG_USB_SEC_WHITELIST + if (otg_data->sec_whlist_table_num) + g_pUsbHcd->sec_whlist_table_num = otg_data->sec_whlist_table_num; +#endif + + /* + * Finish generic HCD initialization and start the HCD. This function + * allocates the DMA buffer pool, registers the USB bus, requests the + * IRQ line, and calls s5pc110_otghcd_start method. + */ + ret_val = usb_add_hcd(g_pUsbHcd, + pdev->resource[1].start, IRQF_DISABLED); + if (ret_val < 0) { + goto err_out_host_notify_register; + } + + otg_dbg(OTG_DBG_OTGHCDI_DRIVER, + "OTG HCD Initialized HCD, bus=%s, usbbus=%d\n", + "C110 OTG Controller", g_pUsbHcd->self.busnum); + + /* otg_print_registers(); */ + + wake_lock_init(&otghost->wake_lock, WAKE_LOCK_SUSPEND, "usb_otg"); + wake_lock(&otghost->wake_lock); + + return USB_ERR_SUCCESS; +err_out_host_notify_register: +#ifdef CONFIG_USB_HOST_NOTIFY + host_notify_dev_unregister(&g_pUsbHcd->ndev); +#endif + +err_out_create_hcd_init: + otg_hcd_deinit_modules(otghost); + release_mem_region(g_pUsbHcd->rsrc_start, g_pUsbHcd->rsrc_len); + +err_out_create_hcd: + usb_put_hcd(g_pUsbHcd); + +err_out_clk: + + return ret_val; +} + +/** + * static int s5pc110_otg_drv_remove (struct platform_device *dev) + * + * @brief remove function of OTG hcd platform_driver + * + * @param [in] pdev : pointer of platform_device of otg hcd platform_driver + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If fail \n + * @remark + * This function is called when the otg device unregistered with the + * s5pc110_otg_driver. This happens, for example, when the rmmod command is + * executed. The device may or may not be electrically present. If it is + * present, the driver stops device processing. Any resources used on behalf + * of this device are freed. + */ +static int s5pc110_otg_drv_remove (struct platform_device *dev) +{ + struct sec_otghost *otghost = NULL; + + otg_dbg(OTG_DBG_OTGHCDI_DRIVER, "s5pc110_otg_drv_remove\n"); + + otghost = hcd_to_sec_otghost(g_pUsbHcd); + +#ifdef CONFIG_USB_HOST_NOTIFY + host_notify_dev_unregister(&g_pUsbHcd->ndev); +#endif + + otg_hcd_deinit_modules(otghost); + + destroy_workqueue(otghost->wq); + + wake_unlock(&otghost->wake_lock); + wake_lock_destroy(&otghost->wake_lock); + + usb_remove_hcd(g_pUsbHcd); + + release_mem_region(g_pUsbHcd->rsrc_start, g_pUsbHcd->rsrc_len); + + usb_put_hcd(g_pUsbHcd); + + otg_phy_off(); + + return USB_ERR_SUCCESS; +} + +/** + * @struct s5pc110_otg_driver + * + * @brief + * This structure defines the methods to be called by a bus driver + * during the lifecycle of a device on that bus. Both drivers and + * devices are registered with a bus driver. The bus driver matches + * devices to drivers based on information in the device and driver + * structures. + * + * The probe function is called when the bus driver matches a device + * to this driver. The remove function is called when a device is + * unregistered with the bus driver. + */ +struct platform_driver s5pc110_otg_driver = { + .probe = s5pc110_otg_drv_probe, + .remove = s5pc110_otg_drv_remove, + .shutdown = usb_hcd_platform_shutdown, + .driver = { + .name = "s3c_otghcd", + .owner = THIS_MODULE, + }, +}; + +/** + * static int __init s5pc110_otg_module_init(void) + * + * @brief module_init function + * + * @return it returns result of platform_driver_register + * @remark + * This function is called when the s5pc110_otg_driver is installed with the + * insmod command. It registers the s5pc110_otg_driver structure with the + * appropriate bus driver. This will cause the s5pc110_otg_driver_probe function + * to be called. In addition, the bus driver will automatically expose + * attributes defined for the device and driver in the special sysfs file + * system. + */ + /* +static int __init s5pc110_otg_module_init(void) +{ + int ret_val = 0; + + otg_dbg(OTG_DBG_OTGHCDI_DRIVER, + "s3c_otg_module_init \n"); + + ret_val = platform_driver_register(&s5pc110_otg_driver); + if (ret_val < 0) + { + otg_err(OTG_DBG_OTGHCDI_DRIVER, + "platform_driver_register \n"); + } + return ret_val; +} */ + +/** + * static void __exit s5pc110_otg_module_exit(void) + * + * @brief module_exit function + * + * @remark + * This function is called when the driver is removed from the kernel + * with the rmmod command. The driver unregisters itself with its bus + * driver. + */ +static void __exit s5pc110_otg_module_exit(void) +{ + otg_dbg(OTG_DBG_OTGHCDI_DRIVER, + "s3c_otg_module_exit \n"); + platform_driver_unregister(&s5pc110_otg_driver); +} + +/* for debug */ +void otg_print_registers(void) +{ + /* USB PHY Control Registers */ + otg_dbg(OTG_DBG_OTGHCDI_DRIVER, + "USB_CONTROL = 0x%x.\n", readl(0xfb10e80c)); + otg_dbg(OTG_DBG_OTGHCDI_DRIVER, + "UPHYPWR = 0x%x.\n", readl(S3C_USBOTG_PHYPWR)); + otg_dbg(OTG_DBG_OTGHCDI_DRIVER, + "UPHYCLK = 0x%x.\n", readl(S3C_USBOTG_PHYCLK)); + otg_dbg(OTG_DBG_OTGHCDI_DRIVER, + "URSTCON = 0x%x.\n", readl(S3C_USBOTG_RSTCON)); + + /* OTG LINK Core registers (Core Global Registers) */ + otg_dbg(OTG_DBG_OTGHCDI_DRIVER, + "GOTGCTL = 0x%x.\n", read_reg_32(GOTGCTL)); + otg_dbg(OTG_DBG_OTGHCDI_DRIVER, + "GOTGINT = 0x%x.\n", read_reg_32(GOTGINT)); + otg_dbg(OTG_DBG_OTGHCDI_DRIVER, + "GAHBCFG = 0x%x.\n", read_reg_32(GAHBCFG)); + otg_dbg(OTG_DBG_OTGHCDI_DRIVER, + "GUSBCFG = 0x%x.\n", read_reg_32(GUSBCFG)); + otg_dbg(OTG_DBG_OTGHCDI_DRIVER, + "GINTSTS = 0x%x.\n", read_reg_32(GINTSTS)); + otg_dbg(OTG_DBG_OTGHCDI_DRIVER, + "GINTMSK = 0x%x.\n", read_reg_32(GINTMSK)); + + /* Host Mode Registers */ + otg_dbg(OTG_DBG_OTGHCDI_DRIVER, + "HCFG = 0x%x.\n", read_reg_32(HCFG)); + otg_dbg(OTG_DBG_OTGHCDI_DRIVER, + "HPRT = 0x%x.\n", read_reg_32(HPRT)); + otg_dbg(OTG_DBG_OTGHCDI_DRIVER, + "HFIR = 0x%x.\n", read_reg_32(HFIR)); + + /* Synopsys ID */ + otg_dbg(OTG_DBG_OTGHCDI_DRIVER, + "GSNPSID = 0x%x.\n", read_reg_32(GSNPSID)); + + /* HWCFG */ + otg_dbg(OTG_DBG_OTGHCDI_DRIVER, + "GHWCFG1 = 0x%x.\n", read_reg_32(GHWCFG1)); + otg_dbg(OTG_DBG_OTGHCDI_DRIVER, + "GHWCFG2 = 0x%x.\n", read_reg_32(GHWCFG2)); + otg_dbg(OTG_DBG_OTGHCDI_DRIVER, + "GHWCFG3 = 0x%x.\n", read_reg_32(GHWCFG3)); + otg_dbg(OTG_DBG_OTGHCDI_DRIVER, + "GHWCFG4 = 0x%x.\n", read_reg_32(GHWCFG4)); + + /* PCGCCTL */ + otg_dbg(OTG_DBG_OTGHCDI_DRIVER, + "PCGCCTL = 0x%x.\n", read_reg_32(PCGCCTL)); +} + +/* +module_init(s5pc110_otg_module_init); +module_exit(s5pc110_otg_module_exit); +*/ + +MODULE_DESCRIPTION("OTG USB HOST controller driver"); +MODULE_AUTHOR("SAMSUNG / System LSI / EMSP"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/s3c-otg/s3c-otg-hcdi-driver.h b/drivers/usb/host/s3c-otg/s3c-otg-hcdi-driver.h new file mode 100644 index 0000000..4488df2 --- /dev/null +++ b/drivers/usb/host/s3c-otg/s3c-otg-hcdi-driver.h @@ -0,0 +1,75 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * @file s3c-otg-hcdi-driver.h + * @brief header of s3c-otg-hcdi-driver \n + * @version + * -# Jun 9,2008 v1.0 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Creating the initial version of this code \n + * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Optimizing for performance \n + * -# Aug 18,2008 v1.3 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Modifying for successful rmmod & disconnecting \n + * @see None + ****************************************************************************/ +/**************************************************************************** + * 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 _S3C_OTG_HCDI_DRIVER_H_ +#define _S3C_OTG_HCDI_DRIVER_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include <linux/module.h> +#include <linux/init.h> +//#include <linux/clk.h> //for clk_get, clk_enable etc. + +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/errno.h> +#include <linux/interrupt.h> //for SA_SHIRQ +#include <mach/map.h> //address for smdk +#include <linux/dma-mapping.h> //dma_alloc_coherent +#include <linux/ioport.h> //request_mem_request ... +#include <asm/irq.h> //for IRQ_OTG +#include <linux/clk.h> + + +#include "s3c-otg-common-common.h" +#include "s3c-otg-common-regdef.h" + +#include "s3c-otg-hcdi-debug.h" +#include "s3c-otg-hcdi-hcd.h" +#include "s3c-otg-hcdi-kal.h" + + +volatile u8 * g_pUDCBase; + +static const char gHcdName[] = "EMSP_OTG_HCD"; + +//extern int otg_hcd_init_modules(struct sec_otghost *otghost); +//extern void otg_hcd_deinit_modules(struct sec_otghost *otghost); + +//void otg_print_registers(); + +#ifdef __cplusplus +} +#endif + +#endif /* _S3C_OTG_HCDI_DRIVER_H_ */ diff --git a/drivers/usb/host/s3c-otg/s3c-otg-hcdi-hcd.c b/drivers/usb/host/s3c-otg/s3c-otg-hcdi-hcd.c new file mode 100644 index 0000000..2b440a3 --- /dev/null +++ b/drivers/usb/host/s3c-otg/s3c-otg-hcdi-hcd.c @@ -0,0 +1,708 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * @file s3c-otg-hcdi-hcd.c + * @brief implementation of structure hc_drive \n + * @version + * -# Jun 11,2008 v1.0 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Creating the initial version of this code \n + * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Optimizing for performance \n + * -# Aug 18,2008 v1.3 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Modifying for successful rmmod & disconnecting \n + * @see None + ****************************************************************************/ +/**************************************************************************** + * 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 "s3c-otg-hcdi-hcd.h" + +/** + * otg_hcd_init_modules(struct sec_otghost *otghost) + * + * @brief call other modules' init functions + * + * @return PASS : If success \n + * FAIL : If fail \n + */ +int otg_hcd_init_modules(struct sec_otghost *otghost) +{ + unsigned long spin_lock_flag = 0; + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "otg_hcd_init_modules\n"); + + spin_lock_init(&otghost->lock); + + spin_lock_irq_save_otg(&otghost->lock, spin_lock_flag); + + init_transfer(); + init_scheduler(); + oci_init(otghost); + + spin_unlock_irq_save_otg(&otghost->lock, spin_lock_flag); + + return USB_ERR_SUCCESS; +}; + +/** + * void otg_hcd_deinit_modules(struct sec_otghost *otghost) + * + * @brief call other modules' de-init functions + * + * @return PASS : If success \n + * FAIL : If fail \n + */ +void otg_hcd_deinit_modules(struct sec_otghost *otghost) +{ + unsigned long spin_lock_flag = 0; + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "otg_hcd_deinit_modules \n"); + + spin_lock_irq_save_otg(&otghost->lock, spin_lock_flag); + + deinit_transfer(otghost); + + spin_unlock_irq_save_otg(&otghost->lock, spin_lock_flag); +} + +/** + * irqreturn_t (*s5pc110_otghcd_irq) (struct usb_hcd *hcd) + * + * @brief interrupt handler of otg irq + * + * @param [in] hcd : pointer of usb_hcd + * + * @return IRQ_HANDLED \n + */ +irqreturn_t s5pc110_otghcd_irq(struct usb_hcd *hcd) +{ + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_irq \n"); + + spin_lock_otg(&otghost->lock); + otg_handle_interrupt(hcd); + spin_unlock_otg(&otghost->lock); + + return IRQ_HANDLED; +} + +/** + * int s5pc110_otghcd_start(struct usb_hcd *hcd) + * + * @brief initialize and start otg hcd + * + * @param [in] usb_hcd_p : pointer of usb_hcd + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + */ +int s5pc110_otghcd_start(struct usb_hcd *usb_hcd_p) +{ + struct usb_bus *usb_bus_p; + struct sec_otghost *otghost = hcd_to_sec_otghost(usb_hcd_p); + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_start \n"); + + usb_bus_p = hcd_to_bus(usb_hcd_p); + + /* Initialize and connect root hub if one is not already attached */ + if (usb_bus_p->root_hub) { + otg_dbg(OTG_DBG_OTGHCDI_HCD, "OTG HCD Has Root Hub\n"); + + /* Inform the HUB driver to resume. */ + otg_usbcore_resume_roothub(); + } else { + otg_err(OTG_DBG_OTGHCDI_HCD, + "OTG HCD Does Not Have Root Hub\n"); + return USB_ERR_FAIL; + } + + set_bit(HCD_FLAG_POLL_RH,&usb_hcd_p->flags); + usb_hcd_p->uses_new_polling = 1; + + /* init bus state before enable irq */ + usb_hcd_p->state = HC_STATE_RUNNING; + + oci_start(otghost); /* enable irq */ + + return USB_ERR_SUCCESS; +} + +/** + * void s5pc110_otghcd_stop(struct usb_hcd *hcd) + * + * @brief deinitialize and stop otg hcd + * + * @param [in] hcd : pointer of usb_hcd + * + */ +void s5pc110_otghcd_stop(struct usb_hcd *hcd) +{ + unsigned long spin_lock_flag = 0; + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_stop \n"); + + otg_hcd_deinit_modules(otghost); + + spin_lock_irq_save_otg(&otghost->lock, spin_lock_flag); + + oci_stop(otghost); + root_hub_feature(hcd, 0, ClearPortFeature, USB_PORT_FEAT_POWER, NULL); + + spin_unlock_irq_save_otg(&otghost->lock, spin_lock_flag); +} + +/** + * void s5pc110_otghcd_shutdown(struct usb_hcd *hcd) + * + * @brief shutdown otg hcd + * + * @param [in] usb_hcd_p : pointer of usb_hcd + * + */ +void s5pc110_otghcd_shutdown(struct usb_hcd *usb_hcd_p) +{ + unsigned long spin_lock_flag = 0; + struct sec_otghost *otghost = hcd_to_sec_otghost(usb_hcd_p); + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_shutdown \n"); + otg_hcd_deinit_modules(otghost); + + spin_lock_irq_save_otg(&otghost->lock, spin_lock_flag); + + oci_stop(otghost); + root_hub_feature(usb_hcd_p, 0, ClearPortFeature, USB_PORT_FEAT_POWER, NULL); + + spin_unlock_irq_save_otg(&otghost->lock, spin_lock_flag); + + free_irq(IRQ_OTG, usb_hcd_p); + usb_hcd_p->state = HC_STATE_HALT; + otg_usbcore_hc_died(); +} + + +/** + * int s5pc110_otghcd_get_frame_number(struct usb_hcd *hcd) + * + * @brief get currnet frame number + * + * @param [in] hcd : pointer of usb_hcd + * + * @return ret : frame number \n + */ +int s5pc110_otghcd_get_frame_number(struct usb_hcd *hcd) +{ + int ret = 0; + unsigned long spin_lock_flag = 0; + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_get_frame_number \n"); + + spin_lock_irq_save_otg(&otghost->lock, spin_lock_flag); + ret = oci_get_frame_num(); + spin_unlock_irq_save_otg(&otghost->lock, spin_lock_flag); + + return ret; +} + + +/** + * int s5pc110_otghcd_urb_enqueue() + * + * @brief enqueue a urb to otg hcd + * + * @param [in] hcd : pointer of usb_hcd + * [in] ep : pointer of usb_host_endpoint + * [in] urb : pointer of urb + * [in] mem_flags : type of gfp_t + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + */ +int s5pc110_otghcd_urb_enqueue (struct usb_hcd *hcd, + struct urb *urb, + gfp_t mem_flags) +{ + int ret_val = 0; + u32 trans_flag = 0; + u32 return_td_addr = 0; + u8 dev_speed, ed_type = 0, additional_multi_count; + u16 max_packet_size; + + u8 dev_addr = 0; + u8 ep_num = 0; + bool f_is_ep_in = true; + u8 interval = 0; + u32 sched_frame = 0; + u8 hub_addr = 0; + u8 hub_port = 0; + bool f_is_do_split = false; + ed_t *target_ed = NULL; + isoch_packet_desc_t *new_isoch_packet_desc = NULL; + unsigned long spin_lock_flag = 0; + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + + if (!otghost->port_flag.b.port_connect_status) { + printk(KERN_ERR"%s %d\n", __func__, __LINE__); + return USB_ERR_NOIO; + } + + spin_lock_irq_save_otg(&otghost->lock, spin_lock_flag); + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_urb_enqueue \n"); + + /* check ep has ed_t or not */ + if(!(urb->ep->hcpriv)) { + /* for getting dev_speed */ + switch (urb->dev->speed) { + case USB_SPEED_HIGH : + otg_dbg(OTG_DBG_OTGHCDI_HCD, "HS_OTG \n"); + dev_speed = HIGH_SPEED_OTG; + break; + + case USB_SPEED_FULL : + otg_dbg(OTG_DBG_OTGHCDI_HCD, "FS_OTG \n"); + dev_speed = FULL_SPEED_OTG; + break; + + case USB_SPEED_LOW : + otg_dbg(OTG_DBG_OTGHCDI_HCD, "LS_OTG \n"); + dev_speed = LOW_SPEED_OTG; + break; + + default: + otg_err(OTG_DBG_OTGHCDI_HCD, + "unKnown Device Speed \n"); + spin_unlock_irq_save_otg(&otghost->lock, + spin_lock_flag); + return USB_ERR_FAIL; + } + + /* for getting ed_type */ + switch (usb_pipetype(urb->pipe)) { + case PIPE_BULK : + otg_dbg(OTG_DBG_OTGHCDI_HCD, "bulk transfer \n"); + ed_type = BULK_TRANSFER; + break; + + case PIPE_INTERRUPT : + otg_dbg(OTG_DBG_OTGHCDI_HCD, "interrupt transfer \n"); + ed_type = INT_TRANSFER; + break; + + case PIPE_CONTROL : + otg_dbg(OTG_DBG_OTGHCDI_HCD, "control transfer \n"); + ed_type = CONTROL_TRANSFER; + break; + + case PIPE_ISOCHRONOUS : + otg_dbg(OTG_DBG_OTGHCDI_HCD, "isochronous transfer \n"); + ed_type = ISOCH_TRANSFER; + break; + default: + otg_err(OTG_DBG_OTGHCDI_HCD, "unKnown ep type \n"); + spin_unlock_irq_save_otg(&otghost->lock, + spin_lock_flag); + return USB_ERR_FAIL; + } + + max_packet_size = usb_maxpacket(urb->dev, urb->pipe, + !(usb_pipein(urb->pipe))); + additional_multi_count = ((max_packet_size) >> 11) & 0x03; + dev_addr = usb_pipedevice(urb->pipe); + ep_num = usb_pipeendpoint(urb->pipe); + f_is_ep_in = usb_pipein(urb->pipe) ? true : false; + interval = (u8)(urb->interval); + sched_frame = (u8)(urb->start_frame); + + /* check */ + if(urb->dev->tt == NULL) { + otg_dbg(OTG_DBG_OTGHCDI_HCD, "urb->dev->tt == NULL\n"); + hub_port = 0; /* u8 hub_port */ + hub_addr = 0; /* u8 hub_addr */ + } + else { + hub_port = (u8)(urb->dev->ttport); + if (urb->dev->tt->hub) { + if (((dev_speed == FULL_SPEED_OTG) || + (dev_speed == LOW_SPEED_OTG)) && + (urb->dev->tt) && (urb->dev->tt->hub->devnum != 1)) { + if (otghost->is_hs) // only allow split transactions in HS mode + f_is_do_split = true; + } + hub_addr = (u8)(urb->dev->tt->hub->devnum); + } + if (urb->dev->tt->multi) { + hub_addr = 0x80; + } + } + otg_dbg(OTG_DBG_OTGHCDI_HCD, + "dev_spped =%d, hub_port=%d, hub_addr=%d\n", + dev_speed, hub_port, hub_addr); + + ret_val = create_ed(&target_ed); + if(ret_val != USB_ERR_SUCCESS) { + otg_err(OTG_DBG_OTGHCDI_HCD, + "fail to create_ed() \n"); + spin_unlock_irq_save_otg(&otghost->lock, + spin_lock_flag); + return ret_val; + } + + ret_val = init_ed( target_ed, + dev_addr, + ep_num, + f_is_ep_in, + dev_speed, + ed_type, + max_packet_size, + additional_multi_count, + interval, + sched_frame, + hub_addr, + hub_port, + f_is_do_split, + (void *)urb->ep); + + if(ret_val != USB_ERR_SUCCESS) { + otg_err(OTG_DBG_OTGHCDI_HCD, + "fail to init_ed() :err = %d \n",(int)ret_val); + otg_mem_free(target_ed); + spin_unlock_irq_save_otg(&otghost->lock, + spin_lock_flag); + return USB_ERR_FAIL; + } + + urb->ep->hcpriv = (void *)(target_ed); + } /* if(!(ep->hcpriv)) */ + else { + dev_addr = usb_pipedevice(urb->pipe); + if(((ed_t *)(urb->ep->hcpriv))->ed_desc.device_addr != dev_addr) { + ((ed_t *)urb->ep->hcpriv)->ed_desc.device_addr = dev_addr; + } + } + + target_ed = (ed_t *)urb->ep->hcpriv; + + if(urb->transfer_flags & URB_SHORT_NOT_OK) + trans_flag += USB_TRANS_FLAG_NOT_SHORT; + if (urb->transfer_flags & URB_ISO_ASAP) + trans_flag += USB_TRANS_FLAG_ISO_ASYNCH; + + if(ed_type == ISOCH_TRANSFER) { + otg_err(OTG_DBG_OTGHCDI_HCD, "ISO not yet supported \n"); + spin_unlock_irq_save_otg(&otghost->lock, spin_lock_flag); + return USB_ERR_FAIL; + } + + if (!HC_IS_RUNNING(hcd->state)) { + otg_err(OTG_DBG_OTGHCDI_HCD, "!HC_IS_RUNNING(hcd->state) \n"); + spin_unlock_irq_save_otg(&otghost->lock, spin_lock_flag); + return -USB_ERR_NODEV; + } + + /* in case of unlink-during-submit */ + if (urb->status != -EINPROGRESS) { + otg_err(OTG_DBG_OTGHCDI_HCD, "urb->status is -EINPROGRESS\n"); + urb->hcpriv = NULL; + spin_unlock_irq_save_otg(&otghost->lock, spin_lock_flag); + usb_hcd_giveback_urb(hcd, urb, urb->status); + return USB_ERR_SUCCESS; + } + + ret_val = issue_transfer(otghost, target_ed, (void *)NULL, (void *)NULL, + trans_flag, + (usb_pipetype(urb->pipe) == PIPE_CONTROL)?true:false, + (u32)urb->setup_packet, (u32)urb->setup_dma, + (u32)urb->transfer_buffer, (u32)urb->transfer_dma, + (u32)urb->transfer_buffer_length, + (u32)urb->start_frame,(u32)urb->number_of_packets, + new_isoch_packet_desc, (void *)urb, &return_td_addr); + + if(ret_val != USB_ERR_SUCCESS) { + otg_err(OTG_DBG_OTGHCDI_HCD, "fail to issue_transfer() \n"); + spin_unlock_irq_save_otg(&otghost->lock, spin_lock_flag); + return USB_ERR_FAIL; + } + urb->hcpriv = (void *)return_td_addr; + + spin_unlock_irq_save_otg(&otghost->lock, spin_lock_flag); + return USB_ERR_SUCCESS; +} + +/** + * int s5pc110_otghcd_urb_dequeue(struct usb_hcd *_hcd, struct urb *_urb ) + * + * @brief dequeue a urb to otg + * + * @param [in] _hcd : pointer of usb_hcd + * [in] _urb : pointer of urb + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + */ +int s5pc110_otghcd_urb_dequeue( + struct usb_hcd *_hcd, struct urb *_urb, int status) +{ + int ret_val = 0; + struct sec_otghost *otghost = hcd_to_sec_otghost(_hcd); + + unsigned long spin_lock_flag = 0; + td_t *cancel_td; + + spin_lock_irq_save_otg(&otghost->lock, spin_lock_flag); + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_urb_dequeue \n"); + + /* Dequeue should be performed only if endpoint is enabled */ + if (_urb->ep->enabled == 0) { + spin_unlock_irq_save_otg(&otghost->lock, spin_lock_flag); + usb_hcd_giveback_urb(_hcd, _urb, status); + return USB_ERR_SUCCESS; + } + + //kevinh read this from inside the spinlock + cancel_td = (td_t *)_urb->hcpriv; + + if (cancel_td == NULL) { + otg_err(OTG_DBG_OTGHCDI_HCD, "cancel_td is NULL\n"); + spin_unlock_irq_save_otg(&otghost->lock, spin_lock_flag); + return USB_ERR_FAIL; + } + otg_dbg(OTG_DBG_OTGHCDI_HCD, + "s5pc110_otghcd_urb_dequeue, status = %d\n", status); + + + ret_val = usb_hcd_check_unlink_urb(_hcd, _urb, status); + if( (ret_val) && (ret_val != -EIDRM) ) { + otg_dbg(OTG_DBG_OTGHCDI_HCD, "ret_val = %d\n", ret_val); + spin_unlock_irq_save_otg(&otghost->lock, spin_lock_flag); + usb_hcd_giveback_urb(_hcd, _urb, status); + return ret_val; + } + + if (!HC_IS_RUNNING(_hcd->state)) { + otg_err(OTG_DBG_OTGHCDI_HCD, "!HC_IS_RUNNING(hcd->state) \n"); + spin_unlock_irq_save_otg(&otghost->lock, spin_lock_flag); + otg_usbcore_giveback(cancel_td); + return USB_ERR_SUCCESS; + } + + ret_val = cancel_transfer(otghost, cancel_td->parent_ed_p, cancel_td); + if(ret_val != USB_ERR_DEQUEUED && ret_val != USB_ERR_NOELEMENT) { + otg_err(OTG_DBG_OTGHCDI_HCD, "fail to cancel_transfer() \n"); + otg_usbcore_giveback(cancel_td); + spin_unlock_irq_save_otg(&otghost->lock, spin_lock_flag); + return USB_ERR_FAIL; + } + otg_usbcore_giveback(cancel_td); + delete_td(otghost, cancel_td); + spin_unlock_irq_save_otg(&otghost->lock, spin_lock_flag); + return USB_ERR_SUCCESS; +} + +/** + * void s5pc110_otghcd_endpoint_disable( + * struct usb_hcd *hcd, + * struct usb_host_endpoint *ep) + * + * @brief disable a endpoint + * + * @param [in] hcd : pointer of usb_hcd + * [in] ep : pointer of usb_host_endpoint + */ +void s5pc110_otghcd_endpoint_disable( + struct usb_hcd *hcd, + struct usb_host_endpoint *ep) +{ + int ret_val = 0; + unsigned long spin_lock_flag = 0; + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_endpoint_disable \n"); + + if(!((ed_t *)ep->hcpriv)) + return; + + spin_lock_irq_save_otg(&otghost->lock, spin_lock_flag); + ret_val = delete_ed(otghost, (ed_t *)ep->hcpriv); + spin_unlock_irq_save_otg(&otghost->lock, spin_lock_flag); + + if(ret_val != USB_ERR_SUCCESS) { + otg_err(OTG_DBG_OTGHCDI_HCD, "fail to delete_ed() \n"); + return ; + } + + /* ep->hcpriv = NULL; delete_ed coveres it */ +} + +/** + * int s5pc110_otghcd_hub_status_data(struct usb_hcd *_hcd, char *_buf) + * + * @brief get status of root hub + * + * @param [in] _hcd : pointer of usb_hcd + * [inout] _buf : pointer of buffer for write a status data + * + * @return ret_val : return port status \n + */ +int s5pc110_otghcd_hub_status_data(struct usb_hcd *_hcd, char *_buf) +{ + int ret_val = 0; + unsigned long spin_lock_flag = 0; + struct sec_otghost *otghost = hcd_to_sec_otghost(_hcd); + + /* otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_hub_status_data \n"); */ + + /* if !USB_SUSPEND, root hub timers won't get shut down ... */ + if (!HC_IS_RUNNING(_hcd->state)) { + otg_dbg(OTG_DBG_OTGHCDI_HCD, + "_hcd->state is NOT HC_IS_RUNNING \n"); + return 0; + } + + spin_lock_irq_save_otg(&otghost->lock, spin_lock_flag); + ret_val = get_otg_port_status(_hcd, OTG_PORT_NUMBER, _buf); + spin_unlock_irq_save_otg(&otghost->lock, spin_lock_flag); + + return (int)ret_val; +} + +/** + * int s5pc110_otghcd_hub_control() + * + * @brief control root hub + * + * @param [in] hcd : pointer of usb_hcd + * [in] typeReq : type of control request + * [in] value : value + * [in] index : index + * [in] buf_p : pointer of urb + * [in] length : type of gfp_t + * + * @return ret_val : return root_hub_feature \n + */ +int +s5pc110_otghcd_hub_control ( + struct usb_hcd *hcd, + u16 typeReq, + u16 value, + u16 index, + char* buf_p, + u16 length +) +{ + int ret_val = 0; + unsigned long spin_lock_flag = 0; + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_hub_control \n"); + + spin_lock_irq_save_otg(&otghost->lock, spin_lock_flag); + + ret_val = root_hub_feature(hcd, OTG_PORT_NUMBER, + typeReq, value, (void *)buf_p); + + spin_unlock_irq_save_otg(&otghost->lock, spin_lock_flag); + if(ret_val != USB_ERR_SUCCESS) { + otg_err(OTG_DBG_OTGHCDI_HCD, "fail to root_hub_feature() \n"); + return ret_val; + } + return (int)ret_val; +} + +/** + * int s5pc110_otghcd_bus_suspend(struct usb_hcd *hcd) + * + * @brief suspend otg hcd + * + * @param [in] hcd : pointer of usb_hcd + * + * @return USB_ERR_SUCCESS \n + */ +int s5pc110_otghcd_bus_suspend(struct usb_hcd *hcd) +{ + unsigned long spin_lock_flag = 0; + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_bus_suspend \n"); + + spin_lock_irq_save_otg(&otghost->lock, spin_lock_flag); + bus_suspend(); + spin_unlock_irq_save_otg(&otghost->lock, spin_lock_flag); + + return USB_ERR_SUCCESS; +} + +/** + * int s5pc110_otghcd_bus_resume(struct usb_hcd *hcd) + * + * @brief resume otg hcd + * + * @param [in] hcd : pointer of usb_hcd + * + * @return USB_ERR_SUCCESS \n + */ +int s5pc110_otghcd_bus_resume(struct usb_hcd *hcd) +{ + unsigned long spin_lock_flag = 0; + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_bus_resume \n"); + + spin_lock_irq_save_otg(&otghost->lock, spin_lock_flag); + + if(bus_resume(otghost) != USB_ERR_SUCCESS) { + return USB_ERR_FAIL; + } + + spin_unlock_irq_save_otg(&otghost->lock, spin_lock_flag); + return USB_ERR_SUCCESS; +} + +/** + * int s5pc110_otghcd_start_port_reset(struct usb_hcd *hcd, unsigned port) + * + * @brief reset otg port + * + * @param [in] hcd : pointer of usb_hcd + * [in] port : number of port + * + * @return USB_ERR_SUCCESS : If success \n + * ret_val : If call fail \n + */ +int s5pc110_otghcd_start_port_reset(struct usb_hcd *hcd, unsigned port) +{ + int ret_val = 0; + + unsigned long spin_lock_flag = 0; + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_start_port_reset \n"); + + spin_lock_irq_save_otg(&otghost->lock, spin_lock_flag); + ret_val = reset_and_enable_port(hcd, OTG_PORT_NUMBER); + spin_unlock_irq_save_otg(&otghost->lock, spin_lock_flag); + + if(ret_val != USB_ERR_SUCCESS) { + otg_err(OTG_DBG_OTGHCDI_HCD, + "fail to reset_and_enable_port() \n"); + return ret_val; + } + return USB_ERR_SUCCESS; +} diff --git a/drivers/usb/host/s3c-otg/s3c-otg-hcdi-hcd.h b/drivers/usb/host/s3c-otg/s3c-otg-hcdi-hcd.h new file mode 100644 index 0000000..95764d8 --- /dev/null +++ b/drivers/usb/host/s3c-otg/s3c-otg-hcdi-hcd.h @@ -0,0 +1,152 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * @file s3c-otg-hcdi-hcd.h + * @brief header of s3c-otg-hcdi-hcd \n + * @version + * -# Jun 9,2008 v1.0 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Creating the initial version of this code \n + * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Optimizing for performance \n + * -# Aug 18,2008 v1.3 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Modifying for successful rmmod & disconnecting \n + * @see None + ****************************************************************************/ +/**************************************************************************** + * 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 _S3C_OTG_HCDI_HCD_H_ +#define _S3C_OTG_HCDI_HCD_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +//for IRQ_NONE (0) IRQ_HANDLED (1) IRQ_RETVAL(x) ((x) != 0) +#include <linux/interrupt.h> + +#include <linux/usb.h> + +#include "s3c-otg-hcdi-debug.h" +#include "s3c-otg-hcdi-kal.h" + +#include "s3c-otg-common-common.h" +#include "s3c-otg-common-datastruct.h" +#include "s3c-otg-common-const.h" + +#include "s3c-otg-transfer-transfer.h" +#include "s3c-otg-oci.h" +#include "s3c-otg-roothub.h" + +//placed in ISR +//void otg_handle_interrupt(struct usb_hcd *hcd); + +irqreturn_t s5pc110_otghcd_irq(struct usb_hcd *hcd); + +int s5pc110_otghcd_start(struct usb_hcd *hcd); +void s5pc110_otghcd_stop(struct usb_hcd *hcd); +void s5pc110_otghcd_shutdown(struct usb_hcd *hcd); + +int s5pc110_otghcd_get_frame_number(struct usb_hcd *hcd); + +int s5pc110_otghcd_urb_enqueue( + struct usb_hcd *hcd, + struct urb *urb, + gfp_t mem_flags); + +int s5pc110_otghcd_urb_dequeue( + struct usb_hcd *_hcd, + struct urb *_urb, + int status); + +void s5pc110_otghcd_endpoint_disable( + struct usb_hcd *hcd, + struct usb_host_endpoint *ep); + +int s5pc110_otghcd_hub_status_data( + struct usb_hcd *_hcd, + char *_buf); + +int s5pc110_otghcd_hub_control( + struct usb_hcd *hcd, + u16 type_req, + u16 value, + u16 index, + char * buf, + u16 length); + +int s5pc110_otghcd_bus_suspend(struct usb_hcd *hcd); +int s5pc110_otghcd_bus_resume(struct usb_hcd *hcd); +int s5pc110_otghcd_start_port_reset(struct usb_hcd *hcd, unsigned port); + +/** + * @struct hc_driver s5pc110_otg_hc_driver + * + * @brief implementation of hc_driver for OTG HCD + * + * describe in detail + */ +static const struct hc_driver s5pc110_otg_hc_driver = { + .description = "EMSP_OTGHCD", + .product_desc = "S3C OTGHCD", + .hcd_priv_size = sizeof(struct sec_otghost), + + .irq = s5pc110_otghcd_irq, + .flags = HCD_MEMORY | HCD_USB2, + + /** basic lifecycle operations */ + //.reset = + .start = s5pc110_otghcd_start, + //.suspend = , + //.resume = , + .stop = s5pc110_otghcd_stop, + .shutdown = s5pc110_otghcd_shutdown, + + /** managing i/o requests and associated device resources */ + .urb_enqueue = s5pc110_otghcd_urb_enqueue, + .urb_dequeue = s5pc110_otghcd_urb_dequeue, + .endpoint_disable = s5pc110_otghcd_endpoint_disable, + + /** scheduling support */ + .get_frame_number = s5pc110_otghcd_get_frame_number, + + /** root hub support */ + .hub_status_data = s5pc110_otghcd_hub_status_data, + .hub_control = s5pc110_otghcd_hub_control, + //.hub_irq_enable = + .bus_suspend = s5pc110_otghcd_bus_suspend, + .bus_resume = s5pc110_otghcd_bus_resume, + .start_port_reset = s5pc110_otghcd_start_port_reset, +}; + +static inline struct sec_otghost *hcd_to_sec_otghost (struct usb_hcd *hcd) +{ + return (struct sec_otghost *) (hcd->hcd_priv); +} +static inline struct usb_hcd *sec_otghost_to_hcd (struct sec_otghost *otghost) +{ + return container_of ((void *) otghost, struct usb_hcd, hcd_priv); +} + +int otg_hcd_init_modules(struct sec_otghost *otghost); +void otg_hcd_deinit_modules(struct sec_otghost *otghost); + +#ifdef __cplusplus +} +#endif +#endif /* _S3C_OTG_HCDI_HCD_H_ */ + diff --git a/drivers/usb/host/s3c-otg/s3c-otg-hcdi-kal.h b/drivers/usb/host/s3c-otg/s3c-otg-hcdi-kal.h new file mode 100644 index 0000000..23bded6 --- /dev/null +++ b/drivers/usb/host/s3c-otg/s3c-otg-hcdi-kal.h @@ -0,0 +1,420 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * @file s3c-otg-hcdi-kal.h + * @brief header of s3c-otg-hcdi-kal \n + * @version + * -# Jun 9,2008 v1.0 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Creating the initial version of this code \n + * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Optimizing for performance \n + * -# Aug 18,2008 v1.3 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Modifying for successful rmmod & disconnecting \n + * @see None + ****************************************************************************/ +/**************************************************************************** + * 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 _S3C_OTG_HCDI_KAL_H_ +#define _S3C_OTG_HCDI_KAL_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "s3c-otg-hcdi-debug.h" +#include "s3c-otg-common-common.h" +#include "s3c-otg-common-datastruct.h" +#include "s3c-otg-common-const.h" + +#include <asm/io.h> //for readl, writel +#include <linux/usb/ch9.h> //for usb_device_driver, enum usb_device_speed +#include <linux/usb.h> +#include <linux/usb/hcd.h> +#include <mach/map.h> +#include <plat/regs-otg.h> + +extern volatile u8 * g_pUDCBase; +extern struct usb_hcd* g_pUsbHcd; + +#include <linux/spinlock.h> +#define SPINLOCK_t spinlock_t +#define SPIN_LOCK_INIT SPIN_LOCK_UNLOCKED + +#define spin_lock_otg(lock) spin_lock(lock) +#define spin_lock_irg_otg(lock) spin_lock_irq(lock) +#define spin_lock_irq_save_otg(lock, flags) spin_lock_irqsave(lock, flags) + +#define spin_unlock_otg(lock) spin_unlock(lock) +#define spin_unlock_irq_otg(lock) spin_unlock_irq(lock) +#define spin_unlock_irq_save_otg(lock, flags) spin_unlock_irqrestore(lock, flags) + +#define ctrlr_base_reg_addr(offset) \ + ((volatile unsigned int *)((g_pUDCBase) + (offset))) +/** + * otg_kal_make_ep_null + * + * @brief make ep->hcpriv NULL + * + * @param [in] pdelete_ed : pointer of ed + * + * @return void \n + */ +static inline void +otg_kal_make_ep_null +( + ed_t *pdelete_ed +) +{ + ((struct usb_host_endpoint *)(pdelete_ed->ed_private))->hcpriv = NULL; +} +//--------------------------------------------------------------------------------------- + +/** + * otg_kal_is_ep_null + * + * @brief check ep->hcpriv is NULL or not + * + * @param [in] pdelete_ed : pointer of ed + * + * @return bool \n + */ +static inline bool +otg_kal_is_ep_null +( + ed_t *pdelete_ed +) +{ + if (((struct usb_host_endpoint *)(pdelete_ed->ed_private))->hcpriv == NULL) + return true; + else + return false; +} +//--------------------------------------------------------------------------------------- + + +/** + * int otg_usbcore_get_calc_bustime() + * + * @brief get bus time of usbcore + * + * @param [in] speed : usb speed + * [in] is_input : input or not + * [in] is_isoch : isochronous or not + * [in] byte_count : bytes + * + * @return bus time of usbcore \n + */ +static inline int +otg_usbcore_get_calc_bustime +( + u8 speed, + bool is_input, + bool is_isoch, + unsigned int byte_count +) +{ + unsigned int convert_speed = 0; + + otg_dbg(OTG_DBG_OTGHCDI_KAL, "otg_usbcore_get_calc_bustime \n"); +/* enum usb_device_speed { + USB_SPEED_UNKNOWN = 0, + USB_SPEED_LOW, USB_SPEED_FULL, + USB_SPEED_HIGH, + USB_SPEED_VARIABLE, };*/ + switch(speed) { + case HIGH_SPEED_OTG : + convert_speed = USB_SPEED_HIGH; + break; + + case FULL_SPEED_OTG : + convert_speed = USB_SPEED_FULL; + break; + + case LOW_SPEED_OTG : + convert_speed = USB_SPEED_LOW; + break; + + default: + convert_speed = USB_SPEED_UNKNOWN; + break; + } + return usb_calc_bus_time(convert_speed, is_input, (unsigned int)is_isoch, byte_count); +} + +//------------------------------------------------------------------------------- + +/** + * void otg_usbcore_giveback(td_t td_p) + * + * @brief give-back a td as urb + * + * @param [in] td_p : pointer of td_t to give back + * + * @return void \n + */ +static inline void +otg_usbcore_giveback(td_t * td_p) +{ + struct urb *urb_p = NULL; + + otg_dbg(OTG_DBG_OTGHCDI_KAL, "otg_usbcore_giveback \n"); + + if (td_p->td_private == NULL) + { + otg_err(OTG_DBG_OTGHCDI_KAL, + "td_p->td_private == NULL \n"); + return; + } + + urb_p = (struct urb *)td_p->td_private; + + urb_p->actual_length = (int)(td_p->transferred_szie); + urb_p->status = (int)(td_p->error_code); + urb_p->error_count = (int)(td_p->err_cnt); + urb_p->hcpriv = NULL; + + usb_hcd_giveback_urb(g_pUsbHcd, urb_p, urb_p->status); +} +//------------------------------------------------------------------------------- + +/** + * void otg_usbcore_hc_died(void) + * + * @brief inform usbcore of hc die + * + * @return void \n + */ +static inline void +otg_usbcore_hc_died(void) +{ + otg_dbg(OTG_DBG_OTGHCDI_KAL, "otg_usbcore_hc_died \n"); + usb_hc_died(g_pUsbHcd); +} +//------------------------------------------------------------------------------- + +/** + * void otg_usbcore_poll_rh_status(void) + * + * @brief invoke usbcore's usb_hcd_poll_rh_status + * + * @param void + * + * @return void \n + */ +static inline void +otg_usbcore_poll_rh_status(void) +{ + usb_hcd_poll_rh_status(g_pUsbHcd); +} +//------------------------------------------------------------------------------- + +/** + * void otg_usbcore_resume_roothub(void) + * + * @brief invoke usbcore's usb_hcd_resume_root_hub + * + * @param void + * + * @return void \n + */ +static inline void +otg_usbcore_resume_roothub(void) +{ + otg_dbg(OTG_DBG_OTGHCDI_KAL, + "otg_usbcore_resume_roothub \n"); + usb_hcd_resume_root_hub(g_pUsbHcd); +}; +//------------------------------------------------------------------------------- + +/** + * int otg_usbcore_inc_usb_bandwidth(u32 band_width) + * + * @brief increase bandwidth of usb bus + * + * @param [in] band_width : bandwidth to be increased + * + * @return USB_ERR_SUCCESS \n + */ +static inline int +otg_usbcore_inc_usb_bandwidth(u32 band_width) +{ + otg_dbg(OTG_DBG_OTGHCDI_KAL, + "otg_usbcore_inc_usb_bandwidth \n"); + hcd_to_bus(g_pUsbHcd)->bandwidth_allocated += band_width; + return USB_ERR_SUCCESS; +} +//------------------------------------------------------------------------------- + +/** + * int otg_usbcore_des_usb_bandwidth(u32 uiBandwidth) + * + * @brief decrease bandwidth of usb bus + * + * @param [in] band_width : bandwidth to be decreased + * + * @return USB_ERR_SUCCESS \n + */ +static inline int +otg_usbcore_des_usb_bandwidth(u32 band_width) +{ + otg_dbg(OTG_DBG_OTGHCDI_KAL, + "otg_usbcore_des_usb_bandwidth \n"); + hcd_to_bus(g_pUsbHcd)->bandwidth_allocated -= band_width; + return USB_ERR_SUCCESS; +} +//------------------------------------------------------------------------------- + +/** + * int otg_usbcore_inc_periodic_transfer_cnt(u8 transfer_type) + * + * @brief increase count of periodic transfer + * + * @param [in] transfer_type : type of transfer + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + */ +static inline int +otg_usbcore_inc_periodic_transfer_cnt(u8 transfer_type) +{ + otg_dbg(OTG_DBG_OTGHCDI_KAL, + "otg_usbcore_inc_periodic_transfer_cnt \n"); + + switch(transfer_type) { + case INT_TRANSFER : + hcd_to_bus(g_pUsbHcd)->bandwidth_int_reqs++; + break; + case ISOCH_TRANSFER : + hcd_to_bus(g_pUsbHcd)->bandwidth_isoc_reqs++; + break; + default: + otg_err(OTG_DBG_OTGHCDI_KAL, + "not proper TransferType for otg_usbcore_inc_periodic_transfer_cnt()\n"); + return USB_ERR_FAIL; + } + return USB_ERR_SUCCESS; +} +//------------------------------------------------------------------------------- + +/** + * int otg_usbcore_des_periodic_transfer_cnt(u8 transfer_type) + * + * @brief decrease count of periodic transfer + * + * @param [in] transfer_type : type of transfer + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + */ +static inline int +otg_usbcore_des_periodic_transfer_cnt(u8 transfer_type) +{ + otg_dbg(OTG_DBG_OTGHCDI_KAL, + "otg_usbcore_des_periodic_transfer_cnt \n"); + + switch(transfer_type) { + case INT_TRANSFER : + hcd_to_bus(g_pUsbHcd)->bandwidth_int_reqs--; + break; + case ISOCH_TRANSFER : + hcd_to_bus(g_pUsbHcd)->bandwidth_isoc_reqs--; + break; + default: + otg_err(OTG_DBG_OTGHCDI_KAL, + "not proper TransferType for otg_usbcore_des_periodic_transfer_cnt()\n"); + return USB_ERR_FAIL; + } + return USB_ERR_SUCCESS; +} +//------------------------------------------------------------------------------- + +/** + * u32 read_reg_32(u32 offset) + * + * @brief Reads the content of a register. + * + * @param [in] offset : offset of address of register to read. + * + * @return contents of the register. \n + * @remark call readl() + */ +static inline u32 read_reg_32(u32 offset) +{ + volatile unsigned int * reg_addr_p = ctrlr_base_reg_addr(offset); + + return *reg_addr_p; + //return readl(reg_addr_p); +}; +//------------------------------------------------------------------------------- + +/** + * void write_reg_32( u32 offset, const u32 value) + * + * @brief Writes a register with a 32 bit value. + * + * @param [in] offset : offset of address of register to write. + * @param [in] value : value to write + * + * @remark call writel() + */ +static inline void write_reg_32( u32 offset, const u32 value) +{ + volatile unsigned int * reg_addr_p = ctrlr_base_reg_addr(offset); + + *reg_addr_p = value; + //writel( value, reg_addr_p ); +}; +//------------------------------------------------------------------------------- + +/** + * void update_reg_32(u32 offset, u32 value) + * + * @brief logic or operation + * + * @param [in] offset : offset of address of register to write. + * @param [in] value : value to or + * + */ +static inline void update_reg_32(u32 offset, u32 value) +{ + write_reg_32(offset, (read_reg_32(offset) | value)); +} +//--------------------------------------------------------------------------------------- + +/** + * void clear_reg_32(u32 offset, u32 value) + * + * @brief logic not operation + * + * @param [in] offset : offset of address of register to write. + * @param [in] value : value to not + * + */ +static inline void clear_reg_32(u32 offset, u32 value) +{ + write_reg_32(offset, (read_reg_32(offset) & ~value)); +} +//--------------------------------------------------------------------------------------- + + +#ifdef __cplusplus +} +#endif + +#endif /* _S3C_OTG_HCDI_KAL_H_ */ + diff --git a/drivers/usb/host/s3c-otg/s3c-otg-hcdi-list.h b/drivers/usb/host/s3c-otg/s3c-otg-hcdi-list.h new file mode 100644 index 0000000..03ae3d8 --- /dev/null +++ b/drivers/usb/host/s3c-otg/s3c-otg-hcdi-list.h @@ -0,0 +1,217 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * @file s3c-otg-hcdi-list.h + * @brief list functions for otg \n + * @version + * -# Jun 9,2008 v1.0 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Creating the initial version of this code \n + * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Optimizing for performance \n + * @see None + ****************************************************************************/ +/**************************************************************************** + * 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 _S3C_OTG_HCDI_LIST_H_ +#define _S3C_OTG_HCDI_LIST_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "s3c-otg-common-common.h" +#include "s3c-otg-hcdi-debug.h" + +#include <linux/list.h> + +typedef struct list_head otg_list_head; + +#define otg_list_get_node(ptr, type, member) container_of(ptr, type, member) + + +#define otg_list_for_each_safe(pos, n, head) \ + for (pos = (head)->next, n = pos->next; pos != (head); \ + pos = n, n = pos->next) + + +/** + * void otg_list_push_next(otg_list_head *new_node_p, otg_list_head *list_head_p) + * + * @brief push a list node into the next of head + * + * @param [in] new_node_p : node to be pushed + * @param [in] otg_list_head : target list head + * + * @return void \n + */ + +static inline +void otg_list_push_next(otg_list_head *new_node_p, otg_list_head *list_head_p) +{ + otg_dbg(OTG_DBG_OTGHCDI_LIST, "otg_list_push_next \n"); + list_add(new_node_p, list_head_p); +} +//------------------------------------------------------------------------------- + +/** + * void otg_list_push_prev(otg_list_head *new_node_p, otg_list_head *list_head_p) + * + * @brief push a list node into the previous of head + * + * @param [in] new_node_p : node to be pushed + * @param [in] otg_list_head : target list head + * + * @return void \n + */ +static inline +void otg_list_push_prev(otg_list_head *new_node_p, otg_list_head *list_head_p) +{ + otg_dbg(OTG_DBG_OTGHCDI_LIST, "otg_list_push_prev \n"); + list_add_tail(new_node_p, list_head_p); +} +//------------------------------------------------------------------------------- + +/** + * void otg_list_pop(otg_list_head *list_entity_p) + * + * @brief pop a list node + * + * @param [in] new_node_p : node to be poped + * @param [in] otg_list_head : target list head + * + * @return void \n + */ +static inline +void otg_list_pop(otg_list_head *list_entity_p) +{ + otg_dbg(OTG_DBG_OTGHCDI_LIST, "otg_list_pop \n"); + list_del(list_entity_p); +} +//------------------------------------------------------------------------------- + +/** + * void otg_list_move_next(otg_list_head *node_p, otg_list_head *list_head_p) + * + * @brief move a list to next of head + * + * @param [in] new_node_p : node to be moved + * @param [in] otg_list_head : target list head + * + * @return void \n + */ +static inline +void otg_list_move_next(otg_list_head *node_p, otg_list_head *list_head_p) +{ + otg_dbg(OTG_DBG_OTGHCDI_LIST, "otg_list_move_next \n"); + list_move(node_p, list_head_p); +} +//------------------------------------------------------------------------------- + +/** + * void otg_list_move_prev(otg_list_head *node_p, otg_list_head *list_head_p) + * + * @brief move a list to previous of head + * + * @param [in] new_node_p : node to be moved + * @param [in] otg_list_head : target list head + * + * @return void \n + */ +static inline +void otg_list_move_prev(otg_list_head *node_p, otg_list_head *list_head_p) +{ + otg_dbg(OTG_DBG_OTGHCDI_LIST, "otg_list_move_prev \n"); + list_move_tail(node_p, list_head_p); +} +//------------------------------------------------------------------------------- + +/** + * bool otg_list_empty(otg_list_head *list_head_p) + * + * @brief check a list empty or not + * + * @param [in] list_head_p : node to check + * + * @return true : empty list \n + * false : not empty list + */ +static inline +bool otg_list_empty(otg_list_head *list_head_p) +{ + + otg_dbg(OTG_DBG_OTGHCDI_LIST, "otg_list_empty \n"); + if(list_empty(list_head_p)) + return true; + return false; +} +//------------------------------------------------------------------------------- + +/** + * void otg_list_merge(otg_list_head *list_p, otg_list_head *head_p) + * + * @brief merge two list + * + * @param [in] list_p : a head + * @param [in] head_p : target list head + * + * @return void \n + */ +static inline +void otg_list_merge(otg_list_head *list_p, otg_list_head *head_p) +{ + otg_dbg(OTG_DBG_OTGHCDI_LIST, "otg_list_merge \n"); + list_splice(list_p, head_p); +} +//------------------------------------------------------------------------------- + +/** + * void otg_list_init(otg_list_head *list_p) + * + * @brief initialize a list + * + * @param [in] list_p : node to be initialized + * + * @return void \n + */ +static inline +void otg_list_init(otg_list_head *list_p) +{ + otg_dbg(OTG_DBG_OTGHCDI_LIST, "otg_list_init \n"); + list_p->next = list_p; + list_p->prev = list_p; +} +//------------------------------------------------------------------------------- + +/* +void otg_list_push_next(otg_list_head *new_node_p, otg_list_head *list_head_p ); +void otg_list_push_prev(otg_list_head *new_node_p, otg_list_head *list_head_p); +void otg_list_pop(otg_list_head *list_entity_p); + +void otg_list_move_next(otg_list_head *node_p, otg_list_head *list_head_p); +void otg_list_move_prev(otg_list_head *node_p, otg_list_head *list_head_p); + +bool otg_list_empty(otg_list_head *list_head_p); +void otg_list_merge(otg_list_head *list_p, otg_list_head *head_p); +void otg_list_init(otg_list_head *list_p); +*/ + +#ifdef __cplusplus +} +#endif +#endif /* _S3C_OTG_HCDI_LIST_H_ */ + diff --git a/drivers/usb/host/s3c-otg/s3c-otg-hcdi-memory.h b/drivers/usb/host/s3c-otg/s3c-otg-hcdi-memory.h new file mode 100644 index 0000000..c87f15e --- /dev/null +++ b/drivers/usb/host/s3c-otg/s3c-otg-hcdi-memory.h @@ -0,0 +1,191 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * @file s3c-otg-hcdi-memory.h + * @brief header of s3c-otg-hcdi-memory \n + * @version + * -# Jun 9,2008 v1.0 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Creating the initial version of this code \n + * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Optimizing for performance \n + * @see None + ****************************************************************************/ +/**************************************************************************** + * 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 _S3C_OTG_HCDI_MEMORY_H_ +#define _S3C_OTG_HCDI_MEMORY_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "s3c-otg-common-common.h" +#include "s3c-otg-hcdi-debug.h" + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/mm.h> +/** + * @enum otg_mem_alloc_flag + * + * @brief enumeration for flag of memory allocation + */ +typedef +enum otg_mem_alloc_flag +{ + USB_MEM_SYNC, USB_MEM_ASYNC, USB_MEM_DMA +}otg_mem_alloc_flag_t; +//--------------------------------------------------------------------------------------- + +/* +inline int otg_mem_alloc(void ** addr_pp, u16 byte_size, otg_mem_alloc_flag_t type); +inline int otg_mem_copy(void * to_addr_p, void * from_addr_p, u16 byte_size); +//inline int otg_mem_free(void * addr_p); +inline int otg_mem_set(void * addr_p, char value, u16 byte_size); +*/ + +/** + * int otg_mem_alloc(void ** addr_pp, u16 byte_size, u8 ubType); + * + * @brief allocating momory specified + * + * @param [inout] addr_pp : address to be assigned + * [in] byte_size : size of memory + * [in] type : otg_mem_alloc_flag_t type + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + */ +static inline int +otg_mem_alloc ( + void ** addr_pp, + u16 byte_size, + otg_mem_alloc_flag_t type +) +{ + gfp_t flags; + otg_dbg(OTG_DBG_OTGHCDI_MEM, "otg_mem_alloc \n"); + + switch(type) { + case USB_MEM_SYNC: + flags = GFP_KERNEL; + break; + case USB_MEM_ASYNC: + flags = GFP_ATOMIC; + break; + case USB_MEM_DMA: + flags = GFP_DMA; + break; + default: + otg_err(OTG_DBG_OTGHCDI_MEM, + "not proper otg_mem_alloc_flag_t in otg_mem_alloc \n"); + return USB_ERR_FAIL; + } + + *addr_pp = kmalloc((size_t)byte_size, flags); + if(*addr_pp == 0) { + otg_err(OTG_DBG_OTGHCDI_MEM, + "kmalloc failed\n"); + return USB_ERR_FAIL; + } + return USB_ERR_SUCCESS; +} +//------------------------------------------------------------------------------- + +/** + * int otg_mem_copy(void * to_addr_p, void * from_addr_p, u16 byte_size); + * + * @brief memory copy + * + * @param [in] to_addr_p : target address + * [in] from_addr_p : source address + * [in] byte_size : size + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + */ +static inline int +otg_mem_copy +( + void * to_addr_p, + void * from_addr_p, + u16 byte_size +) +{ + otg_dbg(OTG_DBG_OTGHCDI_MEM, + "otg_mem_copy \n"); + + memcpy(to_addr_p, from_addr_p, (size_t)byte_size); + + return USB_ERR_SUCCESS; +} +//------------------------------------------------------------------------------- + +/** + * int otg_mem_free(void * addr_p); + * + * @brief de-allocating memory + * + * @param [in] addr_p : target address to be de-allocated + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + */ +static inline int +otg_mem_free(void * addr_p) +{ + otg_dbg(OTG_DBG_OTGHCDI_MEM, + "otg_mem_free \n"); + kfree(addr_p); + return USB_ERR_SUCCESS; +} + +//------------------------------------------------------------------------------- + +/** + * int otg_mem_set(void * addr_p, char value, u16 byte_size) + * + * @brief writing a value to memory + * + * @param [in] addr_p : target address + * [in] value : value to be written + * [in] byte_size : size + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + */ +static inline int +otg_mem_set +( + void * addr_p, + char value, + u16 byte_size +) +{ + otg_dbg(OTG_DBG_OTGHCDI_MEM, + "otg_mem_set \n"); + memset(addr_p, value, (size_t)byte_size); + return USB_ERR_SUCCESS; +} +//------------------------------------------------------------------------------- + + +#ifdef __cplusplus +} +#endif +#endif /* _S3C_OTG_HCDI_MEMORY_H_ */ diff --git a/drivers/usb/host/s3c-otg/s3c-otg-isr.c b/drivers/usb/host/s3c-otg/s3c-otg-isr.c new file mode 100644 index 0000000..6bb790d --- /dev/null +++ b/drivers/usb/host/s3c-otg/s3c-otg-isr.c @@ -0,0 +1,294 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : Isr.c + * [Description] : The file implement the external and internal functions of ISR + * [Author] : Jang Kyu Hyeok { kyuhyeok.jang@samsung.com } + * [Department] : System LSI Division/Embedded S/W Platform + * [Created Date]: 2009/02/10 + * [Revision History] + * (1) 2008/06/13 by Jang Kyu Hyeok { kyuhyeok.jang@samsung.com } + * - Created this file and implements functions of ISR + * + ****************************************************************************/ +/**************************************************************************** + * 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 "s3c-otg-isr.h" + +/** + * void otg_handle_interrupt(void) + * + * @brief Main interrupt processing routine + * + * @param None + * + * @return None + * + * @remark + * + */ + +__inline__ void otg_handle_interrupt(struct usb_hcd *hcd) +{ + gintsts_t clearIntr = {.d32 = 0}; + gintsts_t gintsts = {.d32 = 0}; + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + + gintsts.d32 = read_reg_32(GINTSTS) & read_reg_32(GINTMSK); + + otg_dbg(OTG_DBG_ISR, "otg_handle_interrupt - GINTSTS=0x%8x\n", + gintsts.d32); + + if (gintsts.b.wkupintr) { + otg_dbg(true, "Wakeup Interrupt\n"); + clearIntr.b.wkupintr = 1; + } + + if (gintsts.b.disconnect) { + otg_dbg(true, "Disconnect Interrupt\n"); + otghost->port_flag.b.port_connect_status_change = 1; + otghost->port_flag.b.port_connect_status = 0; + clearIntr.b.disconnect = 1; + /* + wake_unlock(&otghost->wake_lock); + */ + } + + if (gintsts.b.conidstschng) { + otg_dbg(OTG_DBG_ISR, "Connect ID Status Change Interrupt\n"); + clearIntr.b.conidstschng = 1; + oci_init_mode(otghost); + } + + if (gintsts.b.hcintr) { + /* Mask Channel Interrupt to prevent generating interrupt */ + otg_dbg(OTG_DBG_ISR, "Channel Interrupt\n"); + if(!otghost->ch_halt) { + do_transfer_checker(otghost); + } + } + + if (gintsts.b.portintr) { + /* Read Only */ + otg_dbg(true, "Port Interrupt\n"); + process_port_intr(hcd); + } + + + if (gintsts.b.otgintr) { + /* Read Only */ + otg_dbg(OTG_DBG_ISR, "OTG Interrupt\n"); + } + + if (gintsts.b.sofintr) { + /* otg_dbg(OTG_DBG_ISR, "SOF Interrupt\n"); */ + do_schedule(otghost); + clearIntr.b.sofintr = 1; + } + + if (gintsts.b.modemismatch) { + otg_dbg(OTG_DBG_ISR, "Mode Mismatch Interrupt\n"); + clearIntr.b.modemismatch = 1; + } + update_reg_32(GINTSTS, clearIntr.d32); +} + +/** + * void mask_channel_interrupt(u32 ch_num, u32 mask_info) + * + * @brief Mask specific channel interrupt + * + * @param [IN] chnum : channel number for masking + * [IN] mask_info : mask information to write register + * + * @return None + * + * @remark + * + */ +void mask_channel_interrupt(u32 ch_num, u32 mask_info) +{ + clear_reg_32(HCINTMSK(ch_num),mask_info); +} + +/** + * void unmask_channel_interrupt(u32 ch_num, u32 mask_info) + * + * @brief Unmask specific channel interrupt + * + * @param [IN] chnum : channel number for unmasking + * [IN] mask_info : mask information to write register + * + * @return None + * + * @remark + * + */ +void unmask_channel_interrupt(u32 ch_num, u32 mask_info) +{ + update_reg_32(HCINTMSK(ch_num),mask_info); +} + +/** + * int get_ch_info(hc_info_t * hc_reg, u8 ch_num) + * + * @brief Get current channel information about specific channel + * + * @param [OUT] hc_reg : structure to write channel inforamtion value + * [IN] ch_num : channel number for unmasking + * + * @return None + * + * @remark + * + */ +int get_ch_info(hc_info_t *hc_reg, u8 ch_num) +{ + if(hc_reg !=NULL) { + hc_reg->hc_int_msk.d32 = read_reg_32(HCINTMSK(ch_num)); + hc_reg->hc_int.d32 = read_reg_32(HCINT(ch_num)); + hc_reg->dma_addr = read_reg_32(HCDMA(ch_num)); + hc_reg->hc_char.d32 = read_reg_32(HCCHAR(ch_num)); + hc_reg->hc_size.d32 = read_reg_32(HCTSIZ(ch_num)); + + return USB_ERR_SUCCESS; + } + return USB_ERR_FAIL; +} + +/** + * void get_intr_ch(u32* haint, u32* haintmsk) + * + * @brief Get Channel Interrupt Information in HAINT, HAINTMSK register + * + * @param [OUT] haint : HAINT register value + * [OUT] haintmsk : HAINTMSK register value + * + * @return None + * + * @remark + * + */ +void get_intr_ch(u32 *haint, u32 *haintmsk) +{ + *haint = read_reg_32(HAINT); + *haintmsk = read_reg_32(HAINTMSK); +} + +/** + * void clear_ch_intr(u8 ch_num, u32 clear_bit) + * + * @brief Get Channel Interrupt Information in HAINT, HAINTMSK register + * + * @param [IN] haint : HAINT register value + * [IN] haintmsk : HAINTMSK register value + * + * @return None + * + * @remark + * + */ +void clear_ch_intr(u8 ch_num, u32 clear_bit) +{ + update_reg_32(HCINT(ch_num),clear_bit); +} + +/** + * void enable_sof(void) + * + * @brief Generate SOF Interrupt. + * + * @param None + * + * @return None + * + * @remark + * + */ +void enable_sof(void) +{ + gintmsk_t gintmsk = {.d32 = 0}; + gintmsk.b.sofintr = 1; + update_reg_32(GINTMSK, gintmsk.d32); +} + +/** + * void disable_sof(void) + * + * @brief Stop to generage SOF interrupt + * + * @param None + * + * @return None + * + * @remark + * + */ +void disable_sof(void) +{ + gintmsk_t gintmsk = {.d32 = 0}; + gintmsk.b.sofintr = 1; + clear_reg_32(GINTMSK, gintmsk.d32); +} + +/*Internal function of isr */ +void process_port_intr(struct usb_hcd *hcd) +{ + hprt_t hprt; /* by ss1, clear_hprt; */ + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + + hprt.d32 = read_reg_32(HPRT); + + otg_dbg(OTG_DBG_ISR, "Port Interrupt() : HPRT = 0x%x\n",hprt.d32); + + if(hprt.b.prtconndet) { + otg_dbg(true, "detect connection"); + + otghost->port_flag.b.port_connect_status_change = 1; + + if(hprt.b.prtconnsts) + otghost->port_flag.b.port_connect_status = 1; + + /* wake_lock(&otghost->wake_lock); */ + } + + + if(hprt.b.prtenchng) { + otg_dbg(true, "port enable/disable changed\n"); + otghost->port_flag.b.port_enable_change = 1; + } + + if(hprt.b.prtovrcurrchng) { + otg_dbg(true, "over current condition is changed\n"); + if(hprt.b.prtovrcurract) { + otg_dbg(true, "port_over_current_change = 1\n"); + otghost->port_flag.b.port_over_current_change = 1; + } + else { + otghost->port_flag.b.port_over_current_change = 0; + } + /* defer otg power control into a kernel thread */ + queue_work(otghost->wq, &otghost->work); + } + + hprt.b.prtena = 0; /* prtena¸¦ writeclear½ÃŰ¸é ¾ÈµÊ. */ + /* hprt.b.prtpwr = 0; */ + hprt.b.prtrst = 0; + hprt.b.prtconnsts = 0; + + write_reg_32(HPRT, hprt.d32); +} diff --git a/drivers/usb/host/s3c-otg/s3c-otg-isr.h b/drivers/usb/host/s3c-otg/s3c-otg-isr.h new file mode 100644 index 0000000..cad4cf5 --- /dev/null +++ b/drivers/usb/host/s3c-otg/s3c-otg-isr.h @@ -0,0 +1,72 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] :s3c-otg-isr.h + * [Description] : The Header file defines the external and internal functions of ISR. + * [Author] : Jang Kyu Hyeok { kyuhyeok.jang@samsung.com } + * [Department] : System LSI Division/Embedded S/W Platform + * [Created Date]: 2008/06/18 + * [Revision History] + * (1) 2008/06/18 by Jang Kyu Hyeok { kyuhyeok.jang@samsung.com } + * - Created this file and defines functions of Scheduler + * + ****************************************************************************/ +/**************************************************************************** + * 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 _ISR_H_ +#define _ISR_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "s3c-otg-common-errorcode.h" +#include "s3c-otg-common-const.h" +#include "s3c-otg-common-datastruct.h" +#include "s3c-otg-common-regdef.h" +#include "s3c-otg-hcdi-kal.h" +#include "s3c-otg-scheduler-scheduler.h" +#include "s3c-otg-transferchecker-common.h" +#include "s3c-otg-roothub.h" +#include "s3c-otg-oci.h" + +__inline__ void otg_handle_interrupt(struct usb_hcd *hcd); + +void process_port_intr(struct usb_hcd *hcd); + +void mask_channel_interrupt(u32 ch_num, u32 mask_info); + +void unmask_channel_interrupt(u32 ch_num, u32 mask_info); + +extern int get_ch_info(hc_info_t *hc_reg, u8 ch_num); + +extern void get_intr_ch(u32 *haint, u32 *haintmsk); + +extern void clear_ch_intr(u8 ch_num, u32 clear_bit); + +extern void enable_sof(void); + +extern void disable_sof(void); + +#ifdef __cplusplus +} +#endif +#endif /* _ISR_H_ */ + + + diff --git a/drivers/usb/host/s3c-otg/s3c-otg-oci.c b/drivers/usb/host/s3c-otg/s3c-otg-oci.c new file mode 100644 index 0000000..0744b9e --- /dev/null +++ b/drivers/usb/host/s3c-otg/s3c-otg-oci.c @@ -0,0 +1,815 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : OCI.c + * [Description] : The file implement the external and internal functions of OCI + * [Author] : Jang Kyu Hyeok { kyuhyeok.jang@samsung.com } + * [Department] : System LSI Division/Embedded S/W Platform + * [Created Date]: 2009/02/10 + * [Revision History] + * (1) 2008/06/12 by Jang Kyu Hyeok { kyuhyeok.jang@samsung.com } + * - Created this file and Implement functions of OCI + * + ****************************************************************************/ +/**************************************************************************** + * 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 "s3c-otg-oci.h" + +static bool ch_enable[16]; + +/** + * int oci_init(struct sec_otghost *otghost) + * + * @brief Initialize oci module. + * + * @param None + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * @remark + * + */ +int oci_init(struct sec_otghost *otghost) +{ + otg_mem_set((void*)ch_enable, true, sizeof(bool)*16); + otghost->ch_halt = false; + + if(oci_sys_init(otghost) == USB_ERR_SUCCESS) { + if(oci_core_reset() == USB_ERR_SUCCESS) { + oci_set_global_interrupt(false); + return USB_ERR_SUCCESS; + } + else { + otg_dbg(OTG_DBG_OCI, "oci_core_reset() Fail\n"); + return USB_ERR_FAIL; + } + } + + return USB_ERR_FAIL; +} + +/** + * int oci_core_init(struct sec_otghost *otghost) + * + * @brief process core initialize as s5pc110 otg spec + * + * @param None + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * @remark + * + */ +int oci_core_init(struct sec_otghost *otghost) +{ + gahbcfg_t ahbcfg = {.d32 = 0}; + gusbcfg_t usbcfg = {.d32 = 0}; + ghwcfg2_t hwcfg2 = {.d32 = 0}; + gintmsk_t gintmsk = {.d32 = 0}; + + otg_dbg(OTG_DBG_OCI, "oci_core_init \n"); + + usbcfg.d32 = read_reg_32(GUSBCFG); + otg_dbg(OTG_DBG_OCI, "before - GUSBCFG=0x%x, GOTGCTL=0x%x\n", + usbcfg.d32, read_reg_32(GOTGCTL)); + + /* PHY parameters */ + usbcfg.b.physel = 0; + usbcfg.b.phyif = 1; /* 16 bit */ + usbcfg.b.ulpi_utmi_sel = 0; /* UTMI */ + /* usbcfg.b.ddrsel = 1; */ /* DDR */ + usbcfg.b.usbtrdtim = 5; /* 16 bit UTMI */ + usbcfg.b.toutcal = 7; + usbcfg.b.forcehstmode = 1; + write_reg_32 (GUSBCFG, usbcfg.d32); + + // per + // http://forum.xda-developers.com/showthread.php?t=709135&page=21 + // we need a 25msec delay after setting this -kevinh + + mdelay(100); + + otg_dbg(OTG_DBG_OCI, + "after - GUSBCFG=0x%x, GOTGCTL=0x%x\n", + read_reg_32(GUSBCFG), + read_reg_32(GOTGCTL)); + + /* Reset after setting the PHY parameters */ + if(oci_core_reset() == USB_ERR_SUCCESS) { + /* Program the GAHBCFG Register.*/ + hwcfg2.d32 = read_reg_32 (GHWCFG2); + + switch (hwcfg2.b.architecture) { + case HWCFG2_ARCH_SLAVE_ONLY: + otg_dbg(OTG_DBG_OCI, "Slave Only Mode\n"); + ahbcfg.b.nptxfemplvl = 0; + ahbcfg.b.ptxfemplvl = 0; + break; + + case HWCFG2_ARCH_EXT_DMA: + otg_dbg(OTG_DBG_OCI, "External DMA Mode - TBD!\n"); + break; + + case HWCFG2_ARCH_INT_DMA: + otg_dbg(OTG_DBG_OCI, "Internal DMA Setting \n"); + ahbcfg.b.dmaenable = true; + ahbcfg.b.hburstlen = INT_DMA_MODE_INCR4; + break; + + default: + otg_dbg(OTG_DBG_OCI, "ERR> hwcfg2\n "); + break; + } + write_reg_32 (GAHBCFG, ahbcfg.d32); + + /* Program the GUSBCFG register.*/ + switch (hwcfg2.b.op_mode) { + case MODE_HNP_SRP_CAPABLE: + otg_dbg(OTG_DBG_OCI, + "GHWCFG2 OP Mode : MODE_HNP_SRP_CAPABLE \n"); + usbcfg.b.hnpcap = 1; + usbcfg.b.srpcap = 1; + + otg_dbg(OTG_DBG_OCI, + "OTG_DBG_OCI : use HNP and SRP \n"); + break; + + case MODE_SRP_ONLY_CAPABLE: + otg_dbg(OTG_DBG_OCI, + "GHWCFG2 OP Mode : MODE_SRP_ONLY_CAPABLE \n"); + usbcfg.b.srpcap = 1; + break; + + case MODE_NO_HNP_SRP_CAPABLE: + otg_dbg(OTG_DBG_OCI, + "GHWCFG2 OP Mode : MODE_NO_HNP_SRP_CAPABLE \n"); + usbcfg.b.hnpcap = 0; + break; + + case MODE_SRP_CAPABLE_DEVICE: + otg_dbg(OTG_DBG_OCI, + "GHWCFG2 OP Mode : MODE_SRP_CAPABLE_DEVICE \n"); + usbcfg.b.srpcap = 1; + break; + + case MODE_NO_SRP_CAPABLE_DEVICE: + otg_dbg(OTG_DBG_OCI, + "GHWCFG2 OP Mode : MODE_NO_SRP_CAPABLE_DEVICE \n"); + usbcfg.b.srpcap = 0; + break; + + case MODE_SRP_CAPABLE_HOST: + otg_dbg(OTG_DBG_OCI, + "GHWCFG2 OP Mode : MODE_SRP_CAPABLE_HOST \n"); + usbcfg.b.srpcap = 1; + break; + + case MODE_NO_SRP_CAPABLE_HOST: + otg_dbg(OTG_DBG_OCI, + "GHWCFG2 OP Mode : MODE_NO_SRP_CAPABLE_HOST \n"); + usbcfg.b.srpcap = 0; + break; + default : + otg_err(OTG_DBG_OCI, "ERR> hwcfg2\n "); + break; + } + write_reg_32 (GUSBCFG, usbcfg.d32); + + /* Program the GINTMSK register.*/ + gintmsk.b.modemismatch = 1; + gintmsk.b.sofintr = 1; + /*gintmsk.b.otgintr = 1; */ + gintmsk.b.conidstschng = 1; + /*gintmsk.b.wkupintr = 1;*/ + gintmsk.b.disconnect = 1; + /*gintmsk.b.usbsuspend = 1;*/ + /*gintmsk.b.sessreqintr = 1;*/ + /*gintmsk.b.portintr = 1;*/ + /*gintmsk.b.hcintr = 1; */ + write_reg_32(GINTMSK, gintmsk.d32); + + return USB_ERR_SUCCESS; + } + else { + otg_err(OTG_DBG_OCI, "Core Reset FAIL\n"); + return USB_ERR_FAIL; + } +} + +/** + * int oci_host_init(struct sec_otghost *otghost) + * + * @brief Process host initialize as s5pc110 spec + * + * @param None + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * @remark + * + */ +int oci_host_init(struct sec_otghost *otghost) +{ + gintmsk_t gintmsk = {.d32 = 0}; + hcfg_t hcfg = {.d32 = 0}; +#if 0 +/*#ifdef CONFIG_USB_S3C_OTG_HOST_DTGDRVVBUS*/ + hprt_t hprt; + hprt.d32 = read_reg_32(HPRT); +#endif + + otg_dbg(OTG_DBG_OCI, "oci_host_init\n"); + + gintmsk.b.portintr = 1; + update_reg_32(GINTMSK,gintmsk.d32); + + if (!otghost->is_hs) { + hcfg.b.fslssupp = 1; // force USB 1.x mode + } else { + hcfg.b.fslssupp = 0; + } + hcfg.b.fslspclksel = HCFG_30_60_MHZ; + update_reg_32(HCFG, hcfg.d32); + +#if 0 +/*#ifdef CONFIG_USB_S3C_OTG_HOST_DTGDRVVBUS*/ + /* turn on vbus */ + if(!hprt.b.prtpwr) { + hprt.b.prtpwr = 1; + write_reg_32(HPRT, hprt.d32); + + otg_dbg(true, "turn on Vbus\n"); + } +#endif + + oci_config_flush_fifo(OTG_HOST_MODE); + + return USB_ERR_SUCCESS; +} + +/** + * int oci_start(struct sec_otghost *otghost) + * + * @brief start to operate oci module by calling oci_core_init function + * + * @param None + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * @remark + * + */ +int oci_start(struct sec_otghost *otghost) +{ + otg_dbg(OTG_DBG_OCI, "oci_start \n"); + + if(oci_core_init(otghost) == USB_ERR_SUCCESS) { + mdelay(50); + if(oci_init_mode(otghost) == USB_ERR_SUCCESS) { + oci_set_global_interrupt(true); + return USB_ERR_SUCCESS; + } + else { + otg_dbg(OTG_DBG_OCI, "oci_init_mode() Fail\n"); + return USB_ERR_FAIL; + } + } + else { + otg_dbg(OTG_DBG_OCI, "oci_core_init() Fail\n"); + return USB_ERR_FAIL; + } +} + +/** + * int oci_stop(struct sec_otghost *otghost) + * + * @brief stop to opearte otg core + * + * @param None + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * @remark + * + */ +int oci_stop(struct sec_otghost *otghost) +{ + gintmsk_t gintmsk = {.d32 = 0}; + + otg_dbg(OTG_DBG_OCI, "oci_stop\n"); + + /* sometimes, port interrupt occured after removed + * otg host driver. so, we have to mask port interrupt. */ + write_reg_32(GINTMSK, gintmsk.d32); + + oci_set_global_interrupt(false); + + return USB_ERR_SUCCESS; +} + +/** + * oci_start_transfer(struct sec_otghost *otghost, stransfer_t *st_t) + * + * @brief start transfer by using transfer information to receive from scheduler + * + * @param [IN] *st_t - information about transfer to write register by calling oci_channel_init function + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * @remark + * + */ + +u8 oci_start_transfer(struct sec_otghost *otghost, stransfer_t *st_t) +{ + hcchar_t hcchar = {.d32 = 0}; + + otg_dbg(OTG_DBG_OCI, "oci_start_transfer \n"); + + if(st_t->alloc_chnum ==CH_NONE) { + + if( oci_channel_alloc(&(st_t->alloc_chnum)) == USB_ERR_SUCCESS) { + oci_channel_init(st_t->alloc_chnum, st_t); + + hcchar.b.chen = 1; + update_reg_32(HCCHAR(st_t->alloc_chnum), hcchar.d32); + return st_t->alloc_chnum; + } + else { + otg_dbg(OTG_DBG_OCI, + "oci_start_transfer Fail - Channel Allocation Error\n"); + return CH_NONE; + } + } + else { + oci_channel_init(st_t->alloc_chnum, st_t); + + hcchar.b.chen = 1; + update_reg_32(HCCHAR(st_t->alloc_chnum), hcchar.d32); + + return st_t->alloc_chnum; + } +} + +/** + * int oci_stop_transfer(struct sec_otghost *otghost, u8 ch_num) + * + * @brief stop to transfer even if transfering + * + * @param None + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * @remark + * + */ +int oci_stop_transfer(struct sec_otghost *otghost, u8 ch_num) +{ + hcchar_t hcchar = {.d32 = 0}; + hcintmsk_t hcintmsk = {.d32 = 0}; + int count = 0, max_error_count = 10000; + + otg_dbg(OTG_DBG_OCI, + "step1: oci_stop_transfer ch=%d, hcchar=0x%x\n", + ch_num, read_reg_32(HCCHAR(ch_num))); + + if(ch_num>16) + return USB_ERR_FAIL; + + otghost->ch_halt = true; + + hcintmsk.b.chhltd = 1; + update_reg_32(HCINTMSK(ch_num),hcintmsk.d32); + + hcchar.b.chdis = 1; + hcchar.b.chen = 1; + update_reg_32(HCCHAR(ch_num),hcchar.d32); + + /* wait for Channel Disabled Interrupt */ + do { + hcchar.d32 = read_reg_32(HCCHAR(ch_num)); + + if(count > max_error_count) { + otg_dbg(OTG_DBG_OCI, + "Warning!! oci_stop_transfer()" + "ChDis is not cleared! ch=%d, hcchar=0x%x\n", + ch_num, hcchar.d32); + break; + } + count++; + + } while(hcchar.b.chdis); + + //kevinh: wait for Channel Disabled Interrupt + count = 0; + do { + hcintmsk.d32 = read_reg_32(HCINT(ch_num)); + if(count > max_error_count) { + printk("chhltd int never occurred! ch=%d, intreg=0x%x\n", + ch_num, hcintmsk.d32); + break; + } + count++; + } while(!hcintmsk.b.chhltd); + + oci_channel_dealloc(ch_num); + + clear_reg_32(HAINTMSK,ch_num); + write_reg_32(HCINT(ch_num),INT_ALL); + clear_reg_32(HCINTMSK(ch_num), INT_ALL); + + otghost->ch_halt = false; + otg_dbg(OTG_DBG_OCI, + "step2 : oci_stop_transfer ch=%d, hcchar=0x%x\n", + ch_num, read_reg_32(HCCHAR(ch_num))); + + return USB_ERR_SUCCESS; +} + +/** + * int oci_channel_init( u8 ch_num, stransfer_t *st_t) + * + * @brief Process channel initialize to prepare starting transfer + * + * @param None + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * @remark + * + */ +int oci_channel_init( u8 ch_num, stransfer_t *st_t) +{ + u32 intr_enable = 0; + gintmsk_t gintmsk = {.d32 = 0}; + hcchar_t hcchar = {.d32 = 0}; + hctsiz_t hctsiz = {.d32 = 0}; + hcsplt_t hcsplt = {.d32 = 0}; + + otg_dbg(OTG_DBG_OCI, "oci_channel_init \n"); + + /* Clear channel information */ + write_reg_32(HCTSIZ(ch_num), 0); + write_reg_32(HCCHAR(ch_num), 0); + write_reg_32(HCINTMSK(ch_num), 0); + write_reg_32(HCINT(ch_num), INT_ALL);/*write clear*/ + write_reg_32(HCDMA(ch_num), 0); + + /* enable host channel interrupt in GINTSTS */ + gintmsk.b.hcintr =1; + update_reg_32(GINTMSK, gintmsk.d32); + /* Enable the top level host channel interrupt in HAINT */ + intr_enable = (1 << ch_num); + update_reg_32(HAINTMSK, intr_enable); + /* unmask the down level host channel interrupt in HCINT */ + write_reg_32(HCINTMSK(ch_num),st_t->hc_reg.hc_int_msk.d32); + + /* Program the HCSIZn register with the endpoint characteristics for */ + hctsiz.b.xfersize = st_t->buf_size; + hctsiz.b.pktcnt = st_t->packet_cnt; + + /* Program the HCCHARn register with the endpoint characteristics for */ + hcchar.b.mps = st_t->ed_desc_p->max_packet_size; + hcchar.b.epnum = st_t->ed_desc_p->endpoint_num; + hcchar.b.epdir = st_t->ed_desc_p->is_ep_in; + hcchar.b.lspddev = (st_t->ed_desc_p->dev_speed == LOW_SPEED_OTG); + hcchar.b.eptype = st_t->ed_desc_p->endpoint_type; + hcchar.b.multicnt = st_t->ed_desc_p->mc; + hcchar.b.devaddr = st_t->ed_desc_p->device_addr; + + if(st_t->ed_desc_p->endpoint_type == INT_TRANSFER || + st_t->ed_desc_p->endpoint_type == ISOCH_TRANSFER) { + u32 uiFrameNum = 0; + uiFrameNum = oci_get_frame_num(); + + hcchar.b.oddfrm = uiFrameNum%2?1:0; + + /* + * if transfer type is periodic transfer, + * must support sof interrupt + */ + + /* + gintmsk.b.sofintr = 1; + update_reg_32(GINTMSK, gintmsk.d32); + */ + } + + if(st_t->ed_desc_p->endpoint_type == CONTROL_TRANSFER) { + td_t *td_p; + td_p = (td_t *)st_t->parent_td; + + switch(td_p->standard_dev_req_info.conrol_transfer_stage) { + case SETUP_STAGE: + hctsiz.b.pid = st_t->ed_status_p->control_data_tgl.setup_tgl; + hcchar.b.epdir = EP_OUT; + break; + case DATA_STAGE: + hctsiz.b.pid = st_t->ed_status_p->control_data_tgl.data_tgl; + hcchar.b.epdir = st_t->ed_desc_p->is_ep_in; + break; + case STATUS_STAGE: + hctsiz.b.pid = st_t->ed_status_p->control_data_tgl.status_tgl; + + if(td_p->standard_dev_req_info.is_data_stage) { + hcchar.b.epdir = ~(st_t->ed_desc_p->is_ep_in); + } + else { + hcchar.b.epdir = EP_IN; + } + break; + default:break; + } + } + else { + hctsiz.b.pid = st_t->ed_status_p->data_tgl; + } + + hctsiz.b.dopng = st_t->ed_status_p->is_ping_enable; + write_reg_32(HCTSIZ(ch_num),hctsiz.d32); + st_t->ed_status_p->is_ping_enable = false; + + /* Write DMA Address */ + write_reg_32(HCDMA(ch_num),st_t->start_phy_buf_addr); + + /* Wrote HCCHAR Register */ + write_reg_32(HCCHAR(ch_num),hcchar.d32); + + /* sztupy: Enable split transaction support if driver is in HS mode */ + if (st_t->ed_desc_p->is_do_split) { + printk("splittrans"); + hcsplt.b.spltena = 1; + hcsplt.b.hubaddr = st_t->ed_desc_p->hub_addr; + hcsplt.b.prtaddr = st_t->ed_desc_p->hub_port; + hcsplt.b.xactpos = st_t->ed_status_p->split_pos; + hcsplt.b.compsplt = st_t->ed_status_p->is_complete_split; + } + write_reg_32(HCSPLT(ch_num),hcsplt.d32); + + return USB_ERR_SUCCESS; +} + +/** + * u32 oci_get_frame_num(void) + * + * @brief Get current frame number by reading register. + * + * @param None + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * @remark + * + */ +u32 oci_get_frame_num(void) +{ + hfnum_t hfnum; + hfnum.d32 = read_reg_32(HFNUM); + + otg_dbg(OTG_DBG_OCI, " oci_get_frame_num=%d\n", hfnum.b.frnum); + + return hfnum.b.frnum; +} + +/** + * u16 oci_get_frame_interval(void) + * + * @brief Get current frame interval by reading register. + * + * @param None + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * @remark + * + */ +u16 oci_get_frame_interval(void) +{ + hfir_t hfir; + hfir.d32 = read_reg_32(HFIR); + return hfir.b.frint; +} + +void oci_set_frame_interval(u16 interval) +{ + hfir_t hfir = {.d32 = 0}; + hfir.b.frint = interval; + write_reg_32(HFIR, hfir.d32); +} + +/* OCI Internal Functions */ + +int oci_channel_alloc(u8 *ch_num) +{ + u8 ch; + + hcchar_t hcchar = {.d32 = 0}; + + for(ch = 0 ; ch<16 ; ch++) { + if(ch_enable[ch] == true) { + hcchar.d32 = read_reg_32(HCCHAR(ch)); + + if(hcchar.b.chdis == 0) { + *ch_num = ch; + ch_enable[ch] = false; + return USB_ERR_SUCCESS; + } + } + } + + return USB_ERR_FAIL; +} + +int oci_channel_dealloc(u8 ch_num) +{ + if(ch_num < 16 && ch_enable[ch_num] == false) { + ch_enable[ch_num] = true; + + write_reg_32(HCTSIZ(ch_num), 0); + write_reg_32(HCCHAR(ch_num), 0); + write_reg_32(HCINTMSK(ch_num), 0); + write_reg_32(HCINT(ch_num), INT_ALL); + write_reg_32(HCDMA(ch_num), 0); + return USB_ERR_SUCCESS; + } + + return USB_ERR_FAIL; +} + +int oci_sys_init(struct sec_otghost *otghost) +{ + otg_host_phy_init(); + + return USB_ERR_SUCCESS; +} + +void oci_set_global_interrupt(bool set) +{ + gahbcfg_t ahbcfg; + + otg_dbg(OTG_DBG_OCI, " oci_set_global_interrupt\n"); + + ahbcfg.d32 = 0; + ahbcfg.b.glblintrmsk = 1; + + if(set) { + update_reg_32(GAHBCFG,ahbcfg.d32); + } + else { + clear_reg_32(GAHBCFG,ahbcfg.d32); + } +} + +int oci_init_mode(struct sec_otghost *otghost) +{ + gintsts_t gintsts; + + gintsts.d32 = read_reg_32(GINTSTS); + + otg_dbg(OTG_DBG_OCI, + "GINSTS = 0x%x,GINMSK = 0x%x.\n", + (unsigned int)gintsts.d32, + (unsigned int)read_reg_32(GINTMSK)); + + if(gintsts.b.curmode == OTG_HOST_MODE) { + otg_dbg(OTG_DBG_OCI, "HOST Mode\n"); + + if(oci_host_init(otghost) == USB_ERR_SUCCESS) { + return USB_ERR_SUCCESS; + } + else { + otg_dbg(OTG_DBG_OCI, "oci_host_init() Fail\n"); + return USB_ERR_FAIL; + } + } + else { /* Device Mode */ + otg_dbg(OTG_DBG_OCI, "DEVICE Mode\n"); + if(oci_dev_init(otghost) == USB_ERR_SUCCESS) { + return USB_ERR_SUCCESS; + } + else { + otg_err(OTG_DBG_OCI, "oci_dev_init() Fail\n"); + return USB_ERR_FAIL; + } + } + + return USB_ERR_SUCCESS; +} + +void oci_config_flush_fifo(u32 mode) +{ + ghwcfg2_t hwcfg2 = {.d32 = 0}; + + otg_dbg(OTG_DBG_OCI, "oci_config_flush_fifo\n"); + + hwcfg2.d32 = read_reg_32(GHWCFG2); + + /* Configure data FIFO sizes */ + if (hwcfg2.b.dynamic_fifo) { + /* Rx FIFO */ + write_reg_32(GRXFSIZ, 0x0000010D); + + /* Non-periodic Tx FIFO */ + write_reg_32(GNPTXFSIZ, 0x0080010D); + + if (mode == OTG_HOST_MODE) { + /* For Periodic transactions, */ + /* program HPTXFSIZ */ + } + } + + /* Flush the FIFOs */ + oci_flush_tx_fifo(0); + + oci_flush_rx_fifo(); +} + +void oci_flush_tx_fifo(u32 num) +{ + grstctl_t greset = {.d32 = 0}; + u32 count = 0; + + otg_dbg(OTG_DBG_OCI, "oci_flush_tx_fifo\n"); + + greset.b.txfflsh = 1; + greset.b.txfnum = num; + write_reg_32(GRSTCTL, greset.d32); + + /* wait for flush to end */ + while (greset.b.txfflsh == 1) { + greset.d32 = read_reg_32(GRSTCTL); + if (++count > MAX_COUNT) + break; + }; + + /* Wait for 3 PHY Clocks*/ + udelay(30); +} + +void oci_flush_rx_fifo(void) +{ + grstctl_t greset = {.d32 = 0}; + u32 count = 0; + + otg_dbg(OTG_DBG_OCI, "oci_flush_rx_fifo\n"); + + greset.b.rxfflsh = 1; + write_reg_32(GRSTCTL, greset.d32 ); + + do { + greset.d32 = read_reg_32(GRSTCTL); + if (++count > MAX_COUNT) + break; + } while (greset.b.rxfflsh == 1); + + /* Wait for 3 PHY Clocks*/ + udelay(30); +} + +int oci_core_reset(void) +{ + u32 count = 0; + grstctl_t greset = {.d32 = 0}; + + otg_dbg(OTG_DBG_OCI, "oci_core_reset\n"); + + /* Wait for AHB master IDLE state. */ + do { + greset.d32 = read_reg_32 (GRSTCTL); + mdelay (50); + + if(++count>100) { + otg_dbg(OTG_DBG_OCI, "AHB status is not IDLE\n"); + return USB_ERR_FAIL; + } + } while (greset.b.ahbidle != 1); + + /* Core Soft Reset */ + greset.b.csftrst = 1; + write_reg_32 (GRSTCTL, greset.d32); + + /* Wait for 3 PHY Clocks*/ + mdelay (50); + return USB_ERR_SUCCESS; +} + +int oci_dev_init(struct sec_otghost *otghost) +{ + otg_dbg(OTG_DBG_OCI, "oci_dev_init - do nothing.\n"); + /* return USB_ERR_FAIL; */ + return USB_ERR_SUCCESS; +} diff --git a/drivers/usb/host/s3c-otg/s3c-otg-oci.h b/drivers/usb/host/s3c-otg/s3c-otg-oci.h new file mode 100644 index 0000000..03c97e9 --- /dev/null +++ b/drivers/usb/host/s3c-otg/s3c-otg-oci.h @@ -0,0 +1,86 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] :s3c-otg-oci.h + * [Description] : The Header file defines the external and internal functions of OCI. + * [Author] : Jang Kyu Hyeok { kyuhyeok.jang@samsung.com } + * [Department] : System LSI Division/Embedded S/W Platform + * [Created Date]: 2008/06/18 + * [Revision History] + * (1) 2008/06/25 by Jang Kyu Hyeok { kyuhyeok.jang@samsung.com } + * - Added some functions and data structure of OCI + * + ****************************************************************************/ +/**************************************************************************** + * 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 _OCI_H_ +#define _OCI_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +//#include "s3c-otg-common-const.h" +#include "s3c-otg-common-errorcode.h" +#include "s3c-otg-common-regdef.h" +#include "s3c-otg-hcdi-kal.h" +#include "s3c-otg-hcdi-memory.h" +#include "s3c-otg-hcdi-debug.h" +#include "s3c-otg-roothub.h" +#include "s3c-otg-hcdi-hcd.h" + +#include <mach/map.h> //virtual address for smdk + +extern void otg_host_phy_init(void); +#include <mach/regs-clock.h> + +//OCI interace +int oci_init(struct sec_otghost *otghost); + +int oci_start(struct sec_otghost *otghost); +int oci_stop(struct sec_otghost *otghost); + +u8 oci_start_transfer(struct sec_otghost *otghost, stransfer_t *st_t); +int oci_stop_transfer(struct sec_otghost *otghost, u8 ch_num); + +int oci_channel_init(u8 ch_num, stransfer_t *st_t); +u32 oci_get_frame_num(void); +u16 oci_get_frame_interval(void); +void oci_set_frame_interval(u16 intervl); + +///OCI Internal Functions +int oci_sys_init(struct sec_otghost *otghost); +int oci_core_init(struct sec_otghost *otghost); +int oci_init_mode(struct sec_otghost *otghost); +int oci_host_init(struct sec_otghost *otghost); +int oci_dev_init(struct sec_otghost *otghost); + +int oci_channel_alloc(u8 *ch_num); +int oci_channel_dealloc(u8 ch_num); + +void oci_config_flush_fifo(u32 mode); +void oci_flush_tx_fifo(u32 num); +void oci_flush_rx_fifo(void); + +int oci_core_reset(void); +void oci_set_global_interrupt(bool set); + +#ifdef __cplusplus +} +#endif +#endif /* _OCI_H_ */ diff --git a/drivers/usb/host/s3c-otg/s3c-otg-roothub.c b/drivers/usb/host/s3c-otg/s3c-otg-roothub.c new file mode 100644 index 0000000..7d9d289 --- /dev/null +++ b/drivers/usb/host/s3c-otg/s3c-otg-roothub.c @@ -0,0 +1,605 @@ +/* for* ========================================================================== + * Synopsys HS OTG Linux Software Driver and documentation (hereinafter, + * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless + * otherwise expressly agreed to in writing between Synopsys and you. + * + * The Software IS NOT an item of Licensed Software or Licensed Product under + * any End User Software License Agreement or Agreement for Licensed Product + * with Synopsys or any supplement thereto. You are permitted to use and + * redistribute this Software in source and binary forms, with or without + * modification, provided that redistributions of source code must retain this + * notice. You may not view, use, disclose, copy or distribute this file or + * any information contained herein except pursuant to this license grant from + * Synopsys. If you do not agree with this notice, including the disclaimer + * below, then you are not authorized to use the Software. + * + * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * ========================================================================== */ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : RootHub.c + * [Description] : The file implement the external and internal functions of RootHub + * [Author] : Jang Kyu Hyeok { kyuhyeok.jang@samsung.com } + * [Department] : System LSI Division/Embedded S/W Platform + * [Created Date]: 2009/02/10 + * [Revision History] + * (1) 2008/06/13 by Jang Kyu Hyeok { kyuhyeok.jang@samsung.com } + * - Created this file and implements functions of RootHub + * + ****************************************************************************/ +/**************************************************************************** + * 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 "s3c-otg-roothub.h" + +static void setPortPower(bool on) +{ +#ifdef CONFIG_USB_S3C_OTG_HOST_DTGDRVVBUS + hprt_t hprt = {.d32 = 0}; + + if(on) { + hprt.d32 = read_reg_32(HPRT); + if(!hprt.b.prtpwr) { + /*hprt.d32 = 0;*/ + hprt.b.prtpwr = 1; + write_reg_32(HPRT, hprt.d32); + } + } + else { + hprt.b.prtpwr = 1; + clear_reg_32(HPRT, hprt.d32); + } +#else + otg_dbg(true, "turn %s Vbus\n", on ? "on" : "off"); +#endif +} + +/** + * int get_otg_port_status(const u8 port, char* status) + * + * @brief Get port change bitmap information + * + * @param [IN] port : port number + * [OUT] status : buffer to store bitmap information + * + * @returnUSB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * + * @remark + * + */ +__inline__ int get_otg_port_status( + struct usb_hcd *hcd, const u8 port, char *status) +{ + + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + /* return root_hub_feature(port, GetPortStatus, NULL, status); */ +#if 0 + /* for debug */ + hprt_t hprt; + + hprt.d32 = read_reg_32(HPRT); + + otg_dbg(OTG_DBG_ROOTHUB, + "HPRT:spd=%d,pwr=%d,lnsts=%d,rst=%d,susp=%d,res=%d,ovrcurract=%d,ena=%d,connsts=%d\n", + hprt.b.prtspd, + hprt.b.prtpwr, + hprt.b.prtlnsts, + hprt.b.prtrst, + hprt.b.prtsusp, + hprt.b.prtres, + hprt.b.prtovrcurract, + hprt.b.prtena, + hprt.b.prtconnsts); + +#endif + status[port] = 0; + status[port] |= (otghost->port_flag.b.port_connect_status_change || + otghost->port_flag.b.port_reset_change || + otghost->port_flag.b.port_enable_change || + otghost->port_flag.b.port_suspend_change || + otghost->port_flag.b.port_over_current_change) << 1; + +#if 0 + //* for debug */ + otg_dbg(OTG_DBG_ROOTHUB, + "connect:%d,reset:%d,enable:%d,suspend:%d,over_current:%d\n", + otghost->port_flag.b.port_connect_status_change, + otghost->port_flag.b.port_reset_change, + otghost->port_flag.b.port_enable_change, + otghost->port_flag.b.port_suspend_change, + otghost->port_flag.b.port_over_current_change); +#endif + + if (status[port]) { + otg_dbg(OTG_DBG_ROOTHUB, + " Root port status changed\n"); + otg_dbg(OTG_DBG_ROOTHUB, + " port_connect_status_change: %d\n", + otghost->port_flag.b.port_connect_status_change); + otg_dbg(OTG_DBG_ROOTHUB, + " port_reset_change: %d\n", + otghost->port_flag.b.port_reset_change); + otg_dbg(OTG_DBG_ROOTHUB, + " port_enable_change: %d\n", + otghost->port_flag.b.port_enable_change); + otg_dbg(OTG_DBG_ROOTHUB, + " port_suspend_change: %d\n", + otghost->port_flag.b.port_suspend_change); + otg_dbg(OTG_DBG_ROOTHUB, + " port_over_current_change: %d\n", + otghost->port_flag.b.port_over_current_change); + } + + return (status[port] !=0); +} + +/** + * int reset_and_enable_port(struct usb_hcd *hcd, const u8 port) + * + * @brief Reset port and make enable status the specific port + * + * @param [IN] port : port number + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * + * @remark + * + */ +int reset_and_enable_port(struct usb_hcd *hcd, const u8 port) +{ + hprt_t hprt; + u32 count = 0; + u32 max_error_count = 10000; + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + + hprt.d32 = read_reg_32(HPRT); + + otg_dbg(OTG_DBG_ROOTHUB, + " reset_and_enable_port\n"); + + if(hprt.b.prtconnsts==0) { + otg_dbg(OTG_DBG_ROOTHUB, + "No Attached Device, HPRT = 0x%x\n", hprt.d32); + + otghost->port_flag.b.port_connect_status_change = 1; + otghost->port_flag.b.port_connect_status = 0; + + return USB_ERR_FAIL; + } + + if(!hprt.b.prtena) { + hprt.b.prtrst = 1; /* drive reset */ + write_reg_32(HPRT, hprt.d32); + + mdelay(60); + hprt.b.prtrst = 0; + write_reg_32(HPRT, hprt.d32); + + do { + hprt.d32 = read_reg_32(HPRT); + + if(count > max_error_count) { + otg_dbg(OTG_DBG_ROOTHUB, + "Port Reset Fail : HPRT : 0x%x\n", hprt.d32); + return USB_ERR_FAIL; + } + count++; + + } while(!hprt.b.prtena); + + } + return USB_ERR_SUCCESS; +} + +/** + * int root_hub_feature( + * struct usb_hcd *hcd, + * const u8 port, + * const u16 type_req, + * const u16 feature, + * void* buf) + * + * @brief Get port change bitmap information + * + * @param [IN] port : port number + * [IN] type_req : request type of hub feature as usb 2.0 spec + * [IN] feature : hub feature as usb 2.0 spec + * [OUT] status : buffer to store results + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * + * @remark + * + */ +__inline__ int root_hub_feature( + struct usb_hcd *hcd, + const u8 port, + const u16 type_req, + const u16 feature, + void *buf) +{ + int retval = USB_ERR_SUCCESS; + usb_hub_descriptor_t *desc = NULL; + u32 port_status = 0; + hprt_t hprt = {.d32 = 0}; + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + + otg_dbg(OTG_DBG_ROOTHUB, " root_hub_feature\n"); + + switch (type_req) { + case ClearHubFeature: + otg_dbg(OTG_DBG_ROOTHUB, "case ClearHubFeature\n"); + switch (feature) { + case C_HUB_LOCAL_POWER: + otg_dbg(OTG_DBG_ROOTHUB, + "case ClearHubFeature -C_HUB_LOCAL_POWER\n"); + break; + case C_HUB_OVER_CURRENT: + otg_dbg(OTG_DBG_ROOTHUB, + "case ClearHubFeature -C_HUB_OVER_CURRENT\n"); + /* Nothing required here */ + break; + default: + retval = USB_ERR_FAIL; + } + break; + + case ClearPortFeature: + otg_dbg(OTG_DBG_ROOTHUB, "case ClearPortFeature\n"); + switch (feature) { + case USB_PORT_FEAT_ENABLE: + otg_dbg(OTG_DBG_ROOTHUB, + "case ClearPortFeature -USB_PORT_FEAT_ENABLE\n"); + hprt.b.prtena = 1; + update_reg_32(HPRT, hprt.d32); + break; + + case USB_PORT_FEAT_SUSPEND: + otg_dbg(OTG_DBG_ROOTHUB, + "case ClearPortFeature -USB_PORT_FEAT_SUSPEND\n"); + bus_resume(otghost); + break; + + case USB_PORT_FEAT_POWER: + otg_dbg(OTG_DBG_ROOTHUB, + "case ClearPortFeature -USB_PORT_FEAT_POWER\n"); + setPortPower(false); + break; + + case USB_PORT_FEAT_INDICATOR: + otg_dbg(OTG_DBG_ROOTHUB, + "case ClearPortFeature -USB_PORT_FEAT_INDICATOR\n"); + /* Port inidicator not supported */ + break; + + case USB_PORT_FEAT_C_CONNECTION: + otg_dbg(OTG_DBG_ROOTHUB, + "case ClearPortFeature -USB_PORT_FEAT_C_CONNECTION\n"); + /* Clears drivers internal connect status change flag */ + otghost->port_flag.b.port_connect_status_change = 0; + break; + + case USB_PORT_FEAT_C_RESET: + otg_dbg(OTG_DBG_ROOTHUB, + "case ClearPortFeature -USB_PORT_FEAT_C_RESET\n"); + /* Clears the driver's internal Port Reset Change flag*/ + otghost->port_flag.b.port_reset_change = 0; + break; + + case USB_PORT_FEAT_C_ENABLE: + otg_dbg(OTG_DBG_ROOTHUB, + "case ClearPortFeature -USB_PORT_FEAT_C_ENABLE\n"); + /* Clears the driver's internal Port Enable/Disable Change flag */ + otghost->port_flag.b.port_enable_change = 0; + break; + + case USB_PORT_FEAT_C_SUSPEND: + otg_dbg(OTG_DBG_ROOTHUB, + "case ClearPortFeature -USB_PORT_FEAT_C_SUSPEND\n"); + /* Clears the driver's internal Port Suspend + * Change flag, which is set when resume signaling on + * the host port is complete */ + otghost->port_flag.b.port_suspend_change = 0; + break; + + case USB_PORT_FEAT_C_OVER_CURRENT: + otg_dbg(OTG_DBG_ROOTHUB, "case ClearPortFeature - \ + USB_PORT_FEAT_C_OVER_CURRENT\n"); + otghost->port_flag.b.port_over_current_change = 0; + break; + + default: + retval = USB_ERR_FAIL; + otg_dbg(OTG_DBG_ROOTHUB, + "case ClearPortFeature - FAIL\n"); + } + break; + + case GetHubDescriptor: + otg_dbg(OTG_DBG_ROOTHUB, "case GetHubDescriptor\n"); + desc = (usb_hub_descriptor_t *)buf; + desc->desc_length = 9; + desc->desc_type = 0x29; + desc->port_number = 1; + desc->hub_characteristics = 0x08; + desc->power_on_to_power_good = 1; + desc->hub_control_current = 0; + desc->DeviceRemovable[0] = 0; + desc->DeviceRemovable[1] = 0xff; + break; + + case GetHubStatus: + otg_dbg(OTG_DBG_ROOTHUB, "case GetHubStatus\n"); + otg_mem_set(buf, 0, 4); + break; + + case GetPortStatus: + otg_dbg(OTG_DBG_ROOTHUB, "case GetPortStatus\n"); + + if (otghost->port_flag.b.port_connect_status_change) { + port_status |= (1 << USB_PORT_FEAT_C_CONNECTION); + otg_dbg(OTG_DBG_ROOTHUB, + "case GetPortStatus - USB_PORT_FEAT_C_CONNECTION\n"); + } + + if (otghost->port_flag.b.port_enable_change) { + port_status |= (1 << USB_PORT_FEAT_C_ENABLE); + otg_dbg(OTG_DBG_ROOTHUB, + "case GetPortStatus - USB_PORT_FEAT_C_ENABLE\n"); + } + + if (otghost->port_flag.b.port_suspend_change) { + port_status |= (1 << USB_PORT_FEAT_C_SUSPEND); + otg_dbg(OTG_DBG_ROOTHUB, + "case GetPortStatus - USB_PORT_FEAT_C_SUSPEND\n"); + } + + if (otghost->port_flag.b.port_reset_change) { + port_status|= (1 << USB_PORT_FEAT_C_RESET); + otg_dbg(OTG_DBG_ROOTHUB, + "case GetPortStatus - USB_PORT_FEAT_C_RESET\n"); + } + + if (otghost->port_flag.b.port_over_current_change) { + port_status |= (1 << USB_PORT_FEAT_C_OVER_CURRENT); + otg_dbg(OTG_DBG_ROOTHUB, + "case GetPortStatus - USB_PORT_FEAT_C_OVER_CURRENT\n"); + } + + if (!otghost->port_flag.b.port_connect_status) { + /* + * The port is disconnected, which means the core is + * either in device mode or it soon will be. Just + * return 0's for the remainder of the port status + * since the port register can't be read if the core + * is in device mode. + */ + otg_dbg(OTG_DBG_ROOTHUB, + "case GetPortStatus - disconnected\n"); + + *((__le32*)buf) = cpu_to_le32(port_status); + /*break;*/ + } + + hprt.d32 = read_reg_32(HPRT); + + if (hprt.b.prtconnsts) + port_status|= (1 << USB_PORT_FEAT_CONNECTION); + + if (hprt.b.prtena) + port_status |= (1 << USB_PORT_FEAT_ENABLE); + + if (hprt.b.prtsusp) + port_status |= (1 << USB_PORT_FEAT_SUSPEND); + + if (hprt.b.prtovrcurract) + port_status |= (1 << USB_PORT_FEAT_OVER_CURRENT); + + if (hprt.b.prtrst) + port_status |= (1 << USB_PORT_FEAT_RESET); + + if (hprt.b.prtpwr) + port_status |= (1 << USB_PORT_FEAT_POWER); + + if (hprt.b.prtspd == 0) { + port_status |= USB_PORT_STAT_HIGH_SPEED; + } + else { + if (hprt.b.prtspd == 2) + port_status |= USB_PORT_STAT_LOW_SPEED; + } + + if (hprt.b.prttstctl) + port_status |= (1 << USB_PORT_FEAT_TEST); + + *((__le32*)buf) = cpu_to_le32(port_status); + + otg_dbg(OTG_DBG_ROOTHUB, "port_status=0x%x.\n", port_status); + break; + + case SetHubFeature: + otg_dbg(OTG_DBG_ROOTHUB, "case SetHubFeature\n"); + /* No HUB features supported */ + break; + + case SetPortFeature: + otg_dbg(OTG_DBG_ROOTHUB, "case SetPortFeature\n"); + if (!otghost->port_flag.b.port_connect_status) { + /* + * The port is disconnected, which means the core is + * either in device mode or it soon will be. Just + * return without doing anything since the port + * register can't be written if the core is in device + * mode. + */ + otg_dbg(OTG_DBG_ROOTHUB, + "case SetPortFeature - disconnected\n"); + + /*break;*/ + } + + switch (feature) { + case USB_PORT_FEAT_SUSPEND: + otg_dbg(true, + "case SetPortFeature -USB_PORT_FEAT_SUSPEND\n"); + bus_suspend(); + break; + + case USB_PORT_FEAT_POWER: + otg_dbg(true, + "case SetPortFeature -USB_PORT_FEAT_POWER\n"); + setPortPower(true); + break; + + case USB_PORT_FEAT_RESET: + otg_dbg(true, + "case SetPortFeature -USB_PORT_FEAT_RESET\n"); + retval = reset_and_enable_port(hcd, port); + /* + if(retval == USB_ERR_SUCCESS) + wake_lock(&otghost->wake_lock); + */ + break; + + case USB_PORT_FEAT_INDICATOR: + otg_dbg(true, + "case SetPortFeature -USB_PORT_FEAT_INDICATOR\n"); + break; + + default : + otg_dbg(true, "case SetPortFeature -USB_ERR_FAIL\n"); + retval = USB_ERR_FAIL; + break; + } + break; + + default: + retval = USB_ERR_FAIL; + otg_err(true, "root_hub_feature() Function Error\n"); + break; + } + + if(retval != USB_ERR_SUCCESS) + retval = USB_ERR_FAIL; + + return retval; +} + +/** + * void bus_suspend(void) + * + * @brief Make suspend status when this platform support PM Mode + * + * @param None + * + * @return None + * + * @remark + * + */ +void bus_suspend(void) +{ + hprt_t hprt; + pcgcctl_t pcgcctl; + + otg_dbg(OTG_DBG_ROOTHUB, " bus_suspend\n"); + + hprt.d32 = 0; + pcgcctl.d32 = 0; + + hprt.b.prtsusp = 1; + update_reg_32(HPRT, hprt.d32); + + pcgcctl.b.pwrclmp = 1; + update_reg_32(PCGCCTL,pcgcctl.d32); + udelay(1); + + pcgcctl.b.rstpdwnmodule = 1; + update_reg_32(PCGCCTL,pcgcctl.d32); + udelay(1); + + pcgcctl.b.stoppclk = 1; + update_reg_32(PCGCCTL,pcgcctl.d32); + udelay(1); +} + +/** + * int bus_resume(struct sec_otghost *otghost) + * + * @brief Make resume status when this platform support PM Mode + * + * @param None + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * + * @remark + * + */ +int bus_resume(struct sec_otghost *otghost) +{ + /* + hprt_t hprt; + pcgcctl_t pcgcctl; + hprt.d32 = 0; + pcgcctl.d32 = 0; + + pcgcctl.b.stoppclk = 1; + clear_reg_32(PCGCCTL,pcgcctl.d32); + udelay(1); + + pcgcctl.b.pwrclmp = 1; + clear_reg_32(PCGCCTL,pcgcctl.d32); + udelay(1); + + pcgcctl.b.rstpdwnmodule = 1; + clear_reg_32(PCGCCTL,pcgcctl.d32); + udelay(1); + + hprt.b.prtres = 1; + update_reg_32(HPRT, hprt.d32); + mdelay(20); + + clear_reg_32(HPRT, hprt.d32); + */ + otg_dbg(OTG_DBG_ROOTHUB, "bus_resume()......\n"); + + otg_dbg(OTG_DBG_ROOTHUB, "wait for 50 ms...\n"); + + mdelay(50); + if(oci_init(otghost) == USB_ERR_SUCCESS) { + if(oci_start(otghost) == USB_ERR_SUCCESS) { + otg_dbg(OTG_DBG_ROOTHUB, "OTG Init Success\n"); + return USB_ERR_SUCCESS; + } + } + + return USB_ERR_FAIL; +} diff --git a/drivers/usb/host/s3c-otg/s3c-otg-roothub.h b/drivers/usb/host/s3c-otg/s3c-otg-roothub.h new file mode 100644 index 0000000..03883d1 --- /dev/null +++ b/drivers/usb/host/s3c-otg/s3c-otg-roothub.h @@ -0,0 +1,65 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] :s3c-otg-roothub.h + * [Description] : The Header file defines the external and internal functions of RootHub. + * [Author] : Jang Kyu Hyeok { kyuhyeok.jang@samsung.com } + * [Department] : System LSI Division/Embedded S/W Platform + * [Created Date]: 2008/06/13 + * [Revision History] + * (1) 2008/06/13 by Jang Kyu Hyeok { kyuhyeok.jang@samsung.com } + * - Created this file and defines functions of RootHub + * + ****************************************************************************/ +/**************************************************************************** + * 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 _ROOTHUB_H_ +#define _ROOTHUB_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "s3c-otg-common-errorcode.h" +#include "s3c-otg-common-regdef.h" +#include "s3c-otg-common-datastruct.h" +#include "s3c-otg-hcdi-kal.h" +#include "s3c-otg-hcdi-memory.h" +#include "s3c-otg-oci.h" + +__inline__ int root_hub_feature( + struct usb_hcd *hcd, + const u8 port, + const u16 type_req, + const u16 feature, + void *buf); + +__inline__ int get_otg_port_status( + struct usb_hcd *hcd, const u8 port, char *status); + +int reset_and_enable_port(struct usb_hcd *hcd, const u8 port); +void bus_suspend(void); + +int bus_resume(struct sec_otghost *otghost); + +#ifdef __cplusplus +} +#endif +#endif /* _ROOTHUB_H_ */ + + diff --git a/drivers/usb/host/s3c-otg/s3c-otg-scheduler-ischeduler.c b/drivers/usb/host/s3c-otg/s3c-otg-scheduler-ischeduler.c new file mode 100644 index 0000000..d94fb1a --- /dev/null +++ b/drivers/usb/host/s3c-otg/s3c-otg-scheduler-ischeduler.c @@ -0,0 +1,369 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : Scheduler.c + * [Description] : The source file implements the internal functions of Scheduler. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2009/2/10 + * [Revision History] + * (1) 2008/06/03 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and implements functions of Scheduler + * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Optimizing for performance \n + * + ****************************************************************************/ +/**************************************************************************** + * 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 "s3c-otg-scheduler-scheduler.h" + +void init_scheduler(void) +{ + /*init_scheduling();*/ + init_transfer_ready_q(); +} + +/******************************************************************************/ +/*! + * @name int reserve_used_resource_for_periodic(u32 usb_time) + * + * @brief this function reserves the necessary resource of USB Transfer for Periodic Transfer. + * So, this function firstly checks there ares some available USB Time + * and Channel resource for USB Transfer. + * if there exists necessary resources for Periodic Transfer, then reserves the resource. + * + * @param [IN] usb_time = indicates the USB Time for the USB Transfer. + * + * @return USB_ERR_SUCCESS - if success to insert pInsertED to S3CScheduler. + * USB_ERR_NO_BANDWIDTH - if fail to reserve the USB Bandwidth. + * USB_ERR_NO_CHANNEL - if fail to reserve the Channel. + */ +/******************************************************************************/ + +int reserve_used_resource_for_periodic(u32 usb_time, u8 dev_speed, u8 trans_type) +{ + if(inc_perio_bus_time(usb_time,dev_speed)==USB_ERR_SUCCESS) { + if(inc_perio_chnum()==USB_ERR_SUCCESS) { + otg_usbcore_inc_usb_bandwidth(usb_time); + otg_usbcore_inc_periodic_transfer_cnt(trans_type); + return USB_ERR_SUCCESS; + } + else { + dec_perio_bus_time(usb_time); + return USB_ERR_NO_CHANNEL; + } + } + else { + return USB_ERR_NO_BANDWIDTH; + } +} + +/******************************************************************************/ +/*! + * @name int free_usb_resource_for_periodic(ed_t * pFreeED) + * + * @brief this function frees the resources to be allocated to pFreeED at S3CScheduler. + * that is, this functions only releases the resources to be allocated by S3C6400Scheduler. + * + * @param [IN] pFreeED = indicates ed_t to have the information of the resource to be released. + * + * @return USB_ERR_SUCCESS - if success to free the USB Resource. + * USB_ERR_FAIL - if fail to free the USB Resrouce. + */ +/******************************************************************************/ +int free_usb_resource_for_periodic( + u32 free_usb_time, u8 free_chnum, u8 trans_type) +{ + if(dec_perio_bus_time(free_usb_time)==USB_ERR_SUCCESS) { + if(dec_perio_chnum()==USB_ERR_SUCCESS) { + if(free_chnum!=CH_NONE) { + oci_channel_dealloc(free_chnum); + set_transferring_td_array(free_chnum, 0); + } + otg_usbcore_des_usb_bandwidth(free_usb_time); + otg_usbcore_des_periodic_transfer_cnt(trans_type); + return USB_ERR_SUCCESS; + } + } + return USB_ERR_FAIL; +} + +/******************************************************************************/ +/*! + * @name int remove_ed_from_scheduler(ed_t * remove_ed) + * + * @brief this function just remove the remove_ed from TransferReadyQ. So if you want to + * stop the USB Tranfer of remove_ed or release the releated resources. + * you should call another functions of S3CScheduler. + * + * @param [IN] remove_ed = indicates ed_t to be removed from TransferReadyQ. + * + * @return USB_ERR_SUCCESS - if success to remove the remove_ed from TransferReadyQ. + * USB_ERR_FAIL - if fail to remove the remove_ed from TransferReadyQ. + */ + /******************************************************************************/ +int remove_ed_from_scheduler(ed_t *remove_ed) +{ + if(remove_ed->ed_status.is_in_transfer_ready_q) { + remove_ed_from_ready_q(remove_ed); + remove_ed->ed_status.is_in_transfer_ready_q = false; + + return USB_ERR_SUCCESS; + } + else { + return USB_ERR_FAIL; + } +} + +/******************************************************************************/ +/*! + * @name int cancel_to_transfer_td(struct sec_otghost *otghost, td_t *cancel_td) + * + * @brief this function stop to execute the USB Transfer of cancel_td and + * release the Channel Resources to be allocated the cancel_td ,if the Transfer Type of + * cancel_td is NonPeriodic Transfer. + * this function don't release any usb resources(Channel, USB Bandwidth) for Periodic Transfer. + * if you want to release some usb resources for a periodic Transfer, you should call + * the free_usb_resource_for_periodic() + * + * @param [IN] cancel_td = indicates the td_t to be canceled. + * + * @return USB_ERR_SUCCESS - if success to cancel the USB Transfer of cancel_td. + * USB_ERR_FAIL - if fail to cancel the USB Transfer of cancel_td. + */ + /******************************************************************************/ +int cancel_to_transfer_td(struct sec_otghost *otghost, td_t *cancel_td) +{ + if(cancel_td->is_transfer_done) { + return USB_ERR_FAIL; + } + + if(cancel_td->is_transferring) { + int err; + + err = oci_stop_transfer(otghost, cancel_td->cur_stransfer.alloc_chnum); + + if(err == USB_ERR_SUCCESS) { + set_transferring_td_array(cancel_td->cur_stransfer.alloc_chnum,0); + + cancel_td->cur_stransfer.alloc_chnum = CH_NONE; + cancel_td->is_transferring = false; + cancel_td->parent_ed_p->ed_status.is_in_transferring = false; + cancel_td->parent_ed_p->ed_status.in_transferring_td = 0; + cancel_td->parent_ed_p->is_need_to_insert_scheduler = true; + + if(cancel_td->cur_stransfer.ed_desc_p->endpoint_type == BULK_TRANSFER || + cancel_td->cur_stransfer.ed_desc_p->endpoint_type == CONTROL_TRANSFER ) { + dec_nonperio_chnum(); + } + return err; + } + else { + return err; + } + } + else { + return USB_ERR_FAIL; + } + return USB_ERR_SUCCESS; +} + + +/******************************************************************************/ +/*! + * @name int retransmit(struct sec_otghost *otghost, td_t *retrasmit_td) + * + * @brief this function retransmits the retrasmit_td immediately. + * So, the Channel of pRetransmitted is reused for retransmittion. + * + * @param [IN] retrasmit_td = indicates the pointer ot the td_t to be retransmitted. + * + * @return USB_ERR_SUCCESS - if success to retransmit the retrasmit_td. + * USB_ERR_FAIL - if fail to retransmit the retrasmit_td. + */ + /******************************************************************************/ +int retransmit(struct sec_otghost *otghost, td_t *retrasmit_td) +{ + u32 td_addr=0; + + if(get_transferring_td_array(retrasmit_td->cur_stransfer.alloc_chnum,&td_addr)==USB_ERR_SUCCESS) { + if(td_addr == (u32)retrasmit_td) { + if(oci_start_transfer(otghost, &retrasmit_td->cur_stransfer) + == retrasmit_td->cur_stransfer.alloc_chnum) { + retrasmit_td->is_transferring = true; + retrasmit_td->parent_ed_p->ed_status.in_transferring_td = (u32)retrasmit_td; + retrasmit_td->parent_ed_p->ed_status.is_in_transfer_ready_q = false; + retrasmit_td->parent_ed_p->ed_status.is_in_transferring = true; + } + } + else { + return USB_ERR_FAIL; + } + } + else { + return USB_ERR_FAIL; + } + + return USB_ERR_SUCCESS; + +} + +/******************************************************************************/ +/*! + * @name int reschedule(td_t *reschedule_td) + * + * @brief this function re-schedules the reschedule_td. + * So, the Channel of pRescheuleTD is released and reschedule_td is inserted to TransferReadyQ. + * + * @param [IN] reschedule_td = indicates the pointer ot the td_t to be rescheduled. + * + * @return USB_ERR_SUCCESS - if success to re-schedule the reschedule_td. + * USB_ERR_FAIL - if fail to re-schedule the reschedule_td. + */ + /******************************************************************************/ +int reschedule(td_t *reschedule_td) +{ + u32 td_addr; + + if(get_transferring_td_array(reschedule_td->cur_stransfer.alloc_chnum, &td_addr)==USB_ERR_SUCCESS) + { + if((u32)reschedule_td == td_addr) + { + set_transferring_td_array(reschedule_td->cur_stransfer.alloc_chnum, 0); + oci_channel_dealloc(reschedule_td->cur_stransfer.alloc_chnum); + + reschedule_td->cur_stransfer.alloc_chnum = CH_NONE; + reschedule_td->parent_ed_p->is_need_to_insert_scheduler = true; + reschedule_td->parent_ed_p->ed_status.in_transferring_td = 0; + + if(reschedule_td->parent_ed_p->ed_desc.endpoint_type == BULK_TRANSFER|| + reschedule_td->parent_ed_p->ed_desc.endpoint_type == CONTROL_TRANSFER ) + { + /* Increase the available Channel */ + dec_nonperio_chnum(); + + } + + insert_ed_to_ready_q(reschedule_td->parent_ed_p, false); + reschedule_td->parent_ed_p->ed_status.is_in_transfer_ready_q =true; + + } + else + { + /* this case is not support.... */ + } + } + + return USB_ERR_SUCCESS; +} + +/******************************************************************************/ +/*! + * @name int deallocate(td_t *deallocate_td) + * + * @brief this function frees resources to be allocated deallocate_td by S3CScheduler. + * this function just free the resource by S3CScheduler. that is, Channel Resource. + * if there are another td_t at ed_t, deallocate() insert the ed_t to TransferReadyQ. + * + * @param [IN] deallocate_td = indicates the pointer ot the td_t to be deallocated. + * + * @return USB_ERR_SUCCESS - if success to dealloate the resources for the deallocate_td. + * USB_ERR_FAIL - if fail to dealloate the resources for the deallocate_td. + */ + /******************************************************************************/ +int deallocate(td_t *deallocate_td) +{ + u32 td_addr; + + if(get_transferring_td_array(deallocate_td->cur_stransfer.alloc_chnum , &td_addr)==USB_ERR_SUCCESS) + { + if((u32)deallocate_td == td_addr) + { + set_transferring_td_array(deallocate_td->cur_stransfer.alloc_chnum, 0); + oci_channel_dealloc(deallocate_td->cur_stransfer.alloc_chnum); + + deallocate_td->cur_stransfer.alloc_chnum = CH_NONE; + + if(deallocate_td->parent_ed_p->ed_desc.endpoint_type == BULK_TRANSFER|| + deallocate_td->parent_ed_p->ed_desc.endpoint_type == CONTROL_TRANSFER ) + { + /* Increase the available Channel */ + dec_nonperio_chnum(); + } + + deallocate_td->parent_ed_p->is_need_to_insert_scheduler = true; + + if(deallocate_td->parent_ed_p->num_td) + { + /* insert ed_t to TransferReadyQ. */ + insert_ed_to_ready_q(deallocate_td->parent_ed_p , false); + deallocate_td->parent_ed_p->ed_status.is_in_transfer_ready_q = true; + deallocate_td->parent_ed_p->is_need_to_insert_scheduler = false; + } + return USB_ERR_SUCCESS; + } + else + { + return USB_ERR_FAIL; + } + } + else + { + return USB_ERR_FAIL; + } + +} + +/* TBD.... */ +void do_schedule(struct sec_otghost *otghost) +{ + if(get_avail_chnum()) { + do_periodic_schedule(otghost); + do_nonperiodic_schedule(otghost); + } +} + +/******************************************************************************/ +/*! + * @name int get_td_info(u8 chnum, + * unsigned int *td_addr_p) + * + * @brief this function returns the pointer of td_t at TransferringTDArray[chnum] + * + * @param [IN] chnum = indicates the index of TransferringTDArray + * to include the address of td_t which we gets + * [OUT] td_addr_p= indicate pointer to store the address of td_t. + * + * @return USB_ERR_SUCCESS -if success to get the address of td_t. + * USB_ERR_FAIL -if fail to get the address of td_t. + */ + /******************************************************************************/ +int get_td_info( u8 chnum, + unsigned int *td_addr_p) +{ + u32 td_addr; + + if(get_transferring_td_array(chnum, &td_addr)==USB_ERR_SUCCESS) + { + *td_addr_p = td_addr; + return USB_ERR_SUCCESS; + } + + return USB_ERR_FAIL; +} + + diff --git a/drivers/usb/host/s3c-otg/s3c-otg-scheduler-readyq.c b/drivers/usb/host/s3c-otg/s3c-otg-scheduler-readyq.c new file mode 100644 index 0000000..7b41f0c --- /dev/null +++ b/drivers/usb/host/s3c-otg/s3c-otg-scheduler-readyq.c @@ -0,0 +1,264 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : TransferReadyQ.c + * [Description] : The source file implements the internal functions of TransferReadyQ. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2008/06/04 + * [Revision History] + * (1) 2008/06/04 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and implements functions of TransferReadyQ. + * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Optimizing for performance \n + * + ****************************************************************************/ + +/**************************************************************************** + * 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 "s3c-otg-scheduler-scheduler.h" + +static trans_ready_q_t periodic_trans_ready_q; +static trans_ready_q_t nonperiodic_trans_ready_q; + +/******************************************************************************/ +/*! + * @name void init_transfer_ready_q(void) + * + * @brief this function initiates PeriodicTransferReadyQ and NonPeriodicTransferReadyQ. + * + * + * @param void + * + * @return void. + */ +/******************************************************************************/ +void init_transfer_ready_q(void) +{ + otg_dbg(OTG_DBG_SCHEDULE,"start init_transfer_ready_q\n"); + + otg_list_init(&periodic_trans_ready_q.trans_ready_q_list_head); + periodic_trans_ready_q.is_periodic_transfer = true; + periodic_trans_ready_q.trans_ready_entry_num = 0; + periodic_trans_ready_q.total_alloc_chnum = 0; + periodic_trans_ready_q.total_perio_bus_bandwidth = 0; + + otg_list_init(&nonperiodic_trans_ready_q.trans_ready_q_list_head); + nonperiodic_trans_ready_q.is_periodic_transfer = false; + nonperiodic_trans_ready_q.trans_ready_entry_num = 0; + nonperiodic_trans_ready_q.total_alloc_chnum = 0; + nonperiodic_trans_ready_q.total_perio_bus_bandwidth = 0; + + +} + + +/******************************************************************************/ +/*! + * @name int insert_ed_to_ready_q(ed_t *insert_ed, + * bool f_isfirst) + * + * @brief this function inserts ed_t * to TransferReadyQ. + * + * + * @param [IN] insert_ed = indicates the ed_t to be inserted to TransferReadyQ. + * [IN] f_isfirst = indicates whether the insert_ed is inserted as first entry of TransferReadyQ. + * + * @return USB_ERR_SUCCESS -if successes to insert the insert_ed to TransferReadyQ. + * USB_ERR_FAILl -if fails to insert the insert_ed to TransferReadyQ. + */ +/******************************************************************************/ +int insert_ed_to_ready_q(ed_t *insert_ed, + bool f_isfirst) +{ + + if(insert_ed->ed_desc.endpoint_type == BULK_TRANSFER|| + insert_ed->ed_desc.endpoint_type == CONTROL_TRANSFER) + { + if(f_isfirst) + { + otg_list_push_next(&insert_ed->trans_ready_q_list_entry,&nonperiodic_trans_ready_q.trans_ready_q_list_head); + } + else + { + otg_list_push_prev(&insert_ed->trans_ready_q_list_entry,&nonperiodic_trans_ready_q.trans_ready_q_list_head); + } + nonperiodic_trans_ready_q.trans_ready_entry_num++; + } + else + { + if(f_isfirst) + { + otg_list_push_next(&insert_ed->trans_ready_q_list_entry,&periodic_trans_ready_q.trans_ready_q_list_head); + } + else + { + otg_list_push_prev(&insert_ed->trans_ready_q_list_entry,&periodic_trans_ready_q.trans_ready_q_list_head); + } + periodic_trans_ready_q.trans_ready_entry_num++; + } + + return USB_ERR_SUCCESS; + +} + + +u32 get_periodic_ready_q_entity_num(void) +{ + return periodic_trans_ready_q.trans_ready_entry_num; +} +/******************************************************************************/ +/*! + * @name int remove_ed_from_ready_q(ed_t *remove_ed) + * + * @brief this function removes ed_t * from TransferReadyQ. + * + * + * @param [IN] remove_ed = indicate the ed_t to be removed from TransferReadyQ. + * + * @return USB_ERR_SUCCESS -if successes to remove the remove_ed from TransferReadyQ. + * USB_ERR_FAILl -if fails to remove the remove_ed from TransferReadyQ. + */ +/******************************************************************************/ +int remove_ed_from_ready_q(ed_t *remove_ed) +{ +// SPINLOCK_t SLForRemoveED_t = SPIN_LOCK_INIT; +// u32 uiSLFlag=0; + + otg_list_pop(&remove_ed->trans_ready_q_list_entry); + + if(remove_ed->ed_desc.endpoint_type == BULK_TRANSFER|| + remove_ed->ed_desc.endpoint_type == CONTROL_TRANSFER) + { +// spin_lock_irq_save_otg(&SLForRemoveED_t, uiSLFlag); +// otg_list_pop(&remove_ed->trans_ready_q_list_entry); + nonperiodic_trans_ready_q.trans_ready_entry_num--; +// spin_unlock_irq_save_otg(&SLForRemoveED_t, uiSLFlag); + } + else + { +// spin_lock_irq_save_otg(&SLForRemoveED_t, uiSLFlag); +// otg_list_pop(&remove_ed->trans_ready_q_list_entry); + periodic_trans_ready_q.trans_ready_entry_num--; +// spin_unlock_irq_save_otg(&SLForRemoveED_t, uiSLFlag); + } + + return USB_ERR_SUCCESS; + +} + +//by ss1 unused func +/* +bool check_ed_on_ready_q(ed_t *check_ed_p) +{ + + if(check_ed_p->ed_status.is_in_transfer_ready_q) + return true; + else + return false; +}*/ + +/******************************************************************************/ +/*! + * @name int get_ed_from_ready_q(bool f_isperiodic, + * td_t **get_ed) + * + * @brief this function returns the first entity of TransferReadyQ. + * if there are some ed_t on TransferReadyQ, this function pops first ed_t from TransferReadyQ. + * So, the TransferReadyQ don's has the poped ed_t. + * + * + * @param [IN] f_isperiodic = indicate whether Periodic or not + * [OUT] get_ed = indicate the double pointer to store the address of first entity + * on TransferReadyQ. + * + * @return USB_ERR_SUCCESS -if successes to get frist ed_t from TransferReadyQ. + * USB_ERR_NO_ENTITY -if fails to get frist ed_t from TransferReadyQ + * because there is no entity on TransferReadyQ. + */ +/******************************************************************************/ + +int get_ed_from_ready_q(bool f_isperiodic, + ed_t **get_ed) +{ + if(f_isperiodic) + { + otg_list_head *transreadyq_list_entity=NULL; + + if(periodic_trans_ready_q.trans_ready_entry_num==0) + { + return USB_ERR_NO_ENTITY; + } + + transreadyq_list_entity = periodic_trans_ready_q.trans_ready_q_list_head.next; + + //if(transreadyq_list_entity!= &periodic_trans_ready_q.trans_ready_q_list_head) + if(!otg_list_empty(&periodic_trans_ready_q.trans_ready_q_list_head)) + { + *get_ed = otg_list_get_node(transreadyq_list_entity,ed_t,trans_ready_q_list_entry); + if (transreadyq_list_entity->prev == LIST_POISON2 || + transreadyq_list_entity->next == LIST_POISON1) { + printk(KERN_ERR "s3c-otg-scheduler get_ed_from_ready_q error\n"); + periodic_trans_ready_q.trans_ready_entry_num =0; + } + else { + otg_list_pop(transreadyq_list_entity); + periodic_trans_ready_q.trans_ready_entry_num--; + } + + return USB_ERR_SUCCESS; + } + else + { + return USB_ERR_NO_ENTITY; + } + } + else + { + otg_list_head *transreadyq_list_entity=NULL; + + if(nonperiodic_trans_ready_q.trans_ready_entry_num==0) + { + return USB_ERR_NO_ENTITY; + } + + transreadyq_list_entity = nonperiodic_trans_ready_q.trans_ready_q_list_head.next; + + //if(transreadyq_list_entity!= &nonperiodic_trans_ready_q.trans_ready_q_list_head) + if(!otg_list_empty(&nonperiodic_trans_ready_q.trans_ready_q_list_head)) + { + *get_ed = otg_list_get_node(transreadyq_list_entity,ed_t, trans_ready_q_list_entry); + if (transreadyq_list_entity->prev == LIST_POISON2 || + transreadyq_list_entity->next == LIST_POISON1) { + printk(KERN_ERR "s3c-otg-scheduler get_ed_from_ready_q error\n"); + nonperiodic_trans_ready_q.trans_ready_entry_num =0; + } + else { + otg_list_pop(transreadyq_list_entity); + nonperiodic_trans_ready_q.trans_ready_entry_num--; + } + + return USB_ERR_SUCCESS; + } + else + { + return USB_ERR_NO_ENTITY; + } + } +} + + diff --git a/drivers/usb/host/s3c-otg/s3c-otg-scheduler-scheduler.c b/drivers/usb/host/s3c-otg/s3c-otg-scheduler-scheduler.c new file mode 100644 index 0000000..034cec4 --- /dev/null +++ b/drivers/usb/host/s3c-otg/s3c-otg-scheduler-scheduler.c @@ -0,0 +1,424 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : Scheduler.c + * [Description] : The source file implements the internal functions of Scheduler. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2008/06/04 + * [Revision History] + * (1) 2008/06/03 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and implements functions of Scheduler + * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Optimizing for performance \n + * + ****************************************************************************/ +/**************************************************************************** + * 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 "s3c-otg-scheduler-scheduler.h" + +//Define constant variables + +//the max periodic bus time is 80%*125us on High Speed Mode +static const u32 perio_highbustime_threshold = 100; + +//the max periodic bus time is 90%*1000us(1ms) on Full/Low Speed Mode . +static const u32 perio_fullbustime_threshold = 900; + +static const u8 perio_chnum_threshold = 14; +//static const u8 total_chnum_threshold = 16; +static u8 total_chnum_threshold = 16; + + //Define global variables +// kevinh: add volatile +static volatile u32 perio_used_bustime = 0; +static volatile u8 perio_used_chnum = 0; +static volatile u8 nonperio_used_chnum = 0; +static volatile u8 total_used_chnum = 0; +static volatile u32 transferring_td_array[16]={0}; + +void reset_scheduler_numbers(void) { + total_chnum_threshold = 16; + perio_used_bustime = 0; + perio_used_chnum = 0; + nonperio_used_chnum = 0; + total_used_chnum = 0; + memset(transferring_td_array,0,sizeof(transferring_td_array)); +} + +int inc_perio_bus_time(u32 bus_time, u8 dev_speed) +{ + switch(dev_speed) { + case HIGH_SPEED_OTG: + if((bus_time+perio_used_bustime)<=perio_highbustime_threshold) { + perio_used_bustime=+bus_time; + return USB_ERR_SUCCESS; + } + else { + return USB_ERR_FAIL; + } + + case LOW_SPEED_OTG: + case FULL_SPEED_OTG: + if((bus_time+perio_used_bustime)<=perio_fullbustime_threshold) { + perio_used_bustime=+bus_time; + return USB_ERR_SUCCESS; + } + else { + return USB_ERR_FAIL; + } + case SUPER_SPEED_OTG: + break; + default: + break; + } + return USB_ERR_FAIL; +} + +int dec_perio_bus_time(u32 bus_time) +{ + if(perio_used_bustime >= bus_time ) { + perio_used_bustime =- bus_time; + return USB_ERR_SUCCESS; + } + else { + return USB_ERR_FAIL; + } +} + +int inc_perio_chnum(void) +{ + if(perio_used_chnum<perio_chnum_threshold) + { + if(total_used_chnum<total_chnum_threshold) + { + perio_used_chnum++; + total_used_chnum++; + return USB_ERR_SUCCESS; + } + } + return USB_ERR_FAIL; +} + +u8 get_avail_chnum(void) +{ + return total_chnum_threshold - total_used_chnum; +} + +int dec_perio_chnum(void) +{ + if(perio_used_chnum>0) + { + if(total_used_chnum>0) + { + perio_used_chnum--; + total_used_chnum--; + return USB_ERR_SUCCESS; + } + } + return USB_ERR_FAIL; +} + +int inc_non_perio_chnum(void) +{ + if(nonperio_used_chnum<total_chnum_threshold) + { + if(total_used_chnum<total_chnum_threshold) + { + nonperio_used_chnum++; + total_used_chnum++; + return USB_ERR_SUCCESS; + } + } + return USB_ERR_FAIL; +} + +int dec_nonperio_chnum(void) +{ + if(nonperio_used_chnum>0) + { + if(total_used_chnum>0) + { + nonperio_used_chnum--; + total_used_chnum--; + return USB_ERR_SUCCESS; + } + } + return USB_ERR_FAIL; +} + +int get_transferring_td_array(u8 chnum, unsigned int *td_addr) +{ + if(transferring_td_array[chnum]!=0) + { + *td_addr = transferring_td_array[chnum]; + return USB_ERR_SUCCESS; + } + + return USB_ERR_FAIL; +} + +int set_transferring_td_array(u8 chnum, u32 td_addr) +{ + if(td_addr ==0) + { + transferring_td_array[chnum] = td_addr; + return USB_ERR_SUCCESS; + } + + if(transferring_td_array[chnum] == 0) + { + transferring_td_array[chnum] = td_addr; + return USB_ERR_SUCCESS; + } + else + { + return USB_ERR_FAIL; + } +} + +/******************************************************************************/ +/*! + * @name int insert_ed_to_scheduler(struct sec_otghost *otghost, ed_t *insert_ed) + * + * @brief this function transfers the insert_ed to S3C6400Scheduler, and + * after that, the insert_ed is inserted to TransferReadyQ and scheduled by Scheduler. + * + * + * @param [IN] insert_ed = indicates pointer of ed_t to be inserted to TransferReadyQ. + * + * @return USB_ERR_ALREADY_EXIST - if the insert_ed is already existed. + * USB_ERR_SUCCESS - if success to insert insert_ed to S3CScheduler. + */ +/******************************************************************************/ +int insert_ed_to_scheduler(struct sec_otghost *otghost, ed_t *insert_ed) +{ + if(!insert_ed->is_need_to_insert_scheduler) + return USB_ERR_ALREADY_EXIST; + + insert_ed_to_ready_q(insert_ed, false); + insert_ed->is_need_to_insert_scheduler = false; + insert_ed->ed_status.is_in_transfer_ready_q = true; + + do_periodic_schedule(otghost); + do_nonperiodic_schedule(otghost); + + return USB_ERR_SUCCESS; +} + +/******************************************************************************/ +/*! + * @name int do_periodic_schedule(struct sec_otghost *otghost) + * + * @brief this function schedules PeriodicTransferReadyQ. + * this function checks whether PeriodicTransferReadyQ has some ed_t. + * if there are some ed_t on PeriodicTransferReadyQ + * , this function request to start USB Trasnfer to S3C6400OCI. + * + * + * @param void + * + * @return void + */ +/******************************************************************************/ +void do_periodic_schedule(struct sec_otghost *otghost) +{ + ed_t *scheduling_ed= NULL; + int err_sched = USB_ERR_SUCCESS; + u32 sched_cnt = 0; + + otg_dbg(OTG_DBG_SCHEDULE,"*******Start to DoPeriodicSchedul*********\n"); + + sched_cnt = get_periodic_ready_q_entity_num(); + + while(sched_cnt) { + //in periodic transfser, the channel resource was already reserved. + //So, we don't need this routine... + +start_sched_perio_transfer: + if(!sched_cnt) + goto end_sched_perio_transfer; + + err_sched = get_ed_from_ready_q(true, &scheduling_ed); + + if(err_sched==USB_ERR_SUCCESS) { + otg_list_head *td_list_entry; + td_t *td; + u32 cur_frame_num = 0; + + otg_dbg(OTG_DBG_SCHEDULE,"the ed_t to be scheduled :%d",(int)scheduling_ed); + sched_cnt--; + td_list_entry = scheduling_ed->td_list_entry.next; + + if(td_list_entry == &scheduling_ed->td_list_entry) { + //scheduling_ed has no td_t. so we schedules another ed_t on PeriodicTransferReadyQ. + goto start_sched_perio_transfer; + } + + if(scheduling_ed->ed_status.is_in_transferring) { + //scheduling_ed is already Scheduled. so we schedules another ed_t on PeriodicTransferReadyQ. + goto start_sched_perio_transfer; + } + + cur_frame_num = oci_get_frame_num(); + + if(((cur_frame_num-scheduling_ed->ed_desc.sched_frame)&HFNUM_MAX_FRNUM)>(HFNUM_MAX_FRNUM>>1)) { + insert_ed_to_ready_q(scheduling_ed, false); + goto start_sched_perio_transfer; + } + + td = otg_list_get_node(td_list_entry, td_t, td_list_entry); + + if((!td->is_transferring) && (!td->is_transfer_done)) { + u8 alloc_ch; + otg_dbg(OTG_DBG_SCHEDULE,"the td_t to be scheduled :%d",(int)td); + alloc_ch = oci_start_transfer(otghost, &td->cur_stransfer); + if(alloc_ch<total_chnum_threshold) { + td->cur_stransfer.alloc_chnum = alloc_ch; + set_transferring_td_array(alloc_ch, (u32)td); + + scheduling_ed->ed_status.is_in_transferring = true; + scheduling_ed->ed_status.is_in_transfer_ready_q = false; + scheduling_ed->ed_status.in_transferring_td = (u32)td; + + td->is_transferring = true; + } + else { + //we should insert the ed_t to TransferReadyQ, because the USB Transfer of the ed_t is failed. + scheduling_ed->ed_status.is_in_transferring = false; + scheduling_ed->ed_status.is_in_transfer_ready_q = true; + scheduling_ed->ed_status.in_transferring_td = 0; + + insert_ed_to_ready_q(scheduling_ed,true); + + scheduling_ed->is_need_to_insert_scheduler = false; + goto end_sched_perio_transfer; + } + + } + else { // the selected td_t was already transferring or completed to transfer. + //we should decide how to control this case. + goto end_sched_perio_transfer; + } + } + else { + // there is no ED on PeriodicTransferQ. So we finish scheduling. + goto end_sched_perio_transfer; + } + } + +end_sched_perio_transfer: + + return; +} + + +/******************************************************************************/ +/*! + * @name int do_nonperiodic_schedule(struct sec_otghost *otghost) + * + * @brief this function start to schedule thie NonPeriodicTransferReadyQ. + * this function checks whether NonPeriodicTransferReadyQ has some ed_t. + * if there are some ed_t on NonPeriodicTransferReadyQ + * , this function request to start USB Trasnfer to S3C6400OCI. + * + * + * @param void + * + * @return void + */ +/******************************************************************************/ +void do_nonperiodic_schedule(struct sec_otghost *otghost) +{ + if(total_used_chnum<total_chnum_threshold) { + ed_t *scheduling_ed; + int err_sched; + + while(1) { + +start_sched_nonperio_transfer: + + //check there is available channel resource for Non-Periodic Transfer. + if(total_used_chnum==total_chnum_threshold) { + goto end_sched_nonperio_transfer; + } + + err_sched = get_ed_from_ready_q(false, &scheduling_ed); + + if(err_sched ==USB_ERR_SUCCESS ) { + otg_list_head *td_list_entry; + td_t *td; + + td_list_entry = scheduling_ed->td_list_entry.next; + + //if(td_list_entry == &scheduling_ed->td_list_entry) + if(otg_list_empty(&scheduling_ed->td_list_entry)) { + //scheduling_ed has no td_t. so we schedules another ed_t on PeriodicTransferReadyQ. + goto start_sched_nonperio_transfer; + } + + if(scheduling_ed->ed_status.is_in_transferring) { + //scheduling_ed is already Scheduled. so we schedules another ed_t on PeriodicTransferReadyQ. + goto start_sched_nonperio_transfer; + } + + td = otg_list_get_node(td_list_entry, td_t, td_list_entry); + + if((!td->is_transferring) && (!td->is_transfer_done)) { + u8 alloc_ch; + + alloc_ch = oci_start_transfer(otghost, &td->cur_stransfer); + + if(alloc_ch<total_chnum_threshold) { + td->cur_stransfer.alloc_chnum = alloc_ch; + set_transferring_td_array(alloc_ch, (u32)td); + + inc_non_perio_chnum(); + + scheduling_ed->ed_status.is_in_transferring = true; + scheduling_ed->ed_status.is_in_transfer_ready_q = false; + scheduling_ed->ed_status.in_transferring_td =(u32)td; + td->is_transferring = true; + } + else { + //we should insert the ed_t to TransferReadyQ, because the USB Transfer of the ed_t is failed. + scheduling_ed->ed_status.is_in_transferring = false; + scheduling_ed->ed_status.in_transferring_td =0; + insert_ed_to_ready_q(scheduling_ed,true); + scheduling_ed->ed_status.is_in_transfer_ready_q = true; + + goto end_sched_nonperio_transfer; + } + } + else { + goto end_sched_nonperio_transfer; + } + } + else { //there is no ed_t on NonPeriodicTransferReadyQ. + //So, we finish do_nonperiodic_schedule(). + goto end_sched_nonperio_transfer; + } + } + } + +end_sched_nonperio_transfer: + + return; +} + + + diff --git a/drivers/usb/host/s3c-otg/s3c-otg-scheduler-scheduler.h b/drivers/usb/host/s3c-otg/s3c-otg-scheduler-scheduler.h new file mode 100644 index 0000000..f9905fa --- /dev/null +++ b/drivers/usb/host/s3c-otg/s3c-otg-scheduler-scheduler.h @@ -0,0 +1,100 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : Scheduler.h + * [Description] : The Header file defines the external and internal functions of Scheduler. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2008/06/03 + * [Revision History] + * (1) 2008/06/03 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and defines functions of Scheduler + * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Optimizing for performance \n + * + ****************************************************************************/ +/**************************************************************************** + * 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 _SCHEDULER_H +#define _SCHEDULER_H + +/* +// ---------------------------------------------------------------------------- +// Include files : None. +// ---------------------------------------------------------------------------- +*/ +//#include "s3c-otg-common-typedef.h" +#include "s3c-otg-common-const.h" +#include "s3c-otg-common-errorcode.h" +#include "s3c-otg-common-datastruct.h" +#include "s3c-otg-hcdi-memory.h" +#include "s3c-otg-hcdi-kal.h" +#include "s3c-otg-hcdi-debug.h" +#include "s3c-otg-oci.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +//Defines external functions of IScheduler.c +extern void init_scheduler(void); +extern int reserve_used_resource_for_periodic(u32 usb_time,u8 dev_speed, u8 trans_type); +extern int free_usb_resource_for_periodic(u32 free_usb_time, u8 free_chnum, u8 trans_type); +extern int remove_ed_from_scheduler(ed_t *remove_ed); +extern int cancel_to_transfer_td(struct sec_otghost *otghost, td_t *cancel_td); +extern int retransmit(struct sec_otghost *otghost, td_t *retransmit_td); +extern int reschedule(td_t *resched_td); +extern int deallocate(td_t *dealloc_td); +extern void do_schedule(struct sec_otghost *otghost); +extern int get_td_info(u8 chnum,unsigned int *td_addr); + +// Defines functiions of TranferReadyQ. +void init_transfer_ready_q(void); +int insert_ed_to_ready_q(ed_t *insert_ed, bool f_isfirst); +int remove_ed_from_ready_q(ed_t *remove_ed); +int get_ed_from_ready_q(bool f_isperiodic, ed_t **get_ed); + +//Define functions of Scheduler +void do_periodic_schedule(struct sec_otghost *otghost); +void do_nonperiodic_schedule(struct sec_otghost *otghost); +int set_transferring_td_array(u8 chnum, u32 td_addr); +int get_transferring_td_array(u8 chnum, unsigned int *td_addr); + + +//Define fuctions to manage some static global variable. +int inc_perio_bus_time(u32 uiBusTime, u8 dev_speed); +int dec_perio_bus_time(u32 uiBusTime); + +u8 get_avail_chnum(void); +int inc_perio_chnum(void); +int dec_perio_chnum(void); +int inc_non_perio_chnum(void); +int dec_nonperio_chnum(void); +u32 get_periodic_ready_q_entity_num(void); + +int insert_ed_to_scheduler(struct sec_otghost *otghost, ed_t *insert_ed); + +void reset_scheduler_numbers(void); + +#ifdef __cplusplus +} +#endif + + +#endif + diff --git a/drivers/usb/host/s3c-otg/s3c-otg-transfer-common.c b/drivers/usb/host/s3c-otg/s3c-otg-transfer-common.c new file mode 100644 index 0000000..93b6349 --- /dev/null +++ b/drivers/usb/host/s3c-otg/s3c-otg-transfer-common.c @@ -0,0 +1,810 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : Commons3c-otg-transfer-transfer.h + * [Description] : This source file implements the functions to be defined at CommonTransfer Module. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2008/06/03 + * [Revision History] + * (1) 2008/06/03 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and implements some functions of CommonTransfer. + * (2) 2008/07/15 by SeungSoo Yang ( ss1.yang@samsung.com )n + * - Optimizing for performance \n + * (3) 2008/08/18 by SeungSoo Yang ( ss1.yang@samsung.com ) + * - Modifying for successful rmmod & disconnecting \n + * + ****************************************************************************/ +/**************************************************************************** + * 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 "s3c-otg-transfer-transfer.h" + +// the header pointer to indicate the ED_list to manage the ed_t to be created and initiated. +static otg_list_head ed_list_head; +static u32 ref_periodic_transfer; + +/******************************************************************************/ +/*! + * @name void init_transfer(void) + * + * @brief this function initiates the S3CTranfer module. that is, this functions initiates + * the ED_list_head OTG List which manages the all ed_t to be existed. + * + * @param void + * + * @return void + */ +/******************************************************************************/ + +void init_transfer(void) +{ + otg_dbg(OTG_DBG_TRANSFER,"start to init_transfer\n"); + otg_list_init(&ed_list_head); + ref_periodic_transfer = 0; +} + + +/******************************************************************************/ +/*! + * @name void DeInitTransfer(void) + * + * @brief this function Deinitiates the S3CTranfer module. this functions check which there are + * some ed_t on ED_list_head. if some ed_t exists, deinit_transfer() deletes the ed_t. + * + * + * @param void + * + * @return void + */ +/******************************************************************************/ +void deinit_transfer(struct sec_otghost *otghost) +{ + otg_list_head *ed_list_member; + ed_t *delete_ed_p; + + while(otg_list_empty(&ed_list_head) != true) { + ed_list_member = ed_list_head.next; + + /* otg_list_pop(ed_list_member); */ + + delete_ed_p= otg_list_get_node(ed_list_member,ed_t,ed_list_entry); + + delete_ed(otghost, delete_ed_p); + } +} + +/******************************************************************************/ +/*! + * @name int delete_ed(ed_t *delete_ed) + * + * @brief this function delete the delete_ed. + * if there is some available TD_ts on delete_ed, then this function also deletes these td_t + * + * + * @param [IN] delete_ed = indicates the address of ed_t to be deleted. + * + * @return USB_ERR_SUCCESS -if successes to delete the ed_t. + * USB_ERR_FAILl -if fails to delete the ed_t. + */ +/******************************************************************************/ +int delete_ed(struct sec_otghost *otghost, ed_t *delete_ed) +{ + otg_kal_make_ep_null(delete_ed); + + if(delete_ed->num_td) { + cancel_all_td(otghost, delete_ed); + /** + need to giveback of td's urb with considering life-cycle of + TD, ED, urb->hcpriv, td->private, ep->hcpriv, td->parentED + (commented by ss1.yang) + */ + } + + otg_list_pop(&delete_ed->ed_list_entry); + + if(delete_ed->ed_desc.endpoint_type == INT_TRANSFER || + delete_ed->ed_desc.endpoint_type == ISOCH_TRANSFER) { + ref_periodic_transfer--; + } + + if(ref_periodic_transfer==0) { + disable_sof(); + } + otg_mem_free(delete_ed); + + return USB_ERR_SUCCESS; +} + +/******************************************************************************/ +/*! + * @name int delete_td(struct sec_otghost *otghost, td_t *delete_td) + * + * @brief this function frees memory resource for the delete_td. + * and if delete_td is transferring USB Transfer, then this function request to cancel + * the USB Transfer to S3CScheduler. + * + * + * @param [OUT] new_td_p = returns the address of the new td_t . + * + * @return USB_ERR_SUCCESS -if successes to create the new td_t. + * USB_ERR_FAILl -if fails to create to new td_t. + */ +/******************************************************************************/ +int delete_td(struct sec_otghost *otghost, td_t *delete_td) +{ + if(delete_td->is_transferring) { + //at this case, we should cancel the USB Transfer. + cancel_to_transfer_td(otghost, delete_td); + } + + otg_mem_free(delete_td); + return USB_ERR_SUCCESS; +} + +int create_isoch_packet_desc(isoch_packet_desc_t **new_isoch_packet_desc, + u32 isoch_packet_num) +{ + return otg_mem_alloc((void **)new_isoch_packet_desc, + (u16)sizeof(isoch_packet_desc_t)*isoch_packet_num,USB_MEM_SYNC); +} + +int delete_isoch_packet_desc(isoch_packet_desc_t *del_isoch_packet_desc, + u32 isoch_packet_num) +{ + return otg_mem_free(del_isoch_packet_desc); +} + +/******************************************************************************/ +/*! + * @name void init_isoch_packet_desc( isoch_packet_desc_t *init_isoch_packet_desc, + * u32 isoch_packet_start_addr, + * u32 isoch_packet_size, + * u32 index) + * + * @brief this function initiates the isoch_packet_desc_t[index]. + * + * + * @param [OUT] init_isoch_packet_desc = indicates the pointer of IsochPackDesc_t to be initiated. + * [IN] isoch_packet_start_addr = indicates the start address of the buffer to be used + * at USB Isochronous Transfer. + * [IN] isoch_packet_size = indicates the size of Isochronous packet. + * [IN] index = indicates the index to be mapped with this init_isoch_packet_desc. + * + * @return void + */ +/******************************************************************************/ +void init_isoch_packet_desc(isoch_packet_desc_t *init_isoch_packet_desc, + u32 isoch_packet_start_addr, + u32 isoch_packet_size, + u32 index) +{ + init_isoch_packet_desc[index].buf_size = isoch_packet_size; + init_isoch_packet_desc[index].isoch_packiet_start_addr = isoch_packet_start_addr; + init_isoch_packet_desc[index].isoch_status = 0; + init_isoch_packet_desc[index].transferred_szie = 0; +} + +/******************************************************************************/ +/*! + * @name int create_ed(ed_t **new_ed) + * + * @brief this function creates a new ed_t and returns the ed_t to Caller + * + * + * @param [OUT] new_ed = returns the address of the new ed_t . + * + * @return USB_ERR_SUCCESS -if successes to create the new ed_t. + * USB_ERR_FAILl -if fails to create to new ed_t. + */ +/******************************************************************************/ +int create_ed(ed_t **new_ed) +{ + int err_code = USB_ERR_SUCCESS; + + err_code = otg_mem_alloc((void **)new_ed,(u16)sizeof(ed_t), USB_MEM_ASYNC); + otg_mem_set(*new_ed, 0, sizeof(ed_t)); + return err_code; +} + + +/******************************************************************************/ +/*! + * @name int init_ed( ed_t *init_ed, + * u8 dev_addr, + * u8 ep_num, + * bool f_is_ep_in, + * u8 dev_speed, + * u8 ep_type, + * u32 max_packet_size, + * u8 multi_count, + * u8 interval, + * u32 sched_frame, + * u8 hub_addr, + * u8 hub_port, + * bool f_is_do_split) + * + * @brief this function initiates the init_ed by using the another parameters. + * + * + * @param [OUT] init_ed = returns the ed_t to be initiated. + * [IN] dev_addr = inidcates the address of USB Device. + * [IN] ep_num = inidcates the number of the specific endpoint on USB Device. + * [IN] f_is_ep_in = inidcates whether the endpoint is IN or not + * [IN] dev_speed = inidcates the speed of USB Device. + * [IN] max_packet_size = inidcates the maximum packet size of a specific endpoint on USB Device. + * [IN] multi_count = if the endpoint supports periodic transfer + * , this indicates the multiple packet to be transferred on a uframe + * [IN] interval= if the endpoint support periodic transfer, this indicates the polling rate. + * [IN] sched_frame= if the endpoint supports periodic transfer, this indicates the start frame number. + * [IN] hub_addr= indicate the address of hub which the USB device attachs to. + * [IN] hub_port= inidcates the port number of the hub which the USB device attachs to. + * [IN] f_is_do_split= inidcates whether this tranfer is split transaction or not. + * + * @return USB_ERR_SUCCESS -if successes to initiate the ed_t. + * USB_ERR_FAILl -if fails to initiate the ed_t. + * USB_ERR_NOSPACE -if fails to initiate the ed_t + * because there is no USB Resource for this init_ed. + */ +/******************************************************************************/ +int init_ed(ed_t *init_ed, + u8 dev_addr, + u8 ep_num, + bool f_is_ep_in, + u8 dev_speed, + u8 ep_type, + u16 max_packet_size, + u8 multi_count, + u8 interval, + u32 sched_frame, + u8 hub_addr, + u8 hub_port, + bool f_is_do_split, + void *ep) +{ + init_ed->is_halted = false; + init_ed->is_need_to_insert_scheduler= true; + init_ed->ed_id = (u32)init_ed; + init_ed->num_td = 0; + init_ed->ed_private = ep; + + otg_list_init(&init_ed->td_list_entry); + + //start to initiate struct ed_desc.... + init_ed->ed_desc.is_do_split = f_is_do_split; + init_ed->ed_desc.is_ep_in = f_is_ep_in; + init_ed->ed_desc.dev_speed = dev_speed; + init_ed->ed_desc.hub_addr = hub_addr; + init_ed->ed_desc.hub_port = hub_port; + init_ed->ed_desc.mc = multi_count; + init_ed->ed_desc.device_addr = dev_addr; + init_ed->ed_desc.endpoint_num = ep_num; + init_ed->ed_desc.endpoint_type = ep_type; + init_ed->ed_desc.max_packet_size = max_packet_size; + init_ed->ed_desc.sched_frame = sched_frame; + + if(init_ed->ed_desc.endpoint_type == INT_TRANSFER) { + if(init_ed->ed_desc.dev_speed == LOW_SPEED_OTG ||init_ed->ed_desc.dev_speed == FULL_SPEED_OTG) { + init_ed->ed_desc.interval =interval; + } + else if(init_ed->ed_desc.dev_speed == HIGH_SPEED_OTG) { + u8 count = 0; + u8 cal_interval = 1; + + for(count = 0;count<(init_ed->ed_desc.interval-1);count++) { + cal_interval *=2; + } + + init_ed->ed_desc.interval =cal_interval; + } + else { + otg_dbg(OTG_DBG_TRANSFER,"Super-Speed is not supported\n"); + } + init_ed->ed_desc.sched_frame = (SCHEDULE_SLOT+oci_get_frame_num())&HFNUM_MAX_FRNUM; + ref_periodic_transfer++; + } + if(init_ed->ed_desc.endpoint_type==ISOCH_TRANSFER) { + u8 count = 0; + u8 cal_interval = 1; + + for(count = 0;count<(init_ed->ed_desc.interval-1);count++) + { + cal_interval *=2; + } + + init_ed->ed_desc.interval = cal_interval; + init_ed->ed_desc.sched_frame = (SCHEDULE_SLOT+oci_get_frame_num())&HFNUM_MAX_FRNUM; + ref_periodic_transfer++; + } + + //start to initiate struct ed_status.... + + //initiates PID + switch(ep_type) { + case BULK_TRANSFER: + case INT_TRANSFER: + init_ed->ed_status.data_tgl = DATA0; + break; + + case CONTROL_TRANSFER: + init_ed->ed_status.control_data_tgl.setup_tgl = SETUP; + init_ed->ed_status.control_data_tgl.data_tgl = DATA1; + init_ed->ed_status.control_data_tgl.status_tgl = DATA1; + break; + + case ISOCH_TRANSFER: + if(f_is_ep_in) { + switch(multi_count) { + case MULTI_COUNT_ZERO : + init_ed->ed_status.data_tgl = DATA0; + break; + case MULTI_COUNT_ONE : + init_ed->ed_status.data_tgl = DATA1; + break; + case MULTI_COUNT_TWO : + init_ed->ed_status.data_tgl = DATA2; + break; + default: + break; + } + } + else { + switch(multi_count) { + case MULTI_COUNT_ZERO : + init_ed->ed_status.data_tgl = DATA0; + break; + case MULTI_COUNT_ONE : + init_ed->ed_status.data_tgl = MDATA; + break; + case MULTI_COUNT_TWO : + init_ed->ed_status.data_tgl = MDATA; + break; + default: + break; + } + } + break; + default: + break; + } + + if(init_ed->ed_desc.endpoint_type == INT_TRANSFER || + init_ed->ed_desc.endpoint_type == ISOCH_TRANSFER) { + u32 usb_time = 0, byte_count = 0; + + //calculates the bytes to be transferred at one (uframe)frame. + byte_count = (init_ed->ed_desc.mc+1)*init_ed->ed_desc.max_packet_size; + + usb_time = (u32)otg_usbcore_get_calc_bustime(init_ed->ed_desc.dev_speed, + init_ed->ed_desc.is_ep_in, + (init_ed->ed_desc.endpoint_type==ISOCH_TRANSFER?true:false), + byte_count); + usb_time /= 1000; //convert nanosec unit to usec unit + + if(reserve_used_resource_for_periodic(usb_time, init_ed->ed_desc.dev_speed, init_ed->ed_desc.endpoint_type) != USB_ERR_SUCCESS) { + return USB_ERR_NOSPACE; + } + + init_ed->ed_status.is_alloc_resource_for_ed =true; + init_ed->ed_desc.used_bus_time =usb_time; + init_ed->ed_desc.mc =multi_count+1; + } + + init_ed->ed_status.is_in_transfer_ready_q =false; + init_ed->ed_status.is_in_transferring =false; + init_ed->ed_status.is_ping_enable =false; + init_ed->ed_status.in_transferring_td =0; + +// sztupy: split transaction support + init_ed->ed_status.is_complete_split = false; + init_ed->ed_status.split_pos = ED_STATUS_SPLIT_POS_ALL; + init_ed->ed_status.split_offset = 0; + + //push the ed_t to ED_list. + otg_list_push_prev(&init_ed->ed_list_entry,&ed_list_head); + + if(ref_periodic_transfer) { + enable_sof(); + } + return USB_ERR_SUCCESS; +} + + +/******************************************************************************/ +/*! + * @name int create_td(td_t **new_td) + * + * @brief this function creates a new td_t and returns the td_t to Caller + * + * + * @param [OUT] new_td = returns the address of the new td_t . + * + * @return USB_ERR_SUCCESS -if successes to create the new td_t. + * USB_ERR_FAILl -if fails to create to new td_t. + */ +/******************************************************************************/ +int create_td(td_t **new_td) +{ + int err_code = USB_ERR_SUCCESS; + + err_code = otg_mem_alloc((void **)new_td,(u16)sizeof(td_t), USB_MEM_ASYNC); + otg_mem_set(*new_td, 0, sizeof(td_t)); + return err_code; +} + + +/******************************************************************************/ +/*! + * @name int init_td( td_t *init_td, + * ed_t *parent_ed, + * void *call_back_fun, + * void *call_back_param, + * u32 transfer_flag, + * bool f_is_standard_dev_req, + * u32 phy_setup, + * u32 vir_setup, + * u32 vir_buf_addr, + * u32 phy_buf_addr, + * u32 buf_size, + * u32 isoch_start_frame, + * isoch_packet_desc_t *isoch_packet_desc, + * u32 isoch_packet_num, + * void *td_priv) + * + * @brief this function initiates the init_td by using another parameter. + * + * + * @param [IN] init_td - indicate the td_t to be initiated. + * [IN] parent_ed - indicate the ed_t to manage this init_td + * [IN] call_back_func - indicate the call-back function of application. + * [IN] call_back_param - indicate the parameter of the call-back function. + * [IN] transfer_flag - indicate the transfer flag. + * [IN] f_is_standard_dev_req - indicates the issue transfer request is USB Standard Request + * [IN] phy_setup - the physical address of buffer to store the USB Standard Request. + * [IN] vir_setup - the virtual address of buffer to store the USB Standard Request. + * [IN] vir_buf_addr - the virtual address of buffer to store the data to be transferred or received. + * [IN] phy_buf_addr - the physical address of buffer to store the data to be transferred or received. + * [IN] buf_size - indicates the buffer size. + * [IN] isoch_start_frame - if this usb transfer is isochronous transfer + * , this indicates the start frame to start the usb transfer. + * [IN] isoch_packet_desc - if the usb transfer is isochronous transfer + * , this indicates the structure to describe the isochronous transfer. + * [IN] isoch_packet_num - if the usb transfer is isochronous transfer + * , this indicates the number of packet to consist of the usb transfer. + * [IN] td_priv - indicate the private data to be delivered from usb core of linux. + * td_priv stores the urb of linux. + * + * @return USB_ERR_SUCCESS -if successes to initiate the new td_t. + * USB_ERR_FAILl -if fails to create to new td_t. + */ +/******************************************************************************/ +int init_td( td_t *init_td, + ed_t *parent_ed, + void *call_back_fun, + void *call_back_param, + u32 transfer_flag, + bool f_is_standard_dev_req, + u32 phy_setup, + u32 vir_setup, + u32 vir_buf_addr, + u32 phy_buf_addr, + u32 buf_size, + u32 isoch_start_frame, + isoch_packet_desc_t *isoch_packet_desc, + u32 isoch_packet_num, + void *td_priv) +{ + if(f_is_standard_dev_req) { + if((phy_buf_addr>0) && (buf_size>0)) { + init_td->standard_dev_req_info.is_data_stage = true; + } + else { + init_td->standard_dev_req_info.is_data_stage = false; + } + init_td->standard_dev_req_info.conrol_transfer_stage = SETUP_STAGE; + init_td->standard_dev_req_info.phy_standard_dev_req_addr = phy_setup; + init_td->standard_dev_req_info.vir_standard_dev_req_addr = vir_setup; + } + + init_td->call_back_func_p = call_back_fun; + init_td->call_back_func_param_p = call_back_param; + init_td->error_code = USB_ERR_SUCCESS; + init_td->is_standard_dev_req = f_is_standard_dev_req; + init_td->is_transfer_done = false; + init_td->is_transferring = false; + init_td->td_private = td_priv; + init_td->err_cnt = 0; + init_td->parent_ed_p = parent_ed; + init_td->phy_buf_addr = phy_buf_addr; + init_td->vir_buf_addr = vir_buf_addr; + init_td->buf_size = buf_size; + init_td->isoch_packet_desc_p = isoch_packet_desc; + init_td->isoch_packet_num = isoch_packet_num; + init_td->isoch_packet_index = 0; + init_td->isoch_packet_position = 0; + init_td->sched_frame = isoch_start_frame; + init_td->used_total_bus_time = parent_ed->ed_desc.used_bus_time; + init_td->td_id = (u32)init_td; + init_td->transfer_flag = transfer_flag; + init_td->transferred_szie = 0; + + switch(parent_ed->ed_desc.endpoint_type) { + case CONTROL_TRANSFER: + init_nonperio_stransfer(true, init_td); + break; + + case BULK_TRANSFER: + init_nonperio_stransfer(false, init_td); + break; + + case INT_TRANSFER: + init_perio_stransfer(false, init_td); + break; + + case ISOCH_TRANSFER: + init_perio_stransfer(true, init_td); + break; + + default: + return USB_ERR_FAIL; + } + + //insert the td_t to parent_ed->td_list_entry. + otg_list_push_prev(&init_td->td_list_entry,&parent_ed->td_list_entry); + parent_ed->num_td++; + + return USB_ERR_SUCCESS; +} + +/******************************************************************************/ +/*! + * @name int issue_transfer(struct sec_otghost *otghost, + * ed_t *parent_ed, + * void *call_back_func, + * void *call_back_param, + * u32 transfer_flag, + * bool f_is_standard_dev_req, + * u32 setup_vir_addr, + * u32 setup_phy_addr, + * u32 vir_buf_addr, + * u32 phy_buf_addr, + * u32 buf_size, + * u32 start_frame, + * u32 isoch_packet_num, + * isoch_packet_desc_t *isoch_packet_desc, + * void *td_priv, + * unsigned int *return_td_addr) + * + * @brief this function start USB Transfer + * + * + * @param [IN] parent_ed - indicate the ed_t to manage this issue transfer. + * [IN] call_back_func - indicate the call-back function of application. + * [IN] call_back_param - indicate the parameter of the call-back function. + * [IN] transfer_flag - indicate the transfer flag. + * [IN] f_is_standard_dev_req - indicates the issue transfer request is USB Standard Request + * [IN] setup_vir_addr - the virtual address of buffer to store the USB Standard Request. + * [IN] setup_phy_addr - the physical address of buffer to store the USB Standard Request. + * [IN] vir_buf_addr - the virtual address of buffer to store the data to be transferred or received. + * [IN] phy_buf_addr - the physical address of buffer to store the data to be transferred or received. + * [IN] buf_size - indicates the buffer size. + * [IN] start_frame - if this usb transfer is isochronous transfer + * , this indicates the start frame to start the usb transfer. + * [IN] isoch_packet_num - if the usb transfer is isochronous transfer + * , this indicates the number of packet to consist of the usb transfer. + * [IN] isoch_packet_desc - if the usb transfer is isochronous transfer + * , this indicates the structure to describe the isochronous transfer. + * [IN] td_priv - indicate the private data to be delivered from usb core of linux. + * td_priv stores the urb of linux. + * [OUT] return_td_addr - indicates the variable address to store the new td_t for this transfer + * + * @return USB_ERR_SUCCESS -if successes to initiate the new td_t. + * USB_ERR_FAILl -if fails to create to new td_t. + */ +/******************************************************************************/ +int issue_transfer(struct sec_otghost *otghost, + ed_t *parent_ed, + void *call_back_func, + void *call_back_param, + u32 transfer_flag, + bool f_is_standard_dev_req, + u32 setup_vir_addr, + u32 setup_phy_addr, + u32 vir_buf_addr, + u32 phy_buf_addr, + u32 buf_size, + u32 start_frame, + u32 isoch_packet_num, + isoch_packet_desc_t *isoch_packet_desc, + void *td_priv, + unsigned int *return_td_addr) +{ + td_t *new_td_p = NULL; + + int err = USB_ERR_SUCCESS; + if(create_td(&new_td_p)==USB_ERR_SUCCESS) { + err = init_td( new_td_p, + parent_ed, + call_back_func, + call_back_param, + transfer_flag, + f_is_standard_dev_req, + setup_phy_addr, + setup_vir_addr, + vir_buf_addr, + phy_buf_addr, + buf_size, + start_frame, + isoch_packet_desc, + isoch_packet_num, + td_priv); + + if(err !=USB_ERR_SUCCESS) { + return USB_ERR_NOMEM; + } + + if(parent_ed->is_need_to_insert_scheduler) { + insert_ed_to_scheduler(otghost, parent_ed); + } + + *return_td_addr = (u32)new_td_p; + + return USB_ERR_SUCCESS; + } + else { + return USB_ERR_NOMEM; + } +} + +/******************************************************************************/ +/*! + * @name int cancel_transfer(struct sec_otghost *otghost, + * ed_t *parent_ed, + * td_t *cancel_td) + * + * @brief this function cancels to transfer USB Transfer of cancel_td. + * this function firstly check whether this cancel_td is transferring or not + * if the cancel_td is transferring, the this function requests to cancel the USB Transfer + * to S3CScheduler. if the parent_ed is for Periodic Transfer, and + * there is not any td_t at parent_ed, then this function requests to release + * some usb resources for the ed_t to S3CScheduler. finally this function deletes the cancel_td. + * + * @param [IN] pUpdateTD = indicates the pointer ot the td_t to have STransfer to be updated. + * + * @return USB_ERR_SUCCESS - if success to update the STranfer of pUpdateTD. + * USB_ERR_FAIL - if fail to update the STranfer of pUpdateTD. + */ + /******************************************************************************/ +int cancel_transfer(struct sec_otghost *otghost, + ed_t *parent_ed, + td_t *cancel_td) +{ + int err = USB_ERR_DEQUEUED; + otg_list_head *tmp_list_p, *tmp_list2_p; + bool cond_found = false; + + if(parent_ed == NULL || cancel_td == NULL) { + otg_dbg(OTG_DBG_TRANSFER, "parent_ed == NULL || cancel_td == NULL\n"); + cancel_td->error_code = USB_ERR_NOELEMENT; + otg_usbcore_giveback(cancel_td); + return cancel_td->error_code; + } + + otg_list_for_each_safe(tmp_list_p, tmp_list2_p, &parent_ed->td_list_entry) { + if(&cancel_td->td_list_entry == tmp_list_p) { + cond_found = true; + break; + } + } + + if (cond_found != true) { + otg_dbg(OTG_DBG_TRANSFER, "cond_found != true \n"); + cancel_td->error_code = USB_ERR_NOELEMENT; + otg_usbcore_giveback(cancel_td); + return cancel_td->error_code; + } + + + if(cancel_td->is_transferring) { + if(!parent_ed->ed_status.is_in_transfer_ready_q) { + err = cancel_to_transfer_td(otghost, cancel_td); + + parent_ed->ed_status.in_transferring_td = 0; + + if(err != USB_ERR_SUCCESS) { + otg_dbg(OTG_DBG_TRANSFER, "cancel_to_transfer_td \n"); + cancel_td->error_code = err; + otg_usbcore_giveback(cancel_td); + goto ErrorStatus; + } + + } + // kevinh - even if the record was in the ready queue it is important to delete it as well. We can also always remove the ed from the scheduler + // once all tds have been removed + otg_list_pop(&cancel_td->td_list_entry); + parent_ed->num_td--; + } + else { + otg_list_pop(&cancel_td->td_list_entry); + parent_ed->num_td--; + + if(parent_ed->num_td==0) { + remove_ed_from_scheduler(parent_ed); + } + } + + if(parent_ed->num_td) { + // kevinh - we do not want to force insert_scheduler, because if this endpoint _was_ already scheduled + // because the deleted td was not the active td then we will now put ed into the scheduler list twice, thus + // corrupting it. + // parent_ed->is_need_to_insert_scheduler = true; + insert_ed_to_scheduler(otghost, parent_ed); + } + else { + if(parent_ed->ed_desc.endpoint_type == INT_TRANSFER || + parent_ed->ed_desc.endpoint_type == ISOCH_TRANSFER) { + //Release channel and usb bus resource for this ed_t. + //but, not release memory for this ed_t. + free_usb_resource_for_periodic(parent_ed->ed_desc.used_bus_time, + cancel_td->cur_stransfer.alloc_chnum, + cancel_td->parent_ed_p->ed_desc.endpoint_type); + + parent_ed->ed_status.is_alloc_resource_for_ed =false; + } + } + // the caller of this functions should call otg_usbcore_giveback(cancel_td); + cancel_td->error_code = USB_ERR_DEQUEUED; + // kevinh - fixed bug, the caller should take care of calling delete_td because they might still want to do some + // operations on that memory + // delete_td(cancel_td); + // otg_usbcore_giveback(cancel_td); + +ErrorStatus: + + return err; +} + + +/******************************************************************************/ +/*! + * @name int cancel_all_td(struct sec_otghost *otghost, ed_t *parent_ed) + * + * @brief this function cancels all Transfer which parent_ed manages. + * + * @param [IN] parent_ed = indicates the pointer ot the ed_t to manage TD_ts to be canceled. + * + * @return USB_ERR_SUCCESS - if success to cancel all TD_ts of pParentsED. + * USB_ERR_FAIL - if fail to cancel all TD_ts of pParentsED. + */ + /******************************************************************************/ +int cancel_all_td(struct sec_otghost *otghost, ed_t *parent_ed) +{ + otg_list_head *cancel_td_list_entry; + td_t *cancel_td; + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "cancel_all_td \n"); + do { + cancel_td_list_entry = parent_ed->td_list_entry.next; + + cancel_td = otg_list_get_node(cancel_td_list_entry,td_t, td_list_entry); + + if(cancel_transfer(otghost, parent_ed, cancel_td) == USB_ERR_DEQUEUED) + // kevinh FIXME - do we also need to giveback? + delete_td(otghost,cancel_td); + } while(parent_ed->num_td); + + return USB_ERR_SUCCESS; +} diff --git a/drivers/usb/host/s3c-otg/s3c-otg-transfer-nonperiodic.c b/drivers/usb/host/s3c-otg/s3c-otg-transfer-nonperiodic.c new file mode 100644 index 0000000..013a2a2 --- /dev/null +++ b/drivers/usb/host/s3c-otg/s3c-otg-transfer-nonperiodic.c @@ -0,0 +1,140 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : NonPeriodicTransfer.c + * [Description] : This source file implements the functions to be defined at NonPeriodicTransfer Module. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2008/06/07 + * [Revision History] + * (1) 2008/06/07 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and implements some functions of NonPeriodicTransfer. + * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Optimizing for performance \n + * + ****************************************************************************/ +/**************************************************************************** + * 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 "s3c-otg-transfer-transfer.h" + +/******************************************************************************/ +/*! + * @name int init_nonperio_stransfer( bool f_is_standard_dev_req, + * td_t *parent_td) + * + * @brief this function initiates the parent_td->cur_stransfer for NonPeriodic Transfer and + * inserts this init_td_p to init_td_p->parent_ed_p. + * + * @param [IN] f_is_standard_dev_req = indicates whether this transfer is Control or not. + * [IN] parent_td = indicates the address of td_t to be initiated. + * + * @return USB_ERR_SUCCESS - if success to update the STranfer of pUpdateTD. + * USB_ERR_FAIL - if fail to update the STranfer of pUpdateTD. + */ + /******************************************************************************/ +int init_nonperio_stransfer(bool f_is_standard_dev_req, + td_t *parent_td) +{ + + + parent_td->cur_stransfer.ed_desc_p = &parent_td->parent_ed_p->ed_desc; + parent_td->cur_stransfer.ed_status_p = &parent_td->parent_ed_p->ed_status; + parent_td->cur_stransfer.alloc_chnum = CH_NONE; + parent_td->cur_stransfer.parent_td = (u32)parent_td; + parent_td->cur_stransfer.stransfer_id = (u32)&parent_td->cur_stransfer; + + otg_mem_set(&(parent_td->cur_stransfer.hc_reg), 0, sizeof(hc_reg_t)); + + parent_td->cur_stransfer.hc_reg.hc_int_msk.b.chhltd = 1; + + if(f_is_standard_dev_req) + { + parent_td->cur_stransfer.buf_size = USB_20_STAND_DEV_REQUEST_SIZE; + parent_td->cur_stransfer.start_phy_buf_addr = parent_td->standard_dev_req_info.phy_standard_dev_req_addr; + parent_td->cur_stransfer.start_vir_buf_addr = parent_td->standard_dev_req_info.vir_standard_dev_req_addr; + } + else + { + parent_td->cur_stransfer.buf_size = (parent_td->buf_size>MAX_CH_TRANSFER_SIZE) + ?MAX_CH_TRANSFER_SIZE + :parent_td->buf_size; + + parent_td->cur_stransfer.start_phy_buf_addr = parent_td->phy_buf_addr; + parent_td->cur_stransfer.start_vir_buf_addr = parent_td->vir_buf_addr; + } + + parent_td->cur_stransfer.packet_cnt = calc_packet_cnt(parent_td->cur_stransfer.buf_size + , parent_td->parent_ed_p->ed_desc.max_packet_size); + + return USB_ERR_SUCCESS; +} + + +/******************************************************************************/ +/*! + * @name void update_nonperio_stransfer(td_t *parent_td) + * + * @brief this function updates the parent_td->cur_stransfer to be used by S3COCI. + * + * @param [IN/OUT]parent_td = indicates the pointer of td_t to store the STranser to be updated. + * + * @return USB_ERR_SUCCESS -if success to update the parent_td->cur_stransfer. + * USB_ERR_FAIL -if fail to update the parent_td->cur_stransfer. + */ + /******************************************************************************/ +void update_nonperio_stransfer(td_t *parent_td) +{ + switch(parent_td->parent_ed_p->ed_desc.endpoint_type) { + case BULK_TRANSFER: + parent_td->cur_stransfer.start_phy_buf_addr = parent_td->phy_buf_addr+parent_td->transferred_szie; + parent_td->cur_stransfer.start_vir_buf_addr = parent_td->vir_buf_addr+parent_td->transferred_szie; + parent_td->cur_stransfer.buf_size = ((parent_td->buf_size - parent_td->transferred_szie)>MAX_CH_TRANSFER_SIZE) + ?MAX_CH_TRANSFER_SIZE + :parent_td->buf_size - parent_td->transferred_szie; + break; + + case CONTROL_TRANSFER: + if(parent_td->standard_dev_req_info.conrol_transfer_stage == SETUP_STAGE) + { + // but, this case will not be occured...... + parent_td->cur_stransfer.start_phy_buf_addr = parent_td->standard_dev_req_info.phy_standard_dev_req_addr; + parent_td->cur_stransfer.start_vir_buf_addr = parent_td->standard_dev_req_info.vir_standard_dev_req_addr; + parent_td->cur_stransfer.buf_size = 8; + } + else if(parent_td->standard_dev_req_info.conrol_transfer_stage == DATA_STAGE) + { + parent_td->cur_stransfer.start_phy_buf_addr = parent_td->phy_buf_addr+parent_td->transferred_szie; + parent_td->cur_stransfer.start_vir_buf_addr = parent_td->vir_buf_addr+parent_td->transferred_szie; + parent_td->cur_stransfer.buf_size = ((parent_td->buf_size - parent_td->transferred_szie)>MAX_CH_TRANSFER_SIZE) + ?MAX_CH_TRANSFER_SIZE + :parent_td->buf_size - parent_td->transferred_szie; + } + else + { + parent_td->cur_stransfer.start_phy_buf_addr = 0; + parent_td->cur_stransfer.start_vir_buf_addr = 0; + parent_td->cur_stransfer.buf_size = 0; + } + break; + default: + break; + } + + parent_td->cur_stransfer.packet_cnt = calc_packet_cnt(parent_td->cur_stransfer.buf_size, parent_td->parent_ed_p->ed_desc.max_packet_size); + +} diff --git a/drivers/usb/host/s3c-otg/s3c-otg-transfer-periodic.c b/drivers/usb/host/s3c-otg/s3c-otg-transfer-periodic.c new file mode 100644 index 0000000..f254b78 --- /dev/null +++ b/drivers/usb/host/s3c-otg/s3c-otg-transfer-periodic.c @@ -0,0 +1,128 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : NonPeriodicTransfer.c + * [Description] : This source file implements the functions to be defined at NonPeriodicTransfer Module. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2008/06/09 + * [Revision History] + * (1) 2008/06/09 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and implements some functions of PeriodicTransfer. + * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Optimizing for performance \n + * + ****************************************************************************/ +/**************************************************************************** + * 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 "s3c-otg-transfer-transfer.h" + + +/******************************************************************************/ +/*! + * @name int init_perio_stransfer( bool f_is_isoch_transfer, + * td_t *parent_td) + * + * @brief this function initiates the parent_td->cur_stransfer for Periodic Transfer and + * inserts this init_td_p to init_td_p->parent_ed_p. + * + * @param [IN] f_is_isoch_transfer = indicates whether this transfer is Isochronous or not. + * [IN] parent_td = indicates the address of td_t to be initiated. + * + * @return USB_ERR_SUCCESS -if success to update the STranfer of pUpdateTD. + * USB_ERR_FAIL -if fail to update the STranfer of pUpdateTD. + */ + /******************************************************************************/ +int init_perio_stransfer( bool f_is_isoch_transfer, + td_t *parent_td) +{ + parent_td->cur_stransfer.ed_desc_p = &parent_td->parent_ed_p->ed_desc; + parent_td->cur_stransfer.ed_status_p = &parent_td->parent_ed_p->ed_status; + parent_td->cur_stransfer.alloc_chnum = CH_NONE; + parent_td->cur_stransfer.parent_td = (u32)parent_td; + parent_td->cur_stransfer.stransfer_id = (u32)&parent_td->cur_stransfer; + + otg_mem_set(&parent_td->cur_stransfer.hc_reg, 0, sizeof(hc_reg_t)); + + parent_td->cur_stransfer.hc_reg.hc_int_msk.b.chhltd = 1; + + if(f_is_isoch_transfer) + { + // initiates the STransfer usinb the IsochPacketDesc[0]. + parent_td->cur_stransfer.buf_size =parent_td->isoch_packet_desc_p[0].buf_size; + parent_td->cur_stransfer.start_phy_buf_addr =parent_td->phy_buf_addr + parent_td->isoch_packet_desc_p[0].isoch_packiet_start_addr; + parent_td->cur_stransfer.start_vir_buf_addr =parent_td->vir_buf_addr + parent_td->isoch_packet_desc_p[0].isoch_packiet_start_addr; + } + else + { + parent_td->cur_stransfer.buf_size =(parent_td->buf_size>MAX_CH_TRANSFER_SIZE) + ?MAX_CH_TRANSFER_SIZE + :parent_td->buf_size; + + parent_td->cur_stransfer.start_phy_buf_addr =parent_td->phy_buf_addr; + parent_td->cur_stransfer.start_vir_buf_addr =parent_td->vir_buf_addr; + } + + parent_td->cur_stransfer.packet_cnt = calc_packet_cnt(parent_td->cur_stransfer.buf_size, parent_td->parent_ed_p->ed_desc.max_packet_size); + + return USB_ERR_SUCCESS; +} + + +/******************************************************************************/ +/*! + * @name void update_perio_stransfer(td_t *parent_td) + * + * @brief this function updates the parent_td->cur_stransfer to be used by S3COCI. + * the STransfer of parent_td is for Periodic Transfer. + * + * @param [IN/OUT]parent_td = indicates the pointer of td_t to store the STranser to be updated. + * + * @return USB_ERR_SUCCESS -if success to update the parent_td->cur_stransfer. + * USB_ERR_FAIL -if fail to update the parent_td->cur_stransfer. + */ + /******************************************************************************/ +void update_perio_stransfer(td_t *parent_td) +{ + switch(parent_td->parent_ed_p->ed_desc.endpoint_type) { + case INT_TRANSFER: + parent_td->cur_stransfer.start_phy_buf_addr = parent_td->phy_buf_addr+parent_td->transferred_szie; + parent_td->cur_stransfer.start_vir_buf_addr = parent_td->vir_buf_addr+parent_td->transferred_szie; + parent_td->cur_stransfer.buf_size = (parent_td->buf_size>MAX_CH_TRANSFER_SIZE) + ?MAX_CH_TRANSFER_SIZE :parent_td->buf_size; + break; + + case ISOCH_TRANSFER: + parent_td->cur_stransfer.start_phy_buf_addr = parent_td->phy_buf_addr + +parent_td->isoch_packet_desc_p[parent_td->isoch_packet_index].isoch_packiet_start_addr + +parent_td->isoch_packet_position; + + parent_td->cur_stransfer.start_vir_buf_addr = parent_td->vir_buf_addr + +parent_td->isoch_packet_desc_p[parent_td->isoch_packet_index].isoch_packiet_start_addr + +parent_td->isoch_packet_position; + + parent_td->cur_stransfer.buf_size = (parent_td->isoch_packet_desc_p[parent_td->isoch_packet_index].buf_size - parent_td->isoch_packet_position)>MAX_CH_TRANSFER_SIZE + ?MAX_CH_TRANSFER_SIZE + :parent_td->isoch_packet_desc_p[parent_td->isoch_packet_index].buf_size - parent_td->isoch_packet_position; + + break; + + default: + break; + } +} + diff --git a/drivers/usb/host/s3c-otg/s3c-otg-transfer-transfer.h b/drivers/usb/host/s3c-otg/s3c-otg-transfer-transfer.h new file mode 100644 index 0000000..6e1532c --- /dev/null +++ b/drivers/usb/host/s3c-otg/s3c-otg-transfer-transfer.h @@ -0,0 +1,144 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : s3c-otg-transfer-transfer.h + * [Description] : The Header file defines the external and internal functions of Transfer. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2008/06/03 + * [Revision History] + * (1) 2008/06/03 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and defines functions of Transfer + * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Optimizing for performance \n + * + ****************************************************************************/ +/**************************************************************************** + * 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 _TRANSFER_H +#define _TRANSFER_H + +/* +// ---------------------------------------------------------------------------- +// Include files : None. +// ---------------------------------------------------------------------------- +*/ +//#include "s3c-otg-common-const.h" +#include "s3c-otg-common-errorcode.h" +#include "s3c-otg-common-datastruct.h" +#include "s3c-otg-hcdi-memory.h" +#include "s3c-otg-hcdi-kal.h" +#include "s3c-otg-hcdi-debug.h" + +#include "s3c-otg-scheduler-scheduler.h" +#include "s3c-otg-isr.h" + + +#ifdef __cplusplus +extern "C" +{ +#endif + +void init_transfer(void); +void deinit_transfer(struct sec_otghost *otghost); + +int issue_transfer(struct sec_otghost *otghost, + ed_t *parent_ed, + void *call_back_func, + void *call_back_param, + u32 transfer_flag, + bool f_is_standard_dev_req, + u32 setup_vir_addr, + u32 setup_phy_addr, + u32 vir_buf_addr, + u32 phy_buf_addr, + u32 buf_size, + u32 start_frame, + u32 isoch_packet_num, + isoch_packet_desc_t *isoch_packet_desc, + void *td_priv, + unsigned int *return_td_addr); + +int cancel_transfer(struct sec_otghost *otghost, + ed_t *parent_ed, + td_t *cancel_td); + +int cancel_all_td(struct sec_otghost *otghost, ed_t *parent_ed); + +int create_ed(ed_t ** new_ed); + +int init_ed(ed_t * init_ed, + u8 dev_addr, + u8 ep_num, + bool f_is_ep_in, + u8 dev_speed, + u8 ep_type, + u16 max_packet_size, + u8 multi_count, + u8 interval, + u32 sched_frame, + u8 hub_addr, + u8 hub_port, + bool f_is_do_split, + void *ep); + +int delete_ed(struct sec_otghost *otghost, ed_t *delete_ed); + +int delete_td(struct sec_otghost *otghost, td_t * delete_td); + +int create_isoch_packet_desc( isoch_packet_desc_t **new_isoch_packet_desc, + u32 isoch_packet_num); + +int delete_isoch_packet_desc( isoch_packet_desc_t *del_isoch_packet_desc, + u32 isoch_packet_num); + + +void init_isoch_packet_desc( isoch_packet_desc_t *init_isoch_packet_desc, + u32 offset, + u32 isoch_packet_size, + u32 index); + +// NonPeriodicTransfer.c implements. + +int init_nonperio_stransfer(bool f_is_standard_dev_req, + td_t *parent_td); + +void update_nonperio_stransfer(td_t *parent_td); + +//PeriodicTransfer.c implements + +int init_perio_stransfer( bool f_is_isoch_transfer, + td_t *parent_td); + +void update_perio_stransfer(td_t *parent_td); + +static inline u32 calc_packet_cnt(u32 data_size, u16 max_packet_size) +{ + if(data_size != 0) + { + return (data_size%max_packet_size==0)?data_size/max_packet_size:data_size/max_packet_size+1; + } + return 1; +} + +#ifdef __cplusplus +} +#endif + + +#endif + diff --git a/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-bulk.c b/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-bulk.c new file mode 100644 index 0000000..e544f5c --- /dev/null +++ b/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-bulk.c @@ -0,0 +1,720 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : BulkTransferChecker.c + * [Description] : The Source file implements the external and internal functions of BulkTransferChecker. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2008/06/13 + * [Revision History] + * (1) 2008/06/18 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and implements functions of BulkTransferChecker + * + ****************************************************************************/ +/**************************************************************************** + * 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 "s3c-otg-transferchecker-bulk.h" +#include "s3c-otg-isr.h" + +/******************************************************************************/ +/*! + * @name u8 process_bulk_transfer(td_t *result_td, + hc_info_t *hc_reg_data) + + * + * @brief this function processes the result of the Bulk Transfer. + * firstly, this function checks the result of the Bulk Transfer. + * and according to the result, calls the sub-functions to process the result. + * + * + * @param [IN] result_td -indicates the pointer of the td_t whose channel is interruped. + * [IN] hc_reg_data -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_TRANSMIT -if need to retransmit the result_td. + * RE_SCHEDULE -if need to reschedule the result_td. + * DE_ALLOCATE -if USB Transfer is completed. + * NO_ACTION -if we don't need any action, + */ +/******************************************************************************/ +u8 process_bulk_transfer(td_t *result_td, + hc_info_t *hc_reg_data) +{ + hcintn_t hc_intr_info; + u8 return_val=0; + + //we just deal with the interrupts to be unmasked. + hc_intr_info.d32 = hc_reg_data->hc_int.d32 & result_td->cur_stransfer.hc_reg.hc_int_msk.d32; + + if(result_td->parent_ed_p->ed_desc.is_ep_in) + { + if(hc_intr_info.b.chhltd) + { + return_val = process_chhltd_on_bulk(result_td, hc_reg_data); + } + + else if (hc_intr_info.b.ack) + { + return_val =process_ack_on_bulk(result_td, hc_reg_data); + } + + else if (hc_intr_info.b.nak) + { + return_val =process_nak_on_bulk(result_td, hc_reg_data); + } + + else if (hc_intr_info.b.datatglerr) + { + return_val = process_datatgl_on_bulk(result_td,hc_reg_data); + } + } + else + { + if(hc_intr_info.b.chhltd) + { + return_val = process_chhltd_on_bulk(result_td, hc_reg_data); + + } + + else if(hc_intr_info.b.ack) + { + return_val =process_ack_on_bulk( result_td, hc_reg_data); + } + } + + return return_val; +} + +/******************************************************************************/ +/*! + * @name u8 process_chhltd_on_bulk(td_t *result_td, + * hc_info_t *hc_reg_data) + * + * + * @brief this function processes Channel Halt event according to Synopsys OTG Spec. + * firstly, this function checks the reason of the Channel Halt, and according to the reason, + * calls the sub-functions to process the result. + * + * + * @param [IN] result_td -indicates the pointer of the td_t to be mapped with the uChNum. + * [IN] hc_reg_data -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_TRANSMIT -if need to retransmit the result_td. + * RE_SCHEDULE -if need to reschedule the result_td. + * DE_ALLOCATE -if USB Transfer is completed. + */ +/******************************************************************************/ +u8 process_chhltd_on_bulk(td_t *result_td, + hc_info_t *hc_reg_data) +{ +#if 0 + if(result_td->parent_ed_p->ed_desc.is_ep_in) + { + if(hc_reg_data->hc_int.b.xfercompl) + { + return process_xfercompl_on_bulk( result_td, hc_reg_data); + } + else if(hc_reg_data->hc_int.b.stall) + { + return process_stall_on_bulk(result_td, hc_reg_data); + } + else if(hc_reg_data->hc_int.b.bblerr) + { + return process_bblerr_on_bulk(result_td, hc_reg_data); + } + else if(hc_reg_data->hc_int.b.xacterr) + { + return process_xacterr_on_bulk(result_td, hc_reg_data); + } + else + { + //Occure Error State..... + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ALL); + + //Mask ack Interrupt.. + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_DataTglErr); + + result_td->err_cnt++; + if(result_td->err_cnt == 3) + { + result_td->error_code = USB_ERR_STATUS_XACTERR; + result_td->err_cnt = 0; + return DE_ALLOCATE; + } + + return RE_TRANSMIT; + } + } + else + { + if(hc_reg_data->hc_int.b.xfercompl) + { + return process_xfercompl_on_bulk(result_td, hc_reg_data); + } + else if(hc_reg_data->hc_int.b.stall) + { + return process_stall_on_bulk(result_td, hc_reg_data); + } + else if(hc_reg_data->hc_int.b.xacterr) + { + return process_xacterr_on_bulk(result_td, hc_reg_data); + } + + else if(hc_reg_data->hc_int.b.nak) + { + return process_nak_on_bulk(result_td, hc_reg_data); + } + else if(hc_reg_data->hc_int.b.nyet) + { + return process_nyet_on_bulk(result_td, hc_reg_data); + } + else + { + //occur error state... + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ALL); + + //Mask ack Interrupt.. + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_DataTglErr); + + result_td->err_cnt++; + if(result_td->err_cnt == 3) + { + result_td->error_code = USB_ERR_STATUS_XACTERR; + result_td->err_cnt = 0; + return DE_ALLOCATE; + } + + return RE_TRANSMIT; + } + + + + } +#else + if(hc_reg_data->hc_int.b.xfercompl) + { + return process_xfercompl_on_bulk( result_td, hc_reg_data); + } + else if(hc_reg_data->hc_int.b.stall) + { + return process_stall_on_bulk(result_td, hc_reg_data); + } + else if(hc_reg_data->hc_int.b.bblerr) + { + return process_bblerr_on_bulk(result_td, hc_reg_data); + } + else if(hc_reg_data->hc_int.b.xacterr) + { + return process_xacterr_on_bulk(result_td, hc_reg_data); + } + else if(hc_reg_data->hc_int.b.nak) + { + return process_nak_on_bulk(result_td, hc_reg_data); + } + else if(hc_reg_data->hc_int.b.nyet) + { + return process_nyet_on_bulk(result_td, hc_reg_data); + } + else + { + //occur error state... + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ALL); + + //Mask ack Interrupt.. + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_DataTglErr); + + result_td->err_cnt++; + if(result_td->err_cnt == 3) + { + result_td->error_code = USB_ERR_STATUS_XACTERR; + result_td->err_cnt = 0; + return DE_ALLOCATE; + } + + return RE_TRANSMIT; + } +#endif + return USB_ERR_SUCCESS; + +} + +/******************************************************************************/ +/*! + * @name u8 process_xfercompl_on_bulk( td_t *result_td, + * hc_info_t *hc_reg_data) + * + * + * @brief this function deals with the xfercompl event according to Synopsys OTG Spec. + * the procedure of this function is as following + * 1. clears all bits of the channel' HCINT by using clear_ch_intr() of S3CIsr. + * 2. masks some bit of HCINTMSK + * 3. updates the result_td fields + * err_cnt/u8/standard_dev_req_info. + * 4. updates the result_td->parent_ed_p->ed_status. + * BulkDataTgl. + * 5. calculates the tranferred size by calling calc_transferred_size() on DATA_STAGE. + * + * @param [IN] result_td -indicates the pointer of the td_t to be mapped with the uChNum. + * [IN] hc_reg_data -indicates the interrupt information of the Channel to be interrupted + * + * @return USB_ERR_SUCCESS + */ +/******************************************************************************/ +u8 process_xfercompl_on_bulk(td_t *result_td, + hc_info_t *hc_reg_data) +{ + u8 ret_val=0; + + result_td->err_cnt = 0; + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ALL); + + //Mask ack Interrupt.. + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_DataTglErr); + + result_td->parent_ed_p->ed_status.is_ping_enable =false; + + result_td->transferred_szie += calc_transferred_size(true,result_td, hc_reg_data); + + if(result_td->transferred_szie==result_td->buf_size) + {//at IN Transfer, short transfer is accepted. + result_td->error_code = USB_ERR_STATUS_COMPLETE; + ret_val = DE_ALLOCATE; + } + else + { + if(result_td->parent_ed_p->ed_desc.is_ep_in&& hc_reg_data->hc_size.b.xfersize) + { + if(result_td->transfer_flag&USB_TRANS_FLAG_NOT_SHORT) + { + result_td->error_code =USB_ERR_STATUS_SHORTREAD; + } + else + { + result_td->error_code =USB_ERR_STATUS_COMPLETE; + } + ret_val = DE_ALLOCATE; + } + else + { + ret_val = RE_SCHEDULE; + } + } + update_datatgl(hc_reg_data->hc_size.b.pid, result_td); + + if(hc_reg_data->hc_int.b.nyet) + { + //at OUT Transfer, we must re-transmit. + if(result_td->parent_ed_p->ed_desc.is_ep_in==false) + { + + if(result_td->parent_ed_p->ed_desc.dev_speed == HIGH_SPEED_OTG) + { + result_td->parent_ed_p->ed_status.is_ping_enable = true; + } + else + { + result_td->parent_ed_p->ed_status.is_ping_enable = false; + } + } + } + + return ret_val; + +} + +/******************************************************************************/ +/*! + * @name u8 process_ahb_on_bulk(td_t *result_td, + * hc_info_t *hc_reg_data) + * + * + * @brief this function deals with theAHB Errorl event according to Synopsys OTG Spec. + * this function stop the channel to be executed + * TBD.... + * + * @param [IN] result_td -indicates the pointer of the td_t to be mapped with the uChNum. + * [IN] hc_reg_data -indicates the interrupt information of the Channel to be interrupted + * + * @return USB_ERR_SUCCESS + */ +/******************************************************************************/ +u8 process_ahb_on_bulk(td_t *result_td, + hc_info_t *hc_reg_data) +{ + result_td->err_cnt =0; + result_td->error_code =USB_ERR_STATUS_AHBERR; + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_AHBErr); + + //Mask ack Interrupt.. + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_DataTglErr); + + // we just calculate the size of the transferred data on Data Stage of Bulk Transfer. + result_td->transferred_szie += calc_transferred_size(false,result_td, hc_reg_data); + result_td->parent_ed_p->ed_status.is_ping_enable = false; + + return DE_ALLOCATE; + +} + +/******************************************************************************/ +/*! + * @name u8 process_stall_on_bulk(td_t *result_td, + * hc_info_t *hc_reg_data) + * + * + * @brief this function deals with theStall event according to Synopsys OTG Spec. + * when Stall is occured at Bulk Transfer, we should reset the PID as DATA0 + * + * @param [IN] result_td -indicates the pointer of the td_t to be mapped with the uChNum. + * [IN] hc_reg_data -indicates the interrupt information of the Channel to be interrupted + * + * @return DE_ALLOCATE + */ +/******************************************************************************/ +u8 process_stall_on_bulk( td_t * result_td, + hc_info_t * hc_reg_data) +{ + result_td->err_cnt =0; + result_td->error_code =USB_ERR_STATUS_STALL; + + //this channel is stalled, So we don't process another interrupts. + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ALL); + + //Mask ack Interrupt.. + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_DataTglErr); + + result_td->parent_ed_p->ed_status.is_ping_enable = false; + result_td->transferred_szie += calc_transferred_size(false,result_td, hc_reg_data); + + update_datatgl(DATA0, result_td); + + + return DE_ALLOCATE; +} + +/******************************************************************************/ +/*! + * @name u8 process_nak_on_bulk(td_t *result_td, + * hc_info_t *hc_reg_data) + * + * + * @brief this function deals with the nak event according to Synopsys OTG Spec. + * nak is occured at OUT/IN Transaction of Data/Status Stage, and is not occured at Setup Stage. + * If nak is occured at IN Transaction, this function processes this interrupt as following. + * 1. resets the result_td->err_cnt. + * 2. masks ack/nak/DaaTglErr bit of HCINTMSK. + * 3. clears the nak bit of HCINT + * 4. be careful, nak of IN Transaction don't require re-transmit. + * If nak is occured at OUT Transaction, this function processes this interrupt as following. + * 1. all procedures of IN Transaction are executed. + * 2. calculates the size of the transferred data. + * 3. if the speed of USB Device is High-Speed, sets the ping protocol. + * 4. update the Toggle + * at OUT Transaction, this function check whether the speed of USB Device is High-Speed or not. + * if USB Device is High-Speed, then + * this function sets the ping protocol. + * + * @param [IN] result_td -indicates the pointer of the td_t to be mapped with the uChNum. + * [IN] hc_reg_data -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_SCHEDULE -if the direction of the Transfer is OUT + * NO_ACTION -if the direction of the Transfer is IN + */ +/******************************************************************************/ +u8 process_nak_on_bulk(td_t *result_td, + hc_info_t *hc_reg_data) +{ + result_td->err_cnt = 0; + + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_DataTglErr); + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + + //at OUT Transfer, we must re-transmit. + if(result_td->parent_ed_p->ed_desc.is_ep_in==false) + { + result_td->transferred_szie += calc_transferred_size(false,result_td, hc_reg_data); + + if(result_td->parent_ed_p->ed_desc.dev_speed == HIGH_SPEED_OTG) + { + result_td->parent_ed_p->ed_status.is_ping_enable = true; + } + else + { + result_td->parent_ed_p->ed_status.is_ping_enable = false; + } + + update_datatgl(hc_reg_data->hc_size.b.pid, result_td); + + return RE_TRANSMIT; + } + return NO_ACTION; + +} + +/******************************************************************************/ +/*! + * @name u8 process_ack_on_bulk(td_t *result_td, + * hc_info_t *hc_reg_data) + * + * + * @brief this function deals with the ack event according to Synopsys OTG Spec. + * ack of IN/OUT Transaction don't need any retransmit. + * this function just resets result_td->err_cnt and masks ack/nak/DataTgl of HCINTMSK. + * finally, this function clears ack bit of HCINT and ed_status.is_ping_enable. + * + * @param [IN] result_td -indicates the pointer of the td_t to be mapped with the uChNum. + * [IN] hc_reg_data -indicates the interrupt information of the Channel to be interrupted + * + * @return USB_ERR_SUCCESS + */ +/******************************************************************************/ +u8 process_ack_on_bulk(td_t *result_td, + hc_info_t *hc_reg_data) +{ + result_td->err_cnt = 0; + + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_DataTglErr); + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ACK); + + result_td->parent_ed_p->ed_status.is_ping_enable = false; + + return NO_ACTION; + +} + +/******************************************************************************/ +/*! + * @name u8 process_nyet_on_bulk(td_t *result_td, + * hc_info_t *hc_reg_data) + * + * + * @brief this function deals with the nyet event according to Synopsys OTG Spec. + * nyet is only occured at OUT Transaction. + * If nyet is occured at OUT Transaction, this function processes this interrupt as following. + * 1. resets the result_td->err_cnt. + * 2. masks ack/nak/datatglerr bit of HCINTMSK. + * 3. clears the nyet bit of HCINT + * 4. calculates the size of the transferred data. + * 5. if the speed of USB Device is High-Speed, sets the ping protocol. + * 6. update the Data Toggle. + * 7. return RE_SCHEDULE to retransmit. + * + * @param [IN] result_td -indicates the pointer of the td_t to be mapped with the uChNum. + * [IN] hc_reg_data -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_SCHEDULE + */ +/******************************************************************************/ +u8 process_nyet_on_bulk(td_t *result_td, + hc_info_t *hc_reg_data) +{ + if(result_td->parent_ed_p->ed_desc.is_ep_in) + { + // Error State.... + return NO_ACTION; + } + + result_td->err_cnt = 0; + + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_DataTglErr); + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NYET); + + result_td->transferred_szie += calc_transferred_size(false,result_td, hc_reg_data); + + if(result_td->parent_ed_p->ed_desc.dev_speed == HIGH_SPEED_OTG) + { + result_td->parent_ed_p->ed_status.is_ping_enable = true; + } + else + { + result_td->parent_ed_p->ed_status.is_ping_enable = false; + } + + update_datatgl(hc_reg_data->hc_size.b.pid, result_td); + + return RE_TRANSMIT; + +} + +/******************************************************************************/ +/*! + * @name u8 process_xacterr_on_bulk( td_t *result_td, + * hc_info_t *hc_reg_data) + * + * + * @brief this function deals with the xacterr event according to Synopsys OTG Spec. + * xacterr is occured at OUT/IN Transaction and we should retransmit the USB Transfer + * if the Error Counter is less than the RETRANSMIT_THRESHOLD. + * the reasons of xacterr is Timeout/CRC error/false EOP. + * the procedure to process xacterr is as following. + * 1. increses the result_td->err_cnt + * 2. check whether the result_td->err_cnt is equal to 3. + * 2. unmasks ack/nak/datatglerr bit of HCINTMSK. + * 3. clears the xacterr bit of HCINT + * 4. calculates the size of the transferred data. + * 5. if the speed of USB Device is High-Speed, sets the ping protocol. + * 6. update the Data Toggle. + * + * @param [IN] result_td -indicates the pointer of the td_t to be mapped with the uChNum. + * [IN] hc_reg_data -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_TRANSMIT -if the error count is less than 3 + * DE_ALLOCATE -if the error count is equal to 3 + */ +/******************************************************************************/ +u8 process_xacterr_on_bulk( td_t *result_td, + hc_info_t *hc_reg_data) +{ + u8 ret_val = 0; + + if(result_td->err_cnt<RETRANSMIT_THRESHOLD) + { + result_td->cur_stransfer.hc_reg.hc_int_msk.d32 |= (CH_STATUS_ACK+CH_STATUS_NAK+CH_STATUS_DataTglErr); + ret_val = RE_TRANSMIT; + result_td->err_cnt++ ; + } + else + { + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_DataTglErr); + ret_val = DE_ALLOCATE; + result_td->err_cnt = 0 ; + result_td->error_code = USB_ERR_STATUS_XACTERR; + } + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_DataTglErr); + + result_td->transferred_szie += calc_transferred_size(false,result_td, hc_reg_data); + + if(result_td->parent_ed_p->ed_desc.is_ep_in==false) + { + if(result_td->parent_ed_p->ed_desc.dev_speed == HIGH_SPEED_OTG) + { + result_td->parent_ed_p->ed_status.is_ping_enable = true; + } + else + { + result_td->parent_ed_p->ed_status.is_ping_enable = false; + } + } + + + update_datatgl(hc_reg_data->hc_size.b.pid, result_td); + + return ret_val; + +} + +/******************************************************************************/ +/*! + * @name void process_bblerr_on_bulk(td_t *result_td, + * hc_info_t *hc_reg_data) + * + * + * @brief this function deals with the Babble event according to Synopsys OTG Spec. + * babble error is occured when the buffer to receive data to be transmit is overflow. + * So, babble error can be just occured at IN Transaction. + * when Babble Error is occured, we should stop the USB Transfer, and return the fact + * to Application. + * + * @param [IN] result_td -indicates the pointer of the td_t to be mapped with the uChNum. + * [IN] hc_reg_data -indicates the interrupt information of the Channel to be interrupted + * + * @return DE_ALLOCATE + */ +/******************************************************************************/ +u8 process_bblerr_on_bulk(td_t *result_td, + hc_info_t *hc_reg_data) +{ + + if(!result_td->parent_ed_p->ed_desc.is_ep_in) + { + return NO_ACTION; + } + + result_td->err_cnt =0; + result_td->error_code =USB_ERR_STATUS_BBLERR; + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ALL); + + //Mask ack Interrupt.. + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_DataTglErr); + + result_td->parent_ed_p->ed_status.is_ping_enable =false; + result_td->transferred_szie += calc_transferred_size(false, result_td, hc_reg_data); + + return DE_ALLOCATE; + + +} + +/******************************************************************************/ +/*! + * @name u8 process_datatgl_on_bulk( td_t *result_td, + * hc_info_t *hc_reg_data) + * + * + * @brief this function deals with the datatglerr event according to Synopsys OTG Spec. + * the datatglerr event is occured at IN Transfer, and the channel is not halted. + * this function just resets result_td->err_cnt and masks ack/nak/DataTgl of HCINTMSK. + * finally, this function clears datatglerr bit of HCINT. + * + * @param [IN] result_td -indicates the pointer of the td_t to be mapped with the uChNum. + * [IN] hc_reg_data -indicates the interrupt information of the Channel to be interrupted + * + * @return NO_ACTION + */ +/******************************************************************************/ +u8 process_datatgl_on_bulk(td_t *result_td, + hc_info_t *hc_reg_data) +{ + result_td->err_cnt = 0; + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_DataTglErr); + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_DataTglErr); + + return NO_ACTION; + +} + + diff --git a/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-bulk.h b/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-bulk.h new file mode 100644 index 0000000..e1f84ca --- /dev/null +++ b/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-bulk.h @@ -0,0 +1,88 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : BulkTransferChecker.h + * [Description] : The Header file defines the external and internal functions of BulkTransferChecker + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2008/06/18 + * [Revision History] + * (1) 2008/06/18 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and defines functions of BulkTransferChecker + * + ****************************************************************************/ +/**************************************************************************** + * 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 _BULK_TRANSFER_CHECKER_H +#define _BULK_TRANSFER_CHECKER_H + +/* +// ---------------------------------------------------------------------------- +// Include files : None. +// ---------------------------------------------------------------------------- +*/ +//#include "s3c-otg-common-typedef.h" +#include "s3c-otg-common-const.h" +#include "s3c-otg-common-errorcode.h" +#include "s3c-otg-common-datastruct.h" + + + +#ifdef __cplusplus +extern "C" +{ +#endif +u8 process_bulk_transfer(td_t *raw_td, + hc_info_t *hc_reg_data); + +u8 process_xfercompl_on_bulk(td_t *raw_td, + hc_info_t *hc_reg_data); + +u8 process_chhltd_on_bulk(td_t *raw_td, + hc_info_t *hc_reg_data); + +u8 process_ahb_on_bulk(td_t *raw_td, + hc_info_t *hc_reg_data); + +u8 process_stall_on_bulk(td_t *raw_td, + hc_info_t *hc_reg_data); + +u8 process_nak_on_bulk(td_t *raw_td, + hc_info_t *hc_reg_data); + +u8 process_ack_on_bulk(td_t *raw_td, + hc_info_t *hc_reg_data); + +u8 process_nyet_on_bulk(td_t *raw_td, + hc_info_t *hc_reg_data); + +u8 process_xacterr_on_bulk(td_t *raw_td, + hc_info_t *hc_reg_data); + +u8 process_bblerr_on_bulk(td_t *raw_td, + hc_info_t *hc_reg_data); + +u8 process_datatgl_on_bulk(td_t *raw_td, + hc_info_t *hc_reg_data); + +#ifdef __cplusplus +} +#endif + + +#endif + diff --git a/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-checker.h b/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-checker.h new file mode 100644 index 0000000..cefbcaf --- /dev/null +++ b/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-checker.h @@ -0,0 +1,66 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : DoneTransferChecker.h + * [Description] : The Header file defines the external and internal functions of S3CDoneTransferChecker. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2008/06/12 + * [Revision History] + * (1) 2008/06/12 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and defines functions of S3CDoneTransferChecker + * + ****************************************************************************/ +/**************************************************************************** + * 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 _DONE_TRANSFER_CHECKER_H +#define _DONE_TRANSFER_CHECKER_H + +/* +// ---------------------------------------------------------------------------- +// Include files : None. +// ---------------------------------------------------------------------------- +*/ + +//#include "s3c-otg-common-typedef.h" +#include "s3c-otg-common-const.h" +#include "s3c-otg-common-errorcode.h" +#include "s3c-otg-common-datastruct.h" + +#include "s3c-otg-hcdi-debug.h" + + + +#ifdef __cplusplus +extern "C" +{ +#endif + + + +#ifdef __cplusplus +} +#endif + + +#endif + + + + + + diff --git a/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-common.c b/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-common.c new file mode 100644 index 0000000..689d379 --- /dev/null +++ b/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-common.c @@ -0,0 +1,238 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : CommonTransferChecker.c + * [Description] : The Source file implements the external and internal functions of CommonTransferChecker. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2009/01/12 + * [Revision History] + * (1) 2008/06/12 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and implements functions of CommonTransferChecker + * + ****************************************************************************/ +/**************************************************************************** + * 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 "s3c-otg-transferchecker-common.h" + +/******************************************************************************/ +/*! + * @name int init_done_transfer_checker(void) + * + * @brief this function initiates S3CDoneTransferChecker Module. + * + * + * @param void + * + * @return void + */ +/******************************************************************************/ +//ss1 +/*void init_done_transfer_checker(void) +{ + return USB_ERR_SUCCESS; +}*/ + +/******************************************************************************/ +/*! + * @name void do_transfer_checker(struct sec_otghost *otghost) + * + * @brief this function processes the result of USB Transfer. So, do_transfer_checker fistly + * check which channel occurs OTG Interrupt and gets the status information of the channel. + * do_transfer_checker requests the information of td_t to S3CScheduler. + * To process the interrupt of the channel, do_transfer_checker calls the sub-modules of + * S3CDoneTransferChecker, for example, ControlTransferChecker, BulkTransferChecker. + * according to the process result of the channel interrupt, do_transfer_checker decides + * the USB Transfer will be done or retransmitted. + * + * + * @param void + * + * @return void + */ +/*******************************************************************************/ +void do_transfer_checker (struct sec_otghost *otghost) +__releases(&otghost->lock) +__acquires(&otghost->lock) +{ + u32 hc_intr = 0; + u32 hc_intr_msk = 0; + u8 do_try_cnt = 0; + + hc_info_t ch_info; + u32 td_addr = 0; + td_t *done_td = {0}; + u8 proc_result = 0; + + //by ss1 + otg_mem_set((void *)&ch_info, 0, sizeof(hc_info_t)); + + // Get value of HAINT... + get_intr_ch(&hc_intr,&hc_intr_msk); + +start_do_transfer_checker: + + while(do_try_cnt<MAX_CH_NUMBER) { + //checks the channel number to be masked or not. + if(!(hc_intr & hc_intr_msk & (1<<do_try_cnt))) { + do_try_cnt++; + goto start_do_transfer_checker; + } + + //Gets the address of the td_t to have the channel to be interrupted. + if(get_td_info(do_try_cnt, &td_addr) == USB_ERR_SUCCESS) { + + done_td = (td_t *)td_addr; + + if(do_try_cnt != done_td->cur_stransfer.alloc_chnum) { + do_try_cnt++; + goto start_do_transfer_checker; + } + } + else { + do_try_cnt++; + goto start_do_transfer_checker; + } + + //Gets the informationof channel to be interrupted. + get_ch_info(&ch_info,do_try_cnt); + + switch(done_td->parent_ed_p->ed_desc.endpoint_type) { + case CONTROL_TRANSFER: + proc_result = process_control_transfer(done_td, &ch_info); + break; + case BULK_TRANSFER: + proc_result = process_bulk_transfer(done_td, &ch_info); + break; + case INT_TRANSFER: + proc_result = process_intr_transfer(done_td, &ch_info); + break; + case ISOCH_TRANSFER: + /* proc_result = ProcessIsochTransfer(done_td, &ch_info); */ + break; + default:break; + } + + if((proc_result == RE_TRANSMIT) || (proc_result == RE_SCHEDULE)) { + done_td->parent_ed_p->ed_status.is_in_transferring = false; + done_td->is_transfer_done = false; + done_td->is_transferring = false; + + if(done_td->parent_ed_p->ed_desc.endpoint_type == CONTROL_TRANSFER || + done_td->parent_ed_p->ed_desc.endpoint_type==BULK_TRANSFER) { + update_nonperio_stransfer(done_td); + } + else { + update_perio_stransfer(done_td); + } + + if(proc_result == RE_TRANSMIT) { + retransmit(otghost, done_td); + } + else { + reschedule(done_td); + } + } + + else if(proc_result==DE_ALLOCATE) { + done_td->parent_ed_p->ed_status.is_in_transferring = false; + done_td->parent_ed_p->ed_status.in_transferring_td = 0; + done_td->is_transfer_done = true; + done_td->is_transferring = false; + + spin_unlock_otg(&otghost->lock); + otg_usbcore_giveback( done_td); + spin_lock_otg(&otghost->lock); + release_trans_resource(otghost, done_td); + } + else { //NO_ACTION.... + done_td->parent_ed_p->ed_status.is_in_transferring = true; + done_td->parent_ed_p->ed_status.in_transferring_td = (u32)done_td; + done_td->is_transfer_done = false; + done_td->is_transferring = true; + } + do_try_cnt++; + } + // Complete to process the Channel Interrupt. + // So. we now start to scheduler of S3CScheduler. + do_schedule(otghost); +} + + +int release_trans_resource(struct sec_otghost *otghost, td_t *done_td) +{ + //remove the pDeallocateTD from parent_ed_p. + otg_list_pop(&done_td->td_list_entry); + done_td->parent_ed_p->num_td--; + + //Call deallocate to release the channel and bandwidth resource of S3CScheduler. + deallocate(done_td); + delete_td(otghost, done_td); + return USB_ERR_SUCCESS; +} + +u32 calc_transferred_size(bool f_is_complete, td_t *td, hc_info_t *hc_info) +{ + if(f_is_complete) { + if(td->parent_ed_p->ed_desc.is_ep_in) { + return td->cur_stransfer.buf_size - hc_info->hc_size.b.xfersize; + } + else { + return td->cur_stransfer.buf_size; + } + } + else { + return (td->cur_stransfer.packet_cnt - hc_info->hc_size.b.pktcnt)*td->parent_ed_p->ed_desc.max_packet_size; + } +} + +void update_frame_number(td_t *pResultTD) +{ + u32 cur_frame_num=0; + + if(pResultTD->parent_ed_p->ed_desc.endpoint_type == CONTROL_TRANSFER || + pResultTD->parent_ed_p->ed_desc.endpoint_type == BULK_TRANSFER) { + return; + } + + pResultTD->parent_ed_p->ed_desc.sched_frame+= pResultTD->parent_ed_p->ed_desc.interval; + pResultTD->parent_ed_p->ed_desc.sched_frame &= HFNUM_MAX_FRNUM; + + cur_frame_num = oci_get_frame_num(); + if(((cur_frame_num - pResultTD->parent_ed_p->ed_desc.sched_frame)&HFNUM_MAX_FRNUM) <= (HFNUM_MAX_FRNUM>>1)) { + pResultTD->parent_ed_p->ed_desc.sched_frame = cur_frame_num; + } +} + +void update_datatgl(u8 ubCurDataTgl, td_t *td) +{ + switch(td->parent_ed_p->ed_desc.endpoint_type) { + case CONTROL_TRANSFER: + if(td->standard_dev_req_info.conrol_transfer_stage == DATA_STAGE) { + td->parent_ed_p->ed_status.control_data_tgl.data_tgl = ubCurDataTgl; + } + break; + case BULK_TRANSFER: + case INT_TRANSFER: + td->parent_ed_p->ed_status.data_tgl = ubCurDataTgl; + break; + + case ISOCH_TRANSFER: + break; + default:break; + } +} diff --git a/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-common.h b/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-common.h new file mode 100644 index 0000000..2ea05b8 --- /dev/null +++ b/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-common.h @@ -0,0 +1,79 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : CommonTransferChecker.h + * [Description] : The Header file defines the external and internal functions of CommonTransferChecker. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2008/06/12 + * [Revision History] + * (1) 2008/06/12 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and defines functions of CommonTransferChecker + * + ****************************************************************************/ +/**************************************************************************** + * 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 _COMMON_TRANSFER_CHECKER_H +#define _COMMON_TRANSFER_CHECKER_H + +/* +// ---------------------------------------------------------------------------- +// Include files : None. +// ---------------------------------------------------------------------------- +*/ + +#include "s3c-otg-common-common.h" +//#include "s3c-otg-common-const.h" +#include "s3c-otg-common-errorcode.h" +#include "s3c-otg-common-datastruct.h" +#include "s3c-otg-common-regdef.h" +#include "s3c-otg-transfer-transfer.h" + +#include "s3c-otg-hcdi-debug.h" +#include "s3c-otg-hcdi-memory.h" +#include "s3c-otg-scheduler-scheduler.h" +#include "s3c-otg-isr.h" +#include "s3c-otg-transferchecker-control.h" +#include "s3c-otg-transferchecker-bulk.h" +#include "s3c-otg-transferchecker-interrupt.h" +//#include "s3c-otg-transferchecker-iso.h" + + + +#ifdef __cplusplus +extern "C" +{ +#endif + +//void init_done_transfer_checker (void); +void do_transfer_checker (struct sec_otghost *otghost); +int release_trans_resource(struct sec_otghost *otghost, td_t *done_td); +u32 calc_transferred_size(bool f_is_complete, + td_t *td, + hc_info_t *hc_info); +void update_frame_number(td_t *result_td); +void update_datatgl(u8 cur_data_tgl, + td_t *td); + +#ifdef __cplusplus +} +#endif + + +#endif + + diff --git a/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-control.c b/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-control.c new file mode 100644 index 0000000..863f951 --- /dev/null +++ b/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-control.c @@ -0,0 +1,698 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : ControlTransferChecker.c + * [Description] : The Source file implements the external and internal functions of ControlTransferChecker. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2009/02/10 + * [Revision History] + * (1) 2008/06/13 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and implements functions of ControlTransferChecker + * (2) 2008/06/18 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Completed to implement ControlTransferChecker.c v1.0 + * + ****************************************************************************/ +/**************************************************************************** + * 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 "s3c-otg-transferchecker-control.h" +#include "s3c-otg-isr.h" + + + +/******************************************************************************/ +/*! + * @name u8 process_control_transfer(td_t *result_td, + hc_info_t *hc_reg_data) + + * + * @brief this function processes the result of the Control Transfer. + * firstly, this function checks the result the Control Transfer. + * and according to the result, calls the sub-functions to process the result. + * + * + * @param [IN] result_td -indicates the pointer of the td_t to be mapped with the uChNum. + * [IN] ubChNum -indicates the number of the channel to be interrupted. + * [IN] hc_reg_data -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_TRANSMIT -if need to retransmit the result_td. + * RE_SCHEDULE -if need to reschedule the result_td. + * DE_ALLOCATE -if USB Transfer is completed. + */ +/******************************************************************************/ +u8 process_control_transfer(td_t *result_td, + hc_info_t *hc_reg_data) +{ + hcintn_t hcintr_info; + u8 ret_val=0; + + //we just deal with the interrupts to be unmasked. + hcintr_info.d32 = hc_reg_data->hc_int.d32&result_td->cur_stransfer.hc_reg.hc_int_msk.d32; + + if(result_td->parent_ed_p->ed_desc.is_ep_in) + { + if(hcintr_info.b.chhltd) + { + ret_val = process_chhltd_on_control(result_td, hc_reg_data); + } + + else if (hcintr_info.b.ack) + { + ret_val =process_ack_on_control(result_td, hc_reg_data); + } + + else if (hcintr_info.b.nak) + { + ret_val =process_nak_on_control(result_td, hc_reg_data); + } + + else if (hcintr_info.b.datatglerr) + { + ret_val = process_datatgl_on_control(result_td,hc_reg_data); + } + } + else + { + if(hcintr_info.b.chhltd) + { + ret_val = process_chhltd_on_control(result_td, hc_reg_data); + } + + else if(hcintr_info.b.ack) + { + ret_val =process_ack_on_control( result_td, hc_reg_data); + + } + } + + return ret_val; +} + +/******************************************************************************/ +/*! + * @name u8 process_chhltd_on_control( td_t *result_td, + * hc_info_t *hc_reg_data) + * + * + * @brief this function processes Channel Halt event according to Synopsys OTG Spec. + * firstly, this function checks the reason of the Channel Halt, and according to the reason, + * calls the sub-functions to process the result. + * + * + * @param [IN] result_td -indicates the pointer of the td_t to be mapped with the uChNum. + * [IN] hc_reg_data -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_TRANSMIT -if need to retransmit the result_td. + * RE_SCHEDULE -if need to reschedule the result_td. + * DE_ALLOCATE -if USB Transfer is completed. + */ +/******************************************************************************/ +u8 process_chhltd_on_control( td_t *result_td, + hc_info_t *hc_reg_data) +{ + if(hc_reg_data->hc_int.b.xfercompl) + { + return process_xfercompl_on_control( result_td, hc_reg_data); + } + else if(hc_reg_data->hc_int.b.stall) + { + return process_stall_on_control(result_td, hc_reg_data); + } + else if(hc_reg_data->hc_int.b.bblerr) + { + return process_bblerr_on_control(result_td, hc_reg_data); + } + else if(hc_reg_data->hc_int.b.xacterr) + { + return process_xacterr_on_control(result_td, hc_reg_data); + } + else if(hc_reg_data->hc_int.b.nak) + { + return process_nak_on_control(result_td, hc_reg_data); + } + else if(hc_reg_data->hc_int.b.nyet) + { + return process_nyet_on_control(result_td, hc_reg_data); + } + else + { + + //Occure Error State..... + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ALL); + + //Mask ack Interrupt.. + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_DataTglErr); + result_td->err_cnt++; + if(result_td->err_cnt == 3) + { + result_td->error_code = USB_ERR_STATUS_XACTERR; + result_td->err_cnt = 0; + return DE_ALLOCATE; + } + return RE_TRANSMIT; + } + return USB_ERR_SUCCESS; +} + +/******************************************************************************/ +/*! + * @name u8 process_xfercompl_on_control(td_t *result_td, + * hc_info_t *hc_reg_data) + * + * + * @brief this function deals with the xfercompl event according to Synopsys OTG Spec. + * the procedure of this function is as following + * 1. clears all bits of the channel' HCINT by using clear_ch_intr() of S3CIsr. + * 2. masks some bit of HCINTMSK + * 3. updates the result_td fields + * err_cnt/u8/standard_dev_req_info. + * 4. updates the result_td->parent_ed_p->ed_status. + * control_data_tgl. + * 5. calculates the tranferred size by calling calc_transferred_size() on DATA_STAGE. + * + * @param [IN] result_td -indicates the pointer of the td_t to be mapped with the uChNum. + * [IN] hc_reg_data -indicates the interrupt information of the Channel to be interrupted + * + * @return USB_ERR_SUCCESS + */ +/******************************************************************************/ +u8 process_xfercompl_on_control(td_t *result_td, hc_info_t *hc_reg_data) +{ + u8 ret_val = 0; + + result_td->err_cnt = 0; + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum,CH_STATUS_ALL); + //Mask ack Interrupt.. + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_DataTglErr); + + result_td->parent_ed_p->ed_status.is_ping_enable = false; + + switch(result_td->standard_dev_req_info.conrol_transfer_stage) { + case SETUP_STAGE: + if(result_td->standard_dev_req_info.is_data_stage) { + result_td->standard_dev_req_info.conrol_transfer_stage = DATA_STAGE; + } + else { + result_td->standard_dev_req_info.conrol_transfer_stage = STATUS_STAGE; + } + ret_val = RE_TRANSMIT; + + break; + + case DATA_STAGE: + + result_td->transferred_szie += calc_transferred_size(true,result_td, hc_reg_data); + + /* at IN Transfer, short transfer is accepted. */ + if(result_td->transferred_szie==result_td->buf_size) { + result_td->standard_dev_req_info.conrol_transfer_stage =STATUS_STAGE; + result_td->error_code = USB_ERR_STATUS_COMPLETE; + } + else { + if(result_td->parent_ed_p->ed_desc.is_ep_in&& hc_reg_data->hc_size.b.xfersize) { + if(result_td->transfer_flag&USB_TRANS_FLAG_NOT_SHORT) { + result_td->error_code = USB_ERR_STATUS_SHORTREAD; + result_td->standard_dev_req_info.conrol_transfer_stage=STATUS_STAGE; + } + else { + result_td->error_code = USB_ERR_STATUS_COMPLETE; + result_td->standard_dev_req_info.conrol_transfer_stage =STATUS_STAGE; + } + } + else { // the Data Stage is not completed. So we need to continue Data Stage. + result_td->standard_dev_req_info.conrol_transfer_stage = DATA_STAGE; + update_datatgl(hc_reg_data->hc_size.b.pid, result_td); + } + } + + if(hc_reg_data->hc_int.b.nyet) { + /* at OUT Transfer, we must re-transmit. */ + if(result_td->parent_ed_p->ed_desc.is_ep_in==false) { + if(result_td->parent_ed_p->ed_desc.dev_speed == HIGH_SPEED_OTG) { + result_td->parent_ed_p->ed_status.is_ping_enable = true; + } + else { + result_td->parent_ed_p->ed_status.is_ping_enable = false; + } + } + } + ret_val = RE_TRANSMIT; + break; + + case STATUS_STAGE: + result_td->standard_dev_req_info.conrol_transfer_stage = COMPLETE_STAGE; + + if(hc_reg_data->hc_int.b.nyet) { + //at OUT Transfer, we must re-transmit. + if(result_td->parent_ed_p->ed_desc.is_ep_in==false) { + if(result_td->parent_ed_p->ed_desc.dev_speed == HIGH_SPEED_OTG) { + result_td->parent_ed_p->ed_status.is_ping_enable = true; + } + else { + result_td->parent_ed_p->ed_status.is_ping_enable = false; + } + } + } + + ret_val = DE_ALLOCATE; + break; + + default: + break; + } + + return ret_val; +} + +/******************************************************************************/ +/*! + * @name u8 process_ahb_on_control( td_t *result_td, + * hc_info_t *hc_reg_data) + * + * + * @brief this function deals with theAHB Errorl event according to Synopsys OTG Spec. + * this function stop the channel to be executed + * + * @param [IN] result_td -indicates the pointer of the td_t to be mapped with the uChNum. + * [IN] hc_reg_data -indicates the interrupt information of the Channel to be interrupted + * + * @return DE_ALLOCATE + */ +/******************************************************************************/ +u8 process_ahb_on_control( td_t *result_td, + hc_info_t *hc_reg_data) +{ + result_td->err_cnt =0; + result_td->error_code =USB_ERR_STATUS_AHBERR; + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_AHBErr); + + //Mask ack Interrupt.. + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_DataTglErr); + + // we just calculate the size of the transferred data on Data Stage of Control Transfer. + if(result_td->standard_dev_req_info.conrol_transfer_stage == DATA_STAGE) + { + result_td->transferred_szie += calc_transferred_size(false,result_td, hc_reg_data); + } + + return DE_ALLOCATE; + +} + +/******************************************************************************/ +/*! + * @name u8 process_stall_on_control( td_t *result_td, + * hc_info_t *hc_reg_data) + * + * + * @brief this function deals with theStall event according to Synopsys OTG Spec. + * but USB2.0 Spec don't permit the Stall on Setup Stage of Control Transfer. + * + * @param [IN] result_td -indicates the pointer of the td_t to be mapped with the uChNum. + * [IN] hc_reg_data -indicates the interrupt information of the Channel to be interrupted + * + * @return DE_ALLOCATE + */ +/******************************************************************************/ +u8 process_stall_on_control( td_t *result_td, + hc_info_t *hc_reg_data) +{ + result_td->err_cnt =0; + result_td->error_code =USB_ERR_STATUS_STALL; + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ALL); + + //Mask ack Interrupt.. + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_DataTglErr); + + result_td->parent_ed_p->ed_status.is_ping_enable = false; + + // we just calculate the size of the transferred data on Data Stage of Control Transfer. + if(result_td->standard_dev_req_info.conrol_transfer_stage == DATA_STAGE) + { + result_td->transferred_szie += calc_transferred_size(false,result_td, hc_reg_data); + } + + return DE_ALLOCATE; +} + +/******************************************************************************/ +/*! + * @name u8 process_nak_on_control( td_t *result_td, + * hc_info_t *hc_reg_data) + * + * + * @brief this function deals with the nak event according to Synopsys OTG Spec. + * nak is occured at OUT/IN Transaction of Data/Status Stage, and is not occured at Setup Stage. + * If nak is occured at IN Transaction, this function processes this interrupt as following. + * 1. resets the result_td->err_cnt. + * 2. masks ack/nak/DaaTglErr bit of HCINTMSK. + * 3. clears the nak bit of HCINT + * 4. be careful, nak of IN Transaction don't require re-transmit. + * If nak is occured at OUT Transaction, this function processes this interrupt as following. + * 1. all procedures of IN Transaction are executed. + * 2. calculates the size of the transferred data. + * 3. if the speed of USB Device is High-Speed, sets the ping protocol. + * 4. update the Toggle + * at OUT Transaction, this function check whether the speed of USB Device is High-Speed or not. + * if USB Device is High-Speed, then + * this function sets the ping protocol. + * + * @param [IN] result_td -indicates the pointer of the td_t to be mapped with the uChNum. + * [IN] hc_reg_data -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_SCHEDULE + */ +/******************************************************************************/ +u8 process_nak_on_control( td_t *result_td, + hc_info_t *hc_reg_data) +{ + result_td->err_cnt = 0; + + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_DataTglErr); + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + + //at OUT Transfer, we must re-transmit. + if(result_td->parent_ed_p->ed_desc.is_ep_in==false) + { + result_td->transferred_szie += calc_transferred_size(false,result_td, hc_reg_data); + + update_datatgl(hc_reg_data->hc_size.b.pid, result_td); + } + + if(result_td->parent_ed_p->ed_desc.dev_speed == HIGH_SPEED_OTG) + { + if(result_td->standard_dev_req_info.conrol_transfer_stage == DATA_STAGE) + { + if(result_td->parent_ed_p->ed_desc.is_ep_in==false) + { + result_td->parent_ed_p->ed_status.is_ping_enable = true; + } + } + else if(result_td->standard_dev_req_info.conrol_transfer_stage == STATUS_STAGE) + { + if(result_td->parent_ed_p->ed_desc.is_ep_in==true) + { + result_td->parent_ed_p->ed_status.is_ping_enable = true; + } + } + else + { + result_td->parent_ed_p->ed_status.is_ping_enable = false; + } + + } + + return RE_SCHEDULE; + + +} + +/******************************************************************************/ +/*! + * @name u8 process_ack_on_control( td_t *result_td, + * hc_info_t *hc_reg_data) + * + * + * @brief this function deals with the ack event according to Synopsys OTG Spec. + * ack of IN/OUT Transaction don't need any retransmit. + * this function just resets result_td->err_cnt and masks ack/nak/DataTgl of HCINTMSK. + * finally, this function clears ack bit of HCINT. + * + * @param [IN] result_td -indicates the pointer of the td_t to be mapped with the uChNum. + * [IN] hc_reg_data -indicates the interrupt information of the Channel to be interrupted + * + * @return NO_ACTION + */ +/******************************************************************************/ +u8 process_ack_on_control(td_t *result_td, + hc_info_t *hc_reg_data) +{ + result_td->err_cnt = 0; + + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_DataTglErr); + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ACK); + + result_td->parent_ed_p->ed_status.is_ping_enable =false; + + return NO_ACTION; + +} + +/******************************************************************************/ +/*! + * @name u8 process_nyet_on_control(td_t *result_td, + * hc_info_t *hc_reg_data) + * + * + * @brief this function deals with the nyet event according to Synopsys OTG Spec. + * nyet is occured at OUT Transaction of Data/Status Stage, and is not occured at Setup Stage. + * If nyet is occured at OUT Transaction, this function processes this interrupt as following. + * 1. resets the result_td->err_cnt. + * 2. masks ack/nak/datatglerr bit of HCINTMSK. + * 3. clears the nyet bit of HCINT + * 4. calculates the size of the transferred data. + * 5. if the speed of USB Device is High-Speed, sets the ping protocol. + * 6. update the Data Toggle. + * + * @param [IN] result_td -indicates the pointer of the td_t to be mapped with the uChNum. + * [IN] hc_reg_data -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_SCHEDULE + */ +/******************************************************************************/ +u8 process_nyet_on_control(td_t *result_td, + hc_info_t *hc_reg_data) +{ + result_td->err_cnt = 0; + + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_DataTglErr); + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NYET); + + result_td->transferred_szie += calc_transferred_size(false,result_td, hc_reg_data); + + if(result_td->parent_ed_p->ed_desc.dev_speed == HIGH_SPEED_OTG) + { + if(result_td->standard_dev_req_info.conrol_transfer_stage == DATA_STAGE) + { + if(result_td->parent_ed_p->ed_desc.is_ep_in==false) + { + result_td->parent_ed_p->ed_status.is_ping_enable = true; + } + } + else if(result_td->standard_dev_req_info.conrol_transfer_stage == STATUS_STAGE) + { + if(result_td->parent_ed_p->ed_desc.is_ep_in==true) + { + result_td->parent_ed_p->ed_status.is_ping_enable = true; + } + } + else + { + result_td->parent_ed_p->ed_status.is_ping_enable = false; + } + + } + + update_datatgl(hc_reg_data->hc_size.b.pid, result_td); + + return RE_SCHEDULE; +} + +/******************************************************************************/ +/*! + * @name u8 process_xacterr_on_control( td_t *result_td, + * hc_info_t *hc_reg_data) + * + * + * @brief this function deals with the xacterr event according to Synopsys OTG Spec. + * xacterr is occured at OUT/IN Transaction of Data/Status Stage, and is not occured at Setup Stage. + * if Timeout/CRC error/false EOP is occured, then xacterr is occured. + * the procedure to process xacterr is as following. + * 1. increses the result_td->err_cnt + * 2. check whether the result_td->err_cnt is equal to 3. + * 2. unmasks ack/nak/datatglerr bit of HCINTMSK. + * 3. clears the xacterr bit of HCINT + * 4. calculates the size of the transferred data. + * 5. if the speed of USB Device is High-Speed, sets the ping protocol. + * 6. update the Data Toggle. + * + * @param [IN] result_td -indicates the pointer of the td_t to be mapped with the uChNum. + * [IN] hc_reg_data -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_TRANSMIT -if the Error Counter is less than RETRANSMIT_THRESHOLD + * DE_ALLOCATE -if the Error Counter is equal to RETRANSMIT_THRESHOLD + */ +/******************************************************************************/ +u8 process_xacterr_on_control(td_t *result_td, + hc_info_t *hc_reg_data) +{ + u8 ret_val=0; + + if(result_td->err_cnt<RETRANSMIT_THRESHOLD) + { + result_td->cur_stransfer.hc_reg.hc_int_msk.d32 |= (CH_STATUS_ACK+CH_STATUS_NAK+CH_STATUS_DataTglErr); + ret_val = RE_TRANSMIT; + unmask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ACK); + unmask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + unmask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_DataTglErr); + result_td->err_cnt++ ; + } + else + { + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_DataTglErr); + ret_val = DE_ALLOCATE; + result_td->err_cnt = 0 ; + result_td->error_code = USB_ERR_STATUS_XACTERR; + } + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_DataTglErr); + + if(result_td->standard_dev_req_info.conrol_transfer_stage == DATA_STAGE) + { + result_td->transferred_szie += calc_transferred_size(false,result_td, hc_reg_data); + + } + + if(result_td->parent_ed_p->ed_desc.dev_speed == HIGH_SPEED_OTG) + { + if(result_td->standard_dev_req_info.conrol_transfer_stage == DATA_STAGE) + { + if(result_td->parent_ed_p->ed_desc.is_ep_in==false) + { + result_td->parent_ed_p->ed_status.is_ping_enable = true; + } + } + else if(result_td->standard_dev_req_info.conrol_transfer_stage == STATUS_STAGE) + { + if(result_td->parent_ed_p->ed_desc.is_ep_in==true) + { + result_td->parent_ed_p->ed_status.is_ping_enable = true; + } + } + else + { + result_td->parent_ed_p->ed_status.is_ping_enable = false; + } + + + } + + + + update_datatgl(hc_reg_data->hc_size.b.pid, result_td); + + return ret_val; + +} + +/******************************************************************************/ +/*! + * @name void process_bblerr_on_control( td_t *result_td, + * hc_info_t *hc_reg_data) + * + * + * @brief this function deals with the Babble event according to Synopsys OTG Spec. + * babble error can be just occured at IN Transaction. So if the direction of transfer is + * OUT, this function return Error Code. + * + * @param [IN] result_td -indicates the pointer of the td_t to be mapped with the uChNum. + * [IN] hc_reg_data -indicates the interrupt information of the Channel to be interrupted + * + * @return DE_ALLOCATE + * NO_ACTION -if the direction is OUT + */ +/******************************************************************************/ +u8 process_bblerr_on_control( td_t *result_td, + hc_info_t *hc_reg_data) +{ + + if(!result_td->parent_ed_p->ed_desc.is_ep_in) + { + return NO_ACTION; + } + + result_td->err_cnt = 0; + result_td->error_code =USB_ERR_STATUS_BBLERR; + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ALL); + + //Mask ack Interrupt.. + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_DataTglErr); + + result_td->parent_ed_p->ed_status.is_ping_enable =false; + + // we just calculate the size of the transferred data on Data Stage of Control Transfer. + if(result_td->standard_dev_req_info.conrol_transfer_stage == DATA_STAGE) + { + result_td->transferred_szie += calc_transferred_size(false, result_td, hc_reg_data); + } + + return DE_ALLOCATE; +} + +/******************************************************************************/ +/*! + * @name u8 process_datatgl_on_control(td_t *result_td, + * hc_info_t *hc_reg_data) + * + * + * @brief this function deals with the datatglerr event according to Synopsys OTG Spec. + * the datatglerr event is occured at IN Transfer. + * this function just resets result_td->err_cnt and masks ack/nak/DataTgl of HCINTMSK. + * finally, this function clears datatglerr bit of HCINT. + * + * @param [IN] result_td -indicates the pointer of the td_t to be mapped with the uChNum. + * [IN] hc_reg_data -indicates the interrupt information of the Channel to be interrupted + * + * @return NO_ACTION + */ +/******************************************************************************/ +u8 process_datatgl_on_control( td_t * result_td, + hc_info_t * hc_reg_data) +{ + result_td->err_cnt = 0; + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_DataTglErr); + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_DataTglErr); + + return NO_ACTION; + +} + + diff --git a/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-control.h b/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-control.h new file mode 100644 index 0000000..4d85367 --- /dev/null +++ b/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-control.h @@ -0,0 +1,99 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : ControlTransferChecker.h + * [Description] : The Header file defines the external and internal functions of ControlTransferChecker + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2009/01/12 + * [Revision History] + * (1) 2008/06/13 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and defines functions of ControlTransferChecker + * + ****************************************************************************/ +/**************************************************************************** + * 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 _CONTROL_TRANSFER_CHECKER_H +#define _CONTROL_TRANSFER_CHECKER_H + +/* +// ---------------------------------------------------------------------------- +// Include files : None. +// ---------------------------------------------------------------------------- +*/ + +#include "s3c-otg-common-common.h" +//#include "s3c-otg-common-typedef.h" +#include "s3c-otg-common-const.h" +#include "s3c-otg-common-errorcode.h" +#include "s3c-otg-common-datastruct.h" +#include "s3c-otg-common-regdef.h" + +#include "s3c-otg-hcdi-debug.h" +#include "s3c-otg-scheduler-scheduler.h" + +#include "s3c-otg-transferchecker-checker.h" + + +#ifdef __cplusplus +extern "C" +{ +#endif +u8 process_control_transfer( td_t *raw_td, + hc_info_t *hc_reg_data); + +u8 process_xfercompl_on_control(td_t *raw_td, + hc_info_t *hc_reg_data); + +u8 process_chhltd_on_control( td_t *raw_td, + hc_info_t *hc_reg_data); + +u8 process_ahb_on_control( td_t *raw_td, + hc_info_t *hc_reg_data); + +u8 process_stall_on_control( td_t *raw_td, + hc_info_t *hc_reg_data); + +u8 process_nak_on_control (td_t *raw_td, + hc_info_t *hc_reg_data); + +u8 process_ack_on_control (td_t *raw_td, + hc_info_t *hc_reg_data); + +u8 process_nyet_on_control( td_t *raw_td, + hc_info_t *hc_reg_data); + +u8 process_xacterr_on_control(td_t *raw_td, + hc_info_t *hc_reg_data); + +u8 process_bblerr_on_control( td_t *raw_td, + hc_info_t *hc_reg_data); + +u8 process_datatgl_on_control(td_t *raw_td, + hc_info_t *hc_reg_data); +u8 process_indirection_on_control( td_t *result_td, + hc_info_t *hc_reg_data); + +u8 process_outdirection_on_control(td_t *result_td, + hc_info_t *hc_reg_data); + +#ifdef __cplusplus +} +#endif + + +#endif diff --git a/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-interrupt.c b/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-interrupt.c new file mode 100644 index 0000000..653628e --- /dev/null +++ b/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-interrupt.c @@ -0,0 +1,574 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : IntTransferChecker.c + * [Description] : The Source file implements the external and internal functions of IntTransferChecker + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2008/06/19 + * [Revision History] + * (1) 2008/06/18 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and implements functions of IntTransferChecker + * + ****************************************************************************/ +/**************************************************************************** + * 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 "s3c-otg-transferchecker-interrupt.h" + +/******************************************************************************/ +/*! + * @name u8 process_intr_transfer(td_t *result_td, + * hc_info_t *HCRegData) + * + * + * @brief this function processes the result of the Interrupt Transfer. + * firstly, this function checks the result of the Interrupt Transfer. + * and according to the result, calls the sub-functions to process the result. + * + * + * @param [IN] result_td -indicates the pointer of the td_t whose channel is interruped. + * [IN] HCRegData -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_TRANSMIT -if need to retransmit the result_td. + * RE_SCHEDULE -if need to reschedule the result_td. + * DE_ALLOCATE -if USB Transfer is completed. + * NO_ACTION -if we don't need any action, + */ +/******************************************************************************/ +u8 process_intr_transfer(td_t *result_td, hc_info_t *hc_reg_data) +{ + hcintn_t hc_intr_info; + u8 ret_val=0; + + //we just deal with the interrupts to be unmasked. + hc_intr_info.d32 = hc_reg_data->hc_int.d32&result_td->cur_stransfer.hc_reg.hc_int_msk.d32; + + if(result_td->parent_ed_p->ed_desc.is_ep_in) { + if(hc_intr_info.b.chhltd) { + ret_val = process_chhltd_on_intr(result_td, hc_reg_data); + } + + else if (hc_intr_info.b.ack) { + ret_val = process_ack_on_intr(result_td, hc_reg_data); + } + } + else { + if(hc_intr_info.b.chhltd) { + ret_val = process_chhltd_on_intr(result_td, hc_reg_data); + } + + else if(hc_intr_info.b.ack) { + ret_val = process_ack_on_intr( result_td, hc_reg_data); + } + } + + return ret_val; +} + +/******************************************************************************/ +/*! + * @name u8 process_chhltd_on_intr(td_t *result_td, + * hc_info_t *HCRegData) + * + * + * @brief this function processes Channel Halt event according to Synopsys OTG Spec. + * firstly, this function checks the reason of the Channel Halt, and according to the reason, + * calls the sub-functions to process the result. + * + * + * @param [IN] result_td -indicates the pointer of the td_t to be mapped with the uChNum. + * [IN] HCRegData -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_TRANSMIT -if need to retransmit the result_td. + * RE_SCHEDULE -if need to reschedule the result_td. + * DE_ALLOCATE -if USB Transfer is completed. + */ +/******************************************************************************/ +u8 process_chhltd_on_intr(td_t *result_td, + hc_info_t *hc_reg_data) +{ + if(result_td->parent_ed_p->ed_desc.is_ep_in) + { + if(hc_reg_data->hc_int.b.xfercompl) + { + return process_xfercompl_on_intr( result_td, hc_reg_data); + } + else if(hc_reg_data->hc_int.b.stall) + { + return process_stall_on_intr(result_td, hc_reg_data); + } + else if(hc_reg_data->hc_int.b.bblerr) + { + return process_bblerr_on_intr(result_td, hc_reg_data); + } + else if (hc_reg_data->hc_int.b.nak) + { + return process_nak_on_intr(result_td, hc_reg_data); + } + else if(hc_reg_data->hc_int.b.datatglerr) + { + return process_datatgl_on_intr(result_td, hc_reg_data); + } + else if(hc_reg_data->hc_int.b.frmovrun) + { + return process_frmovrrun_on_intr(result_td,hc_reg_data); + } + else if(hc_reg_data->hc_int.b.xacterr) + { + return process_xacterr_on_intr(result_td, hc_reg_data); + } + else + { + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ALL); + + //Mask ack Interrupt.. + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_DataTglErr); + return RE_TRANSMIT; + } + } + else + { + if(hc_reg_data->hc_int.b.xfercompl) + { + return process_xfercompl_on_intr( result_td, hc_reg_data); + } + else if(hc_reg_data->hc_int.b.stall) + { + return process_stall_on_intr(result_td, hc_reg_data); + } + else if (hc_reg_data->hc_int.b.nak) + { + return process_nak_on_intr(result_td, hc_reg_data); + } + else if(hc_reg_data->hc_int.b.frmovrun) + { + return process_frmovrrun_on_intr(result_td,hc_reg_data); + } + else if(hc_reg_data->hc_int.b.xacterr) + { + return process_xacterr_on_intr(result_td, hc_reg_data); + } + else + { + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ALL); + + //Mask ack Interrupt.. + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_DataTglErr); + + result_td->err_cnt++; + if(result_td->err_cnt == 3) + { + result_td->error_code = USB_ERR_STATUS_XACTERR; + result_td->err_cnt = 0; + return DE_ALLOCATE; + } + + return RE_TRANSMIT; + } + + } + + +} + +/******************************************************************************/ +/*! + * @name u8 process_xfercompl_on_intr( td_t *result_td, + * hc_info_t *hc_reg_data) + * + * + * @brief this function deals with the xfercompl event according to Synopsys OTG Spec. + * the procedure of this function is as following + * 1. clears all bits of the channel' HCINT by using clear_ch_intr() of S3CIsr. + * 2. masks ack/nak(?)/datatglerr(?) bit of HCINTMSK + * 3. Resets the err_cnt of result_td. + * 4. updates the result_td->parent_ed_p->ed_status. + * IntDataTgl. + * 5. calculates the tranferred size by calling calc_transferred_size() on DATA_STAGE. + * + * @param [IN] result_td -indicates the pointer of the td_t to be mapped with the uChNum. + * [IN] HCRegData -indicates the interrupt information of the Channel to be interrupted + * + * @return DE_ALLOCATE -if USB Transfer is completed. + * RE_TRANSMIT -if need to retransmit the result_td. + */ +/******************************************************************************/ +u8 process_xfercompl_on_intr( td_t *result_td, + hc_info_t *hc_reg_data) +{ + u8 ret_val=0; + + result_td->err_cnt =0; + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ALL); + + //Mask ack Interrupt.. + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_DataTglErr); + + result_td->transferred_szie += calc_transferred_size(true,result_td, hc_reg_data); + + if(result_td->transferred_szie==result_td->buf_size) + {//at IN Transfer, short transfer is accepted. + result_td->error_code = USB_ERR_STATUS_COMPLETE; + ret_val = DE_ALLOCATE; + } + else + { + // this routine will not be executed on Interrupt Transfer. + // So, we should decide to remove this routine or not. + if(result_td->parent_ed_p->ed_desc.is_ep_in&& hc_reg_data->hc_size.b.xfersize) + { + if(result_td->transfer_flag&USB_TRANS_FLAG_NOT_SHORT) + { + result_td->error_code = USB_ERR_STATUS_SHORTREAD; + } + else + { + result_td->error_code = USB_ERR_STATUS_COMPLETE; + } + ret_val = DE_ALLOCATE; + } + else + { // the Data Stage is not completed. So we need to continue Data Stage. + update_datatgl(hc_reg_data->hc_size.b.pid, result_td); + ret_val = RE_TRANSMIT; + } + } + + update_datatgl(hc_reg_data->hc_size.b.pid, result_td); + + return ret_val; + +} + +/******************************************************************************/ +/*! + * @name u8 process_ahb_on_intr(td_t *result_td, + * hc_info_t *hc_reg_data) + * + * + * @brief this function deals with theAHB Errorl event according to Synopsys OTG Spec. + * this function stop the channel to be executed + * + * + * @param [IN] result_td -indicates the pointer of the td_t to be mapped with the uChNum. + * [IN] HCRegData -indicates the interrupt information of the Channel to be interrupted + * + * @return DE_ALLOCATE + */ +/******************************************************************************/ +u8 process_ahb_on_intr(td_t *result_td, + hc_info_t *hc_reg_data) +{ + result_td->err_cnt = 0; + result_td->error_code = USB_ERR_STATUS_AHBERR; + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_AHBErr); + + //Mask ack Interrupt.. + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_DataTglErr); + + // we just calculate the size of the transferred data on Data Stage of Int Transfer. + result_td->transferred_szie += calc_transferred_size(false,result_td, hc_reg_data); + result_td->parent_ed_p->ed_status.is_ping_enable =false; + + return DE_ALLOCATE; + +} + +/******************************************************************************/ +/*! + * @name u8 process_stall_on_intr(td_t *result_td, + * hc_info_t *hc_reg_data) + * + * + * @brief this function deals with the Stall event according to Synopsys OTG Spec. + * when Stall is occured at Int Transfer, we should reset the PID as DATA0 + * + * @param [IN] result_td -indicates the pointer of the td_t to be mapped with the uChNum. + * [IN] HCRegData -indicates the interrupt information of the Channel to be interrupted + * + * @return DE_ALLOCATE + */ +/******************************************************************************/ +u8 process_stall_on_intr(td_t *result_td, + hc_info_t *hc_reg_data) +{ + result_td->err_cnt = 0; + result_td->error_code = USB_ERR_STATUS_STALL; + + //this channel is stalled, So we don't process another interrupts. + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ALL); + + //Mask ack Interrupt.. + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_DataTglErr); + + result_td->transferred_szie += calc_transferred_size(false,result_td, hc_reg_data); + + update_datatgl(DATA0, result_td); + + return DE_ALLOCATE; +} + +/******************************************************************************/ +/*! + * @name u8 process_nak_on_intr(td_t *result_td, + * hc_info_t *hc_reg_data) + * + * + * @brief this function deals with the nak event according to Synopsys OTG Spec. + * nak is occured at OUT/IN Transaction of Interrupt Transfer. + * we can't use ping protocol on Interrupt Transfer. and Syonopsys OTG IP occures + * chhltd interrupt on nak of IN/OUT Transaction. So we should retransmit the transfer + * on IN Transfer. + * If nak is occured at IN Transaction, this function processes this interrupt as following. + * 1. resets the result_td->err_cnt. + * 2. masks ack/nak/DaaTglErr bit of HCINTMSK. + * 3. clears the nak bit of HCINT + * 4. calculates frame number to retransmit this Interrupt Transfer. + * + * If nak is occured at OUT Transaction, this function processes this interrupt as following. + * 1. all procedures of IN Transaction are executed. + * 2. calculates the size of the transferred data. + * 3. if the speed of USB Device is High-Speed, sets the ping protocol. + * 4. update the Toggle + * at OUT Transaction, this function check whether the speed of USB Device is High-Speed or not. + * if USB Device is High-Speed, then + * this function sets the ping protocol. + * + * @param [IN] result_td -indicates the pointer of the td_t to be mapped with the uChNum. + * [IN] HCRegData -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_SCHEDULE -if the direction of the Transfer is OUT + * NO_ACTION -if the direction of the Transfer is IN + */ +/******************************************************************************/ +u8 process_nak_on_intr(td_t *result_td, + hc_info_t *hc_reg_data) +{ + result_td->err_cnt = 0; + + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_DataTglErr); + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + + + result_td->transferred_szie += calc_transferred_size(false,result_td, hc_reg_data); + + update_datatgl(hc_reg_data->hc_size.b.pid, result_td); + + update_frame_number(result_td); + + return RE_SCHEDULE; +// return RE_TRANSMIT; + + +} + +/******************************************************************************/ +/*! + * @name u8 process_ack_on_intr(td_t *result_td, + * hc_info_t *hc_reg_data) + * + * + * @brief this function deals with the ack event according to Synopsys OTG Spec. + * ack of IN/OUT Transaction don't need any retransmit. + * this function just resets result_td->err_cnt and masks ack/nak/DataTgl of HCINTMSK. + * finally, this function clears ack bit of HCINT and ed_status.is_ping_enable. + * + * @param [IN] result_td -indicates the pointer of the td_t to be mapped with the uChNum. + * [IN] HCRegData -indicates the interrupt information of the Channel to be interrupted + * + * @return NO_ACTION + */ +/******************************************************************************/ +u8 process_ack_on_intr(td_t *result_td, + hc_info_t *hc_reg_data) +{ + result_td->err_cnt = 0; + + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_DataTglErr); + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ACK); + + return NO_ACTION; +} + +/******************************************************************************/ +/*! + * @name u8 process_xacterr_on_intr(td_t *result_td, + * hc_info_t *hc_reg_data) + * + * @brief this function deals with the xacterr event according to Synopsys OTG Spec. + * xacterr is occured at OUT/IN Transaction and we should retransmit the USB Transfer + * if the Error Counter is less than the RETRANSMIT_THRESHOLD. + * the reasons of xacterr is Timeout/CRC error/false EOP. + * the procedure to process xacterr is as following. + * 1. increses the result_td->err_cnt + * 2. check whether the result_td->err_cnt is equal to 3. + * 2. unmasks ack/nak/datatglerr bit of HCINTMSK. + * 3. clears the xacterr bit of HCINT + * 4. calculates the size of the transferred data. + * 5. update the Data Toggle. + * 6. update the frame number to start retransmitting the Interrupt Transfer. + * + * @param [IN] result_td -indicates the pointer of the td_t to be mapped with the uChNum. + * [IN] HCRegData -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_SCHEDULE -if the error count is less than 3 + * DE_ALLOCATE -if the error count is equal to 3 + */ +/******************************************************************************/ +u8 process_xacterr_on_intr(td_t *result_td, + hc_info_t *hc_reg_data) +{ + u8 ret_val = 0; + + if(result_td->err_cnt<RETRANSMIT_THRESHOLD) + { + result_td->cur_stransfer.hc_reg.hc_int_msk.d32 |=(CH_STATUS_ACK+CH_STATUS_NAK+CH_STATUS_DataTglErr); + ret_val = RE_SCHEDULE; + result_td->err_cnt++ ; + } + else + { + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_DataTglErr); + ret_val = DE_ALLOCATE; + result_td->err_cnt = 0 ; + } + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_DataTglErr); + + result_td->transferred_szie += calc_transferred_size(false,result_td, hc_reg_data); + + update_datatgl(hc_reg_data->hc_size.b.pid, result_td); + + if(ret_val == RE_SCHEDULE) + { //Calculates the frame number + update_frame_number(result_td); + } + + return ret_val; +} + +/******************************************************************************/ +/*! + * @name void process_bblerr_on_intr(td_t *result_td, + * hc_info_t *hc_reg_data) + * + * + * @brief this function deals with the Babble event according to Synopsys OTG Spec. + * babble error is occured when the USB device continues to send packets + * althrough EOP is occured. So Babble error is only occured at IN Transfer. + * when Babble Error is occured, we should stop the USB Transfer, and return the fact + * to Application. + * + * @param [IN] result_td -indicates the pointer of the td_t to be mapped with the uChNum. + * [IN] HCRegData -indicates the interrupt information of the Channel to be interrupted + * + * @return DE_ALLOCATE + */ +/******************************************************************************/ +u8 process_bblerr_on_intr(td_t *result_td, + hc_info_t *hc_reg_data) +{ + + if(!result_td->parent_ed_p->ed_desc.is_ep_in) + { + return NO_ACTION; + } + + result_td->err_cnt = 0; + result_td->error_code =USB_ERR_STATUS_BBLERR; + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ALL); + + //Mask ack Interrupt.. + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_DataTglErr); + + result_td->transferred_szie += calc_transferred_size(false, result_td, hc_reg_data); + return DE_ALLOCATE; +} + +/******************************************************************************/ +/*! + * @name u8 process_datatgl_on_intr( td_t *result_td, + * hc_info_t *hc_reg_data) + * + * @brief this function deals with the datatglerr event according to Synopsys OTG Spec. + * the datatglerr event is occured at IN Transfer, and the channel is not halted. + * this function just resets result_td->err_cnt and masks ack/nak/DataTgl of HCINTMSK. + * finally, this function clears datatglerr bit of HCINT. + * + * @param [IN] result_td -indicates the pointer of the td_t to be mapped with the uChNum. + * [IN] HCRegData -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_SCHEDULE + */ +/******************************************************************************/ +u8 process_datatgl_on_intr(td_t *result_td, + hc_info_t *hc_reg_data) +{ + result_td->err_cnt = 0; + + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, CH_STATUS_DataTglErr); + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + + result_td->transferred_szie += calc_transferred_size(false,result_td, hc_reg_data); + + update_datatgl(hc_reg_data->hc_size.b.pid, result_td); + + update_frame_number(result_td); + + return RE_SCHEDULE; +} + +u8 process_frmovrrun_on_intr(td_t *result_td, + hc_info_t *hc_reg_data) +{ + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + + update_datatgl(hc_reg_data->hc_size.b.pid, result_td); + + update_frame_number(result_td); + + return RE_TRANSMIT; +} + diff --git a/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-interrupt.h b/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-interrupt.h new file mode 100644 index 0000000..0a735a2 --- /dev/null +++ b/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-interrupt.h @@ -0,0 +1,97 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : IntTransferChecker.h + * [Description] : The Header file defines the external and internal functions of IntTransferChecker + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2008/06/19 + * [Revision History] + * (1) 2008/06/18 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and defines functions of IntTransferChecker + * + ****************************************************************************/ +/**************************************************************************** + * 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 _INT_TRANSFER_CHECKER_H +#define _INT_TRANSFER_CHECKER_H + +/* +// ---------------------------------------------------------------------------- +// Include files : None. +// ---------------------------------------------------------------------------- +*/ + +#include "s3c-otg-common-common.h" +//#include "s3c-otg-common-typedef.h" +#include "s3c-otg-common-const.h" +#include "s3c-otg-common-errorcode.h" +#include "s3c-otg-common-datastruct.h" +#include "s3c-otg-common-regdef.h" + +#include "s3c-otg-hcdi-debug.h" +#include "s3c-otg-scheduler-scheduler.h" +#include "s3c-otg-isr.h" +#include "s3c-otg-transferchecker-checker.h" + + + +#ifdef __cplusplus +extern "C" +{ +#endif + +u8 process_intr_transfer(td_t *pRawTD, + hc_info_t *pHCRegData); + +u8 process_xfercompl_on_intr(td_t *pRawTD, + hc_info_t *pHCRegData); + +u8 process_chhltd_on_intr(td_t *pRawTD, + hc_info_t *pHCRegData); + +u8 process_ahb_on_intr(td_t *pRawTD, + hc_info_t *pHCRegData); + +u8 process_stall_on_intr(td_t *pRawTD, + hc_info_t *pHCRegData); + +u8 process_nak_on_intr(td_t *pRawTD, + hc_info_t *pHCRegData); + +u8 process_frmovrrun_on_intr(td_t *pRawTD, + hc_info_t *pHCRegData); + +u8 process_ack_on_intr(td_t *pRawTD, + hc_info_t *pHCRegData); + +u8 process_xacterr_on_intr(td_t *pRawTD, + hc_info_t *pHCRegData); + +u8 process_bblerr_on_intr(td_t *pRawTD, + hc_info_t *pHCRegData); + +u8 process_datatgl_on_intr(td_t *pRawTD, + hc_info_t *pHCRegData); + +#ifdef __cplusplus +} +#endif + + +#endif + diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index a4468d9..a44f2d4 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -1505,6 +1505,7 @@ void xhci_free_command(struct xhci_hcd *xhci, void xhci_mem_cleanup(struct xhci_hcd *xhci) { struct pci_dev *pdev = to_pci_dev(xhci_to_hcd(xhci)->self.controller); + struct xhci_cd *cur_cd, *next_cd; int size; int i; @@ -1520,10 +1521,16 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci) xhci->event_ring = NULL; xhci_dbg(xhci, "Freed event ring\n"); + xhci->cmd_ring_reserved_trbs = 0; if (xhci->cmd_ring) xhci_ring_free(xhci, xhci->cmd_ring); xhci->cmd_ring = NULL; xhci_dbg(xhci, "Freed command ring\n"); + list_for_each_entry_safe(cur_cd, next_cd, + &xhci->cancel_cmd_list, cancel_cmd_list) { + list_del(&cur_cd->cancel_cmd_list); + kfree(cur_cd); + } for (i = 1; i < MAX_HC_SLOTS; ++i) xhci_free_virt_device(xhci, i); @@ -2013,6 +2020,7 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) xhci->cmd_ring = xhci_ring_alloc(xhci, 1, true, false, flags); if (!xhci->cmd_ring) goto fail; + INIT_LIST_HEAD(&xhci->cancel_cmd_list); xhci_dbg(xhci, "Allocated command ring at %p\n", xhci->cmd_ring); xhci_dbg(xhci, "First segment DMA is 0x%llx\n", (unsigned long long)xhci->cmd_ring->first_seg->dma); diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index 4509f69..73fea4b 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -123,6 +123,7 @@ static int xhci_pci_setup(struct usb_hcd *hcd) xhci_dbg(xhci, "QUIRK: Fresco Logic revision %u " "has broken MSI implementation\n", pdev->revision); + xhci->quirks |= XHCI_TRUST_TX_LENGTH; } if (pdev->vendor == PCI_VENDOR_ID_NEC) @@ -139,11 +140,22 @@ static int xhci_pci_setup(struct usb_hcd *hcd) xhci->quirks |= XHCI_SPURIOUS_SUCCESS; xhci->quirks |= XHCI_EP_LIMIT_QUIRK; xhci->limit_active_eps = 64; + /* + * PPT desktop boards DH77EB and DH77DF will power back on after + * a few seconds of being shutdown. The fix for this is to + * switch the ports from xHCI to EHCI on shutdown. We can't use + * DMI information to find those particular boards (since each + * vendor will change the board name), so we have to key off all + * PPT chipsets. + */ + xhci->quirks |= XHCI_SPURIOUS_REBOOT; + xhci->quirks |= XHCI_AVOID_BEI; } if (pdev->vendor == PCI_VENDOR_ID_ETRON && pdev->device == PCI_DEVICE_ID_ASROCK_P67) { xhci->quirks |= XHCI_RESET_ON_RESUME; xhci_dbg(xhci, "QUIRK: Resetting on resume\n"); + xhci->quirks |= XHCI_TRUST_TX_LENGTH; } if (pdev->vendor == PCI_VENDOR_ID_VIA) xhci->quirks |= XHCI_RESET_ON_RESUME; diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index c42fdff..1a38281 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -147,25 +147,34 @@ static void next_trb(struct xhci_hcd *xhci, */ static void inc_deq(struct xhci_hcd *xhci, struct xhci_ring *ring, bool consumer) { - union xhci_trb *next = ++(ring->dequeue); unsigned long long addr; ring->deq_updates++; - /* Update the dequeue pointer further if that was a link TRB or we're at - * the end of an event ring segment (which doesn't have link TRBS) - */ - while (last_trb(xhci, ring, ring->deq_seg, next)) { - if (consumer && last_trb_on_last_seg(xhci, ring, ring->deq_seg, next)) { - ring->cycle_state = (ring->cycle_state ? 0 : 1); - if (!in_interrupt()) - xhci_dbg(xhci, "Toggle cycle state for ring %p = %i\n", - ring, - (unsigned int) ring->cycle_state); + + do { + /* + * Update the dequeue pointer further if that was a link TRB or + * we're at the end of an event ring segment (which doesn't have + * link TRBS) + */ + if (last_trb(xhci, ring, ring->deq_seg, ring->dequeue)) { + if (consumer && last_trb_on_last_seg(xhci, ring, + ring->deq_seg, ring->dequeue)) { + if (!in_interrupt()) + xhci_dbg(xhci, "Toggle cycle state " + "for ring %p = %i\n", + ring, + (unsigned int) + ring->cycle_state); + ring->cycle_state = (ring->cycle_state ? 0 : 1); + } + ring->deq_seg = ring->deq_seg->next; + ring->dequeue = ring->deq_seg->trbs; + } else { + ring->dequeue++; } - ring->deq_seg = ring->deq_seg->next; - ring->dequeue = ring->deq_seg->trbs; - next = ring->dequeue; - } + } while (last_trb(xhci, ring, ring->deq_seg, ring->dequeue)); + addr = (unsigned long long) xhci_trb_virt_to_dma(ring->deq_seg, ring->dequeue); } @@ -302,12 +311,123 @@ static int room_on_ring(struct xhci_hcd *xhci, struct xhci_ring *ring, /* Ring the host controller doorbell after placing a command on the ring */ void xhci_ring_cmd_db(struct xhci_hcd *xhci) { + if (!(xhci->cmd_ring_state & CMD_RING_STATE_RUNNING)) + return; + xhci_dbg(xhci, "// Ding dong!\n"); xhci_writel(xhci, DB_VALUE_HOST, &xhci->dba->doorbell[0]); /* Flush PCI posted writes */ xhci_readl(xhci, &xhci->dba->doorbell[0]); } +static int xhci_abort_cmd_ring(struct xhci_hcd *xhci) +{ + u64 temp_64; + int ret; + + xhci_dbg(xhci, "Abort command ring\n"); + + if (!(xhci->cmd_ring_state & CMD_RING_STATE_RUNNING)) { + xhci_dbg(xhci, "The command ring isn't running, " + "Have the command ring been stopped?\n"); + return 0; + } + + temp_64 = xhci_read_64(xhci, &xhci->op_regs->cmd_ring); + if (!(temp_64 & CMD_RING_RUNNING)) { + xhci_dbg(xhci, "Command ring had been stopped\n"); + return 0; + } + xhci->cmd_ring_state = CMD_RING_STATE_ABORTED; + xhci_write_64(xhci, temp_64 | CMD_RING_ABORT, + &xhci->op_regs->cmd_ring); + + /* Section 4.6.1.2 of xHCI 1.0 spec says software should + * time the completion od all xHCI commands, including + * the Command Abort operation. If software doesn't see + * CRR negated in a timely manner (e.g. longer than 5 + * seconds), then it should assume that the there are + * larger problems with the xHC and assert HCRST. + */ + ret = handshake(xhci, &xhci->op_regs->cmd_ring, + CMD_RING_RUNNING, 0, 5 * 1000 * 1000); + if (ret < 0) { + xhci_err(xhci, "Stopped the command ring failed, " + "maybe the host is dead\n"); + xhci->xhc_state |= XHCI_STATE_DYING; + xhci_quiesce(xhci); + xhci_halt(xhci); + return -ESHUTDOWN; + } + + return 0; +} + +static int xhci_queue_cd(struct xhci_hcd *xhci, + struct xhci_command *command, + union xhci_trb *cmd_trb) +{ + struct xhci_cd *cd; + cd = kzalloc(sizeof(struct xhci_cd), GFP_ATOMIC); + if (!cd) + return -ENOMEM; + INIT_LIST_HEAD(&cd->cancel_cmd_list); + + cd->command = command; + cd->cmd_trb = cmd_trb; + list_add_tail(&cd->cancel_cmd_list, &xhci->cancel_cmd_list); + + return 0; +} + +/* + * Cancel the command which has issue. + * + * Some commands may hang due to waiting for acknowledgement from + * usb device. It is outside of the xHC's ability to control and + * will cause the command ring is blocked. When it occurs software + * should intervene to recover the command ring. + * See Section 4.6.1.1 and 4.6.1.2 + */ +int xhci_cancel_cmd(struct xhci_hcd *xhci, struct xhci_command *command, + union xhci_trb *cmd_trb) +{ + int retval = 0; + unsigned long flags; + + spin_lock_irqsave(&xhci->lock, flags); + + if (xhci->xhc_state & XHCI_STATE_DYING) { + xhci_warn(xhci, "Abort the command ring," + " but the xHCI is dead.\n"); + retval = -ESHUTDOWN; + goto fail; + } + + /* queue the cmd desriptor to cancel_cmd_list */ + retval = xhci_queue_cd(xhci, command, cmd_trb); + if (retval) { + xhci_warn(xhci, "Queuing command descriptor failed.\n"); + goto fail; + } + + /* abort command ring */ + retval = xhci_abort_cmd_ring(xhci); + if (retval) { + xhci_err(xhci, "Abort command ring failed\n"); + if (unlikely(retval == -ESHUTDOWN)) { + spin_unlock_irqrestore(&xhci->lock, flags); + usb_hc_died(xhci_to_hcd(xhci)->primary_hcd); + xhci_dbg(xhci, "xHCI host controller is dead.\n"); + return retval; + } + } + +fail: + spin_unlock_irqrestore(&xhci->lock, flags); + return retval; +} + void xhci_ring_ep_doorbell(struct xhci_hcd *xhci, unsigned int slot_id, unsigned int ep_index, @@ -1037,6 +1157,20 @@ static void handle_reset_ep_completion(struct xhci_hcd *xhci, } } +/* Complete the command and detele it from the devcie's command queue. + */ +static void xhci_complete_cmd_in_cmd_wait_list(struct xhci_hcd *xhci, + struct xhci_command *command, u32 status) +{ + command->status = status; + list_del(&command->cmd_list); + if (command->completion) + complete(command->completion); + else + xhci_free_command(xhci, command); +} + + /* Check to see if a command in the device's command queue matches this one. * Signal the completion or free the command, and return 1. Return 0 if the * completed command isn't at the head of the command list. @@ -1055,15 +1189,155 @@ static int handle_cmd_in_cmd_wait_list(struct xhci_hcd *xhci, if (xhci->cmd_ring->dequeue != command->command_trb) return 0; - command->status = GET_COMP_CODE(le32_to_cpu(event->status)); - list_del(&command->cmd_list); - if (command->completion) - complete(command->completion); - else - xhci_free_command(xhci, command); + xhci_complete_cmd_in_cmd_wait_list(xhci, command, + GET_COMP_CODE(le32_to_cpu(event->status))); return 1; } +/* + * Finding the command trb need to be cancelled and modifying it to + * NO OP command. And if the command is in device's command wait + * list, finishing and freeing it. + * + * If we can't find the command trb, we think it had already been + * executed. + */ +static void xhci_cmd_to_noop(struct xhci_hcd *xhci, struct xhci_cd *cur_cd) +{ + struct xhci_segment *cur_seg; + union xhci_trb *cmd_trb; + u32 cycle_state; + + if (xhci->cmd_ring->dequeue == xhci->cmd_ring->enqueue) + return; + + /* find the current segment of command ring */ + cur_seg = find_trb_seg(xhci->cmd_ring->first_seg, + xhci->cmd_ring->dequeue, &cycle_state); + + if (!cur_seg) { + xhci_warn(xhci, "Command ring mismatch, dequeue = %p %llx (dma)\n", + xhci->cmd_ring->dequeue, + (unsigned long long) + xhci_trb_virt_to_dma(xhci->cmd_ring->deq_seg, + xhci->cmd_ring->dequeue)); + xhci_debug_ring(xhci, xhci->cmd_ring); + xhci_dbg_ring_ptrs(xhci, xhci->cmd_ring); + return; + } + + /* find the command trb matched by cd from command ring */ + for (cmd_trb = xhci->cmd_ring->dequeue; + cmd_trb != xhci->cmd_ring->enqueue; + next_trb(xhci, xhci->cmd_ring, &cur_seg, &cmd_trb)) { + /* If the trb is link trb, continue */ + if (TRB_TYPE_LINK_LE32(cmd_trb->generic.field[3])) + continue; + + if (cur_cd->cmd_trb == cmd_trb) { + + /* If the command in device's command list, we should + * finish it and free the command structure. + */ + if (cur_cd->command) + xhci_complete_cmd_in_cmd_wait_list(xhci, + cur_cd->command, COMP_CMD_STOP); + + /* get cycle state from the origin command trb */ + cycle_state = le32_to_cpu(cmd_trb->generic.field[3]) + & TRB_CYCLE; + + /* modify the command trb to NO OP command */ + cmd_trb->generic.field[0] = 0; + cmd_trb->generic.field[1] = 0; + cmd_trb->generic.field[2] = 0; + cmd_trb->generic.field[3] = cpu_to_le32( + TRB_TYPE(TRB_CMD_NOOP) | cycle_state); + break; + } + } +} + +static void xhci_cancel_cmd_in_cd_list(struct xhci_hcd *xhci) +{ + struct xhci_cd *cur_cd, *next_cd; + + if (list_empty(&xhci->cancel_cmd_list)) + return; + + list_for_each_entry_safe(cur_cd, next_cd, + &xhci->cancel_cmd_list, cancel_cmd_list) { + xhci_cmd_to_noop(xhci, cur_cd); + list_del(&cur_cd->cancel_cmd_list); + kfree(cur_cd); + } +} + +/* + * traversing the cancel_cmd_list. If the command descriptor according + * to cmd_trb is found, the function free it and return 1, otherwise + * return 0. + */ +static int xhci_search_cmd_trb_in_cd_list(struct xhci_hcd *xhci, + union xhci_trb *cmd_trb) +{ + struct xhci_cd *cur_cd, *next_cd; + + if (list_empty(&xhci->cancel_cmd_list)) + return 0; + + list_for_each_entry_safe(cur_cd, next_cd, + &xhci->cancel_cmd_list, cancel_cmd_list) { + if (cur_cd->cmd_trb == cmd_trb) { + if (cur_cd->command) + xhci_complete_cmd_in_cmd_wait_list(xhci, + cur_cd->command, COMP_CMD_STOP); + list_del(&cur_cd->cancel_cmd_list); + kfree(cur_cd); + return 1; + } + } + + return 0; +} + +/* + * If the cmd_trb_comp_code is COMP_CMD_ABORT, we just check whether the + * trb pointed by the command ring dequeue pointer is the trb we want to + * cancel or not. And if the cmd_trb_comp_code is COMP_CMD_STOP, we will + * traverse the cancel_cmd_list to trun the all of the commands according + * to command descriptor to NO-OP trb. + */ +static int handle_stopped_cmd_ring(struct xhci_hcd *xhci, + int cmd_trb_comp_code) +{ + int cur_trb_is_good = 0; + + /* Searching the cmd trb pointed by the command ring dequeue + * pointer in command descriptor list. If it is found, free it. + */ + cur_trb_is_good = xhci_search_cmd_trb_in_cd_list(xhci, + xhci->cmd_ring->dequeue); + + if (cmd_trb_comp_code == COMP_CMD_ABORT) + xhci->cmd_ring_state = CMD_RING_STATE_STOPPED; + else if (cmd_trb_comp_code == COMP_CMD_STOP) { + /* traversing the cancel_cmd_list and canceling + * the command according to command descriptor + */ + xhci_cancel_cmd_in_cd_list(xhci); + + xhci->cmd_ring_state = CMD_RING_STATE_RUNNING; + /* + * ring command ring doorbell again to restart the + * command ring + */ + if (xhci->cmd_ring->dequeue != xhci->cmd_ring->enqueue) + xhci_ring_cmd_db(xhci); + } + return cur_trb_is_good; +} + static void handle_cmd_completion(struct xhci_hcd *xhci, struct xhci_event_cmd *event) { @@ -1089,6 +1363,22 @@ static void handle_cmd_completion(struct xhci_hcd *xhci, xhci->error_bitmask |= 1 << 5; return; } + + if ((GET_COMP_CODE(le32_to_cpu(event->status)) == COMP_CMD_ABORT) || + (GET_COMP_CODE(le32_to_cpu(event->status)) == COMP_CMD_STOP)) { + /* If the return value is 0, we think the trb pointed by + * command ring dequeue pointer is a good trb. The good + * trb means we don't want to cancel the trb, but it have + * been stopped by host. So we should handle it normally. + * Otherwise, driver should invoke inc_deq() and return. + */ + if (handle_stopped_cmd_ring(xhci, + GET_COMP_CODE(le32_to_cpu(event->status)))) { + inc_deq(xhci, xhci->cmd_ring, false); + return; + } + } + switch (le32_to_cpu(xhci->cmd_ring->dequeue->generic.field[3]) & TRB_TYPE_BITMASK) { case TRB_TYPE(TRB_ENABLE_SLOT): @@ -1739,8 +2029,12 @@ static int process_isoc_td(struct xhci_hcd *xhci, struct xhci_td *td, /* handle completion code */ switch (trb_comp_code) { case COMP_SUCCESS: - frame->status = 0; - break; + if (TRB_LEN(le32_to_cpu(event->transfer_len)) == 0) { + frame->status = 0; + break; + } + if ((xhci->quirks & XHCI_TRUST_TX_LENGTH)) + trb_comp_code = COMP_SHORT_TX; case COMP_SHORT_TX: frame->status = td->urb->transfer_flags & URB_SHORT_NOT_OK ? -EREMOTEIO : 0; @@ -1756,6 +2050,7 @@ static int process_isoc_td(struct xhci_hcd *xhci, struct xhci_td *td, break; case COMP_DEV_ERR: case COMP_STALL: + case COMP_TX_ERR: frame->status = -EPROTO; skip_td = true; break; @@ -1838,13 +2133,16 @@ static int process_bulk_intr_td(struct xhci_hcd *xhci, struct xhci_td *td, switch (trb_comp_code) { case COMP_SUCCESS: /* Double check that the HW transferred everything. */ - if (event_trb != td->last_trb) { + if (event_trb != td->last_trb || + TRB_LEN(le32_to_cpu(event->transfer_len)) != 0) { xhci_warn(xhci, "WARN Successful completion " "on short TX\n"); if (td->urb->transfer_flags & URB_SHORT_NOT_OK) *status = -EREMOTEIO; else *status = 0; + if ((xhci->quirks & XHCI_TRUST_TX_LENGTH)) + trb_comp_code = COMP_SHORT_TX; } else { *status = 0; } @@ -1985,6 +2283,13 @@ static int handle_tx_event(struct xhci_hcd *xhci, * transfer type */ case COMP_SUCCESS: + if (TRB_LEN(le32_to_cpu(event->transfer_len)) == 0) + break; + if (xhci->quirks & XHCI_TRUST_TX_LENGTH) + trb_comp_code = COMP_SHORT_TX; + else + xhci_warn(xhci, "WARN Successful completion on short TX: " + "needs XHCI_TRUST_TX_LENGTH quirk?\n"); case COMP_SHORT_TX: break; case COMP_STOP: @@ -3341,7 +3646,9 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, } else { td->last_trb = ep_ring->enqueue; field |= TRB_IOC; - if (xhci->hci_version == 0x100) { + if (xhci->hci_version == 0x100 && + !(xhci->quirks & + XHCI_AVOID_BEI)) { /* Set BEI bit except for the last td */ if (i < num_tds - 1) field |= TRB_BEI; diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index dbba936..4864b25 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -51,7 +51,7 @@ MODULE_PARM_DESC(link_quirk, "Don't clear the chain bit on a link TRB"); * handshake done). There are two failure modes: "usec" have passed (major * hardware flakeout), or the register reads as all-ones (hardware removed). */ -static int handshake(struct xhci_hcd *xhci, void __iomem *ptr, +int handshake(struct xhci_hcd *xhci, void __iomem *ptr, u32 mask, u32 done, int usec) { u32 result; @@ -104,8 +104,10 @@ int xhci_halt(struct xhci_hcd *xhci) ret = handshake(xhci, &xhci->op_regs->status, STS_HALT, STS_HALT, XHCI_MAX_HALT_USEC); - if (!ret) + if (!ret) { xhci->xhc_state |= XHCI_STATE_HALTED; + xhci->cmd_ring_state = CMD_RING_STATE_STOPPED; + } return ret; } @@ -163,7 +165,7 @@ int xhci_reset(struct xhci_hcd *xhci) xhci_writel(xhci, command, &xhci->op_regs->command); ret = handshake(xhci, &xhci->op_regs->command, - CMD_RESET, 0, 250 * 1000); + CMD_RESET, 0, 10 * 1000 * 1000); if (ret) return ret; @@ -172,7 +174,8 @@ int xhci_reset(struct xhci_hcd *xhci) * xHCI cannot write to any doorbells or operational registers other * than status until the "Controller Not Ready" flag is cleared. */ - return handshake(xhci, &xhci->op_regs->status, STS_CNR, 0, 250 * 1000); + return handshake(xhci, &xhci->op_regs->status, + STS_CNR, 0, 10 * 1000 * 1000); } /* @@ -389,6 +392,7 @@ static int xhci_run_finished(struct xhci_hcd *xhci) return -ENODEV; } xhci->shared_hcd->state = HC_STATE_RUNNING; + xhci->cmd_ring_state = CMD_RING_STATE_RUNNING; if (xhci->quirks & XHCI_NEC_HOST) xhci_ring_cmd_db(xhci); @@ -593,6 +597,9 @@ void xhci_shutdown(struct usb_hcd *hcd) { struct xhci_hcd *xhci = hcd_to_xhci(hcd); + if (xhci->quirks & XHCI_SPURIOUS_REBOOT) + usb_disable_xhci_ports(to_pci_dev(hcd->self.controller)); + spin_lock_irq(&xhci->lock); xhci_halt(xhci); spin_unlock_irq(&xhci->lock); @@ -716,7 +723,7 @@ int xhci_suspend(struct xhci_hcd *xhci) command &= ~CMD_RUN; xhci_writel(xhci, command, &xhci->op_regs->command); if (handshake(xhci, &xhci->op_regs->status, - STS_HALT, STS_HALT, 100*100)) { + STS_HALT, STS_HALT, XHCI_MAX_HALT_USEC)) { xhci_warn(xhci, "WARN: xHC CMD_RUN timeout\n"); spin_unlock_irq(&xhci->lock); return -ETIMEDOUT; @@ -730,8 +737,8 @@ int xhci_suspend(struct xhci_hcd *xhci) command = xhci_readl(xhci, &xhci->op_regs->command); command |= CMD_CSS; xhci_writel(xhci, command, &xhci->op_regs->command); - if (handshake(xhci, &xhci->op_regs->status, STS_SAVE, 0, 10*100)) { - xhci_warn(xhci, "WARN: xHC CMD_CSS timeout\n"); + if (handshake(xhci, &xhci->op_regs->status, STS_SAVE, 0, 10 * 1000)) { + xhci_warn(xhci, "WARN: xHC save state timeout\n"); spin_unlock_irq(&xhci->lock); return -ETIMEDOUT; } @@ -786,8 +793,8 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated) command |= CMD_CRS; xhci_writel(xhci, command, &xhci->op_regs->command); if (handshake(xhci, &xhci->op_regs->status, - STS_RESTORE, 0, 10*100)) { - xhci_dbg(xhci, "WARN: xHC CMD_CSS timeout\n"); + STS_RESTORE, 0, 10 * 1000)) { + xhci_warn(xhci, "WARN: xHC restore state timeout\n"); spin_unlock_irq(&xhci->lock); return -ETIMEDOUT; } @@ -1771,6 +1778,7 @@ static int xhci_configure_endpoint(struct xhci_hcd *xhci, struct completion *cmd_completion; u32 *cmd_status; struct xhci_virt_device *virt_dev; + union xhci_trb *cmd_trb; spin_lock_irqsave(&xhci->lock, flags); virt_dev = xhci->devs[udev->slot_id]; @@ -1813,6 +1821,7 @@ static int xhci_configure_endpoint(struct xhci_hcd *xhci, } init_completion(cmd_completion); + cmd_trb = xhci->cmd_ring->dequeue; if (!ctx_change) ret = xhci_queue_configure_endpoint(xhci, in_ctx->dma, udev->slot_id, must_succeed); @@ -1834,14 +1843,17 @@ static int xhci_configure_endpoint(struct xhci_hcd *xhci, /* Wait for the configure endpoint command to complete */ timeleft = wait_for_completion_interruptible_timeout( cmd_completion, - USB_CTRL_SET_TIMEOUT); + XHCI_CMD_DEFAULT_TIMEOUT); if (timeleft <= 0) { xhci_warn(xhci, "%s while waiting for %s command\n", timeleft == 0 ? "Timeout" : "Signal", ctx_change == 0 ? "configure endpoint" : "evaluate context"); - /* FIXME cancel the configure endpoint command */ + /* cancel the configure endpoint command */ + ret = xhci_cancel_cmd(xhci, command, cmd_trb); + if (ret < 0) + return ret; return -ETIME; } @@ -2774,8 +2786,10 @@ int xhci_alloc_dev(struct usb_hcd *hcd, struct usb_device *udev) unsigned long flags; int timeleft; int ret; + union xhci_trb *cmd_trb; spin_lock_irqsave(&xhci->lock, flags); + cmd_trb = xhci->cmd_ring->dequeue; ret = xhci_queue_slot_control(xhci, TRB_ENABLE_SLOT, 0); if (ret) { spin_unlock_irqrestore(&xhci->lock, flags); @@ -2787,12 +2801,12 @@ int xhci_alloc_dev(struct usb_hcd *hcd, struct usb_device *udev) /* XXX: how much time for xHC slot assignment? */ timeleft = wait_for_completion_interruptible_timeout(&xhci->addr_dev, - USB_CTRL_SET_TIMEOUT); + XHCI_CMD_DEFAULT_TIMEOUT); if (timeleft <= 0) { xhci_warn(xhci, "%s while waiting for a slot\n", timeleft == 0 ? "Timeout" : "Signal"); - /* FIXME cancel the enable slot request */ - return 0; + /* cancel the enable slot request */ + return xhci_cancel_cmd(xhci, NULL, cmd_trb); } if (!xhci->slot_id) { @@ -2853,6 +2867,7 @@ int xhci_address_device(struct usb_hcd *hcd, struct usb_device *udev) struct xhci_slot_ctx *slot_ctx; struct xhci_input_control_ctx *ctrl_ctx; u64 temp_64; + union xhci_trb *cmd_trb; if (!udev->slot_id) { xhci_dbg(xhci, "Bad Slot ID %d\n", udev->slot_id); @@ -2891,6 +2906,7 @@ int xhci_address_device(struct usb_hcd *hcd, struct usb_device *udev) xhci_dbg_ctx(xhci, virt_dev->in_ctx, 2); spin_lock_irqsave(&xhci->lock, flags); + cmd_trb = xhci->cmd_ring->dequeue; ret = xhci_queue_address_device(xhci, virt_dev->in_ctx->dma, udev->slot_id); if (ret) { @@ -2903,7 +2919,7 @@ int xhci_address_device(struct usb_hcd *hcd, struct usb_device *udev) /* ctrl tx can take up to 5 sec; XXX: need more time for xHC? */ timeleft = wait_for_completion_interruptible_timeout(&xhci->addr_dev, - USB_CTRL_SET_TIMEOUT); + XHCI_CMD_DEFAULT_TIMEOUT); /* FIXME: From section 4.3.4: "Software shall be responsible for timing * the SetAddress() "recovery interval" required by USB and aborting the * command on a timeout. @@ -2911,7 +2927,10 @@ int xhci_address_device(struct usb_hcd *hcd, struct usb_device *udev) if (timeleft <= 0) { xhci_warn(xhci, "%s while waiting for a slot\n", timeleft == 0 ? "Timeout" : "Signal"); - /* FIXME cancel the address device command */ + /* cancel the address device command */ + ret = xhci_cancel_cmd(xhci, NULL, cmd_trb); + if (ret < 0) + return ret; return -ETIME; } diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index dfd260a..1d72895 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1070,6 +1070,9 @@ union xhci_trb { #define TRB_MFINDEX_WRAP 39 /* TRB IDs 40-47 reserved, 48-63 is vendor-defined */ +#define TRB_TYPE_LINK_LE32(x) (((x) & cpu_to_le32(TRB_TYPE_BITMASK)) == \ + cpu_to_le32(TRB_TYPE(TRB_LINK))) + /* Nec vendor-specific command completion event. */ #define TRB_NEC_CMD_COMP 48 /* Get NEC firmware revision. */ @@ -1111,6 +1114,16 @@ struct xhci_td { union xhci_trb *last_trb; }; +/* xHCI command default timeout value */ +#define XHCI_CMD_DEFAULT_TIMEOUT (5 * HZ) + +/* command descriptor */ +struct xhci_cd { + struct list_head cancel_cmd_list; + struct xhci_command *command; + union xhci_trb *cmd_trb; +}; + struct xhci_dequeue_state { struct xhci_segment *new_deq_seg; union xhci_trb *new_deq_ptr; @@ -1252,6 +1265,11 @@ struct xhci_hcd { /* data structures */ struct xhci_device_context_array *dcbaa; struct xhci_ring *cmd_ring; + unsigned int cmd_ring_state; +#define CMD_RING_STATE_RUNNING (1 << 0) +#define CMD_RING_STATE_ABORTED (1 << 1) +#define CMD_RING_STATE_STOPPED (1 << 2) + struct list_head cancel_cmd_list; unsigned int cmd_ring_reserved_trbs; struct xhci_ring *event_ring; struct xhci_erst erst; @@ -1315,6 +1333,9 @@ struct xhci_hcd { #define XHCI_BROKEN_MSI (1 << 6) #define XHCI_RESET_ON_RESUME (1 << 7) #define XHCI_AMD_0x96_HOST (1 << 9) +#define XHCI_TRUST_TX_LENGTH (1 << 10) +#define XHCI_SPURIOUS_REBOOT (1 << 13) +#define XHCI_AVOID_BEI (1 << 15) unsigned int num_active_eps; unsigned int limit_active_eps; /* There are two roothubs to keep track of bus suspend info for */ @@ -1483,6 +1504,8 @@ void xhci_unregister_pci(void); #endif /* xHCI host controller glue */ +int handshake(struct xhci_hcd *xhci, void __iomem *ptr, + u32 mask, u32 done, int usec); void xhci_quiesce(struct xhci_hcd *xhci); int xhci_halt(struct xhci_hcd *xhci); int xhci_reset(struct xhci_hcd *xhci); @@ -1565,6 +1588,8 @@ void xhci_queue_config_ep_quirk(struct xhci_hcd *xhci, unsigned int slot_id, unsigned int ep_index, struct xhci_dequeue_state *deq_state); void xhci_stop_endpoint_command_watchdog(unsigned long arg); +int xhci_cancel_cmd(struct xhci_hcd *xhci, struct xhci_command *command, + union xhci_trb *cmd_trb); void xhci_ring_ep_doorbell(struct xhci_hcd *xhci, unsigned int slot_id, unsigned int ep_index, unsigned int stream_id); diff --git a/drivers/usb/misc/emi62.c b/drivers/usb/misc/emi62.c index fc15ad4..723e833 100644 --- a/drivers/usb/misc/emi62.c +++ b/drivers/usb/misc/emi62.c @@ -259,7 +259,7 @@ wraperr: return err; } -static const struct usb_device_id id_table[] __devinitconst = { +static const struct usb_device_id id_table[] = { { USB_DEVICE(EMI62_VENDOR_ID, EMI62_PRODUCT_ID) }, { } /* Terminating entry */ }; diff --git a/drivers/usb/misc/usbtest.c b/drivers/usb/misc/usbtest.c index bb10846..5707f56 100644 --- a/drivers/usb/misc/usbtest.c +++ b/drivers/usb/misc/usbtest.c @@ -1023,7 +1023,10 @@ test_ctrl_queue(struct usbtest_dev *dev, struct usbtest_param *param) case 13: /* short read, resembling case 10 */ req.wValue = cpu_to_le16((USB_DT_CONFIG << 8) | 0); /* last data packet "should" be DATA1, not DATA0 */ - len = 1024 - udev->descriptor.bMaxPacketSize0; + if (udev->speed == USB_SPEED_SUPER) + len = 1024 - 512; + else + len = 1024 - udev->descriptor.bMaxPacketSize0; expected = -EREMOTEIO; break; case 14: /* short read; try to fill the last packet */ @@ -1382,11 +1385,15 @@ static int test_halt(struct usbtest_dev *tdev, int ep, struct urb *urb) static int halt_simple(struct usbtest_dev *dev) { - int ep; - int retval = 0; - struct urb *urb; + int ep; + int retval = 0; + struct urb *urb; + struct usb_device *udev = testdev_to_usbdev(dev); - urb = simple_alloc_urb(testdev_to_usbdev(dev), 0, 512); + if (udev->speed == USB_SPEED_SUPER) + urb = simple_alloc_urb(udev, 0, 1024); + else + urb = simple_alloc_urb(udev, 0, 512); if (urb == NULL) return -ENOMEM; diff --git a/drivers/usb/notify/Kconfig b/drivers/usb/notify/Kconfig new file mode 100755 index 0000000..159affa --- /dev/null +++ b/drivers/usb/notify/Kconfig @@ -0,0 +1,11 @@ +# +# USB Host notify configuration +# + +config USB_HOST_NOTIFY + boolean "USB Host notify Driver" + depends on USB + help + Android framework needs uevents for usb host operation. + Host notify Driver serves uevent format + that is used by usb host or otg.
\ No newline at end of file diff --git a/drivers/usb/notify/Makefile b/drivers/usb/notify/Makefile new file mode 100755 index 0000000..996eaa3 --- /dev/null +++ b/drivers/usb/notify/Makefile @@ -0,0 +1,4 @@ + +# host notify driver +obj-y += host_notify_class.o + diff --git a/drivers/usb/notify/host_notify_class.c b/drivers/usb/notify/host_notify_class.c new file mode 100755 index 0000000..09ad529 --- /dev/null +++ b/drivers/usb/notify/host_notify_class.c @@ -0,0 +1,262 @@ +/* + * drivers/usb/notify/host_notify_class.c + * + * Copyright (C) 2011 Samsung, Inc. + * Author: Dongrak Shin <dongrak.shin@samsung.com> + * +*/ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/err.h> +#include <linux/host_notify.h> + +struct notify_data { + struct class *host_notify_class; + atomic_t device_count; +}; + +static struct notify_data host_notify; + +static ssize_t mode_show( + struct device *dev, struct device_attribute *attr, char *buf) +{ + struct host_notify_dev *ndev = (struct host_notify_dev *) + dev_get_drvdata(dev); + char *mode; + + switch (ndev->mode) { + case NOTIFY_HOST_MODE: + mode = "HOST"; + break; + case NOTIFY_PERIPHERAL_MODE: + mode = "PERIPHERAL"; + break; + case NOTIFY_TEST_MODE: + mode = "TEST"; + break; + case NOTIFY_NONE_MODE: + default: + mode = "NONE"; + break; + } + + return sprintf(buf, "%s\n", mode); +} + +static ssize_t mode_store( + struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct host_notify_dev *ndev = (struct host_notify_dev *) + dev_get_drvdata(dev); + + char *mode; + size_t ret = -ENOMEM; + + mode = kzalloc(size+1, GFP_KERNEL); + if (!mode) + goto error; + + sscanf(buf, "%s", mode); + + if (ndev->set_mode) { + if (!strcmp(mode, "HOST")) + ndev->set_mode(NOTIFY_SET_ON); + else if (!strcmp(mode, "NONE")) + ndev->set_mode(NOTIFY_SET_OFF); + printk(KERN_INFO "host_notify: set mode %s\n", mode); + } + ret = size; + kfree(mode); +error: + return ret; +} + +static ssize_t booster_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct host_notify_dev *ndev = (struct host_notify_dev *) + dev_get_drvdata(dev); + char *booster; + + switch (ndev->booster) { + case NOTIFY_POWER_ON: + booster = "ON"; + break; + case NOTIFY_POWER_OFF: + default: + booster = "OFF"; + break; + } + + return sprintf(buf, "%s\n", booster); +} + +static ssize_t booster_store( + struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct host_notify_dev *ndev = (struct host_notify_dev *) + dev_get_drvdata(dev); + + char *booster; + size_t ret = -ENOMEM; + + booster = kzalloc(size+1, GFP_KERNEL); + if (!booster) + goto error; + + sscanf(buf, "%s", booster); + + if (ndev->set_booster) { + if (!strcmp(booster, "ON")) { + ndev->set_booster(NOTIFY_SET_ON); + ndev->mode = NOTIFY_TEST_MODE; + } else if (!strcmp(booster, "OFF")) { + ndev->set_booster(NOTIFY_SET_OFF); + ndev->mode = NOTIFY_NONE_MODE; + } + printk(KERN_INFO "host_notify: set booster %s\n", booster); + } + ret = size; + kfree(booster); +error: + return ret; +} + +static DEVICE_ATTR(mode, S_IRUGO | S_IWUSR | S_IWGRP, mode_show, mode_store); +static DEVICE_ATTR(booster, S_IRUGO | S_IWUSR | S_IWGRP, + booster_show, booster_store); + +static struct attribute *host_notify_attrs[] = { + &dev_attr_mode.attr, + &dev_attr_booster.attr, + NULL, +}; + +static struct attribute_group host_notify_attr_grp = { + .attrs = host_notify_attrs, +}; + +void host_state_notify(struct host_notify_dev *ndev, int state) +{ + printk(KERN_INFO "host_notify: ndev name=%s: from state=%d -> to state=%d\n", + ndev->name, ndev->state, state); + if (ndev->state != state) { + ndev->state = state; + kobject_uevent(&ndev->dev->kobj, KOBJ_CHANGE); + } +} +EXPORT_SYMBOL_GPL(host_state_notify); + +static int +host_notify_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct host_notify_dev *ndev = (struct host_notify_dev *) + dev_get_drvdata(dev); + char *state; + + if (!ndev) { + /* this happens when the device is first created */ + return 0; + } + switch (ndev->state) { + case NOTIFY_HOST_ADD: + state = "ADD"; + break; + case NOTIFY_HOST_REMOVE: + state = "REMOVE"; + break; + case NOTIFY_HOST_OVERCURRENT: + state = "OVERCURRENT"; + break; + case NOTIFY_HOST_LOWBATT: + state = "LOWBATT"; + break; + case NOTIFY_HOST_UNKNOWN: + state = "UNKNOWN"; + break; + case NOTIFY_HOST_NONE: + default: + return 0; + } + if (add_uevent_var(env, "DEVNAME=%s", ndev->dev->kobj.name)) + return -ENOMEM; + if (add_uevent_var(env, "STATE=%s", state)) + return -ENOMEM; + return 0; +} + +static int create_notify_class(void) +{ + if (!host_notify.host_notify_class) { + host_notify.host_notify_class + = class_create(THIS_MODULE, "host_notify"); + if (IS_ERR(host_notify.host_notify_class)) + return PTR_ERR(host_notify.host_notify_class); + atomic_set(&host_notify.device_count, 0); + host_notify.host_notify_class->dev_uevent = host_notify_uevent; + } + + return 0; +} + +int host_notify_dev_register(struct host_notify_dev *ndev) +{ + int ret; + + if (!host_notify.host_notify_class) { + ret = create_notify_class(); + if (ret < 0) + return ret; + } + + ndev->index = atomic_inc_return(&host_notify.device_count); + ndev->dev = device_create(host_notify.host_notify_class, NULL, + MKDEV(0, ndev->index), NULL, ndev->name); + if (IS_ERR(ndev->dev)) + return PTR_ERR(ndev->dev); + + ret = sysfs_create_group(&ndev->dev->kobj, &host_notify_attr_grp); + if (ret < 0) { + device_destroy(host_notify.host_notify_class, + MKDEV(0, ndev->index)); + return ret; + } + + dev_set_drvdata(ndev->dev, ndev); + ndev->state = 0; + return 0; +} +EXPORT_SYMBOL_GPL(host_notify_dev_register); + +void host_notify_dev_unregister(struct host_notify_dev *ndev) +{ + ndev->state = NOTIFY_HOST_NONE; + sysfs_remove_group(&ndev->dev->kobj, &host_notify_attr_grp); + device_destroy(host_notify.host_notify_class, MKDEV(0, ndev->index)); + dev_set_drvdata(ndev->dev, NULL); +} +EXPORT_SYMBOL_GPL(host_notify_dev_unregister); + +static int __init notify_class_init(void) +{ + return create_notify_class(); +} + +static void __exit notify_class_exit(void) +{ + class_destroy(host_notify.host_notify_class); +} + +module_init(notify_class_init); +module_exit(notify_class_exit); + +MODULE_AUTHOR("Dongrak Shin <dongrak.shin@samsung.com>"); +MODULE_DESCRIPTION("Usb host notify driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index f2c57e0..35e6b5f 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -82,6 +82,7 @@ static const struct usb_device_id id_table[] = { { USB_DEVICE(0x10C4, 0x8066) }, /* Argussoft In-System Programmer */ { USB_DEVICE(0x10C4, 0x806F) }, /* IMS USB to RS422 Converter Cable */ { USB_DEVICE(0x10C4, 0x807A) }, /* Crumb128 board */ + { USB_DEVICE(0x10C4, 0x80C4) }, /* Cygnal Integrated Products, Inc., Optris infrared thermometer */ { USB_DEVICE(0x10C4, 0x80CA) }, /* Degree Controls Inc */ { USB_DEVICE(0x10C4, 0x80DD) }, /* Tracient RFID */ { USB_DEVICE(0x10C4, 0x80F6) }, /* Suunto sports instrument */ @@ -92,6 +93,7 @@ static const struct usb_device_id id_table[] = { { USB_DEVICE(0x10C4, 0x814B) }, /* West Mountain Radio RIGtalk */ { USB_DEVICE(0x10C4, 0x8156) }, /* B&G H3000 link cable */ { USB_DEVICE(0x10C4, 0x815E) }, /* Helicomm IP-Link 1220-DVM */ + { USB_DEVICE(0x10C4, 0x815F) }, /* Timewave HamLinkUSB */ { USB_DEVICE(0x10C4, 0x818B) }, /* AVIT Research USB to TTL */ { USB_DEVICE(0x10C4, 0x819F) }, /* MJS USB Toslink Switcher */ { USB_DEVICE(0x10C4, 0x81A6) }, /* ThinkOptics WavIt */ @@ -133,7 +135,13 @@ static const struct usb_device_id id_table[] = { { USB_DEVICE(0x10CE, 0xEA6A) }, /* Silicon Labs MobiData GPRS USB Modem 100EU */ { USB_DEVICE(0x13AD, 0x9999) }, /* Baltech card reader */ { USB_DEVICE(0x1555, 0x0004) }, /* Owen AC4 USB-RS485 Converter */ + { USB_DEVICE(0x166A, 0x0201) }, /* Clipsal 5500PACA C-Bus Pascal Automation Controller */ + { USB_DEVICE(0x166A, 0x0301) }, /* Clipsal 5800PC C-Bus Wireless PC Interface */ { USB_DEVICE(0x166A, 0x0303) }, /* Clipsal 5500PCU C-Bus USB interface */ + { USB_DEVICE(0x166A, 0x0304) }, /* Clipsal 5000CT2 C-Bus Black and White Touchscreen */ + { USB_DEVICE(0x166A, 0x0305) }, /* Clipsal C-5000CT2 C-Bus Spectrum Colour Touchscreen */ + { USB_DEVICE(0x166A, 0x0401) }, /* Clipsal L51xx C-Bus Architectural Dimmer */ + { USB_DEVICE(0x166A, 0x0101) }, /* Clipsal 5560884 C-Bus Multi-room Audio Matrix Switcher */ { USB_DEVICE(0x16D6, 0x0001) }, /* Jablotron serial interface */ { USB_DEVICE(0x16DC, 0x0010) }, /* W-IE-NE-R Plein & Baus GmbH PL512 Power Supply */ { USB_DEVICE(0x16DC, 0x0011) }, /* W-IE-NE-R Plein & Baus GmbH RCM Remote Control for MARATON Power Supply */ @@ -145,7 +153,11 @@ static const struct usb_device_id id_table[] = { { USB_DEVICE(0x1843, 0x0200) }, /* Vaisala USB Instrument Cable */ { USB_DEVICE(0x18EF, 0xE00F) }, /* ELV USB-I2C-Interface */ { USB_DEVICE(0x1BE3, 0x07A6) }, /* WAGO 750-923 USB Service Cable */ + { USB_DEVICE(0x1E29, 0x0102) }, /* Festo CPX-USB */ + { USB_DEVICE(0x1E29, 0x0501) }, /* Festo CMSP */ { USB_DEVICE(0x3195, 0xF190) }, /* Link Instruments MSO-19 */ + { USB_DEVICE(0x3195, 0xF280) }, /* Link Instruments MSO-28 */ + { USB_DEVICE(0x3195, 0xF281) }, /* Link Instruments MSO-28 */ { USB_DEVICE(0x413C, 0x9500) }, /* DW700 GPS USB interface */ { } /* Terminating Entry */ }; diff --git a/drivers/usb/serial/ftdi_sio.c b/drivers/usb/serial/ftdi_sio.c index 60d7b1e..00f1bf5 100644 --- a/drivers/usb/serial/ftdi_sio.c +++ b/drivers/usb/serial/ftdi_sio.c @@ -582,6 +582,8 @@ static struct usb_device_id id_table_combined [] = { { USB_DEVICE(FTDI_VID, FTDI_IBS_PEDO_PID) }, { USB_DEVICE(FTDI_VID, FTDI_IBS_PROD_PID) }, { USB_DEVICE(FTDI_VID, FTDI_TAVIR_STK500_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_TIAO_UMPA_PID), + .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, /* * ELV devices: */ @@ -702,6 +704,7 @@ static struct usb_device_id id_table_combined [] = { { USB_DEVICE(FTDI_VID, FTDI_PCDJ_DAC2_PID) }, { USB_DEVICE(FTDI_VID, FTDI_RRCIRKITS_LOCOBUFFER_PID) }, { USB_DEVICE(FTDI_VID, FTDI_ASK_RDR400_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_NZR_SEM_USB_PID) }, { USB_DEVICE(ICOM_VID, ICOM_ID_1_PID) }, { USB_DEVICE(ICOM_VID, ICOM_OPC_U_UC_PID) }, { USB_DEVICE(ICOM_VID, ICOM_ID_RP2C1_PID) }, @@ -735,6 +738,7 @@ static struct usb_device_id id_table_combined [] = { { USB_DEVICE(TELLDUS_VID, TELLDUS_TELLSTICK_PID) }, { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_SERIAL_VX7_PID) }, { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_CT29B_PID) }, + { USB_DEVICE(RTSYSTEMS_VID, RTSYSTEMS_RTS01_PID) }, { USB_DEVICE(FTDI_VID, FTDI_MAXSTREAM_PID) }, { USB_DEVICE(FTDI_VID, FTDI_PHI_FISCO_PID) }, { USB_DEVICE(TML_VID, TML_USB_SERIAL_PID) }, @@ -801,12 +805,33 @@ static struct usb_device_id id_table_combined [] = { .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, { USB_DEVICE(ADI_VID, ADI_GNICEPLUS_PID), .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, - { USB_DEVICE(MICROCHIP_VID, MICROCHIP_USB_BOARD_PID) }, + { USB_DEVICE_AND_INTERFACE_INFO(MICROCHIP_VID, MICROCHIP_USB_BOARD_PID, + USB_CLASS_VENDOR_SPEC, + USB_SUBCLASS_VENDOR_SPEC, 0x00) }, { USB_DEVICE(JETI_VID, JETI_SPC1201_PID) }, { USB_DEVICE(MARVELL_VID, MARVELL_SHEEVAPLUG_PID), .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, { USB_DEVICE(LARSENBRUSGAARD_VID, LB_ALTITRACK_PID) }, { USB_DEVICE(GN_OTOMETRICS_VID, AURICAL_USB_PID) }, + { USB_DEVICE(FTDI_VID, PI_C865_PID) }, + { USB_DEVICE(FTDI_VID, PI_C857_PID) }, + { USB_DEVICE(PI_VID, PI_C866_PID) }, + { USB_DEVICE(PI_VID, PI_C663_PID) }, + { USB_DEVICE(PI_VID, PI_C725_PID) }, + { USB_DEVICE(PI_VID, PI_E517_PID) }, + { USB_DEVICE(PI_VID, PI_C863_PID) }, + { USB_DEVICE(PI_VID, PI_E861_PID) }, + { USB_DEVICE(PI_VID, PI_C867_PID) }, + { USB_DEVICE(PI_VID, PI_E609_PID) }, + { USB_DEVICE(PI_VID, PI_E709_PID) }, + { USB_DEVICE(PI_VID, PI_100F_PID) }, + { USB_DEVICE(PI_VID, PI_1011_PID) }, + { USB_DEVICE(PI_VID, PI_1012_PID) }, + { USB_DEVICE(PI_VID, PI_1013_PID) }, + { USB_DEVICE(PI_VID, PI_1014_PID) }, + { USB_DEVICE(PI_VID, PI_1015_PID) }, + { USB_DEVICE(PI_VID, PI_1016_PID) }, + { USB_DEVICE(KONDO_VID, KONDO_USB_SERIAL_PID) }, { USB_DEVICE(BAYER_VID, BAYER_CONTOUR_CABLE_PID) }, { USB_DEVICE(FTDI_VID, MARVELL_OPENRD_PID), .driver_info = (kernel_ulong_t)&ftdi_jtag_quirk }, diff --git a/drivers/usb/serial/ftdi_sio_ids.h b/drivers/usb/serial/ftdi_sio_ids.h index c6dd18e..7b5eb74 100644 --- a/drivers/usb/serial/ftdi_sio_ids.h +++ b/drivers/usb/serial/ftdi_sio_ids.h @@ -75,6 +75,9 @@ #define FTDI_OPENDCC_GATEWAY_PID 0xBFDB #define FTDI_OPENDCC_GBM_PID 0xBFDC +/* NZR SEM 16+ USB (http://www.nzr.de) */ +#define FTDI_NZR_SEM_USB_PID 0xC1E0 /* NZR SEM-LOG16+ */ + /* * RR-CirKits LocoBuffer USB (http://www.rr-cirkits.com) */ @@ -514,6 +517,11 @@ */ #define FTDI_TAVIR_STK500_PID 0xFA33 /* STK500 AVR programmer */ +/* + * TIAO product ids (FTDI_VID) + * http://www.tiaowiki.com/w/Main_Page + */ +#define FTDI_TIAO_UMPA_PID 0x8a98 /* TIAO/DIYGADGET USB Multi-Protocol Adapter */ /********************************/ @@ -539,7 +547,10 @@ /* * Microchip Technology, Inc. * - * MICROCHIP_VID (0x04D8) and MICROCHIP_USB_BOARD_PID (0x000A) are also used by: + * MICROCHIP_VID (0x04D8) and MICROCHIP_USB_BOARD_PID (0x000A) are + * used by single function CDC ACM class based firmware demo + * applications. The VID/PID has also been used in firmware + * emulating FTDI serial chips by: * Hornby Elite - Digital Command Control Console * http://www.hornby.com/hornby-dcc/controllers/ */ @@ -784,6 +795,41 @@ #define RTSYSTEMS_VID 0x2100 /* Vendor ID */ #define RTSYSTEMS_SERIAL_VX7_PID 0x9e52 /* Serial converter for VX-7 Radios using FT232RL */ #define RTSYSTEMS_CT29B_PID 0x9e54 /* CT29B Radio Cable */ +#define RTSYSTEMS_RTS01_PID 0x9e57 /* USB-RTS01 Radio Cable */ + + +/* + * Physik Instrumente + * http://www.physikinstrumente.com/en/products/ + */ +/* These two devices use the VID of FTDI */ +#define PI_C865_PID 0xe0a0 /* PI C-865 Piezomotor Controller */ +#define PI_C857_PID 0xe0a1 /* PI Encoder Trigger Box */ + +#define PI_VID 0x1a72 /* Vendor ID */ +#define PI_C866_PID 0x1000 /* PI C-866 Piezomotor Controller */ +#define PI_C663_PID 0x1001 /* PI C-663 Mercury-Step */ +#define PI_C725_PID 0x1002 /* PI C-725 Piezomotor Controller */ +#define PI_E517_PID 0x1005 /* PI E-517 Digital Piezo Controller Operation Module */ +#define PI_C863_PID 0x1007 /* PI C-863 */ +#define PI_E861_PID 0x1008 /* PI E-861 Piezomotor Controller */ +#define PI_C867_PID 0x1009 /* PI C-867 Piezomotor Controller */ +#define PI_E609_PID 0x100D /* PI E-609 Digital Piezo Controller */ +#define PI_E709_PID 0x100E /* PI E-709 Digital Piezo Controller */ +#define PI_100F_PID 0x100F /* PI Digital Piezo Controller */ +#define PI_1011_PID 0x1011 /* PI Digital Piezo Controller */ +#define PI_1012_PID 0x1012 /* PI Motion Controller */ +#define PI_1013_PID 0x1013 /* PI Motion Controller */ +#define PI_1014_PID 0x1014 /* PI Device */ +#define PI_1015_PID 0x1015 /* PI Device */ +#define PI_1016_PID 0x1016 /* PI Digital Servo Module */ + +/* + * Kondo Kagaku Co.Ltd. + * http://www.kondo-robot.com/EN + */ +#define KONDO_VID 0x165c +#define KONDO_USB_SERIAL_PID 0x0002 /* * Bayer Ascensia Contour blood glucose meter USB-converter cable. diff --git a/drivers/usb/serial/mct_u232.c b/drivers/usb/serial/mct_u232.c index ba0d287..42de17b 100644 --- a/drivers/usb/serial/mct_u232.c +++ b/drivers/usb/serial/mct_u232.c @@ -359,13 +359,16 @@ static int mct_u232_set_modem_ctrl(struct usb_serial *serial, MCT_U232_SET_REQUEST_TYPE, 0, 0, buf, MCT_U232_SET_MODEM_CTRL_SIZE, WDR_TIMEOUT); - if (rc < 0) - dev_err(&serial->dev->dev, - "Set MODEM CTRL 0x%x failed (error = %d)\n", mcr, rc); + kfree(buf); + dbg("set_modem_ctrl: state=0x%x ==> mcr=0x%x", control_state, mcr); - kfree(buf); - return rc; + if (rc < 0) { + dev_err(&serial->dev->dev, + "Set MODEM CTRL 0x%x failed (error = %d)\n", mcr, rc); + return rc; + } + return 0; } /* mct_u232_set_modem_ctrl */ static int mct_u232_get_modem_stat(struct usb_serial *serial, diff --git a/drivers/usb/serial/mos7840.c b/drivers/usb/serial/mos7840.c index 3257519..2d34dfd 100644 --- a/drivers/usb/serial/mos7840.c +++ b/drivers/usb/serial/mos7840.c @@ -206,7 +206,7 @@ static const struct usb_device_id moschip_port_id_table[] = { {} /* terminating entry */ }; -static const struct usb_device_id moschip_id_table_combined[] __devinitconst = { +static const struct usb_device_id moschip_id_table_combined[] = { {USB_DEVICE(USB_VENDOR_ID_MOSCHIP, MOSCHIP_DEVICE_ID_7840)}, {USB_DEVICE(USB_VENDOR_ID_MOSCHIP, MOSCHIP_DEVICE_ID_7820)}, {USB_DEVICE(USB_VENDOR_ID_BANDB, BANDB_DEVICE_ID_USO9ML2_2)}, @@ -235,12 +235,10 @@ struct moschip_port { int port_num; /*Actual port number in the device(1,2,etc) */ struct urb *write_urb; /* write URB for this port */ struct urb *read_urb; /* read URB for this port */ - struct urb *int_urb; __u8 shadowLCR; /* last LCR value received */ __u8 shadowMCR; /* last MCR value received */ char open; char open_ports; - char zombie; wait_queue_head_t wait_chase; /* for handling sleeping while waiting for chase to finish */ wait_queue_head_t delta_msr_wait; /* for handling sleeping while waiting for msr change to happen */ int delta_msr_cond; @@ -505,7 +503,6 @@ static void mos7840_control_callback(struct urb *urb) unsigned char *data; struct moschip_port *mos7840_port; __u8 regval = 0x0; - int result = 0; int status = urb->status; mos7840_port = urb->context; @@ -524,7 +521,7 @@ static void mos7840_control_callback(struct urb *urb) default: dbg("%s - nonzero urb status received: %d", __func__, status); - goto exit; + return; } dbg("%s urb buffer size is %d", __func__, urb->actual_length); @@ -537,17 +534,6 @@ static void mos7840_control_callback(struct urb *urb) mos7840_handle_new_msr(mos7840_port, regval); else if (mos7840_port->MsrLsr == 1) mos7840_handle_new_lsr(mos7840_port, regval); - -exit: - spin_lock(&mos7840_port->pool_lock); - if (!mos7840_port->zombie) - result = usb_submit_urb(mos7840_port->int_urb, GFP_ATOMIC); - spin_unlock(&mos7840_port->pool_lock); - if (result) { - dev_err(&urb->dev->dev, - "%s - Error %d submitting interrupt urb\n", - __func__, result); - } } static int mos7840_get_reg(struct moschip_port *mcs, __u16 Wval, __u16 reg, @@ -655,14 +641,7 @@ static void mos7840_interrupt_callback(struct urb *urb) wreg = MODEM_STATUS_REGISTER; break; } - spin_lock(&mos7840_port->pool_lock); - if (!mos7840_port->zombie) { - rv = mos7840_get_reg(mos7840_port, wval, wreg, &Data); - } else { - spin_unlock(&mos7840_port->pool_lock); - return; - } - spin_unlock(&mos7840_port->pool_lock); + rv = mos7840_get_reg(mos7840_port, wval, wreg, &Data); } } } @@ -1191,9 +1170,12 @@ static int mos7840_chars_in_buffer(struct tty_struct *tty) } spin_lock_irqsave(&mos7840_port->pool_lock, flags); - for (i = 0; i < NUM_URBS; ++i) - if (mos7840_port->busy[i]) - chars += URB_TRANSFER_BUFFER_SIZE; + for (i = 0; i < NUM_URBS; ++i) { + if (mos7840_port->busy[i]) { + struct urb *urb = mos7840_port->write_urb_pool[i]; + chars += urb->transfer_buffer_length; + } + } spin_unlock_irqrestore(&mos7840_port->pool_lock, flags); dbg("%s - returns %d", __func__, chars); return chars; @@ -2592,7 +2574,6 @@ error: kfree(mos7840_port->ctrl_buf); usb_free_urb(mos7840_port->control_urb); kfree(mos7840_port); - serial->port[i] = NULL; } return status; } @@ -2623,9 +2604,6 @@ static void mos7840_disconnect(struct usb_serial *serial) mos7840_port = mos7840_get_port_private(serial->port[i]); dbg ("mos7840_port %d = %p", i, mos7840_port); if (mos7840_port) { - spin_lock_irqsave(&mos7840_port->pool_lock, flags); - mos7840_port->zombie = 1; - spin_unlock_irqrestore(&mos7840_port->pool_lock, flags); usb_kill_urb(mos7840_port->control_urb); } } @@ -2659,6 +2637,7 @@ static void mos7840_release(struct usb_serial *serial) mos7840_port = mos7840_get_port_private(serial->port[i]); dbg("mos7840_port %d = %p", i, mos7840_port); if (mos7840_port) { + usb_free_urb(mos7840_port->control_urb); kfree(mos7840_port->ctrl_buf); kfree(mos7840_port->dr); kfree(mos7840_port); diff --git a/drivers/usb/serial/opticon.c b/drivers/usb/serial/opticon.c index 96423f3..5d274b3 100644 --- a/drivers/usb/serial/opticon.c +++ b/drivers/usb/serial/opticon.c @@ -160,7 +160,11 @@ static int send_control_msg(struct usb_serial_port *port, u8 requesttype, { struct usb_serial *serial = port->serial; int retval; - u8 buffer[2]; + u8 *buffer; + + buffer = kzalloc(1, GFP_KERNEL); + if (!buffer) + return -ENOMEM; buffer[0] = val; /* Send the message to the vendor control endpoint @@ -169,6 +173,7 @@ static int send_control_msg(struct usb_serial_port *port, u8 requesttype, requesttype, USB_DIR_OUT|USB_TYPE_VENDOR|USB_RECIP_INTERFACE, 0, 0, buffer, 1, 0); + kfree(buffer); return retval; } @@ -292,7 +297,7 @@ static int opticon_write(struct tty_struct *tty, struct usb_serial_port *port, if (!dr) { dev_err(&port->dev, "out of memory\n"); count = -ENOMEM; - goto error; + goto error_no_dr; } dr->bRequestType = USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_OUT; @@ -322,6 +327,8 @@ static int opticon_write(struct tty_struct *tty, struct usb_serial_port *port, return count; error: + kfree(dr); +error_no_dr: usb_free_urb(urb); error_no_urb: kfree(buffer); diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index cbe3451..c334670 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -47,6 +47,7 @@ /* Function prototypes */ static int option_probe(struct usb_serial *serial, const struct usb_device_id *id); +static void option_release(struct usb_serial *serial); static int option_send_setup(struct usb_serial_port *port); static void option_instat_callback(struct urb *urb); @@ -79,84 +80,9 @@ static void option_instat_callback(struct urb *urb); #define OPTION_PRODUCT_GTM380_MODEM 0x7201 #define HUAWEI_VENDOR_ID 0x12D1 -#define HUAWEI_PRODUCT_E600 0x1001 -#define HUAWEI_PRODUCT_E220 0x1003 -#define HUAWEI_PRODUCT_E220BIS 0x1004 -#define HUAWEI_PRODUCT_E1401 0x1401 -#define HUAWEI_PRODUCT_E1402 0x1402 -#define HUAWEI_PRODUCT_E1403 0x1403 -#define HUAWEI_PRODUCT_E1404 0x1404 -#define HUAWEI_PRODUCT_E1405 0x1405 -#define HUAWEI_PRODUCT_E1406 0x1406 -#define HUAWEI_PRODUCT_E1407 0x1407 -#define HUAWEI_PRODUCT_E1408 0x1408 -#define HUAWEI_PRODUCT_E1409 0x1409 -#define HUAWEI_PRODUCT_E140A 0x140A -#define HUAWEI_PRODUCT_E140B 0x140B -#define HUAWEI_PRODUCT_E140C 0x140C -#define HUAWEI_PRODUCT_E140D 0x140D -#define HUAWEI_PRODUCT_E140E 0x140E -#define HUAWEI_PRODUCT_E140F 0x140F -#define HUAWEI_PRODUCT_E1410 0x1410 -#define HUAWEI_PRODUCT_E1411 0x1411 -#define HUAWEI_PRODUCT_E1412 0x1412 -#define HUAWEI_PRODUCT_E1413 0x1413 -#define HUAWEI_PRODUCT_E1414 0x1414 -#define HUAWEI_PRODUCT_E1415 0x1415 -#define HUAWEI_PRODUCT_E1416 0x1416 -#define HUAWEI_PRODUCT_E1417 0x1417 -#define HUAWEI_PRODUCT_E1418 0x1418 -#define HUAWEI_PRODUCT_E1419 0x1419 -#define HUAWEI_PRODUCT_E141A 0x141A -#define HUAWEI_PRODUCT_E141B 0x141B -#define HUAWEI_PRODUCT_E141C 0x141C -#define HUAWEI_PRODUCT_E141D 0x141D -#define HUAWEI_PRODUCT_E141E 0x141E -#define HUAWEI_PRODUCT_E141F 0x141F -#define HUAWEI_PRODUCT_E1420 0x1420 -#define HUAWEI_PRODUCT_E1421 0x1421 -#define HUAWEI_PRODUCT_E1422 0x1422 -#define HUAWEI_PRODUCT_E1423 0x1423 -#define HUAWEI_PRODUCT_E1424 0x1424 -#define HUAWEI_PRODUCT_E1425 0x1425 -#define HUAWEI_PRODUCT_E1426 0x1426 -#define HUAWEI_PRODUCT_E1427 0x1427 -#define HUAWEI_PRODUCT_E1428 0x1428 -#define HUAWEI_PRODUCT_E1429 0x1429 -#define HUAWEI_PRODUCT_E142A 0x142A -#define HUAWEI_PRODUCT_E142B 0x142B -#define HUAWEI_PRODUCT_E142C 0x142C -#define HUAWEI_PRODUCT_E142D 0x142D -#define HUAWEI_PRODUCT_E142E 0x142E -#define HUAWEI_PRODUCT_E142F 0x142F -#define HUAWEI_PRODUCT_E1430 0x1430 -#define HUAWEI_PRODUCT_E1431 0x1431 -#define HUAWEI_PRODUCT_E1432 0x1432 -#define HUAWEI_PRODUCT_E1433 0x1433 -#define HUAWEI_PRODUCT_E1434 0x1434 -#define HUAWEI_PRODUCT_E1435 0x1435 -#define HUAWEI_PRODUCT_E1436 0x1436 -#define HUAWEI_PRODUCT_E1437 0x1437 -#define HUAWEI_PRODUCT_E1438 0x1438 -#define HUAWEI_PRODUCT_E1439 0x1439 -#define HUAWEI_PRODUCT_E143A 0x143A -#define HUAWEI_PRODUCT_E143B 0x143B -#define HUAWEI_PRODUCT_E143C 0x143C -#define HUAWEI_PRODUCT_E143D 0x143D -#define HUAWEI_PRODUCT_E143E 0x143E -#define HUAWEI_PRODUCT_E143F 0x143F #define HUAWEI_PRODUCT_K4505 0x1464 #define HUAWEI_PRODUCT_K3765 0x1465 -#define HUAWEI_PRODUCT_E14AC 0x14AC -#define HUAWEI_PRODUCT_K3806 0x14AE #define HUAWEI_PRODUCT_K4605 0x14C6 -#define HUAWEI_PRODUCT_K3770 0x14C9 -#define HUAWEI_PRODUCT_K3771 0x14CA -#define HUAWEI_PRODUCT_K4510 0x14CB -#define HUAWEI_PRODUCT_K4511 0x14CC -#define HUAWEI_PRODUCT_ETS1220 0x1803 -#define HUAWEI_PRODUCT_E353 0x1506 -#define HUAWEI_PRODUCT_E173S 0x1C05 #define QUANTA_VENDOR_ID 0x0408 #define QUANTA_PRODUCT_Q101 0xEA02 @@ -234,6 +160,7 @@ static void option_instat_callback(struct urb *urb); #define NOVATELWIRELESS_PRODUCT_G1 0xA001 #define NOVATELWIRELESS_PRODUCT_G1_M 0xA002 #define NOVATELWIRELESS_PRODUCT_G2 0xA010 +#define NOVATELWIRELESS_PRODUCT_MC551 0xB001 /* AMOI PRODUCTS */ #define AMOI_VENDOR_ID 0x1614 @@ -425,7 +352,7 @@ static void option_instat_callback(struct urb *urb); #define SAMSUNG_VENDOR_ID 0x04e8 #define SAMSUNG_PRODUCT_GT_B3730 0x6889 -/* YUGA products www.yuga-info.com*/ +/* YUGA products www.yuga-info.com gavin.kx@qq.com */ #define YUGA_VENDOR_ID 0x257A #define YUGA_PRODUCT_CEM600 0x1601 #define YUGA_PRODUCT_CEM610 0x1602 @@ -442,6 +369,8 @@ static void option_instat_callback(struct urb *urb); #define YUGA_PRODUCT_CEU516 0x160C #define YUGA_PRODUCT_CEU528 0x160D #define YUGA_PRODUCT_CEU526 0x160F +#define YUGA_PRODUCT_CEU881 0x161F +#define YUGA_PRODUCT_CEU882 0x162F #define YUGA_PRODUCT_CWM600 0x2601 #define YUGA_PRODUCT_CWM610 0x2602 @@ -457,23 +386,26 @@ static void option_instat_callback(struct urb *urb); #define YUGA_PRODUCT_CWU518 0x260B #define YUGA_PRODUCT_CWU516 0x260C #define YUGA_PRODUCT_CWU528 0x260D +#define YUGA_PRODUCT_CWU581 0x260E #define YUGA_PRODUCT_CWU526 0x260F - -#define YUGA_PRODUCT_CLM600 0x2601 -#define YUGA_PRODUCT_CLM610 0x2602 -#define YUGA_PRODUCT_CLM500 0x2603 -#define YUGA_PRODUCT_CLM510 0x2604 -#define YUGA_PRODUCT_CLM800 0x2605 -#define YUGA_PRODUCT_CLM900 0x2606 - -#define YUGA_PRODUCT_CLU718 0x2607 -#define YUGA_PRODUCT_CLU716 0x2608 -#define YUGA_PRODUCT_CLU728 0x2609 -#define YUGA_PRODUCT_CLU726 0x260A -#define YUGA_PRODUCT_CLU518 0x260B -#define YUGA_PRODUCT_CLU516 0x260C -#define YUGA_PRODUCT_CLU528 0x260D -#define YUGA_PRODUCT_CLU526 0x260F +#define YUGA_PRODUCT_CWU582 0x261F +#define YUGA_PRODUCT_CWU583 0x262F + +#define YUGA_PRODUCT_CLM600 0x3601 +#define YUGA_PRODUCT_CLM610 0x3602 +#define YUGA_PRODUCT_CLM500 0x3603 +#define YUGA_PRODUCT_CLM510 0x3604 +#define YUGA_PRODUCT_CLM800 0x3605 +#define YUGA_PRODUCT_CLM900 0x3606 + +#define YUGA_PRODUCT_CLU718 0x3607 +#define YUGA_PRODUCT_CLU716 0x3608 +#define YUGA_PRODUCT_CLU728 0x3609 +#define YUGA_PRODUCT_CLU726 0x360A +#define YUGA_PRODUCT_CLU518 0x360B +#define YUGA_PRODUCT_CLU516 0x360C +#define YUGA_PRODUCT_CLU528 0x360D +#define YUGA_PRODUCT_CLU526 0x360F /* Viettel products */ #define VIETTEL_VENDOR_ID 0x2262 @@ -489,6 +421,19 @@ static void option_instat_callback(struct urb *urb); /* MediaTek products */ #define MEDIATEK_VENDOR_ID 0x0e8d +#define MEDIATEK_PRODUCT_DC_1COM 0x00a0 +#define MEDIATEK_PRODUCT_DC_4COM 0x00a5 +#define MEDIATEK_PRODUCT_DC_5COM 0x00a4 +#define MEDIATEK_PRODUCT_7208_1COM 0x7101 +#define MEDIATEK_PRODUCT_7208_2COM 0x7102 +#define MEDIATEK_PRODUCT_FP_1COM 0x0003 +#define MEDIATEK_PRODUCT_FP_2COM 0x0023 +#define MEDIATEK_PRODUCT_FPDC_1COM 0x0043 +#define MEDIATEK_PRODUCT_FPDC_2COM 0x0033 + +/* Cellient products */ +#define CELLIENT_VENDOR_ID 0x2692 +#define CELLIENT_PRODUCT_MEN200 0x9005 /* some devices interfaces need special handling due to a number of reasons */ enum option_blacklist_reason { @@ -542,6 +487,10 @@ static const struct option_blacklist_info net_intf1_blacklist = { .reserved = BIT(1), }; +static const struct option_blacklist_info net_intf2_blacklist = { + .reserved = BIT(2), +}; + static const struct option_blacklist_info net_intf3_blacklist = { .reserved = BIT(3), }; @@ -554,11 +503,19 @@ static const struct option_blacklist_info net_intf5_blacklist = { .reserved = BIT(5), }; +static const struct option_blacklist_info net_intf6_blacklist = { + .reserved = BIT(6), +}; + static const struct option_blacklist_info zte_mf626_blacklist = { .sendsetup = BIT(0) | BIT(1), .reserved = BIT(4), }; +static const struct option_blacklist_info zte_1255_blacklist = { + .reserved = BIT(3) | BIT(4), +}; + static const struct usb_device_id option_ids[] = { { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_COLT) }, { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_RICOLA) }, @@ -590,99 +547,123 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE(QUANTA_VENDOR_ID, QUANTA_PRODUCT_GLX) }, { USB_DEVICE(QUANTA_VENDOR_ID, QUANTA_PRODUCT_GKE) }, { USB_DEVICE(QUANTA_VENDOR_ID, QUANTA_PRODUCT_GLE) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E600, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E220, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E220BIS, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1401, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1402, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1403, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1404, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1405, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1406, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1407, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1408, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1409, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E140A, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E140B, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E140C, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E140D, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E140E, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E140F, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1410, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1411, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1412, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1413, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1414, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1415, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1416, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1417, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1418, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1419, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E141A, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E141B, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E141C, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E141D, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E141E, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E141F, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1420, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1421, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1422, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1423, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1424, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1425, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1426, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1427, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1428, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1429, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E142A, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E142B, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E142C, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E142D, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E142E, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E142F, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1430, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1431, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1432, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1433, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1434, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1435, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1436, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1437, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1438, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E1439, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E143A, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E143B, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E143C, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E143D, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E143E, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E143F, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E173S, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_K4505, 0xff, 0xff, 0xff), .driver_info = (kernel_ulong_t) &huawei_cdc12_blacklist }, { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_K3765, 0xff, 0xff, 0xff), .driver_info = (kernel_ulong_t) &huawei_cdc12_blacklist }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_ETS1220, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E14AC, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_K3806, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_K4605, 0xff, 0xff, 0xff), .driver_info = (kernel_ulong_t) &huawei_cdc12_blacklist }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_K3770, 0xff, 0x02, 0x31) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_K3770, 0xff, 0x02, 0x32) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_K3771, 0xff, 0x02, 0x31) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_K3771, 0xff, 0x02, 0x32) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_K4510, 0xff, 0x01, 0x31) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_K4510, 0xff, 0x01, 0x32) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_K4511, 0xff, 0x01, 0x31) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_K4511, 0xff, 0x01, 0x32) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E353, 0xff, 0x01, 0x01) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E353, 0xff, 0x01, 0x02) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E353, 0xff, 0x01, 0x03) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E353, 0xff, 0x01, 0x10) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E353, 0xff, 0x01, 0x12) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E353, 0xff, 0x01, 0x13) }, - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E353, 0xff, 0x02, 0x01) }, /* E398 3G Modem */ - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E353, 0xff, 0x02, 0x02) }, /* E398 3G PC UI Interface */ - { USB_DEVICE_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, HUAWEI_PRODUCT_E353, 0xff, 0x02, 0x03) }, /* E398 3G Application Interface */ + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0xff, 0xff) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x01) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x02) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x03) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x04) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x05) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x06) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x0A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x0B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x0D) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x0E) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x0F) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x10) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x12) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x13) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x14) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x15) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x17) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x18) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x19) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x1A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x1B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x1C) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x31) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x32) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x33) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x34) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x35) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x36) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x3A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x3B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x3D) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x3E) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x3F) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x48) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x49) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x4A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x4B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x4C) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x61) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x62) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x63) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x64) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x65) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x66) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x6A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x6B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x6D) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x6E) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x6F) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x78) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x79) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x7A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x7B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x01, 0x7C) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x01) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x02) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x03) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x04) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x05) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x06) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x0A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x0B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x0D) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x0E) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x0F) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x10) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x12) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x13) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x14) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x15) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x17) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x18) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x19) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x1A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x1B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x1C) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x31) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x32) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x33) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x34) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x35) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x36) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x3A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x3B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x3D) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x3E) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x3F) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x48) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x49) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x4A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x4B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x4C) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x61) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x62) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x63) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x64) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x65) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x66) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x6A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x6B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x6D) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x6E) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x6F) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x78) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x79) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x7A) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x7B) }, + { USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, 0xff, 0x02, 0x7C) }, + + { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_V640) }, { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_V620) }, { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_V740) }, @@ -722,6 +703,8 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_G1) }, { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_G1_M) }, { USB_DEVICE(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_G2) }, + /* Novatel Ovation MC551 a.k.a. Verizon USB551L */ + { USB_DEVICE_AND_INTERFACE_INFO(NOVATELWIRELESS_VENDOR_ID, NOVATELWIRELESS_PRODUCT_MC551, 0xff, 0xff, 0xff) }, { USB_DEVICE(AMOI_VENDOR_ID, AMOI_PRODUCT_H01) }, { USB_DEVICE(AMOI_VENDOR_ID, AMOI_PRODUCT_H01A) }, @@ -878,13 +861,19 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0113, 0xff, 0xff, 0xff), .driver_info = (kernel_ulong_t)&net_intf5_blacklist }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0117, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0118, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0121, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0118, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf5_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0121, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf5_blacklist }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0122, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0123, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0124, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0125, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0126, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0123, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0124, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf5_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0125, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf6_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0126, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf5_blacklist }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0128, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0142, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0143, 0xff, 0xff, 0xff) }, @@ -895,8 +884,10 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0153, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0155, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0156, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0157, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0158, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0157, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf5_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0158, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf3_blacklist }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0159, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0161, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0162, 0xff, 0xff, 0xff) }, @@ -904,9 +895,22 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0165, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0167, 0xff, 0xff, 0xff), .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, - { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1008, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1010, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1012, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0191, 0xff, 0xff, 0xff), /* ZTE EuFi890 */ + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0199, 0xff, 0xff, 0xff), /* ZTE MF820S */ + .driver_info = (kernel_ulong_t)&net_intf1_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0257, 0xff, 0xff, 0xff), /* ZTE MF821 */ + .driver_info = (kernel_ulong_t)&net_intf3_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0326, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1008, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1010, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1012, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1021, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf2_blacklist }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1057, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1058, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1059, 0xff, 0xff, 0xff) }, @@ -1022,18 +1026,24 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1169, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1170, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1244, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1245, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1245, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1246, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1247, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1247, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1248, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1249, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1250, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1251, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1252, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1252, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1253, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1254, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1255, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1256, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1254, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1255, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&zte_1255_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1256, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1257, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1258, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1259, 0xff, 0xff, 0xff) }, @@ -1078,6 +1088,16 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1298, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1299, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1300, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1401, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf2_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1402, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf2_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1424, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf2_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1425, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf2_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x1426, 0xff, 0xff, 0xff), /* ZTE MF91 */ + .driver_info = (kernel_ulong_t)&net_intf2_blacklist }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x2002, 0xff, 0xff, 0xff), .driver_info = (kernel_ulong_t)&zte_k3765_z_blacklist }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x2003, 0xff, 0xff, 0xff) }, @@ -1089,15 +1109,21 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0070, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0073, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0094, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0130, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0133, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0141, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0130, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf1_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0133, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf3_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0141, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf5_blacklist }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0147, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0152, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0168, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0168, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf4_blacklist }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0170, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0176, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0178, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0176, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf3_blacklist }, + { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0x0178, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)&net_intf3_blacklist }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_CDMA_TECH, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_AC8710, 0xff, 0xff, 0xff) }, @@ -1109,6 +1135,10 @@ static const struct usb_device_id option_ids[] = { .driver_info = (kernel_ulong_t)&zte_ad3812_z_blacklist }, { USB_DEVICE_AND_INTERFACE_INFO(ZTE_VENDOR_ID, ZTE_PRODUCT_MC2716, 0xff, 0xff, 0xff), .driver_info = (kernel_ulong_t)&zte_mc2716_z_blacklist }, + { USB_VENDOR_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff, 0x02, 0x01) }, + { USB_VENDOR_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff, 0x02, 0x05) }, + { USB_VENDOR_AND_INTERFACE_INFO(ZTE_VENDOR_ID, 0xff, 0x86, 0x10) }, + { USB_DEVICE(BENQ_VENDOR_ID, BENQ_PRODUCT_H10) }, { USB_DEVICE(DLINK_VENDOR_ID, DLINK_PRODUCT_DWM_652) }, { USB_DEVICE(ALINK_VENDOR_ID, DLINK_PRODUCT_DWM_652_U5) }, /* Yes, ALINK_VENDOR_ID */ @@ -1207,6 +1237,11 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CLU516) }, { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CLU528) }, { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CLU526) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CEU881) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CEU882) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CWU581) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CWU582) }, + { USB_DEVICE(YUGA_VENDOR_ID, YUGA_PRODUCT_CWU583) }, { USB_DEVICE_AND_INTERFACE_INFO(VIETTEL_VENDOR_ID, VIETTEL_PRODUCT_VT1000, 0xff, 0xff, 0xff) }, { USB_DEVICE_AND_INTERFACE_INFO(ZD_VENDOR_ID, ZD_PRODUCT_7000, 0xff, 0xff, 0xff) }, { USB_DEVICE(LG_VENDOR_ID, LG_PRODUCT_L02C) }, /* docomo L-02C modem */ @@ -1214,6 +1249,18 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, 0x00a1, 0xff, 0x02, 0x01) }, { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, 0x00a2, 0xff, 0x00, 0x00) }, { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, 0x00a2, 0xff, 0x02, 0x01) }, /* MediaTek MT6276M modem & app port */ + { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_DC_1COM, 0x0a, 0x00, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_DC_5COM, 0xff, 0x02, 0x01) }, + { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_DC_5COM, 0xff, 0x00, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_DC_4COM, 0xff, 0x02, 0x01) }, + { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_DC_4COM, 0xff, 0x00, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_7208_1COM, 0x02, 0x00, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_7208_2COM, 0x02, 0x02, 0x01) }, + { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_FP_1COM, 0x0a, 0x00, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_FP_2COM, 0x0a, 0x00, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_FPDC_1COM, 0x0a, 0x00, 0x00) }, + { USB_DEVICE_AND_INTERFACE_INFO(MEDIATEK_VENDOR_ID, MEDIATEK_PRODUCT_FPDC_2COM, 0x0a, 0x00, 0x00) }, + { USB_DEVICE(CELLIENT_VENDOR_ID, CELLIENT_PRODUCT_MEN200) }, { } /* Terminating entry */ }; MODULE_DEVICE_TABLE(usb, option_ids); @@ -1257,7 +1304,7 @@ static struct usb_serial_driver option_1port_device = { .ioctl = usb_wwan_ioctl, .attach = usb_wwan_startup, .disconnect = usb_wwan_disconnect, - .release = usb_wwan_release, + .release = option_release, .read_int_callback = option_instat_callback, #ifdef CONFIG_PM .suspend = usb_wwan_suspend, @@ -1267,35 +1314,6 @@ static struct usb_serial_driver option_1port_device = { static int debug; -/* per port private data */ - -#define N_IN_URB 4 -#define N_OUT_URB 4 -#define IN_BUFLEN 4096 -#define OUT_BUFLEN 4096 - -struct option_port_private { - /* Input endpoints and buffer for this port */ - struct urb *in_urbs[N_IN_URB]; - u8 *in_buffer[N_IN_URB]; - /* Output endpoints and buffer for this port */ - struct urb *out_urbs[N_OUT_URB]; - u8 *out_buffer[N_OUT_URB]; - unsigned long out_busy; /* Bit vector of URBs in use */ - int opened; - struct usb_anchor delayed; - - /* Settings for the port */ - int rts_state; /* Handshaking pins (outputs) */ - int dtr_state; - int cts_state; /* Handshaking pins (inputs) */ - int dsr_state; - int dcd_state; - int ri_state; - - unsigned long tx_start_time[N_OUT_URB]; -}; - /* Functions used by new usb-serial code. */ static int __init option_init(void) { @@ -1393,12 +1411,22 @@ static int option_probe(struct usb_serial *serial, return 0; } +static void option_release(struct usb_serial *serial) +{ + struct usb_wwan_intf_private *priv = usb_get_serial_data(serial); + + usb_wwan_release(serial); + + kfree(priv); +} + static void option_instat_callback(struct urb *urb) { int err; int status = urb->status; struct usb_serial_port *port = urb->context; - struct option_port_private *portdata = usb_get_serial_port_data(port); + struct usb_wwan_port_private *portdata = + usb_get_serial_port_data(port); dbg("%s", __func__); dbg("%s: urb %p port %p has data %p", __func__, urb, port, portdata); @@ -1459,7 +1487,7 @@ static int option_send_setup(struct usb_serial_port *port) struct usb_serial *serial = port->serial; struct usb_wwan_intf_private *intfdata = (struct usb_wwan_intf_private *) serial->private; - struct option_port_private *portdata; + struct usb_wwan_port_private *portdata; int ifNum = serial->interface->cur_altsetting->desc.bInterfaceNumber; int val = 0; dbg("%s", __func__); diff --git a/drivers/usb/serial/qcaux.c b/drivers/usb/serial/qcaux.c index a348198..87271e3 100644 --- a/drivers/usb/serial/qcaux.c +++ b/drivers/usb/serial/qcaux.c @@ -36,8 +36,6 @@ #define UTSTARCOM_PRODUCT_UM175_V1 0x3712 #define UTSTARCOM_PRODUCT_UM175_V2 0x3714 #define UTSTARCOM_PRODUCT_UM175_ALLTEL 0x3715 -#define PANTECH_PRODUCT_UML190_VZW 0x3716 -#define PANTECH_PRODUCT_UML290_VZW 0x3718 /* CMOTECH devices */ #define CMOTECH_VENDOR_ID 0x16d8 @@ -68,11 +66,9 @@ static struct usb_device_id id_table[] = { { USB_DEVICE_AND_INTERFACE_INFO(LG_VENDOR_ID, LG_PRODUCT_VX4400_6000, 0xff, 0xff, 0x00) }, { USB_DEVICE_AND_INTERFACE_INFO(SANYO_VENDOR_ID, SANYO_PRODUCT_KATANA_LX, 0xff, 0xff, 0x00) }, { USB_DEVICE_AND_INTERFACE_INFO(SAMSUNG_VENDOR_ID, SAMSUNG_PRODUCT_U520, 0xff, 0x00, 0x00) }, - { USB_DEVICE_AND_INTERFACE_INFO(UTSTARCOM_VENDOR_ID, PANTECH_PRODUCT_UML190_VZW, 0xff, 0xff, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(UTSTARCOM_VENDOR_ID, PANTECH_PRODUCT_UML190_VZW, 0xff, 0xfe, 0xff) }, - { USB_DEVICE_AND_INTERFACE_INFO(UTSTARCOM_VENDOR_ID, PANTECH_PRODUCT_UML290_VZW, 0xff, 0xfd, 0xff) }, /* NMEA */ - { USB_DEVICE_AND_INTERFACE_INFO(UTSTARCOM_VENDOR_ID, PANTECH_PRODUCT_UML290_VZW, 0xff, 0xfe, 0xff) }, /* WMC */ - { USB_DEVICE_AND_INTERFACE_INFO(UTSTARCOM_VENDOR_ID, PANTECH_PRODUCT_UML290_VZW, 0xff, 0xff, 0xff) }, /* DIAG */ + { USB_VENDOR_AND_INTERFACE_INFO(UTSTARCOM_VENDOR_ID, 0xff, 0xfd, 0xff) }, /* NMEA */ + { USB_VENDOR_AND_INTERFACE_INFO(UTSTARCOM_VENDOR_ID, 0xff, 0xfe, 0xff) }, /* WMC */ + { USB_VENDOR_AND_INTERFACE_INFO(UTSTARCOM_VENDOR_ID, 0xff, 0xff, 0xff) }, /* DIAG */ { }, }; MODULE_DEVICE_TABLE(usb, id_table); diff --git a/drivers/usb/serial/qcserial.c b/drivers/usb/serial/qcserial.c index 247c014..7cd2c26 100644 --- a/drivers/usb/serial/qcserial.c +++ b/drivers/usb/serial/qcserial.c @@ -104,7 +104,13 @@ static const struct usb_device_id id_table[] = { {USB_DEVICE(0x1410, 0xa021)}, /* Novatel Gobi 3000 Composite */ {USB_DEVICE(0x413c, 0x8193)}, /* Dell Gobi 3000 QDL */ {USB_DEVICE(0x413c, 0x8194)}, /* Dell Gobi 3000 Composite */ + {USB_DEVICE(0x1199, 0x9010)}, /* Sierra Wireless Gobi 3000 QDL */ + {USB_DEVICE(0x1199, 0x9012)}, /* Sierra Wireless Gobi 3000 QDL */ {USB_DEVICE(0x1199, 0x9013)}, /* Sierra Wireless Gobi 3000 Modem device (MC8355) */ + {USB_DEVICE(0x1199, 0x9014)}, /* Sierra Wireless Gobi 3000 QDL */ + {USB_DEVICE(0x1199, 0x9015)}, /* Sierra Wireless Gobi 3000 Modem device */ + {USB_DEVICE(0x1199, 0x9018)}, /* Sierra Wireless Gobi 3000 QDL */ + {USB_DEVICE(0x1199, 0x9019)}, /* Sierra Wireless Gobi 3000 Modem device */ {USB_DEVICE(0x12D1, 0x14F0)}, /* Sony Gobi 3000 QDL */ {USB_DEVICE(0x12D1, 0x14F1)}, /* Sony Gobi 3000 Composite */ { } /* Terminating entry */ diff --git a/drivers/usb/serial/sierra.c b/drivers/usb/serial/sierra.c index ef71ba3..a159ad0 100644 --- a/drivers/usb/serial/sierra.c +++ b/drivers/usb/serial/sierra.c @@ -171,7 +171,6 @@ static int sierra_probe(struct usb_serial *serial, { int result = 0; struct usb_device *udev; - struct sierra_intf_private *data; u8 ifnum; udev = serial->dev; @@ -199,11 +198,6 @@ static int sierra_probe(struct usb_serial *serial, return -ENODEV; } - data = serial->private = kzalloc(sizeof(struct sierra_intf_private), GFP_KERNEL); - if (!data) - return -ENOMEM; - spin_lock_init(&data->susp_lock); - return result; } @@ -304,6 +298,10 @@ static const struct usb_device_id id_table[] = { { USB_DEVICE(0x1199, 0x68A3), /* Sierra Wireless Direct IP modems */ .driver_info = (kernel_ulong_t)&direct_ip_interface_blacklist }, + /* AT&T Direct IP LTE modems */ + { USB_DEVICE_AND_INTERFACE_INFO(0x0F3D, 0x68AA, 0xFF, 0xFF, 0xFF), + .driver_info = (kernel_ulong_t)&direct_ip_interface_blacklist + }, { USB_DEVICE(0x0f3d, 0x68A3), /* Airprime/Sierra Wireless Direct IP modems */ .driver_info = (kernel_ulong_t)&direct_ip_interface_blacklist }, @@ -911,6 +909,7 @@ static void sierra_dtr_rts(struct usb_serial_port *port, int on) static int sierra_startup(struct usb_serial *serial) { struct usb_serial_port *port; + struct sierra_intf_private *intfdata; struct sierra_port_private *portdata; struct sierra_iface_info *himemoryp = NULL; int i; @@ -918,6 +917,14 @@ static int sierra_startup(struct usb_serial *serial) dev_dbg(&serial->dev->dev, "%s\n", __func__); + intfdata = kzalloc(sizeof(*intfdata), GFP_KERNEL); + if (!intfdata) + return -ENOMEM; + + spin_lock_init(&intfdata->susp_lock); + + usb_set_serial_data(serial, intfdata); + /* Set Device mode to D0 */ sierra_set_power_state(serial->dev, 0x0000); @@ -933,7 +940,7 @@ static int sierra_startup(struct usb_serial *serial) dev_dbg(&port->dev, "%s: kmalloc for " "sierra_port_private (%d) failed!\n", __func__, i); - return -ENOMEM; + goto err; } spin_lock_init(&portdata->lock); init_usb_anchor(&portdata->active); @@ -970,6 +977,14 @@ static int sierra_startup(struct usb_serial *serial) } return 0; +err: + for (--i; i >= 0; --i) { + portdata = usb_get_serial_port_data(serial->port[i]); + kfree(portdata); + } + kfree(intfdata); + + return -ENOMEM; } static void sierra_release(struct usb_serial *serial) @@ -989,6 +1004,7 @@ static void sierra_release(struct usb_serial *serial) continue; kfree(portdata); } + kfree(serial->private); } #ifdef CONFIG_PM diff --git a/drivers/usb/serial/ti_usb_3410_5052.c b/drivers/usb/serial/ti_usb_3410_5052.c index 21c82b0..2856474 100644 --- a/drivers/usb/serial/ti_usb_3410_5052.c +++ b/drivers/usb/serial/ti_usb_3410_5052.c @@ -165,7 +165,7 @@ static unsigned int product_5052_count; /* the array dimension is the number of default entries plus */ /* TI_EXTRA_VID_PID_COUNT user defined entries plus 1 terminating */ /* null entry */ -static struct usb_device_id ti_id_table_3410[14+TI_EXTRA_VID_PID_COUNT+1] = { +static struct usb_device_id ti_id_table_3410[15+TI_EXTRA_VID_PID_COUNT+1] = { { USB_DEVICE(TI_VENDOR_ID, TI_3410_PRODUCT_ID) }, { USB_DEVICE(TI_VENDOR_ID, TI_3410_EZ430_ID) }, { USB_DEVICE(MTS_VENDOR_ID, MTS_GSM_NO_FW_PRODUCT_ID) }, @@ -180,6 +180,7 @@ static struct usb_device_id ti_id_table_3410[14+TI_EXTRA_VID_PID_COUNT+1] = { { USB_DEVICE(IBM_VENDOR_ID, IBM_454B_PRODUCT_ID) }, { USB_DEVICE(IBM_VENDOR_ID, IBM_454C_PRODUCT_ID) }, { USB_DEVICE(ABBOTT_VENDOR_ID, ABBOTT_PRODUCT_ID) }, + { USB_DEVICE(TI_VENDOR_ID, FRI2_PRODUCT_ID) }, }; static struct usb_device_id ti_id_table_5052[5+TI_EXTRA_VID_PID_COUNT+1] = { @@ -189,7 +190,7 @@ static struct usb_device_id ti_id_table_5052[5+TI_EXTRA_VID_PID_COUNT+1] = { { USB_DEVICE(TI_VENDOR_ID, TI_5052_FIRMWARE_PRODUCT_ID) }, }; -static struct usb_device_id ti_id_table_combined[18+2*TI_EXTRA_VID_PID_COUNT+1] = { +static struct usb_device_id ti_id_table_combined[19+2*TI_EXTRA_VID_PID_COUNT+1] = { { USB_DEVICE(TI_VENDOR_ID, TI_3410_PRODUCT_ID) }, { USB_DEVICE(TI_VENDOR_ID, TI_3410_EZ430_ID) }, { USB_DEVICE(MTS_VENDOR_ID, MTS_GSM_NO_FW_PRODUCT_ID) }, @@ -208,6 +209,7 @@ static struct usb_device_id ti_id_table_combined[18+2*TI_EXTRA_VID_PID_COUNT+1] { USB_DEVICE(IBM_VENDOR_ID, IBM_454B_PRODUCT_ID) }, { USB_DEVICE(IBM_VENDOR_ID, IBM_454C_PRODUCT_ID) }, { USB_DEVICE(ABBOTT_VENDOR_ID, ABBOTT_PRODUCT_ID) }, + { USB_DEVICE(TI_VENDOR_ID, FRI2_PRODUCT_ID) }, { } }; diff --git a/drivers/usb/serial/ti_usb_3410_5052.h b/drivers/usb/serial/ti_usb_3410_5052.h index f140f1b..b353e7e 100644 --- a/drivers/usb/serial/ti_usb_3410_5052.h +++ b/drivers/usb/serial/ti_usb_3410_5052.h @@ -37,6 +37,7 @@ #define TI_5152_BOOT_PRODUCT_ID 0x5152 /* no EEPROM, no firmware */ #define TI_5052_EEPROM_PRODUCT_ID 0x505A /* EEPROM, no firmware */ #define TI_5052_FIRMWARE_PRODUCT_ID 0x505F /* firmware is running */ +#define FRI2_PRODUCT_ID 0x5053 /* Fish River Island II */ /* Multi-Tech vendor and product ids */ #define MTS_VENDOR_ID 0x06E0 diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c index 5d7b71b..6f81aa5 100644 --- a/drivers/usb/serial/usb-serial.c +++ b/drivers/usb/serial/usb-serial.c @@ -669,12 +669,14 @@ exit: static struct usb_serial_driver *search_serial_device( struct usb_interface *iface) { - const struct usb_device_id *id; + const struct usb_device_id *id = NULL; struct usb_serial_driver *drv; + struct usb_driver *driver = to_usb_driver(iface->dev.driver); /* Check if the usb id matches a known device */ list_for_each_entry(drv, &usb_serial_driver_list, driver_list) { - id = get_iface_id(drv, iface); + if (drv->usb_driver == driver) + id = get_iface_id(drv, iface); if (id) return drv; } diff --git a/drivers/usb/serial/whiteheat.c b/drivers/usb/serial/whiteheat.c index 5b073bc..59d646d 100644 --- a/drivers/usb/serial/whiteheat.c +++ b/drivers/usb/serial/whiteheat.c @@ -576,6 +576,7 @@ no_firmware: "%s: please contact support@connecttech.com\n", serial->type->description); kfree(result); + kfree(command); return -ENODEV; no_command_private: diff --git a/drivers/usb/storage/unusual_devs.h b/drivers/usb/storage/unusual_devs.h index 24caba7..fa8a1b2 100644 --- a/drivers/usb/storage/unusual_devs.h +++ b/drivers/usb/storage/unusual_devs.h @@ -1004,6 +1004,12 @@ UNUSUAL_DEV( 0x07cf, 0x1001, 0x1000, 0x9999, USB_SC_8070, USB_PR_CB, NULL, US_FL_NEED_OVERRIDE | US_FL_FIX_INQUIRY ), +/* Submitted by Oleksandr Chumachenko <ledest@gmail.com> */ +UNUSUAL_DEV( 0x07cf, 0x1167, 0x0100, 0x0100, + "Casio", + "EX-N1 DigitalCamera", + USB_SC_8070, USB_PR_DEVICE, NULL, 0), + /* Submitted by Hartmut Wahl <hwahl@hwahl.de>*/ UNUSUAL_DEV( 0x0839, 0x000a, 0x0001, 0x0001, "Samsung", @@ -1885,6 +1891,13 @@ UNUSUAL_DEV( 0x1652, 0x6600, 0x0201, 0x0201, USB_SC_DEVICE, USB_PR_DEVICE, NULL, US_FL_IGNORE_RESIDUE ), +/* Reported by Jesse Feddema <jdfeddema@gmail.com> */ +UNUSUAL_DEV( 0x177f, 0x0400, 0x0000, 0x0000, + "Yarvik", + "PMP400", + USB_SC_DEVICE, USB_PR_DEVICE, NULL, + US_FL_BULK_IGNORE_TAG | US_FL_MAX_SECTORS_64 ), + /* Reported by Hans de Goede <hdegoede@redhat.com> * These Appotech controllers are found in Picture Frames, they provide a * (buggy) emulation of a cdrom drive which contains the windows software |