diff options
Diffstat (limited to 'drivers/usb/otg/tegra-otg.c')
-rw-r--r-- | drivers/usb/otg/tegra-otg.c | 269 |
1 files changed, 179 insertions, 90 deletions
diff --git a/drivers/usb/otg/tegra-otg.c b/drivers/usb/otg/tegra-otg.c index 4c04e6e183f1..d95d238fd9d6 100644 --- a/drivers/usb/otg/tegra-otg.c +++ b/drivers/usb/otg/tegra-otg.c @@ -42,10 +42,18 @@ #define USB_VBUS_INT_STATUS (1 << 9) #define USB_VBUS_STATUS (1 << 10) #define USB_INTS (USB_VBUS_INT_STATUS | USB_ID_INT_STATUS) +#define USB_INT_EN (USB_VBUS_INT_EN | USB_ID_INT_EN | \ + USB_VBUS_WAKEUP_EN | USB_ID_PIN_WAKEUP_EN) typedef void (*callback_t)(enum usb_otg_state to, enum usb_otg_state from, void *args); +#ifdef DEBUG +#define DBG(stuff...) pr_info("tegra-otg: " stuff) +#else +#define DBG(stuff...) do {} while (0) +#endif + struct tegra_otg_data { struct otg_transceiver otg; unsigned long int_status; @@ -56,11 +64,11 @@ struct tegra_otg_data { struct platform_device *pdev; struct work_struct work; unsigned int intr_reg_data; - bool detect_vbus; bool clk_enabled; callback_t charger_cb; void *charger_cb_data; + bool interrupt_mode; }; static struct tegra_otg_data *tegra_clone; @@ -106,6 +114,24 @@ static const char *tegra_state_name(enum usb_otg_state state) } } +static unsigned long enable_interrupt(struct tegra_otg_data *tegra, bool en) +{ + unsigned long val; + + clk_enable(tegra->clk); + val = otg_readl(tegra, USB_PHY_WAKEUP); + if (en) + val |= USB_INT_EN; + else + val &= ~USB_INT_EN; + otg_writel(tegra, val, USB_PHY_WAKEUP); + /* Add delay to make sure register is updated */ + udelay(1); + clk_disable(tegra->clk); + + return val; +} + static struct platform_device * tegra_usb_otg_host_register(struct platform_device *ehci_device, struct tegra_ehci_platform_data *pdata) @@ -157,19 +183,27 @@ static void tegra_usb_otg_host_unregister(struct platform_device *pdev) void tegra_start_host(struct tegra_otg_data *tegra) { + DBG("%s(%d) BEGIN\n", __func__, __LINE__); + 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) END\n", __func__, __LINE__); } void tegra_stop_host(struct tegra_otg_data *tegra) { + DBG("%s(%d) BEGIN\n", __func__, __LINE__); + if (tegra->pdev) { tegra_usb_otg_host_unregister(tegra->pdev); tegra->pdev = NULL; } + + DBG("%s(%d) END\n", __func__, __LINE__); } int register_otg_callback(callback_t cb, void *args) @@ -182,72 +216,80 @@ int register_otg_callback(callback_t cb, void *args) } EXPORT_SYMBOL_GPL(register_otg_callback); -static void irq_work(struct work_struct *work) +static void tegra_change_otg_state(struct tegra_otg_data *tegra, + enum usb_otg_state to) { - struct tegra_otg_data *tegra = - container_of(work, struct tegra_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; - unsigned long status; - if (tegra->detect_vbus) { - tegra->detect_vbus = false; - tegra_otg_enable_clk(); + if(!tegra->interrupt_mode){ + DBG("OTG: Vbus detection is disabled"); return; } - clk_enable(tegra->clk); - - spin_lock_irqsave(&tegra->lock, flags); - - status = tegra->int_status; - - if (tegra->int_status & USB_ID_INT_STATUS) { - if (status & USB_ID_STATUS) { - if ((status & USB_VBUS_STATUS) && (from != OTG_STATE_A_HOST)) - to = OTG_STATE_B_PERIPHERAL; - else - to = OTG_STATE_A_SUSPEND; - } - else - to = OTG_STATE_A_HOST; - } - if (from != OTG_STATE_A_HOST) { - if (tegra->int_status & USB_VBUS_INT_STATUS) { - if (status & USB_VBUS_STATUS) - to = OTG_STATE_B_PERIPHERAL; - else - to = OTG_STATE_A_SUSPEND; - } - } - spin_unlock_irqrestore(&tegra->lock, flags); + DBG("%s(%d) requested otg state %s-->%s\n", __func__, + __LINE__, tegra_state_name(from), tegra_state_name(to)); - if (to != OTG_STATE_UNDEFINED) { + if (to != OTG_STATE_UNDEFINED && from != to) { otg->state = to; - 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 (to == OTG_STATE_A_SUSPEND) { - if (from == OTG_STATE_A_HOST) + if (from == OTG_STATE_A_SUSPEND) { + if (to == OTG_STATE_B_PERIPHERAL && otg->gadget) + usb_gadget_vbus_connect(otg->gadget); + else if (to == OTG_STATE_A_HOST) + tegra_start_host(tegra); + } else if (from == OTG_STATE_A_HOST) { + if (to == OTG_STATE_A_SUSPEND) tegra_stop_host(tegra); - else if (from == OTG_STATE_B_PERIPHERAL && otg->gadget) + } else if (from == OTG_STATE_B_PERIPHERAL && otg->gadget) { + if (to == OTG_STATE_A_SUSPEND) 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) { - if (from == OTG_STATE_A_SUSPEND) - tegra_start_host(tegra); } } +} +static void irq_work(struct work_struct *work) +{ + struct tegra_otg_data *tegra = + container_of(work, struct tegra_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; + unsigned long status; + + clk_enable(tegra->clk); + + spin_lock_irqsave(&tegra->lock, flags); + + status = tegra->int_status; + /* Debug prints */ + DBG("%s(%d) status = 0x%x\n", __func__, __LINE__, status); + if ((status & USB_ID_INT_STATUS) && + (status & USB_VBUS_INT_STATUS)) + DBG("%s(%d) got vbus & id interrupt\n", __func__, __LINE__); + else { + if (status & USB_ID_INT_STATUS) + DBG("%s(%d) got id interrupt\n", __func__, __LINE__); + if (status & USB_VBUS_INT_STATUS) + DBG("%s(%d) got vbus interrupt\n", __func__, __LINE__); + } + + if (!(status & USB_ID_STATUS)) + to = OTG_STATE_A_HOST; + else if (status & USB_VBUS_STATUS && from != OTG_STATE_A_HOST) + to = OTG_STATE_B_PERIPHERAL; + else + to = OTG_STATE_A_SUSPEND; + + spin_unlock_irqrestore(&tegra->lock, flags); + tegra_change_otg_state(tegra, to); clk_disable(tegra->clk); tegra_otg_disable_clk(); } @@ -262,10 +304,10 @@ static irqreturn_t tegra_otg_irq(int irq, void *data) val = otg_readl(tegra, USB_PHY_WAKEUP); 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; - tegra->detect_vbus = false; schedule_work(&tegra->work); } } @@ -277,8 +319,7 @@ static irqreturn_t tegra_otg_irq(int irq, void *data) void tegra_otg_check_vbus_detection(void) { - tegra_clone->detect_vbus = true; - schedule_work(&tegra_clone->work); + tegra_otg_enable_clk(); } EXPORT_SYMBOL(tegra_otg_check_vbus_detection); @@ -287,33 +328,26 @@ static int tegra_otg_set_peripheral(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->gadget = gadget; - clk_enable(tegra->clk); - val = otg_readl(tegra, USB_PHY_WAKEUP); - val |= (USB_VBUS_INT_EN | USB_VBUS_WAKEUP_EN); - val |= (USB_ID_INT_EN | USB_ID_PIN_WAKEUP_EN); - otg_writel(tegra, val, USB_PHY_WAKEUP); - /* Add delay to make sure register is updated */ - udelay(1); - clk_disable(tegra->clk); + 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)) { + else if (!(val & USB_ID_STATUS)) val |= USB_ID_INT_STATUS; - } else { + 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; - tegra->detect_vbus = false; schedule_work (&tegra->work); } + DBG("%s(%d) END\n", __func__, __LINE__); return 0; } @@ -322,6 +356,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; @@ -334,6 +369,7 @@ static int tegra_otg_set_host(struct otg_transceiver *otg, otg_writel(tegra, val, USB_PHY_WAKEUP); clk_disable(tegra->clk); + DBG("%s(%d) END\n", __func__, __LINE__); return 0; } @@ -347,6 +383,46 @@ static int tegra_otg_set_suspend(struct otg_transceiver *otg, int suspend) return 0; } +static ssize_t show_host_en(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct tegra_otg_data *tegra = platform_get_drvdata(pdev); + + *buf = tegra->interrupt_mode ? '0': '1'; + strcat(buf, "\n"); + return strlen(buf); +} + +static ssize_t store_host_en(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + 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 (host) { + enable_interrupt(tegra, false); + tegra_change_otg_state(tegra, OTG_STATE_A_SUSPEND); + tegra_change_otg_state(tegra, OTG_STATE_A_HOST); + tegra->interrupt_mode = false; + } else { + tegra->interrupt_mode = true; + tegra_change_otg_state(tegra, OTG_STATE_A_SUSPEND); + enable_interrupt(tegra, true); + } + + return count; +} + +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; @@ -373,6 +449,7 @@ static int tegra_otg_probe(struct platform_device *pdev) platform_set_drvdata(pdev, tegra); tegra_clone = tegra; tegra->clk_enabled = false; + tegra->interrupt_mode = true; tegra->clk = clk_get(&pdev->dev, NULL); if (IS_ERR(tegra->clk)) { @@ -424,6 +501,13 @@ static int tegra_otg_probe(struct platform_device *pdev) if (!ehci_pdata->default_enable) clk_disable(tegra->clk); dev_info(&pdev->dev, "otg transceiver registered\n"); + + err = device_create_file(&pdev->dev, &dev_attr_enable_host); + if (err) { + dev_warn(&pdev->dev, "Can't register sysfs attribute\n"); + goto err_irq; + } + return 0; err_irq: @@ -461,17 +545,21 @@ 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; - /* store the interupt enable for cable ID and VBUS */ + int val; + DBG("%s(%d) BEGIN state : %s\n", __func__, __LINE__, + tegra_state_name(otg->state)); + clk_enable(tegra_otg->clk); - tegra_otg->intr_reg_data = readl(tegra_otg->regs + USB_PHY_WAKEUP); - writel(0, (tegra_otg->regs + USB_PHY_WAKEUP)); + val = readl(tegra_otg->regs + USB_PHY_WAKEUP); + val &= ~USB_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; - } + /* suspend peripheral mode, host mode is taken care by host driver */ + if (otg->state == OTG_STATE_B_PERIPHERAL) + tegra_change_otg_state(tegra_otg, OTG_STATE_A_SUSPEND); + + DBG("%s(%d) END\n", __func__, __LINE__); tegra_otg_disable_clk(); return 0; } @@ -480,34 +568,35 @@ 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 otg_transceiver *otg = &tegra_otg->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 */ + /* Clear pending interrupts */ 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); + writel(val, tegra_otg->regs + USB_PHY_WAKEUP); + DBG("%s(%d) PHY WAKEUP register : 0x%x\n", __func__, __LINE__, val); 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); - } + /* 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, OTG_STATE_A_SUSPEND); + + /* Enable interrupt and call work to set to appropriate state */ + spin_lock_irqsave(&tegra_otg->lock, flags); + tegra_otg->int_status = (val | USB_INT_EN); + spin_unlock_irqrestore(&tegra_otg->lock, flags); + irq_work(&tegra_otg->work); - return; + clk_enable(tegra_otg->clk); + val = readl(tegra_otg->regs + USB_PHY_WAKEUP); + val |= USB_INT_EN; + writel(val, tegra_otg->regs + USB_PHY_WAKEUP); + clk_disable(tegra_otg->clk); + DBG("%s(%d) END\n", __func__, __LINE__); } static const struct dev_pm_ops tegra_otg_pm_ops = { |