diff options
Diffstat (limited to 'drivers/usb/musb/am35x.c')
| -rw-r--r-- | drivers/usb/musb/am35x.c | 451 |
1 files changed, 275 insertions, 176 deletions
diff --git a/drivers/usb/musb/am35x.c b/drivers/usb/musb/am35x.c index b0aabf3a606..0a34dd85955 100644 --- a/drivers/usb/musb/am35x.c +++ b/drivers/usb/musb/am35x.c @@ -26,12 +26,14 @@ * */ -#include <linux/init.h> +#include <linux/module.h> #include <linux/clk.h> +#include <linux/err.h> #include <linux/io.h> - -#include <plat/control.h> -#include <plat/usb.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/usb/usb_phy_generic.h> +#include <linux/platform_data/usb-omap.h> #include "musb_core.h" @@ -80,51 +82,18 @@ #define USB_MENTOR_CORE_OFFSET 0x400 -static inline void phy_on(void) -{ - unsigned long timeout = jiffies + msecs_to_jiffies(100); - u32 devconf2; - - /* - * Start the on-chip PHY and its PLL. - */ - devconf2 = omap_ctrl_readl(AM35XX_CONTROL_DEVCONF2); - - devconf2 &= ~(CONF2_RESET | CONF2_PHYPWRDN | CONF2_OTGPWRDN); - devconf2 |= CONF2_PHY_PLLON; - - omap_ctrl_writel(devconf2, AM35XX_CONTROL_DEVCONF2); - - DBG(1, "Waiting for PHY clock good...\n"); - while (!(omap_ctrl_readl(AM35XX_CONTROL_DEVCONF2) - & CONF2_PHYCLKGD)) { - cpu_relax(); - - if (time_after(jiffies, timeout)) { - DBG(1, "musb PHY clock good timed out\n"); - break; - } - } -} - -static inline void phy_off(void) -{ - u32 devconf2; - - /* - * Power down the on-chip PHY. - */ - devconf2 = omap_ctrl_readl(AM35XX_CONTROL_DEVCONF2); - - devconf2 &= ~CONF2_PHY_PLLON; - devconf2 |= CONF2_PHYPWRDN | CONF2_OTGPWRDN; - omap_ctrl_writel(devconf2, AM35XX_CONTROL_DEVCONF2); -} +struct am35x_glue { + struct device *dev; + struct platform_device *musb; + struct platform_device *phy; + struct clk *phy_clk; + struct clk *clk; +}; /* - * musb_platform_enable - enable interrupts + * am35x_musb_enable - enable interrupts */ -void musb_platform_enable(struct musb *musb) +static void am35x_musb_enable(struct musb *musb) { void __iomem *reg_base = musb->ctrl_base; u32 epmask; @@ -137,15 +106,14 @@ void musb_platform_enable(struct musb *musb) musb_writel(reg_base, CORE_INTR_MASK_SET_REG, AM35X_INTR_USB_MASK); /* Force the DRVVBUS IRQ so we can start polling for ID change. */ - if (is_otg_enabled(musb)) - musb_writel(reg_base, CORE_INTR_SRC_SET_REG, - AM35X_INTR_DRVVBUS << AM35X_INTR_USB_SHIFT); + musb_writel(reg_base, CORE_INTR_SRC_SET_REG, + AM35X_INTR_DRVVBUS << AM35X_INTR_USB_SHIFT); } /* - * musb_platform_disable - disable HDRC and flush interrupts + * am35x_musb_disable - disable HDRC and flush interrupts */ -void musb_platform_disable(struct musb *musb) +static void am35x_musb_disable(struct musb *musb) { void __iomem *reg_base = musb->ctrl_base; @@ -156,13 +124,9 @@ void musb_platform_disable(struct musb *musb) musb_writel(reg_base, USB_END_OF_INTR_REG, 0); } -#ifdef CONFIG_USB_MUSB_HDRC_HCD #define portstate(stmt) stmt -#else -#define portstate(stmt) -#endif -static void am35x_set_vbus(struct musb *musb, int is_on) +static void am35x_musb_set_vbus(struct musb *musb, int is_on) { WARN_ON(is_on && is_peripheral_active(musb)); } @@ -183,7 +147,8 @@ static void otg_timer(unsigned long _musb) * status change events (from the transceiver) otherwise. */ devctl = musb_readb(mregs, MUSB_DEVCTL); - DBG(7, "Poll devctl %02x (%s)\n", devctl, otg_state_string(musb)); + dev_dbg(musb->controller, "Poll devctl %02x (%s)\n", devctl, + usb_otg_state_string(musb->xceiv->state)); spin_lock_irqsave(&musb->lock, flags); switch (musb->xceiv->state) { @@ -206,9 +171,6 @@ static void otg_timer(unsigned long _musb) MUSB_INTR_VBUSERROR << AM35X_INTR_USB_SHIFT); break; case OTG_STATE_B_IDLE: - if (!is_peripheral_enabled(musb)) - break; - devctl = musb_readb(mregs, MUSB_DEVCTL); if (devctl & MUSB_DEVCTL_BDEVICE) mod_timer(&otg_workaround, jiffies + POLL_SECONDS * HZ); @@ -221,43 +183,46 @@ static void otg_timer(unsigned long _musb) spin_unlock_irqrestore(&musb->lock, flags); } -void musb_platform_try_idle(struct musb *musb, unsigned long timeout) +static void am35x_musb_try_idle(struct musb *musb, unsigned long timeout) { static unsigned long last_timer; - if (!is_otg_enabled(musb)) - return; - if (timeout == 0) timeout = jiffies + msecs_to_jiffies(3); /* Never idle if active, or when VBUS timeout is not set as host */ if (musb->is_active || (musb->a_wait_bcon == 0 && musb->xceiv->state == OTG_STATE_A_WAIT_BCON)) { - DBG(4, "%s active, deleting timer\n", otg_state_string(musb)); + dev_dbg(musb->controller, "%s active, deleting timer\n", + usb_otg_state_string(musb->xceiv->state)); del_timer(&otg_workaround); last_timer = jiffies; return; } if (time_after(last_timer, timeout) && timer_pending(&otg_workaround)) { - DBG(4, "Longer idle timer already pending, ignoring...\n"); + dev_dbg(musb->controller, "Longer idle timer already pending, ignoring...\n"); return; } last_timer = timeout; - DBG(4, "%s inactive, starting idle timer for %u ms\n", - otg_state_string(musb), jiffies_to_msecs(timeout - jiffies)); + dev_dbg(musb->controller, "%s inactive, starting idle timer for %u ms\n", + usb_otg_state_string(musb->xceiv->state), + jiffies_to_msecs(timeout - jiffies)); mod_timer(&otg_workaround, timeout); } -static irqreturn_t am35x_interrupt(int irq, void *hci) +static irqreturn_t am35x_musb_interrupt(int irq, void *hci) { struct musb *musb = hci; void __iomem *reg_base = musb->ctrl_base; + struct device *dev = musb->controller; + struct musb_hdrc_platform_data *plat = dev_get_platdata(dev); + struct omap_musb_board_data *data = plat->board_data; + struct usb_otg *otg = musb->xceiv->otg; unsigned long flags; irqreturn_t ret = IRQ_NONE; - u32 epintr, usbintr, lvl_intr; + u32 epintr, usbintr; spin_lock_irqsave(&musb->lock, flags); @@ -298,8 +263,7 @@ static irqreturn_t am35x_interrupt(int irq, void *hci) u8 devctl = musb_readb(mregs, MUSB_DEVCTL); int err; - err = is_host_enabled(musb) && (musb->int_usb & - MUSB_INTR_VBUSERROR); + err = musb->int_usb & MUSB_INTR_VBUSERROR; if (err) { /* * The Mentor core doesn't debounce VBUS as needed @@ -316,29 +280,35 @@ static irqreturn_t am35x_interrupt(int irq, void *hci) musb->xceiv->state = OTG_STATE_A_WAIT_VFALL; mod_timer(&otg_workaround, jiffies + POLL_SECONDS * HZ); WARNING("VBUS error workaround (delay coming)\n"); - } else if (is_host_enabled(musb) && drvvbus) { + } else if (drvvbus) { MUSB_HST_MODE(musb); - musb->xceiv->default_a = 1; + otg->default_a = 1; musb->xceiv->state = OTG_STATE_A_WAIT_VRISE; portstate(musb->port1_status |= USB_PORT_STAT_POWER); del_timer(&otg_workaround); } else { musb->is_active = 0; MUSB_DEV_MODE(musb); - musb->xceiv->default_a = 0; + otg->default_a = 0; musb->xceiv->state = OTG_STATE_B_IDLE; portstate(musb->port1_status &= ~USB_PORT_STAT_POWER); } /* NOTE: this must complete power-on within 100 ms. */ - DBG(2, "VBUS %s (%s)%s, devctl %02x\n", + dev_dbg(musb->controller, "VBUS %s (%s)%s, devctl %02x\n", drvvbus ? "on" : "off", - otg_state_string(musb), + usb_otg_state_string(musb->xceiv->state), err ? " ERROR" : "", devctl); ret = IRQ_HANDLED; } + /* Drop spurious RX and TX if device is disconnected */ + if (musb->int_usb & MUSB_INTR_DISCONNECT) { + musb->int_tx = 0; + musb->int_rx = 0; + } + if (musb->int_tx || musb->int_rx || musb->int_usb) ret |= musb_interrupt(musb); @@ -346,15 +316,14 @@ eoi: /* EOI needs to be written for the IRQ to be re-asserted. */ if (ret == IRQ_HANDLED || epintr || usbintr) { /* clear level interrupt */ - lvl_intr = omap_ctrl_readl(AM35XX_CONTROL_LVL_INTR_CLEAR); - lvl_intr |= AM35XX_USBOTGSS_INT_CLR; - omap_ctrl_writel(lvl_intr, AM35XX_CONTROL_LVL_INTR_CLEAR); + if (data->clear_irq) + data->clear_irq(); /* write EOI */ musb_writel(reg_base, USB_END_OF_INTR_REG, 0); } /* Poll for ID change */ - if (is_otg_enabled(musb) && musb->xceiv->state == OTG_STATE_B_IDLE) + if (musb->xceiv->state == OTG_STATE_B_IDLE) mod_timer(&otg_workaround, jiffies + POLL_SECONDS * HZ); spin_unlock_irqrestore(&musb->lock, flags); @@ -362,137 +331,81 @@ eoi: return ret; } -int musb_platform_set_mode(struct musb *musb, u8 musb_mode) +static int am35x_musb_set_mode(struct musb *musb, u8 musb_mode) { - u32 devconf2 = omap_ctrl_readl(AM35XX_CONTROL_DEVCONF2); + struct device *dev = musb->controller; + struct musb_hdrc_platform_data *plat = dev_get_platdata(dev); + struct omap_musb_board_data *data = plat->board_data; + int retval = 0; - devconf2 &= ~CONF2_OTGMODE; - switch (musb_mode) { -#ifdef CONFIG_USB_MUSB_HDRC_HCD - case MUSB_HOST: /* Force VBUS valid, ID = 0 */ - devconf2 |= CONF2_FORCE_HOST; - break; -#endif -#ifdef CONFIG_USB_GADGET_MUSB_HDRC - case MUSB_PERIPHERAL: /* Force VBUS valid, ID = 1 */ - devconf2 |= CONF2_FORCE_DEVICE; - break; -#endif -#ifdef CONFIG_USB_MUSB_OTG - case MUSB_OTG: /* Don't override the VBUS/ID comparators */ - devconf2 |= CONF2_NO_OVERRIDE; - break; -#endif - default: - DBG(2, "Trying to set unsupported mode %u\n", musb_mode); - } + if (data->set_mode) + data->set_mode(musb_mode); + else + retval = -EIO; - omap_ctrl_writel(devconf2, AM35XX_CONTROL_DEVCONF2); - return 0; + return retval; } -int __init musb_platform_init(struct musb *musb, void *board_data) +static int am35x_musb_init(struct musb *musb) { + struct device *dev = musb->controller; + struct musb_hdrc_platform_data *plat = dev_get_platdata(dev); + struct omap_musb_board_data *data = plat->board_data; void __iomem *reg_base = musb->ctrl_base; - u32 rev, lvl_intr, sw_reset; - int status; + u32 rev; musb->mregs += USB_MENTOR_CORE_OFFSET; - clk_enable(musb->clock); - DBG(2, "musb->clock=%lud\n", clk_get_rate(musb->clock)); - - musb->phy_clock = clk_get(musb->controller, "fck"); - if (IS_ERR(musb->phy_clock)) { - status = PTR_ERR(musb->phy_clock); - goto exit0; - } - clk_enable(musb->phy_clock); - DBG(2, "musb->phy_clock=%lud\n", clk_get_rate(musb->phy_clock)); - /* Returns zero if e.g. not clocked */ rev = musb_readl(reg_base, USB_REVISION_REG); - if (!rev) { - status = -ENODEV; - goto exit1; - } - - usb_nop_xceiv_register(); - musb->xceiv = otg_get_transceiver(); - if (!musb->xceiv) { - status = -ENODEV; - goto exit1; - } - - if (is_host_enabled(musb)) - setup_timer(&otg_workaround, otg_timer, (unsigned long) musb); + if (!rev) + return -ENODEV; - musb->board_set_vbus = am35x_set_vbus; + musb->xceiv = usb_get_phy(USB_PHY_TYPE_USB2); + if (IS_ERR_OR_NULL(musb->xceiv)) + return -EPROBE_DEFER; - /* Global reset */ - sw_reset = omap_ctrl_readl(AM35XX_CONTROL_IP_SW_RESET); + setup_timer(&otg_workaround, otg_timer, (unsigned long) musb); - sw_reset |= AM35XX_USBOTGSS_SW_RST; - omap_ctrl_writel(sw_reset, AM35XX_CONTROL_IP_SW_RESET); - - sw_reset &= ~AM35XX_USBOTGSS_SW_RST; - omap_ctrl_writel(sw_reset, AM35XX_CONTROL_IP_SW_RESET); + /* Reset the musb */ + if (data->reset) + data->reset(); /* Reset the controller */ musb_writel(reg_base, USB_CTRL_REG, AM35X_SOFT_RESET_MASK); /* Start the on-chip PHY and its PLL. */ - phy_on(); + if (data->set_phy_power) + data->set_phy_power(1); msleep(5); - musb->isr = am35x_interrupt; + musb->isr = am35x_musb_interrupt; /* clear level interrupt */ - lvl_intr = omap_ctrl_readl(AM35XX_CONTROL_LVL_INTR_CLEAR); - lvl_intr |= AM35XX_USBOTGSS_INT_CLR; - omap_ctrl_writel(lvl_intr, AM35XX_CONTROL_LVL_INTR_CLEAR); + if (data->clear_irq) + data->clear_irq(); + return 0; -exit1: - clk_disable(musb->phy_clock); - clk_put(musb->phy_clock); -exit0: - clk_disable(musb->clock); - return status; } -int musb_platform_exit(struct musb *musb) +static int am35x_musb_exit(struct musb *musb) { - if (is_host_enabled(musb)) - del_timer_sync(&otg_workaround); + struct device *dev = musb->controller; + struct musb_hdrc_platform_data *plat = dev_get_platdata(dev); + struct omap_musb_board_data *data = plat->board_data; - phy_off(); + del_timer_sync(&otg_workaround); - otg_put_transceiver(musb->xceiv); - usb_nop_xceiv_unregister(); + /* Shutdown the on-chip PHY and its PLL. */ + if (data->set_phy_power) + data->set_phy_power(0); - clk_disable(musb->clock); - - clk_disable(musb->phy_clock); - clk_put(musb->phy_clock); + usb_put_phy(musb->xceiv); return 0; } -#ifdef CONFIG_PM -void musb_platform_save_context(struct musb *musb, - struct musb_context_registers *musb_context) -{ - phy_off(); -} - -void musb_platform_restore_context(struct musb *musb, - struct musb_context_registers *musb_context) -{ - phy_on(); -} -#endif - /* AM35x supports only 32bit read operation */ void musb_read_fifo(struct musb_hw_ep *hw_ep, u16 len, u8 *dst) { @@ -522,3 +435,189 @@ void musb_read_fifo(struct musb_hw_ep *hw_ep, u16 len, u8 *dst) memcpy(dst, &val, len); } } + +static const struct musb_platform_ops am35x_ops = { + .init = am35x_musb_init, + .exit = am35x_musb_exit, + + .enable = am35x_musb_enable, + .disable = am35x_musb_disable, + + .set_mode = am35x_musb_set_mode, + .try_idle = am35x_musb_try_idle, + + .set_vbus = am35x_musb_set_vbus, +}; + +static const struct platform_device_info am35x_dev_info = { + .name = "musb-hdrc", + .id = PLATFORM_DEVID_AUTO, + .dma_mask = DMA_BIT_MASK(32), +}; + +static int am35x_probe(struct platform_device *pdev) +{ + struct musb_hdrc_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct platform_device *musb; + struct am35x_glue *glue; + struct platform_device_info pinfo; + struct clk *phy_clk; + struct clk *clk; + + int ret = -ENOMEM; + + glue = kzalloc(sizeof(*glue), GFP_KERNEL); + if (!glue) { + dev_err(&pdev->dev, "failed to allocate glue context\n"); + goto err0; + } + + phy_clk = clk_get(&pdev->dev, "fck"); + if (IS_ERR(phy_clk)) { + dev_err(&pdev->dev, "failed to get PHY clock\n"); + ret = PTR_ERR(phy_clk); + goto err3; + } + + clk = clk_get(&pdev->dev, "ick"); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "failed to get clock\n"); + ret = PTR_ERR(clk); + goto err4; + } + + ret = clk_enable(phy_clk); + if (ret) { + dev_err(&pdev->dev, "failed to enable PHY clock\n"); + goto err5; + } + + ret = clk_enable(clk); + if (ret) { + dev_err(&pdev->dev, "failed to enable clock\n"); + goto err6; + } + + glue->dev = &pdev->dev; + glue->phy_clk = phy_clk; + glue->clk = clk; + + pdata->platform_ops = &am35x_ops; + + glue->phy = usb_phy_generic_register(); + if (IS_ERR(glue->phy)) + goto err7; + platform_set_drvdata(pdev, glue); + + pinfo = am35x_dev_info; + pinfo.parent = &pdev->dev; + pinfo.res = pdev->resource; + pinfo.num_res = pdev->num_resources; + pinfo.data = pdata; + pinfo.size_data = sizeof(*pdata); + + glue->musb = musb = platform_device_register_full(&pinfo); + if (IS_ERR(musb)) { + ret = PTR_ERR(musb); + dev_err(&pdev->dev, "failed to register musb device: %d\n", ret); + goto err8; + } + + return 0; + +err8: + usb_phy_generic_unregister(glue->phy); + +err7: + clk_disable(clk); + +err6: + clk_disable(phy_clk); + +err5: + clk_put(clk); + +err4: + clk_put(phy_clk); + +err3: + kfree(glue); + +err0: + return ret; +} + +static int am35x_remove(struct platform_device *pdev) +{ + struct am35x_glue *glue = platform_get_drvdata(pdev); + + platform_device_unregister(glue->musb); + usb_phy_generic_unregister(glue->phy); + clk_disable(glue->clk); + clk_disable(glue->phy_clk); + clk_put(glue->clk); + clk_put(glue->phy_clk); + kfree(glue); + + return 0; +} + +#ifdef CONFIG_PM +static int am35x_suspend(struct device *dev) +{ + struct am35x_glue *glue = dev_get_drvdata(dev); + struct musb_hdrc_platform_data *plat = dev_get_platdata(dev); + struct omap_musb_board_data *data = plat->board_data; + + /* Shutdown the on-chip PHY and its PLL. */ + if (data->set_phy_power) + data->set_phy_power(0); + + clk_disable(glue->phy_clk); + clk_disable(glue->clk); + + return 0; +} + +static int am35x_resume(struct device *dev) +{ + struct am35x_glue *glue = dev_get_drvdata(dev); + struct musb_hdrc_platform_data *plat = dev_get_platdata(dev); + struct omap_musb_board_data *data = plat->board_data; + int ret; + + /* Start the on-chip PHY and its PLL. */ + if (data->set_phy_power) + data->set_phy_power(1); + + ret = clk_enable(glue->phy_clk); + if (ret) { + dev_err(dev, "failed to enable PHY clock\n"); + return ret; + } + + ret = clk_enable(glue->clk); + if (ret) { + dev_err(dev, "failed to enable clock\n"); + return ret; + } + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(am35x_pm_ops, am35x_suspend, am35x_resume); + +static struct platform_driver am35x_driver = { + .probe = am35x_probe, + .remove = am35x_remove, + .driver = { + .name = "musb-am35x", + .pm = &am35x_pm_ops, + }, +}; + +MODULE_DESCRIPTION("AM35x MUSB Glue Layer"); +MODULE_AUTHOR("Ajay Kumar Gupta <ajay.gupta@ti.com>"); +MODULE_LICENSE("GPL v2"); +module_platform_driver(am35x_driver); |
