diff options
Diffstat (limited to 'drivers/usb/musb/ux500.c')
| -rw-r--r-- | drivers/usb/musb/ux500.c | 274 |
1 files changed, 220 insertions, 54 deletions
diff --git a/drivers/usb/musb/ux500.c b/drivers/usb/musb/ux500.c index d62a91fedc2..f202e508846 100644 --- a/drivers/usb/musb/ux500.c +++ b/drivers/usb/musb/ux500.c @@ -21,14 +21,22 @@ #include <linux/module.h> #include <linux/kernel.h> -#include <linux/init.h> #include <linux/clk.h> #include <linux/err.h> #include <linux/io.h> +#include <linux/of.h> #include <linux/platform_device.h> +#include <linux/usb/musb-ux500.h> #include "musb_core.h" +static struct musb_hdrc_config ux500_musb_hdrc_config = { + .multipoint = true, + .dyn_fifo = true, + .num_eps = 16, + .ram_bits = 16, +}; + struct ux500_glue { struct device *dev; struct platform_device *musb; @@ -36,19 +44,144 @@ struct ux500_glue { }; #define glue_to_musb(g) platform_get_drvdata(g->musb) +static void ux500_musb_set_vbus(struct musb *musb, int is_on) +{ + u8 devctl; + unsigned long timeout = jiffies + msecs_to_jiffies(1000); + /* HDRC controls CPEN, but beware current surges during device + * connect. They can trigger transient overcurrent conditions + * that must be ignored. + */ + + devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + + if (is_on) { + if (musb->xceiv->state == OTG_STATE_A_IDLE) { + /* start the session */ + devctl |= MUSB_DEVCTL_SESSION; + musb_writeb(musb->mregs, MUSB_DEVCTL, devctl); + /* + * Wait for the musb to set as A device to enable the + * VBUS + */ + while (musb_readb(musb->mregs, MUSB_DEVCTL) & 0x80) { + + if (time_after(jiffies, timeout)) { + dev_err(musb->controller, + "configured as A device timeout"); + break; + } + } + + } else { + musb->is_active = 1; + musb->xceiv->otg->default_a = 1; + musb->xceiv->state = OTG_STATE_A_WAIT_VRISE; + devctl |= MUSB_DEVCTL_SESSION; + MUSB_HST_MODE(musb); + } + } else { + musb->is_active = 0; + + /* NOTE: we're skipping A_WAIT_VFALL -> A_IDLE and jumping + * right to B_IDLE... + */ + musb->xceiv->otg->default_a = 0; + devctl &= ~MUSB_DEVCTL_SESSION; + MUSB_DEV_MODE(musb); + } + musb_writeb(musb->mregs, MUSB_DEVCTL, devctl); + + /* + * Devctl values will be updated after vbus goes below + * session_valid. The time taken depends on the capacitance + * on VBUS line. The max discharge time can be upto 1 sec + * as per the spec. Typically on our platform, it is 200ms + */ + if (!is_on) + mdelay(200); + + dev_dbg(musb->controller, "VBUS %s, devctl %02x\n", + usb_otg_state_string(musb->xceiv->state), + musb_readb(musb->mregs, MUSB_DEVCTL)); +} + +static int musb_otg_notifications(struct notifier_block *nb, + unsigned long event, void *unused) +{ + struct musb *musb = container_of(nb, struct musb, nb); + + dev_dbg(musb->controller, "musb_otg_notifications %ld %s\n", + event, usb_otg_state_string(musb->xceiv->state)); + + switch (event) { + case UX500_MUSB_ID: + dev_dbg(musb->controller, "ID GND\n"); + ux500_musb_set_vbus(musb, 1); + break; + case UX500_MUSB_VBUS: + dev_dbg(musb->controller, "VBUS Connect\n"); + break; + case UX500_MUSB_NONE: + dev_dbg(musb->controller, "VBUS Disconnect\n"); + if (is_host_active(musb)) + ux500_musb_set_vbus(musb, 0); + else + musb->xceiv->state = OTG_STATE_B_IDLE; + break; + default: + dev_dbg(musb->controller, "ID float\n"); + return NOTIFY_DONE; + } + return NOTIFY_OK; +} + +static irqreturn_t ux500_musb_interrupt(int irq, void *__hci) +{ + unsigned long flags; + irqreturn_t retval = IRQ_NONE; + struct musb *musb = __hci; + + spin_lock_irqsave(&musb->lock, flags); + + musb->int_usb = musb_readb(musb->mregs, MUSB_INTRUSB); + musb->int_tx = musb_readw(musb->mregs, MUSB_INTRTX); + musb->int_rx = musb_readw(musb->mregs, MUSB_INTRRX); + + if (musb->int_usb || musb->int_tx || musb->int_rx) + retval = musb_interrupt(musb); + + spin_unlock_irqrestore(&musb->lock, flags); + + return retval; +} + static int ux500_musb_init(struct musb *musb) { + int status; + musb->xceiv = usb_get_phy(USB_PHY_TYPE_USB2); if (IS_ERR_OR_NULL(musb->xceiv)) { pr_err("HS USB OTG: no transceiver configured\n"); - return -ENODEV; + return -EPROBE_DEFER; } + musb->nb.notifier_call = musb_otg_notifications; + status = usb_register_notifier(musb->xceiv, &musb->nb); + if (status < 0) { + dev_dbg(musb->controller, "notification register failed\n"); + return status; + } + + musb->isr = ux500_musb_interrupt; + return 0; } static int ux500_musb_exit(struct musb *musb) { + usb_unregister_notifier(musb->xceiv, &musb->nb); + usb_put_phy(musb->xceiv); return 0; @@ -57,53 +190,89 @@ static int ux500_musb_exit(struct musb *musb) static const struct musb_platform_ops ux500_ops = { .init = ux500_musb_init, .exit = ux500_musb_exit, + + .set_vbus = ux500_musb_set_vbus, }; -static int __devinit ux500_probe(struct platform_device *pdev) +static struct musb_hdrc_platform_data * +ux500_of_probe(struct platform_device *pdev, struct device_node *np) +{ + struct musb_hdrc_platform_data *pdata; + const char *mode; + int strlen; + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return NULL; + + mode = of_get_property(np, "dr_mode", &strlen); + if (!mode) { + dev_err(&pdev->dev, "No 'dr_mode' property found\n"); + return NULL; + } + + if (strlen > 0) { + if (!strcmp(mode, "host")) + pdata->mode = MUSB_HOST; + if (!strcmp(mode, "otg")) + pdata->mode = MUSB_OTG; + if (!strcmp(mode, "peripheral")) + pdata->mode = MUSB_PERIPHERAL; + } + + return pdata; +} + +static int ux500_probe(struct platform_device *pdev) { - struct musb_hdrc_platform_data *pdata = pdev->dev.platform_data; + struct resource musb_resources[2]; + struct musb_hdrc_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct device_node *np = pdev->dev.of_node; struct platform_device *musb; struct ux500_glue *glue; struct clk *clk; - int ret = -ENOMEM; + if (!pdata) { + if (np) { + pdata = ux500_of_probe(pdev, np); + if (!pdata) + goto err0; + + pdev->dev.platform_data = pdata; + } else { + dev_err(&pdev->dev, "no pdata or device tree found\n"); + goto err0; + } + } + glue = kzalloc(sizeof(*glue), GFP_KERNEL); if (!glue) { dev_err(&pdev->dev, "failed to allocate glue context\n"); goto err0; } - /* get the musb id */ - musbid = musb_get_id(&pdev->dev, GFP_KERNEL); - if (musbid < 0) { - dev_err(&pdev->dev, "failed to allocate musb id\n"); - ret = -ENOMEM; - goto err1; - } - - musb = platform_device_alloc("musb-hdrc", musbid); + musb = platform_device_alloc("musb-hdrc", PLATFORM_DEVID_AUTO); if (!musb) { dev_err(&pdev->dev, "failed to allocate musb device\n"); - goto err2; + goto err1; } - clk = clk_get(&pdev->dev, "usb"); + clk = clk_get(&pdev->dev, NULL); if (IS_ERR(clk)) { dev_err(&pdev->dev, "failed to get clock\n"); ret = PTR_ERR(clk); goto err3; } - ret = clk_enable(clk); + ret = clk_prepare_enable(clk); if (ret) { dev_err(&pdev->dev, "failed to enable clock\n"); goto err4; } - musb->id = musbid; musb->dev.parent = &pdev->dev; - musb->dev.dma_mask = pdev->dev.dma_mask; + musb->dev.dma_mask = &pdev->dev.coherent_dma_mask; musb->dev.coherent_dma_mask = pdev->dev.coherent_dma_mask; glue->dev = &pdev->dev; @@ -111,11 +280,25 @@ static int __devinit ux500_probe(struct platform_device *pdev) glue->clk = clk; pdata->platform_ops = &ux500_ops; + pdata->config = &ux500_musb_hdrc_config; platform_set_drvdata(pdev, glue); - ret = platform_device_add_resources(musb, pdev->resource, - pdev->num_resources); + memset(musb_resources, 0x00, sizeof(*musb_resources) * + ARRAY_SIZE(musb_resources)); + + musb_resources[0].name = pdev->resource[0].name; + musb_resources[0].start = pdev->resource[0].start; + musb_resources[0].end = pdev->resource[0].end; + musb_resources[0].flags = pdev->resource[0].flags; + + musb_resources[1].name = pdev->resource[1].name; + musb_resources[1].start = pdev->resource[1].start; + musb_resources[1].end = pdev->resource[1].end; + musb_resources[1].flags = pdev->resource[1].flags; + + ret = platform_device_add_resources(musb, musb_resources, + ARRAY_SIZE(musb_resources)); if (ret) { dev_err(&pdev->dev, "failed to add resources\n"); goto err5; @@ -136,7 +319,7 @@ static int __devinit ux500_probe(struct platform_device *pdev) return 0; err5: - clk_disable(clk); + clk_disable_unprepare(clk); err4: clk_put(clk); @@ -144,9 +327,6 @@ err4: err3: platform_device_put(musb); -err2: - musb_put_id(&pdev->dev, musbid); - err1: kfree(glue); @@ -154,14 +334,12 @@ err0: return ret; } -static int __devexit ux500_remove(struct platform_device *pdev) +static int ux500_remove(struct platform_device *pdev) { struct ux500_glue *glue = platform_get_drvdata(pdev); - musb_put_id(&pdev->dev, glue->musb->id); - platform_device_del(glue->musb); - platform_device_put(glue->musb); - clk_disable(glue->clk); + platform_device_unregister(glue->musb); + clk_disable_unprepare(glue->clk); clk_put(glue->clk); kfree(glue); @@ -175,7 +353,7 @@ static int ux500_suspend(struct device *dev) struct musb *musb = glue_to_musb(glue); usb_phy_set_suspend(musb->xceiv, 1); - clk_disable(glue->clk); + clk_disable_unprepare(glue->clk); return 0; } @@ -186,7 +364,7 @@ static int ux500_resume(struct device *dev) struct musb *musb = glue_to_musb(glue); int ret; - ret = clk_enable(glue->clk); + ret = clk_prepare_enable(glue->clk); if (ret) { dev_err(dev, "failed to enable clock\n"); return ret; @@ -196,38 +374,26 @@ static int ux500_resume(struct device *dev) return 0; } +#endif -static const struct dev_pm_ops ux500_pm_ops = { - .suspend = ux500_suspend, - .resume = ux500_resume, -}; +static SIMPLE_DEV_PM_OPS(ux500_pm_ops, ux500_suspend, ux500_resume); -#define DEV_PM_OPS (&ux500_pm_ops) -#else -#define DEV_PM_OPS NULL -#endif +static const struct of_device_id ux500_match[] = { + { .compatible = "stericsson,db8500-musb", }, + {} +}; static struct platform_driver ux500_driver = { .probe = ux500_probe, - .remove = __devexit_p(ux500_remove), + .remove = ux500_remove, .driver = { .name = "musb-ux500", - .pm = DEV_PM_OPS, + .pm = &ux500_pm_ops, + .of_match_table = ux500_match, }, }; MODULE_DESCRIPTION("UX500 MUSB Glue Layer"); MODULE_AUTHOR("Mian Yousaf Kaukab <mian.yousaf.kaukab@stericsson.com>"); MODULE_LICENSE("GPL v2"); - -static int __init ux500_init(void) -{ - return platform_driver_register(&ux500_driver); -} -module_init(ux500_init); - -static void __exit ux500_exit(void) -{ - platform_driver_unregister(&ux500_driver); -} -module_exit(ux500_exit); +module_platform_driver(ux500_driver); |
