diff options
Diffstat (limited to 'drivers/usb/otg/tegra-otg.c')
-rw-r--r-- | drivers/usb/otg/tegra-otg.c | 274 |
1 files changed, 129 insertions, 145 deletions
diff --git a/drivers/usb/otg/tegra-otg.c b/drivers/usb/otg/tegra-otg.c index 68d402afb25e..a5e85bf90d14 100644 --- a/drivers/usb/otg/tegra-otg.c +++ b/drivers/usb/otg/tegra-otg.c @@ -3,7 +3,7 @@ * * OTG transceiver driver for Tegra UTMI phy * - * Copyright (C) 2010 NVIDIA Corp. + * Copyright (C) 2010-2012 NVIDIA CORPORATION. All rights reserved. * Copyright (C) 2010 Google, Inc. * * This program is free software; you can redistribute it and/or modify it @@ -24,7 +24,6 @@ #include <linux/usb.h> #include <linux/usb/otg.h> #include <linux/usb/gadget.h> -#include <linux/usb/hcd.h> #include <linux/platform_device.h> #include <linux/platform_data/tegra_usb.h> #include <linux/clk.h> @@ -41,10 +40,8 @@ #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) - -typedef void (*callback_t)(enum usb_otg_state to, - enum usb_otg_state from, void *args); +#define USB_INT_EN (USB_VBUS_INT_EN | USB_ID_INT_EN | \ + USB_VBUS_WAKEUP_EN | USB_ID_PIN_WAKEUP_EN) #ifdef DEBUG #define DBG(stuff...) pr_info("tegra-otg: " stuff) @@ -63,11 +60,11 @@ struct tegra_otg_data { struct work_struct work; unsigned int intr_reg_data; bool clk_enabled; - callback_t charger_cb; - void *charger_cb_data; - bool interrupt_mode; + bool builtin_host; + bool suspended }; + static struct tegra_otg_data *tegra_clone; static inline unsigned long otg_readl(struct tegra_otg_data *tegra, @@ -82,20 +79,6 @@ static inline void otg_writel(struct tegra_otg_data *tegra, unsigned long val, writel(val, tegra->regs + offset); } -static void tegra_otg_enable_clk(void) -{ - if (!tegra_clone->clk_enabled) - clk_enable(tegra_clone->clk); - tegra_clone->clk_enabled = true; -} - -static void tegra_otg_disable_clk(void) -{ - if (tegra_clone->clk_enabled) - clk_disable(tegra_clone->clk); - tegra_clone->clk_enabled = false; -} - static const char *tegra_state_name(enum usb_otg_state state) { switch (state) { @@ -119,12 +102,13 @@ static unsigned long enable_interrupt(struct tegra_otg_data *tegra, bool en) clk_enable(tegra->clk); val = otg_readl(tegra, USB_PHY_WAKEUP); if (en) { - val |= (USB_VBUS_INT_EN | USB_VBUS_WAKEUP_EN); - val |= (USB_ID_INT_EN | USB_ID_PIN_WAKEUP_EN); - } else { - val &= ~(USB_VBUS_INT_EN | USB_VBUS_WAKEUP_EN); - val &= ~(USB_ID_INT_EN | USB_ID_PIN_WAKEUP_EN); + if (tegra->builtin_host) + val |= USB_INT_EN; + else + val = USB_VBUS_INT_EN | USB_VBUS_WAKEUP_EN | USB_ID_PIN_WAKEUP_EN; } + else + val &= ~USB_INT_EN; otg_writel(tegra, val, USB_PHY_WAKEUP); /* Add delay to make sure register is updated */ udelay(1); @@ -133,81 +117,70 @@ static unsigned long enable_interrupt(struct tegra_otg_data *tegra, bool en) return val; } -static struct platform_device * -tegra_usb_otg_host_register(struct platform_device *ehci_device, - struct tegra_ehci_platform_data *pdata) +static void tegra_start_host(struct tegra_otg_data *tegra) { - struct platform_device *pdev; + struct tegra_usb_otg_data *pdata = tegra->otg.dev->platform_data; + struct platform_device *pdev, *ehci_device = pdata->ehci_device; void *platform_data; int val; + DBG("%s(%d) Begin\n", __func__, __LINE__); + + if (tegra->pdev) + return ; + /* prepare device structure for registering host*/ pdev = platform_device_alloc(ehci_device->name, ehci_device->id); if (!pdev) - return NULL; + return ; val = platform_device_add_resources(pdev, ehci_device->resource, ehci_device->num_resources); if (val) goto error; - pdev->dev.dma_mask = ehci_device->dev.dma_mask; + pdev->dev.dma_mask = ehci_device->dev.dma_mask; pdev->dev.coherent_dma_mask = ehci_device->dev.coherent_dma_mask; - platform_data = kmalloc(sizeof(struct tegra_ehci_platform_data), - GFP_KERNEL); + platform_data = kmalloc(sizeof(struct tegra_usb_platform_data), GFP_KERNEL); if (!platform_data) goto error; - memcpy(platform_data, pdata, sizeof(struct tegra_ehci_platform_data)); + memcpy(platform_data, pdata->ehci_pdata, + sizeof(struct tegra_usb_platform_data)); pdev->dev.platform_data = platform_data; - val = platform_device_add(pdev); if (val) goto error_add; - return pdev; + tegra->pdev = pdev; + DBG("%s(%d) End\n", __func__, __LINE__); + return ; error_add: kfree(platform_data); error: pr_err("%s: failed to add the host controller device\n", __func__); platform_device_put(pdev); - return NULL; + tegra->pdev = NULL; } -static void tegra_usb_otg_host_unregister(struct platform_device *pdev) +static void tegra_stop_host(struct tegra_otg_data *tegra) { - kfree(pdev->dev.platform_data); - pdev->dev.platform_data = NULL; - platform_device_unregister(pdev); -} + struct platform_device *pdev = tegra->pdev; -void tegra_start_host(struct tegra_otg_data *tegra) -{ - struct tegra_otg_platform_data *pdata = tegra->otg.dev->platform_data; - if (!tegra->pdev) { - tegra->pdev = tegra_usb_otg_host_register(pdata->ehci_device, - pdata->ehci_pdata); - } -} + DBG("%s(%d) Begin\n", __func__, __LINE__); -void tegra_stop_host(struct tegra_otg_data *tegra) -{ - if (tegra->pdev) { - tegra_usb_otg_host_unregister(tegra->pdev); + if (pdev) { + /* unregister host from otg */ + kfree(pdev->dev.platform_data); + pdev->dev.platform_data = NULL; + platform_device_unregister(pdev); tegra->pdev = NULL; } -} -int register_otg_callback(callback_t cb, void *args) -{ - if (!tegra_clone) - return -ENODEV; - tegra_clone->charger_cb = cb; - tegra_clone->charger_cb_data = args; - return 0; + DBG("%s(%d) End\n", __func__, __LINE__); } -EXPORT_SYMBOL_GPL(register_otg_callback); + static void tegra_change_otg_state(struct tegra_otg_data *tegra, enum usb_otg_state to) @@ -228,9 +201,6 @@ static void tegra_change_otg_state(struct tegra_otg_data *tegra, dev_info(tegra->otg.dev, "%s --> %s\n", tegra_state_name(from), tegra_state_name(to)); - if (tegra->charger_cb) - tegra->charger_cb(to, from, tegra->charger_cb_data); - if (from == OTG_STATE_A_SUSPEND) { if (to == OTG_STATE_B_PERIPHERAL && otg->gadget) usb_gadget_vbus_connect(otg->gadget); @@ -256,10 +226,7 @@ static void irq_work(struct work_struct *work) unsigned long flags; unsigned long status; - clk_enable(tegra->clk); - spin_lock_irqsave(&tegra->lock, flags); - status = tegra->int_status; /* Debug prints */ @@ -274,7 +241,7 @@ static void irq_work(struct work_struct *work) DBG("%s(%d) got vbus interrupt\n", __func__, __LINE__); } - if (!(status & USB_ID_STATUS)) + if (!(status & USB_ID_STATUS) && (status & USB_ID_INT_EN)) to = OTG_STATE_A_HOST; else if (status & USB_VBUS_STATUS && from != OTG_STATE_A_HOST) to = OTG_STATE_B_PERIPHERAL; @@ -283,8 +250,6 @@ static void irq_work(struct work_struct *work) spin_unlock_irqrestore(&tegra->lock, flags); tegra_change_otg_state(tegra, to); - clk_disable(tegra->clk); - tegra_otg_disable_clk(); } static irqreturn_t tegra_otg_irq(int irq, void *data) @@ -294,51 +259,52 @@ static irqreturn_t tegra_otg_irq(int irq, void *data) unsigned long val; spin_lock_irqsave(&tegra->lock, flags); - val = otg_readl(tegra, USB_PHY_WAKEUP); + DBG("%s(%d) interrupt val = 0x%x\n", __func__, __LINE__, val); + if (val & (USB_VBUS_INT_EN | USB_ID_INT_EN)) { + DBG("%s(%d) PHY_WAKEUP = 0x%x\n", __func__, __LINE__, val); otg_writel(tegra, val, USB_PHY_WAKEUP); if ((val & USB_ID_INT_STATUS) || (val & USB_VBUS_INT_STATUS)) { tegra->int_status = val; schedule_work(&tegra->work); } } - spin_unlock_irqrestore(&tegra->lock, flags); return IRQ_HANDLED; } -void tegra_otg_check_vbus_detection(void) -{ - tegra_otg_enable_clk(); -} -EXPORT_SYMBOL(tegra_otg_check_vbus_detection); static int tegra_otg_set_peripheral(struct otg_transceiver *otg, struct usb_gadget *gadget) { struct tegra_otg_data *tegra; unsigned long val; + DBG("%s(%d) BEGIN\n", __func__, __LINE__); tegra = container_of(otg, struct tegra_otg_data, otg); otg->gadget = gadget; val = enable_interrupt(tegra, true); - if ((val & USB_ID_STATUS) && (val & USB_VBUS_STATUS)) { + if ((val & USB_ID_STATUS) && (val & USB_VBUS_STATUS)) val |= USB_VBUS_INT_STATUS; - } else if (!(val & USB_ID_STATUS)) { - val |= USB_ID_INT_STATUS; - } else { - val &= ~(USB_ID_INT_STATUS | USB_VBUS_INT_STATUS); + else if (!(val & USB_ID_STATUS)) { + if(!tegra->builtin_host) + val &= ~USB_ID_INT_STATUS; + else + val |= USB_ID_INT_STATUS; } + else + val &= ~(USB_ID_INT_STATUS | USB_VBUS_INT_STATUS); if ((val & USB_ID_INT_STATUS) || (val & USB_VBUS_INT_STATUS)) { tegra->int_status = val; - schedule_work (&tegra->work); + schedule_work(&tegra->work); } + DBG("%s(%d) END\n", __func__, __LINE__); return 0; } @@ -347,6 +313,7 @@ static int tegra_otg_set_host(struct otg_transceiver *otg, { struct tegra_otg_data *tegra; unsigned long val; + DBG("%s(%d) BEGIN\n", __func__, __LINE__); tegra = container_of(otg, struct tegra_otg_data, otg); otg->host = host; @@ -354,11 +321,11 @@ static int tegra_otg_set_host(struct otg_transceiver *otg, clk_enable(tegra->clk); val = otg_readl(tegra, USB_PHY_WAKEUP); val &= ~(USB_VBUS_INT_STATUS | USB_ID_INT_STATUS); - val |= (USB_ID_INT_EN | USB_ID_PIN_WAKEUP_EN); otg_writel(tegra, val, USB_PHY_WAKEUP); clk_disable(tegra->clk); + DBG("%s(%d) END\n", __func__, __LINE__); return 0; } @@ -389,12 +356,9 @@ static ssize_t store_host_en(struct device *dev, struct device_attribute *attr, struct platform_device *pdev = to_platform_device(dev); struct tegra_otg_data *tegra = platform_get_drvdata(pdev); unsigned long host; - int err; - err = kstrtoul(buf, 10, &host); - if (err < 0) { - return err; - } + if (sscanf(buf, "%d", &host) != 1 || host < 0 || host > 1) + return -EINVAL; if (host) { enable_interrupt(tegra, false); @@ -415,9 +379,8 @@ static DEVICE_ATTR(enable_host, 0644, show_host_en, store_host_en); static int tegra_otg_probe(struct platform_device *pdev) { struct tegra_otg_data *tegra; - struct tegra_otg_platform_data *otg_pdata; - struct tegra_ehci_platform_data *ehci_pdata; struct resource *res; + struct tegra_usb_otg_data *pdata = dev_get_platdata(&pdev->dev); int err; tegra = kzalloc(sizeof(struct tegra_otg_data), GFP_KERNEL); @@ -425,8 +388,6 @@ static int tegra_otg_probe(struct platform_device *pdev) return -ENOMEM; tegra->otg.dev = &pdev->dev; - otg_pdata = tegra->otg.dev->platform_data; - ehci_pdata = otg_pdata->ehci_pdata; tegra->otg.label = "tegra-otg"; tegra->otg.state = OTG_STATE_UNDEFINED; tegra->otg.set_host = tegra_otg_set_host; @@ -435,10 +396,14 @@ static int tegra_otg_probe(struct platform_device *pdev) tegra->otg.set_power = tegra_otg_set_power; spin_lock_init(&tegra->lock); + if (pdata) { + tegra->builtin_host = !pdata->ehci_pdata->builtin_host_disabled; + } + platform_set_drvdata(pdev, tegra); tegra_clone = tegra; - tegra->clk_enabled = false; tegra->interrupt_mode = true; + tegra->suspended = false; tegra->clk = clk_get(&pdev->dev, NULL); if (IS_ERR(tegra->clk)) { @@ -480,15 +445,23 @@ static int tegra_otg_probe(struct platform_device *pdev) tegra->irq = res->start; err = request_threaded_irq(tegra->irq, tegra_otg_irq, NULL, - IRQF_SHARED, "tegra-otg", tegra); + IRQF_SHARED | IRQF_TRIGGER_HIGH, + "tegra-otg", tegra); if (err) { dev_err(&pdev->dev, "Failed to register IRQ\n"); goto err_irq; } - INIT_WORK (&tegra->work, irq_work); - if (!ehci_pdata->default_enable) - clk_disable(tegra->clk); + err = enable_irq_wake(tegra->irq); + if (err < 0) { + dev_warn(&pdev->dev, + "Couldn't enable USB otg mode wakeup, irq=%d, error=%d\n", + tegra->irq, err); + err = 0; + } + + INIT_WORK(&tegra->work, irq_work); + dev_info(&pdev->dev, "otg transceiver registered\n"); err = device_create_file(&pdev->dev, &dev_attr_enable_host); @@ -497,6 +470,8 @@ static int tegra_otg_probe(struct platform_device *pdev) goto err_irq; } + clk_disable(tegra->clk); + return 0; err_irq: @@ -532,58 +507,67 @@ static int __exit tegra_otg_remove(struct platform_device *pdev) static int tegra_otg_suspend(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); - struct tegra_otg_data *tegra_otg = platform_get_drvdata(pdev); - struct otg_transceiver *otg = &tegra_otg->otg; - enum usb_otg_state from = otg->state; - unsigned int val; - - /* store the interupt enable for cable ID and VBUS */ - clk_enable(tegra_otg->clk); - tegra_otg->intr_reg_data = readl(tegra_otg->regs + USB_PHY_WAKEUP); - val = tegra_otg->intr_reg_data & ~(USB_ID_INT_EN | USB_VBUS_INT_EN); - writel(val, (tegra_otg->regs + USB_PHY_WAKEUP)); - clk_disable(tegra_otg->clk); - - if (from == OTG_STATE_B_PERIPHERAL && otg->gadget) { - usb_gadget_vbus_disconnect(otg->gadget); - otg->state = OTG_STATE_A_SUSPEND; - } - tegra_otg_disable_clk(); + struct tegra_otg_data *tegra = platform_get_drvdata(pdev); + struct otg_transceiver *otg = &tegra->otg; + int val; + DBG("%s(%d) BEGIN state : %s\n", __func__, __LINE__, + tegra_state_name(otg->state)); + + clk_enable(tegra->clk); + val = otg_readl(tegra, USB_PHY_WAKEUP); + val &= ~(USB_ID_INT_EN | USB_VBUS_INT_EN); + otg_writel(tegra, val, USB_PHY_WAKEUP); + clk_disable(tegra->clk); + + /* Suspend peripheral mode, host mode is taken care by host driver */ + if (otg->state == OTG_STATE_B_PERIPHERAL) + tegra_change_otg_state(tegra, OTG_STATE_A_SUSPEND); + + tegra->suspended = true; + + DBG("%s(%d) END\n", __func__, __LINE__); return 0; } static void tegra_otg_resume(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); - struct tegra_otg_data *tegra_otg = platform_get_drvdata(pdev); + struct tegra_otg_data *tegra = platform_get_drvdata(pdev); + struct otg_transceiver *otg = &tegra->otg; int val; unsigned long flags; + DBG("%s(%d) BEGIN\n", __func__, __LINE__); - tegra_otg_enable_clk(); - - /* Following delay is intentional. - * It is placed here after observing system hang. - * Root cause is not confirmed. - */ - msleep(1); - /* restore the interupt enable for cable ID and VBUS */ - clk_enable(tegra_otg->clk); - writel(tegra_otg->intr_reg_data, (tegra_otg->regs + USB_PHY_WAKEUP)); - val = readl(tegra_otg->regs + USB_PHY_WAKEUP); - clk_disable(tegra_otg->clk); - - /* A device might be connected while CPU is in sleep mode. In this case no interrupt - * will be triggered - * force irq_work to recheck connected devices - */ - if (!(val & USB_ID_STATUS)) { - spin_lock_irqsave(&tegra_otg->lock, flags); - tegra_otg->int_status = (val | USB_ID_INT_STATUS ); - schedule_work(&tegra_otg->work); - spin_unlock_irqrestore(&tegra_otg->lock, flags); - } + if (!tegra->suspended) + return; + + /* Clear pending interrupts */ + clk_enable(tegra->clk); + val = otg_readl(tegra, USB_PHY_WAKEUP); + otg_writel(tegra, val, USB_PHY_WAKEUP); + DBG("%s(%d) PHY WAKEUP register : 0x%x\n", __func__, __LINE__, val); + clk_disable(tegra->clk); + + /* Handle if host cable is replaced with device during suspend state */ + if (otg->state == OTG_STATE_A_HOST && (val & USB_ID_STATUS)) + tegra_change_otg_state(tegra, OTG_STATE_A_SUSPEND); + + /* Enable interrupt and call work to set to appropriate state */ + spin_lock_irqsave(&tegra->lock, flags); + if (tegra->builtin_host) + tegra->int_status = val | USB_INT_EN; + else + tegra->int_status = val | USB_VBUS_INT_EN | USB_VBUS_WAKEUP_EN | + USB_ID_PIN_WAKEUP_EN; + + spin_unlock_irqrestore(&tegra->lock, flags); + irq_work(&tegra->work); + + enable_interrupt(tegra, true); + + tegra->suspended = false; - return; + DBG("%s(%d) END\n", __func__, __LINE__); } static const struct dev_pm_ops tegra_otg_pm_ops = { |