diff options
author | Rob Herring <r.herring@freescale.com> | 2008-02-14 14:44:21 -0600 |
---|---|---|
committer | Daniel Schaeffer <daniel.schaeffer@timesys.com> | 2008-08-25 15:18:55 -0400 |
commit | 3f8ed3afb9cee6648f9650d5daf950bb9347cca6 (patch) | |
tree | c9e28cdb34524d44c5dc4dc0778ebdcb858af20c /drivers/usb | |
parent | 49914084e797530d9baaf51df9eda77babc98fa8 (diff) |
ENGR00065563 Merge L2622-01 to 2.6.24
Merge L2622-01 release to 2.6.24 kernel.
Diffstat (limited to 'drivers/usb')
-rw-r--r-- | drivers/usb/Kconfig | 4 | ||||
-rw-r--r-- | drivers/usb/Makefile | 3 | ||||
-rw-r--r-- | drivers/usb/core/Kconfig | 7 | ||||
-rw-r--r-- | drivers/usb/core/hub.c | 8 | ||||
-rw-r--r-- | drivers/usb/gadget/Kconfig | 71 | ||||
-rw-r--r-- | drivers/usb/gadget/Makefile | 1 | ||||
-rw-r--r-- | drivers/usb/gadget/arcotg_udc.c | 3126 | ||||
-rw-r--r-- | drivers/usb/gadget/arcotg_udc.h | 598 | ||||
-rw-r--r-- | drivers/usb/gadget/ether.c | 4 | ||||
-rw-r--r-- | drivers/usb/gadget/gadget_chips.h | 8 | ||||
-rw-r--r-- | drivers/usb/host/Kconfig | 47 | ||||
-rw-r--r-- | drivers/usb/host/ehci-arc.c | 393 | ||||
-rw-r--r-- | drivers/usb/host/ehci-hcd.c | 5 | ||||
-rw-r--r-- | drivers/usb/host/ehci.h | 6 | ||||
-rw-r--r-- | drivers/usb/otg/Kconfig | 5 | ||||
-rw-r--r-- | drivers/usb/otg/Makefile | 6 | ||||
-rw-r--r-- | drivers/usb/otg/fsl_otg.c | 1078 | ||||
-rw-r--r-- | drivers/usb/otg/fsl_otg.h | 239 | ||||
-rw-r--r-- | drivers/usb/otg/otg_fsm.c | 394 | ||||
-rw-r--r-- | drivers/usb/otg/otg_fsm.h | 152 | ||||
-rw-r--r-- | drivers/usb/usblan/Kconfig | 24 | ||||
-rw-r--r-- | drivers/usb/usblan/Makefile | 8 | ||||
-rw-r--r-- | drivers/usb/usblan/usblan-compat.h | 286 | ||||
-rw-r--r-- | drivers/usb/usblan/usblan.c | 3045 | ||||
-rw-r--r-- | drivers/usb/usblan/usblan.h | 84 |
25 files changed, 9591 insertions, 11 deletions
diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index 7580aa5da0f8..a072eafb5509 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -89,6 +89,8 @@ config USB source "drivers/usb/core/Kconfig" +source "drivers/usb/otg/Kconfig" + source "drivers/usb/host/Kconfig" source "drivers/usb/class/Kconfig" @@ -99,6 +101,8 @@ source "drivers/usb/image/Kconfig" source "drivers/usb/mon/Kconfig" +source "drivers/usb/usblan/Kconfig" + comment "USB port drivers" depends on USB diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile index 516a6400db43..f9ba676e1733 100644 --- a/drivers/usb/Makefile +++ b/drivers/usb/Makefile @@ -32,3 +32,6 @@ obj-$(CONFIG_USB) += misc/ obj-$(CONFIG_USB_ATM) += atm/ obj-$(CONFIG_USB_SPEEDTOUCH) += atm/ + +obj-$(CONFIG_USB_OTG) += otg/ +obj-$(CONFIG_USB_USBLAN) += usblan/ diff --git a/drivers/usb/core/Kconfig b/drivers/usb/core/Kconfig index 97b09f282705..77af764e7904 100644 --- a/drivers/usb/core/Kconfig +++ b/drivers/usb/core/Kconfig @@ -112,11 +112,14 @@ config USB_PERSIST If you are unsure, say N here. config USB_OTG - bool + bool "Enable host-side support for On-The-Go (OTG)" depends on USB && EXPERIMENTAL select USB_SUSPEND default n - + help + Say y here if you want support for the Host Negotiation Protocol + (HNP) and the Session Request Protocol (SRP), parts of the OTG + supplement to the USB protocol, to be included in the USB host core. config USB_OTG_WHITELIST bool "Rely on OTG Targeted Peripherals List" diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index b04d232d4c65..6fd435fff671 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -929,6 +929,14 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id) desc = intf->cur_altsetting; hdev = interface_to_usbdev(intf); + /* With OTG enabled, suspending root hub results in gadget not + * working because gadget uses the same root hub. We disable + * this feature when OTG is selected. + */ +#ifdef CONFIG_USB_EHCI_ARC_OTG + hdev->autosuspend_disabled = 1; +#endif + #ifdef CONFIG_USB_OTG_BLACKLIST_HUB if (hdev->parent) { dev_warn(&intf->dev, "ignoring external hub\n"); diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index f81d08d6538b..4f2ccf6cdbf9 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -274,16 +274,25 @@ config USB_OMAP default USB_GADGET select USB_GADGET_SELECTED -config USB_OTG - boolean "OTG Support" - depends on USB_GADGET_OMAP && ARCH_OMAP_OTG && USB_OHCI_HCD +config USB_GADGET_ARC + boolean "ARC USB Device Controller" + depends on ARCH_MXC + select USB_GADGET_DUALSPEED + select USB_GADGET_ARC_OTGHS if USB_EHCI_ARC_OTGHS + select USB_GADGET_ARC_OTGFS if USB_EHCI_ARC_OTGFS help - The most notable feature of USB OTG is support for a - "Dual-Role" device, which can act as either a device - or a host. The initial role choice can be changed - later, when two dual-role devices talk to each other. + Some Freescale processors have an ARC High Speed + USBOTG controller, which supports device mode. - Select this only if your OMAP board has a Mini-AB connector. + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "arc_udc" and force all + gadget drivers to also be dynamically linked. + +config USB_ARC + tristate + depends on USB_GADGET_ARC + default USB_GADGET + select USB_GADGET_SELECTED config USB_GADGET_S3C2410 boolean "S3C2410 USB Device Controller" @@ -358,6 +367,18 @@ config USB_DUMMY_HCD endchoice +config USB_OTG + boolean "OTG Support" + depends on (USB_GADGET_OMAP && ARCH_OMAP_OTG && USB_OHCI_HCD) || \ + (USB_GADGET_ARC && (ARCH_MX3 || ARCH_MX27 || ARCH_MXC91221) && USB_EHCI_HCD) + help + The most notable feature of USB OTG is support for a + "Dual-Role" device, which can act as either a device + or a host. The initial role choice can be changed + later, when two dual-role devices talk to each other. + + Select this only if your board has a Mini-AB connector. + config USB_GADGET_DUALSPEED bool depends on USB_GADGET @@ -366,6 +387,38 @@ config USB_GADGET_DUALSPEED Means that gadget drivers should include extra descriptors and code to handle dual-speed controllers. +config USB_GADGET_ARC_OTG + bool "Support for OTG prepheral port on ARC controller" + depends on USB_GADGET_ARC + default y + ---help--- + Enable support for the USB OTG port in HS/FS prepheral mode. + +choice + prompt "Select transceiver" + depends on USB_GADGET_ARC_OTG + +config USB_GADGET_ARC_OTGFS + bool "Full Speed" + depends on !USB_EHCI_ARC_OTGHS + help + The ARC OTG controller can be connected to either a FS or + a HS transceiver. + + Enable this configuration option if you want to use the FS + transceiver. + +config USB_GADGET_ARC_OTGHS + bool "High Speed" + depends on !USB_EHCI_ARC_OTGFS + help + The ARC OTG controller can be connected to either a FS or + a HS transceiver. + + Enable this configuration option if you want to use the HS + transceiver. +endchoice + # # USB Gadget Drivers # @@ -418,7 +471,7 @@ config USB_ZERO config USB_ZERO_HNPTEST boolean "HNP Test Device" - depends on USB_ZERO && USB_OTG + depends on USB_ZERO && USB_OTG2 help You can configure this device to enumerate using the device identifiers of the USB-OTG test device. That means that when diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index 904e57bf6112..ac95b08a7179 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -17,6 +17,7 @@ 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 obj-$(CONFIG_USB_M66592) += m66592-udc.o +obj-$(CONFIG_USB_ARC) += arcotg_udc.o # # USB gadget drivers diff --git a/drivers/usb/gadget/arcotg_udc.c b/drivers/usb/gadget/arcotg_udc.c new file mode 100644 index 000000000000..9f365abdbae6 --- /dev/null +++ b/drivers/usb/gadget/arcotg_udc.c @@ -0,0 +1,3126 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file arcotg_udc.c + * @brief arc otg device controller driver + * @ingroup USB + */ + +/*! + * Include files + */ + +#if 0 +#define DEBUG +#define VERBOSE +#define DUMP_QUEUES +#endif + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/ioport.h> +#include <linux/types.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/platform_device.h> +#include <linux/usb/ch9.h> +#include <linux/usb_gadget.h> +#include <linux/dma-mapping.h> +#include <linux/dmapool.h> +#include <linux/fsl_devices.h> +#include <linux/usb/fsl_xcvr.h> +#include <linux/usb/otg.h> + +#include <asm/byteorder.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/system.h> +#include <asm/dma.h> + +#include "arcotg_udc.h" +#include <asm/arch/arc_otg.h> + +extern void gpio_usbotg_hs_active(void); +extern void gpio_usbotg_fs_active(void); + +static void Ep0Stall(struct arcotg_udc *); +static int ep0_prime_status(struct arcotg_udc *, int); + +static int timeout; + +#undef USB_TRACE + +#define DRIVER_DESC "ARC USBOTG Device Controller driver" +#define DRIVER_AUTHOR "Freescale Semiconductor" +#define DRIVER_VERSION "1 August 2005" + +#define DMA_ADDR_INVALID (~(dma_addr_t)0) + +/*#define DEBUG_FORCE_FS 1 */ + +static const char driver_name[] = "arc_udc"; +static const char driver_desc[] = DRIVER_DESC; + +volatile static struct usb_dr_device *usb_slave_regs; + +/* it is initialized in probe() */ +static struct arcotg_udc *udc_controller; + +/*ep name is important in gadget, it should obey the convention of ep_match()*/ +/* even numbered EPs are OUT or setup, odd are IN/INTERRUPT */ +static const char *const ep_name[] = { + "ep0-control", NULL, /* everyone has ep0 */ + /* 7 configurable endpoints */ + "ep1out", + "ep1in", + "ep2out", + "ep2in", + "ep3out", + "ep3in", + "ep4out", + "ep4in", + "ep5out", + "ep5in", + "ep6out", + "ep6in", + "ep7out", + "ep7in" +}; +static const struct usb_endpoint_descriptor arcotg_ep0_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = 0, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, + .wMaxPacketSize = USB_MAX_CTRL_PAYLOAD, +}; + +static int udc_suspend(struct arcotg_udc *udc); +static int arcotg_udc_suspend(struct device *dev, pm_message_t state); +static int arcotg_udc_resume(struct device *dev); + +/******************************************************************** + * Internal Used Function +********************************************************************/ + +#ifdef DUMP_QUEUES +static void dump_ep_queue(struct arcotg_ep *ep) +{ + int ep_index; + struct arcotg_req *req; + struct ep_td_struct *dtd; + + if (list_empty(&ep->queue)) { + pr_debug("udc: empty\n"); + return; + } + + ep_index = ep_index(ep) * 2 + ep_is_in(ep); + pr_debug("udc: ep=0x%p index=%d\n", ep, ep_index); + + list_for_each_entry(req, &ep->queue, queue) { + pr_debug("udc: req=0x%p dTD count=%d\n", req, req->dtd_count); + pr_debug("udc: dTD head=0x%p tail=0x%p\n", req->head, + req->tail); + + dtd = req->head; + + while (dtd) { + if (le32_to_cpu(dtd->next_td_ptr) & DTD_NEXT_TERMINATE) + break; /* end of dTD list */ + + dtd = dtd->next_td_virt; + } + } +} +#else +static inline void dump_ep_queue(struct arcotg_ep *ep) +{ +} +#endif + +/*! + * done() - retire a request; caller blocked irqs + * @param ep endpoint pointer + * @param req require pointer + * @param status when req->req.status is -EINPROGRESSS, it is input para + * else it will be a output parameter + * req->req.status : in ep_queue() it will be set as -EINPROGRESS + */ +static void done(struct arcotg_ep *ep, struct arcotg_req *req, int status) +{ + struct arcotg_udc *udc = NULL; + unsigned char stopped = ep->stopped; + + udc = (struct arcotg_udc *)ep->udc; + + pr_debug("udc: req=0x%p\n", req); + if (req->head) { + pr_debug("udc: freeing head=0x%p\n", req->head); + dma_pool_free(udc->dtd_pool, req->head, req->head->td_dma); + } + + /* the req->queue pointer is used by ep_queue() func, in which + * the request will be added into a udc_ep->queue 'd tail + * so here the req will be dropped from the ep->queue + */ + list_del_init(&req->queue); + + /* req.status should be set as -EINPROGRESS in ep_queue() */ + if (req->req.status == -EINPROGRESS) + req->req.status = status; + else + status = req->req.status; + + pr_debug("udc: req=0x%p mapped=%x\n", req, req->mapped); + + if (req->mapped) { + pr_debug("udc: calling dma_unmap_single(buf,%s) req=0x%p " + "a=0x%x len=%d\n", + ep_is_in(ep) ? "to_dvc" : "from_dvc", + req, req->req.dma, req->req.length); + + dma_unmap_single(ep->udc->gadget.dev.parent, + req->req.dma, req->req.length, + ep_is_in(ep) ? DMA_TO_DEVICE : + DMA_FROM_DEVICE); + + req->req.dma = DMA_ADDR_INVALID; + req->mapped = 0; + pr_debug("udc: req=0x%p set req.dma=0x%x\n", req, req->req.dma); + } else { + if ((req->req.length != 0) + && (req->req.dma != DMA_ADDR_INVALID)) { + pr_debug("udc: calling dma_sync_single_for_cpu(buf,%s) " + "req=0x%p dma=0x%x len=%d\n", + ep_is_in(ep) ? "to_dvc" : "from_dvc", req, + req->req.dma, req->req.length); + + dma_sync_single_for_cpu(ep->udc->gadget.dev.parent, + req->req.dma, req->req.length, + ep_is_in(ep) ? DMA_TO_DEVICE : + DMA_FROM_DEVICE); + } + } + + if (status && (status != -ESHUTDOWN)) { + pr_debug("udc: complete %s req 0c%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->udc->lock); + + /* this complete() should a func implemented by gadget layer, + * eg fsg->bulk_in_complete() */ + if (req->req.complete) { + pr_debug("udc: calling gadget's complete() req=0x%p\n", req); + req->req.complete(&ep->ep, &req->req); + pr_debug("udc: back from gadget's complete()\n"); + } + + spin_lock(&ep->udc->lock); + ep->stopped = stopped; +} + +/*! + * nuke(): delete all requests related to this ep + * called by ep_disable() within spinlock held + * add status paramter? + * @param ep endpoint pointer + * @param status current status + */ +static void nuke(struct arcotg_ep *ep, int status) +{ + pr_debug("udc: ep=0x%p\n", ep); + ep->stopped = 1; + + /* Whether this eq has request linked */ + while (!list_empty(&ep->queue)) { + struct arcotg_req *req = NULL; + + req = list_entry(ep->queue.next, struct arcotg_req, queue); + + done(ep, req, status); + } + dump_ep_queue(ep); +} + +/*------------------------------------------------------------------ + Internal Hardware related function + ------------------------------------------------------------------*/ +extern void fsl_platform_set_vbus_power(struct fsl_usb2_platform_data *pdata, + int on); + +/* + * init device controller + * @param qh_addr the aligned virt addr of ep QH addr + * @param dev device controller pointer + */ +static int dr_controller_setup(struct arcotg_udc *udc) +{ + unsigned int tmp = 0, portctrl = 0; + void *qh_addr = udc->ep_qh; + struct device *dev __attribute((unused)) = udc->gadget.dev.parent; + struct fsl_usb2_platform_data *pdata; + + pdata = udc->pdata; + + pr_debug("udc: dev=0x%p\n", dev); + + /* before here, make sure usb_slave_regs has been initialized */ + if (!qh_addr) + return -EINVAL; + + /* Stop and reset the usb controller */ + tmp = le32_to_cpu(usb_slave_regs->usbcmd); + tmp &= ~USB_CMD_RUN_STOP; + usb_slave_regs->usbcmd = cpu_to_le32(tmp); + + tmp = le32_to_cpu(usb_slave_regs->usbcmd); + tmp |= USB_CMD_CTRL_RESET; + usb_slave_regs->usbcmd = cpu_to_le32(tmp); + + /* Wait for reset to complete */ + timeout = 10000000; + while ((le32_to_cpu(usb_slave_regs->usbcmd) & USB_CMD_CTRL_RESET) && + --timeout) { + continue; + } + if (timeout == 0) { + printk(KERN_ERR "%s: TIMEOUT\n", __FUNCTION__); + return -ETIMEDOUT; + } + + /* Set the controller as device mode */ + tmp = le32_to_cpu(usb_slave_regs->usbmode); + tmp |= USB_MODE_CTRL_MODE_DEVICE; + /* Disable Setup Lockout */ + tmp |= USB_MODE_SETUP_LOCK_OFF; + usb_slave_regs->usbmode = cpu_to_le32(tmp); + + if (pdata->xcvr_ops && pdata->xcvr_ops->set_device) + pdata->xcvr_ops->set_device(); + + /* Clear the setup status */ + usb_slave_regs->usbsts = 0; + + tmp = udc->ep_qh_dma; + tmp &= USB_EP_LIST_ADDRESS_MASK; + usb_slave_regs->endpointlistaddr = cpu_to_le32(tmp); + + pr_debug("udc: vir[qh_base]=0x%p phy[qh_base]=0x%8x epla_reg=0x%8x\n", + qh_addr, (int)tmp, + le32_to_cpu(usb_slave_regs->endpointlistaddr)); + + portctrl = le32_to_cpu(usb_slave_regs->portsc1); + portctrl &= ~PORTSCX_PHY_TYPE_SEL; + + portctrl |= udc->xcvr_type; + +#ifdef DEBUG_FORCE_FS + portctrl |= 0x1000000; +#endif + + usb_slave_regs->portsc1 = cpu_to_le32(portctrl); + + fsl_platform_set_vbus_power(pdata, 0); + + return 0; +} + +/*! + * just Enable DR irq reg and Set Dr controller Run + * @param udc device controller pointer + */ + +static void dr_controller_run(struct arcotg_udc *udc) +{ + u32 tmp; + + pr_debug("%s\n", __FUNCTION__); + + /*Enable DR irq reg */ + tmp = USB_INTR_INT_EN | USB_INTR_ERR_INT_EN | + USB_INTR_PTC_DETECT_EN | USB_INTR_RESET_EN | + USB_INTR_DEVICE_SUSPEND | USB_INTR_SYS_ERR_EN; + + usb_slave_regs->usbintr = le32_to_cpu(tmp); + + /* Clear stopped bit */ + udc->stopped = 0; + + /* Set the controller as device mode */ + tmp = le32_to_cpu(usb_slave_regs->usbmode); + tmp |= USB_MODE_CTRL_MODE_DEVICE; + usb_slave_regs->usbmode = cpu_to_le32(tmp); + + /* Set controller to Run */ + tmp = le32_to_cpu(usb_slave_regs->usbcmd); + tmp |= USB_CMD_RUN_STOP; + usb_slave_regs->usbcmd = le32_to_cpu(tmp); + + return; +} + +/* + * just Disable DR irq reg and Set Dr controller Stop + * @param udc device controller pointer + */ +static void dr_controller_stop(struct arcotg_udc *udc) +{ + unsigned int tmp; + + pr_debug("%s\n", __FUNCTION__); + + /* if we're in OTG mode, and the Host is currently using the port, + * stop now and don't rip the controller out from under the + * ehci driver + */ + if (udc->gadget.is_otg) { + if (!(usb_slave_regs->otgsc & OTGSC_STS_USB_ID)) { + pr_debug("udc: Leaving early\n"); + return; + } + } + + /* disable all INTR */ + usb_slave_regs->usbintr = 0; + + /* Set stopped bit for isr */ + udc->stopped = 1; + + /* set controller to Stop */ + tmp = le32_to_cpu(usb_slave_regs->usbcmd); + tmp &= ~USB_CMD_RUN_STOP; + usb_slave_regs->usbcmd = le32_to_cpu(tmp); +} + +void dr_ep_setup(unsigned char ep_num, unsigned char dir, unsigned char ep_type) +{ + unsigned int tmp_epctrl = 0; + + tmp_epctrl = le32_to_cpu(usb_slave_regs->endptctrl[ep_num]); + if (dir) { + if (ep_num) + tmp_epctrl |= EPCTRL_TX_DATA_TOGGLE_RST; + tmp_epctrl |= EPCTRL_TX_ENABLE; + tmp_epctrl |= + ((unsigned int)(ep_type) << EPCTRL_TX_EP_TYPE_SHIFT); + } else { + if (ep_num) + tmp_epctrl |= EPCTRL_RX_DATA_TOGGLE_RST; + tmp_epctrl |= EPCTRL_RX_ENABLE; + tmp_epctrl |= + ((unsigned int)(ep_type) << EPCTRL_RX_EP_TYPE_SHIFT); + } + + usb_slave_regs->endptctrl[ep_num] = cpu_to_le32(tmp_epctrl); + + /* wait for the write reg to finish */ + timeout = 10000000; + while ((!(le32_to_cpu(usb_slave_regs->endptctrl[ep_num]) & + (tmp_epctrl & (EPCTRL_TX_ENABLE | EPCTRL_RX_ENABLE)))) + && --timeout) { + continue; + } + if (timeout == 0) { + printk(KERN_ERR "%s: TIMEOUT\n", __FUNCTION__); + } +} + +static void dr_ep_change_stall(unsigned char ep_num, unsigned char dir, + int value) +{ + unsigned int tmp_epctrl = 0; + + tmp_epctrl = le32_to_cpu(usb_slave_regs->endptctrl[ep_num]); + + if (value) { + /* set the stall bit */ + if (dir) + tmp_epctrl |= EPCTRL_TX_EP_STALL; + else + tmp_epctrl |= EPCTRL_RX_EP_STALL; + } else { + /* clear the stall bit and reset data toggle */ + if (dir) { + tmp_epctrl &= ~EPCTRL_TX_EP_STALL; + tmp_epctrl |= EPCTRL_TX_DATA_TOGGLE_RST; + } else { + tmp_epctrl &= ~EPCTRL_RX_EP_STALL; + tmp_epctrl |= EPCTRL_RX_DATA_TOGGLE_RST; + } + + } + usb_slave_regs->endptctrl[ep_num] = cpu_to_le32(tmp_epctrl); +} + +/******************************************************************** + Internal Structure Build up functions +********************************************************************/ + +/*! + * set the Endpoint Capabilites field of QH + * @param handle udc handler + * @param ep_num endpoint number + * @param dir in or out + * @param ep_type USB_ENDPOINT_XFER_CONTROL or other + * @param max_pkt_len max packet length of this endpoint + * @param zlt Zero Length Termination Select + * @param mult Mult field + */ +static void struct_ep_qh_setup(void *handle, unsigned char ep_num, + unsigned char dir, unsigned char ep_type, + unsigned int max_pkt_len, + unsigned int zlt, unsigned char mult) +{ + struct arcotg_udc *udc = NULL; + struct ep_queue_head *p_QH = NULL; + unsigned int tmp = 0; + + udc = (struct arcotg_udc *)handle; + + p_QH = &udc->ep_qh[2 * ep_num + dir]; + + /* set the Endpoint Capabilites Reg of QH */ + switch (ep_type) { + case USB_ENDPOINT_XFER_CONTROL: + /* Interrupt On Setup (IOS). for control ep */ + tmp = (max_pkt_len << EP_QUEUE_HEAD_MAX_PKT_LEN_POS) | + EP_QUEUE_HEAD_IOS; + break; + case USB_ENDPOINT_XFER_ISOC: + tmp = (max_pkt_len << EP_QUEUE_HEAD_MAX_PKT_LEN_POS) | + (mult << EP_QUEUE_HEAD_MULT_POS); + break; + case USB_ENDPOINT_XFER_BULK: + case USB_ENDPOINT_XFER_INT: + tmp = max_pkt_len << EP_QUEUE_HEAD_MAX_PKT_LEN_POS; + if (zlt) + tmp |= EP_QUEUE_HEAD_ZLT_SEL; + break; + default: + pr_debug("udc: error ep type is %d\n", ep_type); + return; + } + p_QH->max_pkt_length = le32_to_cpu(tmp); + + return; +} + +/*! + * This function only to make code looks good + * it is a collection of struct_ep_qh_setup and dr_ep_setup for ep0 + * ep0 should set OK before the bind() of gadget layer + * @param udc device controller pointer + */ +static void ep0_dr_and_qh_setup(struct arcotg_udc *udc) +{ + /* the intialization of an ep includes: fields in QH, Regs, + * arcotg_ep struct */ + struct_ep_qh_setup(udc, 0, USB_RECV, + USB_ENDPOINT_XFER_CONTROL, USB_MAX_CTRL_PAYLOAD, + 0, 0); + struct_ep_qh_setup(udc, 0, USB_SEND, + USB_ENDPOINT_XFER_CONTROL, USB_MAX_CTRL_PAYLOAD, + 0, 0); + dr_ep_setup(0, USB_RECV, USB_ENDPOINT_XFER_CONTROL); + dr_ep_setup(0, USB_SEND, USB_ENDPOINT_XFER_CONTROL); + + return; + +} + +/*********************************************************************** + Endpoint Management Functions +***********************************************************************/ + +/*! + * when configurations are set, or when interface settings change + * for example the do_set_interface() in gadget layer, + * the driver will enable or disable the relevant endpoints + * ep0 will not use this func it is enable in probe() + * @param _ep endpoint pointer + * @param desc endpoint descriptor pointer + * @return The function returns 0 on success or -1 if failed + */ +static int arcotg_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct arcotg_udc *udc = NULL; + struct arcotg_ep *ep = NULL; + unsigned short max = 0; + unsigned char mult = 0, zlt = 0; + int retval = 0; + unsigned long flags = 0; + char *val = NULL; /* for debug */ + + ep = container_of(_ep, struct arcotg_ep, ep); + + pr_debug("udc: %s ep.name=%s\n", __FUNCTION__, ep->ep.name); + /* catch various bogus parameters */ + if (!_ep || !desc || ep->desc || _ep->name == ep_name[0] || + (desc->bDescriptorType != USB_DT_ENDPOINT)) + /* FIXME: add judge for ep->bEndpointAddress */ + return -EINVAL; + + udc = ep->udc; + + if (!udc->driver || (udc->gadget.speed == USB_SPEED_UNKNOWN)) + return -ESHUTDOWN; + + max = le16_to_cpu(desc->wMaxPacketSize); + retval = -EINVAL; + + /* check the max package size validate for this endpoint */ + /* Refer to USB2.0 spec table 9-13, + */ + switch (desc->bmAttributes & 0x03) { + case USB_ENDPOINT_XFER_BULK: + if (strstr(ep->ep.name, "-iso") + || strstr(ep->ep.name, "-int")) + goto en_done; + mult = 0; + zlt = 1; + switch (udc->gadget.speed) { + case USB_SPEED_HIGH: + if ((max == 128) || (max == 256) || (max == 512)) + break; + default: + switch (max) { + case 4: + case 8: + case 16: + case 32: + case 64: + break; + default: + case USB_SPEED_LOW: + goto en_done; + } + } + break; + case USB_ENDPOINT_XFER_INT: + if (strstr(ep->ep.name, "-iso")) /* bulk is ok */ + goto en_done; + mult = 0; + zlt = 1; + switch (udc->gadget.speed) { + case USB_SPEED_HIGH: + if (max <= 1024) + break; + case USB_SPEED_FULL: + if (max <= 64) + break; + default: + if (max <= 8) + break; + goto en_done; + } + break; + case USB_ENDPOINT_XFER_ISOC: + if (strstr(ep->ep.name, "-bulk") || strstr(ep->ep.name, "-int")) + goto en_done; + mult = (unsigned char) + (1 + ((le16_to_cpu(desc->wMaxPacketSize) >> 11) & 0x03)); + zlt = 0; + switch (udc->gadget.speed) { + case USB_SPEED_HIGH: + if (max <= 1024) + break; + case USB_SPEED_FULL: + if (max <= 1023) + break; + default: + goto en_done; + } + break; + case USB_ENDPOINT_XFER_CONTROL: + if (strstr(ep->ep.name, "-iso") || strstr(ep->ep.name, "-int")) + goto en_done; + mult = 0; + zlt = 1; + switch (udc->gadget.speed) { + case USB_SPEED_HIGH: + case USB_SPEED_FULL: + switch (max) { + case 1: + case 2: + case 4: + case 8: + case 16: + case 32: + case 64: + break; + default: + goto en_done; + } + case USB_SPEED_LOW: + switch (max) { + case 1: + case 2: + case 4: + case 8: + break; + default: + goto en_done; + } + default: + goto en_done; + } + break; + + default: + goto en_done; + } + + /* here initialize variable of ep */ + spin_lock_irqsave(&udc->lock, flags); + ep->ep.maxpacket = max; + ep->desc = desc; + ep->stopped = 0; + + /* hardware special operation */ + + /* Init EPx Queue Head (Ep Capabilites field in QH + * according to max, zlt, mult) */ + struct_ep_qh_setup((void *)udc, (unsigned char)ep_index(ep), + (unsigned char) + ((desc->bEndpointAddress & USB_DIR_IN) ? + USB_SEND : USB_RECV), (unsigned char) + (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK), + max, zlt, mult); + + /* Init endpoint x at here */ + /* 83xx RM chapter 16.3.2.24, here init the endpoint ctrl reg */ + dr_ep_setup((unsigned char)ep_index(ep), + (unsigned char)((desc->bEndpointAddress & USB_DIR_IN) + ? USB_SEND : USB_RECV), + (unsigned char)(desc->bmAttributes & + USB_ENDPOINT_XFERTYPE_MASK)); + + /* Now HW will be NAKing transfers to that EP, + * until a buffer is queued to it. */ + + /* should have stop the lock */ + spin_unlock_irqrestore(&udc->lock, flags); + retval = 0; + switch (desc->bmAttributes & 0x03) { + case USB_ENDPOINT_XFER_BULK: + val = "bulk"; + break; + case USB_ENDPOINT_XFER_ISOC: + val = "iso"; + break; + case USB_ENDPOINT_XFER_INT: + val = "intr"; + break; + default: + val = "ctrl"; + break; + } + + pr_debug("udc: enabled %s (ep%d%s-%s) maxpacket %d\n", ep->ep.name, + ep->desc->bEndpointAddress & 0x0f, + (desc->bEndpointAddress & USB_DIR_IN) ? "in" : "out", + val, max); + en_done: + return retval; +} + +/*! + * disable endpoint + * Any pending and uncomplete req will complete with status (-ESHUTDOWN) + * @param _ep the ep being unconfigured. May not be ep0 + * @return The function returns 0 on success or -1 if failed + */ +static int arcotg_ep_disable(struct usb_ep *_ep) +{ + struct arcotg_udc *udc = NULL; + struct arcotg_ep *ep = NULL; + unsigned long flags = 0; + + ep = container_of(_ep, struct arcotg_ep, ep); + if (!_ep || !ep->desc) { + pr_debug("udc: %s not enabled\n", _ep ? ep->ep.name : NULL); + return -EINVAL; + } + + udc = (struct arcotg_udc *)ep->udc; + + spin_lock_irqsave(&udc->lock, flags); + + /* Nuke all pending requests (does flush) */ + nuke(ep, -ESHUTDOWN); + + ep->desc = 0; + ep->stopped = 1; + spin_unlock_irqrestore(&udc->lock, flags); + + pr_debug("udc: disabled %s OK\n", _ep->name); + return 0; +} + +/*! + * allocate a request object used by this endpoint + * the main operation is to insert the req->queue to the eq->queue + * @param _ep the ep being unconfigured. May not be ep0 + * @param gfp_flags mem flags + * @return Returns the request, or null if one could not be allocated + */ +static struct usb_request *arcotg_alloc_request(struct usb_ep *_ep, + gfp_t gfp_flags) +{ + struct arcotg_req *req = NULL; + + req = kmalloc(sizeof *req, gfp_flags); + if (!req) + return NULL; + + memset(req, 0, sizeof *req); + req->req.dma = DMA_ADDR_INVALID; + pr_debug("udc: req=0x%p set req.dma=0x%x\n", req, req->req.dma); + INIT_LIST_HEAD(&req->queue); + + return &req->req; +} + +/*! + * free request memory + * @param _ep the ep being unconfigured. May not be ep0 + * @param _req usb request pointer + */ +static void arcotg_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + struct arcotg_req *req; + + req = container_of(_req, struct arcotg_req, req); + pr_debug("udc: req=0x%p\n", req); + + if (_req) + kfree(req); +} + +/*! + * Allocate an I/O buffer for the ep->req->buf. + * @param _ep endpoint pointer + * @param bytes length of the desired buffer + * @param dma pointer to the buffer's DMA address; must be valid + * when gadget layer calls this function, ma is &req->dma + * @param gfp_flags GFP_* flags to use + * @return Returns a new buffer, or null if one could not be allocated + */ +static void *arcotg_alloc_buffer(struct usb_ep *_ep, unsigned bytes, + dma_addr_t * dma, gfp_t gfp_flags) +{ + void *retval = NULL; + + if (!bytes) + return 0; + + retval = kmalloc(bytes, gfp_flags); + + if (retval) + *dma = virt_to_phys(retval); + + pr_debug("udc: ep=%s buffer=0x%p dma=0x%x len=%d\n", + _ep->name, retval, *dma, bytes); + + return retval; +} + +/*! + * Free an I/O buffer for the ep->req->buf + * @param _ep endpoint pointer + * @param buf data buf pointer + * @param dma for 834x, we will not touch dma field + * @param bytes buffer size + */ +static void arcotg_free_buffer(struct usb_ep *_ep, void *buf, + dma_addr_t dma, unsigned bytes) +{ + pr_debug("udc: buf=0x%p dma=0x%x\n", buf, dma); + if (buf) + kfree(buf); +} + +/*-------------------------------------------------------------------------*/ + +/*! + * link req's dTD queue to the end of ep's QH's dTD queue. + * @param ep endpoint pointer + * @param req request pointer + * @return The function returns 0 on success or -1 if failed + */ +static int arcotg_queue_td(struct arcotg_ep *ep, struct arcotg_req *req) +{ + int i = ep_index(ep) * 2 + ep_is_in(ep); + u32 temp, bitmask, tmp_stat; + struct ep_queue_head *dQH = &ep->udc->ep_qh[i]; + + pr_debug("udc: queue req=0x%p to ep index %d\n", req, i); + bitmask = (ep_is_in(ep)) ? (1 << (ep_index(ep) + 16)) : + (1 << (ep_index(ep))); + + /* check if the pipe is empty */ + if (!(list_empty(&ep->queue))) { + /* Add td to the end */ + struct arcotg_req *lastreq; + lastreq = list_entry(ep->queue.prev, struct arcotg_req, queue); + + WARN_ON(req->head->td_dma & 31); + lastreq->tail->next_td_ptr = req->head->td_dma; + lastreq->tail->next_td_virt = req->head; + + pr_debug("udc: ep's queue not empty. lastreq=0x%p\n", lastreq); + + /* Read prime bit, if 1 goto done */ + if (usb_slave_regs->endpointprime & cpu_to_le32(bitmask)) { + pr_debug("udc: ep's already primed\n"); + goto out; + } + + timeout = 10000000; + do { + /* Set ATDTW bit in USBCMD */ + usb_slave_regs->usbcmd |= cpu_to_le32(USB_CMD_ATDTW); + + /* Read correct status bit */ + tmp_stat = le32_to_cpu(usb_slave_regs->endptstatus) & + bitmask; + + } while ((!(usb_slave_regs->usbcmd & + cpu_to_le32(USB_CMD_ATDTW))) + && --timeout); + + if (timeout == 0) { + printk(KERN_ERR "%s: TIMEOUT\n", __FUNCTION__); + } + + /* Write ATDTW bit to 0 */ + usb_slave_regs->usbcmd &= cpu_to_le32(~USB_CMD_ATDTW); + + if (tmp_stat) + goto out; + + /* fall through to Case 1: List is empty */ + } + + /* Write dQH next pointer and terminate bit to 0 */ + WARN_ON(req->head->td_dma & 31); + dQH->next_dtd_ptr = cpu_to_le32(req->head->td_dma); + + /* Clear active and halt bit */ + temp = cpu_to_le32(~(EP_QUEUE_HEAD_STATUS_ACTIVE | + EP_QUEUE_HEAD_STATUS_HALT)); + dQH->size_ioc_int_sts &= temp; + + /* Prime endpoint by writing 1 to ENDPTPRIME */ + temp = (ep_is_in(ep)) ? (1 << (ep_index(ep) + 16)) : + (1 << (ep_index(ep))); + + pr_debug("udc: setting endpointprime. temp=0x%x (bitmask=0x%x)\n", + temp, bitmask); + usb_slave_regs->endpointprime |= cpu_to_le32(temp); + + out: + return 0; +} + +static int arcotg_build_dtd(struct arcotg_req *req, unsigned max, + struct ep_td_struct **address, + struct arcotg_udc *udc) +{ + unsigned length; + u32 swap_temp; + struct ep_td_struct *dtd; + dma_addr_t handle; + + /* how big will this packet be? */ + length = min(req->req.length - req->req.actual, max); + + dtd = dma_pool_alloc(udc->dtd_pool, GFP_KERNEL, &handle); + pr_debug("udc: dma_pool_alloc() ep(0x%p)=%s virt=0x%p dma=0x%x\n", + req->ep, req->ep->name, dtd, handle); + + /* check alignment - must be 32 byte aligned (bits 4:0 == 0) */ + BUG_ON((u32) dtd & 31); + + memset(dtd, 0, sizeof(struct ep_td_struct)); + dtd->td_dma = handle; + + /* Fill in the transfer size; set interrupt on every dtd; + set active bit */ + swap_temp = ((length << DTD_LENGTH_BIT_POS) | DTD_IOC + | DTD_STATUS_ACTIVE); + + dtd->size_ioc_sts = cpu_to_le32(swap_temp); + + /* Clear reserved field */ + swap_temp = cpu_to_le32(dtd->size_ioc_sts); + swap_temp &= ~DTD_RESERVED_FIELDS; + dtd->size_ioc_sts = cpu_to_le32(swap_temp); + + pr_debug("udc: req=0x%p dtd=0x%p req.dma=0x%x req.length=%d " + "length=%d size_ioc_sts=0x%x\n", + req, dtd, req->req.dma, req->req.length, + length, dtd->size_ioc_sts); + + /* Init all of buffer page pointers */ + swap_temp = (u32) (req->req.dma + req->req.actual); + dtd->buff_ptr0 = cpu_to_le32(swap_temp); + dtd->buff_ptr1 = cpu_to_le32(swap_temp + 0x1000); + dtd->buff_ptr2 = cpu_to_le32(swap_temp + 0x2000); + dtd->buff_ptr3 = cpu_to_le32(swap_temp + 0x3000); + dtd->buff_ptr4 = cpu_to_le32(swap_temp + 0x4000); + + req->req.actual += length; + *address = dtd; + + return length; +} + +/*! + * add USB request to dtd queue + * @param req USB request + * @param dev device pointer + * @return Returns zero on success , or a negative error code + */ +static int arcotg_req_to_dtd(struct arcotg_req *req, struct arcotg_udc *udc) +{ + unsigned max; + unsigned count; + int is_last; + int is_first = 1; + struct ep_td_struct *last_addr = NULL, *addr; + + pr_debug("udc: req=0x%p\n", req); + + max = EP_MAX_LENGTH_TRANSFER; + do { + count = arcotg_build_dtd(req, max, &addr, udc); + + if (is_first) { + is_first = 0; + req->head = addr; + } else { + if (!last_addr) { + /* FIXME last_addr not set. iso only + * case, which we don't do yet + */ + pr_debug("udc: wiping out something at 0!!\n"); + } + + last_addr->next_td_ptr = cpu_to_le32(addr->td_dma); + last_addr->next_td_virt = addr; + last_addr = addr; + } + + /* last packet is usually short (or a zlp) */ + if (unlikely(count != max)) + is_last = 1; + else if (likely(req->req.length != req->req.actual) || + req->req.zero) + is_last = 0; + else + is_last = 1; + + req->dtd_count++; + } while (!is_last); + + addr->next_td_ptr = cpu_to_le32(DTD_NEXT_TERMINATE); + addr->next_td_virt = NULL; + req->tail = addr; + + return 0; +} + +/*! + * add transfer request to queue + * @param _ep endpoint pointer + * @param _req request pointer + * @param gfp_flags GFP_* flags to use + * @return Returns zero on success , or a negative error code + */ +static int arcotg_ep_queue(struct usb_ep *_ep, struct usb_request *_req, + gfp_t gfp_flags) +{ + struct arcotg_ep *ep = container_of(_ep, struct arcotg_ep, ep); + struct arcotg_req *req = container_of(_req, struct arcotg_req, req); + struct arcotg_udc *udc; + unsigned long flags; + int is_iso = 0; + + pr_debug("udc: _req=0x%p len=%d\n", _req, _req->length); + + /* catch various bogus parameters */ + if (!_req || !req->req.complete || !req->req.buf + || !list_empty(&req->queue)) { + pr_debug("udc: %s, bad params\n", __FUNCTION__); + return -EINVAL; + } + if (!_ep || (!ep->desc && ep_index(ep))) { + pr_debug("udc: %s, bad ep\n", __FUNCTION__); + return -EINVAL; + } + if (ep->desc->bmAttributes == USB_ENDPOINT_XFER_ISOC) { + if (req->req.length > ep->ep.maxpacket) + return -EMSGSIZE; + is_iso = 1; + } + + udc = ep->udc; + if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + + req->ep = ep; + + /* if data phase is absent send the status phase */ + if ((ep_index(ep) == 0)) { + if (udc->ep0_state != DATA_STATE_XMIT && + udc->ep0_state != DATA_STATE_RECV && + (udc->local_setup_buff).wLength == 0) { + if (ep0_prime_status(udc, EP_DIR_IN)) + Ep0Stall(udc); + else + return 0; + } + } + + /* map virtual address to hardware */ + if (req->req.dma == DMA_ADDR_INVALID) { + req->req.dma = dma_map_single(ep->udc->gadget.dev.parent, + req->req.buf, + req->req.length, ep_is_in(ep) + ? DMA_TO_DEVICE : + DMA_FROM_DEVICE); + req->mapped = 1; + pr_debug("udc: called dma_map_single(buffer,%s) req=0x%p " + "buf=0x%p dma=0x%x len=%d\n", + ep_is_in(ep) ? "to_dvc" : "from_dvc", + req, req->req.buf, req->req.dma, req->req.length); + pr_debug("udc: req=0x%p set req.dma=0x%x\n", req, req->req.dma); + } else { + dma_sync_single_for_device(ep->udc->gadget.dev.parent, + req->req.dma, req->req.length, + ep_is_in(ep) ? DMA_TO_DEVICE : + DMA_FROM_DEVICE); + + req->mapped = 0; + pr_debug("udc: called dma_sync_single_for_device(buffer,%s) " + "req=0x%p buf=0x%p dma=0x%x len=%d\n", + ep_is_in(ep) ? "to_dvc" : "from_dvc", + req, req->req.buf, req->req.dma, req->req.length); + } + + req->req.status = -EINPROGRESS; + req->req.actual = 0; + req->dtd_count = 0; + + spin_lock_irqsave(&udc->lock, flags); + + /* push the dtds to device queue */ + if (!arcotg_req_to_dtd(req, udc)) + arcotg_queue_td(ep, req); + else + return -ENOMEM; + + /* EP0 */ + if ((ep_index(ep) == 0)) { + udc->ep0_state = DATA_STATE_XMIT; + pr_debug("udc: ep0_state now DATA_STATE_XMIT\n"); + } + + /* put this req at the end of the ep's queue */ + /* irq handler advances the queue */ + if (req != NULL) + list_add_tail(&req->queue, &ep->queue); + + dump_ep_queue(ep); + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +/*! + * remove the endpoint buffer + * @param _ep endpoint pointer + * @param _req usb request pointer + * @return Returns zero on success , or a negative error code + */ +static int arcotg_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct arcotg_ep *ep = container_of(_ep, struct arcotg_ep, ep); + struct arcotg_req *req; + unsigned long flags; + + pr_debug("%s\n", __FUNCTION__); + if (!_ep || !_req) + return -EINVAL; + + spin_lock_irqsave(&ep->udc->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->udc->lock, flags); + return -EINVAL; + } + pr_debug("udc: req=0x%p\n", req); + + done(ep, req, -ECONNRESET); + + spin_unlock_irqrestore(&ep->udc->lock, flags); + return 0; + +} + +/*-------------------------------------------------------------------------*/ + +/*! + * modify the endpoint halt feature + * @param _ep the non-isochronous endpoint being stalled + * @param value 1--set halt 0--clear halt + * @return Returns zero, or a negative error code + */ +static int _arcotg_ep_set_halt(struct usb_ep *_ep, int value) +{ + + struct arcotg_ep *ep = NULL; + unsigned long flags = 0; + int status = -EOPNOTSUPP; /* operation not supported */ + unsigned char ep_dir = 0, ep_num = 0; + struct arcotg_udc *udc = NULL; + + ep = container_of(_ep, struct arcotg_ep, ep); + udc = ep->udc; + if (!_ep || !ep->desc) { + status = -EINVAL; + goto out; + } + + if (ep->desc->bmAttributes == USB_ENDPOINT_XFER_ISOC) { + status = -EOPNOTSUPP; + goto out; + } + + /* Attemp to halt IN ep will fail if any transfer requests + are still queue */ + if (value && ep_is_in(ep) && !list_empty(&ep->queue)) { + status = -EAGAIN; + goto out; + } + + status = 0; + ep_dir = ep_is_in(ep) ? USB_SEND : USB_RECV; + ep_num = (unsigned char)(ep_index(ep)); + spin_lock_irqsave(&ep->udc->lock, flags); + dr_ep_change_stall(ep_num, ep_dir, value); + spin_unlock_irqrestore(&ep->udc->lock, flags); + + if (ep_index(ep) == 0) { + udc->ep0_state = WAIT_FOR_SETUP; + pr_debug("udc: ep0_state now WAIT_FOR_SETUP\n"); + udc->ep0_dir = 0; + } + out: + pr_debug("udc: %s %s halt rc=%d\n", + ep->ep.name, value ? "set" : "clear", status); + + return status; +} + +static int arcotg_ep_set_halt(struct usb_ep *_ep, int value) +{ + return (_arcotg_ep_set_halt(_ep, value)); +} + +static int arcotg_fifo_status(struct usb_ep *_ep) +{ + struct arcotg_ep *ep; + struct arcotg_udc *udc; + int size = 0; + u32 bitmask; + struct ep_queue_head *d_qh; + + ep = container_of(_ep, struct arcotg_ep, ep); + if (!_ep || (!ep->desc && ep_index(ep) != 0)) + return -ENODEV; + + udc = (struct arcotg_udc *)ep->udc; + + if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + + d_qh = &ep->udc->ep_qh[ep_index(ep) * 2 + ep_is_in(ep)]; + + bitmask = (ep_is_in(ep)) ? (1 << (ep_index(ep) + 16)) : + (1 << (ep_index(ep))); + + if (le32_to_cpu(usb_slave_regs->endptstatus) & bitmask) + size = (d_qh->size_ioc_int_sts & DTD_PACKET_SIZE) + >> DTD_LENGTH_BIT_POS; + + pr_debug("%s %u\n", __FUNCTION__, size); + return size; +} + +static void arcotg_fifo_flush(struct usb_ep *_ep) +{ + struct arcotg_ep *ep; + struct arcotg_udc *udc; + u32 bitmask; + int loops = 0; + + ep = container_of(_ep, struct arcotg_ep, ep); + if (!_ep || (!ep->desc && ep_index(ep) != 0)) + return; + + udc = (struct arcotg_udc *)ep->udc; + + if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN) + return; + + bitmask = (ep_is_in(ep)) ? (1 << (ep_index(ep) + 16)) : + (1 << (ep_index(ep))); + + do { + /* set the flush bit, and wait for it to clear */ + usb_slave_regs->endptflush = cpu_to_le32(bitmask); + while (usb_slave_regs->endptflush) + continue; + + /* if ENDPTSTAT bit is set, the flush failed. Retry. */ + if (!(cpu_to_le32(usb_slave_regs->endptstatus) && bitmask)) + break; + } while (++loops > 3); +} + +/*! + * endpoint callback functions + */ +static const struct usb_ep_ops arcotg_ep_ops = { + .enable = arcotg_ep_enable, + .disable = arcotg_ep_disable, + + .alloc_request = arcotg_alloc_request, + .free_request = arcotg_free_request, + + .alloc_buffer = arcotg_alloc_buffer, + .free_buffer = arcotg_free_buffer, + + .queue = arcotg_ep_queue, + .dequeue = arcotg_ep_dequeue, + + .set_halt = arcotg_ep_set_halt, + + .fifo_status = arcotg_fifo_status, + .fifo_flush = arcotg_fifo_flush, +}; + +/*------------------------------------------------------------------------- + Gadget Driver Layer Operations +-------------------------------------------------------------------------*/ + +/************************************************************************* + Gadget Driver Layer Operations +*************************************************************************/ + +/*! + * Get the current frame number (from DR frame_index Reg ) + * @param gadget gadget pointer + * @return return frame count + */ +static int arcotg_get_frame(struct usb_gadget *gadget) +{ + return (int)(le32_to_cpu(usb_slave_regs->frindex) & USB_FRINDEX_MASKS); +} + +/*----------------------------------------------------------------------- + * Tries to wake up the host connected to this gadget + * + * Return : 0-success + * Negative-this feature not enabled by host or not supported by device hw + * FIXME: RM 16.6.2.2.1 DR support this wake-up feature? + -----------------------------------------------------------------------*/ +static int arcotg_wakeup(struct usb_gadget *gadget) +{ + pr_debug("%s\n", __FUNCTION__); + return -ENOTSUPP; +} + +/*! + * sets the device selfpowered feature + * this affects the device status reported by the hw driver + * to reflect that it now has a local power supply + * usually device hw has register for this feature + * @param gadget gadget pointer + * @param is_selfpowered self powered? + * @return if selfpowered + */ +static int arcotg_set_selfpowered(struct usb_gadget *gadget, int is_selfpowered) +{ + pr_debug("%s\n", __FUNCTION__); + return -ENOTSUPP; +} + +static int can_pullup(struct arcotg_udc *udc) +{ + return udc->driver && udc->softconnect && udc->vbus_active; +} + +/*! + * Notify controller that VBUS is powered, Called by whatever + * detects VBUS sessions + * @param gadger gadger pointer + * @param is_active is active? + * @return Returns zero on success , or a negative error code + */ +static int arcotg_vbus_session(struct usb_gadget *gadget, int is_active) +{ + struct arcotg_udc *udc; + unsigned long flags; + + pr_debug("%s\n", __FUNCTION__); + + udc = container_of(gadget, struct arcotg_udc, gadget); + spin_lock_irqsave(&udc->lock, flags); + + pr_debug("udc: VBUS %s\n", is_active ? "on" : "off"); + udc->vbus_active = (is_active != 0); + +#if 0 /* FIXME manipulate pullups?? check other platforms. */ + if (can_pullup(udc)) + usb_slave_regs->usbcmd |= USB_CMD_RUN_STOP; + else + usb_slave_regs->usbcmd &= ~USB_CMD_RUN_STOP; +#endif + + spin_unlock_irqrestore(&udc->lock, flags); + return 0; +} + +/*! + * constrain controller's VBUS power usage + * This call is used by gadget drivers during SET_CONFIGURATION calls, + * reporting how much power the device may consume. For example, this + * could affect how quickly batteries are recharged. + * Returns zero on success, else negative errno. + * @param gadget gadger pointer + * @param mA power + * @return Returns zero on success , or a negative error code + */ +static int arcotg_vbus_draw(struct usb_gadget *gadget, unsigned mA) +{ + struct arcotg_udc *udc; + + pr_debug("%s\n", __FUNCTION__); + + udc = container_of(gadget, struct arcotg_udc, gadget); + + if (udc->transceiver) + return otg_set_power(udc->transceiver, mA); + + return -ENOTSUPP; +} + +/*! + * Change Data+ pullup status + * this func is used by usb_gadget_connect/disconnet + * @param gadget gadger pointer + * @param is_on on or off + * @return Returns zero on success , or a negative error code + */ +static int arcotg_pullup(struct usb_gadget *gadget, int is_on) +{ + struct arcotg_udc *udc; + + pr_debug("%s\n", __FUNCTION__); + + udc = container_of(gadget, struct arcotg_udc, gadget); + udc->softconnect = (is_on != 0); + if (can_pullup(udc)) + usb_slave_regs->usbcmd |= USB_CMD_RUN_STOP; + else + usb_slave_regs->usbcmd &= ~USB_CMD_RUN_STOP; + + return 0; +} + +static const struct usb_gadget_ops arcotg_gadget_ops = { + .get_frame = arcotg_get_frame, + .wakeup = arcotg_wakeup, + .set_selfpowered = arcotg_set_selfpowered, + .vbus_session = arcotg_vbus_session, + .vbus_draw = arcotg_vbus_draw, + .pullup = arcotg_pullup, +}; + +static void Ep0Stall(struct arcotg_udc *udc) +{ + u32 tmp; + + pr_debug("%s\n", __FUNCTION__); + /* a protocol stall */ + tmp = le32_to_cpu(usb_slave_regs->endptctrl[0]); + tmp |= EPCTRL_TX_EP_STALL | EPCTRL_RX_EP_STALL; + usb_slave_regs->endptctrl[0] = cpu_to_le32(tmp); + udc->ep0_state = WAIT_FOR_SETUP; + pr_debug("udc: ep0_state now WAIT_FOR_SETUP\n"); + udc->ep0_dir = 0; +} + +/*! + * if direction is EP_IN, the status is Device->Host + * if direction is EP_OUT, the status transaction is Device<-Host + * @param udc device controller pointer + * @param direction in or out + * @return Returns zero on success , or a negative error code + */ +static int ep0_prime_status(struct arcotg_udc *udc, int direction) +{ + + struct arcotg_req *req = udc->status_req; + struct arcotg_ep *ep; + int status = 0; + unsigned long flags; + + pr_debug("%s\n", __FUNCTION__); + if (direction == EP_DIR_IN) + udc->ep0_dir = USB_DIR_IN; + else + udc->ep0_dir = USB_DIR_OUT; + + ep = &udc->eps[0]; + udc->ep0_state = WAIT_FOR_OUT_STATUS; + pr_debug("udc: ep0_state now WAIT_FOR_OUT_STATUS\n"); + + req->ep = ep; + req->req.length = 0; + req->req.status = -EINPROGRESS; + req->req.actual = 0; + req->req.complete = NULL; + req->dtd_count = 0; + + spin_lock_irqsave(&udc->lock, flags); + + if ((arcotg_req_to_dtd(req, udc) == 0)) + status = arcotg_queue_td(ep, req); + else + return -ENOMEM; + + if (status) + printk(KERN_ERR "Can't get control status request \n"); + + list_add_tail(&req->queue, &ep->queue); + dump_ep_queue(ep); + + spin_unlock_irqrestore(&udc->lock, flags); + + return status; +} + +static int udc_reset_ep_queue(struct arcotg_udc *udc, u8 pipe) +{ + struct arcotg_ep *ep = get_ep_by_pipe(udc, pipe); + + pr_debug("%s\n", __FUNCTION__); + /* FIXME: collect completed requests? */ + if (!ep->name) + return 0; + + nuke(ep, -ECONNRESET); + + return 0; +} +static void ch9SetAddress(struct arcotg_udc *udc, u16 value, u16 index, + u16 length) +{ + pr_debug("udc: new address=%d\n", value); + + /* Save the new address to device struct */ + udc->device_address = (u8) value; + + /* Update usb state */ + udc->usb_state = USB_STATE_ADDRESS; + + /* Status phase */ + if (ep0_prime_status(udc, EP_DIR_IN)) + Ep0Stall(udc); +} + +static void ch9GetStatus(struct arcotg_udc *udc, u16 value, u16 index, + u16 length) +{ + u16 usb_status = 0; /* fix me to give correct status */ + + struct arcotg_req *req; + struct arcotg_ep *ep; + int status = 0; + unsigned long flags; + + pr_debug("%s\n", __FUNCTION__); + ep = &udc->eps[0]; + + req = container_of(arcotg_alloc_request(&ep->ep, GFP_KERNEL), + struct arcotg_req, req); + req->req.length = 2; + req->req.buf = &usb_status; + req->req.status = -EINPROGRESS; + req->req.actual = 0; + + spin_lock_irqsave(&udc->lock, flags); + + /* data phase */ + if ((arcotg_req_to_dtd(req, udc) == 0)) + status = arcotg_queue_td(ep, req); + else /* no mem */ + goto stall; + + if (status) { + printk(KERN_ERR "Can't respond to getstatus request \n"); + Ep0Stall(udc); + } else { + udc->ep0_state = DATA_STATE_XMIT; + pr_debug("udc: ep0_state now DATA_STATE_XMIT\n"); + } + + list_add_tail(&req->queue, &ep->queue); + dump_ep_queue(ep); + + spin_unlock_irqrestore(&udc->lock, flags); + return; + + stall: + Ep0Stall(udc); +} + +static void ch9SetConfig(struct arcotg_udc *udc, u16 value, u16 index, + u16 length) +{ + pr_debug("udc: 1 calling gadget driver->setup\n"); + udc->ep0_dir = USB_DIR_IN; + if (udc->driver->setup(&udc->gadget, &udc->local_setup_buff) >= 0) { + /* gadget layer deal with the status phase */ + udc->usb_state = USB_STATE_CONFIGURED; + udc->ep0_state = WAIT_FOR_OUT_STATUS; + pr_debug("udc: ep0_state now WAIT_FOR_OUT_STATUS\n"); + } +} + +static void setup_received_irq(struct arcotg_udc *udc, + struct usb_ctrlrequest *setup) +{ + u16 ptc = 0; /* port test control */ + int handled = 1; /* set to zero if we do not handle the message, */ + /* and should pass it to the gadget driver */ + + pr_debug("udc: request=0x%x\n", setup->bRequest); + /* Fix Endian (udc->local_setup_buff is cpu Endian now) */ + setup->wValue = le16_to_cpu(setup->wValue); + setup->wIndex = le16_to_cpu(setup->wIndex); + setup->wLength = le16_to_cpu(setup->wLength); + + udc_reset_ep_queue(udc, 0); + + /* We asume setup only occurs on EP0 */ + if (setup->bRequestType & USB_DIR_IN) + udc->ep0_dir = USB_DIR_IN; + else + udc->ep0_dir = USB_DIR_OUT; + + if ((setup->bRequestType & USB_TYPE_MASK) == USB_TYPE_CLASS) { + /* handle class requests */ + switch (setup->bRequest) { + + case USB_BULK_RESET_REQUEST: + udc->ep0_dir = USB_DIR_IN; + if (udc->driver->setup(&udc->gadget, + &udc->local_setup_buff) >= 0) { + udc->ep0_state = WAIT_FOR_SETUP; + pr_debug("udc: ep0_state now WAIT_FOR_SETUP\n"); + } + break; + + default: + handled = 0; + break; + } + } else if ((setup->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { + /* handle standard requests */ + switch (setup->bRequest) { + + case USB_REQ_GET_STATUS: + if ((setup-> + bRequestType & (USB_DIR_IN | USB_TYPE_STANDARD)) + != (USB_DIR_IN | USB_TYPE_STANDARD)) + break; + ch9GetStatus(udc, setup->wValue, setup->wIndex, + setup->wLength); + break; + + case USB_REQ_SET_ADDRESS: + if (setup->bRequestType != + (USB_DIR_OUT | USB_TYPE_STANDARD | + USB_RECIP_DEVICE)) + break; + ch9SetAddress(udc, setup->wValue, setup->wIndex, + setup->wLength); + break; + + case USB_REQ_SET_CONFIGURATION: + if (setup->bRequestType != + (USB_DIR_OUT | USB_TYPE_STANDARD | + USB_RECIP_DEVICE)) + break; + /* gadget layer take over the status phase */ + ch9SetConfig(udc, setup->wValue, setup->wIndex, + setup->wLength); + break; + case USB_REQ_SET_INTERFACE: + if (setup->bRequestType != + (USB_DIR_OUT | USB_TYPE_STANDARD | + USB_RECIP_INTERFACE)) + break; + udc->ep0_dir = USB_DIR_IN; + if (udc->driver->setup(&udc->gadget, + &udc->local_setup_buff) >= 0) + /* gadget layer take over the status phase */ + break; + /* Requests with no data phase */ + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_FEATURE: + { /* status transaction */ + int rc = -EOPNOTSUPP; + + if ((setup->bRequestType & USB_TYPE_MASK) != + USB_TYPE_STANDARD) + break; + + /* we only support set/clear feature for endpoint */ + if (setup->bRequestType == USB_RECIP_ENDPOINT) { + int dir = (setup->wIndex & 0x0080) ? + EP_DIR_IN : EP_DIR_OUT; + int num = (setup->wIndex & 0x000f); + struct arcotg_ep *ep; + + if (setup->wValue != 0 + || setup->wLength != 0 + || (num * 2 + dir) > USB_MAX_PIPES) + break; + ep = &udc->eps[num * 2 + dir]; + + if (setup->bRequest == + USB_REQ_SET_FEATURE) { + pr_debug("udc: udc: SET_FEATURE" + " doing set_halt\n"); + rc = _arcotg_ep_set_halt(&ep-> + ep, 1); + } else { + pr_debug("udc: CLEAR_FEATURE" + " doing clear_halt\n"); + rc = _arcotg_ep_set_halt(&ep-> + ep, 0); + } + + } else if (setup->bRequestType == + USB_RECIP_DEVICE) { + if (setup->bRequest == + USB_REQ_SET_FEATURE) { + ptc = setup->wIndex >> 8; + rc = 0; + } + if (!udc->gadget.is_otg) + break; + else if (setup->bRequest == + USB_DEVICE_B_HNP_ENABLE) + udc->gadget.b_hnp_enable = 1; + else if (setup->bRequest == + USB_DEVICE_A_HNP_SUPPORT) + udc->gadget.a_hnp_support = 1; + else if (setup->bRequest == + USB_DEVICE_A_ALT_HNP_SUPPORT) + udc->gadget.a_alt_hnp_support = + 1; + rc = 0; + } + if (rc == 0) { + /* send status only if _arcotg_ep_set_halt success */ + if (ep0_prime_status(udc, EP_DIR_IN)) + Ep0Stall(udc); + } + break; + } + default: + handled = 0; + break; + } + } else { + /* vendor requests */ + handled = 0; + } + + if (!handled) { + if (udc->driver->setup(&udc->gadget, &udc->local_setup_buff) + != 0) { + Ep0Stall(udc); + } else if (setup->bRequestType & USB_DIR_IN) { + udc->ep0_state = DATA_STATE_XMIT; + pr_debug("udc: ep0_state now DATA_STATE_XMIT\n"); + } else { + if (setup->wLength != 0) { + udc->ep0_state = DATA_STATE_RECV; + pr_debug + ("udc: ep0_state now DATA_STATE_RECV\n"); + } + } + } + + if (ptc) { + usb_slave_regs->portsc1 |= ptc << 16; + pr_debug("udc: switch to test mode.\n"); + } +} + +static void ep0_req_complete(struct arcotg_udc *udc, struct arcotg_ep *ep0, + struct arcotg_req *req) +{ + pr_debug("udc: req=0x%p ep0_state=0x%x\n", req, udc->ep0_state); + if (udc->usb_state == USB_STATE_ADDRESS) { + /* Set the new address */ + u32 new_address = (u32) udc->device_address; + usb_slave_regs->deviceaddr = cpu_to_le32(new_address << + USB_DEVICE_ADDRESS_BIT_POS); + pr_debug("udc: set deviceaddr to %d\n", + usb_slave_regs-> + deviceaddr >> USB_DEVICE_ADDRESS_BIT_POS); + } + + switch (udc->ep0_state) { + case DATA_STATE_XMIT: + + done(ep0, req, 0); + /* receive status phase */ + if (ep0_prime_status(udc, EP_DIR_OUT)) + Ep0Stall(udc); + break; + + case DATA_STATE_RECV: + + done(ep0, req, 0); + /* send status phase */ + if (ep0_prime_status(udc, EP_DIR_IN)) + Ep0Stall(udc); + break; + + case WAIT_FOR_OUT_STATUS: + done(ep0, req, 0); + udc->ep0_state = WAIT_FOR_SETUP; + pr_debug("udc: ep0_state now WAIT_FOR_SETUP\n"); + break; + + case WAIT_FOR_SETUP: + pr_debug("udc: Unexpected interrupt\n"); + break; + + default: + Ep0Stall(udc); + break; + } +} + +static void tripwire_handler(struct arcotg_udc *udc, u8 ep_num, u8 * buffer_ptr) +{ + u32 temp; + struct ep_queue_head *qh; + + qh = &udc->ep_qh[ep_num * 2 + EP_DIR_OUT]; + + /* Clear bit in ENDPTSETUPSTAT */ + temp = cpu_to_le32(1 << ep_num); + usb_slave_regs->endptsetupstat |= temp; + + /* while a hazard exists when setup package arrives */ + do { + /* Set Setup Tripwire */ + temp = cpu_to_le32(USB_CMD_SUTW); + usb_slave_regs->usbcmd |= temp; + + /* Copy the setup packet to local buffer */ + pr_debug("udc: qh=0x%p copy setup buffer from 0x%p to 0x%p\n", + qh, qh->setup_buffer, buffer_ptr); + memcpy(buffer_ptr, (u8 *) qh->setup_buffer, 8); + } while (!(le32_to_cpu(usb_slave_regs->usbcmd) & USB_CMD_SUTW)); + + /* Clear Setup Tripwire */ + temp = le32_to_cpu(usb_slave_regs->usbcmd); + temp &= ~USB_CMD_SUTW; + usb_slave_regs->usbcmd = le32_to_cpu(temp); + + timeout = 10000000; + while ((usb_slave_regs->endptsetupstat & 1) && --timeout) { + continue; + } + if (timeout == 0) { + printk(KERN_ERR "%s: TIMEOUT\n", __FUNCTION__); + } +} + +/*! + * process_ep_req(): free the completed Tds for this req + * FIXME: ERROR handling for multi-dtd requests + * @param udc device controller pointer + * @param pipe endpoint pipe + * @param req request pointer + * @return Returns zero on success , or a negative error code + */ +static int process_ep_req(struct arcotg_udc *udc, int pipe, + struct arcotg_req *curr_req) +{ + struct ep_td_struct *curr_td, *tmp_td; + int td_complete, actual, remaining_length, j, tmp; + int status = 0; + int errors = 0; + struct ep_queue_head *curr_qh = &udc->ep_qh[pipe]; + int direction = pipe % 2; + + curr_td = curr_req->head; + td_complete = 0; + actual = curr_req->req.length; + + pr_debug + ("udc: curr_req=0x%p curr_td=0x%p actual=%d size_ioc_sts=0x%x\n", + curr_req, curr_td, actual, curr_td->size_ioc_sts); + + for (j = 0; j < curr_req->dtd_count; j++) { + remaining_length = ((le32_to_cpu(curr_td->size_ioc_sts) + & DTD_PACKET_SIZE) >> DTD_LENGTH_BIT_POS); + actual -= remaining_length; + + if ((errors = le32_to_cpu(curr_td->size_ioc_sts) & + DTD_ERROR_MASK)) { + if (errors & DTD_STATUS_HALTED) { + printk(KERN_ERR "dTD error %08x \n", errors); + /* Clear the errors and Halt condition */ + tmp = le32_to_cpu(curr_qh->size_ioc_int_sts); + tmp &= ~errors; + curr_qh->size_ioc_int_sts = cpu_to_le32(tmp); + status = -EPIPE; + /*FIXME clearing active bit, update + * nextTD pointer re-prime ep */ + + break; + } + if (errors & DTD_STATUS_DATA_BUFF_ERR) { + pr_debug("udc: Transfer overflow\n"); + status = -EPROTO; + break; + } else if (errors & DTD_STATUS_TRANSACTION_ERR) { + pr_debug("udc: ISO error\n"); + status = -EILSEQ; + break; + } else + printk(KERN_ERR + "Unknown error has occured (0x%x)!\r\n", + errors); + + } else if (le32_to_cpu(curr_td->size_ioc_sts) & + DTD_STATUS_ACTIVE) { + pr_debug("udc: Request not wholly complete dtd=0x%p\n", + curr_td); + status = REQ_UNCOMPLETE; + return status; + } else if (remaining_length) + if (direction) { + pr_debug + ("udc: Transmit dTD remaining length not zero " + "(rl=%d)\n", remaining_length); + status = -EPROTO; + break; + } else { + td_complete += 1; + break; + } else { + td_complete += 1; + pr_debug("udc: dTD transmitted successful\n"); + } + + if (j != curr_req->dtd_count - 1) + curr_td = curr_td->next_td_virt; + } + + if (status) + return status; + + curr_req->req.actual = actual; + + /* Free dtd for completed/error request */ + curr_td = curr_req->head; + for (j = 0; j < curr_req->dtd_count; j++) { + tmp_td = curr_td; + if (j != curr_req->dtd_count - 1) + curr_td = curr_td->next_td_virt; + pr_debug("udc: freeing dtd 0x%p curr_req=0x%p\n", tmp_td, + curr_req); + dma_pool_free(udc->dtd_pool, tmp_td, tmp_td->td_dma); + } + + return status; +} + +static void dtd_complete_irq(struct arcotg_udc *udc) +{ + u32 bit_pos; + int i, ep_num, direction, bit_mask, status; + struct arcotg_ep *curr_ep; + struct arcotg_req *curr_req, *temp_req; + + pr_debug("%s\n", __FUNCTION__); + /* Clear the bits in the register */ + bit_pos = usb_slave_regs->endptcomplete; + usb_slave_regs->endptcomplete = bit_pos; + bit_pos = le32_to_cpu(bit_pos); + + if (!bit_pos) + return; + + for (i = 0; i < USB_MAX_ENDPOINTS * 2; i++) { + ep_num = i >> 1; + direction = i % 2; + + bit_mask = 1 << (ep_num + 16 * direction); + + if (!(bit_pos & bit_mask)) + continue; + + curr_ep = get_ep_by_pipe(udc, i); + + /* If the ep is configured */ + if (curr_ep->name == NULL) { + printk(KERN_WARNING "Invalid EP?\n"); + continue; + } + + dump_ep_queue(curr_ep); + + /* search all arcotg_reqs of ep */ + list_for_each_entry_safe(curr_req, temp_req, &curr_ep->queue, + queue) { + status = process_ep_req(udc, i, curr_req); + if (status == REQ_UNCOMPLETE) { + pr_debug + ("udc: Not all tds are completed in the req\n"); + break; + } + + if (ep_num == 0) { + ep0_req_complete(udc, curr_ep, curr_req); + break; + } else + done(curr_ep, curr_req, status); + + } + + dump_ep_queue(curr_ep); + } +} + +static void port_change_irq(struct arcotg_udc *udc) +{ + u32 speed; + + if (udc->bus_reset) + udc->bus_reset = FALSE; + + /* Bus resetting is finished */ + if (!(le32_to_cpu(usb_slave_regs->portsc1) & PORTSCX_PORT_RESET)) { + /* Get the speed */ + speed = (le32_to_cpu(usb_slave_regs->portsc1) & + PORTSCX_PORT_SPEED_MASK); + switch (speed) { + case PORTSCX_PORT_SPEED_HIGH: + udc->gadget.speed = USB_SPEED_HIGH; + break; + case PORTSCX_PORT_SPEED_FULL: + udc->gadget.speed = USB_SPEED_FULL; + break; + case PORTSCX_PORT_SPEED_LOW: + udc->gadget.speed = USB_SPEED_LOW; + break; + default: + udc->gadget.speed = USB_SPEED_UNKNOWN; + break; + } + } + pr_debug("udc: speed now %d\n", udc->gadget.speed); + + /* Update USB state */ + if (!udc->resume_state) + udc->usb_state = USB_STATE_DEFAULT; +} + +static void suspend_irq(struct arcotg_udc *udc) +{ + pr_debug("%s\n", __FUNCTION__); + + udc->resume_state = udc->usb_state; + udc->usb_state = USB_STATE_SUSPENDED; + + /* report suspend to the driver ,serial.c not support this */ + if (udc->driver->suspend) + udc->driver->suspend(&udc->gadget); +} + +static void resume_irq(struct arcotg_udc *udc) +{ + pr_debug("%s\n", __FUNCTION__); + + udc->usb_state = udc->resume_state; + udc->resume_state = 0; + + /* report resume to the driver , serial.c not support this */ + if (udc->driver->resume) + udc->driver->resume(&udc->gadget); + +} + +static int reset_queues(struct arcotg_udc *udc) +{ + u8 pipe; + + pr_debug("udc: disconnect\n"); + for (pipe = 0; pipe < udc->max_pipes; pipe++) + udc_reset_ep_queue(udc, pipe); + + /* report disconnect; the driver is already quiesced */ + udc->driver->disconnect(&udc->gadget); + + return 0; +} + +static void reset_irq(struct arcotg_udc *udc) +{ + u32 temp; + + /* Clear the device address */ + temp = le32_to_cpu(usb_slave_regs->deviceaddr); + temp &= ~USB_DEVICE_ADDRESS_MASK; + usb_slave_regs->deviceaddr = cpu_to_le32(temp); + pr_debug("udc: set deviceaddr to %d\n", + usb_slave_regs->deviceaddr >> USB_DEVICE_ADDRESS_BIT_POS); + udc->device_address = 0; + + /* Clear usb state */ + udc->usb_state = USB_STATE_DEFAULT; + + /* Clear all the setup token semaphores */ + temp = le32_to_cpu(usb_slave_regs->endptsetupstat); + usb_slave_regs->endptsetupstat = cpu_to_le32(temp); + + /* Clear all the endpoint complete status bits */ + temp = le32_to_cpu(usb_slave_regs->endptcomplete); + usb_slave_regs->endptcomplete = cpu_to_le32(temp); + + timeout = 10000000; + /* Wait until all endptprime bits cleared */ + while ((usb_slave_regs->endpointprime) && --timeout) { + continue; + } + if (timeout == 0) { + printk(KERN_ERR "%s: TIMEOUT\n", __FUNCTION__); + } + + /* Write 1s to the Flush register */ + usb_slave_regs->endptflush = 0xFFFFFFFF; + + if (le32_to_cpu(usb_slave_regs->portsc1) & PORTSCX_PORT_RESET) { + pr_debug("udc: Bus RESET\n"); + /* Bus is reseting */ + udc->bus_reset = TRUE; + udc->ep0_state = WAIT_FOR_SETUP; + pr_debug("udc: ep0_state now WAIT_FOR_SETUP\n"); + udc->ep0_dir = 0; + /* Reset all the queues, include XD, dTD, EP queue + * head and TR Queue */ + reset_queues(udc); + } else { + pr_debug("udc: Controller reset\n"); + /* initialize usb hw reg except for regs for EP, not + * touch usbintr reg */ + dr_controller_setup(udc); + + /* FIXME: Reset all internal used Queues */ + reset_queues(udc); + + ep0_dr_and_qh_setup(udc); + + /* Enable DR IRQ reg, Set Run bit, change udc state */ + dr_controller_run(udc); + udc->usb_state = USB_STATE_ATTACHED; + udc->ep0_state = WAIT_FOR_SETUP; + pr_debug("udc: ep0_state now WAIT_FOR_SETUP\n"); + udc->ep0_dir = 0; + } +} + +static irqreturn_t arcotg_udc_irq(int irq, void *_udc) +{ + struct arcotg_udc *udc = _udc; + u32 irq_src; + irqreturn_t status = IRQ_NONE; + unsigned long flags; + + if (udc->stopped) + return IRQ_NONE; /* ignore irq if we're not running */ + + spin_lock_irqsave(&udc->lock, flags); + irq_src = usb_slave_regs->usbsts & usb_slave_regs->usbintr; + /* Clear notification bits */ + usb_slave_regs->usbsts &= irq_src; + + irq_src = le32_to_cpu(irq_src); + pr_debug("udc: irq_src [0x%08x]\n", irq_src); + + /* USB Interrupt */ + if (irq_src & USB_STS_INT) { + /* Setup packet, we only support ep0 as control ep */ + pr_debug("udc: endptsetupstat=0x%x endptcomplete=0x%x\n", + usb_slave_regs->endptsetupstat, + usb_slave_regs->endptcomplete); + if (usb_slave_regs-> + endptsetupstat & cpu_to_le32(EP_SETUP_STATUS_EP0)) { + tripwire_handler(udc, 0, + (u8 *) (&udc->local_setup_buff)); + setup_received_irq(udc, &udc->local_setup_buff); + status = IRQ_HANDLED; + } + + /* completion of dtd */ + if (usb_slave_regs->endptcomplete) { + dtd_complete_irq(udc); + status = IRQ_HANDLED; + } + } + + /* SOF (for ISO transfer) */ + if (irq_src & USB_STS_SOF) { + status = IRQ_HANDLED; + } + + /* Port Change */ + if (irq_src & USB_STS_PORT_CHANGE) { + port_change_irq(udc); + status = IRQ_HANDLED; + } + + /* Reset Received */ + if (irq_src & USB_STS_RESET) { + reset_irq(udc); + status = IRQ_HANDLED; + } + + /* Sleep Enable (Suspend) */ + if (irq_src & USB_STS_SUSPEND) { + suspend_irq(udc); + status = IRQ_HANDLED; + } else if (udc->resume_state) { + resume_irq(udc); + status = IRQ_HANDLED; + } + + if (irq_src & (USB_STS_ERR | USB_STS_SYS_ERR)) { + pr_debug("udc: Error IRQ %x\n", irq_src); + status = IRQ_HANDLED; + } + + if (status != IRQ_HANDLED) { + pr_debug("udc: not handled irq_src=0x%x\n", irq_src); + } + + pr_debug("udc: irq_src [0x%08x] done. regs now=0x%08x\n", irq_src, + usb_slave_regs->usbsts & usb_slave_regs->usbintr); + pr_debug("-\n"); + pr_debug("-\n"); + spin_unlock_irqrestore(&udc->lock, flags); + + return status; +} + +/*! + * tell the controller driver about gadget layer driver + * The driver's bind function will be called to bind it to a gadget. + * @param driver for example fsg_driver from file_storage.c + * @return Returns zero on success , or a negative error code + */ +int usb_gadget_register_driver(struct usb_gadget_driver *driver) +{ + int retval = -ENODEV; + unsigned long flags = 0; + struct arcotg_udc *udc = udc_controller; + + pr_debug("udc: udc=0x%p\n", udc); + + /* standard operations */ + if (!udc) + return -ENODEV; + + if (!driver || (driver->speed != USB_SPEED_FULL + && driver->speed != USB_SPEED_HIGH) + || !driver->bind || !driver->unbind || + !driver->disconnect || !driver->setup) + return -EINVAL; + + if (udc->driver) + return -EBUSY; + + /* lock is needed but whether should use this lock or another */ + spin_lock_irqsave(&udc->lock, flags); + + driver->driver.bus = 0; + /* hook up the driver */ + udc->driver = driver; + udc->gadget.dev.driver = &driver->driver; + spin_unlock_irqrestore(&udc->lock, flags); + + retval = driver->bind(&udc->gadget); + if (retval) { + pr_debug("bind to %s --> %d\n", driver->driver.name, retval); + udc->gadget.dev.driver = 0; + udc->driver = 0; + goto out; + } + + if (udc->transceiver) { + /* Suspend the controller until OTG enables it */ + udc_suspend(udc); + pr_debug("udc: suspend udc for OTG auto detect \n"); + + /* export udc suspend/resume call to OTG */ + udc->gadget.dev.parent->driver->suspend = arcotg_udc_suspend; + udc->gadget.dev.parent->driver->resume = arcotg_udc_resume; + + /* connect to bus through transceiver */ + retval = otg_set_peripheral(udc->transceiver, &udc->gadget); + if (retval < 0) { + pr_debug("udc: can't bind to transceiver\n"); + driver->unbind(&udc->gadget); + udc->gadget.dev.driver = 0; + udc->driver = 0; + return retval; + } + } else { + /* Enable DR IRQ reg and Set usbcmd reg Run bit */ + dr_controller_run(udc); + udc->usb_state = USB_STATE_ATTACHED; + udc->ep0_state = WAIT_FOR_SETUP; + pr_debug("udc: ep0_state now WAIT_FOR_SETUP\n"); + udc->ep0_dir = 0; + } + + printk(KERN_INFO "arcotg_udc: gadget %s bound to driver %s\n", + udc->gadget.name, driver->driver.name); + + out: + return retval; +} + +EXPORT_SYMBOL(usb_gadget_register_driver); + +int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) +{ + struct arcotg_ep *loop_ep; + unsigned long flags; + struct arcotg_udc *udc = udc_controller; + + pr_debug("usb_gadget_unregister_driver: udc=0x%p\n", udc); + if (!udc) + return -ENODEV; + + if (!driver || driver != udc->driver) + return -EINVAL; + + if (udc->transceiver) { + (void)otg_set_peripheral(udc->transceiver, 0); + pr_debug("udc: set peripheral=NULL\n"); + } else { + /* FIXME + pullup_disable(udc); + */ + } + + /* stop DR, disable intr */ + dr_controller_stop(udc); + + /* in fact, no needed */ + udc->usb_state = USB_STATE_ATTACHED; + udc->ep0_state = WAIT_FOR_SETUP; + pr_debug("udc: ep0_state now WAIT_FOR_SETUP\n"); + udc->ep0_dir = 0; + + /* stand operation */ + spin_lock_irqsave(&udc->lock, flags); + udc->gadget.speed = USB_SPEED_UNKNOWN; + nuke(&udc->eps[0], -ESHUTDOWN); + list_for_each_entry(loop_ep, &udc->gadget.ep_list, ep.ep_list) + nuke(loop_ep, -ESHUTDOWN); + + /* report disconnect to free up endpoints */ + pr_debug("udc: disconnect\n"); + driver->disconnect(&udc->gadget); + + spin_unlock_irqrestore(&udc->lock, flags); + + /* unbind gadget and unhook driver. */ + pr_debug("udc: unbind\n"); + driver->unbind(&udc->gadget); + udc->gadget.dev.driver = 0; + udc->driver = 0; + + printk(KERN_INFO "unregistered gadget driver '%s'\r\n", + driver->driver.name); + return 0; +} + +EXPORT_SYMBOL(usb_gadget_unregister_driver); + +/*------------------------------------------------------------------------- + PROC File System Support +-------------------------------------------------------------------------*/ +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + +#include <linux/seq_file.h> + +static const char proc_filename[] = "driver/arcotg_udc"; + +static int arcotg_proc_read(char *page, char **start, off_t off, int count, + int *eof, void *_dev) +{ + char *buf = page; + char *next = buf; + unsigned size = count; + unsigned long flags; + int t, i; + u32 tmp_reg; + struct arcotg_ep *ep = NULL; + struct arcotg_req *req; + + struct arcotg_udc *udc = udc_controller; + if (off != 0) + return 0; + + spin_lock_irqsave(&udc->lock, flags); + + /* ------basic driver infomation ---- */ + t = scnprintf(next, size, + DRIVER_DESC "\n" "%s version: %s\n" + "Gadget driver: %s\n\n", driver_name, DRIVER_VERSION, + udc->driver ? udc->driver->driver.name : "(none)"); + size -= t; + next += t; + + /* ------ DR Registers ----- */ + tmp_reg = le32_to_cpu(usb_slave_regs->usbcmd); + t = scnprintf(next, size, + "USBCMD reg:\n" "SetupTW: %d\n" "Run/Stop: %s\n\n", + (tmp_reg & USB_CMD_SUTW) ? 1 : 0, + (tmp_reg & USB_CMD_RUN_STOP) ? "Run" : "Stop"); + size -= t; + next += t; + + tmp_reg = le32_to_cpu(usb_slave_regs->usbsts); + t = scnprintf(next, size, + "USB Status Reg:\n" "Dr Suspend: %d" + "Reset Received: %d" "System Error: %s" + "USB Error Interrupt: %s\n\n", + (tmp_reg & USB_STS_SUSPEND) ? 1 : 0, + (tmp_reg & USB_STS_RESET) ? 1 : 0, + (tmp_reg & USB_STS_SYS_ERR) ? "Err" : "Normal", + (tmp_reg & USB_STS_ERR) ? "Err detected" : "No err"); + size -= t; + next += t; + + tmp_reg = le32_to_cpu(usb_slave_regs->usbintr); + t = scnprintf(next, size, + "USB Intrrupt Enable Reg:\n" + "Sleep Enable: %d" "SOF Received Enable: %d" + "Reset Enable: %d\n" "System Error Enable: %d" + "Port Change Dectected Enable: %d\n" + "USB Error Intr Enable: %d" + "USB Intr Enable: %d\n\n", + (tmp_reg & USB_INTR_DEVICE_SUSPEND) ? 1 : 0, + (tmp_reg & USB_INTR_SOF_EN) ? 1 : 0, + (tmp_reg & USB_INTR_RESET_EN) ? 1 : 0, + (tmp_reg & USB_INTR_SYS_ERR_EN) ? 1 : 0, + (tmp_reg & USB_INTR_PTC_DETECT_EN) ? 1 : 0, + (tmp_reg & USB_INTR_ERR_INT_EN) ? 1 : 0, + (tmp_reg & USB_INTR_INT_EN) ? 1 : 0); + size -= t; + next += t; + + tmp_reg = le32_to_cpu(usb_slave_regs->frindex); + t = scnprintf(next, size, + "USB Frame Index Reg:" "Frame Number is 0x%x\n\n", + (tmp_reg & USB_FRINDEX_MASKS)); + size -= t; + next += t; + + tmp_reg = le32_to_cpu(usb_slave_regs->deviceaddr); + t = scnprintf(next, size, + "USB Device Address Reg:" "Device Addr is 0x%x\n\n", + (tmp_reg & USB_DEVICE_ADDRESS_MASK)); + size -= t; + next += t; + + tmp_reg = le32_to_cpu(usb_slave_regs->endpointlistaddr); + t = scnprintf(next, size, + "USB Endpoint List Address Reg:" + "Device Addr is 0x%x\n\n", + (tmp_reg & USB_EP_LIST_ADDRESS_MASK)); + size -= t; + next += t; + + tmp_reg = le32_to_cpu(usb_slave_regs->portsc1); + t = scnprintf(next, size, + "USB Port Status&Control Reg:\n" + "Port Transceiver Type : %s" "Port Speed: %s \n" + "PHY Low Power Suspend: %s" "Port Reset: %s" + "Port Suspend Mode: %s \n" "Over-current Change: %s" + "Port Enable/Disable Change: %s\n" + "Port Enabled/Disabled: %s" + "Current Connect Status: %s\n\n", ( { + char *s; + switch (tmp_reg & + PORTSCX_PTS_FSLS) + { +case PORTSCX_PTS_UTMI: +s = "UTMI"; break; case PORTSCX_PTS_ULPI: +s = "ULPI "; break; case PORTSCX_PTS_FSLS: +s = "FS/LS Serial"; break; default: + s = "None"; break;} + s;} + ), ( { + char *s; switch (tmp_reg & PORTSCX_PORT_SPEED_UNDEF) { +case PORTSCX_PORT_SPEED_FULL: +s = "Full Speed"; break; case PORTSCX_PORT_SPEED_LOW: +s = "Low Speed"; break; case PORTSCX_PORT_SPEED_HIGH: +s = "High Speed"; break; default: + s = "Undefined"; break;} + s;} + ), + (tmp_reg & PORTSCX_PHY_LOW_POWER_SPD) ? + "Normal PHY mode" : "Low power mode", + (tmp_reg & PORTSCX_PORT_RESET) ? "In Reset" : + "Not in Reset", + (tmp_reg & PORTSCX_PORT_SUSPEND) ? "In " : "Not in", + (tmp_reg & PORTSCX_OVER_CURRENT_CHG) ? "Dected" : + "No", + (tmp_reg & PORTSCX_PORT_EN_DIS_CHANGE) ? "Disable" : + "Not change", + (tmp_reg & PORTSCX_PORT_ENABLE) ? "Enable" : + "Not correct", + (tmp_reg & PORTSCX_CURRENT_CONNECT_STATUS) ? + "Attached" : "Not-Att") ; + size -= t; + next += t; + + tmp_reg = le32_to_cpu(usb_slave_regs->usbmode); + t = scnprintf(next, size, "USB Mode Reg:" "Controller Mode is : %s\n\n", ( { + char + *s; + switch + (tmp_reg + & + USB_MODE_CTRL_MODE_HOST) + { +case USB_MODE_CTRL_MODE_IDLE: +s = "Idle"; break; case USB_MODE_CTRL_MODE_DEVICE: +s = "Device Controller"; break; case USB_MODE_CTRL_MODE_HOST: +s = "Host Controller"; break; default: + s + = + "None"; + break;} + s;} + )) ; + size -= t; + next += t; + + tmp_reg = le32_to_cpu(usb_slave_regs->endptsetupstat); + t = scnprintf(next, size, + "Endpoint Setup Status Reg:" "SETUP on ep 0x%x\n\n", + (tmp_reg & EP_SETUP_STATUS_MASK)); + size -= t; + next += t; + + for (i = 0; i < USB_MAX_ENDPOINTS; i++) { + tmp_reg = le32_to_cpu(usb_slave_regs->endptctrl[i]); + t = scnprintf(next, size, "EP Ctrl Reg [0x%x]: = [0x%x]\n", + i, tmp_reg); + size -= t; + next += t; + } + tmp_reg = le32_to_cpu(usb_slave_regs->endpointprime); + t = scnprintf(next, size, "EP Prime Reg = [0x%x]\n", tmp_reg); + size -= t; + next += t; + + /* ------arcotg_udc, arcotg_ep, arcotg_request structure information ----- */ + ep = &udc->eps[0]; + t = scnprintf(next, size, "For %s Maxpkt is 0x%x index is 0x%x\n", + ep->ep.name, ep_maxpacket(ep), ep_index(ep)); + size -= t; + next += t; + + if (list_empty(&ep->queue)) { + t = scnprintf(next, size, "its req queue is empty\n\n"); + size -= t; + next += t; + } else { + list_for_each_entry(req, &ep->queue, queue) { + t = scnprintf(next, size, + "req %p actual 0x%x length 0x%x buf %p\n", + &req->req, req->req.actual, + req->req.length, req->req.buf); + size -= t; + next += t; + } + } + /* other gadget->eplist ep */ + list_for_each_entry(ep, &udc->gadget.ep_list, ep.ep_list) { + if (ep->desc) { + t = scnprintf(next, size, + "\nFor %s Maxpkt is 0x%x index is 0x%x\n", + ep->ep.name, + ep_maxpacket(ep), ep_index(ep)); + size -= t; + next += t; + + if (list_empty(&ep->queue)) { + t = scnprintf(next, size, + "its req queue is empty\n\n"); + size -= t; + next += t; + } else { + list_for_each_entry(req, &ep->queue, queue) { + t = scnprintf(next, size, + "req %p actual 0x%x length" + "0x%x buf %p\n", + &req->req, + req->req.actual, + req->req.length, + req->req.buf); + size -= t; + next += t; + } + } + } +#if 0 /* DDD debug */ + else { + t = scnprintf(next, size, "\nno desc for %s\n", + ep->ep.name); + size -= t; + next += t; + } +#endif + } + + spin_unlock_irqrestore(&udc->lock, flags); + + *eof = 1; + return count - size; +} + +#define create_proc_file() create_proc_read_entry(proc_filename, \ + 0, NULL, arcotg_proc_read, NULL) + +#define remove_proc_file() remove_proc_entry(proc_filename, NULL) + +#else /* !CONFIG_USB_GADGET_DEBUG_FILES */ + +#define create_proc_file() do {} while (0) +#define remove_proc_file() do {} while (0) + +#endif /*CONFIG_USB_GADGET_DEBUG_FILES */ + +/*-------------------------------------------------------------------------*/ + +/*! + * Release the ARC OTG specific udc structure + * it is not stand gadget function + * it is called when the last reference to the device is removed; + * it is called from the embedded kobject's release method. + * All device structures registered with the core must have a + * release method, or the kernel prints out scary complaints + * @param dev device controller pointer + */ +static void arcotg_gadget_release(struct device *dev) +{ + struct device *udc_dev = dev->parent; + + struct arcotg_udc *udc = (struct arcotg_udc *)dev_get_drvdata(udc_dev); + + complete(udc->done); + dma_free_coherent(dev, udc->ep_qh_size, udc->ep_qh, udc->ep_qh_dma); + kfree(udc); +} + +/****************************************************************** + Internal Structure Build up functions -2 +*******************************************************************/ +/*! + * this func will init resource for globle controller + * Return the udc handle on success or Null on failing + * @param pdev device controller pointer + */ +static void *struct_udc_setup(struct platform_device *pdev) +{ + struct arcotg_udc *udc = NULL; + + udc = (struct arcotg_udc *) + kmalloc(sizeof(struct arcotg_udc), GFP_KERNEL); + pr_debug("udc: kmalloc(ucd)=0x%p\n", udc); + if (udc == NULL) { + printk(KERN_ERR "malloc udc failed\n"); + goto cleanup; + } + + /* Zero out the internal USB state structure */ + memset(udc, 0, sizeof(struct arcotg_udc)); + + /* initialized QHs, take care the 2K align */ + udc->ep_qh_size = USB_MAX_PIPES * sizeof(struct ep_queue_head); + + /* Arc OTG IP-core requires 2K alignment of queuehead + * this if fullfilled by per page allocation + * by dma_alloc_coherent(...) + */ + udc->ep_qh = (struct ep_queue_head *) + dma_alloc_coherent(&pdev->dev, udc->ep_qh_size, + &udc->ep_qh_dma, GFP_KERNEL); + if (!udc->ep_qh) { + printk(KERN_ERR "malloc QHs for udc failed\n"); + goto cleanup; + } + pr_debug("udc: udc->ep_qh=0x%p\n", udc->ep_qh); + + memset(udc->ep_qh, 0, udc->ep_qh_size); + + /* need 32 byte alignment, don't cross 4K boundary */ + udc->dtd_pool = dma_pool_create("arcotg_dtd", &pdev->dev, + sizeof(struct ep_td_struct), 32, 4096); + if (!udc->dtd_pool) { + printk(KERN_ERR "dtd_pool alloc failed\n"); + goto cleanup; + } + + /* Initialize ep0 status request structure */ + /* FIXME: arcotg_alloc_request() ignores ep argument */ + udc->status_req = + container_of(arcotg_alloc_request(NULL, GFP_KERNEL), + struct arcotg_req, req); + + /* allocate a small amount of memory to get valid address */ + udc->status_req->req.buf = kmalloc(8, GFP_KERNEL); + udc->status_req->req.dma = virt_to_phys(udc->status_req->req.buf); + + pr_debug("udc: status_req=0x%p status_req->req.buf=0x%p " + "status_req->req.dma=0x%x", + udc->status_req, udc->status_req->req.buf, + udc->status_req->req.dma); + + udc->resume_state = USB_STATE_NOTATTACHED; + udc->usb_state = USB_STATE_POWERED; + udc->ep0_dir = 0; + /* initliaze the arcotg_udc lock */ + spin_lock_init(&udc->lock); + + return udc; + + cleanup: + kfree(udc); + return NULL; +} + +/*! + * set up the arcotg_ep struct for eps + * ep0out isnot used so do nothing here + * ep0in should be taken care + * It also link this arcotg_ep->ep to gadget->ep_list + * @param udc device controller pointer + * @param pipe_num pipe number + * @return Returns zero on success , or a negative error code + */ +static int struct_ep_setup(struct arcotg_udc *udc, unsigned char pipe_num) +{ + struct arcotg_ep *ep = get_ep_by_pipe(udc, pipe_num); + + /* + VDBG("pipe_num=%d name[%d]=%s", + pipe_num, pipe_num, ep_name[pipe_num]); + */ + ep->udc = udc; + strcpy(ep->name, ep_name[pipe_num]); + ep->ep.name = ep_name[pipe_num]; + ep->ep.ops = &arcotg_ep_ops; + ep->stopped = 0; + + /* for ep0: the desc defined here; + * for other eps, gadget layer called ep_enable with defined desc + */ + /* for ep0: maxP defined in desc + * for other eps, maxP is set by epautoconfig() called by gadget layer + */ + if (pipe_num == 0) { + ep->desc = &arcotg_ep0_desc; + ep->ep.maxpacket = USB_MAX_CTRL_PAYLOAD; + } else { + ep->ep.maxpacket = (unsigned short)~0; + ep->desc = NULL; + } + + /* the queue lists any req for this ep */ + INIT_LIST_HEAD(&ep->queue); + + /* arcotg_ep->ep.ep_list: gadget ep_list hold all of its eps + * so only the first should init--it is ep0' */ + + /* gagdet.ep_list used for ep_autoconfig so no ep0 */ + if (pipe_num != 0) + list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list); + + ep->gadget = &udc->gadget; + + return 0; +} + +static int board_init(struct platform_device *pdev) +{ + struct fsl_usb2_platform_data *pdata; + pdata = (struct fsl_usb2_platform_data *)pdev->dev.platform_data; + + pr_debug("%s: pdev=0x%p pdata=0x%p\n", __FUNCTION__, pdev, pdata); + + /* + * do platform specific init: check the clock, grab/config pins, etc. + */ + if (pdata->platform_init && pdata->platform_init(pdev) != 0) + return -EINVAL; + + return 0; +} + +/* Driver probe functions */ + + /*! + * all intialize operations implemented here except Enable usb_intr reg + * @param dev device controller pointer + * @return Returns zero on success , or a negative error code + */ +static int __devinit arcotg_udc_probe(struct platform_device *pdev) +{ + struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data; + struct arcotg_udc *udc; + + unsigned int tmp_status = -ENODEV; + unsigned int i; + u32 id; + u64 rsrc_start, rsrc_len; + + if (strcmp(pdev->name, "arc_udc")) { + pr_debug("udc: Wrong device\n"); + return -ENODEV; + } + + pr_debug("%s: pdev=0x%p pdata=0x%p\n", __FUNCTION__, pdev, pdata); + + if (board_init(pdev) != 0) + return -EINVAL; + + /* Initialize the udc structure including QH member and other member */ + udc = (struct arcotg_udc *)struct_udc_setup(pdev); + udc_controller = udc; + + if (!udc) { + pr_debug("udc: udc is NULL\n"); + return -ENOMEM; + } + + dev_set_drvdata(&pdev->dev, udc); + + udc->pdata = pdata; + udc->xcvr_type = pdata->xcvr_type; + +#ifdef CONFIG_USB_OTG + udc->transceiver = otg_get_transceiver(); + pr_debug("udc: otg_get_transceiver returns 0x%p", udc->transceiver); +#endif + + if (pdev->resource[1].flags != IORESOURCE_IRQ) { + return -ENODEV; + } + + rsrc_start = pdev->resource[0].start; + rsrc_len = pdev->resource[0].end - pdev->resource[0].start + 1; + + pr_debug("start=0x%x end=0x%x\n", + pdev->resource[0].start, pdev->resource[0].end); + pr_debug("rsrc_start=0x%llx rsrc_len=0x%llx\n", rsrc_start, rsrc_len); + +#if 0 /* DDD */ + pr_debug("doing request_mem_region(start=0x%llx, len=0x%llx)\n", + rsrc_start, rsrc_len); + if (!request_mem_region(rsrc_start, rsrc_len, driver_name)) { + printk(KERN_ERR "request_mem_region failed\n"); + return -EBUSY; + } +#endif + usb_slave_regs = (struct usb_dr_device *)(int)IO_ADDRESS(rsrc_start); + + pr_debug("udc: usb_slave_regs = 0x%p\n", usb_slave_regs); + pr_debug("udc: hci_version=0x%x\n", usb_slave_regs->hciversion); + pr_debug("udc: otgsc at 0x%p\n", &usb_slave_regs->otgsc); + + id = usb_slave_regs->id; + printk(KERN_INFO "ARC USBOTG h/w ID=0x%x revision=0x%x\n", + id & 0x3f, id >> 16); + + /* request irq and disable DR */ + tmp_status = request_irq(pdev->resource[1].start, arcotg_udc_irq, + IRQF_SHARED, driver_name, udc); + if (tmp_status != 0) { + printk(KERN_ERR "cannot request irq %d err %d \n", + (int)pdev->resource[1].start, tmp_status); + /* DDD free mem_region here */ + return tmp_status; + } + + if (!udc->transceiver) { + /* initialize usb hw reg except for regs for EP, + * leave usbintr reg untouched*/ + dr_controller_setup(udc); + } + + /* here comes the stand operations for probe + * set the arcotg_udc->gadget.xxx + */ + udc->gadget.ops = &arcotg_gadget_ops; + udc->gadget.is_dualspeed = 1; + + /* gadget.ep0 is a pointer */ + udc->gadget.ep0 = &udc->eps[0].ep; + + INIT_LIST_HEAD(&udc->gadget.ep_list); + + udc->gadget.speed = USB_SPEED_UNKNOWN; + + /* name: Identifies the controller hardware type. */ + udc->gadget.name = driver_name; + + device_initialize(&udc->gadget.dev); + + strcpy(udc->gadget.dev.bus_id, "gadget"); + + udc->gadget.dev.release = arcotg_gadget_release; + udc->gadget.dev.parent = &pdev->dev; + + if (udc->transceiver) { + udc->gadget.is_otg = 1; + } + + /* for an EP, the intialization includes: fields in QH, Regs, + * arcotg_ep struct */ + ep0_dr_and_qh_setup(udc); + for (i = 0; i < USB_MAX_PIPES; i++) { + /* because the ep type is not decide here so + * struct_ep_qh_setup() and dr_ep_setup() + * should be called in ep_enable() + */ + if (ep_name[i] != NULL) + /* setup the arcotg_ep struct and link ep.ep.list + * into gadget.ep_list */ + struct_ep_setup(udc, i); + } + + create_proc_file(); + tmp_status = device_add(&udc->gadget.dev); + pr_debug("udc: back from device_add "); + + return tmp_status; +} + +/*! + * Driver removal functions + * Free resources + * Finish pending transaction + * @param dev device controller pointer + * @return Returns zero on success , or a negative error code + */ +static int __devexit arcotg_udc_remove(struct platform_device *pdev) +{ + struct arcotg_udc *udc = + (struct arcotg_udc *)platform_get_drvdata(pdev); + struct fsl_usb2_platform_data *pdata = pdev->dev.platform_data; + + DECLARE_COMPLETION(done); + + if (!udc) + return -ENODEV; + + udc->done = &done; + + if (udc->transceiver) { + put_device(udc->transceiver->dev); + udc->transceiver = 0; + } + + /* DR has been stopped in usb_gadget_unregister_driver() */ + + /* remove proc */ + remove_proc_file(); + + /* free irq */ + free_irq(pdev->resource[1].start, udc); + + /* deinitialize all ep: strcut */ + /* deinitialize ep0: reg and QH */ + + /* Free allocated memory */ + pr_debug("status_req->head = 0x%p\n", udc->status_req->head); + if (udc->status_req->head) { + pr_debug("freeing head=0x%p\n", udc->status_req->head); + dma_pool_free(udc->dtd_pool, + udc->status_req->head, + udc->status_req->head->td_dma); + } + + kfree(udc->status_req->req.buf); + kfree(udc->status_req); + + if (udc->dtd_pool) + dma_pool_destroy(udc->dtd_pool); + + device_unregister(&udc->gadget.dev); + /* free udc --wait for the release() finished */ + wait_for_completion(&done); + +#if 0 /* DDD */ + release_mem_region(pdev->resource[0].start, + pdev->resource[0].end - pdev->resource[0].start + 1); +#endif + + /* + * do platform specific un-initialization: + * release iomux pins, etc. + */ + if (pdata->platform_uninit) + pdata->platform_uninit(pdata); + + return 0; +} + +static int udc_suspend(struct arcotg_udc *udc) +{ + udc->stopped = 1; + return 0; +} + +/*! + * Modify Power management attributes + * Here we stop the DR controller and disable the irq + * @param dev device controller pointer + * @param state current state + * @return The function returns 0 on success or -1 if failed + */ +static int arcotg_udc_suspend(struct device *dev, pm_message_t state) +{ + struct arcotg_udc *udc = (struct arcotg_udc *)dev_get_drvdata(dev); + pr_debug("udc: Suspend. state=%d\n", state.event); + return udc_suspend(udc); +} + +static int udc_resume(struct arcotg_udc *udc) +{ + /*Enable DR irq reg and set controller Run */ + if (udc->stopped) { + dr_controller_setup(udc); + dr_controller_run(udc); + } + udc->usb_state = USB_STATE_ATTACHED; + udc->ep0_state = WAIT_FOR_SETUP; + udc->ep0_dir = 0; + return 0; +} + +/*! + * Invoked on USB resume. May be called in_interrupt. + * Here we start the DR controller and enable the irq + * @param dev device controller pointer + * @return The function returns 0 on success or -1 if failed + */ +static int arcotg_udc_resume(struct device *dev) +{ + struct arcotg_udc *udc = (struct arcotg_udc *)dev_get_drvdata(dev); + pr_debug("udc: Resume dev=0x%p udc=0x%p\n", dev, udc); + + return udc_resume(udc); +} + +/*! + * Register entry point for the peripheral controller driver + */ +static struct platform_driver udc_driver = { + .probe = arcotg_udc_probe, + .remove = __exit_p(arcotg_udc_remove), + .driver = { + .name = driver_name, + }, +}; + +static int __init udc_init(void) +{ + int rc; + + printk(KERN_INFO "%s version %s init \n", driver_desc, DRIVER_VERSION); + rc = platform_driver_register(&udc_driver); + pr_debug("udc: %s() driver_register returns %d\n", __FUNCTION__, rc); + return rc; +} + +module_init(udc_init); + +static void __exit udc_exit(void) +{ + platform_driver_unregister(&udc_driver); +} + +module_exit(udc_exit); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/arcotg_udc.h b/drivers/usb/gadget/arcotg_udc.h new file mode 100644 index 000000000000..fad34300b5ba --- /dev/null +++ b/drivers/usb/gadget/arcotg_udc.h @@ -0,0 +1,598 @@ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file arcotg_udc.h + * @brief Freescale USB device/endpoint management registers + * @ingroup USB + */ + +#ifndef __ARCOTG_UDC_H +#define __ARCOTG_UDC_H + +#define TRUE 1 +#define FALSE 0 + +/* ### define USB registers here + */ +#define USB_MAX_ENDPOINTS 8 +#define USB_MAX_PIPES (USB_MAX_ENDPOINTS*2) +#define USB_MAX_CTRL_PAYLOAD 64 +#define USB_DR_SYS_OFFSET 0x400 + +#define USB_DR_OFFSET 0x3100 + +struct usb_dr_device { + /* Capability register */ + u32 id; + u32 res1[63]; + u16 caplength; /* Capability Register Length */ + u16 hciversion; /* Host Controller Interface Version */ + u32 hcsparams; /* Host Controller Structual Parameters */ + u32 hccparams; /* Host Controller Capability Parameters */ + u32 res2[5]; + u32 dciversion; /* Device Controller Interface Version */ + u32 dccparams; /* Device Controller Capability Parameters */ + u32 res3[6]; + /* Operation register */ + u32 usbcmd; /* USB Command Register */ + u32 usbsts; /* USB Status Register */ + u32 usbintr; /* USB Interrupt Enable Register */ + u32 frindex; /* Frame Index Register */ + u32 res4; + u32 deviceaddr; /* Device Address */ + u32 endpointlistaddr; /* Endpoint List Address Register */ + u32 res5; + u32 burstsize; /* Master Interface Data Burst Size Register */ + u32 txttfilltuning; /* Transmit FIFO Tuning Controls Register */ + u32 res6[6]; + u32 configflag; /* Configure Flag Register */ + u32 portsc1; /* Port 1 Status and Control Register */ + u32 res7[7]; + u32 otgsc; /* On-The-Go Status and Control */ + u32 usbmode; /* USB Mode Register */ + u32 endptsetupstat; /* Endpoint Setup Status Register */ + u32 endpointprime; /* Endpoint Initialization Register */ + u32 endptflush; /* Endpoint Flush Register */ + u32 endptstatus; /* Endpoint Status Register */ + u32 endptcomplete; /* Endpoint Complete Register */ + u32 endptctrl[8 * 2]; /* Endpoint Control Registers */ +} __attribute__ ((packed)); + +/* ep0 transfer state */ +#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 + +/* Frame Index Register Bit Masks */ +#define USB_FRINDEX_MASKS (0x3fff) +/* USB CMD Register Bit Masks */ +#define USB_CMD_RUN_STOP (0x00000001) +#define USB_CMD_CTRL_RESET (0x00000002) +#define USB_CMD_PERIODIC_SCHEDULE_EN (0x00000010) +#define USB_CMD_ASYNC_SCHEDULE_EN (0x00000020) +#define USB_CMD_INT_AA_DOORBELL (0x00000040) +#define USB_CMD_ASP (0x00000300) +#define USB_CMD_ASYNC_SCH_PARK_EN (0x00000800) +#define USB_CMD_SUTW (0x00002000) +#define USB_CMD_ATDTW (0x00004000) +#define USB_CMD_ITC (0x00FF0000) + +/* bit 15,3,2 are frame list size */ +#define USB_CMD_FRAME_SIZE_1024 (0x00000000) +#define USB_CMD_FRAME_SIZE_512 (0x00000004) +#define USB_CMD_FRAME_SIZE_256 (0x00000008) +#define USB_CMD_FRAME_SIZE_128 (0x0000000C) +#define USB_CMD_FRAME_SIZE_64 (0x00008000) +#define USB_CMD_FRAME_SIZE_32 (0x00008004) +#define USB_CMD_FRAME_SIZE_16 (0x00008008) +#define USB_CMD_FRAME_SIZE_8 (0x0000800C) + +/* bit 9-8 are async schedule park mode count */ +#define USB_CMD_ASP_00 (0x00000000) +#define USB_CMD_ASP_01 (0x00000100) +#define USB_CMD_ASP_10 (0x00000200) +#define USB_CMD_ASP_11 (0x00000300) +#define USB_CMD_ASP_BIT_POS (8) + +/* bit 23-16 are interrupt threshold control */ +#define USB_CMD_ITC_NO_THRESHOLD (0x00000000) +#define USB_CMD_ITC_1_MICRO_FRM (0x00010000) +#define USB_CMD_ITC_2_MICRO_FRM (0x00020000) +#define USB_CMD_ITC_4_MICRO_FRM (0x00040000) +#define USB_CMD_ITC_8_MICRO_FRM (0x00080000) +#define USB_CMD_ITC_16_MICRO_FRM (0x00100000) +#define USB_CMD_ITC_32_MICRO_FRM (0x00200000) +#define USB_CMD_ITC_64_MICRO_FRM (0x00400000) +#define USB_CMD_ITC_BIT_POS (16) + +/* USB STS Register Bit Masks */ +#define USB_STS_INT (0x00000001) +#define USB_STS_ERR (0x00000002) +#define USB_STS_PORT_CHANGE (0x00000004) +#define USB_STS_FRM_LST_ROLL (0x00000008) +#define USB_STS_SYS_ERR (0x00000010) +#define USB_STS_IAA (0x00000020) +#define USB_STS_RESET (0x00000040) +#define USB_STS_SOF (0x00000080) +#define USB_STS_SUSPEND (0x00000100) +#define USB_STS_HC_HALTED (0x00001000) +#define USB_STS_RCL (0x00002000) +#define USB_STS_PERIODIC_SCHEDULE (0x00004000) +#define USB_STS_ASYNC_SCHEDULE (0x00008000) + +/* USB INTR Register Bit Masks */ +#define USB_INTR_INT_EN (0x00000001) +#define USB_INTR_ERR_INT_EN (0x00000002) +#define USB_INTR_PTC_DETECT_EN (0x00000004) +#define USB_INTR_FRM_LST_ROLL_EN (0x00000008) +#define USB_INTR_SYS_ERR_EN (0x00000010) +#define USB_INTR_ASYN_ADV_EN (0x00000020) +#define USB_INTR_RESET_EN (0x00000040) +#define USB_INTR_SOF_EN (0x00000080) +#define USB_INTR_DEVICE_SUSPEND (0x00000100) + +/* Device Address bit masks */ +#define USB_DEVICE_ADDRESS_MASK (0xFE000000) +#define USB_DEVICE_ADDRESS_BIT_POS (25) + +/* endpoint list address bit masks */ +#define USB_EP_LIST_ADDRESS_MASK (0xfffff800) + +/* PORTSCX Register Bit Masks */ +#define PORTSCX_CURRENT_CONNECT_STATUS (0x00000001) +#define PORTSCX_CONNECT_STATUS_CHANGE (0x00000002) +#define PORTSCX_PORT_ENABLE (0x00000004) +#define PORTSCX_PORT_EN_DIS_CHANGE (0x00000008) +#define PORTSCX_OVER_CURRENT_ACT (0x00000010) +#define PORTSCX_OVER_CURRENT_CHG (0x00000020) +#define PORTSCX_PORT_FORCE_RESUME (0x00000040) +#define PORTSCX_PORT_SUSPEND (0x00000080) +#define PORTSCX_PORT_RESET (0x00000100) +#define PORTSCX_LINE_STATUS_BITS (0x00000C00) +#define PORTSCX_PORT_POWER (0x00001000) +#define PORTSCX_PORT_INDICTOR_CTRL (0x0000C000) +#define PORTSCX_PORT_TEST_CTRL (0x000F0000) +#define PORTSCX_WAKE_ON_CONNECT_EN (0x00100000) +#define PORTSCX_WAKE_ON_CONNECT_DIS (0x00200000) +#define PORTSCX_WAKE_ON_OVER_CURRENT (0x00400000) +#define PORTSCX_PHY_LOW_POWER_SPD (0x00800000) +#define PORTSCX_PORT_FORCE_FULL_SPEED (0x01000000) +#define PORTSCX_PORT_SPEED_MASK (0x0C000000) +#define PORTSCX_PORT_WIDTH (0x10000000) +#define PORTSCX_PHY_TYPE_SEL (0xC0000000) + +/* bit 11-10 are line status */ +#define PORTSCX_LINE_STATUS_SE0 (0x00000000) +#define PORTSCX_LINE_STATUS_JSTATE (0x00000400) +#define PORTSCX_LINE_STATUS_KSTATE (0x00000800) +#define PORTSCX_LINE_STATUS_UNDEF (0x00000C00) +#define PORTSCX_LINE_STATUS_BIT_POS (10) + +/* bit 15-14 are port indicator control */ +#define PORTSCX_PIC_OFF (0x00000000) +#define PORTSCX_PIC_AMBER (0x00004000) +#define PORTSCX_PIC_GREEN (0x00008000) +#define PORTSCX_PIC_UNDEF (0x0000C000) +#define PORTSCX_PIC_BIT_POS (14) + +/* bit 19-16 are port test control */ +#define PORTSCX_PTC_DISABLE (0x00000000) +#define PORTSCX_PTC_JSTATE (0x00010000) +#define PORTSCX_PTC_KSTATE (0x00020000) +#define PORTSCX_PTC_SEQNAK (0x00030000) +#define PORTSCX_PTC_PACKET (0x00040000) +#define PORTSCX_PTC_FORCE_EN (0x00050000) +#define PORTSCX_PTC_BIT_POS (16) + +/* bit 27-26 are port speed */ +#define PORTSCX_PORT_SPEED_FULL (0x00000000) +#define PORTSCX_PORT_SPEED_LOW (0x04000000) +#define PORTSCX_PORT_SPEED_HIGH (0x08000000) +#define PORTSCX_PORT_SPEED_UNDEF (0x0C000000) +#define PORTSCX_SPEED_BIT_POS (26) + +/* bit 28 is parallel transceiver width for UTMI interface */ +#define PORTSCX_PTW (0x10000000) +#define PORTSCX_PTW_8BIT (0x00000000) +#define PORTSCX_PTW_16BIT (0x10000000) + +/* bit 31-30 are port transceiver select */ +#define PORTSCX_PTS_UTMI (0x00000000) +#define PORTSCX_PTS_ULPI (0x80000000) +#define PORTSCX_PTS_FSLS (0xC0000000) +#define PORTSCX_PTS_BIT_POS (30) + +/* USB MODE Register Bit Masks */ +#define USB_MODE_CTRL_MODE_IDLE (0x00000000) +#define USB_MODE_CTRL_MODE_DEVICE (0x00000002) +#define USB_MODE_CTRL_MODE_HOST (0x00000003) +#define USB_MODE_CTRL_MODE_RSV (0x00000001) +#define USB_MODE_SETUP_LOCK_OFF (0x00000008) +#define USB_MODE_STREAM_DISABLE (0x00000010) +/* Endpoint Flush Register */ +#define EPFLUSH_TX_OFFSET (0x00010000) +#define EPFLUSH_RX_OFFSET (0x00000000) + +/* Endpoint Setup Status bit masks */ +#define EP_SETUP_STATUS_MASK (0x0000003F) +#define EP_SETUP_STATUS_EP0 (0x00000001) + +/* ENDPOINTCTRLx Register Bit Masks */ +#define EPCTRL_TX_ENABLE (0x00800000) +#define EPCTRL_TX_DATA_TOGGLE_RST (0x00400000) /* Not EP0 */ +#define EPCTRL_TX_DATA_TOGGLE_INH (0x00200000) /* Not EP0 */ +#define EPCTRL_TX_TYPE (0x000C0000) +#define EPCTRL_TX_DATA_SOURCE (0x00020000) /* Not EP0 */ +#define EPCTRL_TX_EP_STALL (0x00010000) +#define EPCTRL_RX_ENABLE (0x00000080) +#define EPCTRL_RX_DATA_TOGGLE_RST (0x00000040) /* Not EP0 */ +#define EPCTRL_RX_DATA_TOGGLE_INH (0x00000020) /* Not EP0 */ +#define EPCTRL_RX_TYPE (0x0000000C) +#define EPCTRL_RX_DATA_SINK (0x00000002) /* Not EP0 */ +#define EPCTRL_RX_EP_STALL (0x00000001) + +/* bit 19-18 and 3-2 are endpoint type */ +#define EPCTRL_EP_TYPE_CONTROL (0) +#define EPCTRL_EP_TYPE_ISO (1) +#define EPCTRL_EP_TYPE_BULK (2) +#define EPCTRL_EP_TYPE_INTERRUPT (3) +#define EPCTRL_TX_EP_TYPE_SHIFT (18) +#define EPCTRL_RX_EP_TYPE_SHIFT (2) + +/* SNOOPn Register Bit Masks */ +#define SNOOP_ADDRESS_MASK (0xFFFFF000) +#define SNOOP_SIZE_ZERO (0x00) /* snooping disable */ +#define SNOOP_SIZE_4KB (0x0B) /* 4KB snoop size */ +#define SNOOP_SIZE_8KB (0x0C) +#define SNOOP_SIZE_16KB (0x0D) +#define SNOOP_SIZE_32KB (0x0E) +#define SNOOP_SIZE_64KB (0x0F) +#define SNOOP_SIZE_128KB (0x10) +#define SNOOP_SIZE_256KB (0x11) +#define SNOOP_SIZE_512KB (0x12) +#define SNOOP_SIZE_1MB (0x13) +#define SNOOP_SIZE_2MB (0x14) +#define SNOOP_SIZE_4MB (0x15) +#define SNOOP_SIZE_8MB (0x16) +#define SNOOP_SIZE_16MB (0x17) +#define SNOOP_SIZE_32MB (0x18) +#define SNOOP_SIZE_64MB (0x19) +#define SNOOP_SIZE_128MB (0x1A) +#define SNOOP_SIZE_256MB (0x1B) +#define SNOOP_SIZE_512MB (0x1C) +#define SNOOP_SIZE_1GB (0x1D) +#define SNOOP_SIZE_2GB (0x1E) /* 2GB snoop size */ + +/* pri_ctrl Register Bit Masks */ +#define PRI_CTRL_PRI_LVL1 (0x0000000C) +#define PRI_CTRL_PRI_LVL0 (0x00000003) + +/* si_ctrl Register Bit Masks */ +#define SI_CTRL_ERR_DISABLE (0x00000010) +#define SI_CTRL_IDRC_DISABLE (0x00000008) +#define SI_CTRL_RD_SAFE_EN (0x00000004) +#define SI_CTRL_RD_PREFETCH_DISABLE (0x00000002) +#define SI_CTRL_RD_PREFEFETCH_VAL (0x00000001) + +/* control Register Bit Masks */ +#define USB_CTRL_IOENB (0x00000004) +#define USB_CTRL_ULPI_INT0EN (0x00000001) + +/*! + * Endpoint Queue Head data struct + * Rem: all the variables of qh are LittleEndian Mode + * and NEXT_POINTER_MASK should operate on a LittleEndian, Phy Addr + */ +struct ep_queue_head { + /*! + * Mult(31-30) , Zlt(29) , Max Pkt len and IOS(15) + */ + u32 max_pkt_length; + + /*! + * Current dTD Pointer(31-5) + */ + u32 curr_dtd_ptr; + + /*! + * Next dTD Pointer(31-5), T(0) + */ + u32 next_dtd_ptr; + + /*! + * Total bytes (30-16), IOC (15), MultO(11-10), STS (7-0) + */ + u32 size_ioc_int_sts; + + /*! + * Buffer pointer Page 0 (31-12) + */ + u32 buff_ptr0; + + /*! + * Buffer pointer Page 1 (31-12) + */ + u32 buff_ptr1; + + /*! + * Buffer pointer Page 2 (31-12) + */ + u32 buff_ptr2; + + /*! + * Buffer pointer Page 3 (31-12) + */ + u32 buff_ptr3; + + /*! + * Buffer pointer Page 4 (31-12) + */ + u32 buff_ptr4; + + /*! + * reserved field 1 + */ + u32 res1; + /*! + * Setup data 8 bytes + */ + u8 setup_buffer[8]; /* Setup data 8 bytes */ + + /*! + * reserved field 2,pad out to 64 bytes + */ + u32 res2[4]; +}; + +/* Endpoint Queue Head Bit Masks */ +#define EP_QUEUE_HEAD_MULT_POS (30) +#define EP_QUEUE_HEAD_ZLT_SEL (0x20000000) +#define EP_QUEUE_HEAD_MAX_PKT_LEN_POS (16) +#define EP_QUEUE_HEAD_MAX_PKT_LEN(ep_info) (((ep_info)>>16)&0x07ff) +#define EP_QUEUE_HEAD_IOS (0x00008000) +#define EP_QUEUE_HEAD_NEXT_TERMINATE (0x00000001) +#define EP_QUEUE_HEAD_IOC (0x00008000) +#define EP_QUEUE_HEAD_MULTO (0x00000C00) +#define EP_QUEUE_HEAD_STATUS_HALT (0x00000040) +#define EP_QUEUE_HEAD_STATUS_ACTIVE (0x00000080) +#define EP_QUEUE_CURRENT_OFFSET_MASK (0x00000FFF) +#define EP_QUEUE_FRINDEX_MASK (0x000007FF) +#define EP_MAX_LENGTH_TRANSFER (0x4000) + +/*! + * Endpoint Transfer Descriptor data struct + * Rem: all the variables of td are LittleEndian Mode + * must be 32-byte aligned + */ +struct ep_td_struct { + /*! + * Next TD pointer(31-5), T(0) set indicate invalid + */ + u32 next_td_ptr; + + /*! + * Total bytes (30-16), IOC (15),MultO(11-10), STS (7-0) + */ + u32 size_ioc_sts; + + /*! + * Buffer pointer Page 0 + */ + u32 buff_ptr0; + + /*! + * Buffer pointer Page 1 + */ + u32 buff_ptr1; + + /*! + * Buffer pointer Page 2 + */ + u32 buff_ptr2; + + /*! + * Buffer pointer Page 3 + */ + u32 buff_ptr3; + + /*! + * Buffer pointer Page 4 + */ + u32 buff_ptr4; + + /*! + * dma address of this td + * */ + dma_addr_t td_dma; + + /*! + * virtual address of next td + * */ + struct ep_td_struct *next_td_virt; + + /*! + * make it an even 16 words + * */ + u32 res[7]; +}; + +/*! + * Endpoint Transfer Descriptor bit Masks + */ +#define DTD_NEXT_TERMINATE (0x00000001) +#define DTD_IOC (0x00008000) +#define DTD_STATUS_ACTIVE (0x00000080) +#define DTD_STATUS_HALTED (0x00000040) +#define DTD_STATUS_DATA_BUFF_ERR (0x00000020) +#define DTD_STATUS_TRANSACTION_ERR (0x00000008) +#define DTD_RESERVED_FIELDS (0x80007300) +#define DTD_PACKET_SIZE (0x7FFF0000) +#define DTD_LENGTH_BIT_POS (16) +#define DTD_ERROR_MASK (DTD_STATUS_HALTED | \ + DTD_STATUS_DATA_BUFF_ERR | \ + DTD_STATUS_TRANSACTION_ERR) + +/* -----------------------------------------------------------------------*/ +/* ##### enum data +*/ +typedef enum { + e_ULPI, + e_UTMI_8BIT, + e_UTMI_16BIT, + e_SERIAL +} e_PhyInterface; + +/*-------------------------------------------------------------------------*/ + +struct arcotg_req { + struct usb_request req; + struct list_head queue; + /* ep_queue() func will add + a request->queue into a udc_ep->queue 'd tail */ + struct arcotg_ep *ep; + unsigned mapped; + + struct ep_td_struct *head, *tail; /* For dTD List + this is a BigEndian Virtual addr */ + unsigned int dtd_count; +}; + +#define REQ_UNCOMPLETE (1) + +struct arcotg_ep { + struct usb_ep ep; + struct list_head queue; + struct arcotg_udc *udc; + const struct usb_endpoint_descriptor *desc; + struct usb_gadget *gadget; + + u8 already_seen; + u8 setup_stage; + u32 last_io; /* timestamp */ + + char name[14]; +#if 0 + u16 maxpacket; + u8 bEndpointAddress; + u8 bmAttributes; +#endif + unsigned double_buf:1; + unsigned stopped:1; + unsigned fnf:1; + unsigned has_dma:1; + u8 ackwait; + u8 dma_channel; + u16 dma_counter; + int lch; + + struct timer_list timer; + +}; + +#define EP_DIR_IN 1 +#define EP_DIR_OUT 0 + +struct arcotg_udc { + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + struct arcotg_ep eps[USB_MAX_ENDPOINTS * 2]; + struct usb_ctrlrequest local_setup_buff; + spinlock_t lock; + struct fsl_usb2_platform_data *pdata; + u32 xcvr_type; + struct otg_transceiver *transceiver; + unsigned softconnect:1; + unsigned vbus_active:1; + unsigned stopped:1; + + struct ep_queue_head *ep_qh; /* Endpoints Queue-Head */ + int ep_qh_size; /* Endpoints Queue-Head */ + struct arcotg_req *status_req; /* ep0 status request */ + + u32 max_pipes; /* Device max pipes */ + u32 max_use_endpts; /* Max endpointes to be used */ + u32 bus_reset; /* Device is bus reseting */ + u32 resume_state; /* USB state to resume */ + u32 usb_state; /* USB current state */ + u32 usb_next_state; /* USB next state */ + u32 ep0_state; /* Enpoint zero state */ + u32 ep0_dir; /* Enpoint zero direction: can be + USB_DIR_IN or USB_DIR_OUT */ + u32 usb_sof_count; /* SOF count */ + u32 errors; /* USB ERRORs count */ + dma_addr_t ep_qh_dma; /* DMA address of ep_qh */ + struct dma_pool *dtd_pool; + u8 device_address; /* Device USB address */ + + struct completion *done; /* to make sure release() is done */ +}; + +#if 0 +static void dump_msg(const char *label, const u8 * buf, unsigned int length) +{ + unsigned int start, num, i; + char line[52], *p; + + if (length >= 512) + return; + pr_debug("udc: %s, length %u:\n", label, length); + start = 0; + while (length > 0) { + num = min(length, 16u); + p = line; + for (i = 0; i < num; ++i) { + if (i == 8) + *p++ = ' '; + sprintf(p, " %02x", buf[i]); + p += 3; + } + *p = 0; + printk(KERN_DEBUG "%6x: %s\n", start, line); + buf += num; + start += num; + length -= num; + } +} +#endif + +/*-------------------------------------------------------------------------*/ + +/* ### Add board specific defines here + */ + +/* + * ### pipe direction macro from device view + */ +#define USB_RECV (0) /* OUT EP */ +#define USB_SEND (1) /* IN EP */ + +/* + * ### internal used help routines. + */ +#define ep_index(EP) ((EP)->desc->bEndpointAddress&0xF) +#define ep_maxpacket(EP) ((EP)->ep.maxpacket) + +#define ep_is_in(EP) ( (ep_index(EP) == 0) ? (EP->udc->ep0_dir == \ + USB_DIR_IN ):((EP)->desc->bEndpointAddress \ + & USB_DIR_IN)==USB_DIR_IN) + +#define get_ep_by_pipe(udc, pipe) ((pipe == 1)? &udc->eps[0]: \ + &udc->eps[pipe]) + +/* Bulk only class request */ +#define USB_BULK_RESET_REQUEST 0xff + +#endif /* __ARCOTG_UDC_H */ diff --git a/drivers/usb/gadget/ether.c b/drivers/usb/gadget/ether.c index 9e732bff9df0..d786788b9c02 100644 --- a/drivers/usb/gadget/ether.c +++ b/drivers/usb/gadget/ether.c @@ -239,6 +239,10 @@ MODULE_PARM_DESC(host_addr, "Host Ethernet Address"); #define DEV_CONFIG_CDC #endif +#ifdef CONFIG_USB_GADGET_ARC +#define DEV_CONFIG_CDC +#endif + #ifdef CONFIG_USB_GADGET_S3C2410 #define DEV_CONFIG_CDC #endif diff --git a/drivers/usb/gadget/gadget_chips.h b/drivers/usb/gadget/gadget_chips.h index f7f159c1002b..579cd6e992c1 100644 --- a/drivers/usb/gadget/gadget_chips.h +++ b/drivers/usb/gadget/gadget_chips.h @@ -88,6 +88,12 @@ #define gadget_is_pxa27x(g) 0 #endif +#ifdef CONFIG_USB_GADGET_ARC +#define gadget_is_arcotg(g) !strcmp("arc_udc", (g)->name) +#else +#define gadget_is_arcotg(g) 0 +#endif + #ifdef CONFIG_USB_GADGET_ATMEL_USBA #define gadget_is_atmel_usba(g) !strcmp("atmel_usba_udc", (g)->name) #else @@ -212,5 +218,7 @@ static inline int usb_gadget_controller_number(struct usb_gadget *gadget) return 0x20; else if (gadget_is_m66592(gadget)) return 0x21; + else if (gadget_is_arcotg(gadget)) + return 0x22; return -ENOENT; } diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 49a91c5ee51b..35b34bf3908c 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -29,6 +29,52 @@ config USB_EHCI_HCD To compile this driver as a module, choose M here: the module will be called ehci-hcd. +config USB_EHCI_ARC + bool "Support for ARC controller" + depends on USB_EHCI_HCD && ARCH_MXC + ---help--- + Some Freescale processors have an ARC High Speed + USBOTG controller, which supports EHCI host mode. + + Say "y" here to add support for this controller + to the EHCI HCD driver. + +config USB_EHCI_ARC_H1 + bool "Support for Host1 port on ARC controller" + depends on USB_EHCI_ARC && (ARCH_MX27 || ARCH_MX3) + ---help--- + Enable support for the USB Host1 port. + +config USB_EHCI_ARC_H2 + bool "Support for Host2 port on ARC controller" + depends on USB_EHCI_ARC && (ARCH_MX27 || ARCH_MX3) + ---help--- + Enable support for the USB Host2 port. + +config USB_EHCI_ARC_OTG + bool "Support for OTG host port on ARC controller" + depends on USB_EHCI_ARC + default y + ---help--- + Enable support for the USB OTG port in HS/FS Host mode. + +choice + prompt "Select transceiver speed" + depends on USB_EHCI_ARC_OTG + default USB_EHCI_ARC_OTGHS + default USB_EHCI_ARC_OTGFS if ARCH_MX27 + +config USB_EHCI_ARC_OTGHS + bool "High Speed" + ---help--- + Enable support for the USB OTG port in HS Host mode. + +config USB_EHCI_ARC_OTGFS + bool "Full Speed" + ---help--- + Enable support for the USB OTG port in FS Host mode. +endchoice + config USB_EHCI_SPLIT_ISO bool "Full speed ISO transactions (EXPERIMENTAL)" depends on USB_EHCI_HCD && EXPERIMENTAL @@ -41,6 +87,7 @@ config USB_EHCI_SPLIT_ISO config USB_EHCI_ROOT_HUB_TT bool "Root Hub Transaction Translators (EXPERIMENTAL)" depends on USB_EHCI_HCD && EXPERIMENTAL + default y if USB_EHCI_ARC ---help--- Some EHCI chips have vendor-specific extensions to integrate transaction translators, so that no OHCI or UHCI companion diff --git a/drivers/usb/host/ehci-arc.c b/drivers/usb/host/ehci-arc.c new file mode 100644 index 000000000000..99d6772302de --- /dev/null +++ b/drivers/usb/host/ehci-arc.c @@ -0,0 +1,393 @@ +/* + * drivers/usb/host/ehci-arc.c + * + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @defgroup USB ARC OTG USB Driver + */ +/*! + * @file ehci-arc.c + * @brief platform related part of usb host driver. + * @ingroup USB + */ + +/*! + * Include files + */ + +/* Note: this file is #included by ehci-hcd.c */ + +#include <linux/platform_device.h> +#include <linux/fsl_devices.h> +#include <linux/usb/otg.h> +#include <linux/usb/fsl_xcvr.h> +#include <asm/arch/fsl_usb.h> + +#include "ehci-fsl.h" + +#undef dbg +#undef vdbg + +#if 0 +#define dbg printk +#else +#define dbg(fmt, ...) do {} while (0) +#endif + +#if 0 +#define vdbg dbg +#else +#define vdbg(fmt, ...) do {} while (0) +#endif + +extern void fsl_platform_set_vbus_power(struct fsl_usb2_platform_data *pdata, + int on); + +/* PCI-based HCs are common, but plenty of non-PCI HCs are used too */ + +/* configure so an HC device and id are always provided */ +/* always called with process context; sleeping is OK */ + +/** + * usb_hcd_fsl_probe - initialize FSL-based HCDs + * @drvier: Driver to be used for this HCD + * @pdev: USB Host Controller being probed + * Context: !in_interrupt() + * + * Allocates basic resources for this USB host controller. + * + */ +static int usb_hcd_fsl_probe(const struct hc_driver *driver, + struct platform_device *pdev) +{ + struct fsl_usb2_platform_data *pdata; + struct usb_hcd *hcd; + struct resource *res; + int irq; + int retval; + + pr_debug("initializing FSL-SOC USB Controller\n"); + + /* Need platform data for setup */ + pdata = (struct fsl_usb2_platform_data *)pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, + "No platform data for %s.\n", pdev->dev.bus_id); + return -ENODEV; + } + + retval = fsl_platform_verify(pdev); + if (retval) + return retval; + + /* + * do platform specific init: check the clock, grab/config pins, etc. + */ + if (pdata->platform_init && pdata->platform_init(pdev)) { + retval = -ENODEV; + goto err1; + } + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(&pdev->dev, + "Found HC with no IRQ. Check %s setup!\n", + pdev->dev.bus_id); + return -ENODEV; + } + irq = res->start; + + fsl_platform_set_vbus_power(pdata, 1); + + hcd = usb_create_hcd(driver, &pdev->dev, pdev->dev.bus_id); + if (!hcd) { + retval = -ENOMEM; + goto err1; + } + + hcd->rsrc_start = pdata->r_start; + hcd->rsrc_len = pdata->r_len; + hcd->regs = pdata->regs; + vdbg("rsrc_start=0x%llx rsrc_len=0x%llx virtual=0x%x\n", + hcd->rsrc_start, hcd->rsrc_len, hcd->regs); + + hcd->power_budget = pdata->power_budget; + + /* DDD + * the following must be done by this point, otherwise the OTG + * host port doesn't make it thru initializtion. + * ehci_halt(), called by ehci_fsl_setup() returns -ETIMEDOUT + */ + fsl_platform_set_host_mode(hcd); + + retval = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (retval != 0) { + pr_debug("failed with usb_add_hcd\n"); + goto err2; + } +#if defined(CONFIG_USB_OTG) + if (pdata->does_otg) { + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + + dbg("pdev=0x%p hcd=0x%p ehci=0x%p\n", pdev, hcd, ehci); + + ehci->transceiver = otg_get_transceiver(); + dbg("ehci->transceiver=0x%p\n", ehci->transceiver); + + if (ehci->transceiver) { + retval = otg_set_host(ehci->transceiver, + &ehci_to_hcd(ehci)->self); + dev_dbg(ehci->transceiver->dev, + "init %s transceiver, retval %d\n", + ehci->transceiver->label, retval); + if (retval) { + if (ehci->transceiver) + put_device(ehci->transceiver->dev); + goto err2; + } + } else { + printk(KERN_ERR "can't find transceiver\n"); + retval = -ENODEV; + goto err2; + } + } +#endif + + return retval; + + err2: + usb_put_hcd(hcd); + err1: + dev_err(&pdev->dev, "init %s fail, %d\n", pdev->dev.bus_id, retval); + if (pdata->platform_uninit) + pdata->platform_uninit(pdata); + return retval; +} + +static void usb_hcd_fsl_remove(struct usb_hcd *hcd, + struct platform_device *pdev) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + struct fsl_usb2_platform_data *pdata; + pdata = (struct fsl_usb2_platform_data *)pdev->dev.platform_data; + + dbg("%s hcd=0x%p\n", __FUNCTION__, hcd); + + /* DDD shouldn't we turn off the power here? */ + fsl_platform_set_vbus_power(pdata, 0); + + usb_remove_hcd(hcd); + usb_put_hcd(hcd); + + if (ehci->transceiver) { + (void)otg_set_host(ehci->transceiver, 0); + put_device(ehci->transceiver->dev); + } + + /* + * do platform specific un-initialization: + * release iomux pins, etc. + */ + if (pdata->platform_uninit) + pdata->platform_uninit(pdata); +} + +/* called after powerup, by probe or system-pm "wakeup" */ +static int ehci_fsl_reinit(struct ehci_hcd *ehci) +{ + fsl_platform_usb_setup(ehci_to_hcd(ehci)); + ehci_port_power(ehci, 0); + + return 0; +} + +/* called during probe() after chip reset completes */ +static int ehci_fsl_setup(struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + int retval; + + /* EHCI registers start at offset 0x00 */ + ehci->caps = hcd->regs + 0x100; + ehci->regs = hcd->regs + 0x100 + + HC_LENGTH(readl(&ehci->caps->hc_capbase)); + + vdbg("%s(): ehci->caps=0x%p ehci->regs=0x%p\n", __FUNCTION__, + ehci->caps, ehci->regs); + + dbg_hcs_params(ehci, "reset"); + dbg_hcc_params(ehci, "reset"); + + /* cache this readonly data; minimize chip reads */ + ehci->hcs_params = readl(&ehci->caps->hcs_params); + + retval = ehci_halt(ehci); + if (retval) + return retval; + + /* data structure init */ + retval = ehci_init(hcd); + if (retval) + return retval; + + ehci->is_tdi_rh_tt = 1; + + ehci->sbrn = 0x20; + + ehci_reset(ehci); + + retval = ehci_fsl_reinit(ehci); + return retval; +} + +/* *INDENT-OFF* */ +static const struct hc_driver ehci_arc_hc_driver = { + .description = hcd_name, + .product_desc = "Freescale On-Chip EHCI Host Controller", + .hcd_priv_size = sizeof(struct ehci_hcd), + + /* + * generic hardware linkage + */ + .irq = ehci_irq, + .flags = FSL_PLATFORM_HC_FLAGS, + + /* + * basic lifecycle operations + */ + .reset = ehci_fsl_setup, + .start = ehci_run, + .stop = ehci_stop, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = ehci_urb_enqueue, + .urb_dequeue = ehci_urb_dequeue, + .endpoint_disable = ehci_endpoint_disable, + + /* + * scheduling support + */ + .get_frame_number = ehci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ehci_hub_status_data, + .hub_control = ehci_hub_control, + .bus_suspend = ehci_bus_suspend, + .bus_resume = ehci_bus_resume, +}; +/* *INDENT-ON* */ + +#ifdef CONFIG_USB_OTG +volatile static struct ehci_regs usb_ehci_regs; + +/* suspend/resume, section 4.3 */ + +/* These routines rely on the bus (pci, platform, etc) + * to handle powerdown and wakeup, and currently also on + * transceivers that don't need any software attention to set up + * the right sort of wakeup. + * + * They're also used for turning on/off the port when doing OTG. + */ +static int ehci_arc_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + struct fsl_usb2_platform_data *pdata = + (struct fsl_usb2_platform_data *)pdev->dev.platform_data; + u32 cmd; + + dbg("%s pdev=0x%p pdata=0x%p ehci=0x%p hcd=0x%p\n", + __FUNCTION__, pdev, pdata, ehci, hcd); + dbg("%s ehci->regs=0x%p hcd->regs=0x%p hcd->state=%d\n", + __FUNCTION__, ehci->regs, hcd->regs, hcd->state); + dbg("%s pdata->usbmode=0x%x\n", __FUNCTION__, pdata->usbmode); + + hcd->state = HC_STATE_HALT; /* ignore non-host interrupts */ + + cmd = readl(&ehci->regs->command); + cmd &= ~CMD_RUN; + writel(cmd, &ehci->regs->command); + + memcpy((void *)&usb_ehci_regs, ehci->regs, sizeof(struct ehci_regs)); + usb_ehci_regs.port_status[0] &= + cpu_to_le32(~(PORT_PEC | PORT_OCC | PORT_CSC)); + + fsl_platform_set_vbus_power(pdata, 0); + + return 0; +} + +static int ehci_arc_resume(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + u32 cmd; + struct fsl_usb2_platform_data *pdata = + (struct fsl_usb2_platform_data *)pdev->dev.platform_data; + + dbg("%s pdev=0x%p pdata=0x%p ehci=0x%p hcd=0x%p\n", + __FUNCTION__, pdev, pdata, ehci, hcd); + vdbg("%s ehci->regs=0x%p hcd->regs=0x%p usbmode=0x%x\n", + __FUNCTION__, ehci->regs, hcd->regs, pdata->usbmode); + + writel(USBMODE_CM_HOST, pdata->usbmode); + memcpy(ehci->regs, (void *)&usb_ehci_regs, sizeof(struct ehci_regs)); + + hcd->state = HC_STATE_RUNNING; + + cmd = readl(&ehci->regs->command); + cmd |= CMD_RUN; + writel(cmd, &ehci->regs->command); + + fsl_platform_set_vbus_power(pdata, 1); + + return 0; +} +#endif /* CONFIG_USB_OTG */ + +static int ehci_hcd_drv_probe(struct platform_device *pdev) +{ + if (usb_disabled()) + return -ENODEV; + + return usb_hcd_fsl_probe(&ehci_arc_hc_driver, pdev); +} + +static int __init_or_module ehci_hcd_drv_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + + usb_hcd_fsl_remove(hcd, pdev); + + return 0; +} + +/* *INDENT-OFF* */ +static struct platform_driver ehci_fsl_driver = { + .probe = ehci_hcd_drv_probe, + .remove = ehci_hcd_drv_remove, +#ifdef CONFIG_USB_OTG + .suspend = ehci_arc_suspend, + .resume = ehci_arc_resume, +#endif + .driver = { + .name = "fsl-ehci", + }, +}; +/* *INDENT-ON* */ diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 5f2d74ed5ad7..7bfd0bcc7bd0 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -954,6 +954,11 @@ MODULE_LICENSE ("GPL"); #define PLATFORM_DRIVER ehci_hcd_au1xxx_driver #endif +#ifdef CONFIG_USB_EHCI_ARC +#include "ehci-arc.c" +#define PLATFORM_DRIVER ehci_fsl_driver +#endif + #ifdef CONFIG_PPC_PS3 #include "ehci-ps3.c" #define PS3_SYSTEM_BUS_DRIVER ps3_ehci_driver diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index 951d69fec513..8946ef4482a7 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -120,6 +120,12 @@ struct ehci_hcd { /* one per controller */ u8 sbrn; /* packed release number */ + /* + * OTG controllers and transceivers need software interaction; + * other external transceivers should be software-transparent + */ + struct otg_transceiver *transceiver; + /* irq statistics */ #ifdef EHCI_STATS struct ehci_stats stats; diff --git a/drivers/usb/otg/Kconfig b/drivers/usb/otg/Kconfig new file mode 100644 index 000000000000..20ccb828b740 --- /dev/null +++ b/drivers/usb/otg/Kconfig @@ -0,0 +1,5 @@ +config TRANSCEIVER_MXC_OTG + tristate "usb otg pin detect support" + depends on (MC13783_MXC || ISP1504_MXC) && USB_GADGET && USB_EHCI_HCD && USB_OTG + help + Support for USB OTG PIN detect on MXC platforms. diff --git a/drivers/usb/otg/Makefile b/drivers/usb/otg/Makefile new file mode 100644 index 000000000000..dc37de2c2360 --- /dev/null +++ b/drivers/usb/otg/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for USB OTG controller driver +# +# USB transceiver +fsl_otg_arc-objs := fsl_otg.o otg_fsm.o +obj-$(CONFIG_TRANSCEIVER_MXC_OTG) += fsl_otg_arc.o diff --git a/drivers/usb/otg/fsl_otg.c b/drivers/usb/otg/fsl_otg.c new file mode 100644 index 000000000000..1436fa1c6c4b --- /dev/null +++ b/drivers/usb/otg/fsl_otg.c @@ -0,0 +1,1078 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/proc_fs.h> +#include <linux/errno.h> +#include <linux/timer.h> +#include <linux/list.h> +#include <linux/usb.h> +#include <linux/platform_device.h> +#include <linux/usb_gadget.h> +#include <linux/time.h> + +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/system.h> + +#include <linux/fsl_devices.h> +#include "fsl_otg.h" +#include <asm/arch/arc_otg.h> + +#define CONFIG_USB_OTG_DEBUG_FILES +#define DRIVER_VERSION "Revision: 1.0" +#define DRIVER_AUTHOR "Jerry Huang/Leo Li" +#define DRIVER_DESC "USB OTG Driver" +#define DRIVER_INFO DRIVER_VERSION " " DRIVER_DESC + +MODULE_DESCRIPTION("ARC USB OTG Transceiver Driver"); + +static const char otg_dr_name[] = "fsl_arc"; +static spinlock_t usb_dr_regs_lock; + +#undef HA_DATA_PULSE + +volatile static struct usb_dr_mmap *usb_dr_regs; +static struct fsl_otg *fsl_otg_dev = NULL; +static int srp_wait_done; + +/* FSM timers */ +struct fsl_otg_timer *a_wait_vrise_tmr, *a_wait_bcon_tmr, *a_aidl_bdis_tmr, + *b_ase0_brst_tmr, *b_se0_srp_tmr; + +/* Driver specific timers */ +struct fsl_otg_timer *b_data_pulse_tmr, *b_vbus_pulse_tmr, *b_srp_fail_tmr, + *b_srp_wait_tmr, *a_wait_enum_tmr; + +static struct list_head active_timers; + +static struct fsl_otg_config fsl_otg_initdata = { + .otg_port = 1, +}; + +/** + * usb_bus_start_enum - start immediate enumeration (for OTG) + * @bus: the bus (must use hcd framework) + * @port: 1-based number of port; usually bus->otg_port + * Context: in_interrupt() + * + * Starts enumeration, with an immediate reset followed later by + * khubd identifying and possibly configuring the device. + * This is needed by OTG controller drivers, where it helps meet + * HNP protocol timing requirements for starting a port reset. + */ + +#include "../../../drivers/usb/core/hcd.h" + +int usb_bus_start_enum(struct usb_bus *bus, unsigned port_num) +{ + struct usb_hcd *hcd; + int status = -EOPNOTSUPP; + + /* NOTE: since HNP can't start by grabbing the bus's address0_sem, + * boards with root hubs hooked up to internal devices (instead of + * just the OTG port) may need more attention to resetting... + */ + + hcd = container_of(bus, struct usb_hcd, self); + if (port_num && hcd->driver->start_port_reset) + status = hcd->driver->start_port_reset(hcd, port_num); + + /* run khubd shortly after (first) root port reset finishes; + * it may issue others, until at least 50 msecs have passed. + */ + if (status == 0) + mod_timer(&hcd->rh_timer, jiffies + msecs_to_jiffies(10)); + + return status; +} + +#if defined(CONFIG_ISP1504_MXC) +int write_ulpi(u8 addr, u8 data) +{ + u32 temp; + temp = 0x60000000 | (addr << 16) | data; + temp = cpu_to_le32(temp); + usb_dr_regs->ulpiview = temp; + return 0; +} +#endif + +/* prototype declaration */ +void fsl_otg_add_timer(void *timer); +void fsl_otg_del_timer(void *timer); + +/* -------------------------------------------------------------*/ +/* Operations that will be called from OTG Finite State Machine */ + +/* Charge vbus for vbus pulsing in SRP */ +void fsl_otg_chrg_vbus(int on) +{ + if (on) + usb_dr_regs->otgsc = + cpu_to_le32((le32_to_cpu(usb_dr_regs->otgsc) & + ~OTGSC_INTSTS_MASK & + ~OTGSC_CTRL_VBUS_DISCHARGE) | + OTGSC_CTRL_VBUS_CHARGE); + else + usb_dr_regs->otgsc = + cpu_to_le32((le32_to_cpu(usb_dr_regs->otgsc) & + ~OTGSC_INTSTS_MASK & ~OTGSC_CTRL_VBUS_CHARGE)); +} + +/* Discharge vbus through a resistor to ground */ +void fsl_otg_dischrg_vbus(int on) +{ + if (on) + usb_dr_regs->otgsc = + cpu_to_le32((le32_to_cpu(usb_dr_regs->otgsc) & + ~OTGSC_INTSTS_MASK) + | OTGSC_CTRL_VBUS_DISCHARGE); + else + usb_dr_regs->otgsc = + cpu_to_le32((le32_to_cpu(usb_dr_regs->otgsc) & + ~OTGSC_INTSTS_MASK & + ~OTGSC_CTRL_VBUS_DISCHARGE)); +} + +/* A-device driver vbus, controlled through PP bit in PORTSC */ +void fsl_otg_drv_vbus(int on) +{ + if (on) + usb_dr_regs->portsc = + cpu_to_le32((le32_to_cpu(usb_dr_regs->portsc) & + ~PORTSC_W1C_BITS) | PORTSC_PORT_POWER); + else + usb_dr_regs->portsc = + cpu_to_le32(le32_to_cpu(usb_dr_regs->portsc) & + ~PORTSC_W1C_BITS & ~PORTSC_PORT_POWER); + +} + +/* Pull-up D+, signalling connect by periperal. Also used in + * data-line pulsing in SRP */ +void fsl_otg_loc_conn(int on) +{ + if (on) + usb_dr_regs->otgsc = + cpu_to_le32((le32_to_cpu(usb_dr_regs->otgsc) & + ~OTGSC_INTSTS_MASK) | OTGSC_CTRL_DATA_PULSING); + else + usb_dr_regs->otgsc = + cpu_to_le32(le32_to_cpu(usb_dr_regs->otgsc) & + ~OTGSC_INTSTS_MASK & ~OTGSC_CTRL_DATA_PULSING); +} + +/* Generate SOF by host. This is controlled through suspend/resume the + * port. In host mode, controller will automatically send SOF. + * Suspend will block the data on the port. + */ +void fsl_otg_loc_sof(int on) +{ +} + +/* Start SRP pulsing by data-line pulsing, followed with v-bus pulsing. */ +void fsl_otg_start_pulse(void) +{ + srp_wait_done = 0; +#ifdef HA_DATA_PULSE + usb_dr_regs->otgsc = + cpu_to_le32((le32_to_cpu(usb_dr_regs->otgsc) & ~OTGSC_INTSTS_MASK) + | OTGSC_HA_DATA_PULSE); +#else + fsl_otg_loc_conn(1); +#endif + + fsl_otg_add_timer(b_data_pulse_tmr); +} + +void fsl_otg_pulse_vbus(void); + +void b_data_pulse_end(unsigned long foo) +{ +#ifdef HA_DATA_PULSE +#else + fsl_otg_loc_conn(0); +#endif + + /* Do VBUS pulse after data pulse */ + fsl_otg_pulse_vbus(); +} + +void fsl_otg_pulse_vbus(void) +{ + srp_wait_done = 0; + fsl_otg_chrg_vbus(1); + /* start the timer to end vbus charge */ + fsl_otg_add_timer(b_vbus_pulse_tmr); +} + +void b_vbus_pulse_end(unsigned long foo) +{ + fsl_otg_chrg_vbus(0); + + /* As USB3300 using the same a_sess_vld and b_sess_vld voltage + * we need to discharge the bus for a while to distinguish + * residual voltage of vbus pulsing and A device pull up */ + fsl_otg_dischrg_vbus(1); + fsl_otg_add_timer(b_srp_wait_tmr); +} + +void b_srp_end(unsigned long foo) +{ + fsl_otg_dischrg_vbus(0); + srp_wait_done = 1; + + if ((fsl_otg_dev->otg.state == OTG_STATE_B_SRP_INIT) && + fsl_otg_dev->fsm.b_sess_vld) + fsl_otg_dev->fsm.b_srp_done = 1; +} + +/* Workaround for a_host suspending too fast. When a_bus_req=0, + * a_host will start by SRP. It needs to set b_hnp_enable before + * actually suspending to start HNP + */ +void a_wait_enum(unsigned long foo) +{ + VDBG("a_wait_enum timeout\n"); + if (!fsl_otg_dev->otg.host->b_hnp_enable) + fsl_otg_add_timer(a_wait_enum_tmr); + else + otg_statemachine(&fsl_otg_dev->fsm); +} + +/* ------------------------------------------------------*/ + +/* The timeout callback function to set time out bit */ +void set_tmout(unsigned long indicator) +{ + *(int *)indicator = 1; +} + +/* Initialize timers */ +int fsl_otg_init_timers(struct otg_fsm *fsm) +{ + /* FSM used timers */ + a_wait_vrise_tmr = otg_timer_initializer(&set_tmout, TA_WAIT_VRISE, + (unsigned long)&fsm-> + a_wait_vrise_tmout); + if (a_wait_vrise_tmr == NULL) + return -ENOMEM; + + a_wait_bcon_tmr = + otg_timer_initializer(&set_tmout, TA_WAIT_BCON, + (unsigned long)&fsm->a_wait_bcon_tmout); + if (a_wait_bcon_tmr == NULL) + return -ENOMEM; + + a_aidl_bdis_tmr = + otg_timer_initializer(&set_tmout, TA_AIDL_BDIS, + (unsigned long)&fsm->a_aidl_bdis_tmout); + if (a_aidl_bdis_tmr == NULL) + return -ENOMEM; + + b_ase0_brst_tmr = + otg_timer_initializer(&set_tmout, TB_ASE0_BRST, + (unsigned long)&fsm->b_ase0_brst_tmout); + if (b_ase0_brst_tmr == NULL) + return -ENOMEM; + + b_se0_srp_tmr = + otg_timer_initializer(&set_tmout, TB_SE0_SRP, + (unsigned long)&fsm->b_se0_srp); + if (b_se0_srp_tmr == NULL) + return -ENOMEM; + + b_srp_fail_tmr = + otg_timer_initializer(&set_tmout, TB_SRP_FAIL, + (unsigned long)&fsm->b_srp_done); + if (b_srp_fail_tmr == NULL) + return -ENOMEM; + + a_wait_enum_tmr = + otg_timer_initializer(&a_wait_enum, 10, (unsigned long)&fsm); + if (a_wait_enum_tmr == NULL) + return -ENOMEM; + + /* device driver used timers */ + b_srp_wait_tmr = otg_timer_initializer(&b_srp_end, TB_SRP_WAIT, 0); + if (b_srp_wait_tmr == NULL) + return -ENOMEM; + + b_data_pulse_tmr = otg_timer_initializer(&b_data_pulse_end, + TB_DATA_PLS, 0); + if (b_data_pulse_tmr == NULL) + return -ENOMEM; + + b_vbus_pulse_tmr = otg_timer_initializer(&b_vbus_pulse_end, + TB_VBUS_PLS, 0); + if (b_vbus_pulse_tmr == NULL) + return -ENOMEM; + + return 0; +} + +/* Uninitialize timers */ +void fsl_otg_uninit_timers(void) +{ + /* FSM used timers */ + if (a_wait_vrise_tmr != NULL) + kfree(a_wait_vrise_tmr); + if (a_wait_bcon_tmr != NULL) + kfree(a_wait_bcon_tmr); + if (a_aidl_bdis_tmr != NULL) + kfree(a_aidl_bdis_tmr); + if (b_ase0_brst_tmr != NULL) + kfree(b_ase0_brst_tmr); + if (b_se0_srp_tmr != NULL) + kfree(b_se0_srp_tmr); + if (b_srp_fail_tmr != NULL) + kfree(b_srp_fail_tmr); + if (a_wait_enum_tmr != NULL) + kfree(a_wait_enum_tmr); + + /* device driver used timers */ + if (b_srp_wait_tmr != NULL) + kfree(b_srp_wait_tmr); + if (b_data_pulse_tmr != NULL) + kfree(b_data_pulse_tmr); + if (b_vbus_pulse_tmr != NULL) + kfree(b_vbus_pulse_tmr); +} + +/* Add timer to timer list */ +void fsl_otg_add_timer(void *gtimer) +{ + struct fsl_otg_timer *timer = (struct fsl_otg_timer *)gtimer; + struct fsl_otg_timer *tmp_timer; + + /* Check if the timer is already in the active list, + * if so update timer count + */ + list_for_each_entry(tmp_timer, &active_timers, list) + if (tmp_timer == timer) { + timer->count = timer->expires; + return; + } + timer->count = timer->expires; + list_add_tail(&timer->list, &active_timers); +} + +/* Remove timer from the timer list; clear timeout status */ +void fsl_otg_del_timer(void *gtimer) +{ + struct fsl_otg_timer *timer = (struct fsl_otg_timer *)gtimer; + struct fsl_otg_timer *tmp_timer, *del_tmp; + + list_for_each_entry_safe(tmp_timer, del_tmp, &active_timers, list) + if (tmp_timer == timer) + list_del(&timer->list); +} + +/* Reduce timer count by 1, and find timeout conditions. + * Called by fsl_otg 1ms timer interrupt + */ +int fsl_otg_tick_timer(void) +{ + struct fsl_otg_timer *tmp_timer, *del_tmp; + int expired = 0; + + list_for_each_entry_safe(tmp_timer, del_tmp, &active_timers, list) { + tmp_timer->count--; + /* check if timer expires */ + if (!tmp_timer->count) { + list_del(&tmp_timer->list); + tmp_timer->function(tmp_timer->data); + expired = 1; + } + } + + return expired; +} + +/* Reset controller, not reset the bus */ +void otg_reset_controller(void) +{ + u32 command; + unsigned long flags; + + spin_lock_irqsave(&usb_dr_regs_lock, flags); + command = readl(&usb_dr_regs->usbcmd); + command |= UCMD_RESET; + writel(command, &usb_dr_regs->usbcmd); + spin_unlock_irqrestore(&usb_dr_regs_lock, flags); + while (readl(&usb_dr_regs->usbcmd) & UCMD_RESET) + continue; +} + +/* Call suspend/resume routines in host driver */ +int fsl_otg_start_host(struct otg_fsm *fsm, int on) +{ + struct otg_transceiver *xceiv = fsm->transceiver; + struct device *dev; + struct fsl_otg *otg_dev = container_of(xceiv, struct fsl_otg, otg); + u32 retval = 0; + pm_message_t state = { 0 }; + + if (!xceiv->host) + return -ENODEV; + + dev = xceiv->host->controller; + + /* Update a_vbus_vld state as a_vbus_vld int is disabled + * in device mode + */ + fsm->a_vbus_vld = + (le32_to_cpu(usb_dr_regs->otgsc) & OTGSC_STS_A_VBUS_VALID) ? 1 : 0; + if (on) { + /* start fsl usb host controller */ + if (otg_dev->host_working) + goto end; + else { + otg_reset_controller(); + VDBG("host on......"); + if (dev->driver->resume) { + retval = dev->driver->resume(dev); + if (fsm->id) { + /* default-b */ + fsl_otg_drv_vbus(1); + /* Workaround: b_host can't driver + * vbus, but PP in PORTSC needs to + * be 1 for host to work. + * So we set drv_vbus bit in + * transceiver to 0 thru ULPI. */ +#if defined(CONFIG_ISP1504_MXC) + write_ulpi(0x0c, 0x20); +#endif + } + } + + otg_dev->host_working = 1; + } + } else { + /* stop fsl usb host controller */ + if (!otg_dev->host_working) + goto end; + else { + VDBG("host off......"); + if (dev && dev->driver) { + retval = dev->driver->suspend(dev, state); + if (fsm->id) + /* default-b */ + fsl_otg_drv_vbus(0); + } + otg_dev->host_working = 0; + } + } + end: + return retval; +} + +/* Call suspend and resume function in udc driver + * to stop and start udc driver. + */ +int fsl_otg_start_gadget(struct otg_fsm *fsm, int on) +{ + struct otg_transceiver *xceiv = fsm->transceiver; + struct device *udc_dev; + pm_message_t state = { 0 }; + + if (!xceiv->gadget || !xceiv->gadget->dev.parent) + return -ENODEV; + + VDBG("gadget %s", on ? "on" : "off"); + udc_dev = xceiv->gadget->dev.parent; + + if (on) + udc_dev->driver->resume(udc_dev); + else + udc_dev->driver->suspend(udc_dev, state); + + return 0; +} + +/* Called by initialization code of host driver. Register host controller + * to the OTG. Suspend host for OTG role detection. + */ +static int fsl_otg_set_host(struct otg_transceiver *otg_p, struct usb_bus *host) +{ + struct fsl_otg *otg_dev = container_of(otg_p, struct fsl_otg, otg); + struct device *dev; + pm_message_t state = { 0 }; + + if (!otg_p || otg_dev != fsl_otg_dev) + return -ENODEV; + + otg_p->host = host; + + otg_dev->fsm.a_bus_drop = 0; + otg_dev->fsm.a_bus_req = 1; + + if (host) { + VDBG("host off......\n"); + + otg_p->host->otg_port = fsl_otg_initdata.otg_port; + otg_p->host->is_b_host = otg_dev->fsm.id; + dev = host->controller; + + if (dev && dev->driver) + dev->driver->suspend(dev, state); + } else { /* host driver going away */ + + if (!(le32_to_cpu(otg_dev->dr_mem_map->otgsc) & + OTGSC_STS_USB_ID)) { + /* Mini-A cable connected */ + struct otg_fsm *fsm = &otg_dev->fsm; + + otg_p->state = OTG_STATE_UNDEFINED; + fsm->protocol = PROTO_UNDEF; + } + } + + otg_dev->host_working = 0; + + otg_statemachine(&otg_dev->fsm); + + return 0; +} + +/* Called by initialization code of udc. Register udc to OTG.*/ +static int fsl_otg_set_peripheral(struct otg_transceiver *otg_p, + struct usb_gadget *gadget) +{ + struct fsl_otg *otg_dev = container_of(otg_p, struct fsl_otg, otg); + + VDBG("otg_dev 0x%x", (int)otg_dev); + VDBG("fsl_otg_dev 0x%x", (int)fsl_otg_dev); + + if (!otg_p || otg_dev != fsl_otg_dev) + return -ENODEV; + + if (!gadget) { + if (!otg_dev->otg.default_a) + otg_p->gadget->ops->vbus_draw(otg_p->gadget, 0); + usb_gadget_vbus_disconnect(otg_dev->otg.gadget); + otg_dev->otg.gadget = 0; + otg_dev->fsm.b_bus_req = 0; + otg_statemachine(&otg_dev->fsm); + return 0; + } +#ifdef DEBUG + /* + * debug the initial state of the ID pin when only + * the gadget driver is loaded and no cable is connected. + * sometimes, we get an ID irq right + * after the udc driver's otg_get_transceiver() call + * that indicates that IDpin=0, which means a Mini-A + * connector is attached. not good. + */ + DBG("before: fsm.id ID pin=%d", otg_dev->fsm.id); + otg_dev->fsm.id = (otg_dev->dr_mem_map->otgsc & OTGSC_STS_USB_ID) ? + 1 : 0; + DBG("after: fsm.id ID pin=%d", otg_dev->fsm.id); + /*if (!otg_dev->fsm.id) { + printk("OTG Control = 0x%x\n", + isp1504_read(ISP1504_OTGCTL, + &otg_dev->dr_mem_map->ulpiview)); + } */ +#endif + + otg_p->gadget = gadget; + otg_p->gadget->is_a_peripheral = !otg_dev->fsm.id; + + otg_dev->fsm.b_bus_req = 1; + + /* start the gadget right away if the ID pin says Mini-B */ + DBG("ID pin=%d", otg_dev->fsm.id); + if (otg_dev->fsm.id == 1) { + fsl_otg_start_host(&otg_dev->fsm, 0); + otg_drv_vbus(&otg_dev->fsm, 0); + fsl_otg_start_gadget(&otg_dev->fsm, 1); + } + + return 0; +} + +/* Set OTG port power, only for B-device */ +static int fsl_otg_set_power(struct otg_transceiver *otg_p, unsigned mA) +{ + if (!fsl_otg_dev) + return -ENODEV; + if (otg_p->state == OTG_STATE_B_PERIPHERAL) + printk("FSL OTG:Draw %d mA\n", mA); + + return 0; +} + +/* Delayed pin detect interrupt processing. + * + * When the Mini-A cable is disconnected from the board, + * the pin-detect interrupt happens before the disconnnect + * interrupts for the connected device(s). In order to + * process the disconnect interrupt(s) prior to switching + * roles, the pin-detect interrupts are delayed, and handled + * by this routine. + */ +static void fsl_otg_event(struct work_struct *work) +{ + struct delayed_work *dwork = + container_of(work, struct delayed_work, work); + struct fsl_otg *otg = container_of(dwork, struct fsl_otg, otg_event); + struct otg_fsm *fsm = &otg->fsm; + + if (fsm->id) { /* switch to gadget */ + fsl_otg_start_host(fsm, 0); + otg_drv_vbus(fsm, 0); + fsl_otg_start_gadget(fsm, 1); + } +} + +/* Interrupt handler. OTG/host/peripheral share the same int line. + * OTG driver clears OTGSC interrupts and leaves USB interrupts + * intact. It needs to have knowledge of some USB interrupts + * such as port change. + */ +irqreturn_t fsl_otg_isr(int irq, void *dev_id) +{ + struct otg_fsm *fsm = &((struct fsl_otg *)dev_id)->fsm; + struct otg_transceiver *otg = &((struct fsl_otg *)dev_id)->otg; + u32 otg_int_src, otg_sc; + + otg_sc = le32_to_cpu(usb_dr_regs->otgsc); + otg_int_src = otg_sc & OTGSC_INTSTS_MASK & (otg_sc >> 8); + + /* Only clear otg interrupts */ + usb_dr_regs->otgsc |= cpu_to_le32(otg_sc & OTGSC_INTSTS_MASK); + + /*FIXME: ID change not generate when init to 0 */ + fsm->id = (otg_sc & OTGSC_STS_USB_ID) ? 1 : 0; + otg->default_a = (fsm->id == 0); + + /* process OTG interrupts */ + if (otg_int_src) { + if (otg_int_src & OTGSC_IS_USB_ID) { + fsm->id = (otg_sc & OTGSC_STS_USB_ID) ? 1 : 0; + otg->default_a = (fsm->id == 0); + if (otg->host) + otg->host->is_b_host = fsm->id; + if (otg->gadget) + otg->gadget->is_a_peripheral = !fsm->id; + VDBG("IRQ=ID now=%d", fsm->id); + + if (fsm->id) { /* switch to gadget */ + schedule_delayed_work((struct delayed_work *) + &((struct fsl_otg *) + dev_id)->otg_event, 25); + } else { /* switch to host */ + cancel_delayed_work((struct delayed_work *) + &((struct fsl_otg *) + dev_id)->otg_event); + fsl_otg_start_gadget(fsm, 0); + otg_drv_vbus(fsm, 1); + fsl_otg_start_host(fsm, 1); + } + + return IRQ_HANDLED; + } + } + + return IRQ_NONE; +} + +static struct otg_fsm_ops fsl_otg_ops = { + .chrg_vbus = fsl_otg_chrg_vbus, + .drv_vbus = fsl_otg_drv_vbus, + .loc_conn = fsl_otg_loc_conn, + .loc_sof = fsl_otg_loc_sof, + .start_pulse = fsl_otg_start_pulse, + + .add_timer = fsl_otg_add_timer, + .del_timer = fsl_otg_del_timer, + + .start_host = fsl_otg_start_host, + .start_gadget = fsl_otg_start_gadget, +}; + +/* Initialize the global variable fsl_otg_dev and request IRQ for OTG */ +static int fsl_otg_conf(struct platform_device *pdev) +{ + int status; + struct fsl_otg *fsl_otg_tc; + struct fsl_usb2_platform_data *pdata; + + pdata = (struct fsl_usb2_platform_data *)pdev->dev.platform_data; + + DBG(); + + if (fsl_otg_dev) + return 0; + + /* allocate space to fsl otg device */ + fsl_otg_tc = kmalloc(sizeof(struct fsl_otg), GFP_KERNEL); + if (!fsl_otg_tc) + return -ENODEV; + + memset(fsl_otg_tc, 0, sizeof(struct fsl_otg)); + + fsl_otg_tc->dr_mem_map = pdata->regs; + + DBG("set dr_mem_map to 0x%p", pdata->regs); + spin_lock_init(&usb_dr_regs_lock); + + INIT_DELAYED_WORK(&fsl_otg_tc->otg_event, fsl_otg_event); + + INIT_LIST_HEAD(&active_timers); + status = fsl_otg_init_timers(&fsl_otg_tc->fsm); + if (status) { + printk(KERN_INFO "Couldn't init OTG timers\n"); + fsl_otg_uninit_timers(); + kfree(fsl_otg_tc); + return status; + } + + /* Set OTG state machine operations */ + fsl_otg_tc->fsm.ops = &fsl_otg_ops; + + /* record initial state of ID pin */ + fsl_otg_tc->fsm.id = (fsl_otg_tc->dr_mem_map->otgsc & OTGSC_STS_USB_ID) + ? 1 : 0; + DBG("initial ID pin=%d", fsl_otg_tc->fsm.id); + + /* initialize the otg structure */ + fsl_otg_tc->otg.label = DRIVER_DESC; + fsl_otg_tc->otg.set_host = fsl_otg_set_host; + fsl_otg_tc->otg.set_peripheral = fsl_otg_set_peripheral; + fsl_otg_tc->otg.set_power = fsl_otg_set_power; + + fsl_otg_dev = fsl_otg_tc; + + /* Store the otg transceiver */ + status = otg_set_transceiver(&fsl_otg_tc->otg); + if (status) { + printk(KERN_WARNING ": unable to register OTG transceiver.\n"); + return status; + } + + return 0; +} + +/* OTG Initialization*/ +int usb_otg_start(struct platform_device *pdev) +{ + struct fsl_otg *p_otg; + struct otg_transceiver *otg_trans = otg_get_transceiver(); + struct otg_fsm *fsm; + int status; + u32 temp; + struct resource *res; + unsigned long flags; + + DBG(); + + p_otg = container_of(otg_trans, struct fsl_otg, otg); + fsm = &p_otg->fsm; + + /* Initialize the state machine structure with default values */ + SET_OTG_STATE(otg_trans, OTG_STATE_UNDEFINED); + fsm->transceiver = &p_otg->otg; + + usb_dr_regs = p_otg->dr_mem_map; + DBG("set usb_dr_regs to 0x%p", usb_dr_regs); + + /* request irq */ + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(&pdev->dev, "Can't find irq resource.\n"); + return -ENODEV; + } + p_otg->irq = res->start; + DBG("requesting irq %d", p_otg->irq); + status = + request_irq(p_otg->irq, fsl_otg_isr, IRQF_SHARED, "fsl_arc", p_otg); + if (status) { + dev_dbg(p_otg->otg.dev, "can't get IRQ %d, error %d\n", + p_otg->irq, status); + kfree(p_otg); + return status; + } + + /* + * The ID input is FALSE when a Mini-A plug is inserted + * in the Mini-AB receptacle. Otherwise, this input is TRUE. + */ + if (le32_to_cpu(p_otg->dr_mem_map->otgsc) & OTGSC_STS_USB_ID) + p_otg->otg.state = OTG_STATE_UNDEFINED; /* not Mini-A */ + else + p_otg->otg.state = OTG_STATE_A_IDLE; /* Mini-A */ + + /* enable OTG interrupt */ + spin_lock_irqsave(&usb_dr_regs_lock, flags); + temp = readl(&p_otg->dr_mem_map->otgsc); + + temp &= ~OTGSC_INTERRUPT_ENABLE_BITS_MASK; + temp |= OTGSC_IE_USB_ID; + writel(temp, &p_otg->dr_mem_map->otgsc); + spin_unlock_irqrestore(&usb_dr_regs_lock, flags); + + return 0; +} + +static int board_init(struct platform_device *pdev) +{ + struct fsl_usb2_platform_data *pdata; + pdata = (struct fsl_usb2_platform_data *)pdev->dev.platform_data; + + /* + * do platform specific init: check the clock, grab/config pins, etc. + */ + if (pdata->platform_init(pdev) != 0) + return -EINVAL; + + return 0; +} + +/*------------------------------------------------------------------------- + PROC File System Support +-------------------------------------------------------------------------*/ +#ifdef CONFIG_USB_OTG_DEBUG_FILES + +#include <linux/seq_file.h> + +static const char proc_filename[] = "driver/isp1504_otg"; + +static int otg_proc_read(char *page, char **start, off_t off, int count, + int *eof, void *_dev) +{ + struct otg_fsm *fsm = &fsl_otg_dev->fsm; + char *buf = page; + char *next = buf; + unsigned size = count; + unsigned long flags; + int t; + u32 tmp_reg; + + if (off != 0) + return 0; + + spin_lock_irqsave(&fsm->lock, flags); + + /* ------basic driver infomation ---- */ + t = scnprintf(next, size, + DRIVER_DESC "\n" "isp1504_otg version: %s\n\n", + DRIVER_VERSION); + size -= t; + next += t; + + /* ------ Registers ----- */ + tmp_reg = le32_to_cpu(usb_dr_regs->otgsc); + t = scnprintf(next, size, "OTGSC reg: %x\n", tmp_reg); + size -= t; + next += t; + + tmp_reg = le32_to_cpu(usb_dr_regs->portsc); + t = scnprintf(next, size, "PORTSC reg: %x\n", tmp_reg); + size -= t; + next += t; + + tmp_reg = le32_to_cpu(usb_dr_regs->usbmode); + t = scnprintf(next, size, "USBMODE reg: %x\n", tmp_reg); + size -= t; + next += t; + + tmp_reg = le32_to_cpu(usb_dr_regs->usbcmd); + t = scnprintf(next, size, "USBCMD reg: %x\n", tmp_reg); + size -= t; + next += t; + + tmp_reg = le32_to_cpu(usb_dr_regs->usbsts); + t = scnprintf(next, size, "USBSTS reg: %x\n", tmp_reg); + size -= t; + next += t; + + /* ------ State ----- */ + t = scnprintf(next, size, + "OTG state: %s\n\n", + state_string(fsl_otg_dev->otg.state)); + size -= t; + next += t; + +#ifdef DEBUG + /* ------ State Machine Variables ----- */ + t = scnprintf(next, size, "a_bus_req: %d\n", fsm->a_bus_req); + size -= t; + next += t; + + t = scnprintf(next, size, "b_bus_req: %d\n", fsm->b_bus_req); + size -= t; + next += t; + + t = scnprintf(next, size, "a_bus_resume: %d\n", fsm->a_bus_resume); + size -= t; + next += t; + + t = scnprintf(next, size, "a_bus_suspend: %d\n", fsm->a_bus_suspend); + size -= t; + next += t; + + t = scnprintf(next, size, "a_conn: %d\n", fsm->a_conn); + size -= t; + next += t; + + t = scnprintf(next, size, "a_sess_vld: %d\n", fsm->a_sess_vld); + size -= t; + next += t; + + t = scnprintf(next, size, "a_srp_det: %d\n", fsm->a_srp_det); + size -= t; + next += t; + + t = scnprintf(next, size, "a_vbus_vld: %d\n", fsm->a_vbus_vld); + size -= t; + next += t; + + t = scnprintf(next, size, "b_bus_resume: %d\n", fsm->b_bus_resume); + size -= t; + next += t; + + t = scnprintf(next, size, "b_bus_suspend: %d\n", fsm->b_bus_suspend); + size -= t; + next += t; + + t = scnprintf(next, size, "b_conn: %d\n", fsm->b_conn); + size -= t; + next += t; + + t = scnprintf(next, size, "b_se0_srp: %d\n", fsm->b_se0_srp); + size -= t; + next += t; + + t = scnprintf(next, size, "b_sess_end: %d\n", fsm->b_sess_end); + size -= t; + next += t; + + t = scnprintf(next, size, "b_sess_vld: %d\n", fsm->b_sess_vld); + size -= t; + next += t; + + t = scnprintf(next, size, "id: %d\n", fsm->id); + size -= t; + next += t; +#endif + + spin_unlock_irqrestore(&fsm->lock, flags); + + *eof = 1; + return count - size; +} + +#define create_proc_file() create_proc_read_entry(proc_filename, \ + 0, NULL, otg_proc_read, NULL) + +#define remove_proc_file() remove_proc_entry(proc_filename, NULL) + +#else /* !CONFIG_USB_OTG_DEBUG_FILES */ + +#define create_proc_file() do {} while (0) +#define remove_proc_file() do {} while (0) + +#endif /*CONFIG_USB_OTG_DEBUG_FILES */ + +static int __init fsl_otg_probe(struct platform_device *pdev) +{ + int status; + + DBG("pdev=0x%p", pdev); + + if (!pdev) + return -ENODEV; + + /* Initialize the clock, multiplexing pin and PHY interface */ + board_init(pdev); + + /* configure the OTG */ + status = fsl_otg_conf(pdev); + if (status) { + printk(KERN_INFO "Couldn't init OTG module\n"); + return -status; + } + + /* start OTG */ + status = usb_otg_start(pdev); + + create_proc_file(); + return status; +} + +static int __exit fsl_otg_remove(struct platform_device *pdev) +{ + u32 ie; + struct fsl_usb2_platform_data *pdata; + unsigned long flags; + + pdata = (struct fsl_usb2_platform_data *)pdev->dev.platform_data; + + DBG("pdev=0x%p pdata=0x%p", pdev, pdata); + + otg_set_transceiver(NULL); + + /* disable and clear OTGSC interrupts */ + spin_lock_irqsave(&usb_dr_regs_lock, flags); + ie = readl(&usb_dr_regs->otgsc); + ie &= ~OTGSC_INTERRUPT_ENABLE_BITS_MASK; + ie |= OTGSC_INTERRUPT_STATUS_BITS_MASK; + writel(ie, &usb_dr_regs->otgsc); + spin_unlock_irqrestore(&usb_dr_regs_lock, flags); + + free_irq(fsl_otg_dev->irq, fsl_otg_dev); + + kfree(fsl_otg_dev); + + remove_proc_file(); + + if (pdata->platform_uninit) + pdata->platform_uninit(pdata); + + fsl_otg_dev = NULL; + return 0; +} + +struct platform_driver fsl_otg_driver = { + .probe = fsl_otg_probe, + .remove = fsl_otg_remove, + .driver = { + .name = "fsl_arc", + .owner = THIS_MODULE, + }, +}; + +/*-------------------------------------------------------------------------*/ + +static int __init fsl_usb_otg_init(void) +{ + printk(KERN_INFO "driver %s, %s\n", otg_dr_name, DRIVER_VERSION); + return platform_driver_register(&fsl_otg_driver); +} + +static void __exit fsl_usb_otg_exit(void) +{ + platform_driver_unregister(&fsl_otg_driver); +} + +module_init(fsl_usb_otg_init); +module_exit(fsl_usb_otg_exit); + +MODULE_DESCRIPTION(DRIVER_INFO); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/otg/fsl_otg.h b/drivers/usb/otg/fsl_otg.h new file mode 100644 index 000000000000..b5a8a4b0afa5 --- /dev/null +++ b/drivers/usb/otg/fsl_otg.h @@ -0,0 +1,239 @@ +/* + * Copyright 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include "otg_fsm.h" +#include <linux/usb/otg.h> + + /* USB Command Register Bit Masks */ +#define USB_CMD_RUN_STOP (0x1<<0 ) +#define USB_CMD_CTRL_RESET (0x1<<1 ) +#define USB_CMD_PERIODIC_SCHEDULE_EN (0x1<<4 ) +#define USB_CMD_ASYNC_SCHEDULE_EN (0x1<<5 ) +#define USB_CMD_INT_AA_DOORBELL (0x1<<6 ) +#define USB_CMD_ASP (0x3<<8 ) +#define USB_CMD_ASYNC_SCH_PARK_EN (0x1<<11 ) +#define USB_CMD_SUTW (0x1<<13 ) +#define USB_CMD_ATDTW (0x1<<14 ) +#define USB_CMD_ITC (0xFF<<16) + +/* bit 15,3,2 are frame list size */ +#define USB_CMD_FRAME_SIZE_1024 (0x0<<15 | 0x0<<2) +#define USB_CMD_FRAME_SIZE_512 (0x0<<15 | 0x1<<2) +#define USB_CMD_FRAME_SIZE_256 (0x0<<15 | 0x2<<2) +#define USB_CMD_FRAME_SIZE_128 (0x0<<15 | 0x3<<2) +#define USB_CMD_FRAME_SIZE_64 (0x1<<15 | 0x0<<2) +#define USB_CMD_FRAME_SIZE_32 (0x1<<15 | 0x1<<2) +#define USB_CMD_FRAME_SIZE_16 (0x1<<15 | 0x2<<2) +#define USB_CMD_FRAME_SIZE_8 (0x1<<15 | 0x3<<2) + +/* bit 9-8 are async schedule park mode count */ +#define USB_CMD_ASP_00 (0x0<<8) +#define USB_CMD_ASP_01 (0x1<<8) +#define USB_CMD_ASP_10 (0x2<<8) +#define USB_CMD_ASP_11 (0x3<<8) +#define USB_CMD_ASP_BIT_POS (8) + +/* bit 23-16 are interrupt threshold control */ +#define USB_CMD_ITC_NO_THRESHOLD (0x00<<16) +#define USB_CMD_ITC_1_MICRO_FRM (0x01<<16) +#define USB_CMD_ITC_2_MICRO_FRM (0x02<<16) +#define USB_CMD_ITC_4_MICRO_FRM (0x04<<16) +#define USB_CMD_ITC_8_MICRO_FRM (0x08<<16) +#define USB_CMD_ITC_16_MICRO_FRM (0x10<<16) +#define USB_CMD_ITC_32_MICRO_FRM (0x20<<16) +#define USB_CMD_ITC_64_MICRO_FRM (0x40<<16) +#define USB_CMD_ITC_BIT_POS (16) + +/* USB Status Register Bit Masks */ +#define USB_STS_INT (0x1<<0 ) +#define USB_STS_ERR (0x1<<1 ) +#define USB_STS_PORT_CHANGE (0x1<<2 ) +#define USB_STS_FRM_LST_ROLL (0x1<<3 ) +#define USB_STS_SYS_ERR (0x1<<4 ) +#define USB_STS_IAA (0x1<<5 ) +#define USB_STS_RESET_RECEIVED (0x1<<6 ) +#define USB_STS_SOF (0x1<<7 ) +#define USB_STS_DCSUSPEND (0x1<<8 ) +#define USB_STS_HC_HALTED (0x1<<12) +#define USB_STS_RCL (0x1<<13) +#define USB_STS_PERIODIC_SCHEDULE (0x1<<14) +#define USB_STS_ASYNC_SCHEDULE (0x1<<15) + +/* USB Interrupt Enable Register Bit Masks */ +#define USB_INTR_INT_EN (0x1<<0 ) +#define USB_INTR_ERR_INT_EN (0x1<<1 ) +#define USB_INTR_PC_DETECT_EN (0x1<<2 ) +#define USB_INTR_FRM_LST_ROLL_EN (0x1<<3 ) +#define USB_INTR_SYS_ERR_EN (0x1<<4 ) +#define USB_INTR_ASYN_ADV_EN (0x1<<5 ) +#define USB_INTR_RESET_EN (0x1<<6 ) +#define USB_INTR_SOF_EN (0x1<<7 ) +#define USB_INTR_DEVICE_SUSPEND (0x1<<8 ) + +/* Device Address bit masks */ +#define USB_DEVICE_ADDRESS_MASK (0x7F<<25) +#define USB_DEVICE_ADDRESS_BIT_POS (25) + +/* USB MODE Register Bit Masks */ +#define USB_MODE_CTRL_MODE_IDLE (0x0<<0) +#define USB_MODE_CTRL_MODE_DEVICE (0x2<<0) +#define USB_MODE_CTRL_MODE_HOST (0x3<<0) +#define USB_MODE_CTRL_MODE_RSV (0x1<<0) +#define USB_MODE_SETUP_LOCK_OFF (0x1<<3) +#define USB_MODE_STREAM_DISABLE (0x1<<4) + +/* + * A-DEVICE timing constants + */ + +/* Wait for VBUS Rise */ +#define TA_WAIT_VRISE (100) /* a_wait_vrise 100 ms, section: 6.6.5.1 */ + +/* Wait for B-Connect */ +#define TA_WAIT_BCON (10000) /* a_wait_bcon > 1 sec, section: 6.6.5.2 + * This is only used to get out of + * OTG_STATE_A_WAIT_BCON state if there was + * no connection for these many milliseconds + */ + +/* A-Idle to B-Disconnect */ +/* It is necessary for this timer to be more than 750 ms because of a bug in OPT + * test 5.4 in which B OPT disconnects after 750 ms instead of 75ms as stated + * in the test description + */ +#define TA_AIDL_BDIS (5000) /* a_suspend minimum 200 ms, section: 6.6.5.3 */ + +/* B-Idle to A-Disconnect */ +#define TA_BIDL_ADIS (12) /* 3 to 200 ms */ + +/* B-device timing constants */ + +/* Data-Line Pulse Time*/ +#define TB_DATA_PLS (10) /* b_srp_init,continue 5~10ms, section:5.3.3 */ +#define TB_DATA_PLS_MIN (5) /* minimum 5 ms */ +#define TB_DATA_PLS_MAX (10) /* maximum 10 ms */ + +/* SRP Initiate Time */ +#define TB_SRP_INIT (100) /* b_srp_init,maximum 100 ms, section:5.3.8 */ + +/* SRP Fail Time */ +#define TB_SRP_FAIL (7000) /* b_srp_init,Fail time 5~30s, section:6.8.2.2 */ + +/* SRP result wait time */ +#define TB_SRP_WAIT (60) + +/* VBus time */ +#define TB_VBUS_PLS (30) /* time to keep vbus pulsing asserted */ + +/* Discharge time */ +/* This time should be less than 10ms. It varies from system to system. */ +#define TB_VBUS_DSCHRG (8) + +/* A-SE0 to B-Reset */ +#define TB_ASE0_BRST (20) /* b_wait_acon, mini 3.125 ms,section:6.8.2.4 */ + +/* A bus suspend timer before we can switch to b_wait_aconn */ +#define TB_A_SUSPEND (7) +#define TB_BUS_RESUME (12) + +/* SE0 Time Before SRP */ +#define TB_SE0_SRP (2) /* b_idle,minimum 2 ms, section:5.3.2 */ + +#define SET_OTG_STATE(otg_ptr, newstate) ((otg_ptr)->state=newstate) + +struct usb_dr_mmap { + /* Capability register */ + u8 res1[256]; + u16 caplength; /* Capability Register Length */ + u16 hciversion; /* Host Controller Interface Version */ + u32 hcsparams; /* Host Controller Structual Parameters */ + u32 hccparams; /* Host Controller Capability Parameters */ + u8 res2[20]; + u32 dciversion; /* Device Controller Interface Version */ + u32 dccparams; /* Device Controller Capability Parameters */ + u8 res3[24]; + /* Operation register */ + u32 usbcmd; /* USB Command Register */ + u32 usbsts; /* USB Status Register */ + u32 usbintr; /* USB Interrupt Enable Register */ + u32 frindex; /* Frame Index Register */ + u8 res4[4]; + u32 deviceaddr; /* Device Address */ + u32 endpointlistaddr; /* Endpoint List Address Register */ + u8 res5[4]; + u32 burstsize; /* Master Interface Data Burst Size Register */ + u32 txttfilltuning; /* Transmit FIFO Tuning Controls Register */ + u8 res6[8]; + u32 ulpiview; /* ULPI register access */ + u8 res7[12]; + u32 configflag; /* Configure Flag Register */ + u32 portsc; /* Port 1 Status and Control Register */ + u8 res8[28]; + u32 otgsc; /* On-The-Go Status and Control */ + u32 usbmode; /* USB Mode Register */ + u32 endptsetupstat; /* Endpoint Setup Status Register */ + u32 endpointprime; /* Endpoint Initialization Register */ + u32 endptflush; /* Endpoint Flush Register */ + u32 endptstatus; /* Endpoint Status Register */ + u32 endptcomplete; /* Endpoint Complete Register */ + u32 endptctrl[6]; /* Endpoint Control Registers */ + u8 res9[552]; + u32 snoop1; + u32 snoop2; + u32 age_cnt_thresh; /* Age Count Threshold Register */ + u32 pri_ctrl; /* Priority Control Register */ + u32 si_ctrl; /* System Interface Control Register */ + u8 res10[236]; + u32 control; /* General Purpose Control Register */ +}; + +struct fsl_otg_timer { + unsigned long expires; /* Number of count increase to timeout */ + unsigned long count; /* Tick counter */ + void (*function) (unsigned long); /* Timeout function */ + unsigned long data; /* Data passed to function */ + struct list_head list; +}; + +struct fsl_otg_timer inline *otg_timer_initializer + (void (*function) (unsigned long), unsigned long expires, + unsigned long data) { + struct fsl_otg_timer *timer; + timer = kmalloc(sizeof(struct fsl_otg_timer), GFP_KERNEL); + if (timer == NULL) + return NULL; + timer->function = function; + timer->expires = expires; + timer->data = data; + return timer; +} + +struct fsl_otg { + struct otg_transceiver otg; + struct otg_fsm fsm; + struct usb_dr_mmap *dr_mem_map; + struct delayed_work otg_event; + + /*used for usb host */ + u8 host_working; + u8 on_off; + + int irq; +}; + +struct fsl_otg_config { + u8 otg_port; +}; + +extern const char *state_string(enum usb_otg_state state); +extern int otg_set_resources(struct resource *resources, int num); diff --git a/drivers/usb/otg/otg_fsm.c b/drivers/usb/otg/otg_fsm.c new file mode 100644 index 000000000000..43e5af105441 --- /dev/null +++ b/drivers/usb/otg/otg_fsm.c @@ -0,0 +1,394 @@ +/* + * Copyright 2006-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <asm/types.h> +#include <linux/kernel.h> +#include <linux/usb/otg.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/usb.h> +#include <linux/usb_gadget.h> + +#include "otg_fsm.h" + +/* Defined by device specific driver, for different timer implementation */ +extern void *a_wait_vrise_tmr, *a_wait_bcon_tmr, *a_aidl_bdis_tmr, + *b_ase0_brst_tmr, *b_se0_srp_tmr, *b_srp_fail_tmr, *a_wait_enum_tmr; + +const char *state_string(enum usb_otg_state state) +{ + switch (state) { + case OTG_STATE_A_IDLE: + return "a_idle"; + case OTG_STATE_A_WAIT_VRISE: + return "a_wait_vrise"; + case OTG_STATE_A_WAIT_BCON: + return "a_wait_bcon"; + case OTG_STATE_A_HOST: + return "a_host"; + case OTG_STATE_A_SUSPEND: + return "a_suspend"; + case OTG_STATE_A_PERIPHERAL: + return "a_peripheral"; + case OTG_STATE_A_WAIT_VFALL: + return "a_wait_vfall"; + case OTG_STATE_A_VBUS_ERR: + return "a_vbus_err"; + case OTG_STATE_B_IDLE: + return "b_idle"; + case OTG_STATE_B_SRP_INIT: + return "b_srp_init"; + case OTG_STATE_B_PERIPHERAL: + return "b_peripheral"; + case OTG_STATE_B_WAIT_ACON: + return "b_wait_acon"; + case OTG_STATE_B_HOST: + return "b_host"; + default: + return "UNDEFINED"; + } +} + +const char *protocol_string(int p) +{ + switch (p) { + case PROTO_HOST: + return "Host"; + case PROTO_GADGET: + return "Peripheral"; + default: + return "undef"; + } +} + +/* Change USB protocol when there is a protocol change */ +static int otg_set_protocol(struct otg_fsm *fsm, int protocol) +{ + int ret = 0; + + if (fsm->protocol != protocol) { + VDBG("Change role from %s to %s", + protocol_string(fsm->protocol), protocol_string(protocol)); + + /* stop old protocol */ + if (fsm->protocol == PROTO_HOST) + ret = fsm->ops->start_host(fsm, 0); + else if (fsm->protocol == PROTO_GADGET) + ret = fsm->ops->start_gadget(fsm, 0); + if (ret) + return ret; + + /* start new protocol */ + if (protocol == PROTO_HOST) + ret = fsm->ops->start_host(fsm, 1); + else if (protocol == PROTO_GADGET) + ret = fsm->ops->start_gadget(fsm, 1); + if (ret) + return ret; + + fsm->protocol = protocol; + return 0; + } + + return 0; +} + +static int state_changed = 0; + +/* Called when leaving a state. Do state clean up jobs here */ +void otg_leave_state(struct otg_fsm *fsm, enum usb_otg_state old_state) +{ + switch (old_state) { + case OTG_STATE_B_IDLE: + otg_del_timer(fsm, b_se0_srp_tmr); + fsm->b_se0_srp = 0; + break; + case OTG_STATE_B_SRP_INIT: + fsm->b_srp_done = 0; + break; + case OTG_STATE_B_PERIPHERAL: + break; + case OTG_STATE_B_WAIT_ACON: + otg_del_timer(fsm, b_ase0_brst_tmr); + fsm->b_ase0_brst_tmout = 0; + break; + case OTG_STATE_B_HOST: + break; + case OTG_STATE_A_IDLE: + break; + case OTG_STATE_A_WAIT_VRISE: + otg_del_timer(fsm, a_wait_vrise_tmr); + fsm->a_wait_vrise_tmout = 0; + break; + case OTG_STATE_A_WAIT_BCON: + otg_del_timer(fsm, a_wait_bcon_tmr); + fsm->a_wait_bcon_tmout = 0; + break; + case OTG_STATE_A_HOST: + otg_del_timer(fsm, a_wait_enum_tmr); + break; + case OTG_STATE_A_SUSPEND: + otg_del_timer(fsm, a_aidl_bdis_tmr); + fsm->a_aidl_bdis_tmout = 0; + fsm->a_suspend_req = 0; + break; + case OTG_STATE_A_PERIPHERAL: + break; + case OTG_STATE_A_WAIT_VFALL: + otg_del_timer(fsm, a_wait_vrise_tmr); + break; + case OTG_STATE_A_VBUS_ERR: + break; + default: + break; + } +} + +/* Called when entering a state */ +int otg_set_state(struct otg_fsm *fsm, enum usb_otg_state new_state) +{ + state_changed = 1; + if (fsm->transceiver->state == new_state) + return 0; + + VDBG("chg state to %s", state_string(new_state)); + + otg_leave_state(fsm, fsm->transceiver->state); + + switch (new_state) { + case OTG_STATE_B_IDLE: + otg_drv_vbus(fsm, 0); + otg_chrg_vbus(fsm, 0); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_UNDEF); + otg_add_timer(fsm, b_se0_srp_tmr); + break; + case OTG_STATE_B_SRP_INIT: + otg_start_pulse(fsm); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_UNDEF); + otg_add_timer(fsm, b_srp_fail_tmr); + break; + case OTG_STATE_B_PERIPHERAL: + otg_chrg_vbus(fsm, 0); + otg_loc_conn(fsm, 1); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_GADGET); + break; + case OTG_STATE_B_WAIT_ACON: + otg_chrg_vbus(fsm, 0); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_HOST); + otg_add_timer(fsm, b_ase0_brst_tmr); + fsm->a_bus_suspend = 0; + break; + case OTG_STATE_B_HOST: + otg_chrg_vbus(fsm, 0); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 1); + otg_set_protocol(fsm, PROTO_HOST); + usb_bus_start_enum(fsm->transceiver->host, + fsm->transceiver->host->otg_port); + break; + case OTG_STATE_A_IDLE: + otg_drv_vbus(fsm, 0); + otg_chrg_vbus(fsm, 0); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_HOST); + break; + case OTG_STATE_A_WAIT_VRISE: + otg_drv_vbus(fsm, 1); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_HOST); + otg_add_timer(fsm, a_wait_vrise_tmr); + break; + case OTG_STATE_A_WAIT_BCON: + otg_drv_vbus(fsm, 1); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_HOST); + otg_add_timer(fsm, a_wait_bcon_tmr); + break; + case OTG_STATE_A_HOST: + otg_drv_vbus(fsm, 1); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 1); + otg_set_protocol(fsm, PROTO_HOST); + /* When HNP is triggered while a_bus_req = 0, a_host will + * suspend too fast to complete a_set_b_hnp_en + */ + if (!fsm->a_bus_req || fsm->a_suspend_req) + otg_add_timer(fsm, a_wait_enum_tmr); + break; + case OTG_STATE_A_SUSPEND: + otg_drv_vbus(fsm, 1); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_HOST); + otg_add_timer(fsm, a_aidl_bdis_tmr); + + break; + case OTG_STATE_A_PERIPHERAL: + otg_loc_conn(fsm, 1); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_GADGET); + otg_drv_vbus(fsm, 1); + break; + case OTG_STATE_A_WAIT_VFALL: + otg_drv_vbus(fsm, 0); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_HOST); + break; + case OTG_STATE_A_VBUS_ERR: + otg_drv_vbus(fsm, 0); + otg_loc_conn(fsm, 0); + otg_loc_sof(fsm, 0); + otg_set_protocol(fsm, PROTO_UNDEF); + break; + default: + break; + } + + fsm->transceiver->state = new_state; + return 0; +} + +/* State change judgement */ +int otg_statemachine(struct otg_fsm *fsm) +{ + enum usb_otg_state state; + unsigned long flags; + + spin_lock_irqsave(&fsm->lock, flags); + + state = fsm->transceiver->state; + state_changed = 0; + /* State machine state change judgement */ + + VDBG("top: curr state=%s", state_string(state)); + + switch (state) { + case OTG_STATE_UNDEFINED: + VDBG("fsm->id = %d", fsm->id); + if (fsm->id) + otg_set_state(fsm, OTG_STATE_B_IDLE); + else + otg_set_state(fsm, OTG_STATE_A_IDLE); + break; + case OTG_STATE_B_IDLE: + VDBG("gadget: %p", fsm->transceiver->gadget); + if (!fsm->id) + otg_set_state(fsm, OTG_STATE_A_IDLE); + else if (fsm->b_sess_vld && fsm->transceiver->gadget) + otg_set_state(fsm, OTG_STATE_B_PERIPHERAL); + else if (fsm->b_bus_req && fsm->b_sess_end && fsm->b_se0_srp) + otg_set_state(fsm, OTG_STATE_B_SRP_INIT); + break; + case OTG_STATE_B_SRP_INIT: + if (!fsm->id || fsm->b_srp_done) + otg_set_state(fsm, OTG_STATE_B_IDLE); + break; + case OTG_STATE_B_PERIPHERAL: + if (!fsm->id || !fsm->b_sess_vld) + otg_set_state(fsm, OTG_STATE_B_IDLE); + else if (fsm->b_bus_req && + fsm->transceiver->gadget->b_hnp_enable && + fsm->a_bus_suspend) + otg_set_state(fsm, OTG_STATE_B_WAIT_ACON); + break; + case OTG_STATE_B_WAIT_ACON: + if (fsm->a_conn) + otg_set_state(fsm, OTG_STATE_B_HOST); + else if (!fsm->id || !fsm->b_sess_vld) + otg_set_state(fsm, OTG_STATE_B_IDLE); + else if (fsm->a_bus_resume || fsm->b_ase0_brst_tmout) { + fsm->b_ase0_brst_tmout = 0; + otg_set_state(fsm, OTG_STATE_B_PERIPHERAL); + } + break; + case OTG_STATE_B_HOST: + if (!fsm->id || !fsm->b_sess_vld) + otg_set_state(fsm, OTG_STATE_B_IDLE); + else if (!fsm->b_bus_req || !fsm->a_conn) + otg_set_state(fsm, OTG_STATE_B_PERIPHERAL); + break; + case OTG_STATE_A_IDLE: + if (fsm->id) + otg_set_state(fsm, OTG_STATE_B_IDLE); + else if (!fsm->a_bus_drop && (fsm->a_bus_req || fsm->a_srp_det)) + otg_set_state(fsm, OTG_STATE_A_WAIT_VRISE); + break; + case OTG_STATE_A_WAIT_VRISE: + if (fsm->id || fsm->a_bus_drop || fsm->a_vbus_vld || + fsm->a_wait_vrise_tmout) { + otg_set_state(fsm, OTG_STATE_A_WAIT_BCON); + } + break; + case OTG_STATE_A_WAIT_BCON: + if (!fsm->a_vbus_vld) + otg_set_state(fsm, OTG_STATE_A_VBUS_ERR); + else if (fsm->b_conn) + otg_set_state(fsm, OTG_STATE_A_HOST); + else if (fsm->id | fsm->a_bus_drop | fsm->a_wait_bcon_tmout) + otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL); + break; + case OTG_STATE_A_HOST: + if ((!fsm->a_bus_req || fsm->a_suspend_req) && + fsm->transceiver->host->b_hnp_enable) + otg_set_state(fsm, OTG_STATE_A_SUSPEND); + else if (fsm->id || !fsm->b_conn || fsm->a_bus_drop) + otg_set_state(fsm, OTG_STATE_A_WAIT_BCON); + else if (!fsm->a_vbus_vld) + otg_set_state(fsm, OTG_STATE_A_VBUS_ERR); + break; + case OTG_STATE_A_SUSPEND: + if (!fsm->b_conn && fsm->transceiver->host->b_hnp_enable) + otg_set_state(fsm, OTG_STATE_A_PERIPHERAL); + else if (!fsm->b_conn && !fsm->transceiver->host->b_hnp_enable) + otg_set_state(fsm, OTG_STATE_A_WAIT_BCON); + else if (fsm->a_bus_req || fsm->b_bus_resume) + otg_set_state(fsm, OTG_STATE_A_HOST); + else if (fsm->id || fsm->a_bus_drop || fsm->a_aidl_bdis_tmout) + otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL); + else if (!fsm->a_vbus_vld) + otg_set_state(fsm, OTG_STATE_A_VBUS_ERR); + break; + case OTG_STATE_A_PERIPHERAL: + if (fsm->id || fsm->a_bus_drop) + otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL); + else if (fsm->b_bus_suspend) + otg_set_state(fsm, OTG_STATE_A_WAIT_BCON); + else if (!fsm->a_vbus_vld) + otg_set_state(fsm, OTG_STATE_A_VBUS_ERR); + break; + case OTG_STATE_A_WAIT_VFALL: + if (fsm->id || fsm->a_bus_req || + (!fsm->a_sess_vld && !fsm->b_conn)) + otg_set_state(fsm, OTG_STATE_A_IDLE); + break; + case OTG_STATE_A_VBUS_ERR: + if (fsm->id || fsm->a_bus_drop || fsm->a_clr_err) + otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL); + break; + default: + break; + } + spin_unlock_irqrestore(&fsm->lock, flags); + + return state_changed; +} diff --git a/drivers/usb/otg/otg_fsm.h b/drivers/usb/otg/otg_fsm.h new file mode 100644 index 000000000000..5b1c98f16be9 --- /dev/null +++ b/drivers/usb/otg/otg_fsm.h @@ -0,0 +1,152 @@ +/* + * Copyright 2006-2007 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#if 0 +#define DEBUG 1 +#define VERBOSE 1 +#endif + +#ifdef DEBUG + +/* +#define DBG(fmt, args...) printk("[%s] " fmt "\n", \ + __FUNCTION__, ## args) +*/ +#define DBG(fmt, args...) printk("j=%lu [%s] " fmt "\n", \ + jiffies, __FUNCTION__, ## args) + +#else +#define DBG(fmt, args...) do{}while(0) +#endif + +#ifdef VERBOSE +#define VDBG DBG +#else +#define VDBG(stuff...) do{}while(0) +#endif + +#ifdef VERBOSE +#define MPC_LOC printk("Current Location [%s]:[%d]\n", __FILE__, __LINE__) +#else +#define MPC_LOC do{}while(0) +#endif + +#define PROTO_UNDEF (0) +#define PROTO_HOST (1) +#define PROTO_GADGET (2) + +/* OTG state machine according to the OTG spec */ +struct otg_fsm { + /* Input */ + int a_bus_resume; + int a_bus_suspend; + int a_conn; + int a_sess_vld; + int a_srp_det; + int a_vbus_vld; + int b_bus_resume; + int b_bus_suspend; + int b_conn; + int b_se0_srp; + int b_sess_end; + int b_sess_vld; + int id; + + /* Internal variables */ + int a_set_b_hnp_en; + int b_srp_done; + int b_hnp_enable; + + /* Timeout indicator for timers */ + int a_wait_vrise_tmout; + int a_wait_bcon_tmout; + int a_aidl_bdis_tmout; + int b_ase0_brst_tmout; + + /* Informative variables */ + int a_bus_drop; + int a_bus_req; + int a_clr_err; + int a_suspend_req; + int b_bus_req; + + /* Output */ + int drv_vbus; + int loc_conn; + int loc_sof; + + struct otg_fsm_ops *ops; + struct otg_transceiver *transceiver; + + /* Current usb protocol used: 0:undefine; 1:host; 2:client */ + int protocol; + spinlock_t lock; +}; + +struct otg_fsm_ops { + void (*chrg_vbus) (int on); + void (*drv_vbus) (int on); + void (*loc_conn) (int on); + void (*loc_sof) (int on); + void (*start_pulse) (void); + void (*add_timer) (void *timer); + void (*del_timer) (void *timer); + int (*start_host) (struct otg_fsm * fsm, int on); + int (*start_gadget) (struct otg_fsm * fsm, int on); +}; + +static inline void otg_chrg_vbus(struct otg_fsm *fsm, int on) +{ + fsm->ops->chrg_vbus(on); +} + +static inline void otg_drv_vbus(struct otg_fsm *fsm, int on) +{ + if (fsm->drv_vbus != on) { + fsm->drv_vbus = on; + fsm->ops->drv_vbus(on); + } +} + +static inline void otg_loc_conn(struct otg_fsm *fsm, int on) +{ + if (fsm->loc_conn != on) { + fsm->loc_conn = on; + fsm->ops->loc_conn(on); + } +} + +static inline void otg_loc_sof(struct otg_fsm *fsm, int on) +{ + if (fsm->loc_sof != on) { + fsm->loc_sof = on; + fsm->ops->loc_sof(on); + } +} + +static inline void otg_start_pulse(struct otg_fsm *fsm) +{ + fsm->ops->start_pulse(); +} + +static inline void otg_add_timer(struct otg_fsm *fsm, void *timer) +{ + fsm->ops->add_timer(timer); +} + +static inline void otg_del_timer(struct otg_fsm *fsm, void *timer) +{ + fsm->ops->del_timer(timer); +} + +int otg_statemachine(struct otg_fsm *fsm); diff --git a/drivers/usb/usblan/Kconfig b/drivers/usb/usblan/Kconfig new file mode 100644 index 000000000000..cdf4d1c45d63 --- /dev/null +++ b/drivers/usb/usblan/Kconfig @@ -0,0 +1,24 @@ +# +# USB Network devices configuration +# +comment "Belcarra USBLAN Networking for USB" + depends on USB && NET + +config USB_USBLAN + tristate "Support for Belcarra USBLAN Network Devices" + depends on USB && NET + +config USB_USBLAN_IDS + bool "Override built-in Vendor and Product ID's" + depends on USB && NET && USB_USBLAN + default "n" + +config USB_USBLAN_VENDORID + hex "USB Vendor ID for the USBLAN network device" + depends on USB_USBLAN && USB_USBLAN_IDS + default "0" + +config USB_USBLAN_PRODUCTID + hex "USB Product ID for the USBLAN network device" + depends on USB_USBLAN && USB_USBLAN_IDS + default "0" diff --git a/drivers/usb/usblan/Makefile b/drivers/usb/usblan/Makefile new file mode 100644 index 000000000000..969d96804c50 --- /dev/null +++ b/drivers/usb/usblan/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for USB Network drivers +# + +OTGDIR=$(srctree)/drivers/otg +EXTRA_CFLAGS += -I$(OTGDIR) +EXTRA_CFLAGS_nostdinc += -I$(OTGDIR) +obj-$(CONFIG_USB_USBLAN) += usblan.o diff --git a/drivers/usb/usblan/usblan-compat.h b/drivers/usb/usblan/usblan-compat.h new file mode 100644 index 000000000000..b02eba4056d9 --- /dev/null +++ b/drivers/usb/usblan/usblan-compat.h @@ -0,0 +1,286 @@ +/********************************************************************* + * A set of macros to cross-navigate Linux 2.4 and Linux 2.6 kernel + * facilities. + * Copyright (c) 2004, 2005 Belcarra Technologies Corp + * + * 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. + * + * By: + * Stuart Lynne <sl@belcarra.com>, Bruce Balden <balden@belcarra.com> + * + ***********************************************************************/ +#ifndef _USB_NET_USBLAN_COMPAT_H +#define _USB_NET_USBLAN_COMPAT_H 1 +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,2) +#define LINUX26 +#elif LINUX_VERSION_CODE > KERNEL_VERSION(2,4,5) +#define LINUX24 +#else /* LINUX_VERSION_CODE > KERNEL_VERSION(2,4,5) */ +#define LINUX24 +#define LINUX_OLD +#warning "Early unsupported release of Linux kernel" +#endif /* LINUX_VERSION_CODE > KERNEL_VERSION(2,4,5) */ + + +/*! @{ */ +#undef PRAGMAPACK +#define PACKED __attribute__((packed)) +#define INLINE __inline__ + +/*! @} */ + +/*! @name Memory Allocation Primitives + * + * CKMALLOC() + * LSTRDUP() + * LKFREE() + * LIST_ENTRY() + * LIST_FOR_EACH() + */ + + /*! @{ */ + +#if defined(LINUX26) + #include <linux/gfp.h> +#define GET_KERNEL_PAGE() __get_free_page(GFP_KERNEL) + +#else /* LINUX26 */ + + #include <linux/mm.h> + #define GET_KERNEL_PAGE() get_free_page(GFP_KERNEL) +#endif /* LINUX26 */ + + + +// Common to all supported versions of Linux ?? + +#define CKMALLOC(n,f) _ckmalloc(__FUNCTION__, __LINE__, n, f) +#define LSTRDUP(str) _lstrdup(__FUNCTION__, __LINE__, str) +#define LKFREE(p) _lkfree(__FUNCTION__, __LINE__, p) + +#define ckmalloc(n,f) _ckmalloc(__FUNCTION__, __LINE__, n, f) +#define lstrdup(str) _lstrdup(__FUNCTION__, __LINE__, str) +#define lkfree(p) _lkfree(__FUNCTION__, __LINE__, p) + +#define OTG_MALLOC_TEST +#undef OTG_MALLOC_DEBUG + +#ifdef OTG_MALLOC_TEST + extern int otg_mallocs; +#endif + + +static INLINE void *_ckmalloc (const char *func, int line, int n, int f) +{ + void *p; + if ((p = kmalloc (n, f)) == NULL) { + return NULL; + } + memset (p, 0, n); + #ifdef OTG_MALLOC_TEST + ++otg_mallocs; + #endif + #ifdef OTG_MALLOC_DEBUG + printk(KERN_INFO"%s: %p %s %d %d\n", __FUNCTION__, p, func, line, otg_mallocs); + #endif + return p; +} + +static INLINE char *_lstrdup (const char *func, int line, char *str) +{ + int n; + char *s; + if (str && (n = strlen (str) + 1) && (s = kmalloc (n, GFP_ATOMIC))) { +#ifdef OTG_MALLOC_TEST + ++otg_mallocs; +#endif +#ifdef OTG_MALLOC_DEBUG + printk(KERN_INFO"%s: %p %s %d %d\n", __FUNCTION__, s, func, line, otg_mallocs); +#endif + return strcpy (s, str); + } + return NULL; +} + +static INLINE void _lkfree (const char *func, int line, void *p) +{ + if (p) { +#ifdef OTG_MALLOC_TEST + --otg_mallocs; +#endif +#ifdef OTG_MALLOC_DEBUG + printk(KERN_INFO"%s: %p %s %d %d\n", __FUNCTION__, p, func, line, otg_mallocs); +#endif + kfree (p); +#ifdef MALLOC_TEST + if (otg_mallocs < 0) { + printk(KERN_INFO"%s: %p %s %d %d otg_mallocs less zero!\n", __FUNCTION__, p, func, line, otg_mallocs); + } +#endif +#ifdef OTG_MALLOC_DEBUG + else { + printk(KERN_INFO"%s: %s %d NULL\n", __FUNCTION__, func, line); + } +#endif + } +} + +#if 1 +#include <linux/list.h> +#define LIST_NODE struct list_head +#define LIST_NODE_INIT(name) struct list_head name = {&name, &name} +#define INIT_LIST_NODE(ptr) INIT_LIST_HEAD(ptr) +#define LIST_ENTRY(pointer, type, member) list_entry(pointer, type, member) +#define LIST_FOR_EACH(cursor, head) list_for_each(cursor, head) +#define LIST_ADD_TAIL(n,h) list_add_tail(n,h) +#define LIST_DEL(h) list_del(&(h)) +#else +#include <otg/otg-list.h> +#endif + +/*! @} */ + + +/*! @name Atomic Operations + * + * atomic_post_inc() + * atomic_pre_dec() + */ +/*! @{ */ +static __inline__ int atomic_post_inc(volatile atomic_t *v) +{ + unsigned long flags; + int result; + local_irq_save(flags); + result = (v->counter)++; + local_irq_restore(flags); + return(result); +} + +static __inline__ int atomic_pre_dec(volatile atomic_t *v) +{ + unsigned long flags; + int result; + local_irq_save(flags); + result = --(v->counter); + local_irq_restore(flags); + return(result); +} +/*! @} */ + + + +/*!@name Scheduling Primitives + * + * WORK_STRUCT + * WORK_ITEM + * + * SCHEDULE_TIMEOUT()\n + * SET_WORK_ARG()\n + * SCHEDULE_WORK()\n + * SCHEDULE_IMMEDIATE_WORK()\n + * NO_WORK_DATA()\n + * MOD_DEC_USE_COUNT\n + * MOD_INC_USE_COUNT\n + */ + +/*! @{ */ + +static void inline SCHEDULE_TIMEOUT(int seconds){ + schedule_timeout( seconds * HZ ); +} + + +/* Separate Linux 2.4 and 2.6 versions of scheduling primitives */ +#if defined(LINUX26) + #include <linux/workqueue.h> + + #define WORK_STRUCT work_struct + #define WORK_ITEM work_struct + typedef struct WORK_ITEM WORK_ITEM; + #if 0 + #define PREPARE_WORK_ITEM(__item,__routine,__data) INIT_WORK((__item),(__routine),(__data)) + #else + #include <linux/interrupt.h> + #define PREPARE_WORK_ITEM(__item,__routine,__data) __prepare_work(&(__item),(__routine),(__data)) + static inline void __prepare_work(struct work_struct *_work, + void (*_routine), + void * _data){ + INIT_LIST_HEAD(&_work->entry); + _work->pending = 0; + _work->func = _routine; + _work->data = _data; + init_timer(&_work->timer); + } + #endif + #undef PREPARE_WORK + typedef void (* WORK_PROC)(void *); + + #define SET_WORK_ARG(__item, __data) (__item).data = __data + + #define SCHEDULE_WORK(item) schedule_work(&(item)) + #define SCHEDULE_IMMEDIATE_WORK(item) SCHEDULE_WORK((item)) + #define PENDING_WORK_ITEM(item) ((item).pending != 0) + #define NO_WORK_DATA(item) (!(item).data) + #define _MOD_DEC_USE_COUNT //Not used in 2.6 + #define _MOD_INC_USE_COUNT //Not used in 2.6 + +#else /* LINUX26 */ + + #define WORK_STRUCT tq_struct + #define WORK_ITEM tq_struct + typedef struct WORK_ITEM WORK_ITEM; + #define PREPARE_WORK_ITEM(item,work_routine,work_data) { item.routine = work_routine; item.data = work_data; } + #define SET_WORK_ARG(__item, __data) (__item).data = __data + #define NO_WORK_DATA(item) (!(item).data) + #define SCHEDULE_WORK(item) schedule_task(&(item)) + #define PENDING_WORK_ITEM(item) ((item).sync != 0) + #define _MOD_DEC_USE_COUNT MOD_DEC_USE_COUNT + #define _MOD_INC_USE_COUNT MOD_INC_USE_COUNT + + typedef void (* WORK_PROC)(void *); + + #if !defined(IRQ_HANDLED) + // Irq's + typedef void irqreturn_t; + #define IRQ_NONE + #define IRQ_HANDLED + #define IRQ_RETVAL(x) + #endif +#endif /* LINUX26 */ + +/*! @} */ + +/*!@name Semaphores + * + * up() + * down() + */ +/*! @{ */ +#define UP(s) up(s) +#define DOWN(s) down(s) +/*! @} */ + +/*! @name Printk + * + * PRINTK() + */ +/*! @{ */ +#define PRINTK(s) printk(s) +/*! @} */ + +/*! + * Cache + * @{ + */ +#define CACHE_SYNC_RCV(buf, len) pci_map_single (NULL, (void *) buf, len, PCI_DMA_FROMDEVICE) +#define CACHE_SYNC_TX(buf, len) consistent_sync (buf, len, PCI_DMA_TODEVICE) + +/* @} */ + + + +#endif diff --git a/drivers/usb/usblan/usblan.c b/drivers/usb/usblan/usblan.c new file mode 100644 index 000000000000..f59c094ac015 --- /dev/null +++ b/drivers/usb/usblan/usblan.c @@ -0,0 +1,3045 @@ +/* + * USB Host to USB Device Network Function Driver + * + * Copyright (c) 2002, 2003 Belcarra + * Copyright (c) 2001 Lineo + * + * + * 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. + * + * By: + * Stuart Lynne <sl@belcarra.com>, Bruce Balden <balden@belcarra.com> + * + * Some algorithms adopted from usbnet.c: + * + * Copyright (C) 2000-2001 by David Brownell <dbrownell@users.sourceforge.net> + * + */ + +#ifdef MODULE +#include <linux/module.h> +#endif +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +//#include <net/arp.h> +#include <linux/rtnetlink.h> +#include <linux/smp_lock.h> +#include <linux/ctype.h> +#include <linux/timer.h> +#include <linux/string.h> +#include <linux/pkt_sched.h> +#include <linux/random.h> +#include <linux/version.h> + +#include <linux/skbuff.h> +#include <net/arp.h> +#include <linux/atmdev.h> +#include <linux/ip.h> +#include <linux/if_ether.h> +#include <linux/in.h> +#include <linux/inetdevice.h> +#include <linux/workqueue.h> + + + +//#include "linux-pch.h" + +#include <linux/usb.h> + +#include <asm/uaccess.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/system.h> +#include "usblan-compat.h" +#include "usblan.h" + +#define PACKED_ENUM enum +#define PACKED_ENUM_EXTRA +#define PACKED1 __attribute__((packed)) +#define PACKED2 +#define PACKED0 + + +#if defined(USBLAN_LOCAL_CONFIG) +#include "./usblan-config.h" +#endif + +#define MIN(a,b) (((a) < (b))?(a):(b)) +#define MAX(a,b) (((a) > (b))?(a):(b)) + +#define UNLESS(x) if (!(x)) +#define THROW(x) goto x +#define CATCH(x) while(0) x: +#define THROW_IF(e, x) if (e) { goto x; } +#define THROW_UNLESS(e, x) UNLESS (e) { goto x; } +#define BREAK_IF(x) if (x) { break; } +#define CONTINUE_IF(x) if (x) { continue; } +#define RETURN_IF(x,y) if (y) { return x; } + + +#define mutex_lock(x) down(x) +#define mutex_unlock(x) up(x) + + +#define DRIVER_VERSION "2.0.0" +#define DRIVER_AUTHOR "sl@belcarra.com" +#define DRIVER_DESC "Linux USBLAN driver" + +#ifdef MODULE +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); + +// XXX This needs to be corrected down to the last version of the +// kernel that did NOT have MODULE_LICENSE +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,15) +MODULE_LICENSE("GPL"); +#endif +#endif + +#define STATIC + +/* Module Parameters ************************************************************************* */ + +#define MAX_INTERFACES 1 + +#define MAX_RCV_SKBS 10 +#define TIMEOUT_JIFFIES (4*HZ) +#define MAX_PACKET 32768 +#define MIN_PACKET sizeof(struct ethhdr) + + +#define TX_QLEN 2 +#define RX_QLEN 2 + +static DECLARE_MUTEX(usbd_mutex); // lock for global changes +static LIST_HEAD(usbd_list); // a list for all active devices + + +typedef enum usbdnet_device_type { + usbdnet_unknown, usbdnet_basic, usbdnet_cdc, usbdnet_safe, usbdnet_blan, usbdnet_rndis +} usbdnet_device_type_t; + +char * usbdnet_device_names[] = { + "UNKNOWN", "BASIC", "CDC", "SAFE", "BLAN", "RNDIS", +}; + + + +__u8 SAFE_VERSION[2] = { + 0x00, 0x01, /* BCD Version */ +}; +__u8 SAFE_GUID[16] = { + 0x5d, 0x34, 0xcf, 0x66, 0x11, 0x18, 0x11, 0xd6, /* bGUID */ + 0xa2, 0x1a, 0x00, 0x01, 0x02, 0xca, 0x9a, 0x7f, /* bGUID */ +}; + +__u8 BLAN_VERSION[2] = { + 0x00, 0x01, /* BCD Version */ +}; +__u8 BLAN_GUID[16] = { + 0x74, 0xf0, 0x3d, 0xbd, 0x1e, 0xc1, 0x44, 0x70, /* bGUID */ + 0xa3, 0x67, 0x71, 0x34, 0xc9, 0xf5, 0x54, 0x37, /* bGUID */ +}; + +// data detail +#define DATA_CRC 0x01 +#define DATA_PADBEFORE 0x02 +#define DATA_PADAFTER 0x04 +#define DATA_FERMAT 0x08 + +// cdc notifications +#define CDC_NOTIFICATION 0xa1 +#define CDC_NOTIFICATION_NETWORK 0x00 +#define CDC_NOTIFICATION_SPEEDCHANGE 0x2a + +#define USB_DT_CS_INTERFACE 0x24 + +#define MDLM_FUNCTIONAL 0x12 +#define MDLM_DETAIL 0x13 + +#define MDLM_SAFE_GUID 0x00 +#define MDLM_BLAN_GUID 0x01 + + +#if 0 +STATIC void usblan_test_kalloc(char *msg) +{ + struct urb *foo; + printk(KERN_INFO "%s: %s\n",__FUNCTION__,msg); + if (NULL != (foo = USB_ALLOC_URB(0,GFP_ATOMIC))) { + usb_free_urb(foo); + } + printk(KERN_INFO "%s: #%08x\n",__FUNCTION__,(u32)(void*)foo); +} +#endif + + + +/* struct private + * + * This structure contains the network interface and additional per USB device information. + * + * A pointer to this structure is used in two three places: + * + * net->priv + * urb->context + * skb->cb.priv + */ +struct private { + + // general + struct usb_device *usbdev; // usb core layer provides this for the probed device + struct semaphore mutex; // lock for changes to this structure + struct list_head list; // to maintain a list of these devices () + wait_queue_head_t *wait; + wait_queue_head_t *ctrl_wait; + + struct tasklet_struct bh; + struct WORK_STRUCT crc_task; + struct WORK_STRUCT ctrl_task; + struct WORK_STRUCT reset_task; + struct WORK_STRUCT unlink_task; + + int intf_count; + int intf_max; + + // network + struct net_device net; + struct net_device_stats stats; + unsigned char dev_addr[ETH_ALEN]; + + // queues + struct sk_buff_head rxq; + struct sk_buff_head txq; + struct sk_buff_head unlink; + struct sk_buff_head done; + + // + int crc32; // append and check for appended 32bit CRC + int padded; // pad bulk transfers such that (urb->transfer_buffer_length % data_ep_out_size) == 1 + int addr_set; // set_address + int sawCRC; + + int timeouts; + + usbdnet_device_type_t usbdnet_device_type; + + //struct usb_interface *data_interface; + //struct usb_interface *comm_interface; + + struct usb_ctrlrequest ctrl_request; + struct urb *ctrl_urb; + int configuration_number; + int bConfigurationValue; + + int comm_interface; + int comm_bInterfaceNumber; + int comm_bAlternateSetting; + int comm_ep_in; + int comm_ep_in_size; + + int data_interface; + + int data_bInterfaceNumber; + int data_bAlternateSetting; + + int nodata_bInterfaceNumber; + int nodata_bAlternateSetting; + + int data_ep_in; + int data_ep_out; + int data_ep_in_size; + int data_ep_out_size; + + u8 CRCInUse; + u8 CDC; + u8 bmNetworkCapabilities; + u8 bmDataCapabilities; + u8 bPad; +}; + +/* struct skb_cb + * + * This defines how we use the skb->cb data area. It allows us to get back to the private + * data structure and track the current state of skb in our done queue. There is a pointer + * to the active urb so that it can be cancelled (e.g. tx_timeout). + * + * skb->cb + */ +typedef enum skb_state { + unknown = 0, + tx_start, // an skb in priv->txq + tx_done, // a transmitted skb in priv->done + rx_start, // an skb in priv->rxq + rx_done, // a received skb in priv->done + rx_cleanup, // a received skb being thrown out due to an error condition +} skb_state_t; + +struct skb_cb { + struct private *priv; + struct urb *urb; + skb_state_t state; + unsigned long int jiffies; +}; + + +static struct usb_driver usblan_driver; + + +struct usb_mdlm_detail_descriptor { + u8 bLength; + u8 bDescriptorType; + u8 bDescriptorSubType; + u8 bGuidDescriptorType; +} __attribute__ ((packed)); + +struct usb_safe_detail_descriptor { + u8 bLength; + u8 bDescriptorType; + u8 bDescriptorSubType; + u8 bGuidDescriptorType; + u8 bmNetworkCapabilities; + u8 bmDataCapabilities; +} __attribute__ ((packed)); + +struct usb_blan_detail_descriptor { + u8 bLength; + u8 bDescriptorType; + u8 bDescriptorSubType; + u8 bGuidDescriptorType; + u8 bmNetworkCapabilities; + u8 bmDataCapabilities; + u8 bPad; +} __attribute__ ((packed)); + +struct usb_class_descriptor { + u8 bLength; + u8 bDescriptorType; + u8 bDescriptorSubType; + u8 data[0]; +} __attribute__ ((packed)); + +struct usb_guid_descriptor { + u8 bLength; + u8 bDescriptorType; + u8 bDescriptorSubType; + u8 bGuidDescriptorType; + u8 data[0]; +} __attribute__ ((packed)); + +struct usb_notification_descriptor { + u8 bmRequestType; + u8 bNotification; + u16 wValue; + u16 wIndex; + u16 wLength; + u8 data[2]; +} __attribute__ ((packed)); + +struct usb_speedchange_descriptor { + u8 bmRequestType; + u8 bNotification; + u16 wValue; + u16 wIndex; + u16 wLength; + u32 data[2]; +} __attribute__ ((packed)); + +struct usb_mdlm_functional_descriptor { + u8 bLength; + u8 bDescriptorType; + u8 bDescriptorSubType; + u16 bcdVersion; + u8 bGuid[16]; +} __attribute__ ((packed)); + + + +/* + * default MAC address to use + */ +static unsigned char default_addr[ETH_ALEN]; + +/* Module Parameters ************************************************************************* */ + +#define VENDOR_SPECIFIC_CLASS 0xff +#define VENDOR_SPECIFIC_SUBCLASS 0xff +#define VENDOR_SPECIFIC_PROTOCOL 0xff + +#define MTU 1500+100 + + +#if defined(CONFIG_USBD_USBLAN_VENDOR) && !defined(CONFIG_USBD_USBLAN_PRODUCT) +#abort "USBLAN_VENDOR defined without USBLAN_PRODUCT" +#endif + +#define CDC_DEVICE_CLASS 0x02 // Device descriptor Class + +#define CDC_INTERFACE_CLASS 0x02 // CDC interface descriptor Class +#define CDC_INTERFACE_SUBCLASS 0x06 // CDC interface descriptor SubClass +#define RNDIS_INTERFACE_SUBCLASS 0x02 // CDC interface descriptor SubClass +#define MDLM_INTERFACE_SUBCLASS 0x0a // CDC interface descriptor SubClass + +#define DATA_INTERFACE_CLASS 0x0a // Data interface descriptor Class + +#define DEFAULT_PROTOCOL 0x00 +#define VENDOR_SPECIFIC_PROTOCOL 0xff + +#define LINEO_INTERFACE_CLASS 0xff // Lineo private interface descriptor Class + +#define LINEO_INTERFACE_SUBCLASS_SAFENET 0x01 // Lineo private interface descriptor SubClass +#define LINEO_INTERFACE_SUBCLASS_SAFESERIAL 0x02 + +#define LINEO_SAFENET_CRC 0x01 // Lineo private interface descriptor Protocol +#define LINEO_SAFENET_CRC_PADDED 0x02 // Lineo private interface descriptor Protocol + +#define LINEO_SAFESERIAL_CRC 0x01 +#define LINEO_SAFESERIAL_CRC_PADDED 0x02 + + +#define NETWORK_ADDR_HOST 0xac100005 /* 172.16.0.0 */ +#define NETWORK_ADDR_CLIENT 0xac100006 /* 172.16.0.0 */ +#define NETWORK_MASK 0xfffffffc + + +static __u32 vendor_id; // no default +static __u32 product_id; // no default +static __u32 class = LINEO_INTERFACE_CLASS; +static __u32 subclass = LINEO_INTERFACE_SUBCLASS_SAFENET; + +static __u32 echo_fcs; // no default +static __u32 noisy_fcs; // no default +static __u32 echo_rx; // no default +static __u32 echo_tx; // no default + +#ifdef MODULE +MODULE_PARM_DESC(vendor_id, "User specified USB idVendor"); +MODULE_PARM_DESC(product_id, "User specified USB idProduct"); +MODULE_PARM_DESC(class, "User specified USB Class"); +MODULE_PARM_DESC(subclass, "User specified USB SubClass"); +MODULE_PARM(vendor_id, "i"); +MODULE_PARM(product_id, "i"); +MODULE_PARM(class, "i"); +MODULE_PARM(subclass, "i"); + +MODULE_PARM_DESC(echo_tx, "echo TX urbs"); +MODULE_PARM_DESC(echo_rx, "echo RCV urbs"); +MODULE_PARM_DESC(echo_fcs, "BAD FCS"); +MODULE_PARM_DESC(noisy_fcs, "BAD FCS info"); +MODULE_PARM(echo_tx, "i"); +MODULE_PARM(echo_rx, "i"); +MODULE_PARM(echo_fcs, "i"); +MODULE_PARM(noisy_fcs, "i"); +#endif + +//match_flags: DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_DEV_CLASS , + +#define MY_USB_DEVICE(vend,prod) \ +match_flags: USB_DEVICE_ID_MATCH_DEVICE , \ +idVendor: (vend), \ +idProduct: (prod),\ +bDeviceClass: (CDC_INTERFACE_CLASS), + +static __devinitdata struct usb_device_id id_table[] = { + + // RNDIS devices Vend Prod bDeviceClass bInterfaceClass bInterfaceSubClass + + {MY_USB_DEVICE(0x15ec, 0xf001)}, // Belcarra Network Demo + {MY_USB_DEVICE(0x12b9, 0xf001)}, // Belcarra Network Demo + + +#if 1 +#if defined(CONFIG_USB_USBLAN_VENDORID) && defined(CONFIG_USB_USBLAN_PRODUCTID) + // A configured driver + {MY_USB_DEVICE(CONFIG_USB_USBLAN_VENDORID, CONFIG_USB_USBLAN_PRODUCTID)}, +#endif +#endif + + // extra null entry for module vendor_id/produc parameters and terminating entry + {}, {}, +}; + + +#ifdef MODULE +MODULE_DEVICE_TABLE(usb, id_table); +#endif + +#define ECHO_FCS +#define ECHO_RCV + +#undef ECHO_TX_SKB +#define ECHO_TX_URB + +__u32 crc32_table[256] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, + 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d +}; + +#define CRC32_INITFCS 0xffffffff // Initial FCS value +#define CRC32_GOODFCS 0xdebb20e3 // Good final FCS value + +#define CRC32_FCS(fcs, c) (((fcs) >> 8) ^ crc32_table[((fcs) ^ (c)) & 0xff]) + +/* fcs_memcpy32 - memcpy and calculate fcs + * Perform a memcpy and calculate fcs using ppp 32bit CRC algorithm. + */ +static __u32 __inline__ +fcs_memcpy32(unsigned char *dp, unsigned char *sp, int len, __u32 fcs) +{ + for (; len-- > 0; fcs = CRC32_FCS(fcs, *dp++ = *sp++)); + return fcs; +} + +/* fcs_pad32 - pad and calculate fcs + * Pad and calculate fcs using ppp 32bit CRC algorithm. + */ +static __u32 __inline__ +fcs_pad32(unsigned char *dp, int len, __u32 fcs) +{ + for (; len-- > 0; fcs = CRC32_FCS(fcs, *dp++ = '\0')); + return fcs; +} + +/* fcs_compute32 - memcpy and calculate fcs + * Perform a memcpy and calculate fcs using ppp 32bit CRC algorithm. + */ +static __u32 __inline__ +fcs_compute32(unsigned char *sp, int len, __u32 fcs) +{ + for (; len-- > 0; fcs = CRC32_FCS(fcs, *sp++)); + return fcs; +} + +void +wait_for_sync(struct WORK_STRUCT *tq) +{ + + // wait for pending bottom halfs to exit + while (PENDING_WORK_ITEM((*tq))){ + SCHEDULE_TIMEOUT(1); + } + + tq->data = 0; + + while (PENDING_WORK_ITEM((*tq))) { + SCHEDULE_TIMEOUT(1); + } +} + + +#define RETRYTIME 2 + +void +skb_bad_crc(struct sk_buff *skb, __u32 fcs) +{ +#ifdef ECHO_FCS + if (noisy_fcs) { + printk(KERN_INFO"%s: BAD FCS len: %4d crc: %08x last: %02x %02x %02x %02x\n", __FUNCTION__, + skb->len, fcs, skb->data[skb->len - 4], + skb->data[skb->len - 3], skb->data[skb->len - 2], skb->data[skb->len - 1] + ); + } + if (echo_fcs) { + int i; + unsigned char *cp = skb->data; + printk(KERN_INFO "%s: FAILED skb: %p head: %p data: %p tail: %p len: %d", __FUNCTION__, + skb, skb->head, skb->data, skb->tail, skb->len); + for (i = 0; i < skb->len; i++) { + if ((i % 32) == 0) { + printk("\nrcv[%02x]: ", i); + } + printk("%02x ", cp[i]); + } + printk("\n"); + } +#endif +} + +#if 0 +static void +dump_skb(struct sk_buff *skb, char *msg) +{ + int i; + unsigned char *cp = skb->data; + + printk(KERN_INFO "\n%s", msg); + for (i = 0; i < skb->len; i++) { + if (!(i % 32)) { + printk("\n[%02x] ", i); + } + printk("%02x ", cp[i]); + } + printk("\n"); +} +#endif + +/* ********************************************************************************************* */ +#if defined(LONG_STRING_OF_ZEROES_HACK) + +/* fermat + * + * This is a hack designed to help some broken hardware that cannot successfully + * transmit long strings of zero's without causing the host port to signal a + * status change and drop the connection. + * + */ + +typedef unsigned char BYTE; +typedef struct fermat { + int length; + BYTE power[256]; +} FERMAT; + +STATIC void fermat_init(void); +STATIC void fermat_encode(BYTE *data, int length); +STATIC void fermat_decode(BYTE *data, int length); + +STATIC int fermat_setup(FERMAT *p, int seed){ + int i = 0; + unsigned long x,y; + y = 1; + do{ + x = y; + p->power[i] = ( x == 256 ? 0 : x); + y = ( seed * x ) % 257; + i += 1; + }while( y != 1); + p->length = i; + return i; +} + +STATIC void fermat_xform(FERMAT *p, BYTE *data, int length){ + BYTE *pw = p->power; + int i, j; + BYTE * q ; + for(i = 0, j=0, q = data; i < length; i++, j++, q++){ + if(j>=p->length){ + j = 0; + } + *q ^= pw[j]; + } +} + +static FERMAT default_fermat; +static const int primitive_root = 5; +STATIC void fermat_init(){ + (void) fermat_setup(&default_fermat, primitive_root); +} + +// Here are the public official versions. +// Change the primitive_root above to another primitive root +// if you need better scatter. Possible values are 3 and 7 + + +STATIC void fermat_encode(BYTE *data, int length){ + fermat_xform(&default_fermat, data, length); +} + +STATIC void fermat_decode(BYTE *data, int length){ + fermat_xform(&default_fermat, data, length); +} +#endif + + +/* ********************************************************************************************* */ + +STATIC void defer_skb(struct private *priv, struct sk_buff *skb, struct skb_cb *cb, + skb_state_t state, struct sk_buff_head *list); +STATIC int unlink_urbs(struct sk_buff_head *q); +STATIC void urb_tx_complete(struct urb *urb); +STATIC void urb_rx_complete(struct urb *urb); +#if 0 +STATIC void urb_dead_complete(struct urb *urb); +#endif + + +/* Network Configuration *********************************************************************** */ + +/* sock_ioctl - perform an ioctl call to inet device + */ +static int sock_ioctl(u32 cmd, struct ifreq *ifreq) +{ + int rc = 0; + mm_segment_t save_get_fs = get_fs(); + //printk(KERN_INFO"%s: cmd: %x\n", __FUNCTION__, cmd); + set_fs(get_ds()); + rc = devinet_ioctl(cmd, ifreq); + set_fs(save_get_fs); + return rc; +} + +/* sock_addr - setup a socket address for specified interface + */ +static int sock_addr(char * ifname, u32 cmd, u32 s_addr) +{ + struct ifreq ifreq; + struct sockaddr_in *sin = (void *) &(ifreq.ifr_ifru.ifru_addr); + + //printk(KERN_INFO"%s: ifname: %s addr: %x\n", __FUNCTION__, ifname, ntohl(s_addr)); + + memset(&ifreq, 0, sizeof(ifreq)); + strcpy(ifreq.ifr_ifrn.ifrn_name, ifname); + + sin->sin_family = AF_INET; + sin->sin_addr.s_addr = s_addr; + + return sock_ioctl(cmd, &ifreq); +} + + +/* sock_flags - set flags for specified interface + */ +static int sock_flags(char * ifname, u16 oflags, u16 sflags, u16 rflags) +{ + int rc = 0; + struct ifreq ifreq; + + //printk(KERN_INFO"%s: ifname: %s oflags: %x s_flags: %x r_flags: %x\n", __FUNCTION__, ifname, oflags, sflags, rflags); + + memset(&ifreq, 0, sizeof(ifreq)); + strcpy(ifreq.ifr_ifrn.ifrn_name, ifname); + + oflags |= sflags; + oflags &= ~rflags; + ifreq.ifr_flags = oflags; + + //printk(KERN_INFO"%s: -> ifr_flags: %x \n", __FUNCTION__, ifreq.ifr_flags); + + THROW_IF ((rc = sock_ioctl(SIOCSIFFLAGS, &ifreq)), error); + + //printk(KERN_INFO"%s: <- ifr_flags: %x \n", __FUNCTION__, ifreq.ifr_flags); + + CATCH(error) { + printk(KERN_INFO"%s: ifconfig: cannot get/set interface flags (%d)\n", __FUNCTION__, rc); + return rc; + } + return rc; +} + +/* network_attach - configure interface + * + * This will use socket calls to configure the interface to the supplied + * ip address and make it active. + */ +STATIC int network_attach(struct net_device *net, u32 host_ip, u32 mask, int attach) +{ + int err = 0; + + //printk(KERN_INFO"%s: net: %p host_ip: %08x mask: %08x attach: %d\n", __FUNCTION__, net, host_ip, mask, attach); + if (attach) { + u16 oflags = net ? net->flags : 0; + + /* setup host_ip address, netwask, and broadcast address */ + if (host_ip) { + THROW_IF ((err = sock_addr(net->name, SIOCSIFADDR, htonl(host_ip))), error); + if (mask) { + THROW_IF ((err = sock_addr(net->name, SIOCSIFNETMASK, htonl(mask))), error); + THROW_IF ((err = sock_addr(net->name, SIOCSIFBRDADDR, htonl(host_ip | ~mask))), error); + } + /* bring the interface up */ + THROW_IF ((err = sock_flags(net->name, oflags, IFF_UP, 0)), error); + } + + + } + else { + u16 oflags = net ? net->flags : 0; + /* bring the interface down */ + THROW_IF ((err = sock_flags(net->name, oflags, 0, IFF_UP)), error); + } + + CATCH(error) { + printk(KERN_INFO"%s: ifconfig: cannot configure interface (%d)\n", __FUNCTION__, err); + return err; + } + return 0; +} + + +/* ********************************************************************************************* */ + +/* Network Support Functions - these are called by the network layer *************************** */ + +/* net_get_stats - network device get stats function + * Retreive network device stats structure. + */ +STATIC struct net_device_stats * +net_get_stats(struct net_device *net) +{ + struct private *priv = (struct private *) net->priv; + //printk(KERN_INFO "%s:\n", __FUNCTION__); + return &priv->stats; +} + +/* net_set_mac_addr - network device set mac address function + */ +STATIC int +net_set_mac_address(struct net_device *net, void *p) +{ + struct private *priv = (struct private *) net->priv; + struct sockaddr *addr = p; + + //printk(KERN_INFO "%s:\n", __FUNCTION__); + if (netif_running(net)) { + return -EBUSY; + } + memcpy(net->dev_addr, addr->sa_data, net->addr_len); + priv->addr_set = 1; + return 0; +} + +/* net_change_mtu - network device set config function + * Set MTU, if running we can only change it to something less + * than or equal to MTU when PVC opened. + */ +STATIC int +net_change_mtu(struct net_device *net, int mtu) +{ + //printk(KERN_INFO "%s:\n", __FUNCTION__); + if ((mtu < sizeof(struct ethhdr)) || (mtu > (MAX_PACKET - 4))) { + return -EINVAL; + } + if (netif_running(net)) { + if (mtu > net->mtu) { + return -EBUSY; + } + } + net->mtu = mtu; + return 0; +} + +/* net_open - called by network layer to open network interface + */ +STATIC int +net_open(struct net_device *net) +{ + struct private *priv = (struct private *) net->priv; + + //printk(KERN_INFO "%s: priv#%08x net#%08x\n",__FUNCTION__,(u32)(void*)priv,(u32)(void*)net); + mutex_lock(&priv->mutex); + //printk(KERN_INFO "%s: AAA usbdev#%08x dataIF=%d alt=%d\n",__FUNCTION__,(u32)(void*)priv->usbdev, priv->data_bInterfaceNumber, priv->data_bAlternateSetting); + + // enable traffic + //printk(KERN_INFO"%s: setting data interface bInterfaceNumber: %d bAlternateSetting: %d\n", __FUNCTION__, + // priv->data_bInterfaceNumber, priv->data_bAlternateSetting); + +#if defined(LINUX24) + if (usb_set_interface( priv->usbdev, priv->data_bInterfaceNumber, priv->data_bAlternateSetting)) { + err("usb_set_interface() failed"); + } +#endif + + // XXX find interface ip / netmask, if netmask is 255.255.255.252 + // then send ip and ip+1 to device as suggested addresses + + + + + //printk(KERN_INFO "%s: BBB\n",__FUNCTION__); + + // tell the network layer to enable transmit queue + netif_start_queue(net); + //printk(KERN_INFO "%s: BBB\n",__FUNCTION__); + + // call the bottom half to schedule some receive urbs + tasklet_schedule(&priv->bh); + //printk(KERN_INFO "%s: CCC\n",__FUNCTION__); + + mutex_unlock(&priv->mutex); + //printk(KERN_INFO "%s: DDD\n",__FUNCTION__); + return 0; +} + +/* net_stop - called by network layer to stop network interface + */ +STATIC int +net_stop(struct net_device *net) +{ + struct private *priv = (struct private *) net->priv; + + DECLARE_WAIT_QUEUE_HEAD(unlink_wakeup); + DECLARE_WAITQUEUE(wait, current); + + //printk(KERN_INFO "%s:\n",__FUNCTION__); + + mutex_lock(&priv->mutex); + + // tell the network layer to disable the transmit queue + netif_stop_queue(net); + + + // disable (if possible) network traffic + if (priv->nodata_bInterfaceNumber >= 0) { + + //printk(KERN_INFO"%s: setting nodata interface bInterfaceNumber: %d bAlternateSetting: %d\n", __FUNCTION__, + // priv->nodata_bInterfaceNumber, priv->nodata_bAlternateSetting); + + if (usb_set_interface( priv->usbdev, priv->nodata_bInterfaceNumber, priv->nodata_bAlternateSetting)) { + err("usb_set_interface() failed"); + } + } + + + // setup a wait queue - this also acts as a flag to prevent bottom half from allocating more urbs + add_wait_queue(&unlink_wakeup, &wait); + priv->wait = &unlink_wakeup; + + // move the tx and rx urbs into the done queue + unlink_urbs(&priv->txq); + unlink_urbs(&priv->rxq); + + // wait for done queue to empty + while (skb_queue_len(&priv->rxq) || skb_queue_len(&priv->txq) || skb_queue_len(&priv->done)) { + current->state = TASK_UNINTERRUPTIBLE; + schedule_timeout(TIMEOUT_JIFFIES * 1000); + } + priv->wait = 0; + + // cleanup + remove_wait_queue(&unlink_wakeup, &wait); + + mutex_unlock(&priv->mutex); + return 0; +} + + +/* net_tx_timeout - called by network layer to cancel outstanding skbs + */ +STATIC void +net_tx_timeout(struct net_device *net) +{ + //struct private *priv = (struct private *) net->priv; + //unsigned char *cp; + //struct urb *urb; + + //unsigned char deadbeef[] = "DEADBEEF"; + + //printk(KERN_INFO"%s:\n", __FUNCTION__); + //unlink_urbs(&priv->txq); + //tasklet_schedule(&priv->bh); + + //if (priv->unlink_task.sync == 0) { + // schedule_task(&priv->unlink_task); + //} + + return; +#if 0 + // Attempt to send a short usb packet to the device, this will ensure that + // any partially completed bulk transfer will be terminated. + + if (!(cp = kmalloc(sizeof(deadbeef), GFP_KERNEL))) { + return; + } + if (!(urb = USB_ALLOC_URB(0,GFP_ATOMIC))) { + kfree(cp); + return; + } + memcpy(cp, deadbeef, sizeof(deadbeef)); + + FILL_BULK_URB(urb, priv->usbdev, usb_sndbulkpipe(priv->usbdev, priv->data_ep_out), + cp, sizeof(deadbeef), urb_dead_complete, NULL); + + urb->transfer_flags = USB_QUEUE_BULK | USB_ASYNC_UNLINK | USB_NO_FSBR; + + //usb_endpoint_running(priv->usbdev, usb_pipeendpoint(priv->data_ep_out), usb_pipeout(priv->data_ep_out)); + //usb_settoggle(priv->usbdev, usb_pipeendpoint(priv->data_ep_out), usb_pipeout(priv->data_ep_out), 0); + + if (usb_submit_urb(urb)) { + kfree(cp); + urb->transfer_buffer = NULL; + usb_free_urb(urb); + } +#endif +} + +/* net_hard_start_xmit - called by network layer to transmit skb + */ +STATIC int +net_hard_start_xmit(struct sk_buff *skb, struct net_device *net) +{ + struct private *priv = (struct private *) net->priv; + struct urb *urb; + struct skb_cb *cb; + struct sk_buff *skb2; + int flags = in_interrupt()? GFP_ATOMIC : GFP_KERNEL; + __u32 fcs; + int length; + int pad; + + //printk(KERN_INFO "%s:\n",__FUNCTION__); + + echo_tx = 0; + // debug + if (echo_tx) { + int i; + unsigned char *cp = skb->data; + printk(KERN_INFO"%s: skb: %p %d\n", __FUNCTION__, skb, skb->len); + for (i = 0; i < skb->len; i++) { + if ((i % 32) == 0) { + printk("\ntx[%3x]: ", i); + } + printk("%02x ", cp[i]); + } + printk("\n"); + } + + // allocate urb + THROW_IF (priv->wait || !(urb = USB_ALLOC_URB(0,flags)), free_skb_only); + + // calculate length required + if (priv->bmDataCapabilities & (DATA_PADBEFORE | DATA_PADAFTER)) { + // we need to pad so that after appending the CRC we have a multiple of packetsize + length = priv->data_ep_out_size * (((skb->len + 4 + 1) / priv->data_ep_out_size) + 1); + } + else { + // require a minimum of one full packet + length = MAX(priv->data_ep_out_size, skb->len + 4 + 1); + } + + // allocate a new skb, copy data to it computing FCS, + // the extra bytes are for the CRC and optional pad byte + THROW_IF (!(skb2 = alloc_skb(length + 4, flags)), free_urb_and_skb); // XXX +4 ? + + if (priv->bmDataCapabilities & DATA_CRC) { + + fcs = fcs_memcpy32(skb_put(skb2, skb->len), skb->data, skb->len, CRC32_INITFCS); + + //dump_skb(skb, "skb"); + dev_kfree_skb_any(skb); + skb = skb2; + + if (priv->bmDataCapabilities & DATA_PADBEFORE) { + if ((pad = (length - skb->len - 4)) > 0) { + // pad to required length less four (CRC), copy fcs and append pad byte if required + fcs = fcs_pad32(skb_put(skb, pad), pad, fcs); + } + } + + fcs = ~fcs; + *skb_put(skb, 1) = fcs & 0xff; + *skb_put(skb, 1) = (fcs >> 8) & 0xff; + *skb_put(skb, 1) = (fcs >> 16) & 0xff; + *skb_put(skb, 1) = (fcs >> 24) & 0xff; + + if (priv->bmDataCapabilities & DATA_PADAFTER) { + while ((skb->len % priv->bPad) || !(skb->len % priv->data_ep_out_size)) skb_put(skb, 1); + } + + // append a byte if required, we overallocated by one to allow for this + else if (!(skb->len % priv->data_ep_out_size)) { + *skb_put(skb, 1) = 0; + } + + //dump_skb(skb, "skb with CRC"); + + } + else { + memcpy(skb_put(skb2, skb->len), skb->data, skb->len); + + //dump_skb(skb, "skb"); + dev_kfree_skb_any(skb); + skb = skb2; + + if (!(skb->len % priv->data_ep_out_size)) { + *skb_put(skb, 1) = 0; + } + } + + // hand urb off to usb layer + + cb = (struct skb_cb *) skb->cb; + cb->urb = urb; + cb->priv = priv; + cb->state = tx_start; + cb->jiffies = jiffies; + + // urb->context ends up with pointer to skb + FILL_BULK_URB(urb, priv->usbdev, usb_sndbulkpipe(priv->usbdev, priv->data_ep_out), skb->data, skb->len, + urb_tx_complete, skb); + + urb->transfer_flags = USB_QUEUE_BULK | USB_ASYNC_UNLINK | USB_NO_FSBR; +#if defined(LINUX24) + urb->timeout = 2; +#endif /* XXX FIX ME */ + + + // submit the urb and restart (or not) the network device queue + //printk(KERN_INFO"%s: skb: %p %d urb %p len: %d\n", __FUNCTION__, skb, skb->len, urb, urb->transfer_buffer_length); + + if (USB_SUBMIT_URB(urb)) { + netif_start_queue(net); + priv->stats.tx_dropped++; + usb_free_urb(urb); + THROW(free_urb_and_skb); + } + skb_queue_tail(&priv->txq, skb); + if (priv->txq.qlen < TX_QLEN) { + netif_start_queue(net); + } + else { + net->trans_start = jiffies; + } + CATCH(free_skb_only) { + CATCH(free_urb_and_skb) { + usb_free_urb(urb); + } + dev_kfree_skb_any(skb); + return NET_XMIT_DROP; + } + return NET_XMIT_SUCCESS; +} + + +/* Receive Related ***************************************************************************** */ + +/* rx_submit - queue an urb to receive data + */ +STATIC void +rx_submit(struct private *priv, struct urb *urb, int gpf) +{ + struct sk_buff *skb; + struct skb_cb *cb; + unsigned long flags; + int size; + + //printk(KERN_INFO "%s:\n",__FUNCTION__); + + //usblan_test_kalloc("AAA"); + size = ((priv->net.mtu + 14 + 4 + priv->data_ep_in_size) / priv->data_ep_in_size) * priv->data_ep_in_size; + + //printk(KERN_INFO"%s: ep_in: %d ep_in_size: %d size: %d urb#%08x\n", __FUNCTION__, + // priv->data_ep_in, priv->data_ep_in_size, size, (u32)(void*)urb); + + //TBR-32-bit if (!(skb = alloc_skb(size + 2, gpf))) { + if (!(skb = alloc_skb(size + 4, gpf))) { + //printk(KERN_INFO "%s: alloc_skb() failed.\n",__FUNCTION__); + dbg("no rx skb"); + tasklet_schedule(&priv->bh); + THROW(free_urb_only); + return; + } + //TBR-32-bit skb_reserve(skb, 2); + skb_reserve(skb, 4); + //usblan_test_kalloc("BBB"); + + cb = (struct skb_cb *) skb->cb; + cb->priv = priv; + cb->urb = urb; + cb->state = rx_start; + //usblan_test_kalloc("CCC"); + + //printk(KERN_INFO"%s: AAA\n", __FUNCTION__); + // urb->context ends up with pointer to skb + FILL_BULK_URB(urb, priv->usbdev, usb_rcvbulkpipe(priv->usbdev, priv->data_ep_in), skb->data, size, urb_rx_complete, skb); + //usblan_test_kalloc("DDD"); + + urb->transfer_flags |= USB_QUEUE_BULK; + + //printk(KERN_INFO"%s: BBB\n", __FUNCTION__); + spin_lock_irqsave(&priv->rxq.lock, flags); + //printk(KERN_INFO"%s: CCC\n", __FUNCTION__); + if (netif_running(&priv->net)) { + + //printk(KERN_INFO"%s: DDD urb %p\n", __FUNCTION__, urb); + //usblan_test_kalloc("EEE"); + + //printk(KERN_INFO "%s: starting URB #%08x\n",__FUNCTION__,(u32)(void*)urb); + + spin_unlock_irqrestore(&priv->rxq.lock, flags); + if (USB_SUBMIT_URB(urb)) { + //printk(KERN_INFO "%s: submit failed freeing URB: %p\n", __FUNCTION__, urb); + //usblan_test_kalloc("FFF"); + tasklet_schedule(&priv->bh); + THROW(free_urb_and_skb); + } + else { + //printk(KERN_INFO"%s: FFF skb %p\n", __FUNCTION__, skb); + __skb_queue_tail(&priv->rxq, skb); + } + //printk(KERN_INFO"%s: GGG urb %p\n", __FUNCTION__, urb); + //usblan_test_kalloc("GGG"); + } + else { + spin_unlock_irqrestore(&priv->rxq.lock, flags); + //printk(KERN_INFO "%s: network stopped, freeing urb: %p\n", __FUNCTION__, urb); + THROW(free_urb_and_skb); + } + + CATCH(free_urb_only) { + + CATCH(free_urb_and_skb) { + printk(KERN_INFO"%s: error freeing skb %p\n", __FUNCTION__, skb); + dev_kfree_skb_any(skb); + } + printk(KERN_INFO"%s: error freeing urb %p\n", __FUNCTION__, urb); + usb_free_urb(urb); + } +} + +/* urb_rx_complete - called by usb core layer when urb has been received + */ +STATIC void +urb_rx_complete(struct urb *urb) +{ + struct sk_buff *skb; + struct skb_cb *cb; + struct private *priv; + + //printk(KERN_INFO "%s: urb %p len: %d status=%d\n", __FUNCTION__, urb, urb->actual_length,urb->status); + + if (!(skb = (struct sk_buff *) urb->context)) { + printk(KERN_ERR "%s: skb NULL\n", __FUNCTION__); + } + if (!(cb = (struct skb_cb *) skb->cb)) { + printk(KERN_ERR "%s: cb NULL\n", __FUNCTION__); + } + if (!(priv = cb->priv)) { + printk(KERN_ERR "%s: priv NULL\n", __FUNCTION__); + } + + + switch (urb->status) { + case 0: + priv->timeouts = 0; + + if ((MIN_PACKET < urb->actual_length) && (urb->actual_length < MAX_PACKET)) { + cb->urb = NULL; + skb_put(skb, urb->actual_length); + defer_skb(priv, skb, cb, rx_done, &priv->rxq); + if (netif_running(&priv->net)) { + rx_submit(priv, urb, GFP_ATOMIC); + } + break; + } + + /* FALLTHROUGH */ + + case -EOVERFLOW: + priv->stats.rx_over_errors++; + case -EILSEQ: + case -ECONNABORTED: + case -ETIMEDOUT: + priv->timeouts++; + priv->stats.rx_dropped++; + //printk(KERN_INFO"%s: RX_CLEANUP urb->status: %d timeout: %d\n", __FUNCTION__, urb->status, priv->timeouts); + defer_skb(priv, skb, cb, rx_cleanup, &priv->rxq); + + // XXX provisional, this will attempt to force a reset for a device that + // there have been multiple receive timeouts. This is a host + // that is no longer responding to IN with a NAK. Typically this is + // due to a device that has stopped operation without dropping the + // usb control resistor to tell us. + + if (priv->timeouts > 20) { + priv->timeouts = 0; + //printk(KERN_INFO "%s: scheduling reset task\n", __FUNCTION__); + +#if 0 + if (priv->reset_task.sync == 0) { + schedule_task(&priv->reset_task); +#else + if(!PENDING_WORK_ITEM(priv->reset_task)){ + SCHEDULE_WORK(priv->reset_task); +#endif + } + } + break; + } +} + + +/* bh_rx_process - called by bottom half to process received skb + */ +STATIC inline void +bh_rx_process(struct private *priv, struct sk_buff *skb) +{ + __u32 fcs; + +#if 0 + if (skb->len > (priv->net.mtu + 16 + 4 + 1 + 100)) { + printk(KERN_INFO "%s: URB too large\n", __FUNCTION__); + priv->stats.rx_length_errors++; + THROW(crc_error); + dev_kfree_skb(skb); + priv->stats.rx_errors++; + return; + } +#endif +#if 0 + if (priv->bmDataCapabilities & DATA_FERMAT) { + fermat_decode(skb->data, skb->len); + } +#endif + //printk(KERN_INFO "%s:\n",__FUNCTION__); + if (priv->bmDataCapabilities & DATA_CRC) { + + // check if we need to check for extra byte + if ((skb->len % priv->data_ep_in_size) == 1) { + + // check fcs across length minus one bytes + if ((fcs = fcs_compute32(skb->data, skb->len - 1, CRC32_INITFCS)) == CRC32_GOODFCS) { + // success, trim extra byte and fall through + skb_trim(skb, skb->len - 1); + + priv->sawCRC = 1; + } + // failed, check additional byte + else if ((fcs = fcs_compute32(skb->data + skb->len - 1, 1, fcs)) != CRC32_GOODFCS) { + // failed + printk(KERN_INFO "%s: CRC fail on extra byte\n", __FUNCTION__); + THROW_IF(priv->sawCRC, crc_error); + return; + } + // success fall through, possibly with corrected length + } + // normal check across full frame + else if ((fcs = fcs_compute32(skb->data, skb->len, CRC32_INITFCS)) != CRC32_GOODFCS) { + printk(KERN_INFO "%s: CRC fail len: %d\n", __FUNCTION__, skb->len); + THROW_IF(priv->sawCRC, crc_error); + return; + } + + // trim fcs + skb_trim(skb, skb->len - 4); + } + + // debug + echo_rx = 0; + if (echo_rx) { + int i; + unsigned char *cp = skb->data; + + for (i = 0; i < skb->len; i++) { + if ((i % 32) == 0) { + printk("\nrx[%3x]: ", i); + } + printk("%02x ", cp[i]); + } + printk("\n"); + } + + // push the skb up + memset(skb->cb, 0, sizeof(struct skb_cb)); + skb->dev = &priv->net; + skb->protocol = eth_type_trans(skb, &priv->net); + skb->pkt_type = PACKET_HOST; + skb->ip_summed = CHECKSUM_UNNECESSARY; + priv->stats.rx_packets++; + priv->stats.rx_bytes += skb->len; + if (netif_rx(skb)) { + printk(KERN_INFO "%s: submitting skb failed\n", __FUNCTION__); + } + + CATCH(crc_error) { + dev_kfree_skb_any(skb); + priv->stats.rx_errors++; + } +} + + +/* Transmit Related **************************************************************************** */ + +/* This is a version of usb_clear_halt() that doesn't read the status from + * the device -- this is because some devices crash their internal firmware + * when the status is requested after a halt + */ +STATIC int +local_clear_halt(struct usb_device *dev, int pipe) +{ + int result; + int endp = usb_pipeendpoint(pipe) | (usb_pipein(pipe) << 7); + + if ((result = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + USB_REQ_CLEAR_FEATURE, USB_RECIP_ENDPOINT, 0, endp, NULL, 0, HZ * 3))) + { + return result; + } + + // reset the toggles and endpoint flags + // usb_endpoint_running(dev, usb_pipeendpoint(pipe), usb_pipeout(pipe)); + // usb_settoggle(dev, usb_pipeendpoint(pipe), usb_pipeout(pipe), 0); + + return 0; +} + +/* ctrl_task - called as kernel task to send clear halt message + */ +STATIC void +ctrl_task(void *data) +{ + struct private *priv = (struct private *) data; + + local_clear_halt(priv->usbdev, usb_sndbulkpipe(priv->usbdev, priv->data_ep_out)); + netif_wake_queue(&priv->net); +} + +/* reset_task - called as kernel task to send reset the device + */ +STATIC void +reset_task(void *data) +{ + struct private *priv = (struct private *) data; + + if (usb_reset_device(priv->usbdev)) { + printk(KERN_INFO "%s: reset failed\n", __FUNCTION__); + } +} + +/* urb_tx_complete - called by usb core layer when network skb urb has been transmitted + */ +STATIC void +urb_tx_complete(struct urb *urb) +{ + struct sk_buff *skb; + + //printk(KERN_INFO"%s: urb: %p\n", __FUNCTION__, urb); + + if (urb->status) { + printk(KERN_INFO "%s: urb: %p status: %d\n", __FUNCTION__, urb, urb->status); + } + + urb->dev = 0; + + if ((skb = urb->context)) { + struct skb_cb *cb; + struct private *priv; + + if (!(cb = (struct skb_cb *) skb->cb)) { + printk(KERN_ERR "%s: cb NULL skb: %p\n", __FUNCTION__, skb); + } + if (!(priv = cb->priv)) { + printk(KERN_ERR "%s: priv NULL skb: %p cb: %p\n", __FUNCTION__, skb, cb); + } + + if (!cb->urb) { + printk(KERN_ERR "%s: urb NULL skb: %p cb: %p\n", __FUNCTION__, skb, cb); + } + + if (cb->urb != urb) { + printk(KERN_ERR "%s: urb not urb skb: %p cb: %p cb->urb: %p urb: %p\n", __FUNCTION__, + skb, cb, cb->urb, urb); + } + + if (urb->status == USB_ST_STALL) { + printk(KERN_ERR "%s: USB_ST_STALL\n", __FUNCTION__); +#if defined(LINUX24) + if (priv->ctrl_task.sync == 0) { + schedule_task(&priv->ctrl_task); + } +#else + if (!PENDING_WORK_ITEM(priv->ctrl_task)){ + SCHEDULE_WORK(priv->ctrl_task); + } +#endif + + } + defer_skb(priv, skb, cb, tx_done, &priv->txq); + } +} + +#if 0 +/* urb_dead_complete - called by usb core layer when deadbeef urb has been transmitted + */ +STATIC void +urb_dead_complete(struct urb *urb) +{ + urb->dev = 0; + + if (urb->transfer_buffer) { + kfree(urb->transfer_buffer); + urb->transfer_buffer = NULL; + } + usb_free_urb(urb); +} +#endif + + +/* Bottom Half ********************************************************************************* */ + +/* defer_skb - put an skb on done list and schedule bottom half if necessary + */ +STATIC void +defer_skb(struct private *priv, struct sk_buff *skb, struct skb_cb *cb, + skb_state_t state, struct sk_buff_head *list) +{ + unsigned long flags; + + if (!cb) { + printk(KERN_ERR "%s: cb is NULL!\n", __FUNCTION__); + } + + //if !(cb->urb) { + // printk(KERN_ERR"%s: urb is NULL!\n", __FUNCTION__); + //} + + cb->state = state; + + if (!skb) { + printk(KERN_ERR "%s: skb is NULL!\n", __FUNCTION__); + return; + } + + spin_lock_irqsave(&list->lock, flags); + __skb_unlink(skb, list); + spin_unlock(&list->lock); + + + // link to done queue + spin_lock(&priv->done.lock); + __skb_queue_tail(&priv->done, skb); + + if (priv->done.qlen == 1) { + tasklet_schedule(&priv->bh); + } + spin_unlock_irqrestore(&priv->done.lock, flags); +} + + +/* unlink_urbs - tell usb core layer that we want it to abandon attempts to send/receive urbs + */ +STATIC int +unlink_urbs(struct sk_buff_head *q) +{ + struct sk_buff *skb; + int count = 0; + + // move from the current queue to the unlink queue + while ((skb = skb_dequeue(q))) { + struct skb_cb *cb; + struct urb *urb; + struct private *priv; + int retval; + + if (!(cb = (struct skb_cb *) skb->cb)) { + printk(KERN_ERR "%s: cb NULL\n", __FUNCTION__); + continue; + } + + if (!(urb = cb->urb)) { + printk(KERN_ERR "%s: urb NULL\n", __FUNCTION__); + continue; + } + + if (!(priv = cb->priv)) { + printk(KERN_ERR "%s: priv NULL\n", __FUNCTION__); + continue; + } + + // place them here until they can be processed after unlinking + skb_queue_tail(&priv->unlink, skb); + + //printk(KERN_INFO "%s: unlinking skb: %p len: %d jiffs: %ld\n", __FUNCTION__, + // skb, skb->len, jiffies - cb->jiffies); + + // usb core layer will call rx_complete() with appropriate status so that we can remove + urb->transfer_flags |= USB_ASYNC_UNLINK; + if ((retval = usb_unlink_urb(urb)) < 0) { + dbg("unlink urb err, %d", retval); + } + else { + count++; + } + } + return count; +} + +/* unlink_task - called as kernel task to send crc message + */ +STATIC void +unlink_task(void *data) +{ + struct private *priv = (struct private *) data; + //printk(KERN_INFO"%s:\n", __FUNCTION__); + unlink_urbs(&priv->txq); + tasklet_schedule(&priv->bh); +} + + +/* bh - bottom half + */ +STATIC void +bh(unsigned long data) +{ + struct private *priv = (struct private *) data; + struct sk_buff *skb; + + //printk(KERN_INFO "%s: priv#%08x\n", __FUNCTION__, (u32)(void*)priv); + + if (!priv) { + printk(KERN_INFO "%s: priv NULL\n", __FUNCTION__); + return; + } + + // process all skb's on the done queue + while ((skb = skb_dequeue(&priv->done))) { + + struct skb_cb *cb; + + //printk(KERN_INFO "%s: skb#%08x\n", __FUNCTION__, (u32)(void*)skb); + + if (!(cb = (struct skb_cb *) skb->cb)) { + printk(KERN_INFO "%s: cb NULL skb: %p\n", __FUNCTION__, skb); + continue; + } + + switch (cb->state) { + case rx_done: + // printk(KERN_INFO"%s: rx_done skb: %p\n", __FUNCTION__, skb); + bh_rx_process(priv, skb); + break; + + case tx_done: + // printk(KERN_INFO"%s: tx_done skb: %p\n", __FUNCTION__, skb); + { + struct urb *urb; + if (!(urb = cb->urb)) { + printk(KERN_INFO "%s: urb NULL skb: %p cb: %p\n", __FUNCTION__, skb, cb); + dev_kfree_skb_any(skb); + continue; + } + + if (urb->status) { + priv->stats.tx_errors++; + //printk(KERN_INFO "%s: urb: %p skb: %p status: %d\n", __FUNCTION__, + // urb, skb, cb->urb->status); + urb->transfer_buffer_length = 0; + urb->transfer_buffer = NULL; + //usb_free_urb(urb); + //dev_kfree_skb_any(skb); + } + else { + priv->stats.tx_packets++; + priv->stats.tx_bytes += skb->len; + usb_free_urb(urb); + dev_kfree_skb_any(skb); + } + //usb_free_urb(urb); + //dev_kfree_skb_any(skb); + break; + } + case rx_cleanup: + //printk(KERN_INFO"%s: rx_cleanup skb: %p\n", __FUNCTION__, skb); + { + struct urb *urb; + if (!(urb = cb->urb)) { + printk(KERN_INFO "%s: urb NULL skb: %p cb: %p\n", __FUNCTION__, skb, cb); + dev_kfree_skb_any(skb); + continue; + } + + if (urb) { + usb_free_urb(urb); + } + dev_kfree_skb_any(skb); + break; + } + case unknown: + case tx_start: + case rx_start: + printk(KERN_INFO "%s: UNKNOWN\n", __FUNCTION__); + printk(KERN_INFO "%s: inconsistant cb state: %d\n", __FUNCTION__, cb->state); + break; + } + } + + //printk(KERN_INFO "%s: priv->wait#%08x\n", __FUNCTION__, (u32)(void*)priv->wait); + + // are we waiting for pending urbs to complete? + if (priv->wait) { + if (!(priv->txq.qlen + priv->rxq.qlen + priv->done.qlen + priv->unlink.qlen)) { + //printk(KERN_INFO "%s: wakeup\n", __FUNCTION__); + wake_up(priv->wait); + } + } + + // do we need to queue up receive urbs? + else if (netif_running(&priv->net)) { + + while ((priv->rxq.qlen < RX_QLEN) && (priv->timeouts < 4)) { + struct urb *urb; + + //usblan_test_kalloc("bhR0"); + //printk(KERN_INFO "%s: allocating receive urb ATOMIC#%08x KERNEL#%08x\n", __FUNCTION__, + // GFP_ATOMIC,GFP_KERNEL); + // allocate an urb and use rx_submit to prepare and add it to the rxq + if ((urb = USB_ALLOC_URB(0,GFP_ATOMIC))) { + //usblan_test_kalloc("bhR1"); + //printk(KERN_INFO "%s: allocated receive urb#%08x\n", __FUNCTION__,(u32)(void*)urb); + rx_submit(priv, urb, GFP_ATOMIC); + //usblan_test_kalloc("bhR2"); + } + else { + // we failed, schedule another run to try again + //printk(KERN_INFO "%s: allocating receive urb failed\n", __FUNCTION__); + tasklet_schedule(&priv->bh); + } + } + + if (priv->txq.qlen < TX_QLEN) { + //printk(KERN_INFO"%s: wake queue\n", __FUNCTION__); + netif_wake_queue(&priv->net); + } + } +} + + + + +/* USB Functions - Probe and Disconnect ******************************************************** */ + +#define BELCARRA_SETTIME 0x04 +#define BELCARRA_SETIP 0x05 +#define BELCARRA_SETMSK 0x06 +#define BELCARRA_SETROUTER 0x07 + + +static void vendor_callback(struct urb *urb) +{ + //printk(KERN_INFO"%s:\n", __FUNCTION__); + //wake_up(priv->ctrl_wait); + + usb_free_urb(urb); + //printk(KERN_INFO"%s: finish\n", __FUNCTION__); +} + + +int VendorOut(struct private *priv, u8 bRequest, u16 wValue, u16 wIndex) +{ + struct usb_ctrlrequest *ctrl_request = &priv->ctrl_request; + int rc = 0; + + //DECLARE_WAIT_QUEUE_HEAD(vendor_wakeup); + //DECLARE_WAITQUEUE(wait, current); + + //printk(KERN_INFO"%s: bRequest: %02x wValue: %04x wIndex: %04x\n", __FUNCTION__, bRequest, wValue, wIndex); + + // setup a wait queue - this also acts as a flag to prevent bottom half from allocating more urbs + //add_wait_queue(&vendor_wakeup, &wait); + //priv->ctrl_wait = &vendor_wakeup; + + mutex_lock(&priv->mutex); + + THROW_UNLESS((priv->ctrl_urb = USB_ALLOC_URB(0, GFP_ATOMIC)), error); + + // create urb + ctrl_request->bRequestType = USB_TYPE_VENDOR | USB_RECIP_DEVICE; + ctrl_request->bRequest = bRequest; + ctrl_request->wValue = wValue; + ctrl_request->wIndex = wIndex; + ctrl_request->wLength = 0; + + FILL_CONTROL_URB(priv->ctrl_urb, priv->usbdev, + usb_sndctrlpipe(priv->usbdev, 0), + (char *)&priv->ctrl_request, + NULL, 0, vendor_callback, priv + ); + + // submit urb + + THROW_IF(USB_SUBMIT_URB(priv->ctrl_urb), error); + + // sleep + //current->state = TASK_UNINTERRUPTIBLE; + //schedule_timeout(TIMEOUT_JIFFIES * 1000); + //priv->ctrl_wait = NULL; + + + CATCH(error) { + rc = -EINVAL; + } + + //if (priv->ctrl_urb) + // usb_free_urb(priv->ctrl_urb); + //priv->ctrl_urb = NULL; + + // cleanup + //remove_wait_queue(&vendor_wakeup, &wait); + mutex_unlock(&priv->mutex); + return rc; +} + +int VendorOutLong(struct private *priv, u16 bRequest, u32 data) +{ + //printk(KERN_INFO"%s: bRequest: %02x data: %04x\n", __FUNCTION__, bRequest, data); + data = ntohl(data); + return VendorOut(priv, bRequest, (u16)(data>>16), (u16)(data&0xffff)); +} + +void network_notify(struct private *priv, u32 host_ip, u32 mask, u32 client_ip) +{ + + + //printk(KERN_INFO"%s: client_ip: %08x mask: %08x host_ip: %x\n", __FUNCTION__, client_ip, mask, host_ip); + THROW_IF(VendorOutLong(priv, BELCARRA_SETIP, client_ip), error ); + THROW_IF(VendorOutLong(priv, BELCARRA_SETMSK, mask), error ); + THROW_IF(VendorOutLong(priv, BELCARRA_SETROUTER, host_ip), error ); + + //printk(KERN_INFO"%s: host_ip\n", __FUNCTION__); + //printk(KERN_INFO"%s: finished\n", __FUNCTION__); + + CATCH(error) { + } +} + + +#if 0 +/* This is a version of usb_clear_halt() that doesn't read the status from + * the device -- this is because some devices crash their internal firmware + * when the status is requested after a halt + */ +STATIC int +set_crc(struct usb_device *dev, int pipe) +{ + return usb_control_msg(dev, + usb_sndctrlpipe(dev, 0), + 0x3, + USB_TYPE_VENDOR | USB_RECIP_INTERFACE | USB_DIR_OUT, + 0, + usb_pipeendpoint(pipe) | (usb_pipein(pipe) << 7), + NULL, 0, HZ * 3); +} +#endif + +#if 0 +/* crc_task - called as kernel task to send crc message + */ +STATIC void +crc_task(void *data) +{ + struct private *priv = (struct private *) data; + set_crc(priv->usbdev, usb_sndctrlpipe(priv->usbdev, 0)); +} +#endif + + +/* create_private - create private data structure and initialize network interface + */ +STATIC struct private * +create_private(int devnum) +{ + struct private *priv; + struct net_device *net; + + if (!(priv = kmalloc(sizeof(struct private), GFP_KERNEL))) { + return NULL; + } + + memset(priv, 0, sizeof(struct private)); + + net = &priv->net; +#ifdef MODULE + SET_MODULE_OWNER(net); +#endif + net->priv = priv; + //printk(KERN_INFO "%s: priv#%08x net#%08x\n",__FUNCTION__,(u32)(void*)priv,(u32)(void*)net); + // XXX usbb vs usbl + + switch(priv->usbdnet_device_type) { + + case usbdnet_blan: + strcpy(net->name, "usbl%d"); + break; + default: + strcpy(net->name, "usb%d"); + break; + } + memcpy(priv->dev_addr, default_addr, ETH_ALEN); + + priv->dev_addr[ETH_ALEN - 1] = (unsigned char) devnum; + + memcpy(net->dev_addr, default_addr, sizeof(default_addr)); + net->dev_addr[ETH_ALEN - 1] = devnum; + + ether_setup(net); + + net->set_mac_address = net_set_mac_address; + net->hard_start_xmit = net_hard_start_xmit; + net->get_stats = net_get_stats; + net->change_mtu = net_change_mtu; + net->open = net_open; + net->stop = net_stop; + net->tx_timeout = net_tx_timeout; + net->watchdog_timeo = TIMEOUT_JIFFIES; + + //printk(KERN_INFO"%s: finis\n", __FUNCTION__); + return priv; +} + +STATIC struct usb_device_id * +idp_search(int idVendor, int idProduct) +{ + struct usb_device_id *idp; + + //printk(KERN_INFO "%s: look for idVendor: %04x idProduct: %04x\n", __FUNCTION__, idVendor, idProduct); + + // search id_table for a match + for (idp = id_table;; idp++) { + + //printk(KERN_INFO "%s: looking at idVendor: %04x idProduct: %04x\n", __FUNCTION__, + // idp->idVendor, idp->idProduct); + + // end of table + BREAK_IF (!idp->idVendor && !idp->idProduct); + + // check for match + if ((idp->idVendor == idVendor) && (idp->idProduct == idProduct)) { + //printk(KERN_INFO "%s: MATCH\n", __FUNCTION__); + return idp; + } + } + return NULL; +} + + + +/* + * See if we have a CDC style communications interface: + * + * 1. Must have specified class, subclass and protocol + * 2. May have an optional INTERRUPT endpoint. + * + */ +STATIC int find_interface_comm( + struct private *priv, + struct usb_interface *comm_interface, + int class, int subclass, int protocol) +{ + int alternate_number; + struct usb_interface_descriptor *interface_descriptor; + + //printk(KERN_INFO"%s: class: %x subclass: %x protocol: %x\n", __FUNCTION__, class, subclass, protocol ); + + // iterate across interface alternate descriptors looking for suitable one + for (alternate_number = 0; alternate_number < comm_interface->num_altsetting; alternate_number++) { + +// interface_descriptor = comm_interface->altsetting + alternate_number; + interface_descriptor = USB_ALTSETTING(comm_interface,alternate_number); + priv->comm_ep_in = 0; + + //printk(KERN_INFO"%s: alt: %x class: %x subclass: %x protocol: %x\n", __FUNCTION__, + // alternate_number, + // interface_descriptor->bInterfaceClass, + // interface_descriptor->bInterfaceSubClass, + // interface_descriptor->bInterfaceProtocol); + + // check for class, sub-class and too many endpoints (zero or one ok) + CONTINUE_IF ((interface_descriptor->bInterfaceClass != class) || + (interface_descriptor->bInterfaceSubClass != subclass) || + (interface_descriptor->bInterfaceProtocol != protocol) || + (1 < interface_descriptor->bNumEndpoints) ); + + // if we have an endpoint check for validity + if (interface_descriptor->bNumEndpoints) { + //struct usb_endpoint_descriptor *endpoint = interface_descriptor->endpoint; + struct usb_endpoint_descriptor *endpoint = USB_IFC2EP(interface_descriptor,0); + CONTINUE_IF (!(endpoint->bEndpointAddress & USB_DIR_IN) || + (endpoint->bmAttributes != USB_ENDPOINT_XFER_INT)); + priv->comm_ep_in = endpoint->bEndpointAddress & 0x7f; + priv->comm_ep_in_size = endpoint->wMaxPacketSize; + //printk(KERN_INFO"%s: ep found %d\n", __FUNCTION__, priv->comm_ep_in); + } + + priv->comm_bInterfaceNumber = interface_descriptor->bInterfaceNumber; + priv->comm_bAlternateSetting = interface_descriptor->bAlternateSetting; + + //printk(KERN_INFO"%s: found %d %d\n", __FUNCTION__, + // priv->comm_bInterfaceNumber, priv->comm_bAlternateSetting); + return 0; + } + return -1; +} + + +/* + * See if we can have a CDC style data interface: + * + * 1. Must have specified class, subclass and protocol + * 2. Must have (only) a BULK-IN and BULK-OUT endpoint. + * 3. Optionally May have INTERRUPT endpoint. + * + */ +STATIC int find_interface_data( + struct private *priv, + struct usb_interface *data_interface, + int class, int subclass, int protocol, + int allow_interrupt) +{ + int alternate_number; + struct usb_interface_descriptor *interface_descriptor; + + //printk(KERN_INFO"%s: class: %x subclass: %x protocol: %x\n", __FUNCTION__, class, subclass, protocol ); + + for (alternate_number = 0; alternate_number < data_interface->num_altsetting; alternate_number++) { + + //interface_descriptor = data_interface->altsetting + alternate_number; + interface_descriptor = USB_ALTSETTING(data_interface,alternate_number); + priv->data_ep_in = priv->data_ep_out = 0; + + //printk(KERN_INFO"%s: alt: %d class: %x subclass: %x protocol: %x endpoints: %d\n", __FUNCTION__, + // alternate_number, + // interface_descriptor->bInterfaceClass, + // interface_descriptor->bInterfaceSubClass, + // interface_descriptor->bInterfaceProtocol, + // interface_descriptor->bNumEndpoints); + + // check for class, sub-class and wrong number of endpoints (must be two) + CONTINUE_IF ((interface_descriptor->bInterfaceClass != class) || + (interface_descriptor->bInterfaceSubClass != subclass) || + (interface_descriptor->bInterfaceProtocol != protocol) ); + + CONTINUE_IF (allow_interrupt && !((1 < interface_descriptor->bNumEndpoints) + && (3 >= interface_descriptor->bNumEndpoints))); + + CONTINUE_IF (!allow_interrupt && (2 != interface_descriptor->bNumEndpoints)); + + // check endpoints for validity, must be BULK and we want one in each direction, no more, no less + if (interface_descriptor->bNumEndpoints) { + int endpoint_number; + for (endpoint_number = 0; endpoint_number < interface_descriptor->bNumEndpoints; endpoint_number++) { + //struct usb_endpoint_descriptor *endpoint = interface_descriptor->endpoint + endpoint_number; + struct usb_endpoint_descriptor *endpoint = USB_IFC2EP(interface_descriptor,endpoint_number); + + BREAK_IF (USB_ENDPOINT_XFER_BULK != endpoint->bmAttributes); + + if (endpoint->bEndpointAddress & USB_DIR_IN) { + priv->data_ep_in = endpoint->bEndpointAddress & 0x7f; + priv->data_ep_in_size = endpoint->wMaxPacketSize; + } + else { + priv->data_ep_out = endpoint->bEndpointAddress & 0x7f; + priv->data_ep_out_size = endpoint->wMaxPacketSize; + } + } + } + + CONTINUE_IF (!priv->data_ep_in || !priv->data_ep_out); + + //printk(KERN_INFO"%s: ep found %d %d\n", __FUNCTION__, priv->data_ep_in, priv->data_ep_out); + + priv->data_bInterfaceNumber = interface_descriptor->bInterfaceNumber; + priv->data_bAlternateSetting = interface_descriptor->bAlternateSetting; + + //printk(KERN_INFO"%s: found %d %d\n", __FUNCTION__, + // priv->data_bInterfaceNumber, priv->data_bAlternateSetting); + return 0; + } + //printk(KERN_INFO"%s: not found\n", __FUNCTION__); + return -1; +} + +/* + * See if we can have a CDC style no-data interface: + * + * 1. Must have specified class, subclass and protocol + * 2. Must have a zero endpoints. + */ +STATIC int find_interface_nodata( + struct private *priv, + struct usb_interface *data_interface, + int class, int subclass, int protocol) +{ + int alternate_number; + struct usb_interface_descriptor *interface_descriptor; + + for (alternate_number = 0; alternate_number < data_interface->num_altsetting; alternate_number++) { + + //interface_descriptor = data_interface->altsetting + alternate_number; + interface_descriptor = USB_ALTSETTING(data_interface,alternate_number); + + //printk(KERN_INFO"%s: alt: %d class: %d subclass: %d endpoints: %d\n", __FUNCTION__, alternate_number, + // interface_descriptor->bInterfaceClass, + // interface_descriptor->bInterfaceSubClass, + // interface_descriptor->bNumEndpoints); + + // check for class, sub-class, protocol and verify no endpoints + CONTINUE_IF ((interface_descriptor->bInterfaceClass != class) || + (interface_descriptor->bInterfaceSubClass != subclass) || + (interface_descriptor->bInterfaceProtocol != protocol) || + (interface_descriptor->bNumEndpoints)); + + priv->nodata_bInterfaceNumber = interface_descriptor->bInterfaceNumber; + priv->nodata_bAlternateSetting = interface_descriptor->bAlternateSetting; + + //printk(KERN_INFO"%s: found %d %d\n", __FUNCTION__, + // priv->nodata_bInterfaceNumber, priv->nodata_bAlternateSetting); + + return 0; + } + //printk(KERN_INFO"%s: not found\n", __FUNCTION__); + return -1; +} + +/* + * See if we can have a MDLM style no-data interface: + * + * 1. Must have specified class, subclass and protocol + * 2. Must have have BULK-IN and BULK-OUT endpoints + * 3. May have INTERRUPT endpoint + * + */ +STATIC int find_interface_mdlm( + struct private *priv, + struct usb_interface *mdlm_interface, + int class, + int subclass, + int protocol, + char *guid + ) +{ + struct usb_interface_descriptor *interface_descriptor; + struct usb_class_descriptor *extra; + int extralen; + + if (mdlm_interface->num_altsetting > 1) { + printk(KERN_INFO"%s: too many intefaces num_altsetting: %d\n", __FUNCTION__, mdlm_interface->num_altsetting); + return -1; + } + + //interface_descriptor = mdlm_interface->altsetting; + interface_descriptor = USB_ALTSETTING(mdlm_interface,0); + + //printk(KERN_INFO"%s: class: %d subclass: %d endpoints: %d\n", __FUNCTION__, + // interface_descriptor->bInterfaceClass, + // interface_descriptor->bInterfaceSubClass, + // interface_descriptor->bNumEndpoints + // ); + + // check for class, sub-class, protocol and correct number of endpoints (two or three ok) + if ((interface_descriptor->bInterfaceClass != class) || + (interface_descriptor->bInterfaceSubClass != subclass) || + (interface_descriptor->bInterfaceProtocol != protocol) || + !((1 < interface_descriptor->bNumEndpoints) && (3 >= interface_descriptor->bNumEndpoints)) + ) + { + return -1; + } + + if (interface_descriptor->bNumEndpoints) { + int endpoint_number; + priv->comm_ep_in = priv->data_ep_in = priv->data_ep_out = 0; + for (endpoint_number = 0; endpoint_number < interface_descriptor->bNumEndpoints; endpoint_number++) { + //struct usb_endpoint_descriptor *endpoint = interface_descriptor->endpoint + endpoint_number; + struct usb_endpoint_descriptor *endpoint = USB_IFC2EP(interface_descriptor,endpoint_number); + + if (USB_ENDPOINT_XFER_BULK == endpoint->bmAttributes) { + if (endpoint->bEndpointAddress & USB_DIR_IN) { + priv->data_ep_in = endpoint->bEndpointAddress & 0x7f; + priv->data_ep_in_size = endpoint->wMaxPacketSize; + } + else { + priv->data_ep_out = endpoint->bEndpointAddress & 0x7f; + priv->data_ep_out_size = endpoint->wMaxPacketSize; + } + } + else if ((USB_ENDPOINT_XFER_INT == endpoint->bmAttributes) && + (endpoint->bEndpointAddress & USB_DIR_IN)) + { + priv->comm_ep_in = endpoint->bEndpointAddress & 0x7f; + priv->comm_ep_in_size = endpoint->wMaxPacketSize; + } + else { + return -1; + } + } + } + + if (!priv->data_ep_in || !priv->data_ep_out) { + return -1; + } + + extra = (struct usb_class_descriptor *)USB_IFC2HOST(interface_descriptor)->extra; + extralen = USB_IFC2HOST(interface_descriptor)->extralen; + while (extra && (extralen > 0)) { + + u8 *cp = (u8 *) extra; + + //printk(KERN_INFO"%s: %p extralen: %02x bLength: %02x bDescriptorType: %02x bDescriptorSubType: %02x\n", + // __FUNCTION__, extra, extralen, + // extra->bLength, extra->bDescriptorType, extra->bDescriptorSubType); + + BREAK_IF(!extra->bLength); + + // check for CS descriptors (0x24) + switch(extra->bDescriptorType) { + + + case USB_DT_CS_INTERFACE: + + //printk(KERN_INFO"%s: CS bDescriptorSubType: %02x\n", __FUNCTION__, extra->bDescriptorSubType); + + // then check for MDLM Functional or Detail descriptors + switch (extra->bDescriptorSubType) { + + // check for bGuid match + case MDLM_FUNCTIONAL: + { + struct usb_mdlm_functional_descriptor *functional = + (struct usb_mdlm_functional_descriptor *) extra; + //u8 *bGuid = (u8 *)&functional->bGuid; + //printk(KERN_INFO"%s: FUNCTIONAL bGUID %02x %02x %02x %02x %02x %02x %02x %02x\n", + // __FUNCTION__, + // bGuid[0],bGuid[1],bGuid[2],bGuid[3], + // bGuid[4],bGuid[5],bGuid[6],bGuid[7] + // ); + + //printk(KERN_INFO"%s: FUNCTIONAL bGUID %02x %02x %02x %02x %02x %02x %02x %02x\n", + // __FUNCTION__, + // bGuid[8],bGuid[9],bGuid[10],bGuid[11], + // bGuid[12],bGuid[13],bGuid[14],bGuid[15] + // ); + + RETURN_IF( -1, memcmp(guid, functional->bGuid, sizeof(functional->bGuid) != 0 )); + break; + } + + // find details + case MDLM_DETAIL: + { + struct usb_mdlm_detail_descriptor *mdlm = (struct usb_mdlm_detail_descriptor *) extra; + + //printk(KERN_INFO"%s: DETAIL bGuidDescriptorType\n", __FUNCTION__); + + // figure out what type of detail record it is + switch (mdlm->bGuidDescriptorType) { + case MDLM_SAFE_GUID: + { + struct usb_safe_detail_descriptor *safe = + (struct usb_safe_detail_descriptor *) extra; + //printk(KERN_INFO"%s: SAFE bmDataCapabilities: %02x\n", + // __FUNCTION__, safe->bmDataCapabilities); + priv->bmNetworkCapabilities = safe->bmNetworkCapabilities; + priv->bmDataCapabilities = safe->bmDataCapabilities; + break; + } + case MDLM_BLAN_GUID: + { + struct usb_blan_detail_descriptor *blan = + (struct usb_blan_detail_descriptor *) extra; + + //printk(KERN_INFO"%s: BLAN bmDataCapabilities: %02x\n", + // __FUNCTION__, blan->bmDataCapabilities); + priv->bmNetworkCapabilities = blan->bmNetworkCapabilities; + priv->bmDataCapabilities = blan->bmDataCapabilities; + priv->bPad = blan->bPad; + break; + } + default: + break; + } + } + + default: + break; + } + + default: + break; + } + + extralen -= extra->bLength; + cp = cp + extra->bLength; + extra = (struct usb_class_descriptor *) cp; + } + + priv->data_bInterfaceNumber = interface_descriptor->bInterfaceNumber; + priv->data_bAlternateSetting = interface_descriptor->bAlternateSetting; + + //printk(KERN_INFO"%s: found %d %d\n", __FUNCTION__, priv->data_bInterfaceNumber, priv->data_bAlternateSetting); + return 0; +} + +/* + * See if we can have a CDC interface: + * + * 1. class CDC_INTERFACE_CLASS + * 2. subclass CDC_INTERFACE_SUBCLASS + * 3. protocol DEFAULT_PROTOCOL + * + */ +STATIC int verify_ethernet_comm_interface( + struct usb_device *device, + struct private *priv, + int comm_number, struct usb_interface *comm_interface, + int data_number, struct usb_interface *data_interface + ) +{ + //printk(KERN_INFO"verify_ethernet_comm_interface:\n"); + + if (find_interface_comm(priv, comm_interface, CDC_INTERFACE_CLASS, CDC_INTERFACE_SUBCLASS, DEFAULT_PROTOCOL)) { + //printk(KERN_INFO"verify_ethernet_comm_interface: no comm\n"); + return -1; + } + + if (find_interface_data(priv, data_interface, DATA_INTERFACE_CLASS, 0, DEFAULT_PROTOCOL, 0)) { + //printk(KERN_INFO"verify_ethernet_comm_interface: no data\n"); + return -1; + } + + if (find_interface_nodata(priv, data_interface, DATA_INTERFACE_CLASS, 0, DEFAULT_PROTOCOL)) { + //printk(KERN_INFO"verify_ethernet_comm_interface: no nodata\n"); + } + + //printk(KERN_INFO"verify_ethernet_comm_interface: found\n"); + + priv->comm_interface = comm_number; + priv->data_interface = data_number; + priv->usbdnet_device_type = usbdnet_cdc; + + return 0; +} + +/* + * See if we can have an RNDIS interface: + * + * 1. class CDC_INTERFACE_CLASS + * 2. subclass RNDIS_INTERFACE_SUBCLASS + * 3. protocol VENDOR_SPECIFIC_PROTOCOL + * + */ +STATIC int verify_rndis_comm_interface( + struct usb_device *device, + struct private *priv, + int comm_number, struct usb_interface *comm_interface, + int data_number, struct usb_interface *data_interface + ) +{ + + //printk(KERN_INFO"verify_rndis_comm_interface:\n"); + + if (find_interface_comm(priv, comm_interface, CDC_INTERFACE_CLASS, RNDIS_INTERFACE_SUBCLASS, VENDOR_SPECIFIC_PROTOCOL)) { + //printk(KERN_INFO"verify_rndis_comm_interface: no comm\n"); + return -1; + } + + if (find_interface_data(priv, data_interface, DATA_INTERFACE_CLASS, 0, DEFAULT_PROTOCOL, 0)) { + //printk(KERN_INFO"verify_rndis_comm_interface: no data\n"); + return -1; + } + + //if (find_interface_nodata(priv, data_interface, DATA_INTERFACE_CLASS, 0, DEFAULT_PROTOCOL)) { + // printk(KERN_INFO"verify_rndis_comm_interface: no nodata\n"); + //} + + //printk(KERN_INFO"verify_rndis_comm_interface: found\n"); + + priv->comm_interface = comm_number; + priv->data_interface = data_number; + priv->usbdnet_device_type = usbdnet_rndis; + + return 0; +} + + +/* + * See if we can have an MDLM-BLAN interface: + * + * 1. class CDC_INTERFACE_CLASS + * 2. subclass MDLM_INTERFACE_SUBCLASS + * 3. protocol DEFAULT_PROTOCOL + * + */ +STATIC int verify_blan_interface( + struct usb_device *device, + struct private *priv, + struct usb_interface *data_interface + ) +{ + if (find_interface_mdlm(priv, data_interface, + CDC_INTERFACE_CLASS, + MDLM_INTERFACE_SUBCLASS, + DEFAULT_PROTOCOL, + BLAN_GUID)) + { + printk(KERN_INFO"%s: not found\n", __FUNCTION__); + return -1; + } + + //printk(KERN_INFO"%s: found bmDataCapabilities: %02x bPad %02x\n", + // __FUNCTION__, priv->bmDataCapabilities, priv->bPad); + + priv->comm_interface = priv->data_interface = 0; + priv->usbdnet_device_type = usbdnet_blan; + return 0; +} + +/* + * See if we can have an MDLM-SAFE interface: + * + * 1. class CDC_INTERFACE_CLASS + * 2. subclass MDLM_INTERFACE_SUBCLASS + * 3. protocol DEFAULT_PROTOCOL + * + */ +STATIC int verify_safe_interface( + struct usb_device *device, + struct private *priv, + struct usb_interface *data_interface + ) +{ + if (find_interface_mdlm(priv, data_interface, + CDC_INTERFACE_CLASS, + MDLM_INTERFACE_SUBCLASS, + DEFAULT_PROTOCOL, + SAFE_GUID)) + { + printk(KERN_INFO"%s: not found\n", __FUNCTION__); + return -1; + } + + //printk(KERN_INFO"%s: found bmDataCapabilities: %02x\n", __FUNCTION__, priv->bmDataCapabilities); + + priv->comm_interface = priv->data_interface = 0; + priv->usbdnet_device_type = usbdnet_safe; + return 0; +} + +/* + * See if we can have a BASIC interface: + * + * 1. class VENDOR_SPECIFIC_CLASS + * 2. subclass LINEO_INTERFACE_SUBCLASS_SAFENET + * 3. protocol LINEO_SAFENET_CRC + * + */ +STATIC int verify_basic_interface( + struct usb_device *device, + struct private *priv, + struct usb_interface *data_interface + ) +{ + if (find_interface_data(priv, data_interface, + VENDOR_SPECIFIC_CLASS, + LINEO_INTERFACE_SUBCLASS_SAFENET, + LINEO_SAFENET_CRC, + 1)) + { + printk(KERN_INFO"%s: not found\n", __FUNCTION__); + return -1; + } + + //printk(KERN_INFO"%s: found\n", __FUNCTION__); + + priv->comm_interface = priv->data_interface = 0; + priv->usbdnet_device_type = usbdnet_basic; + return 0; +} + + +/* + * Iterate across configurations looking for one that we like. + */ +STATIC int find_valid_configuration(struct usb_device *usbdev, struct private *priv) +{ + int configuration_number; + + // We will try each and every possible configuration + for ( configuration_number = 0; + configuration_number < usbdev->descriptor.bNumConfigurations; + configuration_number++ ) + { + + //struct usb_config_descriptor *configuration = usbdev->config + configuration_number; + struct usb_config_descriptor *configuration = USB_DEV2CONFIG(usbdev,configuration_number); + + //printk(KERN_INFO"%s[%d] bConfigurationValue: %d bNumInterfaces: %d\n", __FUNCTION__, + // configuration_number, configuration->bConfigurationValue, configuration->bNumInterfaces); + + priv->configuration_number = configuration_number; + priv->bConfigurationValue = configuration->bConfigurationValue; + + priv->comm_bInterfaceNumber = priv->comm_bAlternateSetting = -1; + priv->data_bInterfaceNumber = priv->data_bAlternateSetting = -1; + priv->nodata_bInterfaceNumber = priv->nodata_bAlternateSetting = -1; + + /* + * CDC and RNDIS devices have two interfaces, + * MDLM and simple devices have a single interface, + * we don't handle any devices with more or less than one or two intefaces. + */ + //printk(KERN_INFO"%s: interface(s) %d\n", __FUNCTION__, configuration->bNumInterfaces); + switch(configuration->bNumInterfaces) { + + /* + * Tests for devices with two interfaces, e.g. CDC and RNDIS. + * + * We cannot guarantee which order the comm and data interfaces will be so we have to check for + * comm/data or data/comm. + */ + case 2: { + //struct usb_interface *int0 = configuration->interface + 0; + //struct usb_interface *int1 = configuration->interface + 1; + struct usb_interface *int0 = USB_CONFIG2IFC(configuration,0); + struct usb_interface *int1 = USB_CONFIG2IFC(configuration,1); + + if ( !verify_ethernet_comm_interface(usbdev, priv, 0, int0, 1, int1)) { + //printk(KERN_INFO"%s: cdc 0 1\n", __FUNCTION__); + return 0; + } + else if ( !verify_ethernet_comm_interface(usbdev, priv, 1, int1, 0, int0)) { + //printk(KERN_INFO"%s: cdc 1 0\n", __FUNCTION__); + return 0; + } + else if ( !verify_rndis_comm_interface(usbdev, priv, 0, int0, 1, int1)) { + //printk(KERN_INFO"%s: rndis 0 1\n", __FUNCTION__); + return 0; + } + else if ( !verify_rndis_comm_interface(usbdev, priv, 1, int1, 0, int0)) { + //printk(KERN_INFO"%s: rndis 1 0\n", __FUNCTION__); + return 0; + } + } + break; + + /* + * tests for devices with a single interface, e.g. MDLM-SAFE MDLM-BLAN or BASIC + */ + case 1: + + if ( !verify_blan_interface(usbdev, priv, USB_CONFIG2IFC(configuration,0))) { + //printk(KERN_INFO"%s[%d] bConfigurationValue: %d bNumInterfaces: %d\n", __FUNCTION__, + // configuration_number, priv->bConfigurationValue, configuration->bNumInterfaces); + //printk(KERN_INFO"%s: mdlm-blan\n", __FUNCTION__); + return 0; + } + else if ( !verify_safe_interface(usbdev, priv, USB_CONFIG2IFC(configuration,0))) { + //printk(KERN_INFO"%s[%d] bConfigurationValue: %d bNumInterfaces: %d\n", __FUNCTION__, + // configuration_number, priv->bConfigurationValue, configuration->bNumInterfaces); + //printk(KERN_INFO"%s: mdlm-safe\n", __FUNCTION__); + return 0; + } + else if ( !verify_basic_interface(usbdev, priv, USB_CONFIG2IFC(configuration,0))) { + //printk(KERN_INFO"%s: basic\n", __FUNCTION__); + return 0; + } + break; + + /* + * currently we have no support for devices with zero or more than + * two interfaces.... + */ + default: + break; + } + priv->configuration_number = priv->bConfigurationValue = 0; + } + + // None of the configurations suited us. + //printk(KERN_INFO"%s: nothing found\n", __FUNCTION__); + return -1; +} + +/* + * check the active configuration to see if there are any claimed interfaces + */ +STATIC int verify_no_claimed_interfaces(struct usb_config_descriptor *act_config) +{ + struct usb_interface *interface; + int interface_number; + + // Go through all the interfaces and make sure none are + // claimed by anybody else. + // + + //printk(KERN_INFO"%s:\n", __FUNCTION__); + if (!act_config) { + printk(KERN_INFO"%s: NULL\n", __FUNCTION__); + return 0; + } + + //printk(KERN_INFO"%s: bNumInterfaces: %d\n", __FUNCTION__, act_config->bNumInterfaces); + for (interface_number = 0; interface_number < act_config->bNumInterfaces; interface_number++) { + + + interface = USB_CONFIG2IFC(act_config,interface_number); + +#if 0 + if (usb_interface_claimed(interface)) { + printk(KERN_INFO"%s: failed\n", __FUNCTION__); + return -1; + } +#endif + } + //printk(KERN_INFO"%s: ok\n", __FUNCTION__); + return 0; +} + +#if defined(CONFIG_USB_USBLAN) || defined(CONFIG_USB_USBLAN_MODULE) +/******************************************************** + * In the 2.6 host system, the result of "probe" is a + * ERROR value, (0 for success, negative for failure) + * The actual value of priv is not passed to the caller + * of probe (usb_probe_interface) + *****************************************************/ +STATIC int usblan_probe_return(struct private *priv){ + return ((priv == 0) ? -1 : 0); +} + +#else + +#error +/******************************************************** + * In the 2.4 host system, the probe routine is called + * by usb_find_interface_driver. If probe() returns a + * non zero pointer, that value is passed along to + * usb_claim_interface() + *******************************************************/ +STATIC void * usblan_probe_return(struct private *priv){ + return priv; +} + +#error + +#endif + +#if defined(CONFIG_USB_USBLAN) || defined(CONFIG_USB_USBLAN_MODULE) +STATIC int usblan_probe(struct usb_interface * udev, const struct usb_device_id *id){ + struct private *priv = NULL; + struct usb_device_descriptor *device = NULL; + struct usb_device *usbdev; + + /* find the usb device for the usb_interface */ + usbdev = interface_to_usbdev(udev); + /* find the usb device descriptor */ + device = &usbdev->descriptor; + //We can now resume the old probe routine except when returning a value +#else +#error +STATIC void * +usblan_probe(struct usb_device *usbdev, unsigned int ifnum, const struct usb_device_id *id) +{ + struct private *priv = NULL; + struct usb_device_descriptor *device = &usbdev->descriptor; + +#endif + + /* Begin the common portion of the probe routine */ + //printk(KERN_INFO "%s: probe\n", __FUNCTION__); + + /* do we have a valid device structure, is the device a CDC device, is the product_id and + * vendor_id one that we are supposed to handle, has anyone else claimed any interfaces? + */ + THROW_IF (device->bDeviceClass != CDC_DEVICE_CLASS, error); + THROW_IF (!idp_search(device->idVendor, device->idProduct), error); +#if defined(LINUX24) + THROW_IF (verify_no_claimed_interfaces(USB_CONFIG2DESC(usbdev->actconfig)), error); +#endif + + /* create a private structure to save state information about this usb device. + */ + THROW_IF (!(priv = create_private(usbdev->devnum)), error); + THROW_IF (find_valid_configuration(usbdev, priv), error); + + /* + * There is a valid configuration. + */ + //printk(KERN_INFO"%s: configuration_number: %d bConfigurationValue: %d\n", __FUNCTION__, + // priv->configuration_number, priv->bConfigurationValue); + + THROW_IF (register_netdev(&priv->net), free_priv); + + +#if !defined(CONFIG_USB_USBLAN) && !defined(CONFIG_USB_USBLAN_MODULE) + // usb_set_config() is not available inside probe() for 2.6. + //printk(KERN_INFO"%s: setting configuration bConfigurationValue: %d\n", __FUNCTION__, priv->bConfigurationValue); + + THROW_IF ( usb_set_configuration( usbdev, priv->bConfigurationValue ), unregister ); +#else + // Claim the interfaces we want before starting to operate on them... + // manually claim the interfaces we want. + switch (priv->usbdnet_device_type ) { + case usbdnet_cdc: + case usbdnet_rndis: + //printk(KERN_INFO"%s: claiming comm interface: configuration: %d interface: %d\n", __FUNCTION__, + // priv->configuration_number, priv->comm_interface); + + usb_driver_claim_interface( &usblan_driver, + USB_CONFIG2IFC(USB_DEV2CONFIG(usbdev,priv->configuration_number),priv->comm_interface), priv); + priv->intf_count += 1; + + /* FALL THROUGH */ // In order to claim the data interface too. + case usbdnet_safe: + case usbdnet_basic: + case usbdnet_blan: + //printk(KERN_INFO"%s: claiming data interface: configuration: %d interface: %d\n", __FUNCTION__, + // priv->configuration_number, priv->data_interface); + + usb_driver_claim_interface( &usblan_driver, + USB_CONFIG2IFC(USB_DEV2CONFIG(usbdev,priv->configuration_number),priv->data_interface), + priv ); + priv->intf_count += 1; + + break; + case usbdnet_unknown: + THROW(unregister); + } + priv->intf_max = priv->intf_count; +#endif + + //printk(KERN_INFO"%s: %s\n", __FUNCTION__, usbdnet_device_names[priv->usbdnet_device_type]); + + /* + * If we have a nodata interface interface use it, otherwise use the normal data interface. + * MDLM and Safe devices never have a nodata interface. + */ + switch (priv->usbdnet_device_type ) { + case usbdnet_cdc: + case usbdnet_rndis: + + //printk(KERN_INFO"%s: setting comm interface bInterfaceNumber: %d bAlternateSetting: %d\n", __FUNCTION__, + // priv->comm_bInterfaceNumber, priv->comm_bAlternateSetting); + THROW_IF (usb_set_interface(usbdev, priv->comm_bInterfaceNumber, priv->comm_bAlternateSetting), unregister); + + if (priv->nodata_bInterfaceNumber >= 0) { + //printk(KERN_INFO"%s: setting nodata interface bInterfaceNumber: %d bAlternateSetting: %d\n", + // __FUNCTION__, priv->nodata_bInterfaceNumber, priv->nodata_bAlternateSetting); + THROW_IF (usb_set_interface( usbdev, priv->nodata_bInterfaceNumber, priv->nodata_bAlternateSetting), + unregister); + } + else { + /* FALL THROUGH */ + + //printk(KERN_INFO"%s: setting data interface bInterfaceNumber: %d bAlternateSetting: %d\n", + // __FUNCTION__, priv->data_bInterfaceNumber, priv->data_bAlternateSetting); + THROW_IF (usb_set_interface( usbdev, priv->data_bInterfaceNumber, priv->data_bAlternateSetting), + unregister); + } + break; + + case usbdnet_safe: + case usbdnet_blan: + case usbdnet_basic: + + break; + + case usbdnet_unknown: + THROW(unregister); + } + + +#if !defined(CONFIG_USB_USBLAN) && !defined(CONFIG_USB_USBLAN_MODULE) + // manually claim the interfaces we want. + switch (priv->usbdnet_device_type ) { + case usbdnet_cdc: + case usbdnet_rndis: + //printk(KERN_INFO"%s: claiming comm interface: configuration: %d interface: %d\n", __FUNCTION__, + // priv->configuration_number, priv->comm_interface); + + usb_driver_claim_interface( &usblan_driver, + USB_CONFIG2IFC(USB_DEV2CONFIG(usbdev,priv->configuration_number),priv->comm_interface), priv); + //&(usbdev->config[priv->configuration_number].interface[priv->comm_interface]), priv ); + priv->intf_count += 1; + + /* FALL THROUGH */ // In order to claim the data interface too. + case usbdnet_safe: + case usbdnet_basic: + case usbdnet_blan: + //printk(KERN_INFO"%s: claiming data interface: configuration: %d interface: %d\n", __FUNCTION__, + // priv->configuration_number, priv->data_interface); + + usb_driver_claim_interface( &usblan_driver, + //&(usbdev->config[priv->configuration_number].interface[priv->data_interface]), + USB_CONFIG2IFC(USB_DEV2CONFIG(usbdev,priv->configuration_number),priv->data_interface), + priv ); + priv->intf_count += 1; + + break; + case usbdnet_unknown: + THROW(unregister); + } + priv->intf_max = priv->intf_count; +#endif + + + skb_queue_head_init(&priv->rxq); + skb_queue_head_init(&priv->txq); + skb_queue_head_init(&priv->unlink); + skb_queue_head_init(&priv->done); + + //printk(KERN_INFO "%s: tx_size: %3d rx_size: %3d\n", __FUNCTION__, priv->data_ep_out_size, priv->data_ep_in_size); + //printk(KERN_INFO "%s: tx_ep : %3x rx_ep : %3x\n", __FUNCTION__, priv->data_ep_out, priv->data_ep_in); + + priv->usbdev = usbdev; + +#if defined(LINUX24) + // ctrl task for clear halt operation + priv->ctrl_task.routine = ctrl_task; + priv->ctrl_task.data = priv; + priv->ctrl_task.sync = 0; +#else + PREPARE_WORK_ITEM(priv->ctrl_task, ctrl_task, priv); +#endif + + // reset task for reseting device +#if defined(LINUX26) + PREPARE_WORK_ITEM(priv->reset_task, reset_task, priv); +#else + priv->reset_task.routine = reset_task; + priv->reset_task.data = priv; + priv->reset_task.sync = 0; +#endif + + // unlink task for reseting device +#if defined(LINUX26) + PREPARE_WORK_ITEM(priv->unlink_task, unlink_task, priv); +#else + priv->unlink_task.routine = unlink_task; + priv->unlink_task.data = priv; + priv->unlink_task.sync = 0; +#endif + + // bottom half processing tasklet + priv->bh.func = bh; + priv->bh.data = (unsigned long) priv; + + // init mutex and list, add to device list + init_MUTEX(&priv->mutex); + INIT_LIST_HEAD(&priv->list); + + mutex_lock(&usbd_mutex); + list_add(&priv->list, &usbd_list); + mutex_unlock(&usbd_mutex); + + //printk(KERN_INFO "%s: success %s\n", __FUNCTION__, DRIVER_VERSION); + + switch (priv->usbdnet_device_type ) { + case usbdnet_blan: + network_notify(priv, NETWORK_ADDR_HOST, NETWORK_MASK, NETWORK_ADDR_CLIENT); + network_attach(&priv->net, NETWORK_ADDR_HOST, NETWORK_MASK, 1); + break; + default: + break; + } + + + // start as if the link is up + netif_device_attach(&priv->net); + +//#define NETWORK_ADDR_HOST 0xac100005 /* 172.16.0.0 */ +//#define NETWORK_ADDR_CLIENT 0xac100006 /* 172.16.0.0 */ +//#define NETWORK_MASK 0xfffffffc + +#if defined(CONFIG_USB_USBLAN) || defined(CONFIG_USB_USBLAN_MODULE) + usb_get_dev(usbdev); +#else + usb_inc_dev_use(usbdev); +#endif + + usb_set_intfdata(udev, priv); + + CATCH(error) { + printk(KERN_ERR"%s: caught error\n", __FUNCTION__); + + CATCH(free_priv) { + + printk(KERN_ERR"%s: caught free_priv\n", __FUNCTION__); + + CATCH(unregister) { + + printk(KERN_ERR"%s: caught unregister\n", __FUNCTION__); + + printk(KERN_ERR"%s: unregister\n", __FUNCTION__); + unregister_netdev(&priv->net); + } + + printk(KERN_ERR"%s: free priv\n", __FUNCTION__); + kfree(priv); + } + printk(KERN_ERR"%s: return NULL\n", __FUNCTION__); + return usblan_probe_return(NULL); + } + //printk(KERN_ERR"%s: return %p\n", __FUNCTION__, priv); + return usblan_probe_return(priv); +} + +#if defined(CONFIG_USB_USBLAN) || defined(CONFIG_USB_USBLAN_MODULE) +STATIC void usblan_disconnect(struct usb_interface *intf) +{ + struct private *priv = (struct private *) usb_get_intfdata(intf); + struct usb_device *usbdev = interface_to_usbdev(intf); + + /* If we claimed more than one interface during probe, this will + be called more than once. */ + if (priv->intf_count == priv->intf_max) { + // This is the first interface to be disconnected, unregister the network interface + unregister_netdev(&priv->net); + + // remove from device list + mutex_lock(&usbd_mutex); + mutex_lock(&priv->mutex); + list_del(&priv->list); + mutex_unlock(&usbd_mutex); + } + // release interfaces + + //printk(KERN_INFO"%s: releasing interface: %p from configuration: %d\n", __FUNCTION__, intf, priv->configuration_number); + // XXX usb_driver_release_interface(&usblan_driver,intf); + //printk(KERN_INFO"%s: releasing interface: ok\n", __FUNCTION__); + priv->intf_count -= 1; + + if (priv->intf_count <= 0) { + // disable + usb_put_dev(usbdev); + + // destroy the network interface and free the private storage + kfree(priv); + } +} +#else +STATIC void usblan_disconnect(struct usb_device *usbdev, void *ptr) { + struct private *priv = (struct private *) ptr; + + // unregister the network interface + unregister_netdev(&priv->net); + + // remove from device list + mutex_lock(&usbd_mutex); + mutex_lock(&priv->mutex); + list_del(&priv->list); + mutex_unlock(&usbd_mutex); + + // release interfaces + + + switch (priv->usbdnet_device_type ) { + case usbdnet_cdc: + case usbdnet_rndis: + //printk(KERN_INFO"%s: releasing comm interface: configuration: %d interface: %d\n", __FUNCTION__, + // priv->configuration_number, priv->comm_interface); + usb_driver_release_interface( &usblan_driver, + USB_CONFIG2IFC(USB_DEV2CONFIG(usbdev,priv->configuration_number),priv->comm_interface)); +// &(usbdev->config[priv->configuration_number].interface[priv->comm_interface])); + /* FALL THROUGH */ + case usbdnet_safe: + case usbdnet_basic: + case usbdnet_blan: + //printk(KERN_INFO"%s: releasing data interface: configuration: %d interface: %d\n", __FUNCTION__, + // priv->configuration_number, priv->data_interface); + usb_driver_release_interface( &usblan_driver, + USB_CONFIG2IFC(USB_DEV2CONFIG(usbdev,priv->configuration_number),priv->data_interface)); +// &(usbdev->config[priv->configuration_number].interface[priv->data_interface])); + case usbdnet_unknown: + break; + } + + + // disable + usb_dec_dev_use(usbdev); // QQSV + + // destroy the network interface and free the private storage + kfree(priv); +} +#endif + + +static struct usb_driver usblan_driver = { + name: "usblan", + probe: usblan_probe, + disconnect: usblan_disconnect, // QQSV + id_table: id_table, +}; + + +/* Module loading functions - modinit and modexit*********************************************** */ + +/* + * usbdnet_modinit - module init + * + */ +STATIC int __init usbdnet_modinit(void) +{ + //USB_PROBE_SET(usblan_driver, probe); + //printk(KERN_INFO DRIVER_VERSION " " DRIVER_AUTHOR "XXX\n"); + + + info(DRIVER_DESC " " DRIVER_VERSION " " DRIVER_AUTHOR); + //info(DRIVER_DESC); + + get_random_bytes(default_addr, ETH_ALEN); + default_addr[0] = (default_addr[0] & 0xf0) | 0x02; + +#if defined(LONG_STRING_OF_ZEROES_HACK) + fermat_init(); +#endif + + // if we have vendor_id / product_id parameters patch them into id list + if (vendor_id && product_id) { + int i; + //printk(KERN_INFO "%s: vendor_id: %x product_id: %x\n", __FUNCTION__, vendor_id, product_id); + for (i = 0; i < (sizeof(id_table) / sizeof(struct usb_device_id) - 1); i++) { + + if (id_table[i].idVendor == vendor_id && id_table[i].idProduct == product_id) { + + //printk(KERN_INFO "%s: vendor_id: %x product_id: %x already in table\n", + // __FUNCTION__, vendor_id, product_id); + break; + } + if (!id_table[i].idVendor && !id_table[i].idProduct) { + //printk(KERN_INFO "%s: vendor_id: %x product_id: %x inserted into table\n", + // __FUNCTION__, vendor_id, product_id); + id_table[i].match_flags = USB_DEVICE_ID_MATCH_DEVICE; + id_table[i].idVendor = vendor_id; + id_table[i].idProduct = product_id; + id_table[i].bDeviceClass = class; + id_table[i].bDeviceSubClass = subclass; + break; + } + } + } + + // register us with the usb core layer + if (usb_register(&usblan_driver)) { + return -EINVAL; + } + + return 0; +} + + +/* + * function_exit - module cleanup + * + */ +STATIC void __exit usbdnet_modexit(void) { + // de-register from the usb core layer + usb_deregister(&usblan_driver); +} + +module_init(usbdnet_modinit); +module_exit(usbdnet_modexit); diff --git a/drivers/usb/usblan/usblan.h b/drivers/usb/usblan/usblan.h new file mode 100644 index 000000000000..0e92f3df2c6c --- /dev/null +++ b/drivers/usb/usblan/usblan.h @@ -0,0 +1,84 @@ +#ifndef _USB_NET_USBLAN_H +#define _USB_NET_USBLAN_H 1 + +#define USB_NEW_COMPLETE_T +//#define USB_ALLOC_URB(packets) usb_alloc_urb(packets, GFP_KERNEL) +#define USB_ALLOC_URB(packets,mem_flags) usb_alloc_urb(packets,mem_flags) + +#define FILL_BULK_URB(URB,DEV,PIPE,TRANSFER_BUFFER,BUFFER_LENGTH,COMPLETE,CONTEXT) \ + usb_fill_bulk_urb(URB,DEV,PIPE,TRANSFER_BUFFER,BUFFER_LENGTH,(usb_complete_t)COMPLETE,CONTEXT) + +#define FILL_CONTROL_URB(URB,DEV,PIPE,SETUP,TRANSFER_BUFFER,BUFFER_LENGTH,COMPLETE,CONTEXT) \ + usb_fill_control_urb(URB,DEV,PIPE,SETUP,TRANSFER_BUFFER,BUFFER_LENGTH,(usb_complete_t)COMPLETE,CONTEXT) + +#define USB_SUBMIT_URB(urb) usb_submit_urb(urb, 0) +#define USB_ALTSETTING(ifc,alt_index) &(ifc->altsetting[alt_index].desc) +#define USB_IFC2EP(ifc,index) &(container_of(ifc, struct usb_host_interface, desc)->endpoint[index].desc) +#define USB_IFC2HOST(ifc) container_of(ifc, struct usb_host_interface, desc) +#define USB_DEV2CONFIG(dev,index) &(dev->config[index].desc) +#define USB_CONFIG2IFC(cfg, index) (container_of(cfg, struct usb_host_config, desc)->interface[index]) +#define USB_CONFIG2DESC(cfg) &(cfg->desc) +#define USB_PROBE_DELAYED_SET NULL +#define USB_PROBE_ERROR -1 +#define USB_PROBE_SET(driver,proc) { \ + int __usb_probe_proc(struct usb_interface *intf, \ + const struct usb_device_id *id){\ + struct usb_device *usbdev = NULL;\ + if(!intf) { return USB_PROBE_ERROR; }\ + usbdev = interface_to_usbdev(intf); \ + return(!((*proc)(usbdev,0,id)) ? USB_PROBE_ERROR :0);\ + }\ + driver->probe = __usb_probe_proc; \ +} + + + +//Missing (obsolete??) XXX +//static void usb_inc_dev_use(struct usb_device * dev) {} +#define usb_inc_dev_use(dev) usb_get_dev(dev) +#define usb_dec_dev_use(dev) usb_put_dev(dev) +/*----------------------------------------------------------------------------* + * New USB Structures * + *----------------------------------------------------------------------------*/ + +/* + * urb->transfer_flags: + */ +#define USB_DISABLE_SPD 0x0001 +//#define URB_SHORT_NOT_OK USB_DISABLE_SPD +#define USB_ISO_ASAP 0x0002 +#define USB_ASYNC_UNLINK 0x0008 +#define USB_QUEUE_BULK 0x0010 +#define USB_NO_FSBR 0x0020 +#define USB_ZERO_PACKET 0x0040 // Finish bulk OUTs always with zero length packet +#define URB_NO_INTERRUPT 0x0080 /* HINT: no non-error interrupt needed */ + /* ... less overhead for QUEUE_BULK */ +#define USB_TIMEOUT_KILLED 0x1000 // only set by HCD! + +/* + * USB-status codes: + * USB_ST* maps to -E* and should go away in the future + */ + +#define USB_ST_NOERROR 0 +#define USB_ST_CRC (-EILSEQ) +#define USB_ST_BITSTUFF (-EPROTO) +#define USB_ST_NORESPONSE (-ETIMEDOUT) /* device not responding/handshaking */ +#define USB_ST_DATAOVERRUN (-EOVERFLOW) +#define USB_ST_DATAUNDERRUN (-EREMOTEIO) +#define USB_ST_BUFFEROVERRUN (-ECOMM) +#define USB_ST_BUFFERUNDERRUN (-ENOSR) +#define USB_ST_INTERNALERROR (-EPROTO) /* unknown error */ +#define USB_ST_SHORT_PACKET (-EREMOTEIO) +#define USB_ST_PARTIAL_ERROR (-EXDEV) /* ISO transfer only partially completed */ +#define USB_ST_URB_KILLED (-ENOENT) /* URB canceled by user */ +#define USB_ST_URB_PENDING (-EINPROGRESS) +#define USB_ST_REMOVED (-ENODEV) /* device not existing or removed */ +#define USB_ST_TIMEOUT (-ETIMEDOUT) /* communication timed out, also in urb->status**/ +#define USB_ST_NOTSUPPORTED (-ENOSYS) +#define USB_ST_BANDWIDTH_ERROR (-ENOSPC) /* too much bandwidth used */ +#define USB_ST_URB_INVALID_ERROR (-EINVAL) /* invalid value/transfer type */ +#define USB_ST_URB_REQUEST_ERROR (-ENXIO) /* invalid endpoint */ +#define USB_ST_STALL (-EPIPE) /* pipe stalled, also in urb->status*/ + +#endif |