diff options
Diffstat (limited to 'drivers/usb')
-rw-r--r-- | drivers/usb/gadget/android.c | 4 | ||||
-rw-r--r-- | drivers/usb/gadget/f_rndis.c | 21 | ||||
-rw-r--r-- | drivers/usb/gadget/tegra_udc.c | 19 | ||||
-rw-r--r-- | drivers/usb/gadget/u_ether.h | 23 | ||||
-rw-r--r-- | drivers/usb/host/ehci-tegra.c | 115 | ||||
-rw-r--r-- | drivers/usb/otg/Kconfig | 8 | ||||
-rw-r--r-- | drivers/usb/otg/Makefile | 1 | ||||
-rw-r--r-- | drivers/usb/otg/colibri-otg.c | 268 |
8 files changed, 437 insertions, 22 deletions
diff --git a/drivers/usb/gadget/android.c b/drivers/usb/gadget/android.c index 7b3185ff188a..9f79c267890e 100644 --- a/drivers/usb/gadget/android.c +++ b/drivers/usb/gadget/android.c @@ -399,8 +399,8 @@ static int rndis_function_bind_config(struct android_usb_function *f, rndis_control_intf.bInterfaceProtocol = 0x03; } - return rndis_bind_config(c, rndis->ethaddr, rndis->vendorID, - rndis->manufacturer); + return rndis_bind_config_vendor(c, rndis->ethaddr, rndis->vendorID, + rndis->manufacturer); } static void rndis_function_unbind_config(struct android_usb_function *f, diff --git a/drivers/usb/gadget/f_rndis.c b/drivers/usb/gadget/f_rndis.c index f187b9eee9e5..25cb5fc8b263 100644 --- a/drivers/usb/gadget/f_rndis.c +++ b/drivers/usb/gadget/f_rndis.c @@ -774,9 +774,10 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f) rndis_set_param_medium(rndis->config, NDIS_MEDIUM_802_3, 0); rndis_set_host_mac(rndis->config, rndis->ethaddr); - if (rndis_set_param_vendor(rndis->config, rndis->vendorID, - rndis->manufacturer)) - goto fail; + if (rndis->manufacturer && rndis->vendorID && + rndis_set_param_vendor(rndis->config, rndis->vendorID, + rndis->manufacturer)) + goto fail; /* NOTE: all that is done without knowing or caring about * the network link ... which is unavailable to this code @@ -844,20 +845,8 @@ static inline bool can_support_rndis(struct usb_configuration *c) return true; } -/** - * rndis_bind_config - add RNDIS network link to a configuration - * @c: the configuration to support the network link - * @ethaddr: a buffer in which the ethernet address of the host side - * side of the link was recorded - * Context: single threaded during gadget setup - * - * Returns zero on success, else negative errno. - * - * Caller must have called @gether_setup(). Caller is also responsible - * for calling @gether_cleanup() before module unload. - */ int -rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], +rndis_bind_config_vendor(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], u32 vendorID, const char *manufacturer) { struct f_rndis *rndis; diff --git a/drivers/usb/gadget/tegra_udc.c b/drivers/usb/gadget/tegra_udc.c index c130dae3a8e9..074a9b1ce6d9 100644 --- a/drivers/usb/gadget/tegra_udc.c +++ b/drivers/usb/gadget/tegra_udc.c @@ -52,8 +52,8 @@ #include "tegra_udc.h" -#define DRIVER_DESC "Nvidia Tegra High-Speed USB SOC \ - Device Controller driver" +#define DRIVER_DESC "Nvidia Tegra High-Speed USB SOC " \ + "Device Controller driver" #define DRIVER_AUTHOR "Venkat Moganty/Rakesh Bodla" #define DRIVER_VERSION "Apr 30, 2012" @@ -95,6 +95,11 @@ static const u8 tegra_udc_test_packet[53] = { 0xfc, 0x7e, 0xbf, 0xdf, 0xef, 0xf7, 0xfb, 0xfd, 0x7e }; +#ifdef CONFIG_MACH_COLIBRI_T20 +/* To limit the speed of USB to full speed */ +extern int g_usb_high_speed; +#endif /* CONFIG_MACH_COLIBRI_T20 */ + static struct tegra_udc *the_udc; #ifdef CONFIG_TEGRA_GADGET_BOOST_CPU_FREQ @@ -253,6 +258,16 @@ static int dr_controller_reset(struct tegra_udc *udc) cpu_relax(); } +#ifdef CONFIG_MACH_COLIBRI_T20 + /* To limit the speed of USB to full speed */ + if (!g_usb_high_speed) { + tmp = udc_readl(udc, PORTSCX_REG_OFFSET); + tmp |= PORTSCX_PORT_FORCE_FULL_SPEED; + udc_writel(udc, tmp, PORTSCX_REG_OFFSET); + tmp = udc_readl(udc, PORTSCX_REG_OFFSET); + } +#endif /* CONFIG_MACH_COLIBRI_T20 */ + DBG("%s(%d) END\n", __func__, __LINE__); return 0; } diff --git a/drivers/usb/gadget/u_ether.h b/drivers/usb/gadget/u_ether.h index 46772413f0db..c32227ebac0b 100644 --- a/drivers/usb/gadget/u_ether.h +++ b/drivers/usb/gadget/u_ether.h @@ -111,13 +111,13 @@ int eem_bind_config(struct usb_configuration *c); #ifdef USB_ETH_RNDIS -int rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], +int rndis_bind_config_vendor(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], u32 vendorID, const char *manufacturer); #else static inline int -rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], +rndis_bind_config_vendor(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], u32 vendorID, const char *manufacturer) { return 0; @@ -125,4 +125,23 @@ rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], #endif +/** + * rndis_bind_config - add RNDIS network link to a configuration + * @c: the configuration to support the network link + * @ethaddr: a buffer in which the ethernet address of the host side + * side of the link was recorded + * Context: single threaded during gadget setup + * + * Returns zero on success, else negative errno. + * + * Caller must have called @gether_setup(). Caller is also responsible + * for calling @gether_cleanup() before module unload. + */ +static inline int rndis_bind_config(struct usb_configuration *c, + u8 ethaddr[ETH_ALEN]) +{ + return rndis_bind_config_vendor(c, ethaddr, 0, NULL); +} + + #endif /* __U_ETHER_H */ diff --git a/drivers/usb/host/ehci-tegra.c b/drivers/usb/host/ehci-tegra.c index 4ddb279cfb35..095447d9e3a2 100644 --- a/drivers/usb/host/ehci-tegra.c +++ b/drivers/usb/host/ehci-tegra.c @@ -23,12 +23,16 @@ #include <mach/usb_phy.h> #include <mach/iomap.h> +#include "../../../arch/arm/mach-tegra/tegra_usb_phy.h" + #if 0 #define EHCI_DBG(stuff...) pr_info("ehci-tegra: " stuff) #else #define EHCI_DBG(stuff...) do {} while (0) #endif +#define TEGRA_USB_PORTSC1_PFSC (1 << 24) + static const char driver_name[] = "tegra-ehci"; #define TEGRA_USB_DMA_ALIGN 32 @@ -51,6 +55,22 @@ struct dma_align_buffer { u8 data[0]; }; +#ifdef CONFIG_MACH_COLIBRI_T20 +/* To limit the speed of USB to full speed */ +int g_usb_high_speed = 0; + +/* To limit the speed of USB to full speed */ +static int __init enable_usb_high_speed(char *s) +{ + if (!(*s) || !strcmp(s, "1")) + g_usb_high_speed = 1; + + return 0; +} +__setup("usb_high_speed=", enable_usb_high_speed); +EXPORT_SYMBOL_GPL(g_usb_high_speed); +#endif /* CONFIG_MACH_COLIBRI_T20 */ + static void free_align_buffer(struct urb *urb) { struct dma_align_buffer *temp = container_of(urb->transfer_buffer, @@ -186,6 +206,71 @@ static irqreturn_t tegra_ehci_irq(struct usb_hcd *hcd) return irq_status; } +static int tegra_ehci_internal_port_reset( + struct ehci_hcd *ehci, + u32 __iomem *portsc_reg +) +{ + u32 temp; + unsigned long flags; + int retval = 0; + int i, tries; + u32 saved_usbintr; + + spin_lock_irqsave(&ehci->lock, flags); + saved_usbintr = ehci_readl(ehci, &ehci->regs->intr_enable); + /* disable USB interrupt */ + ehci_writel(ehci, 0, &ehci->regs->intr_enable); + spin_unlock_irqrestore(&ehci->lock, flags); + + /* + * Here we have to do Port Reset at most twice for + * Port Enable bit to be set. + */ + for (i = 0; i < 2; i++) { + temp = ehci_readl(ehci, portsc_reg); + temp |= PORT_RESET; + ehci_writel(ehci, temp, portsc_reg); + mdelay(10); + temp &= ~PORT_RESET; + ehci_writel(ehci, temp, portsc_reg); + mdelay(1); + tries = 100; + do { + mdelay(1); + /* + * Up to this point, Port Enable bit is + * expected to be set after 2 ms waiting. + * USB1 usually takes extra 45 ms, for safety, + * we take 100 ms as timeout. + */ + temp = ehci_readl(ehci, portsc_reg); + } while (!(temp & PORT_PE) && tries--); + if (temp & PORT_PE) + break; + } + if (i == 2) + retval = -ETIMEDOUT; + + /* + * Clear Connect Status Change bit if it's set. + * We can't clear PORT_PEC. It will also cause PORT_PE to be cleared. + */ + if (temp & PORT_CSC) + ehci_writel(ehci, PORT_CSC, portsc_reg); + + /* + * Write to clear any interrupt status bits that might be set + * during port reset. + */ + temp = ehci_readl(ehci, &ehci->regs->status); + ehci_writel(ehci, temp, &ehci->regs->status); + + /* restore original interrupt enable bits */ + ehci_writel(ehci, saved_usbintr, &ehci->regs->intr_enable); + + return retval; +} static int tegra_ehci_hub_control( struct usb_hcd *hcd, @@ -201,6 +286,29 @@ static int tegra_ehci_hub_control( int retval = 0; u32 __iomem *status_reg; +#ifdef CONFIG_MACH_COLIBRI_T20 + u32 temp; + + /* To limit the speed of USB to full speed */ + if (!g_usb_high_speed) { + /* Check whether port is not 2nd one internally connected to + ASIX Ethernet chip, set PFSC (Port Force Full Speed) only + for externally accessible OTG and host port */ + if (tegra->phy->inst != 1) { + status_reg = &ehci->regs->port_status[(wIndex & 0xff) + - 1]; + temp = ehci_readl(ehci, status_reg); + /* Check whether PFSC bit is already set or not */ + if (!(temp & TEGRA_USB_PORTSC1_PFSC)) { + ehci_writel(ehci, (temp | + TEGRA_USB_PORTSC1_PFSC), + status_reg); + temp = ehci_readl(ehci, status_reg); + } + } + } +#endif /* CONFIG_MACH_COLIBRI_T20 */ + if (!tegra_usb_phy_hw_accessible(tegra->phy)) { if (buf) memset(buf, 0, wLength); @@ -249,6 +357,13 @@ static int tegra_ehci_hub_control( break; } + /* For USB1 port we need to issue Port Reset twice internally */ + if (tegra->phy->inst == 0 && + (typeReq == SetPortFeature && wValue == USB_PORT_FEAT_RESET)) { + status_reg = &ehci->regs->port_status[(wIndex & 0xff) - 1]; + return tegra_ehci_internal_port_reset(ehci, status_reg); + } + /* handle ehci hub control request */ retval = ehci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength); diff --git a/drivers/usb/otg/Kconfig b/drivers/usb/otg/Kconfig index bcb3e8680337..e38d2b4ca134 100644 --- a/drivers/usb/otg/Kconfig +++ b/drivers/usb/otg/Kconfig @@ -25,6 +25,14 @@ if USB || USB_GADGET # # USB Transceiver Drivers # +config USB_COLIBRI_OTG + boolean "Colibri OTG Driver" + depends on USB && ARCH_TEGRA && !USB_TEGRA_OTG + select USB_OTG_UTILS + help + Enable this driver on boards which use a regular GPIO for VBUS + detection. + config USB_GPIO_VBUS tristate "GPIO based peripheral-only VBUS sensing 'transceiver'" depends on GENERIC_GPIO diff --git a/drivers/usb/otg/Makefile b/drivers/usb/otg/Makefile index 0ef95e45a583..cbedff4e254f 100644 --- a/drivers/usb/otg/Makefile +++ b/drivers/usb/otg/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_USB_OTG_UTILS) += otg_id.o # transceiver drivers obj-$(CONFIG_USB_GPIO_VBUS) += gpio_vbus.o +obj-$(CONFIG_USB_COLIBRI_OTG) += colibri-otg.o CFLAGS_tegra-otg.o = -Werror obj-$(CONFIG_USB_TEGRA_OTG) += tegra-otg.o obj-$(CONFIG_ISP1301_OMAP) += isp1301_omap.o diff --git a/drivers/usb/otg/colibri-otg.c b/drivers/usb/otg/colibri-otg.c new file mode 100644 index 000000000000..54d792536363 --- /dev/null +++ b/drivers/usb/otg/colibri-otg.c @@ -0,0 +1,268 @@ +/* + * drivers/usb/otg/colibri-otg.c + * + * OTG transceiver driver for Tegra UTMI phy with GPIO VBUS detection + * + * Copyright (C) 2010 NVIDIA Corp. + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2012 Toradex, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include <linux/colibri_usb.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/usb.h> +#include <linux/usb/gadget.h> +#include <linux/usb/hcd.h> +#include <linux/usb/otg.h> + +#include <mach/gpio.h> + +struct colibri_otg_data { + struct otg_transceiver otg; + spinlock_t lock; + int irq; + struct platform_device *host; + struct platform_device *pdev; + struct work_struct work; +}; + +static const char *tegra_state_name(enum usb_otg_state state) +{ + if (state == OTG_STATE_A_HOST) + return "HOST"; + if (state == OTG_STATE_B_PERIPHERAL) + return "PERIPHERAL"; + if (state == OTG_STATE_A_SUSPEND) + return "SUSPEND"; + return "INVALID"; +} + +void tegra_start_host(struct colibri_otg_data *tegra) +{ + struct colibri_otg_platform_data *pdata = tegra->otg.dev->platform_data; + if (!tegra->pdev) { + tegra->pdev = pdata->host_register(); + } +} + +void tegra_stop_host(struct colibri_otg_data *tegra) +{ + struct colibri_otg_platform_data *pdata = tegra->otg.dev->platform_data; + if (tegra->pdev) { + pdata->host_unregister(tegra->pdev); + tegra->pdev = NULL; + } +} + +static void tegra_otg_notify_event(struct otg_transceiver *otg, + enum usb_xceiv_events event) +{ + otg->last_event = event; + atomic_notifier_call_chain(&otg->notifier, event, NULL); +} + +static void irq_work(struct work_struct *work) +{ + struct colibri_otg_data *tegra = + container_of(work, struct colibri_otg_data, work); + struct otg_transceiver *otg = &tegra->otg; + enum usb_otg_state from = otg->state; + enum usb_otg_state to = OTG_STATE_UNDEFINED; + unsigned long flags; + + spin_lock_irqsave(&tegra->lock, flags); + + /* Check client detect (High active) */ + mdelay(100); + if (gpio_get_value(irq_to_gpio(tegra->irq))) + to = OTG_STATE_B_PERIPHERAL; + else + to = OTG_STATE_A_HOST; + + spin_unlock_irqrestore(&tegra->lock, flags); + + if (to != OTG_STATE_UNDEFINED) { + otg->state = to; + + if (from != to) { + dev_info(tegra->otg.dev, "%s --> %s\n", + tegra_state_name(from), tegra_state_name(to)); + + if (to == OTG_STATE_B_PERIPHERAL) { + if (from == OTG_STATE_A_HOST) { + tegra_stop_host(tegra); + tegra_otg_notify_event(otg, USB_EVENT_NONE); + } + if (otg->gadget) { + usb_gadget_vbus_connect(otg->gadget); + tegra_otg_notify_event(otg, USB_EVENT_VBUS); + } + } else if (to == OTG_STATE_A_HOST) { + if (otg->gadget && (from == OTG_STATE_B_PERIPHERAL)) { + usb_gadget_vbus_disconnect(otg->gadget); + tegra_otg_notify_event(otg, USB_EVENT_NONE); + } + tegra_start_host(tegra); + tegra_otg_notify_event(otg, USB_EVENT_ID); + } + } + } +} + +static irqreturn_t colibri_otg_irq(int irq, void *data) +{ + struct colibri_otg_data *tegra = data; + + schedule_work(&tegra->work); + + return IRQ_HANDLED; +} + +static int colibri_otg_set_peripheral(struct otg_transceiver *otg, + struct usb_gadget *gadget) +{ + struct colibri_otg_data *tegra; + + tegra = container_of(otg, struct colibri_otg_data, otg); + otg->gadget = gadget; + + /* Set initial state */ + schedule_work(&tegra->work); + + return 0; +} + +static int colibri_otg_set_host(struct otg_transceiver *otg, + struct usb_bus *host) +{ + struct colibri_otg_data *tegra; + + tegra = container_of(otg, struct colibri_otg_data, otg); + otg->host = host; + + return 0; +} + +static int colibri_otg_set_power(struct otg_transceiver *otg, unsigned mA) +{ + return 0; +} + +static int colibri_otg_set_suspend(struct otg_transceiver *otg, int suspend) +{ + return 0; +} + +static int colibri_otg_probe(struct platform_device *pdev) +{ + struct colibri_otg_data *tegra; + struct colibri_otg_platform_data *plat = pdev->dev.platform_data; + int err, gpio_cd; + + if (!plat) { + dev_err(&pdev->dev, "no platform data?\n"); + return -ENODEV; + } + + tegra = kzalloc(sizeof(struct colibri_otg_data), GFP_KERNEL); + if (!tegra) + return -ENOMEM; + + tegra->otg.dev = &pdev->dev; + tegra->otg.label = "colibri-otg"; + tegra->otg.state = OTG_STATE_UNDEFINED; + tegra->otg.set_host = colibri_otg_set_host; + tegra->otg.set_peripheral = colibri_otg_set_peripheral; + tegra->otg.set_suspend = colibri_otg_set_suspend; + tegra->otg.set_power = colibri_otg_set_power; + spin_lock_init(&tegra->lock); + + platform_set_drvdata(pdev, tegra); + + tegra->otg.state = OTG_STATE_A_SUSPEND; + + err = otg_set_transceiver(&tegra->otg); + if (err) { + dev_err(&pdev->dev, "can't register transceiver (%d)\n", err); + goto err_otg; + } + + gpio_cd = plat->cable_detect_gpio; + err = gpio_request(gpio_cd, "USBC_DET"); + if (err) + goto err_gpio; + gpio_direction_input(gpio_cd); + + tegra->irq = gpio_to_irq(gpio_cd); + err = request_threaded_irq(tegra->irq, colibri_otg_irq, NULL, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, + "colibri-otg USBC_DET", tegra); + if (err) { + dev_err(&pdev->dev, "Failed to register USB client detect IRQ\n"); + goto err_irq; + } + + INIT_WORK (&tegra->work, irq_work); + + dev_info(&pdev->dev, "otg transceiver registered\n"); + + return 0; + +err_irq: + gpio_free(gpio_cd); +err_gpio: + otg_set_transceiver(NULL); +err_otg: + platform_set_drvdata(pdev, NULL); + kfree(tegra); + return err; +} + +static int __exit colibri_otg_remove(struct platform_device *pdev) +{ + struct colibri_otg_data *tegra = platform_get_drvdata(pdev); + + free_irq(tegra->irq, tegra); + gpio_free(irq_to_gpio(tegra->irq)); + otg_set_transceiver(NULL); + platform_set_drvdata(pdev, NULL); + kfree(tegra); + + return 0; +} + +static struct platform_driver colibri_otg_driver = { + .driver = { + .name = "colibri-otg", + }, + .remove = __exit_p(colibri_otg_remove), + .probe = colibri_otg_probe, +}; + +static int __init colibri_otg_init(void) +{ + return platform_driver_register(&colibri_otg_driver); +} +subsys_initcall(colibri_otg_init); + +static void __exit colibri_otg_exit(void) +{ + platform_driver_unregister(&colibri_otg_driver); +} +module_exit(colibri_otg_exit); |