summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarcel Ziswiler <marcel.ziswiler@toradex.com>2012-06-22 18:31:46 +0200
committerMarcel Ziswiler <marcel.ziswiler@toradex.com>2012-06-22 18:31:46 +0200
commitcc32fc1c9c1672348db32a2ce2d45db404d1355f (patch)
tree62f98d120a35a28caaef5fdb2ca0250149a4a8b5
parentc56c5857d5c1aa711b548e788073b35c9f9197fa (diff)
tegra: colibri_t20: USB OTG support
Migrate Colibri USB OTG support.
-rw-r--r--arch/arm/configs/colibri_t20_defconfig2
-rw-r--r--arch/arm/mach-tegra/board-colibri_t20.c141
-rw-r--r--drivers/usb/otg/Kconfig8
-rw-r--r--drivers/usb/otg/Makefile1
-rw-r--r--drivers/usb/otg/colibri-otg.c255
-rw-r--r--include/linux/colibri_usb.h28
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_ */