diff options
author | Marcel Ziswiler <marcel.ziswiler@toradex.com> | 2012-06-22 18:31:46 +0200 |
---|---|---|
committer | Marcel Ziswiler <marcel.ziswiler@toradex.com> | 2012-06-22 18:31:46 +0200 |
commit | cc32fc1c9c1672348db32a2ce2d45db404d1355f (patch) | |
tree | 62f98d120a35a28caaef5fdb2ca0250149a4a8b5 | |
parent | c56c5857d5c1aa711b548e788073b35c9f9197fa (diff) |
tegra: colibri_t20: USB OTG support
Migrate Colibri USB OTG support.
-rw-r--r-- | arch/arm/configs/colibri_t20_defconfig | 2 | ||||
-rw-r--r-- | arch/arm/mach-tegra/board-colibri_t20.c | 141 | ||||
-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 | 255 | ||||
-rw-r--r-- | include/linux/colibri_usb.h | 28 |
6 files changed, 397 insertions, 38 deletions
diff --git a/arch/arm/configs/colibri_t20_defconfig b/arch/arm/configs/colibri_t20_defconfig index a5e097be906a..b77bbe272bb9 100644 --- a/arch/arm/configs/colibri_t20_defconfig +++ b/arch/arm/configs/colibri_t20_defconfig @@ -357,7 +357,7 @@ CONFIG_USB_GADGET=y CONFIG_USB_GADGET_VBUS_DRAW=500 CONFIG_USB_FSL_USB2=y CONFIG_USB_MASS_STORAGE=m -CONFIG_USB_TEGRA_OTG=y +CONFIG_USB_COLIBRI_OTG=y CONFIG_MMC=y CONFIG_MMC_UNSAFE_RESUME=y CONFIG_MMC_TEST=y diff --git a/arch/arm/mach-tegra/board-colibri_t20.c b/arch/arm/mach-tegra/board-colibri_t20.c index d2c67f08c9c6..7ab1a73d3810 100644 --- a/arch/arm/mach-tegra/board-colibri_t20.c +++ b/arch/arm/mach-tegra/board-colibri_t20.c @@ -19,6 +19,7 @@ #include <asm/setup.h> #include <linux/clk.h> +#include <linux/colibri_usb.h> #include <linux/dma-mapping.h> #include <linux/gpio_keys.h> #include <linux/i2c.h> @@ -67,6 +68,9 @@ TEGRA_OSC_CRYSTAL_FREQ_13MHZ #endif #endif +//conflicts with MECS Tellurium xPOD2 SSPTXD2 +#define USB_CABLE_DETECT_GPIO TEGRA_GPIO_PK5 /* USBC_DET */ + /* ADC */ static struct wm97xx_batt_pdata colibri_t20_adc_pdata = { @@ -735,28 +739,40 @@ static void __init colibri_t20_uart_init(void) /* USB */ +//overcurrent? + +//USB1_IF_USB_PHY_VBUS_WAKEUP_ID_0 +//Offset: 408h +//ID_PU: ID pullup enable. Set to 1. + static struct tegra_utmip_config utmi_phy_config[] = { [0] = { - .hssync_start_delay = 9, - .idle_wait_delay = 17, +//bias 6 .elastic_limit = 16, + .hssync_start_delay = 0, +// .hssync_start_delay = 9, + .idle_wait_delay = 17, +//squelch 2 .term_range_adj = 6, + .xcvr_lsfslew = 2, + .xcvr_lsrslew = 2, .xcvr_setup = 15, .xcvr_setup_offset = 0, .xcvr_use_fuses = 1, - .xcvr_lsfslew = 2, - .xcvr_lsrslew = 2, }, [1] = { - .hssync_start_delay = 9, - .idle_wait_delay = 17, +//bias 0 .elastic_limit = 16, + .hssync_start_delay = 0, +// .hssync_start_delay = 9, + .idle_wait_delay = 17, +//squelch 2 .term_range_adj = 6, + .xcvr_lsfslew = 2, + .xcvr_lsrslew = 2, .xcvr_setup = 8, .xcvr_setup_offset = 0, .xcvr_use_fuses = 1, - .xcvr_lsfslew = 2, - .xcvr_lsrslew = 2, }, }; @@ -766,18 +782,15 @@ static struct tegra_ulpi_config colibri_t20_ehci2_ulpi_phy_config = { }; static struct tegra_ehci_platform_data colibri_t20_ehci2_ulpi_platform_data = { - .operating_mode = TEGRA_USB_HOST, - .power_down_on_bus_suspend = 1, - .phy_config = &colibri_t20_ehci2_ulpi_phy_config, - .phy_type = TEGRA_USB_PHY_TYPE_LINK_ULPI, + .operating_mode = TEGRA_USB_HOST, + .phy_config = &colibri_t20_ehci2_ulpi_phy_config, + .phy_type = TEGRA_USB_PHY_TYPE_LINK_ULPI, + .power_down_on_bus_suspend = 1, }; static struct usb_phy_plat_data tegra_usb_phy_pdata[] = { [0] = { .instance = 0, -//[ 4.309032] Failed to register IRQ -//tegra_gpio_enable required? -// .vbus_irq = TEGRA_GPIO_PK5, /* USBC_DET */ .vbus_gpio = -1, }, [1] = { @@ -794,50 +807,104 @@ static struct usb_phy_plat_data tegra_usb_phy_pdata[] = { static struct tegra_ehci_platform_data tegra_ehci_pdata[] = { [0] = { - .phy_config = &utmi_phy_config[0], - .operating_mode = TEGRA_USB_HOST, - .power_down_on_bus_suspend = 0, /* otherwise might prevent enumeration */ + .phy_config = &utmi_phy_config[0], + .operating_mode = TEGRA_USB_OTG, + .power_down_on_bus_suspend = 0, /* otherwise might prevent enumeration */ }, [1] = { - .phy_config = &colibri_t20_ehci2_ulpi_phy_config, - .operating_mode = TEGRA_USB_HOST, - .power_down_on_bus_suspend = 1, - .phy_type = TEGRA_USB_PHY_TYPE_LINK_ULPI, + .phy_config = &colibri_t20_ehci2_ulpi_phy_config, + .operating_mode = TEGRA_USB_HOST, + .power_down_on_bus_suspend = 1, + .phy_type = TEGRA_USB_PHY_TYPE_LINK_ULPI, }, [2] = { - .phy_config = &utmi_phy_config[1], - .operating_mode = TEGRA_USB_HOST, - .power_down_on_bus_suspend = 0, /* otherwise might prevent enumeration */ - .hotplug = 1, + .phy_config = &utmi_phy_config[1], + .operating_mode = TEGRA_USB_HOST, + .power_down_on_bus_suspend = 0, /* otherwise might prevent enumeration */ + .hotplug = 1, }, }; -static struct tegra_otg_platform_data tegra_otg_pdata = { - .ehci_device = &tegra_ehci1_device, - .ehci_pdata = &tegra_ehci_pdata[0], +static struct platform_device *tegra_usb_otg_host_register(void) +{ + struct platform_device *pdev; + void *platform_data; + int val; + + pdev = platform_device_alloc(tegra_ehci1_device.name, + tegra_ehci1_device.id); + if (!pdev) + return NULL; + + val = platform_device_add_resources(pdev, tegra_ehci1_device.resource, + tegra_ehci1_device.num_resources); + if (val) + goto error; + + pdev->dev.dma_mask = tegra_ehci1_device.dev.dma_mask; + pdev->dev.coherent_dma_mask = tegra_ehci1_device.dev.coherent_dma_mask; + + platform_data = kmalloc(sizeof(struct tegra_ehci_platform_data), + GFP_KERNEL); + if (!platform_data) + goto error; + + memcpy(platform_data, &tegra_ehci_pdata[0], + sizeof(struct tegra_ehci_platform_data)); + pdev->dev.platform_data = platform_data; + + val = platform_device_add(pdev); + if (val) + goto error_add; + + return pdev; + +error_add: + kfree(platform_data); +error: + pr_err("%s: failed to add the host contoller device\n", __func__); + platform_device_put(pdev); + return NULL; +} + +static void tegra_usb_otg_host_unregister(struct platform_device *pdev) +{ + kfree(pdev->dev.platform_data); + pdev->dev.platform_data = NULL; + platform_device_unregister(pdev); +} + +static struct colibri_otg_platform_data colibri_otg_pdata = { + .cable_detect_gpio = USB_CABLE_DETECT_GPIO, + .host_register = &tegra_usb_otg_host_register, + .host_unregister = &tegra_usb_otg_host_unregister, +}; + +struct platform_device colibri_otg_device = { + .name = "colibri-otg", + .id = -1, + .dev = { + .platform_data = &colibri_otg_pdata, + }, }; static void colibri_t20_usb_init(void) { -//no improvement -// tegra_gpio_enable(TEGRA_GPIO_PK5); #ifdef CONFIG_USB_SUPPORT tegra_usb_phy_init(tegra_usb_phy_pdata, ARRAY_SIZE(tegra_usb_phy_pdata)); #endif /* OTG should be the first to be registered EHCI instance 0: USB1_DP/N -> USBOTG_P/N */ - tegra_otg_device.dev.platform_data = &tegra_otg_pdata; - platform_device_register(&tegra_otg_device); + platform_device_register(&colibri_otg_device); platform_device_register(&tegra_udc_device); -// - tegra_ehci2_device.dev.platform_data=&tegra_ehci_pdata[1]; -// + /* EHCI instance 1: ULPI PHY -> ASIX ETH */ + tegra_ehci2_device.dev.platform_data = &tegra_ehci_pdata[1]; platform_device_register(&tegra_ehci2_device); /* EHCI instance 2: USB3_DP/N -> USBH1_P/N */ - tegra_ehci3_device.dev.platform_data=&tegra_ehci_pdata[2]; + tegra_ehci3_device.dev.platform_data = &tegra_ehci_pdata[2]; platform_device_register(&tegra_ehci3_device); #ifdef MECS_TELLURIUM 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 0bc9935c2180..c09f9b829382 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 obj-$(CONFIG_USB_TEGRA_OTG) += tegra-otg.o obj-$(CONFIG_ISP1301_OMAP) += isp1301_omap.o obj-$(CONFIG_TWL4030_USB) += twl4030-usb.o diff --git a/drivers/usb/otg/colibri-otg.c b/drivers/usb/otg/colibri-otg.c new file mode 100644 index 000000000000..a21ae12df4fb --- /dev/null +++ b/drivers/usb/otg/colibri-otg.c @@ -0,0 +1,255 @@ +/* + * 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 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); + if (otg->gadget) + usb_gadget_vbus_connect(otg->gadget); + } else if (to == OTG_STATE_A_HOST) { + if (otg->gadget && (from == OTG_STATE_B_PERIPHERAL)) + usb_gadget_vbus_disconnect(otg->gadget); + tegra_start_host(tegra); + } + } + } +} + +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_gpio_enable(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); diff --git a/include/linux/colibri_usb.h b/include/linux/colibri_usb.h new file mode 100644 index 000000000000..6b3949979c38 --- /dev/null +++ b/include/linux/colibri_usb.h @@ -0,0 +1,28 @@ +/* + * 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. + */ + +#ifndef _COLIBRI_USB_H_ +#define _COLIBRI_USB_H_ + +struct colibri_otg_platform_data { + int cable_detect_gpio; + struct platform_device* (*host_register)(void); + void (*host_unregister)(struct platform_device*); +}; + +#endif /* _COLIBRI_USB_H_ */ |