From: Gary King Date: Tue, 28 Sep 2010 19:32:13 +0000 (-0700) Subject: usb: add OTG driver for tegra USB PHY X-Git-Tag: firefly_0821_release~9833^2~191 X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=e9ba19bc3febbac6cfe27fe59dcfd1e3d03af61f;p=firefly-linux-kernel-4.4.55.git usb: add OTG driver for tegra USB PHY Change-Id: I23be62e819b658c0e25f70eebfbb8dc0dc76c173 Signed-off-by: Gary King --- diff --git a/drivers/usb/otg/Kconfig b/drivers/usb/otg/Kconfig index 3b1289572d72..2240602fc81b 100644 --- a/drivers/usb/otg/Kconfig +++ b/drivers/usb/otg/Kconfig @@ -67,4 +67,12 @@ config NOP_USB_XCEIV built-in with usb ip or which are autonomous and doesn't require any phy programming such as ISP1x04 etc. +config USB_TEGRA_OTG + boolean "Tegra OTG Driver" + depends on USB && ARCH_TEGRA + select USB_OTG_UTILS + help + Enable this driver on boards which use the internal VBUS and ID + sensing of the Tegra USB PHY. + endif # USB || OTG diff --git a/drivers/usb/otg/Makefile b/drivers/usb/otg/Makefile index aeb49a8ec412..fbf2a25a2e8c 100644 --- a/drivers/usb/otg/Makefile +++ b/drivers/usb/otg/Makefile @@ -7,6 +7,7 @@ obj-$(CONFIG_USB_OTG_UTILS) += otg.o # transceiver drivers obj-$(CONFIG_USB_GPIO_VBUS) += gpio_vbus.o +obj-$(CONFIG_USB_TEGRA_OTG) += tegra-otg.o obj-$(CONFIG_ISP1301_OMAP) += isp1301_omap.o obj-$(CONFIG_TWL4030_USB) += twl4030-usb.o obj-$(CONFIG_NOP_USB_XCEIV) += nop-usb-xceiv.o diff --git a/drivers/usb/otg/tegra-otg.c b/drivers/usb/otg/tegra-otg.c new file mode 100644 index 000000000000..542a184824a3 --- /dev/null +++ b/drivers/usb/otg/tegra-otg.c @@ -0,0 +1,393 @@ +/* + * drivers/usb/otg/tegra-otg.c + * + * OTG transceiver driver for Tegra UTMI phy + * + * Copyright (C) 2010 NVIDIA Corp. + * Copyright (C) 2010 Google, 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define USB_PHY_WAKEUP 0x408 +#define USB_ID_INT_EN (1 << 0) +#define USB_ID_INT_STATUS (1 << 1) +#define USB_ID_STATUS (1 << 2) +#define USB_ID_PIN_WAKEUP_EN (1 << 6) +#define USB_VBUS_WAKEUP_EN (1 << 30) +#define USB_VBUS_INT_EN (1 << 8) +#define USB_VBUS_INT_STATUS (1 << 9) +#define USB_VBUS_STATUS (1 << 10) +#define USB_INTS (USB_VBUS_INT_STATUS | USB_ID_INT_STATUS) + +struct tegra_otg_data { + struct otg_transceiver otg; + unsigned long int_status; + spinlock_t lock; + void __iomem *regs; + struct clk *clk; + int irq; + struct platform_device *host; + struct platform_device *pdev; +}; + +static inline unsigned long otg_readl(struct tegra_otg_data *tegra, + unsigned int offset) +{ + return readl(tegra->regs + offset); +} + +static inline void otg_writel(struct tegra_otg_data *tegra, unsigned long val, + unsigned int offset) +{ + writel(val, tegra->regs + offset); +} + +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 tegra_otg_data *tegra) +{ + int retval; + struct platform_device *pdev; + struct platform_device *host = tegra->host; + void *platform_data; + + pdev = platform_device_alloc(host->name, host->id); + if (!pdev) + return; + + if (host->resource) { + retval = platform_device_add_resources(pdev, host->resource, + host->num_resources); + if (retval) + goto error; + } + + pdev->dev.dma_mask = host->dev.dma_mask; + pdev->dev.coherent_dma_mask = host->dev.coherent_dma_mask; + + platform_data = kmalloc(sizeof(struct tegra_ehci_platform_data), GFP_KERNEL); + if (!platform_data) + goto error; + + memcpy(platform_data, host->dev.platform_data, + sizeof(struct tegra_ehci_platform_data)); + pdev->dev.platform_data = platform_data; + + retval = platform_device_add(pdev); + if (retval) + goto error_add; + + tegra->pdev = pdev; + return; + +error_add: + kfree(platform_data); +error: + pr_err("%s: failed to add the host contoller device\n", __func__); + platform_device_put(pdev); +} + +void tegra_stop_host(struct tegra_otg_data *tegra) +{ + if (tegra->pdev) { + platform_device_unregister(tegra->pdev); + tegra->pdev = NULL; + } +} + +static irqreturn_t tegra_otg_irq_thread(int irq, void *data) +{ + struct tegra_otg_data *tegra = data; + struct otg_transceiver *otg = &tegra->otg; + enum usb_otg_state from = otg->state; + enum usb_otg_state to = OTG_STATE_UNDEFINED; + unsigned long flags; + unsigned long status; + + clk_enable(tegra->clk); + + status = otg_readl(tegra, USB_PHY_WAKEUP); + + spin_lock_irqsave(&tegra->lock, flags); + + if (tegra->int_status & USB_ID_INT_STATUS) { + if (status & USB_ID_STATUS) + to = OTG_STATE_A_SUSPEND; + else + to = OTG_STATE_A_HOST; + } else if (tegra->int_status & USB_VBUS_INT_STATUS) { + if (status & USB_VBUS_STATUS) + to = OTG_STATE_B_PERIPHERAL; + else + to = OTG_STATE_A_SUSPEND; + } + + tegra->int_status = 0; + + spin_unlock_irqrestore(&tegra->lock, flags); + + otg->state = to; + + dev_info(tegra->otg.dev, "%s --> %s", tegra_state_name(from), + tegra_state_name(to)); + + if (to == OTG_STATE_A_SUSPEND) { + if (from == OTG_STATE_A_HOST && tegra->host) + tegra_stop_host(tegra); + else if (from == OTG_STATE_B_PERIPHERAL && otg->gadget) + usb_gadget_vbus_disconnect(otg->gadget); + } else if (to == OTG_STATE_B_PERIPHERAL && otg->gadget) { + if (from == OTG_STATE_A_SUSPEND) + usb_gadget_vbus_connect(otg->gadget); + } else if (to == OTG_STATE_A_HOST && tegra->host) { + if (from == OTG_STATE_A_SUSPEND) + tegra_start_host(tegra); + } + + clk_disable(tegra->clk); + + return IRQ_HANDLED; + +} + +static irqreturn_t tegra_otg_irq(int irq, void *data) +{ + struct tegra_otg_data *tegra = data; + unsigned long val; + + clk_enable(tegra->clk); + + spin_lock(&tegra->lock); + val = otg_readl(tegra, USB_PHY_WAKEUP); + otg_writel(tegra, val, USB_PHY_WAKEUP); + + /* and the interrupt enables into the interrupt status bits */ + val = (val & (val << 1)) & USB_INTS; + + tegra->int_status |= val; + + spin_unlock(&tegra->lock); + + clk_disable(tegra->clk); + + return (val) ? IRQ_WAKE_THREAD : IRQ_NONE; +} + +static int tegra_otg_set_peripheral(struct otg_transceiver *otg, + struct usb_gadget *gadget) +{ + struct tegra_otg_data *tegra; + unsigned long val; + + tegra = container_of(otg, struct tegra_otg_data, otg); + otg->gadget = gadget; + + clk_enable(tegra->clk); + val = otg_readl(tegra, USB_PHY_WAKEUP); + val &= ~(USB_VBUS_INT_STATUS | USB_ID_INT_STATUS); + + if (gadget) + val |= (USB_VBUS_INT_EN | USB_VBUS_WAKEUP_EN); + else + val &= ~(USB_VBUS_INT_EN | USB_VBUS_WAKEUP_EN); + + otg_writel(tegra, val, USB_PHY_WAKEUP); + clk_disable(tegra->clk); + + return 0; +} + +static int tegra_otg_set_host(struct otg_transceiver *otg, + struct usb_bus *host) +{ + struct tegra_otg_data *tegra; + unsigned long val; + + tegra = container_of(otg, struct tegra_otg_data, otg); + otg->host = host; + + clk_enable(tegra->clk); + val = otg_readl(tegra, USB_PHY_WAKEUP); + val &= ~(USB_VBUS_INT_STATUS | USB_ID_INT_STATUS); + + if (host) + val |= USB_ID_INT_EN | USB_ID_PIN_WAKEUP_EN; + else + val &= ~(USB_ID_INT_EN | USB_ID_PIN_WAKEUP_EN); + otg_writel(tegra, val, USB_PHY_WAKEUP); + clk_disable(tegra->clk); + + return 0; +} + +static int tegra_otg_set_power(struct otg_transceiver *otg, unsigned mA) +{ + return 0; +} + +static int tegra_otg_set_suspend(struct otg_transceiver *otg, int suspend) +{ + return 0; +} + +static int tegra_otg_probe(struct platform_device *pdev) +{ + struct tegra_otg_data *tegra; + struct resource *res; + unsigned long val; + int err; + + tegra = kzalloc(sizeof(struct tegra_otg_data), GFP_KERNEL); + if (!tegra) + return -ENOMEM; + + tegra->otg.dev = &pdev->dev; + tegra->otg.label = "tegra-otg"; + tegra->otg.state = OTG_STATE_UNDEFINED; + tegra->otg.set_host = tegra_otg_set_host; + tegra->otg.set_peripheral = tegra_otg_set_peripheral; + tegra->otg.set_suspend = tegra_otg_set_suspend; + tegra->otg.set_power = tegra_otg_set_power; + tegra->host = pdev->dev.platform_data; + spin_lock_init(&tegra->lock); + + platform_set_drvdata(pdev, tegra); + + tegra->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(tegra->clk)) { + dev_err(&pdev->dev, "Can't get otg clock\n"); + err = PTR_ERR(tegra->clk); + goto err_clk; + } + + err = clk_enable(tegra->clk); + if (err) + goto err_clken; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "Failed to get I/O memory\n"); + err = -ENXIO; + goto err_io; + } + tegra->regs = ioremap(res->start, resource_size(res)); + if (!tegra->regs) { + err = -ENOMEM; + goto err_io; + } + + val = otg_readl(tegra, USB_PHY_WAKEUP); + + val &= ~(USB_VBUS_INT_STATUS | USB_VBUS_INT_EN | + USB_ID_INT_STATUS | USB_ID_INT_EN | + USB_VBUS_WAKEUP_EN | USB_ID_PIN_WAKEUP_EN); + + otg_writel(tegra, val, USB_PHY_WAKEUP); + + 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; + } + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(&pdev->dev, "Failed to get IRQ\n"); + err = -ENXIO; + goto err_irq; + } + tegra->irq = res->start; + err = request_threaded_irq(tegra->irq, tegra_otg_irq, + tegra_otg_irq_thread, + IRQF_SHARED, "tegra-otg", tegra); + if (err) { + dev_err(&pdev->dev, "Failed to register IRQ\n"); + goto err_irq; + } + + dev_info(&pdev->dev, "otg transceiver registered\n"); + return 0; + +err_irq: + otg_set_transceiver(NULL); +err_otg: + iounmap(tegra->regs); +err_io: + clk_disable(tegra->clk); +err_clken: + clk_put(tegra->clk); +err_clk: + platform_set_drvdata(pdev, NULL); + kfree(tegra); + return err; +} + +static int __exit tegra_otg_remove(struct platform_device *pdev) +{ + struct tegra_otg_data *tegra = platform_get_drvdata(pdev); + + free_irq(tegra->irq, tegra); + otg_set_transceiver(NULL); + iounmap(tegra->regs); + clk_disable(tegra->clk); + clk_put(tegra->clk); + platform_set_drvdata(pdev, NULL); + kfree(tegra); + + return 0; +} + +static struct platform_driver tegra_otg_driver = { + .driver = { + .name = "tegra-otg", + }, + .remove = __exit_p(tegra_otg_remove), + .probe = tegra_otg_probe, +}; + +static int __init tegra_otg_init(void) +{ + return platform_driver_register(&tegra_otg_driver); +} +subsys_initcall(tegra_otg_init); + +static void __exit tegra_otg_exit(void) +{ + platform_driver_unregister(&tegra_otg_driver); +} +module_exit(tegra_otg_exit);