diff options
Diffstat (limited to 'drivers/usb/musb/musb_core.c')
-rw-r--r-- | drivers/usb/musb/musb_core.c | 199 |
1 files changed, 184 insertions, 15 deletions
diff --git a/drivers/usb/musb/musb_core.c b/drivers/usb/musb/musb_core.c index 15cca912c53e..f616fb489542 100644 --- a/drivers/usb/musb/musb_core.c +++ b/drivers/usb/musb/musb_core.c @@ -73,6 +73,7 @@ #include <linux/prefetch.h> #include <linux/platform_device.h> #include <linux/io.h> +#include <linux/iopoll.h> #include <linux/dma-mapping.h> #include <linux/usb.h> #include <linux/usb/of.h> @@ -246,7 +247,7 @@ static u32 musb_default_busctl_offset(u8 epnum, u16 offset) return 0x80 + (0x08 * epnum) + offset; } -static u8 musb_default_readb(const void __iomem *addr, unsigned offset) +static u8 musb_default_readb(void __iomem *addr, u32 offset) { u8 data = __raw_readb(addr + offset); @@ -254,13 +255,13 @@ static u8 musb_default_readb(const void __iomem *addr, unsigned offset) return data; } -static void musb_default_writeb(void __iomem *addr, unsigned offset, u8 data) +static void musb_default_writeb(void __iomem *addr, u32 offset, u8 data) { trace_musb_writeb(__builtin_return_address(0), addr, offset, data); __raw_writeb(data, addr + offset); } -static u16 musb_default_readw(const void __iomem *addr, unsigned offset) +static u16 musb_default_readw(void __iomem *addr, u32 offset) { u16 data = __raw_readw(addr + offset); @@ -268,12 +269,44 @@ static u16 musb_default_readw(const void __iomem *addr, unsigned offset) return data; } -static void musb_default_writew(void __iomem *addr, unsigned offset, u16 data) +static void musb_default_writew(void __iomem *addr, u32 offset, u16 data) { trace_musb_writew(__builtin_return_address(0), addr, offset, data); __raw_writew(data, addr + offset); } +static u16 musb_default_get_toggle(struct musb_qh *qh, int is_out) +{ + void __iomem *epio = qh->hw_ep->regs; + u16 csr; + + if (is_out) + csr = musb_readw(epio, MUSB_TXCSR) & MUSB_TXCSR_H_DATATOGGLE; + else + csr = musb_readw(epio, MUSB_RXCSR) & MUSB_RXCSR_H_DATATOGGLE; + + return csr; +} + +static u16 musb_default_set_toggle(struct musb_qh *qh, int is_out, + struct urb *urb) +{ + u16 csr; + u16 toggle; + + toggle = usb_gettoggle(urb->dev, qh->epnum, is_out); + + if (is_out) + csr = toggle ? (MUSB_TXCSR_H_WR_DATATOGGLE + | MUSB_TXCSR_H_DATATOGGLE) + : MUSB_TXCSR_CLRDATATOG; + else + csr = toggle ? (MUSB_RXCSR_H_WR_DATATOGGLE + | MUSB_RXCSR_H_DATATOGGLE) : 0; + + return csr; +} + /* * Load an endpoint's FIFO */ @@ -364,19 +397,25 @@ static void musb_default_read_fifo(struct musb_hw_ep *hw_ep, u16 len, u8 *dst) /* * Old style IO functions */ -u8 (*musb_readb)(const void __iomem *addr, unsigned offset); +u8 (*musb_readb)(void __iomem *addr, u32 offset); EXPORT_SYMBOL_GPL(musb_readb); -void (*musb_writeb)(void __iomem *addr, unsigned offset, u8 data); +void (*musb_writeb)(void __iomem *addr, u32 offset, u8 data); EXPORT_SYMBOL_GPL(musb_writeb); -u16 (*musb_readw)(const void __iomem *addr, unsigned offset); +u8 (*musb_clearb)(void __iomem *addr, u32 offset); +EXPORT_SYMBOL_GPL(musb_clearb); + +u16 (*musb_readw)(void __iomem *addr, u32 offset); EXPORT_SYMBOL_GPL(musb_readw); -void (*musb_writew)(void __iomem *addr, unsigned offset, u16 data); +void (*musb_writew)(void __iomem *addr, u32 offset, u16 data); EXPORT_SYMBOL_GPL(musb_writew); -u32 musb_readl(const void __iomem *addr, unsigned offset) +u16 (*musb_clearw)(void __iomem *addr, u32 offset); +EXPORT_SYMBOL_GPL(musb_clearw); + +u32 musb_readl(void __iomem *addr, u32 offset) { u32 data = __raw_readl(addr + offset); @@ -385,7 +424,7 @@ u32 musb_readl(const void __iomem *addr, unsigned offset) } EXPORT_SYMBOL_GPL(musb_readl); -void musb_writel(void __iomem *addr, unsigned offset, u32 data) +void musb_writel(void __iomem *addr, u32 offset, u32 data) { trace_musb_writel(__builtin_return_address(0), addr, offset, data); __raw_writel(data, addr + offset); @@ -414,6 +453,108 @@ void musb_write_fifo(struct musb_hw_ep *hw_ep, u16 len, const u8 *src) return hw_ep->musb->io.write_fifo(hw_ep, len, src); } +static u8 musb_read_devctl(struct musb *musb) +{ + return musb_readb(musb->mregs, MUSB_DEVCTL); +} + +/** + * musb_set_host - set and initialize host mode + * @musb: musb controller driver data + * + * At least some musb revisions need to enable devctl session bit in + * peripheral mode to switch to host mode. Initializes things to host + * mode and sets A_IDLE. SoC glue needs to advance state further + * based on phy provided VBUS state. + * + * Note that the SoC glue code may need to wait for musb to settle + * on enable before calling this to avoid babble. + */ +int musb_set_host(struct musb *musb) +{ + int error = 0; + u8 devctl; + + if (!musb) + return -EINVAL; + + devctl = musb_read_devctl(musb); + if (!(devctl & MUSB_DEVCTL_BDEVICE)) { + dev_info(musb->controller, + "%s: already in host mode: %02x\n", + __func__, devctl); + goto init_data; + } + + devctl |= MUSB_DEVCTL_SESSION; + musb_writeb(musb->mregs, MUSB_DEVCTL, devctl); + + error = readx_poll_timeout(musb_read_devctl, musb, devctl, + !(devctl & MUSB_DEVCTL_BDEVICE), 5000, + 1000000); + if (error) { + dev_err(musb->controller, "%s: could not set host: %02x\n", + __func__, devctl); + + return error; + } + +init_data: + musb->is_active = 1; + musb->xceiv->otg->state = OTG_STATE_A_IDLE; + MUSB_HST_MODE(musb); + + return error; +} +EXPORT_SYMBOL_GPL(musb_set_host); + +/** + * musb_set_peripheral - set and initialize peripheral mode + * @musb: musb controller driver data + * + * Clears devctl session bit and initializes things for peripheral + * mode and sets B_IDLE. SoC glue needs to advance state further + * based on phy provided VBUS state. + */ +int musb_set_peripheral(struct musb *musb) +{ + int error = 0; + u8 devctl; + + if (!musb) + return -EINVAL; + + devctl = musb_read_devctl(musb); + if (devctl & MUSB_DEVCTL_BDEVICE) { + dev_info(musb->controller, + "%s: already in peripheral mode: %02x\n", + __func__, devctl); + + goto init_data; + } + + devctl &= ~MUSB_DEVCTL_SESSION; + musb_writeb(musb->mregs, MUSB_DEVCTL, devctl); + + error = readx_poll_timeout(musb_read_devctl, musb, devctl, + devctl & MUSB_DEVCTL_BDEVICE, 5000, + 1000000); + if (error) { + dev_err(musb->controller, "%s: could not set peripheral: %02x\n", + __func__, devctl); + + return error; + } + +init_data: + musb->is_active = 0; + musb->xceiv->otg->state = OTG_STATE_B_IDLE; + MUSB_DEV_MODE(musb); + + return error; +} +EXPORT_SYMBOL_GPL(musb_set_peripheral); + /*-------------------------------------------------------------------------*/ /* for high speed test mode; see USB 2.0 spec 7.1.20 */ @@ -909,7 +1050,6 @@ static void musb_handle_intr_reset(struct musb *musb) * @param musb instance pointer * @param int_usb register contents * @param devctl - * @param power */ static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb, @@ -1015,7 +1155,6 @@ static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb, static void musb_disable_interrupts(struct musb *musb) { void __iomem *mbase = musb->mregs; - u16 temp; /* disable interrupts */ musb_writeb(mbase, MUSB_INTRUSBE, 0); @@ -1025,9 +1164,9 @@ static void musb_disable_interrupts(struct musb *musb) musb_writew(mbase, MUSB_INTRRXE, 0); /* flush pending interrupts */ - temp = musb_readb(mbase, MUSB_INTRUSB); - temp = musb_readw(mbase, MUSB_INTRTX); - temp = musb_readw(mbase, MUSB_INTRRX); + musb_clearb(mbase, MUSB_INTRUSB); + musb_clearw(mbase, MUSB_INTRTX); + musb_clearw(mbase, MUSB_INTRRX); } static void musb_enable_interrupts(struct musb *musb) @@ -1840,6 +1979,9 @@ ATTRIBUTE_GROUPS(musb); #define MUSB_QUIRK_B_INVALID_VBUS_91 (MUSB_DEVCTL_BDEVICE | \ (2 << MUSB_DEVCTL_VBUS_SHIFT) | \ MUSB_DEVCTL_SESSION) +#define MUSB_QUIRK_B_DISCONNECT_99 (MUSB_DEVCTL_BDEVICE | \ + (3 << MUSB_DEVCTL_VBUS_SHIFT) | \ + MUSB_DEVCTL_SESSION) #define MUSB_QUIRK_A_DISCONNECT_19 ((3 << MUSB_DEVCTL_VBUS_SHIFT) | \ MUSB_DEVCTL_SESSION) @@ -1862,6 +2004,11 @@ static void musb_pm_runtime_check_session(struct musb *musb) s = MUSB_DEVCTL_FSDEV | MUSB_DEVCTL_LSDEV | MUSB_DEVCTL_HR; switch (devctl & ~s) { + case MUSB_QUIRK_B_DISCONNECT_99: + musb_dbg(musb, "Poll devctl in case of suspend after disconnect\n"); + schedule_delayed_work(&musb->irq_work, + msecs_to_jiffies(1000)); + break; case MUSB_QUIRK_B_INVALID_VBUS_91: if (musb->quirk_retries && !musb->flush_irq_work) { musb_dbg(musb, @@ -2246,10 +2393,19 @@ musb_init_controller(struct device *dev, int nIrq, void __iomem *ctrl) musb_readb = musb->ops->readb; if (musb->ops->writeb) musb_writeb = musb->ops->writeb; + if (musb->ops->clearb) + musb_clearb = musb->ops->clearb; + else + musb_clearb = musb_readb; + if (musb->ops->readw) musb_readw = musb->ops->readw; if (musb->ops->writew) musb_writew = musb->ops->writew; + if (musb->ops->clearw) + musb_clearw = musb->ops->clearw; + else + musb_clearw = musb_readw; #ifndef CONFIG_MUSB_PIO_ONLY if (!musb->ops->dma_init || !musb->ops->dma_exit) { @@ -2271,6 +2427,16 @@ musb_init_controller(struct device *dev, int nIrq, void __iomem *ctrl) else musb->io.write_fifo = musb_default_write_fifo; + if (musb->ops->get_toggle) + musb->io.get_toggle = musb->ops->get_toggle; + else + musb->io.get_toggle = musb_default_get_toggle; + + if (musb->ops->set_toggle) + musb->io.set_toggle = musb->ops->set_toggle; + else + musb->io.set_toggle = musb_default_set_toggle; + if (!musb->xceiv->io_ops) { musb->xceiv->io_dev = musb->controller; musb->xceiv->io_priv = musb->mregs; @@ -2310,6 +2476,9 @@ musb_init_controller(struct device *dev, int nIrq, void __iomem *ctrl) musb_disable_interrupts(musb); musb_writeb(musb->mregs, MUSB_DEVCTL, 0); + /* MUSB_POWER_SOFTCONN might be already set, JZ4740 does this. */ + musb_writeb(musb->mregs, MUSB_POWER, 0); + /* Init IRQ workqueue before request_irq */ INIT_DELAYED_WORK(&musb->irq_work, musb_irq_work); INIT_DELAYED_WORK(&musb->deassert_reset_work, musb_deassert_reset); |