diff options
Diffstat (limited to 'drivers/usb/musb/blackfin.c')
| -rw-r--r-- | drivers/usb/musb/blackfin.c | 391 |
1 files changed, 303 insertions, 88 deletions
diff --git a/drivers/usb/musb/blackfin.c b/drivers/usb/musb/blackfin.c index ad26e656966..d40d5f0b552 100644 --- a/drivers/usb/musb/blackfin.c +++ b/drivers/usb/musb/blackfin.c @@ -11,46 +11,59 @@ #include <linux/module.h> #include <linux/kernel.h> #include <linux/sched.h> -#include <linux/slab.h> -#include <linux/init.h> #include <linux/list.h> #include <linux/gpio.h> #include <linux/io.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/prefetch.h> +#include <linux/usb/usb_phy_generic.h> #include <asm/cacheflush.h> #include "musb_core.h" +#include "musbhsdma.h" #include "blackfin.h" +struct bfin_glue { + struct device *dev; + struct platform_device *musb; + struct platform_device *phy; +}; +#define glue_to_musb(g) platform_get_drvdata(g->musb) + /* * Load an endpoint's FIFO */ void musb_write_fifo(struct musb_hw_ep *hw_ep, u16 len, const u8 *src) { + struct musb *musb = hw_ep->musb; void __iomem *fifo = hw_ep->fifo; void __iomem *epio = hw_ep->regs; u8 epnum = hw_ep->epnum; - u16 dma_reg = 0; prefetch((u8 *)src); musb_writew(epio, MUSB_TXCOUNT, len); - DBG(4, "TX ep%d fifo %p count %d buf %p, epio %p\n", + dev_dbg(musb->controller, "TX ep%d fifo %p count %d buf %p, epio %p\n", hw_ep->epnum, fifo, len, src, epio); dump_fifo_data(src, len); if (!ANOMALY_05000380 && epnum != 0) { - flush_dcache_range((unsigned int)src, - (unsigned int)(src + len)); + u16 dma_reg; + + flush_dcache_range((unsigned long)src, + (unsigned long)(src + len)); /* Setup DMA address register */ - dma_reg = (u16) ((u32) src & 0xFFFF); + dma_reg = (u32)src; bfin_write16(USB_DMA_REG(epnum, USB_DMAx_ADDR_LOW), dma_reg); SSYNC(); - dma_reg = (u16) (((u32) src >> 16) & 0xFFFF); + dma_reg = (u32)src >> 16; bfin_write16(USB_DMA_REG(epnum, USB_DMAx_ADDR_HIGH), dma_reg); SSYNC(); @@ -64,7 +77,7 @@ void musb_write_fifo(struct musb_hw_ep *hw_ep, u16 len, const u8 *src) bfin_write16(USB_DMA_REG(epnum, USB_DMAx_CTRL), dma_reg); SSYNC(); - /* Wait for compelete */ + /* Wait for complete */ while (!(bfin_read_USB_DMA_INTERRUPT() & (1 << epnum))) cpu_relax(); @@ -79,12 +92,9 @@ void musb_write_fifo(struct musb_hw_ep *hw_ep, u16 len, const u8 *src) SSYNC(); if (unlikely((unsigned long)src & 0x01)) - outsw_8((unsigned long)fifo, src, - len & 0x01 ? (len >> 1) + 1 : len >> 1); + outsw_8((unsigned long)fifo, src, (len + 1) >> 1); else - outsw((unsigned long)fifo, src, - len & 0x01 ? (len >> 1) + 1 : len >> 1); - + outsw((unsigned long)fifo, src, (len + 1) >> 1); } } /* @@ -92,21 +102,22 @@ void musb_write_fifo(struct musb_hw_ep *hw_ep, u16 len, const u8 *src) */ void musb_read_fifo(struct musb_hw_ep *hw_ep, u16 len, u8 *dst) { + struct musb *musb = hw_ep->musb; void __iomem *fifo = hw_ep->fifo; u8 epnum = hw_ep->epnum; - u16 dma_reg = 0; if (ANOMALY_05000467 && epnum != 0) { + u16 dma_reg; - invalidate_dcache_range((unsigned int)dst, - (unsigned int)(dst + len)); + invalidate_dcache_range((unsigned long)dst, + (unsigned long)(dst + len)); /* Setup DMA address register */ - dma_reg = (u16) ((u32) dst & 0xFFFF); + dma_reg = (u32)dst; bfin_write16(USB_DMA_REG(epnum, USB_DMAx_ADDR_LOW), dma_reg); SSYNC(); - dma_reg = (u16) (((u32) dst >> 16) & 0xFFFF); + dma_reg = (u32)dst >> 16; bfin_write16(USB_DMA_REG(epnum, USB_DMAx_ADDR_HIGH), dma_reg); SSYNC(); @@ -120,7 +131,7 @@ void musb_read_fifo(struct musb_hw_ep *hw_ep, u16 len, u8 *dst) bfin_write16(USB_DMA_REG(epnum, USB_DMAx_CTRL), dma_reg); SSYNC(); - /* Wait for compelete */ + /* Wait for complete */ while (!(bfin_read_USB_DMA_INTERRUPT() & (1 << epnum))) cpu_relax(); @@ -148,7 +159,7 @@ void musb_read_fifo(struct musb_hw_ep *hw_ep, u16 len, u8 *dst) *(dst + len - 1) = (u8)inw((unsigned long)fifo + 4); } } - DBG(4, "%cX ep%d fifo %p count %d buf %p\n", + dev_dbg(musb->controller, "%cX ep%d fifo %p count %d buf %p\n", 'R', hw_ep->epnum, fifo, len, dst); dump_fifo_data(dst, len); @@ -173,15 +184,17 @@ static irqreturn_t blackfin_interrupt(int irq, void *__hci) retval = musb_interrupt(musb); } - spin_unlock_irqrestore(&musb->lock, flags); + /* Start sampling ID pin, when plug is removed from MUSB */ + if ((musb->xceiv->state == OTG_STATE_B_IDLE + || musb->xceiv->state == OTG_STATE_A_WAIT_BCON) || + (musb->int_usb & MUSB_INTR_DISCONNECT && is_host_active(musb))) { + mod_timer(&musb_conn_timer, jiffies + TIMER_DELAY); + musb->a_wait_bcon = TIMER_DELAY; + } - /* REVISIT we sometimes get spurious IRQs on g_ep0 - * not clear why... fall in BF54x too. - */ - if (retval != IRQ_HANDLED) - DBG(5, "spurious?\n"); + spin_unlock_irqrestore(&musb->lock, flags); - return IRQ_HANDLED; + return retval; } static void musb_conn_timer_handler(unsigned long _musb) @@ -189,6 +202,7 @@ static void musb_conn_timer_handler(unsigned long _musb) struct musb *musb = (void *)_musb; unsigned long flags; u16 val; + static u8 toggle; spin_lock_irqsave(&musb->lock, flags); switch (musb->xceiv->state) { @@ -196,10 +210,39 @@ static void musb_conn_timer_handler(unsigned long _musb) case OTG_STATE_A_WAIT_BCON: /* Start a new session */ val = musb_readw(musb->mregs, MUSB_DEVCTL); + val &= ~MUSB_DEVCTL_SESSION; + musb_writew(musb->mregs, MUSB_DEVCTL, val); val |= MUSB_DEVCTL_SESSION; musb_writew(musb->mregs, MUSB_DEVCTL, val); + /* Check if musb is host or peripheral. */ + val = musb_readw(musb->mregs, MUSB_DEVCTL); + + if (!(val & MUSB_DEVCTL_BDEVICE)) { + gpio_set_value(musb->config->gpio_vrsel, 1); + musb->xceiv->state = OTG_STATE_A_WAIT_BCON; + } else { + gpio_set_value(musb->config->gpio_vrsel, 0); + /* Ignore VBUSERROR and SUSPEND IRQ */ + val = musb_readb(musb->mregs, MUSB_INTRUSBE); + val &= ~MUSB_INTR_VBUSERROR; + musb_writeb(musb->mregs, MUSB_INTRUSBE, val); + val = MUSB_INTR_SUSPEND | MUSB_INTR_VBUSERROR; + musb_writeb(musb->mregs, MUSB_INTRUSB, val); + musb->xceiv->state = OTG_STATE_B_IDLE; + } + mod_timer(&musb_conn_timer, jiffies + TIMER_DELAY); + break; + case OTG_STATE_B_IDLE: + /* + * Start a new session. It seems that MUSB needs taking + * some time to recognize the type of the plug inserted? + */ + val = musb_readw(musb->mregs, MUSB_DEVCTL); + val |= MUSB_DEVCTL_SESSION; + musb_writew(musb->mregs, MUSB_DEVCTL, val); val = musb_readw(musb->mregs, MUSB_DEVCTL); + if (!(val & MUSB_DEVCTL_BDEVICE)) { gpio_set_value(musb->config->gpio_vrsel, 1); musb->xceiv->state = OTG_STATE_A_WAIT_BCON; @@ -214,93 +257,98 @@ static void musb_conn_timer_handler(unsigned long _musb) val = MUSB_INTR_SUSPEND | MUSB_INTR_VBUSERROR; musb_writeb(musb->mregs, MUSB_INTRUSB, val); - val = MUSB_POWER_HSENAB; - musb_writeb(musb->mregs, MUSB_POWER, val); + /* Toggle the Soft Conn bit, so that we can response to + * the inserting of either A-plug or B-plug. + */ + if (toggle) { + val = musb_readb(musb->mregs, MUSB_POWER); + val &= ~MUSB_POWER_SOFTCONN; + musb_writeb(musb->mregs, MUSB_POWER, val); + toggle = 0; + } else { + val = musb_readb(musb->mregs, MUSB_POWER); + val |= MUSB_POWER_SOFTCONN; + musb_writeb(musb->mregs, MUSB_POWER, val); + toggle = 1; + } + /* The delay time is set to 1/4 second by default, + * shortening it, if accelerating A-plug detection + * is needed in OTG mode. + */ + mod_timer(&musb_conn_timer, jiffies + TIMER_DELAY / 4); } - mod_timer(&musb_conn_timer, jiffies + TIMER_DELAY); break; - default: - DBG(1, "%s state not handled\n", otg_state_string(musb)); + dev_dbg(musb->controller, "%s state not handled\n", + usb_otg_state_string(musb->xceiv->state)); break; } spin_unlock_irqrestore(&musb->lock, flags); - DBG(4, "state is %s\n", otg_state_string(musb)); + dev_dbg(musb->controller, "state is %s\n", + usb_otg_state_string(musb->xceiv->state)); } -void musb_platform_enable(struct musb *musb) +static void bfin_musb_enable(struct musb *musb) { - if (is_host_enabled(musb)) { - mod_timer(&musb_conn_timer, jiffies + TIMER_DELAY); - musb->a_wait_bcon = TIMER_DELAY; - } + /* REVISIT is this really correct ? */ } -void musb_platform_disable(struct musb *musb) +static void bfin_musb_disable(struct musb *musb) { } -static void bfin_vbus_power(struct musb *musb, int is_on, int sleeping) +static void bfin_musb_set_vbus(struct musb *musb, int is_on) { -} + int value = musb->config->gpio_vrsel_active; + if (!is_on) + value = !value; + gpio_set_value(musb->config->gpio_vrsel, value); -static void bfin_set_vbus(struct musb *musb, int is_on) -{ - if (is_on) - gpio_set_value(musb->config->gpio_vrsel, 1); - else - gpio_set_value(musb->config->gpio_vrsel, 0); - - DBG(1, "VBUS %s, devctl %02x " + dev_dbg(musb->controller, "VBUS %s, devctl %02x " /* otg %3x conf %08x prcm %08x */ "\n", - otg_state_string(musb), + usb_otg_state_string(musb->xceiv->state), musb_readb(musb->mregs, MUSB_DEVCTL)); } -static int bfin_set_power(struct otg_transceiver *x, unsigned mA) +static int bfin_musb_set_power(struct usb_phy *x, unsigned mA) { return 0; } -void musb_platform_try_idle(struct musb *musb, unsigned long timeout) -{ - if (is_host_enabled(musb)) - mod_timer(&musb_conn_timer, jiffies + TIMER_DELAY); -} - -int musb_platform_get_vbus_status(struct musb *musb) +static int bfin_musb_vbus_status(struct musb *musb) { return 0; } -int musb_platform_set_mode(struct musb *musb, u8 musb_mode) +static int bfin_musb_set_mode(struct musb *musb, u8 musb_mode) { return -EIO; } -int __init musb_platform_init(struct musb *musb) +static int bfin_musb_adjust_channel_params(struct dma_channel *channel, + u16 packet_sz, u8 *mode, + dma_addr_t *dma_addr, u32 *len) { + struct musb_dma_channel *musb_channel = channel->private_data; /* - * Rev 1.0 BF549 EZ-KITs require PE7 to be high for both DEVICE - * and OTG HOST modes, while rev 1.1 and greater require PE7 to - * be low for DEVICE mode and high for HOST mode. We set it high - * here because we are in host mode + * Anomaly 05000450 might cause data corruption when using DMA + * MODE 1 transmits with short packet. So to work around this, + * we truncate all MODE 1 transfers down to a multiple of the + * max packet size, and then do the last short packet transfer + * (if there is any) using MODE 0. */ - - if (gpio_request(musb->config->gpio_vrsel, "USB_VRSEL")) { - printk(KERN_ERR "Failed ro request USB_VRSEL GPIO_%d \n", - musb->config->gpio_vrsel); - return -ENODEV; + if (ANOMALY_05000450) { + if (musb_channel->transmit && *mode == 1) + *len = *len - (*len % packet_sz); } - gpio_direction_output(musb->config->gpio_vrsel, 0); - usb_nop_xceiv_register(); - musb->xceiv = otg_get_transceiver(); - if (!musb->xceiv) - return -ENODEV; + return 0; +} +static void bfin_musb_reg_init(struct musb *musb) +{ if (ANOMALY_05000346) { bfin_write_USB_APHY_CALIB(ANOMALY_05000346_value); SSYNC(); @@ -312,7 +360,8 @@ int __init musb_platform_init(struct musb *musb) } /* Configure PLL oscillator register */ - bfin_write_USB_PLLOSC_CTRL(0x30a8); + bfin_write_USB_PLLOSC_CTRL(0x3080 | + ((480/musb->config->clkin) << 1)); SSYNC(); bfin_write_USB_SRP_CLKDIV((get_sclk()/1000) / 32 - 1); @@ -334,37 +383,203 @@ int __init musb_platform_init(struct musb *musb) EP2_RX_ENA | EP3_RX_ENA | EP4_RX_ENA | EP5_RX_ENA | EP6_RX_ENA | EP7_RX_ENA); SSYNC(); +} + +static int bfin_musb_init(struct musb *musb) +{ - if (is_host_enabled(musb)) { - musb->board_set_vbus = bfin_set_vbus; - setup_timer(&musb_conn_timer, - musb_conn_timer_handler, (unsigned long) musb); + /* + * Rev 1.0 BF549 EZ-KITs require PE7 to be high for both DEVICE + * and OTG HOST modes, while rev 1.1 and greater require PE7 to + * be low for DEVICE mode and high for HOST mode. We set it high + * here because we are in host mode + */ + + if (gpio_request(musb->config->gpio_vrsel, "USB_VRSEL")) { + printk(KERN_ERR "Failed ro request USB_VRSEL GPIO_%d\n", + musb->config->gpio_vrsel); + return -ENODEV; } - if (is_peripheral_enabled(musb)) - musb->xceiv->set_power = bfin_set_power; + gpio_direction_output(musb->config->gpio_vrsel, 0); + + musb->xceiv = usb_get_phy(USB_PHY_TYPE_USB2); + if (IS_ERR_OR_NULL(musb->xceiv)) { + gpio_free(musb->config->gpio_vrsel); + return -EPROBE_DEFER; + } + + bfin_musb_reg_init(musb); + + setup_timer(&musb_conn_timer, musb_conn_timer_handler, + (unsigned long) musb); + + musb->xceiv->set_power = bfin_musb_set_power; musb->isr = blackfin_interrupt; + musb->double_buffer_not_ok = true; return 0; } -int musb_platform_suspend(struct musb *musb) +static int bfin_musb_exit(struct musb *musb) { + gpio_free(musb->config->gpio_vrsel); + usb_put_phy(musb->xceiv); + + return 0; +} + +static const struct musb_platform_ops bfin_ops = { + .init = bfin_musb_init, + .exit = bfin_musb_exit, + + .enable = bfin_musb_enable, + .disable = bfin_musb_disable, + + .set_mode = bfin_musb_set_mode, + + .vbus_status = bfin_musb_vbus_status, + .set_vbus = bfin_musb_set_vbus, + + .adjust_channel_params = bfin_musb_adjust_channel_params, +}; + +static u64 bfin_dmamask = DMA_BIT_MASK(32); + +static int bfin_probe(struct platform_device *pdev) +{ + struct resource musb_resources[2]; + struct musb_hdrc_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct platform_device *musb; + struct bfin_glue *glue; + + int ret = -ENOMEM; + + glue = kzalloc(sizeof(*glue), GFP_KERNEL); + if (!glue) { + dev_err(&pdev->dev, "failed to allocate glue context\n"); + goto err0; + } + + musb = platform_device_alloc("musb-hdrc", PLATFORM_DEVID_AUTO); + if (!musb) { + dev_err(&pdev->dev, "failed to allocate musb device\n"); + goto err1; + } + + musb->dev.parent = &pdev->dev; + musb->dev.dma_mask = &bfin_dmamask; + musb->dev.coherent_dma_mask = bfin_dmamask; + + glue->dev = &pdev->dev; + glue->musb = musb; + + pdata->platform_ops = &bfin_ops; + + glue->phy = usb_phy_generic_register(); + if (IS_ERR(glue->phy)) + goto err2; + platform_set_drvdata(pdev, glue); + + 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 err3; + } + + ret = platform_device_add_data(musb, pdata, sizeof(*pdata)); + if (ret) { + dev_err(&pdev->dev, "failed to add platform_data\n"); + goto err3; + } + + ret = platform_device_add(musb); + if (ret) { + dev_err(&pdev->dev, "failed to register musb device\n"); + goto err3; + } + return 0; + +err3: + usb_phy_generic_unregister(glue->phy); + +err2: + platform_device_put(musb); + +err1: + kfree(glue); + +err0: + return ret; } -int musb_platform_resume(struct musb *musb) +static int bfin_remove(struct platform_device *pdev) { + struct bfin_glue *glue = platform_get_drvdata(pdev); + + platform_device_unregister(glue->musb); + usb_phy_generic_unregister(glue->phy); + kfree(glue); + return 0; } +#ifdef CONFIG_PM +static int bfin_suspend(struct device *dev) +{ + struct bfin_glue *glue = dev_get_drvdata(dev); + struct musb *musb = glue_to_musb(glue); + + if (is_host_active(musb)) + /* + * During hibernate gpio_vrsel will change from high to low + * low which will generate wakeup event resume the system + * immediately. Set it to 0 before hibernate to avoid this + * wakeup event. + */ + gpio_set_value(musb->config->gpio_vrsel, 0); -int musb_platform_exit(struct musb *musb) + return 0; +} + +static int bfin_resume(struct device *dev) { + struct bfin_glue *glue = dev_get_drvdata(dev); + struct musb *musb = glue_to_musb(glue); - bfin_vbus_power(musb, 0 /*off*/, 1); - gpio_free(musb->config->gpio_vrsel); - musb_platform_suspend(musb); + bfin_musb_reg_init(musb); return 0; } +#endif + +static SIMPLE_DEV_PM_OPS(bfin_pm_ops, bfin_suspend, bfin_resume); + +static struct platform_driver bfin_driver = { + .probe = bfin_probe, + .remove = __exit_p(bfin_remove), + .driver = { + .name = "musb-blackfin", + .pm = &bfin_pm_ops, + }, +}; + +MODULE_DESCRIPTION("Blackfin MUSB Glue Layer"); +MODULE_AUTHOR("Bryan Wy <cooloney@kernel.org>"); +MODULE_LICENSE("GPL v2"); +module_platform_driver(bfin_driver); |
