linux/drivers/usb/musb/musb_core.c

2964 lines
79 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0
/*
* MUSB OTG driver core code
*
* Copyright 2005 Mentor Graphics Corporation
* Copyright (C) 2005-2006 by Texas Instruments
* Copyright (C) 2006-2007 Nokia Corporation
*/
/*
* Inventra (Multipoint) Dual-Role Controller Driver for Linux.
*
* This consists of a Host Controller Driver (HCD) and a peripheral
* controller driver implementing the "Gadget" API; OTG support is
* in the works. These are normal Linux-USB controller drivers which
* use IRQs and have no dedicated thread.
*
* This version of the driver has only been used with products from
* Texas Instruments. Those products integrate the Inventra logic
* with other DMA, IRQ, and bus modules, as well as other logic that
* needs to be reflected in this driver.
*
*
* NOTE: the original Mentor code here was pretty much a collection
* of mechanisms that don't seem to have been fully integrated/working
* for *any* Linux kernel version. This version aims at Linux 2.6.now,
* Key open issues include:
*
* - Lack of host-side transaction scheduling, for all transfer types.
* The hardware doesn't do it; instead, software must.
*
* This is not an issue for OTG devices that don't support external
* hubs, but for more "normal" USB hosts it's a user issue that the
* "multipoint" support doesn't scale in the expected ways. That
* includes DaVinci EVM in a common non-OTG mode.
*
* * Control and bulk use dedicated endpoints, and there's as
* yet no mechanism to either (a) reclaim the hardware when
* peripherals are NAKing, which gets complicated with bulk
* endpoints, or (b) use more than a single bulk endpoint in
* each direction.
*
* RESULT: one device may be perceived as blocking another one.
*
* * Interrupt and isochronous will dynamically allocate endpoint
* hardware, but (a) there's no record keeping for bandwidth;
* (b) in the common case that few endpoints are available, there
* is no mechanism to reuse endpoints to talk to multiple devices.
*
* RESULT: At one extreme, bandwidth can be overcommitted in
* some hardware configurations, no faults will be reported.
* At the other extreme, the bandwidth capabilities which do
* exist tend to be severely undercommitted. You can't yet hook
* up both a keyboard and a mouse to an external USB hub.
*/
/*
* This gets many kinds of configuration information:
* - Kconfig for everything user-configurable
* - platform_device for addressing, irq, and platform_data
* - platform_data is mostly for board-specific information
* (plus recentrly, SOC or family details)
*
* Most of the conditional compilation will (someday) vanish.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/kobject.h>
#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>
#include "musb_core.h"
#include "musb_trace.h"
#define TA_WAIT_BCON(m) max_t(int, (m)->a_wait_bcon, OTG_TIME_A_WAIT_BCON)
#define DRIVER_AUTHOR "Mentor Graphics, Texas Instruments, Nokia"
#define DRIVER_DESC "Inventra Dual-Role USB Controller Driver"
#define MUSB_VERSION "6.0"
#define DRIVER_INFO DRIVER_DESC ", v" MUSB_VERSION
#define MUSB_DRIVER_NAME "musb-hdrc"
const char musb_driver_name[] = MUSB_DRIVER_NAME;
MODULE_DESCRIPTION(DRIVER_INFO);
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:" MUSB_DRIVER_NAME);
/*-------------------------------------------------------------------------*/
static inline struct musb *dev_to_musb(struct device *dev)
{
return dev_get_drvdata(dev);
}
enum musb_mode musb_get_mode(struct device *dev)
{
enum usb_dr_mode mode;
mode = usb_get_dr_mode(dev);
switch (mode) {
case USB_DR_MODE_HOST:
return MUSB_HOST;
case USB_DR_MODE_PERIPHERAL:
return MUSB_PERIPHERAL;
case USB_DR_MODE_OTG:
case USB_DR_MODE_UNKNOWN:
default:
return MUSB_OTG;
}
}
EXPORT_SYMBOL_GPL(musb_get_mode);
/*-------------------------------------------------------------------------*/
static int musb_ulpi_read(struct usb_phy *phy, u32 reg)
{
void __iomem *addr = phy->io_priv;
int i = 0;
u8 r;
u8 power;
int ret;
pm_runtime_get_sync(phy->io_dev);
/* Make sure the transceiver is not in low power mode */
power = musb_readb(addr, MUSB_POWER);
power &= ~MUSB_POWER_SUSPENDM;
musb_writeb(addr, MUSB_POWER, power);
/* REVISIT: musbhdrc_ulpi_an.pdf recommends setting the
* ULPICarKitControlDisableUTMI after clearing POWER_SUSPENDM.
*/
musb_writeb(addr, MUSB_ULPI_REG_ADDR, (u8)reg);
musb_writeb(addr, MUSB_ULPI_REG_CONTROL,
MUSB_ULPI_REG_REQ | MUSB_ULPI_RDN_WR);
while (!(musb_readb(addr, MUSB_ULPI_REG_CONTROL)
& MUSB_ULPI_REG_CMPLT)) {
i++;
if (i == 10000) {
ret = -ETIMEDOUT;
goto out;
}
}
r = musb_readb(addr, MUSB_ULPI_REG_CONTROL);
r &= ~MUSB_ULPI_REG_CMPLT;
musb_writeb(addr, MUSB_ULPI_REG_CONTROL, r);
ret = musb_readb(addr, MUSB_ULPI_REG_DATA);
out:
pm_runtime_put(phy->io_dev);
return ret;
}
static int musb_ulpi_write(struct usb_phy *phy, u32 val, u32 reg)
{
void __iomem *addr = phy->io_priv;
int i = 0;
u8 r = 0;
u8 power;
int ret = 0;
pm_runtime_get_sync(phy->io_dev);
/* Make sure the transceiver is not in low power mode */
power = musb_readb(addr, MUSB_POWER);
power &= ~MUSB_POWER_SUSPENDM;
musb_writeb(addr, MUSB_POWER, power);
musb_writeb(addr, MUSB_ULPI_REG_ADDR, (u8)reg);
musb_writeb(addr, MUSB_ULPI_REG_DATA, (u8)val);
musb_writeb(addr, MUSB_ULPI_REG_CONTROL, MUSB_ULPI_REG_REQ);
while (!(musb_readb(addr, MUSB_ULPI_REG_CONTROL)
& MUSB_ULPI_REG_CMPLT)) {
i++;
if (i == 10000) {
ret = -ETIMEDOUT;
goto out;
}
}
r = musb_readb(addr, MUSB_ULPI_REG_CONTROL);
r &= ~MUSB_ULPI_REG_CMPLT;
musb_writeb(addr, MUSB_ULPI_REG_CONTROL, r);
out:
pm_runtime_put(phy->io_dev);
return ret;
}
static struct usb_phy_io_ops musb_ulpi_access = {
.read = musb_ulpi_read,
.write = musb_ulpi_write,
};
/*-------------------------------------------------------------------------*/
static u32 musb_default_fifo_offset(u8 epnum)
{
return 0x20 + (epnum * 4);
}
/* "flat" mapping: each endpoint has its own i/o address */
static void musb_flat_ep_select(void __iomem *mbase, u8 epnum)
{
}
static u32 musb_flat_ep_offset(u8 epnum, u16 offset)
{
return 0x100 + (0x10 * epnum) + offset;
}
/* "indexed" mapping: INDEX register controls register bank select */
static void musb_indexed_ep_select(void __iomem *mbase, u8 epnum)
{
musb_writeb(mbase, MUSB_INDEX, epnum);
}
static u32 musb_indexed_ep_offset(u8 epnum, u16 offset)
{
return 0x10 + offset;
}
static u32 musb_default_busctl_offset(u8 epnum, u16 offset)
{
return 0x80 + (0x08 * epnum) + offset;
}
static u8 musb_default_readb(void __iomem *addr, u32 offset)
{
u8 data = __raw_readb(addr + offset);
trace_musb_readb(__builtin_return_address(0), addr, offset, data);
return 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(void __iomem *addr, u32 offset)
{
u16 data = __raw_readw(addr + offset);
trace_musb_readw(__builtin_return_address(0), addr, offset, data);
return 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
*/
static void musb_default_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;
if (unlikely(len == 0))
return;
prefetch((u8 *)src);
dev_dbg(musb->controller, "%cX ep%d fifo %p count %d buf %p\n",
'T', hw_ep->epnum, fifo, len, src);
/* we can't assume unaligned reads work */
if (likely((0x01 & (unsigned long) src) == 0)) {
u16 index = 0;
/* best case is 32bit-aligned source address */
if ((0x02 & (unsigned long) src) == 0) {
if (len >= 4) {
iowrite32_rep(fifo, src + index, len >> 2);
index += len & ~0x03;
}
if (len & 0x02) {
__raw_writew(*(u16 *)&src[index], fifo);
index += 2;
}
} else {
if (len >= 2) {
iowrite16_rep(fifo, src + index, len >> 1);
index += len & ~0x01;
}
}
if (len & 0x01)
__raw_writeb(src[index], fifo);
} else {
/* byte aligned */
iowrite8_rep(fifo, src, len);
}
}
/*
* Unload an endpoint's FIFO
*/
static void musb_default_read_fifo(struct musb_hw_ep *hw_ep, u16 len, u8 *dst)
{
struct musb *musb = hw_ep->musb;
void __iomem *fifo = hw_ep->fifo;
if (unlikely(len == 0))
return;
dev_dbg(musb->controller, "%cX ep%d fifo %p count %d buf %p\n",
'R', hw_ep->epnum, fifo, len, dst);
/* we can't assume unaligned writes work */
if (likely((0x01 & (unsigned long) dst) == 0)) {
u16 index = 0;
/* best case is 32bit-aligned destination address */
if ((0x02 & (unsigned long) dst) == 0) {
if (len >= 4) {
ioread32_rep(fifo, dst, len >> 2);
index = len & ~0x03;
}
if (len & 0x02) {
*(u16 *)&dst[index] = __raw_readw(fifo);
index += 2;
}
} else {
if (len >= 2) {
ioread16_rep(fifo, dst, len >> 1);
index = len & ~0x01;
}
}
if (len & 0x01)
dst[index] = __raw_readb(fifo);
} else {
/* byte aligned */
ioread8_rep(fifo, dst, len);
}
}
/*
* Old style IO functions
*/
u8 (*musb_readb)(void __iomem *addr, u32 offset);
EXPORT_SYMBOL_GPL(musb_readb);
void (*musb_writeb)(void __iomem *addr, u32 offset, u8 data);
EXPORT_SYMBOL_GPL(musb_writeb);
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, u32 offset, u16 data);
EXPORT_SYMBOL_GPL(musb_writew);
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);
trace_musb_readl(__builtin_return_address(0), addr, offset, data);
return data;
}
EXPORT_SYMBOL_GPL(musb_readl);
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);
}
EXPORT_SYMBOL_GPL(musb_writel);
#ifndef CONFIG_MUSB_PIO_ONLY
struct dma_controller *
(*musb_dma_controller_create)(struct musb *musb, void __iomem *base);
EXPORT_SYMBOL(musb_dma_controller_create);
void (*musb_dma_controller_destroy)(struct dma_controller *c);
EXPORT_SYMBOL(musb_dma_controller_destroy);
#endif
/*
* New style IO functions
*/
void musb_read_fifo(struct musb_hw_ep *hw_ep, u16 len, u8 *dst)
{
return hw_ep->musb->io.read_fifo(hw_ep, len, dst);
}
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 */
static const u8 musb_test_packet[53] = {
/* implicit SYNC then DATA0 to start */
/* JKJKJKJK x9 */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* JJKKJJKK x8 */
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
/* JJJJKKKK x8 */
0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee,
/* JJJJJJJKKKKKKK x8 */
0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
/* JJJJJJJK x8 */
0x7f, 0xbf, 0xdf, 0xef, 0xf7, 0xfb, 0xfd,
/* JKKKKKKK x10, JK */
0xfc, 0x7e, 0xbf, 0xdf, 0xef, 0xf7, 0xfb, 0xfd, 0x7e
/* implicit CRC16 then EOP to end */
};
void musb_load_testpacket(struct musb *musb)
{
void __iomem *regs = musb->endpoints[0].regs;
musb_ep_select(musb->mregs, 0);
musb_write_fifo(musb->control_ep,
sizeof(musb_test_packet), musb_test_packet);
musb_writew(regs, MUSB_CSR0, MUSB_CSR0_TXPKTRDY);
}
/*-------------------------------------------------------------------------*/
/*
* Handles OTG hnp timeouts, such as b_ase0_brst
*/
static void musb_otg_timer_func(struct timer_list *t)
{
struct musb *musb = from_timer(musb, t, otg_timer);
unsigned long flags;
spin_lock_irqsave(&musb->lock, flags);
switch (musb->xceiv->otg->state) {
case OTG_STATE_B_WAIT_ACON:
musb_dbg(musb,
"HNP: b_wait_acon timeout; back to b_peripheral");
musb_g_disconnect(musb);
musb->xceiv->otg->state = OTG_STATE_B_PERIPHERAL;
musb->is_active = 0;
break;
case OTG_STATE_A_SUSPEND:
case OTG_STATE_A_WAIT_BCON:
musb_dbg(musb, "HNP: %s timeout",
usb_otg_state_string(musb->xceiv->otg->state));
musb_platform_set_vbus(musb, 0);
musb->xceiv->otg->state = OTG_STATE_A_WAIT_VFALL;
break;
default:
musb_dbg(musb, "HNP: Unhandled mode %s",
usb_otg_state_string(musb->xceiv->otg->state));
}
spin_unlock_irqrestore(&musb->lock, flags);
}
/*
* Stops the HNP transition. Caller must take care of locking.
*/
void musb_hnp_stop(struct musb *musb)
{
struct usb_hcd *hcd = musb->hcd;
void __iomem *mbase = musb->mregs;
u8 reg;
musb_dbg(musb, "HNP: stop from %s",
usb_otg_state_string(musb->xceiv->otg->state));
switch (musb->xceiv->otg->state) {
case OTG_STATE_A_PERIPHERAL:
musb_g_disconnect(musb);
musb_dbg(musb, "HNP: back to %s",
usb_otg_state_string(musb->xceiv->otg->state));
break;
case OTG_STATE_B_HOST:
musb_dbg(musb, "HNP: Disabling HR");
if (hcd)
hcd->self.is_b_host = 0;
musb->xceiv->otg->state = OTG_STATE_B_PERIPHERAL;
MUSB_DEV_MODE(musb);
reg = musb_readb(mbase, MUSB_POWER);
reg |= MUSB_POWER_SUSPENDM;
musb_writeb(mbase, MUSB_POWER, reg);
/* REVISIT: Start SESSION_REQUEST here? */
break;
default:
musb_dbg(musb, "HNP: Stopping in unknown state %s",
usb_otg_state_string(musb->xceiv->otg->state));
}
/*
* When returning to A state after HNP, avoid hub_port_rebounce(),
* which cause occasional OPT A "Did not receive reset after connect"
* errors.
*/
musb->port1_status &= ~(USB_PORT_STAT_C_CONNECTION << 16);
}
static void musb_recover_from_babble(struct musb *musb);
static void musb_handle_intr_resume(struct musb *musb, u8 devctl)
{
musb_dbg(musb, "RESUME (%s)",
usb_otg_state_string(musb->xceiv->otg->state));
if (devctl & MUSB_DEVCTL_HM) {
switch (musb->xceiv->otg->state) {
case OTG_STATE_A_SUSPEND:
/* remote wakeup? */
musb->port1_status |=
(USB_PORT_STAT_C_SUSPEND << 16)
| MUSB_PORT_STAT_RESUME;
musb->rh_timer = jiffies
+ msecs_to_jiffies(USB_RESUME_TIMEOUT);
musb->xceiv->otg->state = OTG_STATE_A_HOST;
musb->is_active = 1;
musb_host_resume_root_hub(musb);
schedule_delayed_work(&musb->finish_resume_work,
msecs_to_jiffies(USB_RESUME_TIMEOUT));
break;
case OTG_STATE_B_WAIT_ACON:
musb->xceiv->otg->state = OTG_STATE_B_PERIPHERAL;
musb->is_active = 1;
MUSB_DEV_MODE(musb);
break;
default:
WARNING("bogus %s RESUME (%s)\n",
"host",
usb_otg_state_string(musb->xceiv->otg->state));
}
} else {
switch (musb->xceiv->otg->state) {
case OTG_STATE_A_SUSPEND:
/* possibly DISCONNECT is upcoming */
musb->xceiv->otg->state = OTG_STATE_A_HOST;
musb_host_resume_root_hub(musb);
break;
case OTG_STATE_B_WAIT_ACON:
case OTG_STATE_B_PERIPHERAL:
/* disconnect while suspended? we may
* not get a disconnect irq...
*/
if ((devctl & MUSB_DEVCTL_VBUS)
!= (3 << MUSB_DEVCTL_VBUS_SHIFT)
) {
musb->int_usb |= MUSB_INTR_DISCONNECT;
musb->int_usb &= ~MUSB_INTR_SUSPEND;
break;
}
musb_g_resume(musb);
break;
case OTG_STATE_B_IDLE:
musb->int_usb &= ~MUSB_INTR_SUSPEND;
break;
default:
WARNING("bogus %s RESUME (%s)\n",
"peripheral",
usb_otg_state_string(musb->xceiv->otg->state));
}
}
}
/* return IRQ_HANDLED to tell the caller to return immediately */
static irqreturn_t musb_handle_intr_sessreq(struct musb *musb, u8 devctl)
{
void __iomem *mbase = musb->mregs;
if ((devctl & MUSB_DEVCTL_VBUS) == MUSB_DEVCTL_VBUS
&& (devctl & MUSB_DEVCTL_BDEVICE)) {
musb_dbg(musb, "SessReq while on B state");
return IRQ_HANDLED;
}
musb_dbg(musb, "SESSION_REQUEST (%s)",
usb_otg_state_string(musb->xceiv->otg->state));
/* IRQ arrives from ID pin sense or (later, if VBUS power
* is removed) SRP. responses are time critical:
* - turn on VBUS (with silicon-specific mechanism)
* - go through A_WAIT_VRISE
* - ... to A_WAIT_BCON.
* a_wait_vrise_tmout triggers VBUS_ERROR transitions
*/
musb_writeb(mbase, MUSB_DEVCTL, MUSB_DEVCTL_SESSION);
musb->ep0_stage = MUSB_EP0_START;
musb->xceiv->otg->state = OTG_STATE_A_IDLE;
MUSB_HST_MODE(musb);
musb_platform_set_vbus(musb, 1);
return IRQ_NONE;
}
static void musb_handle_intr_vbuserr(struct musb *musb, u8 devctl)
{
int ignore = 0;
/* During connection as an A-Device, we may see a short
* current spikes causing voltage drop, because of cable
* and peripheral capacitance combined with vbus draw.
* (So: less common with truly self-powered devices, where
* vbus doesn't act like a power supply.)
*
* Such spikes are short; usually less than ~500 usec, max
* of ~2 msec. That is, they're not sustained overcurrent
* errors, though they're reported using VBUSERROR irqs.
*
* Workarounds: (a) hardware: use self powered devices.
* (b) software: ignore non-repeated VBUS errors.
*
* REVISIT: do delays from lots of DEBUG_KERNEL checks
* make trouble here, keeping VBUS < 4.4V ?
*/
switch (musb->xceiv->otg->state) {
case OTG_STATE_A_HOST:
/* recovery is dicey once we've gotten past the
* initial stages of enumeration, but if VBUS
* stayed ok at the other end of the link, and
* another reset is due (at least for high speed,
* to redo the chirp etc), it might work OK...
*/
case OTG_STATE_A_WAIT_BCON:
case OTG_STATE_A_WAIT_VRISE:
if (musb->vbuserr_retry) {
void __iomem *mbase = musb->mregs;
musb->vbuserr_retry--;
ignore = 1;
devctl |= MUSB_DEVCTL_SESSION;
musb_writeb(mbase, MUSB_DEVCTL, devctl);
} else {
musb->port1_status |=
USB_PORT_STAT_OVERCURRENT
| (USB_PORT_STAT_C_OVERCURRENT << 16);
}
break;
default:
break;
}
dev_printk(ignore ? KERN_DEBUG : KERN_ERR, musb->controller,
"VBUS_ERROR in %s (%02x, %s), retry #%d, port1 %08x\n",
usb_otg_state_string(musb->xceiv->otg->state),
devctl,
({ char *s;
switch (devctl & MUSB_DEVCTL_VBUS) {
case 0 << MUSB_DEVCTL_VBUS_SHIFT:
s = "<SessEnd"; break;
case 1 << MUSB_DEVCTL_VBUS_SHIFT:
s = "<AValid"; break;
case 2 << MUSB_DEVCTL_VBUS_SHIFT:
s = "<VBusValid"; break;
/* case 3 << MUSB_DEVCTL_VBUS_SHIFT: */
default:
s = "VALID"; break;
} s; }),
VBUSERR_RETRY_COUNT - musb->vbuserr_retry,
musb->port1_status);
/* go through A_WAIT_VFALL then start a new session */
if (!ignore)
musb_platform_set_vbus(musb, 0);
}
static void musb_handle_intr_suspend(struct musb *musb, u8 devctl)
{
musb_dbg(musb, "SUSPEND (%s) devctl %02x",
usb_otg_state_string(musb->xceiv->otg->state), devctl);
switch (musb->xceiv->otg->state) {
case OTG_STATE_A_PERIPHERAL:
/* We also come here if the cable is removed, since
* this silicon doesn't report ID-no-longer-grounded.
*
* We depend on T(a_wait_bcon) to shut us down, and
* hope users don't do anything dicey during this
* undesired detour through A_WAIT_BCON.
*/
musb_hnp_stop(musb);
musb_host_resume_root_hub(musb);
musb_root_disconnect(musb);
musb_platform_try_idle(musb, jiffies
+ msecs_to_jiffies(musb->a_wait_bcon
? : OTG_TIME_A_WAIT_BCON));
break;
case OTG_STATE_B_IDLE:
if (!musb->is_active)
break;
fallthrough;
case OTG_STATE_B_PERIPHERAL:
musb_g_suspend(musb);
musb->is_active = musb->g.b_hnp_enable;
if (musb->is_active) {
musb->xceiv->otg->state = OTG_STATE_B_WAIT_ACON;
musb_dbg(musb, "HNP: Setting timer for b_ase0_brst");
mod_timer(&musb->otg_timer, jiffies
+ msecs_to_jiffies(
OTG_TIME_B_ASE0_BRST));
}
break;
case OTG_STATE_A_WAIT_BCON:
if (musb->a_wait_bcon != 0)
musb_platform_try_idle(musb, jiffies
+ msecs_to_jiffies(musb->a_wait_bcon));
break;
case OTG_STATE_A_HOST:
musb->xceiv->otg->state = OTG_STATE_A_SUSPEND;
musb->is_active = musb->hcd->self.b_hnp_enable;
break;
case OTG_STATE_B_HOST:
/* Transition to B_PERIPHERAL, see 6.8.2.6 p 44 */
musb_dbg(musb, "REVISIT: SUSPEND as B_HOST");
break;
default:
/* "should not happen" */
musb->is_active = 0;
break;
}
}
static void musb_handle_intr_connect(struct musb *musb, u8 devctl, u8 int_usb)
{
struct usb_hcd *hcd = musb->hcd;
musb->is_active = 1;
musb->ep0_stage = MUSB_EP0_START;
musb->intrtxe = musb->epmask;
musb_writew(musb->mregs, MUSB_INTRTXE, musb->intrtxe);
musb->intrrxe = musb->epmask & 0xfffe;
musb_writew(musb->mregs, MUSB_INTRRXE, musb->intrrxe);
musb_writeb(musb->mregs, MUSB_INTRUSBE, 0xf7);
musb->port1_status &= ~(USB_PORT_STAT_LOW_SPEED
|USB_PORT_STAT_HIGH_SPEED
|USB_PORT_STAT_ENABLE
);
musb->port1_status |= USB_PORT_STAT_CONNECTION
|(USB_PORT_STAT_C_CONNECTION << 16);
/* high vs full speed is just a guess until after reset */
if (devctl & MUSB_DEVCTL_LSDEV)
musb->port1_status |= USB_PORT_STAT_LOW_SPEED;
/* indicate new connection to OTG machine */
switch (musb->xceiv->otg->state) {
case OTG_STATE_B_PERIPHERAL:
if (int_usb & MUSB_INTR_SUSPEND) {
musb_dbg(musb, "HNP: SUSPEND+CONNECT, now b_host");
int_usb &= ~MUSB_INTR_SUSPEND;
goto b_host;
} else
musb_dbg(musb, "CONNECT as b_peripheral???");
break;
case OTG_STATE_B_WAIT_ACON:
musb_dbg(musb, "HNP: CONNECT, now b_host");
b_host:
musb->xceiv->otg->state = OTG_STATE_B_HOST;
if (musb->hcd)
musb->hcd->self.is_b_host = 1;
del_timer(&musb->otg_timer);
break;
default:
if ((devctl & MUSB_DEVCTL_VBUS)
== (3 << MUSB_DEVCTL_VBUS_SHIFT)) {
musb->xceiv->otg->state = OTG_STATE_A_HOST;
if (hcd)
hcd->self.is_b_host = 0;
}
break;
}
musb_host_poke_root_hub(musb);
musb_dbg(musb, "CONNECT (%s) devctl %02x",
usb_otg_state_string(musb->xceiv->otg->state), devctl);
}
static void musb_handle_intr_disconnect(struct musb *musb, u8 devctl)
{
musb_dbg(musb, "DISCONNECT (%s) as %s, devctl %02x",
usb_otg_state_string(musb->xceiv->otg->state),
MUSB_MODE(musb), devctl);
switch (musb->xceiv->otg->state) {
case OTG_STATE_A_HOST:
case OTG_STATE_A_SUSPEND:
musb_host_resume_root_hub(musb);
musb_root_disconnect(musb);
if (musb->a_wait_bcon != 0)
musb_platform_try_idle(musb, jiffies
+ msecs_to_jiffies(musb->a_wait_bcon));
break;
case OTG_STATE_B_HOST:
/* REVISIT this behaves for "real disconnect"
* cases; make sure the other transitions from
* from B_HOST act right too. The B_HOST code
* in hnp_stop() is currently not used...
*/
musb_root_disconnect(musb);
if (musb->hcd)
musb->hcd->self.is_b_host = 0;
musb->xceiv->otg->state = OTG_STATE_B_PERIPHERAL;
MUSB_DEV_MODE(musb);
musb_g_disconnect(musb);
break;
case OTG_STATE_A_PERIPHERAL:
musb_hnp_stop(musb);
musb_root_disconnect(musb);
fallthrough;
case OTG_STATE_B_WAIT_ACON:
case OTG_STATE_B_PERIPHERAL:
case OTG_STATE_B_IDLE:
musb_g_disconnect(musb);
break;
default:
WARNING("unhandled DISCONNECT transition (%s)\n",
usb_otg_state_string(musb->xceiv->otg->state));
break;
}
}
/*
* mentor saves a bit: bus reset and babble share the same irq.
* only host sees babble; only peripheral sees bus reset.
*/
static void musb_handle_intr_reset(struct musb *musb)
{
if (is_host_active(musb)) {
/*
* When BABBLE happens what we can depends on which
* platform MUSB is running, because some platforms
* implemented proprietary means for 'recovering' from
* Babble conditions. One such platform is AM335x. In
* most cases, however, the only thing we can do is
* drop the session.
*/
dev_err(musb->controller, "Babble\n");
musb_recover_from_babble(musb);
} else {
musb_dbg(musb, "BUS RESET as %s",
usb_otg_state_string(musb->xceiv->otg->state));
switch (musb->xceiv->otg->state) {
case OTG_STATE_A_SUSPEND:
musb_g_reset(musb);
fallthrough;
case OTG_STATE_A_WAIT_BCON: /* OPT TD.4.7-900ms */
/* never use invalid T(a_wait_bcon) */
musb_dbg(musb, "HNP: in %s, %d msec timeout",
usb_otg_state_string(musb->xceiv->otg->state),
TA_WAIT_BCON(musb));
mod_timer(&musb->otg_timer, jiffies
+ msecs_to_jiffies(TA_WAIT_BCON(musb)));
break;
case OTG_STATE_A_PERIPHERAL:
del_timer(&musb->otg_timer);
musb_g_reset(musb);
break;
case OTG_STATE_B_WAIT_ACON:
musb_dbg(musb, "HNP: RESET (%s), to b_peripheral",
usb_otg_state_string(musb->xceiv->otg->state));
musb->xceiv->otg->state = OTG_STATE_B_PERIPHERAL;
musb_g_reset(musb);
break;
case OTG_STATE_B_IDLE:
musb->xceiv->otg->state = OTG_STATE_B_PERIPHERAL;
fallthrough;
case OTG_STATE_B_PERIPHERAL:
musb_g_reset(musb);
break;
default:
musb_dbg(musb, "Unhandled BUS RESET as %s",
usb_otg_state_string(musb->xceiv->otg->state));
}
}
}
/*
* Interrupt Service Routine to record USB "global" interrupts.
* Since these do not happen often and signify things of
* paramount importance, it seems OK to check them individually;
* the order of the tests is specified in the manual
*
* @param musb instance pointer
* @param int_usb register contents
* @param devctl
*/
static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb,
u8 devctl)
{
irqreturn_t handled = IRQ_NONE;
musb_dbg(musb, "<== DevCtl=%02x, int_usb=0x%x", devctl, int_usb);
/* in host mode, the peripheral may issue remote wakeup.
* in peripheral mode, the host may resume the link.
* spurious RESUME irqs happen too, paired with SUSPEND.
*/
if (int_usb & MUSB_INTR_RESUME) {
musb_handle_intr_resume(musb, devctl);
handled = IRQ_HANDLED;
}
/* see manual for the order of the tests */
if (int_usb & MUSB_INTR_SESSREQ) {
if (musb_handle_intr_sessreq(musb, devctl))
return IRQ_HANDLED;
handled = IRQ_HANDLED;
}
if (int_usb & MUSB_INTR_VBUSERROR) {
musb_handle_intr_vbuserr(musb, devctl);
handled = IRQ_HANDLED;
}
if (int_usb & MUSB_INTR_SUSPEND) {
musb_handle_intr_suspend(musb, devctl);
handled = IRQ_HANDLED;
}
if (int_usb & MUSB_INTR_CONNECT) {
musb_handle_intr_connect(musb, devctl, int_usb);
handled = IRQ_HANDLED;
}
if (int_usb & MUSB_INTR_DISCONNECT) {
musb_handle_intr_disconnect(musb, devctl);
handled = IRQ_HANDLED;
}
if (int_usb & MUSB_INTR_RESET) {
musb_handle_intr_reset(musb);
handled = IRQ_HANDLED;
}
#if 0
/* REVISIT ... this would be for multiplexing periodic endpoints, or
* supporting transfer phasing to prevent exceeding ISO bandwidth
* limits of a given frame or microframe.
*
* It's not needed for peripheral side, which dedicates endpoints;
* though it _might_ use SOF irqs for other purposes.
*
* And it's not currently needed for host side, which also dedicates
* endpoints, relies on TX/RX interval registers, and isn't claimed
* to support ISO transfers yet.
*/
if (int_usb & MUSB_INTR_SOF) {
void __iomem *mbase = musb->mregs;
struct musb_hw_ep *ep;
u8 epnum;
u16 frame;
dev_dbg(musb->controller, "START_OF_FRAME\n");
handled = IRQ_HANDLED;
/* start any periodic Tx transfers waiting for current frame */
frame = musb_readw(mbase, MUSB_FRAME);
ep = musb->endpoints;
for (epnum = 1; (epnum < musb->nr_endpoints)
&& (musb->epmask >= (1 << epnum));
epnum++, ep++) {
/*
* FIXME handle framecounter wraps (12 bits)
* eliminate duplicated StartUrb logic
*/
if (ep->dwWaitFrame >= frame) {
ep->dwWaitFrame = 0;
pr_debug("SOF --> periodic TX%s on %d\n",
ep->tx_channel ? " DMA" : "",
epnum);
if (!ep->tx_channel)
musb_h_tx_start(musb, epnum);
else
cppi_hostdma_start(musb, epnum);
}
} /* end of for loop */
}
#endif
schedule_delayed_work(&musb->irq_work, 0);
return handled;
}
/*-------------------------------------------------------------------------*/
static void musb_disable_interrupts(struct musb *musb)
{
void __iomem *mbase = musb->mregs;
/* disable interrupts */
musb_writeb(mbase, MUSB_INTRUSBE, 0);
musb->intrtxe = 0;
musb_writew(mbase, MUSB_INTRTXE, 0);
musb->intrrxe = 0;
musb_writew(mbase, MUSB_INTRRXE, 0);
/* flush pending interrupts */
musb_clearb(mbase, MUSB_INTRUSB);
musb_clearw(mbase, MUSB_INTRTX);
musb_clearw(mbase, MUSB_INTRRX);
}
static void musb_enable_interrupts(struct musb *musb)
{
void __iomem *regs = musb->mregs;
/* Set INT enable registers, enable interrupts */
musb->intrtxe = musb->epmask;
musb_writew(regs, MUSB_INTRTXE, musb->intrtxe);
musb->intrrxe = musb->epmask & 0xfffe;
musb_writew(regs, MUSB_INTRRXE, musb->intrrxe);
musb_writeb(regs, MUSB_INTRUSBE, 0xf7);
}
usb: musb: start musb on the udc side, too I have am335x-evm with one port running in OTG mode. Since commit fe4cb09 ("usb: musb: gadget: remove hcd initialization") the loaded gadget does non pop up on the host. All I see is |usb 4-5: new high-speed USB device number 52 using ehci-pci |usb 4-5: device descriptor read/64, error -110 Since a later commit 2cc65fe ("usb: musb: add musb_host_setup() and musb_host_cleanup()) the gadget shows up on the host again but only in OTG mode (because we have the host init code running). It does not work in device only mode. If running in OTG mode and the gadget is removed and added back (rmmod followed by modprobe of a gadget) then the same error is pops up on the host side. This patch ensures that the gadget side also executes musb_start() which puts the chip in "connect accept" mode. With this change the device works in OTG & device mode and the gadget can be added & removed multiple times. A device (if musb is in OTG mode acting as a host) is only recognized if it is attached during module load (musb_hdrc module). After the device unplugged and plugged again the host does not recognize it. We get a buch of errors if musb running in OTG mode, attached to a host and no gadget is loaded. Bah. This is one step forward. Host & device only mode should work. I will look at OTG later. I looked at this before commit fe4cb09 and OTG wasn't working there perfectly so I am not sure that it is a regression :) Cc: <stable@vger.kernel.org> # v3.11 Cc: Daniel Mack <zonque@gmail.com> Cc: Peter Korsgaard <jacmet@sunsite.dk> Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de> Signed-off-by: Felipe Balbi <balbi@ti.com>
2013-10-11 08:38:13 +00:00
/*
* Program the HDRC to start (enable interrupts, dma, etc.).
*/
void musb_start(struct musb *musb)
{
void __iomem *regs = musb->mregs;
u8 devctl = musb_readb(regs, MUSB_DEVCTL);
u8 power;
usb: musb: start musb on the udc side, too I have am335x-evm with one port running in OTG mode. Since commit fe4cb09 ("usb: musb: gadget: remove hcd initialization") the loaded gadget does non pop up on the host. All I see is |usb 4-5: new high-speed USB device number 52 using ehci-pci |usb 4-5: device descriptor read/64, error -110 Since a later commit 2cc65fe ("usb: musb: add musb_host_setup() and musb_host_cleanup()) the gadget shows up on the host again but only in OTG mode (because we have the host init code running). It does not work in device only mode. If running in OTG mode and the gadget is removed and added back (rmmod followed by modprobe of a gadget) then the same error is pops up on the host side. This patch ensures that the gadget side also executes musb_start() which puts the chip in "connect accept" mode. With this change the device works in OTG & device mode and the gadget can be added & removed multiple times. A device (if musb is in OTG mode acting as a host) is only recognized if it is attached during module load (musb_hdrc module). After the device unplugged and plugged again the host does not recognize it. We get a buch of errors if musb running in OTG mode, attached to a host and no gadget is loaded. Bah. This is one step forward. Host & device only mode should work. I will look at OTG later. I looked at this before commit fe4cb09 and OTG wasn't working there perfectly so I am not sure that it is a regression :) Cc: <stable@vger.kernel.org> # v3.11 Cc: Daniel Mack <zonque@gmail.com> Cc: Peter Korsgaard <jacmet@sunsite.dk> Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de> Signed-off-by: Felipe Balbi <balbi@ti.com>
2013-10-11 08:38:13 +00:00
musb_dbg(musb, "<== devctl %02x", devctl);
usb: musb: start musb on the udc side, too I have am335x-evm with one port running in OTG mode. Since commit fe4cb09 ("usb: musb: gadget: remove hcd initialization") the loaded gadget does non pop up on the host. All I see is |usb 4-5: new high-speed USB device number 52 using ehci-pci |usb 4-5: device descriptor read/64, error -110 Since a later commit 2cc65fe ("usb: musb: add musb_host_setup() and musb_host_cleanup()) the gadget shows up on the host again but only in OTG mode (because we have the host init code running). It does not work in device only mode. If running in OTG mode and the gadget is removed and added back (rmmod followed by modprobe of a gadget) then the same error is pops up on the host side. This patch ensures that the gadget side also executes musb_start() which puts the chip in "connect accept" mode. With this change the device works in OTG & device mode and the gadget can be added & removed multiple times. A device (if musb is in OTG mode acting as a host) is only recognized if it is attached during module load (musb_hdrc module). After the device unplugged and plugged again the host does not recognize it. We get a buch of errors if musb running in OTG mode, attached to a host and no gadget is loaded. Bah. This is one step forward. Host & device only mode should work. I will look at OTG later. I looked at this before commit fe4cb09 and OTG wasn't working there perfectly so I am not sure that it is a regression :) Cc: <stable@vger.kernel.org> # v3.11 Cc: Daniel Mack <zonque@gmail.com> Cc: Peter Korsgaard <jacmet@sunsite.dk> Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de> Signed-off-by: Felipe Balbi <balbi@ti.com>
2013-10-11 08:38:13 +00:00
musb_enable_interrupts(musb);
usb: musb: start musb on the udc side, too I have am335x-evm with one port running in OTG mode. Since commit fe4cb09 ("usb: musb: gadget: remove hcd initialization") the loaded gadget does non pop up on the host. All I see is |usb 4-5: new high-speed USB device number 52 using ehci-pci |usb 4-5: device descriptor read/64, error -110 Since a later commit 2cc65fe ("usb: musb: add musb_host_setup() and musb_host_cleanup()) the gadget shows up on the host again but only in OTG mode (because we have the host init code running). It does not work in device only mode. If running in OTG mode and the gadget is removed and added back (rmmod followed by modprobe of a gadget) then the same error is pops up on the host side. This patch ensures that the gadget side also executes musb_start() which puts the chip in "connect accept" mode. With this change the device works in OTG & device mode and the gadget can be added & removed multiple times. A device (if musb is in OTG mode acting as a host) is only recognized if it is attached during module load (musb_hdrc module). After the device unplugged and plugged again the host does not recognize it. We get a buch of errors if musb running in OTG mode, attached to a host and no gadget is loaded. Bah. This is one step forward. Host & device only mode should work. I will look at OTG later. I looked at this before commit fe4cb09 and OTG wasn't working there perfectly so I am not sure that it is a regression :) Cc: <stable@vger.kernel.org> # v3.11 Cc: Daniel Mack <zonque@gmail.com> Cc: Peter Korsgaard <jacmet@sunsite.dk> Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de> Signed-off-by: Felipe Balbi <balbi@ti.com>
2013-10-11 08:38:13 +00:00
musb_writeb(regs, MUSB_TESTMODE, 0);
power = MUSB_POWER_ISOUPDATE;
/*
* treating UNKNOWN as unspecified maximum speed, in which case
* we will default to high-speed.
*/
if (musb->config->maximum_speed == USB_SPEED_HIGH ||
musb->config->maximum_speed == USB_SPEED_UNKNOWN)
power |= MUSB_POWER_HSENAB;
musb_writeb(regs, MUSB_POWER, power);
usb: musb: start musb on the udc side, too I have am335x-evm with one port running in OTG mode. Since commit fe4cb09 ("usb: musb: gadget: remove hcd initialization") the loaded gadget does non pop up on the host. All I see is |usb 4-5: new high-speed USB device number 52 using ehci-pci |usb 4-5: device descriptor read/64, error -110 Since a later commit 2cc65fe ("usb: musb: add musb_host_setup() and musb_host_cleanup()) the gadget shows up on the host again but only in OTG mode (because we have the host init code running). It does not work in device only mode. If running in OTG mode and the gadget is removed and added back (rmmod followed by modprobe of a gadget) then the same error is pops up on the host side. This patch ensures that the gadget side also executes musb_start() which puts the chip in "connect accept" mode. With this change the device works in OTG & device mode and the gadget can be added & removed multiple times. A device (if musb is in OTG mode acting as a host) is only recognized if it is attached during module load (musb_hdrc module). After the device unplugged and plugged again the host does not recognize it. We get a buch of errors if musb running in OTG mode, attached to a host and no gadget is loaded. Bah. This is one step forward. Host & device only mode should work. I will look at OTG later. I looked at this before commit fe4cb09 and OTG wasn't working there perfectly so I am not sure that it is a regression :) Cc: <stable@vger.kernel.org> # v3.11 Cc: Daniel Mack <zonque@gmail.com> Cc: Peter Korsgaard <jacmet@sunsite.dk> Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de> Signed-off-by: Felipe Balbi <balbi@ti.com>
2013-10-11 08:38:13 +00:00
musb->is_active = 0;
devctl = musb_readb(regs, MUSB_DEVCTL);
devctl &= ~MUSB_DEVCTL_SESSION;
/* session started after:
* (a) ID-grounded irq, host mode;
* (b) vbus present/connect IRQ, peripheral mode;
* (c) peripheral initiates, using SRP
*/
if (musb->port_mode != MUSB_HOST &&
musb->xceiv->otg->state != OTG_STATE_A_WAIT_BCON &&
usb: musb: start musb on the udc side, too I have am335x-evm with one port running in OTG mode. Since commit fe4cb09 ("usb: musb: gadget: remove hcd initialization") the loaded gadget does non pop up on the host. All I see is |usb 4-5: new high-speed USB device number 52 using ehci-pci |usb 4-5: device descriptor read/64, error -110 Since a later commit 2cc65fe ("usb: musb: add musb_host_setup() and musb_host_cleanup()) the gadget shows up on the host again but only in OTG mode (because we have the host init code running). It does not work in device only mode. If running in OTG mode and the gadget is removed and added back (rmmod followed by modprobe of a gadget) then the same error is pops up on the host side. This patch ensures that the gadget side also executes musb_start() which puts the chip in "connect accept" mode. With this change the device works in OTG & device mode and the gadget can be added & removed multiple times. A device (if musb is in OTG mode acting as a host) is only recognized if it is attached during module load (musb_hdrc module). After the device unplugged and plugged again the host does not recognize it. We get a buch of errors if musb running in OTG mode, attached to a host and no gadget is loaded. Bah. This is one step forward. Host & device only mode should work. I will look at OTG later. I looked at this before commit fe4cb09 and OTG wasn't working there perfectly so I am not sure that it is a regression :) Cc: <stable@vger.kernel.org> # v3.11 Cc: Daniel Mack <zonque@gmail.com> Cc: Peter Korsgaard <jacmet@sunsite.dk> Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de> Signed-off-by: Felipe Balbi <balbi@ti.com>
2013-10-11 08:38:13 +00:00
(devctl & MUSB_DEVCTL_VBUS) == MUSB_DEVCTL_VBUS) {
musb->is_active = 1;
} else {
devctl |= MUSB_DEVCTL_SESSION;
}
musb_platform_enable(musb);
musb_writeb(regs, MUSB_DEVCTL, devctl);
}
/*
* Make the HDRC stop (disable interrupts, etc.);
* reversible by musb_start
* called on gadget driver unregister
* with controller locked, irqs blocked
* acts as a NOP unless some role activated the hardware
*/
void musb_stop(struct musb *musb)
{
/* stop IRQs, timers, ... */
musb_platform_disable(musb);
musb_disable_interrupts(musb);
musb_writeb(musb->mregs, MUSB_DEVCTL, 0);
/* FIXME
* - mark host and/or peripheral drivers unusable/inactive
* - disable DMA (and enable it in HdrcStart)
* - make sure we can musb_start() after musb_stop(); with
* OTG mode, gadget driver module rmmod/modprobe cycles that
* - ...
*/
musb_platform_try_idle(musb, 0);
}
/*-------------------------------------------------------------------------*/
/*
* The silicon either has hard-wired endpoint configurations, or else
* "dynamic fifo" sizing. The driver has support for both, though at this
* writing only the dynamic sizing is very well tested. Since we switched
* away from compile-time hardware parameters, we can no longer rely on
* dead code elimination to leave only the relevant one in the object file.
*
* We don't currently use dynamic fifo setup capability to do anything
* more than selecting one of a bunch of predefined configurations.
*/
static ushort fifo_mode;
/* "modprobe ... fifo_mode=1" etc */
module_param(fifo_mode, ushort, 0);
MODULE_PARM_DESC(fifo_mode, "initial endpoint configuration");
/*
* tables defining fifo_mode values. define more if you like.
* for host side, make sure both halves of ep1 are set up.
*/
/* mode 0 - fits in 2KB */
static struct musb_fifo_cfg mode_0_cfg[] = {
{ .hw_ep_num = 1, .style = FIFO_TX, .maxpacket = 512, },
{ .hw_ep_num = 1, .style = FIFO_RX, .maxpacket = 512, },
{ .hw_ep_num = 2, .style = FIFO_RXTX, .maxpacket = 512, },
{ .hw_ep_num = 3, .style = FIFO_RXTX, .maxpacket = 256, },
{ .hw_ep_num = 4, .style = FIFO_RXTX, .maxpacket = 256, },
};
/* mode 1 - fits in 4KB */
static struct musb_fifo_cfg mode_1_cfg[] = {
{ .hw_ep_num = 1, .style = FIFO_TX, .maxpacket = 512, .mode = BUF_DOUBLE, },
{ .hw_ep_num = 1, .style = FIFO_RX, .maxpacket = 512, .mode = BUF_DOUBLE, },
{ .hw_ep_num = 2, .style = FIFO_RXTX, .maxpacket = 512, .mode = BUF_DOUBLE, },
{ .hw_ep_num = 3, .style = FIFO_RXTX, .maxpacket = 256, },
{ .hw_ep_num = 4, .style = FIFO_RXTX, .maxpacket = 256, },
};
/* mode 2 - fits in 4KB */
static struct musb_fifo_cfg mode_2_cfg[] = {
{ .hw_ep_num = 1, .style = FIFO_TX, .maxpacket = 512, },
{ .hw_ep_num = 1, .style = FIFO_RX, .maxpacket = 512, },
{ .hw_ep_num = 2, .style = FIFO_TX, .maxpacket = 512, },
{ .hw_ep_num = 2, .style = FIFO_RX, .maxpacket = 512, },
{ .hw_ep_num = 3, .style = FIFO_RXTX, .maxpacket = 960, },
{ .hw_ep_num = 4, .style = FIFO_RXTX, .maxpacket = 1024, },
};
/* mode 3 - fits in 4KB */
static struct musb_fifo_cfg mode_3_cfg[] = {
{ .hw_ep_num = 1, .style = FIFO_TX, .maxpacket = 512, .mode = BUF_DOUBLE, },
{ .hw_ep_num = 1, .style = FIFO_RX, .maxpacket = 512, .mode = BUF_DOUBLE, },
{ .hw_ep_num = 2, .style = FIFO_TX, .maxpacket = 512, },
{ .hw_ep_num = 2, .style = FIFO_RX, .maxpacket = 512, },
{ .hw_ep_num = 3, .style = FIFO_RXTX, .maxpacket = 256, },
{ .hw_ep_num = 4, .style = FIFO_RXTX, .maxpacket = 256, },
};
/* mode 4 - fits in 16KB */
static struct musb_fifo_cfg mode_4_cfg[] = {
{ .hw_ep_num = 1, .style = FIFO_TX, .maxpacket = 512, },
{ .hw_ep_num = 1, .style = FIFO_RX, .maxpacket = 512, },
{ .hw_ep_num = 2, .style = FIFO_TX, .maxpacket = 512, },
{ .hw_ep_num = 2, .style = FIFO_RX, .maxpacket = 512, },
{ .hw_ep_num = 3, .style = FIFO_TX, .maxpacket = 512, },
{ .hw_ep_num = 3, .style = FIFO_RX, .maxpacket = 512, },
{ .hw_ep_num = 4, .style = FIFO_TX, .maxpacket = 512, },
{ .hw_ep_num = 4, .style = FIFO_RX, .maxpacket = 512, },
{ .hw_ep_num = 5, .style = FIFO_TX, .maxpacket = 512, },
{ .hw_ep_num = 5, .style = FIFO_RX, .maxpacket = 512, },
{ .hw_ep_num = 6, .style = FIFO_TX, .maxpacket = 512, },
{ .hw_ep_num = 6, .style = FIFO_RX, .maxpacket = 512, },
{ .hw_ep_num = 7, .style = FIFO_TX, .maxpacket = 512, },
{ .hw_ep_num = 7, .style = FIFO_RX, .maxpacket = 512, },
{ .hw_ep_num = 8, .style = FIFO_TX, .maxpacket = 512, },
{ .hw_ep_num = 8, .style = FIFO_RX, .maxpacket = 512, },
{ .hw_ep_num = 9, .style = FIFO_TX, .maxpacket = 512, },
{ .hw_ep_num = 9, .style = FIFO_RX, .maxpacket = 512, },
{ .hw_ep_num = 10, .style = FIFO_TX, .maxpacket = 256, },
{ .hw_ep_num = 10, .style = FIFO_RX, .maxpacket = 64, },
{ .hw_ep_num = 11, .style = FIFO_TX, .maxpacket = 256, },
{ .hw_ep_num = 11, .style = FIFO_RX, .maxpacket = 64, },
{ .hw_ep_num = 12, .style = FIFO_TX, .maxpacket = 256, },
{ .hw_ep_num = 12, .style = FIFO_RX, .maxpacket = 64, },
{ .hw_ep_num = 13, .style = FIFO_RXTX, .maxpacket = 4096, },
{ .hw_ep_num = 14, .style = FIFO_RXTX, .maxpacket = 1024, },
{ .hw_ep_num = 15, .style = FIFO_RXTX, .maxpacket = 1024, },
};
/* mode 5 - fits in 8KB */
static struct musb_fifo_cfg mode_5_cfg[] = {
{ .hw_ep_num = 1, .style = FIFO_TX, .maxpacket = 512, },
{ .hw_ep_num = 1, .style = FIFO_RX, .maxpacket = 512, },
{ .hw_ep_num = 2, .style = FIFO_TX, .maxpacket = 512, },
{ .hw_ep_num = 2, .style = FIFO_RX, .maxpacket = 512, },
{ .hw_ep_num = 3, .style = FIFO_TX, .maxpacket = 512, },
{ .hw_ep_num = 3, .style = FIFO_RX, .maxpacket = 512, },
{ .hw_ep_num = 4, .style = FIFO_TX, .maxpacket = 512, },
{ .hw_ep_num = 4, .style = FIFO_RX, .maxpacket = 512, },
{ .hw_ep_num = 5, .style = FIFO_TX, .maxpacket = 512, },
{ .hw_ep_num = 5, .style = FIFO_RX, .maxpacket = 512, },
{ .hw_ep_num = 6, .style = FIFO_TX, .maxpacket = 32, },
{ .hw_ep_num = 6, .style = FIFO_RX, .maxpacket = 32, },
{ .hw_ep_num = 7, .style = FIFO_TX, .maxpacket = 32, },
{ .hw_ep_num = 7, .style = FIFO_RX, .maxpacket = 32, },
{ .hw_ep_num = 8, .style = FIFO_TX, .maxpacket = 32, },
{ .hw_ep_num = 8, .style = FIFO_RX, .maxpacket = 32, },
{ .hw_ep_num = 9, .style = FIFO_TX, .maxpacket = 32, },
{ .hw_ep_num = 9, .style = FIFO_RX, .maxpacket = 32, },
{ .hw_ep_num = 10, .style = FIFO_TX, .maxpacket = 32, },
{ .hw_ep_num = 10, .style = FIFO_RX, .maxpacket = 32, },
{ .hw_ep_num = 11, .style = FIFO_TX, .maxpacket = 32, },
{ .hw_ep_num = 11, .style = FIFO_RX, .maxpacket = 32, },
{ .hw_ep_num = 12, .style = FIFO_TX, .maxpacket = 32, },
{ .hw_ep_num = 12, .style = FIFO_RX, .maxpacket = 32, },
{ .hw_ep_num = 13, .style = FIFO_RXTX, .maxpacket = 512, },
{ .hw_ep_num = 14, .style = FIFO_RXTX, .maxpacket = 1024, },
{ .hw_ep_num = 15, .style = FIFO_RXTX, .maxpacket = 1024, },
};
/*
* configure a fifo; for non-shared endpoints, this may be called
* once for a tx fifo and once for an rx fifo.
*
* returns negative errno or offset for next fifo.
*/
static int
fifo_setup(struct musb *musb, struct musb_hw_ep *hw_ep,
const struct musb_fifo_cfg *cfg, u16 offset)
{
void __iomem *mbase = musb->mregs;
int size = 0;
u16 maxpacket = cfg->maxpacket;
u16 c_off = offset >> 3;
u8 c_size;
/* expect hw_ep has already been zero-initialized */
size = ffs(max(maxpacket, (u16) 8)) - 1;
maxpacket = 1 << size;
c_size = size - 3;
if (cfg->mode == BUF_DOUBLE) {
if ((offset + (maxpacket << 1)) >
(1 << (musb->config->ram_bits + 2)))
return -EMSGSIZE;
c_size |= MUSB_FIFOSZ_DPB;
} else {
if ((offset + maxpacket) > (1 << (musb->config->ram_bits + 2)))
return -EMSGSIZE;
}
/* configure the FIFO */
musb_writeb(mbase, MUSB_INDEX, hw_ep->epnum);
/* EP0 reserved endpoint for control, bidirectional;
* EP1 reserved for bulk, two unidirectional halves.
*/
if (hw_ep->epnum == 1)
musb->bulk_ep = hw_ep;
/* REVISIT error check: be sure ep0 can both rx and tx ... */
switch (cfg->style) {
case FIFO_TX:
musb_writeb(mbase, MUSB_TXFIFOSZ, c_size);
musb_writew(mbase, MUSB_TXFIFOADD, c_off);
hw_ep->tx_double_buffered = !!(c_size & MUSB_FIFOSZ_DPB);
hw_ep->max_packet_sz_tx = maxpacket;
break;
case FIFO_RX:
musb_writeb(mbase, MUSB_RXFIFOSZ, c_size);
musb_writew(mbase, MUSB_RXFIFOADD, c_off);
hw_ep->rx_double_buffered = !!(c_size & MUSB_FIFOSZ_DPB);
hw_ep->max_packet_sz_rx = maxpacket;
break;
case FIFO_RXTX:
musb_writeb(mbase, MUSB_TXFIFOSZ, c_size);
musb_writew(mbase, MUSB_TXFIFOADD, c_off);
hw_ep->rx_double_buffered = !!(c_size & MUSB_FIFOSZ_DPB);
hw_ep->max_packet_sz_rx = maxpacket;
musb_writeb(mbase, MUSB_RXFIFOSZ, c_size);
musb_writew(mbase, MUSB_RXFIFOADD, c_off);
hw_ep->tx_double_buffered = hw_ep->rx_double_buffered;
hw_ep->max_packet_sz_tx = maxpacket;
hw_ep->is_shared_fifo = true;
break;
}
/* NOTE rx and tx endpoint irqs aren't managed separately,
* which happens to be ok
*/
musb->epmask |= (1 << hw_ep->epnum);
return offset + (maxpacket << ((c_size & MUSB_FIFOSZ_DPB) ? 1 : 0));
}
static struct musb_fifo_cfg ep0_cfg = {
.style = FIFO_RXTX, .maxpacket = 64,
};
static int ep_config_from_table(struct musb *musb)
{
const struct musb_fifo_cfg *cfg;
unsigned i, n;
int offset;
struct musb_hw_ep *hw_ep = musb->endpoints;
if (musb->config->fifo_cfg) {
cfg = musb->config->fifo_cfg;
n = musb->config->fifo_cfg_size;
goto done;
}
switch (fifo_mode) {
default:
fifo_mode = 0;
fallthrough;
case 0:
cfg = mode_0_cfg;
n = ARRAY_SIZE(mode_0_cfg);
break;
case 1:
cfg = mode_1_cfg;
n = ARRAY_SIZE(mode_1_cfg);
break;
case 2:
cfg = mode_2_cfg;
n = ARRAY_SIZE(mode_2_cfg);
break;
case 3:
cfg = mode_3_cfg;
n = ARRAY_SIZE(mode_3_cfg);
break;
case 4:
cfg = mode_4_cfg;
n = ARRAY_SIZE(mode_4_cfg);
break;
case 5:
cfg = mode_5_cfg;
n = ARRAY_SIZE(mode_5_cfg);
break;
}
pr_debug("%s: setup fifo_mode %d\n", musb_driver_name, fifo_mode);
done:
offset = fifo_setup(musb, hw_ep, &ep0_cfg, 0);
/* assert(offset > 0) */
/* NOTE: for RTL versions >= 1.400 EPINFO and RAMINFO would
* be better than static musb->config->num_eps and DYN_FIFO_SIZE...
*/
for (i = 0; i < n; i++) {
u8 epn = cfg->hw_ep_num;
if (epn >= musb->config->num_eps) {
pr_debug("%s: invalid ep %d\n",
musb_driver_name, epn);
return -EINVAL;
}
offset = fifo_setup(musb, hw_ep + epn, cfg++, offset);
if (offset < 0) {
pr_debug("%s: mem overrun, ep %d\n",
musb_driver_name, epn);
return offset;
}
epn++;
musb->nr_endpoints = max(epn, musb->nr_endpoints);
}
pr_debug("%s: %d/%d max ep, %d/%d memory\n",
musb_driver_name,
n + 1, musb->config->num_eps * 2 - 1,
offset, (1 << (musb->config->ram_bits + 2)));
if (!musb->bulk_ep) {
pr_debug("%s: missing bulk\n", musb_driver_name);
return -EINVAL;
}
return 0;
}
/*
* ep_config_from_hw - when MUSB_C_DYNFIFO_DEF is false
* @param musb the controller
*/
static int ep_config_from_hw(struct musb *musb)
{
u8 epnum = 0;
struct musb_hw_ep *hw_ep;
usb: musb: fix sparse warnings The following warnings are fixed: drivers/usb/musb/musb_core.c:357:6: warning: symbol 'musb_otg_timer_func' was not declared. Should it be static? drivers/usb/musb/musb_core.c:1339:27: warning: incorrect type in initializer (different address spaces) drivers/usb/musb/musb_core.c:1339:27: expected void *mbase drivers/usb/musb/musb_core.c:1339:27: got void [noderef] <asn:2>*mregs drivers/usb/musb/musb_core.c:1347:17: warning: incorrect type in argument 1 (different address spaces) drivers/usb/musb/musb_core.c:1347:17: expected void [noderef] <asn:2>*addr drivers/usb/musb/musb_core.c:1347:17: got void *mbase drivers/usb/musb/musb_core.h:487:27: warning: incorrect type in initializer (different address spaces) drivers/usb/musb/musb_core.h:487:27: expected void *mbase drivers/usb/musb/musb_core.h:487:27: got void [noderef] <asn:2>*mregs drivers/usb/musb/musb_core.h:491:26: warning: incorrect type in argument 1 (different address spaces) drivers/usb/musb/musb_core.h:491:26: expected void const [noderef] <asn:2>*addr drivers/usb/musb/musb_core.h:491:26: got void *mbase drivers/usb/musb/tusb6010.c:270:48: warning: incorrect type in argument 2 (different address spaces) drivers/usb/musb/tusb6010.c:270:48: expected void [noderef] <asn:2>*buf drivers/usb/musb/tusb6010.c:270:48: got unsigned char [usertype] *[assigned] buf drivers/usb/musb/tusb6010.c:164:32: warning: incorrect type in argument 1 (different address spaces) drivers/usb/musb/tusb6010.c:164:32: expected void *to drivers/usb/musb/tusb6010.c:164:32: got void [noderef] <asn:2>*buf drivers/usb/musb/tusb6010.c:172:24: warning: incorrect type in argument 1 (different address spaces) drivers/usb/musb/tusb6010.c:172:24: expected void *to drivers/usb/musb/tusb6010.c:172:24: got void [noderef] <asn:2>*[assigned] buf Signed-off-by: Felipe Balbi <balbi@ti.com>
2012-08-07 11:00:50 +00:00
void __iomem *mbase = musb->mregs;
int ret = 0;
musb_dbg(musb, "<== static silicon ep config");
/* FIXME pick up ep0 maxpacket size */
for (epnum = 1; epnum < musb->config->num_eps; epnum++) {
musb_ep_select(mbase, epnum);
hw_ep = musb->endpoints + epnum;
ret = musb_read_fifosize(musb, hw_ep, epnum);
if (ret < 0)
break;
/* FIXME set up hw_ep->{rx,tx}_double_buffered */
/* pick an RX/TX endpoint for bulk */
if (hw_ep->max_packet_sz_tx < 512
|| hw_ep->max_packet_sz_rx < 512)
continue;
/* REVISIT: this algorithm is lazy, we should at least
* try to pick a double buffered endpoint.
*/
if (musb->bulk_ep)
continue;
musb->bulk_ep = hw_ep;
}
if (!musb->bulk_ep) {
pr_debug("%s: missing bulk\n", musb_driver_name);
return -EINVAL;
}
return 0;
}
enum { MUSB_CONTROLLER_MHDRC, MUSB_CONTROLLER_HDRC, };
/* Initialize MUSB (M)HDRC part of the USB hardware subsystem;
* configure endpoints, or take their config from silicon
*/
static int musb_core_init(u16 musb_type, struct musb *musb)
{
u8 reg;
char *type;
char aInfo[90];
void __iomem *mbase = musb->mregs;
int status = 0;
int i;
/* log core options (read using indexed model) */
reg = musb_read_configdata(mbase);
strcpy(aInfo, (reg & MUSB_CONFIGDATA_UTMIDW) ? "UTMI-16" : "UTMI-8");
if (reg & MUSB_CONFIGDATA_DYNFIFO) {
strcat(aInfo, ", dyn FIFOs");
musb->dyn_fifo = true;
}
if (reg & MUSB_CONFIGDATA_MPRXE) {
strcat(aInfo, ", bulk combine");
musb->bulk_combine = true;
}
if (reg & MUSB_CONFIGDATA_MPTXE) {
strcat(aInfo, ", bulk split");
musb->bulk_split = true;
}
if (reg & MUSB_CONFIGDATA_HBRXE) {
strcat(aInfo, ", HB-ISO Rx");
musb->hb_iso_rx = true;
}
if (reg & MUSB_CONFIGDATA_HBTXE) {
strcat(aInfo, ", HB-ISO Tx");
musb->hb_iso_tx = true;
}
if (reg & MUSB_CONFIGDATA_SOFTCONE)
strcat(aInfo, ", SoftConn");
pr_debug("%s: ConfigData=0x%02x (%s)\n", musb_driver_name, reg, aInfo);
if (MUSB_CONTROLLER_MHDRC == musb_type) {
musb->is_multipoint = 1;
type = "M";
} else {
musb->is_multipoint = 0;
type = "";
if (IS_ENABLED(CONFIG_USB) &&
!IS_ENABLED(CONFIG_USB_OTG_DISABLE_EXTERNAL_HUB)) {
pr_err("%s: kernel must disable external hubs, please fix the configuration\n",
musb_driver_name);
}
}
/* log release info */
musb->hwvers = musb_readw(mbase, MUSB_HWVERS);
pr_debug("%s: %sHDRC RTL version %d.%d%s\n",
musb_driver_name, type, MUSB_HWVERS_MAJOR(musb->hwvers),
MUSB_HWVERS_MINOR(musb->hwvers),
(musb->hwvers & MUSB_HWVERS_RC) ? "RC" : "");
/* configure ep0 */
musb_configure_ep0(musb);
/* discover endpoint configuration */
musb->nr_endpoints = 1;
musb->epmask = 1;
if (musb->dyn_fifo)
status = ep_config_from_table(musb);
else
status = ep_config_from_hw(musb);
if (status < 0)
return status;
/* finish init, and print endpoint config */
for (i = 0; i < musb->nr_endpoints; i++) {
struct musb_hw_ep *hw_ep = musb->endpoints + i;
hw_ep->fifo = musb->io.fifo_offset(i) + mbase;
#if IS_ENABLED(CONFIG_USB_MUSB_TUSB6010)
if (musb->ops->quirks & MUSB_IN_TUSB) {
hw_ep->fifo_async = musb->async + 0x400 +
musb->io.fifo_offset(i);
hw_ep->fifo_sync = musb->sync + 0x400 +
musb->io.fifo_offset(i);
hw_ep->fifo_sync_va =
musb->sync_va + 0x400 + musb->io.fifo_offset(i);
if (i == 0)
hw_ep->conf = mbase - 0x400 + TUSB_EP0_CONF;
else
hw_ep->conf = mbase + 0x400 +
(((i - 1) & 0xf) << 2);
}
#endif
hw_ep->regs = musb->io.ep_offset(i, 0) + mbase;
hw_ep->rx_reinit = 1;
hw_ep->tx_reinit = 1;
if (hw_ep->max_packet_sz_tx) {
musb_dbg(musb, "%s: hw_ep %d%s, %smax %d",
musb_driver_name, i,
hw_ep->is_shared_fifo ? "shared" : "tx",
hw_ep->tx_double_buffered
? "doublebuffer, " : "",
hw_ep->max_packet_sz_tx);
}
if (hw_ep->max_packet_sz_rx && !hw_ep->is_shared_fifo) {
musb_dbg(musb, "%s: hw_ep %d%s, %smax %d",
musb_driver_name, i,
"rx",
hw_ep->rx_double_buffered
? "doublebuffer, " : "",
hw_ep->max_packet_sz_rx);
}
if (!(hw_ep->max_packet_sz_tx || hw_ep->max_packet_sz_rx))
musb_dbg(musb, "hw_ep %d not configured", i);
}
return 0;
}
/*-------------------------------------------------------------------------*/
/*
* handle all the irqs defined by the HDRC core. for now we expect: other
* irq sources (phy, dma, etc) will be handled first, musb->int_* values
* will be assigned, and the irq will already have been acked.
*
* called in irq context with spinlock held, irqs blocked
*/
irqreturn_t musb_interrupt(struct musb *musb)
{
irqreturn_t retval = IRQ_NONE;
unsigned long status;
unsigned long epnum;
u8 devctl;
if (!musb->int_usb && !musb->int_tx && !musb->int_rx)
return IRQ_NONE;
devctl = musb_readb(musb->mregs, MUSB_DEVCTL);
trace_musb_isr(musb);
/**
* According to Mentor Graphics' documentation, flowchart on page 98,
* IRQ should be handled as follows:
*
* . Resume IRQ
* . Session Request IRQ
* . VBUS Error IRQ
* . Suspend IRQ
* . Connect IRQ
* . Disconnect IRQ
* . Reset/Babble IRQ
* . SOF IRQ (we're not using this one)
* . Endpoint 0 IRQ
* . TX Endpoints
* . RX Endpoints
*
* We will be following that flowchart in order to avoid any problems
* that might arise with internal Finite State Machine.
*/
if (musb->int_usb)
retval |= musb_stage0_irq(musb, musb->int_usb, devctl);
if (musb->int_tx & 1) {
if (is_host_active(musb))
retval |= musb_h_ep0_irq(musb);
else
retval |= musb_g_ep0_irq(musb);
/* we have just handled endpoint 0 IRQ, clear it */
musb->int_tx &= ~BIT(0);
}
status = musb->int_tx;
for_each_set_bit(epnum, &status, 16) {
retval = IRQ_HANDLED;
if (is_host_active(musb))
musb_host_tx(musb, epnum);
else
musb_g_tx(musb, epnum);
}
status = musb->int_rx;
for_each_set_bit(epnum, &status, 16) {
retval = IRQ_HANDLED;
if (is_host_active(musb))
musb_host_rx(musb, epnum);
else
musb_g_rx(musb, epnum);
}
return retval;
}
EXPORT_SYMBOL_GPL(musb_interrupt);
#ifndef CONFIG_MUSB_PIO_ONLY
static bool use_dma = true;
/* "modprobe ... use_dma=0" etc */
module_param(use_dma, bool, 0644);
MODULE_PARM_DESC(use_dma, "enable/disable use of DMA");
void musb_dma_completion(struct musb *musb, u8 epnum, u8 transmit)
{
/* called with controller lock already held */
if (!epnum) {
if (!is_cppi_enabled(musb)) {
/* endpoint 0 */
if (is_host_active(musb))
musb_h_ep0_irq(musb);
else
musb_g_ep0_irq(musb);
}
} else {
/* endpoints 1..15 */
if (transmit) {
if (is_host_active(musb))
musb_host_tx(musb, epnum);
else
musb_g_tx(musb, epnum);
} else {
/* receive */
if (is_host_active(musb))
musb_host_rx(musb, epnum);
else
musb_g_rx(musb, epnum);
}
}
}
EXPORT_SYMBOL_GPL(musb_dma_completion);
#else
#define use_dma 0
#endif
static int (*musb_phy_callback)(enum musb_vbus_id_status status);
/*
* musb_mailbox - optional phy notifier function
* @status phy state change
*
* Optionally gets called from the USB PHY. Note that the USB PHY must be
* disabled at the point the phy_callback is registered or unregistered.
*/
int musb_mailbox(enum musb_vbus_id_status status)
{
if (musb_phy_callback)
return musb_phy_callback(status);
return -ENODEV;
};
EXPORT_SYMBOL_GPL(musb_mailbox);
/*-------------------------------------------------------------------------*/
static ssize_t
mode_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct musb *musb = dev_to_musb(dev);
unsigned long flags;
int ret;
spin_lock_irqsave(&musb->lock, flags);
ret = sprintf(buf, "%s\n", usb_otg_state_string(musb->xceiv->otg->state));
spin_unlock_irqrestore(&musb->lock, flags);
return ret;
}
static ssize_t
mode_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t n)
{
struct musb *musb = dev_to_musb(dev);
unsigned long flags;
int status;
spin_lock_irqsave(&musb->lock, flags);
if (sysfs_streq(buf, "host"))
status = musb_platform_set_mode(musb, MUSB_HOST);
else if (sysfs_streq(buf, "peripheral"))
status = musb_platform_set_mode(musb, MUSB_PERIPHERAL);
else if (sysfs_streq(buf, "otg"))
status = musb_platform_set_mode(musb, MUSB_OTG);
else
status = -EINVAL;
spin_unlock_irqrestore(&musb->lock, flags);
return (status == 0) ? n : status;
}
static DEVICE_ATTR_RW(mode);
static ssize_t
vbus_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t n)
{
struct musb *musb = dev_to_musb(dev);
unsigned long flags;
unsigned long val;
if (sscanf(buf, "%lu", &val) < 1) {
dev_err(dev, "Invalid VBUS timeout ms value\n");
return -EINVAL;
}
spin_lock_irqsave(&musb->lock, flags);
/* force T(a_wait_bcon) to be zero/unlimited *OR* valid */
musb->a_wait_bcon = val ? max_t(int, val, OTG_TIME_A_WAIT_BCON) : 0 ;
if (musb->xceiv->otg->state == OTG_STATE_A_WAIT_BCON)
musb->is_active = 0;
musb_platform_try_idle(musb, jiffies + msecs_to_jiffies(val));
spin_unlock_irqrestore(&musb->lock, flags);
return n;
}
static ssize_t
vbus_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct musb *musb = dev_to_musb(dev);
unsigned long flags;
unsigned long val;
int vbus;
u8 devctl;
pm_runtime_get_sync(dev);
spin_lock_irqsave(&musb->lock, flags);
val = musb->a_wait_bcon;
vbus = musb_platform_get_vbus_status(musb);
if (vbus < 0) {
/* Use default MUSB method by means of DEVCTL register */
devctl = musb_readb(musb->mregs, MUSB_DEVCTL);
if ((devctl & MUSB_DEVCTL_VBUS)
== (3 << MUSB_DEVCTL_VBUS_SHIFT))
vbus = 1;
else
vbus = 0;
}
spin_unlock_irqrestore(&musb->lock, flags);
pm_runtime_put_sync(dev);
return sprintf(buf, "Vbus %s, timeout %lu msec\n",
vbus ? "on" : "off", val);
}
static DEVICE_ATTR_RW(vbus);
/* Gadget drivers can't know that a host is connected so they might want
* to start SRP, but users can. This allows userspace to trigger SRP.
*/
static ssize_t srp_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t n)
{
struct musb *musb = dev_to_musb(dev);
unsigned short srp;
if (sscanf(buf, "%hu", &srp) != 1
|| (srp != 1)) {
dev_err(dev, "SRP: Value must be 1\n");
return -EINVAL;
}
if (srp == 1)
musb_g_wakeup(musb);
return n;
}
static DEVICE_ATTR_WO(srp);
static struct attribute *musb_attrs[] = {
&dev_attr_mode.attr,
&dev_attr_vbus.attr,
&dev_attr_srp.attr,
NULL
};
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)
/*
* Check the musb devctl session bit to determine if we want to
* allow PM runtime for the device. In general, we want to keep things
* active when the session bit is set except after host disconnect.
*
* Only called from musb_irq_work. If this ever needs to get called
* elsewhere, proper locking must be implemented for musb->session.
*/
static void musb_pm_runtime_check_session(struct musb *musb)
{
u8 devctl, s;
int error;
devctl = musb_readb(musb->mregs, MUSB_DEVCTL);
/* Handle session status quirks first */
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:
USB: musb: fix late external abort on suspend The musb delayed irq work was never flushed on suspend, something which since 4.9 can lead to an external abort if the work is scheduled after the grandparent's clock has been disabled: PM: Suspending system (mem) PM: suspend of devices complete after 125.224 msecs PM: suspend devices took 0.132 seconds PM: late suspend of devices complete after 7.423 msecs PM: noirq suspend of devices complete after 7.083 msecs suspend debug: Waiting for 5 second(s). Unhandled fault: external abort on non-linefetch (0x1008) at 0xd0262c60 ... [<c054880c>] (musb_default_readb) from [<c0547b5c>] (musb_irq_work+0x48/0x220) [<c0547b5c>] (musb_irq_work) from [<c014f8a4>] (process_one_work+0x1f4/0x758) [<c014f8a4>] (process_one_work) from [<c014fe5c>] (worker_thread+0x54/0x514) [<c014fe5c>] (worker_thread) from [<c015704c>] (kthread+0x128/0x158) [<c015704c>] (kthread) from [<c0109330>] (ret_from_fork+0x14/0x24) Commit 2bff3916fda9 ("usb: musb: Fix PM for hub disconnect") started scheduling musb_irq_work with a delay of up to a second and with retries thereby making this easy to trigger, for example, by suspending shortly after a disconnect. Note that we set a flag to prevent the irq work from rescheduling itself during suspend and instead process a disconnect immediately. This takes care of the case where we are disconnected shortly before suspending. However, when in host mode, a disconnect while suspended will still go unnoticed and thus prevent the controller from runtime suspending upon resume as the session bit is always set. This will need to be addressed separately. Fixes: 550a7375fe72 ("USB: Add MUSB and TUSB support") Fixes: 467d5c980709 ("usb: musb: Implement session bit based runtime PM for musb-core") Fixes: 2bff3916fda9 ("usb: musb: Fix PM for hub disconnect") Cc: stable <stable@vger.kernel.org> # 4.9 Cc: Felipe Balbi <felipe.balbi@linux.intel.com> Cc: Tony Lindgren <tony@atomide.com> Signed-off-by: Johan Hovold <johan@kernel.org> Tested-by: Tony Lindgren <tony@atomide.com> Signed-off-by: Bin Liu <b-liu@ti.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2017-10-10 03:46:08 +00:00
if (musb->quirk_retries && !musb->flush_irq_work) {
musb_dbg(musb,
"Poll devctl on invalid vbus, assume no session");
schedule_delayed_work(&musb->irq_work,
msecs_to_jiffies(1000));
musb->quirk_retries--;
return;
}
fallthrough;
case MUSB_QUIRK_A_DISCONNECT_19:
USB: musb: fix late external abort on suspend The musb delayed irq work was never flushed on suspend, something which since 4.9 can lead to an external abort if the work is scheduled after the grandparent's clock has been disabled: PM: Suspending system (mem) PM: suspend of devices complete after 125.224 msecs PM: suspend devices took 0.132 seconds PM: late suspend of devices complete after 7.423 msecs PM: noirq suspend of devices complete after 7.083 msecs suspend debug: Waiting for 5 second(s). Unhandled fault: external abort on non-linefetch (0x1008) at 0xd0262c60 ... [<c054880c>] (musb_default_readb) from [<c0547b5c>] (musb_irq_work+0x48/0x220) [<c0547b5c>] (musb_irq_work) from [<c014f8a4>] (process_one_work+0x1f4/0x758) [<c014f8a4>] (process_one_work) from [<c014fe5c>] (worker_thread+0x54/0x514) [<c014fe5c>] (worker_thread) from [<c015704c>] (kthread+0x128/0x158) [<c015704c>] (kthread) from [<c0109330>] (ret_from_fork+0x14/0x24) Commit 2bff3916fda9 ("usb: musb: Fix PM for hub disconnect") started scheduling musb_irq_work with a delay of up to a second and with retries thereby making this easy to trigger, for example, by suspending shortly after a disconnect. Note that we set a flag to prevent the irq work from rescheduling itself during suspend and instead process a disconnect immediately. This takes care of the case where we are disconnected shortly before suspending. However, when in host mode, a disconnect while suspended will still go unnoticed and thus prevent the controller from runtime suspending upon resume as the session bit is always set. This will need to be addressed separately. Fixes: 550a7375fe72 ("USB: Add MUSB and TUSB support") Fixes: 467d5c980709 ("usb: musb: Implement session bit based runtime PM for musb-core") Fixes: 2bff3916fda9 ("usb: musb: Fix PM for hub disconnect") Cc: stable <stable@vger.kernel.org> # 4.9 Cc: Felipe Balbi <felipe.balbi@linux.intel.com> Cc: Tony Lindgren <tony@atomide.com> Signed-off-by: Johan Hovold <johan@kernel.org> Tested-by: Tony Lindgren <tony@atomide.com> Signed-off-by: Bin Liu <b-liu@ti.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2017-10-10 03:46:08 +00:00
if (musb->quirk_retries && !musb->flush_irq_work) {
musb_dbg(musb,
"Poll devctl on possible host mode disconnect");
schedule_delayed_work(&musb->irq_work,
msecs_to_jiffies(1000));
musb->quirk_retries--;
return;
}
if (!musb->session)
break;
musb_dbg(musb, "Allow PM on possible host mode disconnect");
pm_runtime_mark_last_busy(musb->controller);
pm_runtime_put_autosuspend(musb->controller);
musb->session = false;
return;
default:
break;
}
/* No need to do anything if session has not changed */
s = devctl & MUSB_DEVCTL_SESSION;
if (s == musb->session)
return;
/* Block PM or allow PM? */
if (s) {
musb_dbg(musb, "Block PM on active session: %02x", devctl);
error = pm_runtime_get_sync(musb->controller);
if (error < 0)
dev_err(musb->controller, "Could not enable: %i\n",
error);
musb->quirk_retries = 3;
} else {
musb_dbg(musb, "Allow PM with no session: %02x", devctl);
pm_runtime_mark_last_busy(musb->controller);
pm_runtime_put_autosuspend(musb->controller);
}
musb->session = s;
}
/* Only used to provide driver mode change events */
static void musb_irq_work(struct work_struct *data)
{
struct musb *musb = container_of(data, struct musb, irq_work.work);
int error;
error = pm_runtime_get_sync(musb->controller);
if (error < 0) {
dev_err(musb->controller, "Could not enable: %i\n", error);
return;
}
musb_pm_runtime_check_session(musb);
if (musb->xceiv->otg->state != musb->xceiv_old_state) {
musb->xceiv_old_state = musb->xceiv->otg->state;
sysfs_notify(&musb->controller->kobj, NULL, "mode");
}
pm_runtime_mark_last_busy(musb->controller);
pm_runtime_put_autosuspend(musb->controller);
}
static void musb_recover_from_babble(struct musb *musb)
{
int ret;
u8 devctl;
musb_disable_interrupts(musb);
/*
* wait at least 320 cycles of 60MHz clock. That's 5.3us, we will give
* it some slack and wait for 10us.
*/
udelay(10);
ret = musb_platform_recover(musb);
if (ret) {
musb_enable_interrupts(musb);
return;
}
/* drop session bit */
devctl = musb_readb(musb->mregs, MUSB_DEVCTL);
devctl &= ~MUSB_DEVCTL_SESSION;
musb_writeb(musb->mregs, MUSB_DEVCTL, devctl);
/* tell usbcore about it */
musb_root_disconnect(musb);
/*
* When a babble condition occurs, the musb controller
* removes the session bit and the endpoint config is lost.
*/
if (musb->dyn_fifo)
ret = ep_config_from_table(musb);
else
ret = ep_config_from_hw(musb);
/* restart session */
if (ret == 0)
musb_start(musb);
}
/* --------------------------------------------------------------------------
* Init support
*/
static struct musb *allocate_instance(struct device *dev,
const struct musb_hdrc_config *config, void __iomem *mbase)
{
struct musb *musb;
struct musb_hw_ep *ep;
int epnum;
int ret;
musb = devm_kzalloc(dev, sizeof(*musb), GFP_KERNEL);
if (!musb)
return NULL;
INIT_LIST_HEAD(&musb->control);
INIT_LIST_HEAD(&musb->in_bulk);
INIT_LIST_HEAD(&musb->out_bulk);
usb: musb: Fix sleeping function called from invalid context for hdrc glue Commit 65b3f50ed6fa ("usb: musb: Add PM runtime support for MUSB DSPS glue layer") wrongly added a call for pm_runtime_get_sync to otg_timer that runs in softirq context. That causes a "BUG: sleeping function called from invalid context" every time when polling the cable status: [<c015ebb4>] (__might_sleep) from [<c0413d60>] (__pm_runtime_resume+0x9c/0xa0) [<c0413d60>] (__pm_runtime_resume) from [<c04d0bc4>] (otg_timer+0x3c/0x254) [<c04d0bc4>] (otg_timer) from [<c0191180>] (call_timer_fn+0xfc/0x41c) [<c0191180>] (call_timer_fn) from [<c01915c0>] (expire_timers+0x120/0x210) [<c01915c0>] (expire_timers) from [<c0191acc>] (run_timer_softirq+0xa4/0xdc) [<c0191acc>] (run_timer_softirq) from [<c010168c>] (__do_softirq+0x12c/0x594) I did not notice that as I did not have CONFIG_DEBUG_ATOMIC_SLEEP enabled. And looks like also musb_gadget_queue() suffers from the same problem. Let's fix the issue by using a list of delayed work then call it on resume. Note that we want to do this only when musb core and it's parent devices are awake, and we need to make sure the DSPS glue timer is stopped as noted by Johan Hovold <johan@kernel.org>. Note that we already are re-enabling the timer with mod_timer() in dsps_musb_enable(). Later on we may be able to remove other delayed work in the musb driver and just do it from pending_resume_work. But this should be done only for delayed work that does not have other timing requirements beyond just being run on resume. Fixes: 65b3f50ed6fa ("usb: musb: Add PM runtime support for MUSB DSPS glue layer") Reported-by: Johan Hovold <johan@kernel.org> Reviewed-by: Johan Hovold <johan@kernel.org> Tested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Signed-off-by: Tony Lindgren <tony@atomide.com> Signed-off-by: Bin Liu <b-liu@ti.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2016-11-16 19:21:23 +00:00
INIT_LIST_HEAD(&musb->pending_list);
musb->vbuserr_retry = VBUSERR_RETRY_COUNT;
musb->a_wait_bcon = OTG_TIME_A_WAIT_BCON;
musb->mregs = mbase;
musb->ctrl_base = mbase;
musb->nIrq = -ENODEV;
musb->config = config;
BUG_ON(musb->config->num_eps > MUSB_C_NUM_EPS);
for (epnum = 0, ep = musb->endpoints;
epnum < musb->config->num_eps;
epnum++, ep++) {
ep->musb = musb;
ep->epnum = epnum;
}
musb->controller = dev;
ret = musb_host_alloc(musb);
if (ret < 0)
goto err_free;
dev_set_drvdata(dev, musb);
return musb;
err_free:
return NULL;
}
static void musb_free(struct musb *musb)
{
/* this has multiple entry modes. it handles fault cleanup after
* probe(), where things may be partially set up, as well as rmmod
* cleanup after everything's been de-activated.
*/
if (musb->nIrq >= 0) {
if (musb->irq_wake)
disable_irq_wake(musb->nIrq);
free_irq(musb->nIrq, musb);
}
musb_host_free(musb);
}
usb: musb: Fix sleeping function called from invalid context for hdrc glue Commit 65b3f50ed6fa ("usb: musb: Add PM runtime support for MUSB DSPS glue layer") wrongly added a call for pm_runtime_get_sync to otg_timer that runs in softirq context. That causes a "BUG: sleeping function called from invalid context" every time when polling the cable status: [<c015ebb4>] (__might_sleep) from [<c0413d60>] (__pm_runtime_resume+0x9c/0xa0) [<c0413d60>] (__pm_runtime_resume) from [<c04d0bc4>] (otg_timer+0x3c/0x254) [<c04d0bc4>] (otg_timer) from [<c0191180>] (call_timer_fn+0xfc/0x41c) [<c0191180>] (call_timer_fn) from [<c01915c0>] (expire_timers+0x120/0x210) [<c01915c0>] (expire_timers) from [<c0191acc>] (run_timer_softirq+0xa4/0xdc) [<c0191acc>] (run_timer_softirq) from [<c010168c>] (__do_softirq+0x12c/0x594) I did not notice that as I did not have CONFIG_DEBUG_ATOMIC_SLEEP enabled. And looks like also musb_gadget_queue() suffers from the same problem. Let's fix the issue by using a list of delayed work then call it on resume. Note that we want to do this only when musb core and it's parent devices are awake, and we need to make sure the DSPS glue timer is stopped as noted by Johan Hovold <johan@kernel.org>. Note that we already are re-enabling the timer with mod_timer() in dsps_musb_enable(). Later on we may be able to remove other delayed work in the musb driver and just do it from pending_resume_work. But this should be done only for delayed work that does not have other timing requirements beyond just being run on resume. Fixes: 65b3f50ed6fa ("usb: musb: Add PM runtime support for MUSB DSPS glue layer") Reported-by: Johan Hovold <johan@kernel.org> Reviewed-by: Johan Hovold <johan@kernel.org> Tested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Signed-off-by: Tony Lindgren <tony@atomide.com> Signed-off-by: Bin Liu <b-liu@ti.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2016-11-16 19:21:23 +00:00
struct musb_pending_work {
int (*callback)(struct musb *musb, void *data);
void *data;
struct list_head node;
};
#ifdef CONFIG_PM
usb: musb: Fix sleeping function called from invalid context for hdrc glue Commit 65b3f50ed6fa ("usb: musb: Add PM runtime support for MUSB DSPS glue layer") wrongly added a call for pm_runtime_get_sync to otg_timer that runs in softirq context. That causes a "BUG: sleeping function called from invalid context" every time when polling the cable status: [<c015ebb4>] (__might_sleep) from [<c0413d60>] (__pm_runtime_resume+0x9c/0xa0) [<c0413d60>] (__pm_runtime_resume) from [<c04d0bc4>] (otg_timer+0x3c/0x254) [<c04d0bc4>] (otg_timer) from [<c0191180>] (call_timer_fn+0xfc/0x41c) [<c0191180>] (call_timer_fn) from [<c01915c0>] (expire_timers+0x120/0x210) [<c01915c0>] (expire_timers) from [<c0191acc>] (run_timer_softirq+0xa4/0xdc) [<c0191acc>] (run_timer_softirq) from [<c010168c>] (__do_softirq+0x12c/0x594) I did not notice that as I did not have CONFIG_DEBUG_ATOMIC_SLEEP enabled. And looks like also musb_gadget_queue() suffers from the same problem. Let's fix the issue by using a list of delayed work then call it on resume. Note that we want to do this only when musb core and it's parent devices are awake, and we need to make sure the DSPS glue timer is stopped as noted by Johan Hovold <johan@kernel.org>. Note that we already are re-enabling the timer with mod_timer() in dsps_musb_enable(). Later on we may be able to remove other delayed work in the musb driver and just do it from pending_resume_work. But this should be done only for delayed work that does not have other timing requirements beyond just being run on resume. Fixes: 65b3f50ed6fa ("usb: musb: Add PM runtime support for MUSB DSPS glue layer") Reported-by: Johan Hovold <johan@kernel.org> Reviewed-by: Johan Hovold <johan@kernel.org> Tested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Signed-off-by: Tony Lindgren <tony@atomide.com> Signed-off-by: Bin Liu <b-liu@ti.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2016-11-16 19:21:23 +00:00
/*
* Called from musb_runtime_resume(), musb_resume(), and
* musb_queue_resume_work(). Callers must take musb->lock.
*/
static int musb_run_resume_work(struct musb *musb)
{
struct musb_pending_work *w, *_w;
unsigned long flags;
int error = 0;
spin_lock_irqsave(&musb->list_lock, flags);
list_for_each_entry_safe(w, _w, &musb->pending_list, node) {
if (w->callback) {
error = w->callback(musb, w->data);
if (error < 0) {
dev_err(musb->controller,
"resume callback %p failed: %i\n",
w->callback, error);
}
}
list_del(&w->node);
devm_kfree(musb->controller, w);
}
spin_unlock_irqrestore(&musb->list_lock, flags);
return error;
}
#endif
usb: musb: Fix sleeping function called from invalid context for hdrc glue Commit 65b3f50ed6fa ("usb: musb: Add PM runtime support for MUSB DSPS glue layer") wrongly added a call for pm_runtime_get_sync to otg_timer that runs in softirq context. That causes a "BUG: sleeping function called from invalid context" every time when polling the cable status: [<c015ebb4>] (__might_sleep) from [<c0413d60>] (__pm_runtime_resume+0x9c/0xa0) [<c0413d60>] (__pm_runtime_resume) from [<c04d0bc4>] (otg_timer+0x3c/0x254) [<c04d0bc4>] (otg_timer) from [<c0191180>] (call_timer_fn+0xfc/0x41c) [<c0191180>] (call_timer_fn) from [<c01915c0>] (expire_timers+0x120/0x210) [<c01915c0>] (expire_timers) from [<c0191acc>] (run_timer_softirq+0xa4/0xdc) [<c0191acc>] (run_timer_softirq) from [<c010168c>] (__do_softirq+0x12c/0x594) I did not notice that as I did not have CONFIG_DEBUG_ATOMIC_SLEEP enabled. And looks like also musb_gadget_queue() suffers from the same problem. Let's fix the issue by using a list of delayed work then call it on resume. Note that we want to do this only when musb core and it's parent devices are awake, and we need to make sure the DSPS glue timer is stopped as noted by Johan Hovold <johan@kernel.org>. Note that we already are re-enabling the timer with mod_timer() in dsps_musb_enable(). Later on we may be able to remove other delayed work in the musb driver and just do it from pending_resume_work. But this should be done only for delayed work that does not have other timing requirements beyond just being run on resume. Fixes: 65b3f50ed6fa ("usb: musb: Add PM runtime support for MUSB DSPS glue layer") Reported-by: Johan Hovold <johan@kernel.org> Reviewed-by: Johan Hovold <johan@kernel.org> Tested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Signed-off-by: Tony Lindgren <tony@atomide.com> Signed-off-by: Bin Liu <b-liu@ti.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2016-11-16 19:21:23 +00:00
/*
* Called to run work if device is active or else queue the work to happen
* on resume. Caller must take musb->lock and must hold an RPM reference.
*
* Note that we cowardly refuse queuing work after musb PM runtime
* resume is done calling musb_run_resume_work() and return -EINPROGRESS
* instead.
*/
int musb_queue_resume_work(struct musb *musb,
int (*callback)(struct musb *musb, void *data),
void *data)
{
struct musb_pending_work *w;
unsigned long flags;
int error;
if (WARN_ON(!callback))
return -EINVAL;
if (pm_runtime_active(musb->controller))
return callback(musb, data);
w = devm_kzalloc(musb->controller, sizeof(*w), GFP_ATOMIC);
if (!w)
return -ENOMEM;
w->callback = callback;
w->data = data;
spin_lock_irqsave(&musb->list_lock, flags);
if (musb->is_runtime_suspended) {
list_add_tail(&w->node, &musb->pending_list);
error = 0;
} else {
dev_err(musb->controller, "could not add resume work %p\n",
callback);
devm_kfree(musb->controller, w);
error = -EINPROGRESS;
}
spin_unlock_irqrestore(&musb->list_lock, flags);
return error;
}
EXPORT_SYMBOL_GPL(musb_queue_resume_work);
static void musb_deassert_reset(struct work_struct *work)
{
struct musb *musb;
unsigned long flags;
musb = container_of(work, struct musb, deassert_reset_work.work);
spin_lock_irqsave(&musb->lock, flags);
if (musb->port1_status & USB_PORT_STAT_RESET)
musb_port_reset(musb, false);
spin_unlock_irqrestore(&musb->lock, flags);
}
/*
* Perform generic per-controller initialization.
*
* @dev: the controller (already clocked, etc)
* @nIrq: IRQ number
* @ctrl: virtual address of controller registers,
* not yet corrected for platform-specific offsets
*/
static int
musb_init_controller(struct device *dev, int nIrq, void __iomem *ctrl)
{
int status;
struct musb *musb;
struct musb_hdrc_platform_data *plat = dev_get_platdata(dev);
/* The driver might handle more features than the board; OK.
* Fail when the board needs a feature that's not enabled.
*/
if (!plat) {
dev_err(dev, "no platform_data?\n");
status = -ENODEV;
goto fail0;
}
/* allocate */
musb = allocate_instance(dev, plat->config, ctrl);
if (!musb) {
status = -ENOMEM;
goto fail0;
}
spin_lock_init(&musb->lock);
usb: musb: Fix sleeping function called from invalid context for hdrc glue Commit 65b3f50ed6fa ("usb: musb: Add PM runtime support for MUSB DSPS glue layer") wrongly added a call for pm_runtime_get_sync to otg_timer that runs in softirq context. That causes a "BUG: sleeping function called from invalid context" every time when polling the cable status: [<c015ebb4>] (__might_sleep) from [<c0413d60>] (__pm_runtime_resume+0x9c/0xa0) [<c0413d60>] (__pm_runtime_resume) from [<c04d0bc4>] (otg_timer+0x3c/0x254) [<c04d0bc4>] (otg_timer) from [<c0191180>] (call_timer_fn+0xfc/0x41c) [<c0191180>] (call_timer_fn) from [<c01915c0>] (expire_timers+0x120/0x210) [<c01915c0>] (expire_timers) from [<c0191acc>] (run_timer_softirq+0xa4/0xdc) [<c0191acc>] (run_timer_softirq) from [<c010168c>] (__do_softirq+0x12c/0x594) I did not notice that as I did not have CONFIG_DEBUG_ATOMIC_SLEEP enabled. And looks like also musb_gadget_queue() suffers from the same problem. Let's fix the issue by using a list of delayed work then call it on resume. Note that we want to do this only when musb core and it's parent devices are awake, and we need to make sure the DSPS glue timer is stopped as noted by Johan Hovold <johan@kernel.org>. Note that we already are re-enabling the timer with mod_timer() in dsps_musb_enable(). Later on we may be able to remove other delayed work in the musb driver and just do it from pending_resume_work. But this should be done only for delayed work that does not have other timing requirements beyond just being run on resume. Fixes: 65b3f50ed6fa ("usb: musb: Add PM runtime support for MUSB DSPS glue layer") Reported-by: Johan Hovold <johan@kernel.org> Reviewed-by: Johan Hovold <johan@kernel.org> Tested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Signed-off-by: Tony Lindgren <tony@atomide.com> Signed-off-by: Bin Liu <b-liu@ti.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2016-11-16 19:21:23 +00:00
spin_lock_init(&musb->list_lock);
musb->board_set_power = plat->set_power;
musb->min_power = plat->min_power;
musb->ops = plat->platform_ops;
musb->port_mode = plat->mode;
/*
* Initialize the default IO functions. At least omap2430 needs
* these early. We initialize the platform specific IO functions
* later on.
*/
musb_readb = musb_default_readb;
musb_writeb = musb_default_writeb;
musb_readw = musb_default_readw;
musb_writew = musb_default_writew;
/* The musb_platform_init() call:
* - adjusts musb->mregs
* - sets the musb->isr
* - may initialize an integrated transceiver
* - initializes musb->xceiv, usually by otg_get_phy()
* - stops powering VBUS
*
* There are various transceiver configurations.
* DaVinci, TUSB60x0, and others integrate them. OMAP3 uses
* external/discrete ones in various flavors (twl4030 family,
* isp1504, non-OTG, etc) mostly hooking up through ULPI.
*/
status = musb_platform_init(musb);
if (status < 0)
goto fail1;
if (!musb->isr) {
status = -ENODEV;
goto fail2;
}
/* Most devices use indexed offset or flat offset */
if (musb->ops->quirks & MUSB_INDEXED_EP) {
musb->io.ep_offset = musb_indexed_ep_offset;
musb->io.ep_select = musb_indexed_ep_select;
} else {
musb->io.ep_offset = musb_flat_ep_offset;
musb->io.ep_select = musb_flat_ep_select;
}
if (musb->ops->quirks & MUSB_G_NO_SKB_RESERVE)
musb->g.quirk_avoids_skb_reserve = 1;
/* At least tusb6010 has its own offsets */
if (musb->ops->ep_offset)
musb->io.ep_offset = musb->ops->ep_offset;
if (musb->ops->ep_select)
musb->io.ep_select = musb->ops->ep_select;
if (musb->ops->fifo_mode)
fifo_mode = musb->ops->fifo_mode;
else
fifo_mode = 4;
if (musb->ops->fifo_offset)
musb->io.fifo_offset = musb->ops->fifo_offset;
else
musb->io.fifo_offset = musb_default_fifo_offset;
if (musb->ops->busctl_offset)
musb->io.busctl_offset = musb->ops->busctl_offset;
else
musb->io.busctl_offset = musb_default_busctl_offset;
if (musb->ops->readb)
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) {
dev_err(dev, "DMA controller not set\n");
status = -ENODEV;
goto fail2;
}
musb_dma_controller_create = musb->ops->dma_init;
musb_dma_controller_destroy = musb->ops->dma_exit;
#endif
if (musb->ops->read_fifo)
musb->io.read_fifo = musb->ops->read_fifo;
else
musb->io.read_fifo = musb_default_read_fifo;
if (musb->ops->write_fifo)
musb->io.write_fifo = musb->ops->write_fifo;
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;
musb->xceiv->io_ops = &musb_ulpi_access;
}
if (musb->ops->phy_callback)
musb_phy_callback = musb->ops->phy_callback;
/*
* We need musb_read/write functions initialized for PM.
* Note that at least 2430 glue needs autosuspend delay
* somewhere above 300 ms for the hardware to idle properly
* after disconnecting the cable in host mode. Let's use
* 500 ms for some margin.
*/
pm_runtime_use_autosuspend(musb->controller);
pm_runtime_set_autosuspend_delay(musb->controller, 500);
pm_runtime_enable(musb->controller);
pm_runtime_get_sync(musb->controller);
status = usb_phy_init(musb->xceiv);
if (status < 0)
goto err_usb_phy_init;
if (use_dma && dev->dma_mask) {
musb->dma_controller =
musb_dma_controller_create(musb, musb->mregs);
if (IS_ERR(musb->dma_controller)) {
status = PTR_ERR(musb->dma_controller);
goto fail2_5;
}
}
/* be sure interrupts are disabled before connecting ISR */
musb_platform_disable(musb);
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);
usb: musb: only cancel work if it is initialized Since commit c5340bd14 ("usb: musb: cancel work on removal") the workqueue is cancelled but then if we bail out before the workqueue is setup we get this: |INFO: trying to register non-static key. |the code is fine but needs lockdep annotation. |turning off the locking correctness validator. |CPU: 0 PID: 708 Comm: modprobe Not tainted 3.12.0+ #435 |[<c00867bc>] (lock_acquire+0xf0/0x108) from [<c00529d0>] (flush_work+0x38/0x2ec) |[<c00529d0>] (flush_work+0x38/0x2ec) from [<c0052d24>] (__cancel_work_timer+0xa0/0x134) |[<c0052d24>] (__cancel_work_timer+0xa0/0x134) from [<bf0e4ae4>] (musb_free+0x40/0x60 [musb_hdrc]) |[<bf0e4ae4>] (musb_free+0x40/0x60 [musb_hdrc]) from [<bf0e5364>] (musb_probe+0x678/0xb78 [musb_hdrc]) |[<bf0e5364>] (musb_probe+0x678/0xb78 [musb_hdrc]) from [<c0294bf0>] (platform_drv_probe+0x1c/0x24) |[<c0294bf0>] (platform_drv_probe+0x1c/0x24) from [<c0293970>] (driver_probe_device+0x90/0x224) |[<c0293970>] (driver_probe_device+0x90/0x224) from [<c0291ef0>] (bus_for_each_drv+0x60/0x8c) |[<c0291ef0>] (bus_for_each_drv+0x60/0x8c) from [<c02938bc>] (device_attach+0x80/0xa4) |[<c02938bc>] (device_attach+0x80/0xa4) from [<c0292b24>] (bus_probe_device+0x88/0xac) |[<c0292b24>] (bus_probe_device+0x88/0xac) from [<c0291490>] (device_add+0x388/0x6c8) |[<c0291490>] (device_add+0x388/0x6c8) from [<c02952a0>] (platform_device_add+0x188/0x22c) |[<c02952a0>] (platform_device_add+0x188/0x22c) from [<bf11ea30>] (dsps_probe+0x294/0x394 [musb_dsps]) |[<bf11ea30>] (dsps_probe+0x294/0x394 [musb_dsps]) from [<c0294bf0>] (platform_drv_probe+0x1c/0x24) |platform musb-hdrc.1.auto: Driver musb-hdrc requests probe deferral |musb-hdrc musb-hdrc.1.auto: musb_init_controller failed with status -517 This patch moves the init part to earlier part so it can be cleaned as part of the fail3 label because now it is surrounded by the fail4 label. Step two is to remove it from musb_free() and add it to the two cleanup paths (error path and device removal) separately. Cc: stable@vger.kernel.org Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de> Signed-off-by: Felipe Balbi <balbi@ti.com>
2013-11-06 08:25:27 +00:00
/* 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);
INIT_DELAYED_WORK(&musb->finish_resume_work, musb_host_finish_resume);
usb: musb: only cancel work if it is initialized Since commit c5340bd14 ("usb: musb: cancel work on removal") the workqueue is cancelled but then if we bail out before the workqueue is setup we get this: |INFO: trying to register non-static key. |the code is fine but needs lockdep annotation. |turning off the locking correctness validator. |CPU: 0 PID: 708 Comm: modprobe Not tainted 3.12.0+ #435 |[<c00867bc>] (lock_acquire+0xf0/0x108) from [<c00529d0>] (flush_work+0x38/0x2ec) |[<c00529d0>] (flush_work+0x38/0x2ec) from [<c0052d24>] (__cancel_work_timer+0xa0/0x134) |[<c0052d24>] (__cancel_work_timer+0xa0/0x134) from [<bf0e4ae4>] (musb_free+0x40/0x60 [musb_hdrc]) |[<bf0e4ae4>] (musb_free+0x40/0x60 [musb_hdrc]) from [<bf0e5364>] (musb_probe+0x678/0xb78 [musb_hdrc]) |[<bf0e5364>] (musb_probe+0x678/0xb78 [musb_hdrc]) from [<c0294bf0>] (platform_drv_probe+0x1c/0x24) |[<c0294bf0>] (platform_drv_probe+0x1c/0x24) from [<c0293970>] (driver_probe_device+0x90/0x224) |[<c0293970>] (driver_probe_device+0x90/0x224) from [<c0291ef0>] (bus_for_each_drv+0x60/0x8c) |[<c0291ef0>] (bus_for_each_drv+0x60/0x8c) from [<c02938bc>] (device_attach+0x80/0xa4) |[<c02938bc>] (device_attach+0x80/0xa4) from [<c0292b24>] (bus_probe_device+0x88/0xac) |[<c0292b24>] (bus_probe_device+0x88/0xac) from [<c0291490>] (device_add+0x388/0x6c8) |[<c0291490>] (device_add+0x388/0x6c8) from [<c02952a0>] (platform_device_add+0x188/0x22c) |[<c02952a0>] (platform_device_add+0x188/0x22c) from [<bf11ea30>] (dsps_probe+0x294/0x394 [musb_dsps]) |[<bf11ea30>] (dsps_probe+0x294/0x394 [musb_dsps]) from [<c0294bf0>] (platform_drv_probe+0x1c/0x24) |platform musb-hdrc.1.auto: Driver musb-hdrc requests probe deferral |musb-hdrc musb-hdrc.1.auto: musb_init_controller failed with status -517 This patch moves the init part to earlier part so it can be cleaned as part of the fail3 label because now it is surrounded by the fail4 label. Step two is to remove it from musb_free() and add it to the two cleanup paths (error path and device removal) separately. Cc: stable@vger.kernel.org Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de> Signed-off-by: Felipe Balbi <balbi@ti.com>
2013-11-06 08:25:27 +00:00
/* setup musb parts of the core (especially endpoints) */
status = musb_core_init(plat->config->multipoint
? MUSB_CONTROLLER_MHDRC
: MUSB_CONTROLLER_HDRC, musb);
if (status < 0)
goto fail3;
timer_setup(&musb->otg_timer, musb_otg_timer_func, 0);
/* attach to the IRQ */
if (request_irq(nIrq, musb->isr, IRQF_SHARED, dev_name(dev), musb)) {
dev_err(dev, "request_irq %d failed!\n", nIrq);
status = -ENODEV;
goto fail3;
}
musb->nIrq = nIrq;
/* FIXME this handles wakeup irqs wrong */
if (enable_irq_wake(nIrq) == 0) {
musb->irq_wake = 1;
device_init_wakeup(dev, 1);
} else {
musb->irq_wake = 0;
}
/* program PHY to use external vBus if required */
if (plat->extvbus) {
u8 busctl = musb_readb(musb->mregs, MUSB_ULPI_BUSCONTROL);
busctl |= MUSB_ULPI_USE_EXTVBUS;
musb_writeb(musb->mregs, MUSB_ULPI_BUSCONTROL, busctl);
}
MUSB_DEV_MODE(musb);
musb->xceiv->otg->state = OTG_STATE_B_IDLE;
switch (musb->port_mode) {
case MUSB_HOST:
status = musb_host_setup(musb, plat->power);
if (status < 0)
goto fail3;
status = musb_platform_set_mode(musb, MUSB_HOST);
break;
case MUSB_PERIPHERAL:
status = musb_gadget_setup(musb);
if (status < 0)
goto fail3;
status = musb_platform_set_mode(musb, MUSB_PERIPHERAL);
break;
case MUSB_OTG:
status = musb_host_setup(musb, plat->power);
if (status < 0)
goto fail3;
status = musb_gadget_setup(musb);
if (status) {
musb_host_cleanup(musb);
goto fail3;
}
status = musb_platform_set_mode(musb, MUSB_OTG);
break;
default:
dev_err(dev, "unsupported port mode %d\n", musb->port_mode);
break;
}
if (status < 0)
goto fail3;
musb_init_debugfs(musb);
musb->is_initialized = 1;
pm_runtime_mark_last_busy(musb->controller);
pm_runtime_put_autosuspend(musb->controller);
return 0;
fail3:
cancel_delayed_work_sync(&musb->irq_work);
cancel_delayed_work_sync(&musb->finish_resume_work);
cancel_delayed_work_sync(&musb->deassert_reset_work);
if (musb->dma_controller)
musb_dma_controller_destroy(musb->dma_controller);
fail2_5:
usb_phy_shutdown(musb->xceiv);
err_usb_phy_init:
pm_runtime_dont_use_autosuspend(musb->controller);
pm_runtime_put_sync(musb->controller);
pm_runtime_disable(musb->controller);
fail2:
if (musb->irq_wake)
device_init_wakeup(dev, 0);
musb_platform_exit(musb);
fail1:
if (status != -EPROBE_DEFER)
dev_err(musb->controller,
"%s failed with status %d\n", __func__, status);
musb_free(musb);
fail0:
return status;
}
/*-------------------------------------------------------------------------*/
/* all implementations (PCI bridge to FPGA, VLYNQ, etc) should just
* bridge to a platform device; this driver then suffices.
*/
static int musb_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int irq = platform_get_irq_byname(pdev, "mc");
void __iomem *base;
if (irq <= 0)
return -ENODEV;
base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(base))
return PTR_ERR(base);
return musb_init_controller(dev, irq, base);
}
static int musb_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct musb *musb = dev_to_musb(dev);
unsigned long flags;
/* this gets called on rmmod.
* - Host mode: host may still be active
* - Peripheral mode: peripheral is deactivated (or never-activated)
* - OTG mode: both roles are deactivated (or never-activated)
*/
musb_exit_debugfs(musb);
cancel_delayed_work_sync(&musb->irq_work);
cancel_delayed_work_sync(&musb->finish_resume_work);
cancel_delayed_work_sync(&musb->deassert_reset_work);
pm_runtime_get_sync(musb->controller);
musb_host_cleanup(musb);
musb_gadget_cleanup(musb);
musb_platform_disable(musb);
usb: musb: fix possible spinlock deadlock The DSPS glue calls del_timer_sync() in its musb_platform_disable() implementation, which requires the caller to not hold a lock. But musb_remove() calls musb_platform_disable() will musb->lock held. This could causes spinlock deadlock. So change musb_remove() to call musb_platform_disable() without holds musb->lock. This doesn't impact the musb_platform_disable implementation in other glue drivers. root@am335x-evm:~# modprobe -r musb-dsps [ 126.134879] musb-hdrc musb-hdrc.1: remove, state 1 [ 126.140465] usb usb2: USB disconnect, device number 1 [ 126.146178] usb 2-1: USB disconnect, device number 2 [ 126.416985] musb-hdrc musb-hdrc.1: USB bus 2 deregistered [ 126.423943] [ 126.425525] ====================================================== [ 126.431997] [ INFO: possible circular locking dependency detected ] [ 126.438564] 4.11.0-rc1-00003-g1557f13bca04-dirty #77 Not tainted [ 126.444852] ------------------------------------------------------- [ 126.451414] modprobe/778 is trying to acquire lock: [ 126.456523] (((&glue->timer))){+.-...}, at: [<c01b8788>] del_timer_sync+0x0/0xd0 [ 126.464403] [ 126.464403] but task is already holding lock: [ 126.470511] (&(&musb->lock)->rlock){-.-...}, at: [<bf30b7f8>] musb_remove+0x50/0x1 30 [musb_hdrc] [ 126.479965] [ 126.479965] which lock already depends on the new lock. [ 126.479965] [ 126.488531] [ 126.488531] the existing dependency chain (in reverse order) is: [ 126.496368] [ 126.496368] -> #1 (&(&musb->lock)->rlock){-.-...}: [ 126.502968] otg_timer+0x80/0xec [musb_dsps] [ 126.507990] call_timer_fn+0xb4/0x390 [ 126.512372] expire_timers+0xf0/0x1fc [ 126.516754] run_timer_softirq+0x80/0x178 [ 126.521511] __do_softirq+0xc4/0x554 [ 126.525802] irq_exit+0xe8/0x158 [ 126.529735] __handle_domain_irq+0x58/0xb8 [ 126.534583] __irq_usr+0x54/0x80 [ 126.538507] [ 126.538507] -> #0 (((&glue->timer))){+.-...}: [ 126.544636] del_timer_sync+0x40/0xd0 [ 126.549066] musb_remove+0x6c/0x130 [musb_hdrc] [ 126.554370] platform_drv_remove+0x24/0x3c [ 126.559206] device_release_driver_internal+0x14c/0x1e0 [ 126.565225] bus_remove_device+0xd8/0x108 [ 126.569970] device_del+0x1e4/0x308 [ 126.574170] platform_device_del+0x24/0x8c [ 126.579006] platform_device_unregister+0xc/0x20 [ 126.584394] dsps_remove+0x14/0x30 [musb_dsps] [ 126.589595] platform_drv_remove+0x24/0x3c [ 126.594432] device_release_driver_internal+0x14c/0x1e0 [ 126.600450] driver_detach+0x38/0x6c [ 126.604740] bus_remove_driver+0x4c/0xa0 [ 126.609407] SyS_delete_module+0x11c/0x1e4 [ 126.614252] __sys_trace_return+0x0/0x10 Fixes: ea2f35c01d5ea ("usb: musb: Fix sleeping function called from invalid context for hdrc glue") Cc: <stable@vger.kernel.org> #4.9+ Acked-by: Tony Lindgren <tony@atomide.com> Signed-off-by: Bin Liu <b-liu@ti.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2017-03-10 20:43:37 +00:00
spin_lock_irqsave(&musb->lock, flags);
musb_disable_interrupts(musb);
musb_writeb(musb->mregs, MUSB_DEVCTL, 0);
spin_unlock_irqrestore(&musb->lock, flags);
musb_platform_exit(musb);
pm_runtime_dont_use_autosuspend(musb->controller);
pm_runtime_put_sync(musb->controller);
pm_runtime_disable(musb->controller);
musb_phy_callback = NULL;
if (musb->dma_controller)
musb_dma_controller_destroy(musb->dma_controller);
usb_phy_shutdown(musb->xceiv);
musb_free(musb);
device_init_wakeup(dev, 0);
return 0;
}
#ifdef CONFIG_PM
static void musb_save_context(struct musb *musb)
{
int i;
void __iomem *musb_base = musb->mregs;
void __iomem *epio;
musb->context.frame = musb_readw(musb_base, MUSB_FRAME);
musb->context.testmode = musb_readb(musb_base, MUSB_TESTMODE);
musb->context.busctl = musb_readb(musb_base, MUSB_ULPI_BUSCONTROL);
musb->context.power = musb_readb(musb_base, MUSB_POWER);
musb->context.intrusbe = musb_readb(musb_base, MUSB_INTRUSBE);
musb->context.index = musb_readb(musb_base, MUSB_INDEX);
musb->context.devctl = musb_readb(musb_base, MUSB_DEVCTL);
for (i = 0; i < musb->config->num_eps; ++i) {
struct musb_hw_ep *hw_ep;
hw_ep = &musb->endpoints[i];
if (!hw_ep)
continue;
epio = hw_ep->regs;
if (!epio)
continue;
musb_writeb(musb_base, MUSB_INDEX, i);
musb->context.index_regs[i].txmaxp =
musb_readw(epio, MUSB_TXMAXP);
musb->context.index_regs[i].txcsr =
musb_readw(epio, MUSB_TXCSR);
musb->context.index_regs[i].rxmaxp =
musb_readw(epio, MUSB_RXMAXP);
musb->context.index_regs[i].rxcsr =
musb_readw(epio, MUSB_RXCSR);
if (musb->dyn_fifo) {
musb->context.index_regs[i].txfifoadd =
musb_readw(musb_base, MUSB_TXFIFOADD);
musb->context.index_regs[i].rxfifoadd =
musb_readw(musb_base, MUSB_RXFIFOADD);
musb->context.index_regs[i].txfifosz =
musb_readb(musb_base, MUSB_TXFIFOSZ);
musb->context.index_regs[i].rxfifosz =
musb_readb(musb_base, MUSB_RXFIFOSZ);
}
musb->context.index_regs[i].txtype =
musb_readb(epio, MUSB_TXTYPE);
musb->context.index_regs[i].txinterval =
musb_readb(epio, MUSB_TXINTERVAL);
musb->context.index_regs[i].rxtype =
musb_readb(epio, MUSB_RXTYPE);
musb->context.index_regs[i].rxinterval =
musb_readb(epio, MUSB_RXINTERVAL);
musb->context.index_regs[i].txfunaddr =
musb_read_txfunaddr(musb, i);
musb->context.index_regs[i].txhubaddr =
musb_read_txhubaddr(musb, i);
musb->context.index_regs[i].txhubport =
musb_read_txhubport(musb, i);
musb->context.index_regs[i].rxfunaddr =
musb_read_rxfunaddr(musb, i);
musb->context.index_regs[i].rxhubaddr =
musb_read_rxhubaddr(musb, i);
musb->context.index_regs[i].rxhubport =
musb_read_rxhubport(musb, i);
}
}
static void musb_restore_context(struct musb *musb)
{
int i;
void __iomem *musb_base = musb->mregs;
void __iomem *epio;
u8 power;
musb_writew(musb_base, MUSB_FRAME, musb->context.frame);
musb_writeb(musb_base, MUSB_TESTMODE, musb->context.testmode);
musb_writeb(musb_base, MUSB_ULPI_BUSCONTROL, musb->context.busctl);
/* Don't affect SUSPENDM/RESUME bits in POWER reg */
power = musb_readb(musb_base, MUSB_POWER);
power &= MUSB_POWER_SUSPENDM | MUSB_POWER_RESUME;
musb->context.power &= ~(MUSB_POWER_SUSPENDM | MUSB_POWER_RESUME);
power |= musb->context.power;
musb_writeb(musb_base, MUSB_POWER, power);
musb_writew(musb_base, MUSB_INTRTXE, musb->intrtxe);
musb_writew(musb_base, MUSB_INTRRXE, musb->intrrxe);
musb_writeb(musb_base, MUSB_INTRUSBE, musb->context.intrusbe);
if (musb->context.devctl & MUSB_DEVCTL_SESSION)
musb_writeb(musb_base, MUSB_DEVCTL, musb->context.devctl);
for (i = 0; i < musb->config->num_eps; ++i) {
struct musb_hw_ep *hw_ep;
hw_ep = &musb->endpoints[i];
if (!hw_ep)
continue;
epio = hw_ep->regs;
if (!epio)
continue;
musb_writeb(musb_base, MUSB_INDEX, i);
musb_writew(epio, MUSB_TXMAXP,
musb->context.index_regs[i].txmaxp);
musb_writew(epio, MUSB_TXCSR,
musb->context.index_regs[i].txcsr);
musb_writew(epio, MUSB_RXMAXP,
musb->context.index_regs[i].rxmaxp);
musb_writew(epio, MUSB_RXCSR,
musb->context.index_regs[i].rxcsr);
if (musb->dyn_fifo) {
musb_writeb(musb_base, MUSB_TXFIFOSZ,
musb->context.index_regs[i].txfifosz);
musb_writeb(musb_base, MUSB_RXFIFOSZ,
musb->context.index_regs[i].rxfifosz);
musb_writew(musb_base, MUSB_TXFIFOADD,
musb->context.index_regs[i].txfifoadd);
musb_writew(musb_base, MUSB_RXFIFOADD,
musb->context.index_regs[i].rxfifoadd);
}
musb_writeb(epio, MUSB_TXTYPE,
musb->context.index_regs[i].txtype);
musb_writeb(epio, MUSB_TXINTERVAL,
musb->context.index_regs[i].txinterval);
musb_writeb(epio, MUSB_RXTYPE,
musb->context.index_regs[i].rxtype);
musb_writeb(epio, MUSB_RXINTERVAL,
musb->context.index_regs[i].rxinterval);
musb_write_txfunaddr(musb, i,
musb->context.index_regs[i].txfunaddr);
musb_write_txhubaddr(musb, i,
musb->context.index_regs[i].txhubaddr);
musb_write_txhubport(musb, i,
musb->context.index_regs[i].txhubport);
musb_write_rxfunaddr(musb, i,
musb->context.index_regs[i].rxfunaddr);
musb_write_rxhubaddr(musb, i,
musb->context.index_regs[i].rxhubaddr);
musb_write_rxhubport(musb, i,
musb->context.index_regs[i].rxhubport);
}
musb_writeb(musb_base, MUSB_INDEX, musb->context.index);
}
static int musb_suspend(struct device *dev)
{
struct musb *musb = dev_to_musb(dev);
unsigned long flags;
USB: musb: fix external abort on suspend Make sure that the controller is runtime resumed when system suspending to avoid an external abort when accessing the interrupt registers: Unhandled fault: external abort on non-linefetch (0x1008) at 0xd025840a ... [<c05481a4>] (musb_default_readb) from [<c0545abc>] (musb_disable_interrupts+0x84/0xa8) [<c0545abc>] (musb_disable_interrupts) from [<c0546b08>] (musb_suspend+0x38/0xb8) [<c0546b08>] (musb_suspend) from [<c04a57f8>] (platform_pm_suspend+0x3c/0x64) This is easily reproduced on a BBB by enabling the peripheral port only (as the host port may enable the shared clock) and keeping it disconnected so that the controller is runtime suspended. (Well, you would also need to the not-yet-merged am33xx-suspend patches by Dave Gerlach to be able to suspend the BBB.) This is a regression that was introduced by commit 1c4d0b4e1806 ("usb: musb: Remove pm_runtime_set_irq_safe") which allowed the parent glue device to runtime suspend and thereby exposed a couple of older issues: Register accesses without explicitly making sure the controller is runtime resumed during suspend was first introduced by commit c338412b5ded ("usb: musb: unconditionally save and restore the context on suspend") in 3.14. Commit a1fc1920aaaa ("usb: musb: core: make sure musb is in RPM_ACTIVE on resume") later started setting the RPM status to active during resume, and this was also implicitly relying on the parent always being active. Since commit 71723f95463d ("PM / runtime: print error when activating a child to unactive parent") this now also results in the following warning: musb-hdrc musb-hdrc.0: runtime PM trying to activate child device musb-hdrc.0 but parent (47401400.usb) is not active This patch has been verified on 4.13-rc2, 4.12 and 4.9 using a BBB (the dsps glue would always be active also in 4.8). Fixes: c338412b5ded ("usb: musb: unconditionally save and restore the context on suspend") Fixes: a1fc1920aaaa ("usb: musb: core: make sure musb is in RPM_ACTIVE on resume") Fixes: 1c4d0b4e1806 ("usb: musb: Remove pm_runtime_set_irq_safe") Cc: stable <stable@vger.kernel.org> # 4.8+ Cc: Alan Stern <stern@rowland.harvard.edu> Cc: Daniel Mack <zonque@gmail.com> Cc: Dave Gerlach <d-gerlach@ti.com> Cc: Rafael J. Wysocki <rjw@rjwysocki.net> Cc: Sebastian Andrzej Siewior <bigeasy@linutronix.de> Cc: Tony Lindgren <tony@atomide.com> Signed-off-by: Johan Hovold <johan@kernel.org> Signed-off-by: Bin Liu <b-liu@ti.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2017-08-24 16:38:36 +00:00
int ret;
ret = pm_runtime_get_sync(dev);
if (ret < 0) {
pm_runtime_put_noidle(dev);
return ret;
}
musb_platform_disable(musb);
musb_disable_interrupts(musb);
USB: musb: fix late external abort on suspend The musb delayed irq work was never flushed on suspend, something which since 4.9 can lead to an external abort if the work is scheduled after the grandparent's clock has been disabled: PM: Suspending system (mem) PM: suspend of devices complete after 125.224 msecs PM: suspend devices took 0.132 seconds PM: late suspend of devices complete after 7.423 msecs PM: noirq suspend of devices complete after 7.083 msecs suspend debug: Waiting for 5 second(s). Unhandled fault: external abort on non-linefetch (0x1008) at 0xd0262c60 ... [<c054880c>] (musb_default_readb) from [<c0547b5c>] (musb_irq_work+0x48/0x220) [<c0547b5c>] (musb_irq_work) from [<c014f8a4>] (process_one_work+0x1f4/0x758) [<c014f8a4>] (process_one_work) from [<c014fe5c>] (worker_thread+0x54/0x514) [<c014fe5c>] (worker_thread) from [<c015704c>] (kthread+0x128/0x158) [<c015704c>] (kthread) from [<c0109330>] (ret_from_fork+0x14/0x24) Commit 2bff3916fda9 ("usb: musb: Fix PM for hub disconnect") started scheduling musb_irq_work with a delay of up to a second and with retries thereby making this easy to trigger, for example, by suspending shortly after a disconnect. Note that we set a flag to prevent the irq work from rescheduling itself during suspend and instead process a disconnect immediately. This takes care of the case where we are disconnected shortly before suspending. However, when in host mode, a disconnect while suspended will still go unnoticed and thus prevent the controller from runtime suspending upon resume as the session bit is always set. This will need to be addressed separately. Fixes: 550a7375fe72 ("USB: Add MUSB and TUSB support") Fixes: 467d5c980709 ("usb: musb: Implement session bit based runtime PM for musb-core") Fixes: 2bff3916fda9 ("usb: musb: Fix PM for hub disconnect") Cc: stable <stable@vger.kernel.org> # 4.9 Cc: Felipe Balbi <felipe.balbi@linux.intel.com> Cc: Tony Lindgren <tony@atomide.com> Signed-off-by: Johan Hovold <johan@kernel.org> Tested-by: Tony Lindgren <tony@atomide.com> Signed-off-by: Bin Liu <b-liu@ti.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2017-10-10 03:46:08 +00:00
musb->flush_irq_work = true;
while (flush_delayed_work(&musb->irq_work))
;
musb->flush_irq_work = false;
if (!(musb->ops->quirks & MUSB_PRESERVE_SESSION))
musb_writeb(musb->mregs, MUSB_DEVCTL, 0);
USB: musb: fix late external abort on suspend The musb delayed irq work was never flushed on suspend, something which since 4.9 can lead to an external abort if the work is scheduled after the grandparent's clock has been disabled: PM: Suspending system (mem) PM: suspend of devices complete after 125.224 msecs PM: suspend devices took 0.132 seconds PM: late suspend of devices complete after 7.423 msecs PM: noirq suspend of devices complete after 7.083 msecs suspend debug: Waiting for 5 second(s). Unhandled fault: external abort on non-linefetch (0x1008) at 0xd0262c60 ... [<c054880c>] (musb_default_readb) from [<c0547b5c>] (musb_irq_work+0x48/0x220) [<c0547b5c>] (musb_irq_work) from [<c014f8a4>] (process_one_work+0x1f4/0x758) [<c014f8a4>] (process_one_work) from [<c014fe5c>] (worker_thread+0x54/0x514) [<c014fe5c>] (worker_thread) from [<c015704c>] (kthread+0x128/0x158) [<c015704c>] (kthread) from [<c0109330>] (ret_from_fork+0x14/0x24) Commit 2bff3916fda9 ("usb: musb: Fix PM for hub disconnect") started scheduling musb_irq_work with a delay of up to a second and with retries thereby making this easy to trigger, for example, by suspending shortly after a disconnect. Note that we set a flag to prevent the irq work from rescheduling itself during suspend and instead process a disconnect immediately. This takes care of the case where we are disconnected shortly before suspending. However, when in host mode, a disconnect while suspended will still go unnoticed and thus prevent the controller from runtime suspending upon resume as the session bit is always set. This will need to be addressed separately. Fixes: 550a7375fe72 ("USB: Add MUSB and TUSB support") Fixes: 467d5c980709 ("usb: musb: Implement session bit based runtime PM for musb-core") Fixes: 2bff3916fda9 ("usb: musb: Fix PM for hub disconnect") Cc: stable <stable@vger.kernel.org> # 4.9 Cc: Felipe Balbi <felipe.balbi@linux.intel.com> Cc: Tony Lindgren <tony@atomide.com> Signed-off-by: Johan Hovold <johan@kernel.org> Tested-by: Tony Lindgren <tony@atomide.com> Signed-off-by: Bin Liu <b-liu@ti.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2017-10-10 03:46:08 +00:00
usb: musb: Fix sleeping function called from invalid context for hdrc glue Commit 65b3f50ed6fa ("usb: musb: Add PM runtime support for MUSB DSPS glue layer") wrongly added a call for pm_runtime_get_sync to otg_timer that runs in softirq context. That causes a "BUG: sleeping function called from invalid context" every time when polling the cable status: [<c015ebb4>] (__might_sleep) from [<c0413d60>] (__pm_runtime_resume+0x9c/0xa0) [<c0413d60>] (__pm_runtime_resume) from [<c04d0bc4>] (otg_timer+0x3c/0x254) [<c04d0bc4>] (otg_timer) from [<c0191180>] (call_timer_fn+0xfc/0x41c) [<c0191180>] (call_timer_fn) from [<c01915c0>] (expire_timers+0x120/0x210) [<c01915c0>] (expire_timers) from [<c0191acc>] (run_timer_softirq+0xa4/0xdc) [<c0191acc>] (run_timer_softirq) from [<c010168c>] (__do_softirq+0x12c/0x594) I did not notice that as I did not have CONFIG_DEBUG_ATOMIC_SLEEP enabled. And looks like also musb_gadget_queue() suffers from the same problem. Let's fix the issue by using a list of delayed work then call it on resume. Note that we want to do this only when musb core and it's parent devices are awake, and we need to make sure the DSPS glue timer is stopped as noted by Johan Hovold <johan@kernel.org>. Note that we already are re-enabling the timer with mod_timer() in dsps_musb_enable(). Later on we may be able to remove other delayed work in the musb driver and just do it from pending_resume_work. But this should be done only for delayed work that does not have other timing requirements beyond just being run on resume. Fixes: 65b3f50ed6fa ("usb: musb: Add PM runtime support for MUSB DSPS glue layer") Reported-by: Johan Hovold <johan@kernel.org> Reviewed-by: Johan Hovold <johan@kernel.org> Tested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Signed-off-by: Tony Lindgren <tony@atomide.com> Signed-off-by: Bin Liu <b-liu@ti.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2016-11-16 19:21:23 +00:00
WARN_ON(!list_empty(&musb->pending_list));
spin_lock_irqsave(&musb->lock, flags);
if (is_peripheral_active(musb)) {
/* FIXME force disconnect unless we know USB will wake
* the system up quickly enough to respond ...
*/
} else if (is_host_active(musb)) {
/* we know all the children are suspended; sometimes
* they will even be wakeup-enabled.
*/
}
musb_save_context(musb);
spin_unlock_irqrestore(&musb->lock, flags);
return 0;
}
static int musb_resume(struct device *dev)
{
usb: musb: Fix sleeping function called from invalid context for hdrc glue Commit 65b3f50ed6fa ("usb: musb: Add PM runtime support for MUSB DSPS glue layer") wrongly added a call for pm_runtime_get_sync to otg_timer that runs in softirq context. That causes a "BUG: sleeping function called from invalid context" every time when polling the cable status: [<c015ebb4>] (__might_sleep) from [<c0413d60>] (__pm_runtime_resume+0x9c/0xa0) [<c0413d60>] (__pm_runtime_resume) from [<c04d0bc4>] (otg_timer+0x3c/0x254) [<c04d0bc4>] (otg_timer) from [<c0191180>] (call_timer_fn+0xfc/0x41c) [<c0191180>] (call_timer_fn) from [<c01915c0>] (expire_timers+0x120/0x210) [<c01915c0>] (expire_timers) from [<c0191acc>] (run_timer_softirq+0xa4/0xdc) [<c0191acc>] (run_timer_softirq) from [<c010168c>] (__do_softirq+0x12c/0x594) I did not notice that as I did not have CONFIG_DEBUG_ATOMIC_SLEEP enabled. And looks like also musb_gadget_queue() suffers from the same problem. Let's fix the issue by using a list of delayed work then call it on resume. Note that we want to do this only when musb core and it's parent devices are awake, and we need to make sure the DSPS glue timer is stopped as noted by Johan Hovold <johan@kernel.org>. Note that we already are re-enabling the timer with mod_timer() in dsps_musb_enable(). Later on we may be able to remove other delayed work in the musb driver and just do it from pending_resume_work. But this should be done only for delayed work that does not have other timing requirements beyond just being run on resume. Fixes: 65b3f50ed6fa ("usb: musb: Add PM runtime support for MUSB DSPS glue layer") Reported-by: Johan Hovold <johan@kernel.org> Reviewed-by: Johan Hovold <johan@kernel.org> Tested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Signed-off-by: Tony Lindgren <tony@atomide.com> Signed-off-by: Bin Liu <b-liu@ti.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2016-11-16 19:21:23 +00:00
struct musb *musb = dev_to_musb(dev);
unsigned long flags;
int error;
u8 devctl;
u8 mask;
/*
* For static cmos like DaVinci, register values were preserved
* unless for some reason the whole soc powered down or the USB
* module got reset through the PSC (vs just being disabled).
*
* For the DSPS glue layer though, a full register restore has to
* be done. As it shouldn't harm other platforms, we do it
* unconditionally.
*/
musb_restore_context(musb);
devctl = musb_readb(musb->mregs, MUSB_DEVCTL);
mask = MUSB_DEVCTL_BDEVICE | MUSB_DEVCTL_FSDEV | MUSB_DEVCTL_LSDEV;
if ((devctl & mask) != (musb->context.devctl & mask))
musb->port1_status = 0;
usb: musb: core: make sure musb is in RPM_ACTIVE on resume On am335x-evm with musb in host mode and using it as a wakeup source the following happens once the CPU comes out of suspend to ram: |PM: Wakeup source MPU_WAKE |PM: noirq resume of devices complete after 15.453 msecs |PM: early resume of devices complete after 2.222 msecs |PM: resume of devices complete after 507.351 msecs |Restarting tasks ... |------------[ cut here ]------------ |WARNING: CPU: 0 PID: 322 at drivers/usb/core/urb.c:339 usb_submit_urb+0x494/0x4c8() |URB cc0db380 submitted while active |[<c0348e64>] (usb_submit_urb) from [<c0340f94>] (hub_activate+0x2b8/0x49c) |[<c0340f94>] (hub_activate) from [<c03411dc>] (hub_resume+0x14/0x1c) |[<c03411dc>] (hub_resume) from [<c034be10>] (usb_resume_interface.isra.4+0xdc/0x110) |[<c034be10>] (usb_resume_interface.isra.4) from [<c034beb0>] (usb_resume_both+0x6c/0x13c) |[<c034beb0>] (usb_resume_both) from [<c034cca4>] (usb_runtime_resume+0x10/0x14) |[<c034cca4>] (usb_runtime_resume) from [<c02bbd80>] (__rpm_callback+0x2c/0x60) |[<c02bbd80>] (__rpm_callback) from [<c02bbdd4>] (rpm_callback+0x20/0x74) |[<c02bbdd4>] (rpm_callback) from [<c02bcc48>] (rpm_resume+0x380/0x548) |[<c02bcc48>] (rpm_resume) from [<c02bcb00>] (rpm_resume+0x238/0x548) |[<c02bcb00>] (rpm_resume) from [<c02bd08c>] (__pm_runtime_resume+0x64/0x94) |[<c02bd08c>] (__pm_runtime_resume) from [<c034b5a4>] (usb_autopm_get_interface+0x18/0x5c) |[<c034b5a4>] (usb_autopm_get_interface) from [<c03438b8>] (hub_thread+0x10c/0x115c) |[<c03438b8>] (hub_thread) from [<c005a70c>] (kthread+0xbc/0xd8) |---[ end trace 036aa5fe78203142 ]--- |hub 1-0:1.0: activate --> -16 |hub 2-0:1.0: activate --> -16 The reason for this backtrace is the attempt of the USB code to resume the HUB twice and thus enqueue the status URB twice. Alan Stern was a great help by explaining how the USB code supposed to work and what is most likely the problem. The root problem is that after resume the musb runtime-suspend state remains RPM_SUSPENDED. According to git log it RPM was added for the omap2430 platform. If I understand it correct the omap2430 invokes a get on musb once a cable is connected and a put once the cable is gone. In between the device could go auto-idle/off. Not sure what happens when the device goes into suspend but then I guess it was gadget only. On DSPS I see only a get in probe and put in remove function. This would forbid RPM from working but then the devices enterns suspended state anyway :) To get rid of this warning, I set the device state to RPM_ACTIVE which the expected state. Cc: Alan Stern <stern@rowland.harvard.edu> Cc: <stable@vger.kernel.org> Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de> Signed-off-by: Felipe Balbi <balbi@ti.com>
2014-11-13 17:33:08 +00:00
musb_enable_interrupts(musb);
musb_platform_enable(musb);
/* session might be disabled in suspend */
if (musb->port_mode == MUSB_HOST &&
!(musb->ops->quirks & MUSB_PRESERVE_SESSION)) {
devctl |= MUSB_DEVCTL_SESSION;
musb_writeb(musb->mregs, MUSB_DEVCTL, devctl);
}
usb: musb: Fix sleeping function called from invalid context for hdrc glue Commit 65b3f50ed6fa ("usb: musb: Add PM runtime support for MUSB DSPS glue layer") wrongly added a call for pm_runtime_get_sync to otg_timer that runs in softirq context. That causes a "BUG: sleeping function called from invalid context" every time when polling the cable status: [<c015ebb4>] (__might_sleep) from [<c0413d60>] (__pm_runtime_resume+0x9c/0xa0) [<c0413d60>] (__pm_runtime_resume) from [<c04d0bc4>] (otg_timer+0x3c/0x254) [<c04d0bc4>] (otg_timer) from [<c0191180>] (call_timer_fn+0xfc/0x41c) [<c0191180>] (call_timer_fn) from [<c01915c0>] (expire_timers+0x120/0x210) [<c01915c0>] (expire_timers) from [<c0191acc>] (run_timer_softirq+0xa4/0xdc) [<c0191acc>] (run_timer_softirq) from [<c010168c>] (__do_softirq+0x12c/0x594) I did not notice that as I did not have CONFIG_DEBUG_ATOMIC_SLEEP enabled. And looks like also musb_gadget_queue() suffers from the same problem. Let's fix the issue by using a list of delayed work then call it on resume. Note that we want to do this only when musb core and it's parent devices are awake, and we need to make sure the DSPS glue timer is stopped as noted by Johan Hovold <johan@kernel.org>. Note that we already are re-enabling the timer with mod_timer() in dsps_musb_enable(). Later on we may be able to remove other delayed work in the musb driver and just do it from pending_resume_work. But this should be done only for delayed work that does not have other timing requirements beyond just being run on resume. Fixes: 65b3f50ed6fa ("usb: musb: Add PM runtime support for MUSB DSPS glue layer") Reported-by: Johan Hovold <johan@kernel.org> Reviewed-by: Johan Hovold <johan@kernel.org> Tested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Signed-off-by: Tony Lindgren <tony@atomide.com> Signed-off-by: Bin Liu <b-liu@ti.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2016-11-16 19:21:23 +00:00
spin_lock_irqsave(&musb->lock, flags);
error = musb_run_resume_work(musb);
if (error)
dev_err(musb->controller, "resume work failed with %i\n",
error);
spin_unlock_irqrestore(&musb->lock, flags);
USB: musb: fix external abort on suspend Make sure that the controller is runtime resumed when system suspending to avoid an external abort when accessing the interrupt registers: Unhandled fault: external abort on non-linefetch (0x1008) at 0xd025840a ... [<c05481a4>] (musb_default_readb) from [<c0545abc>] (musb_disable_interrupts+0x84/0xa8) [<c0545abc>] (musb_disable_interrupts) from [<c0546b08>] (musb_suspend+0x38/0xb8) [<c0546b08>] (musb_suspend) from [<c04a57f8>] (platform_pm_suspend+0x3c/0x64) This is easily reproduced on a BBB by enabling the peripheral port only (as the host port may enable the shared clock) and keeping it disconnected so that the controller is runtime suspended. (Well, you would also need to the not-yet-merged am33xx-suspend patches by Dave Gerlach to be able to suspend the BBB.) This is a regression that was introduced by commit 1c4d0b4e1806 ("usb: musb: Remove pm_runtime_set_irq_safe") which allowed the parent glue device to runtime suspend and thereby exposed a couple of older issues: Register accesses without explicitly making sure the controller is runtime resumed during suspend was first introduced by commit c338412b5ded ("usb: musb: unconditionally save and restore the context on suspend") in 3.14. Commit a1fc1920aaaa ("usb: musb: core: make sure musb is in RPM_ACTIVE on resume") later started setting the RPM status to active during resume, and this was also implicitly relying on the parent always being active. Since commit 71723f95463d ("PM / runtime: print error when activating a child to unactive parent") this now also results in the following warning: musb-hdrc musb-hdrc.0: runtime PM trying to activate child device musb-hdrc.0 but parent (47401400.usb) is not active This patch has been verified on 4.13-rc2, 4.12 and 4.9 using a BBB (the dsps glue would always be active also in 4.8). Fixes: c338412b5ded ("usb: musb: unconditionally save and restore the context on suspend") Fixes: a1fc1920aaaa ("usb: musb: core: make sure musb is in RPM_ACTIVE on resume") Fixes: 1c4d0b4e1806 ("usb: musb: Remove pm_runtime_set_irq_safe") Cc: stable <stable@vger.kernel.org> # 4.8+ Cc: Alan Stern <stern@rowland.harvard.edu> Cc: Daniel Mack <zonque@gmail.com> Cc: Dave Gerlach <d-gerlach@ti.com> Cc: Rafael J. Wysocki <rjw@rjwysocki.net> Cc: Sebastian Andrzej Siewior <bigeasy@linutronix.de> Cc: Tony Lindgren <tony@atomide.com> Signed-off-by: Johan Hovold <johan@kernel.org> Signed-off-by: Bin Liu <b-liu@ti.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2017-08-24 16:38:36 +00:00
pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
return 0;
}
usb: musb: Idle path retention and offmode support for OMAP3 This patch supports the retention and offmode support in the idle path for musb driver using runtime pm APIs. This is restricted to support offmode and retention only when device not connected.When device/cable connected with gadget driver loaded,configured to no idle/standby which will not allow the core transition to retention or off. There is no context save/restore done by hardware for musb in OMAP3 and OMAP4,driver has to take care of saving and restoring the context during offmode. Musb has a requirement of configuring sysconfig register to force idle/standby mode and set the ENFORCE bit in module STANDBY register for retention and offmode support. Runtime pm and hwmod frameworks will take care of configuring to force idle/standby when pm_runtime_put_sync is called and back to no idle/standby when pm_runeime_get_sync is called. Compile, boot tested and also tested the retention in the idle path on OMAP3630Zoom3. And tested the global suspend/resume with offmode enabled. Usb basic functionality tested on OMAP4430SDP. There is some problem with idle path offmode in mainline, I could not test with offmode. But I have tested this patch with resetting the controller in the idle path when wakeup from retention just to make sure that the context is lost, and restore path is working fine. Removed .suspend/.resume fnction pointers and functions because there is no need of having these functions as all required work is done at runtime in the driver. There is no need to call the runtime pm api with glue driver device as glue layer device is the parent of musb core device, when runtime apis are called for the child, parent device runtime functionality will be invoked. Design overview: pm_runtime_get_sync: When called with musb core device takes care of enabling the clock, calling runtime callback function of omap2430 glue layer, runtime call back of musb driver and configure the musb sysconfig to no idle/standby pm_runtime_put: Takes care of calling runtime callback function of omap2430 glue layer, runtime call back of musb driver, Configure the musb sysconfig to force idle/standby and disable the clock. During musb driver load: Call pm_runtime_get_sync. End of musb driver load: Call pm_runtime_put During gadget driver load: Call pm_runtime_get_sync, End of gadget driver load: Call pm_runtime_put if there is no device or cable is connected. During unload of the gadget driver:Call pm_runtime_get_sync if cable/device is not connected. End of the gadget driver unload : pm_runtime_put During unload of musb driver : Call pm_runtime_get_sync End of unload: Call pm_runtime_put On connect of usb cable/device -> transceiver notification(VBUS and ID-GND): pm_runtime_get_sync only if the gadget driver loaded. On disconnect of the cable/device -> Disconnect Notification: pm_runtime_put if the gadget driver is loaded. Signed-off-by: Hema HK <hemahk@ti.com> Signed-off-by: Felipe Balbi <balbi@ti.com>
2011-02-28 08:49:34 +00:00
static int musb_runtime_suspend(struct device *dev)
{
struct musb *musb = dev_to_musb(dev);
musb_save_context(musb);
usb: musb: Fix sleeping function called from invalid context for hdrc glue Commit 65b3f50ed6fa ("usb: musb: Add PM runtime support for MUSB DSPS glue layer") wrongly added a call for pm_runtime_get_sync to otg_timer that runs in softirq context. That causes a "BUG: sleeping function called from invalid context" every time when polling the cable status: [<c015ebb4>] (__might_sleep) from [<c0413d60>] (__pm_runtime_resume+0x9c/0xa0) [<c0413d60>] (__pm_runtime_resume) from [<c04d0bc4>] (otg_timer+0x3c/0x254) [<c04d0bc4>] (otg_timer) from [<c0191180>] (call_timer_fn+0xfc/0x41c) [<c0191180>] (call_timer_fn) from [<c01915c0>] (expire_timers+0x120/0x210) [<c01915c0>] (expire_timers) from [<c0191acc>] (run_timer_softirq+0xa4/0xdc) [<c0191acc>] (run_timer_softirq) from [<c010168c>] (__do_softirq+0x12c/0x594) I did not notice that as I did not have CONFIG_DEBUG_ATOMIC_SLEEP enabled. And looks like also musb_gadget_queue() suffers from the same problem. Let's fix the issue by using a list of delayed work then call it on resume. Note that we want to do this only when musb core and it's parent devices are awake, and we need to make sure the DSPS glue timer is stopped as noted by Johan Hovold <johan@kernel.org>. Note that we already are re-enabling the timer with mod_timer() in dsps_musb_enable(). Later on we may be able to remove other delayed work in the musb driver and just do it from pending_resume_work. But this should be done only for delayed work that does not have other timing requirements beyond just being run on resume. Fixes: 65b3f50ed6fa ("usb: musb: Add PM runtime support for MUSB DSPS glue layer") Reported-by: Johan Hovold <johan@kernel.org> Reviewed-by: Johan Hovold <johan@kernel.org> Tested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Signed-off-by: Tony Lindgren <tony@atomide.com> Signed-off-by: Bin Liu <b-liu@ti.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2016-11-16 19:21:23 +00:00
musb->is_runtime_suspended = 1;
usb: musb: Idle path retention and offmode support for OMAP3 This patch supports the retention and offmode support in the idle path for musb driver using runtime pm APIs. This is restricted to support offmode and retention only when device not connected.When device/cable connected with gadget driver loaded,configured to no idle/standby which will not allow the core transition to retention or off. There is no context save/restore done by hardware for musb in OMAP3 and OMAP4,driver has to take care of saving and restoring the context during offmode. Musb has a requirement of configuring sysconfig register to force idle/standby mode and set the ENFORCE bit in module STANDBY register for retention and offmode support. Runtime pm and hwmod frameworks will take care of configuring to force idle/standby when pm_runtime_put_sync is called and back to no idle/standby when pm_runeime_get_sync is called. Compile, boot tested and also tested the retention in the idle path on OMAP3630Zoom3. And tested the global suspend/resume with offmode enabled. Usb basic functionality tested on OMAP4430SDP. There is some problem with idle path offmode in mainline, I could not test with offmode. But I have tested this patch with resetting the controller in the idle path when wakeup from retention just to make sure that the context is lost, and restore path is working fine. Removed .suspend/.resume fnction pointers and functions because there is no need of having these functions as all required work is done at runtime in the driver. There is no need to call the runtime pm api with glue driver device as glue layer device is the parent of musb core device, when runtime apis are called for the child, parent device runtime functionality will be invoked. Design overview: pm_runtime_get_sync: When called with musb core device takes care of enabling the clock, calling runtime callback function of omap2430 glue layer, runtime call back of musb driver and configure the musb sysconfig to no idle/standby pm_runtime_put: Takes care of calling runtime callback function of omap2430 glue layer, runtime call back of musb driver, Configure the musb sysconfig to force idle/standby and disable the clock. During musb driver load: Call pm_runtime_get_sync. End of musb driver load: Call pm_runtime_put During gadget driver load: Call pm_runtime_get_sync, End of gadget driver load: Call pm_runtime_put if there is no device or cable is connected. During unload of the gadget driver:Call pm_runtime_get_sync if cable/device is not connected. End of the gadget driver unload : pm_runtime_put During unload of musb driver : Call pm_runtime_get_sync End of unload: Call pm_runtime_put On connect of usb cable/device -> transceiver notification(VBUS and ID-GND): pm_runtime_get_sync only if the gadget driver loaded. On disconnect of the cable/device -> Disconnect Notification: pm_runtime_put if the gadget driver is loaded. Signed-off-by: Hema HK <hemahk@ti.com> Signed-off-by: Felipe Balbi <balbi@ti.com>
2011-02-28 08:49:34 +00:00
return 0;
}
static int musb_runtime_resume(struct device *dev)
{
usb: musb: Fix sleeping function called from invalid context for hdrc glue Commit 65b3f50ed6fa ("usb: musb: Add PM runtime support for MUSB DSPS glue layer") wrongly added a call for pm_runtime_get_sync to otg_timer that runs in softirq context. That causes a "BUG: sleeping function called from invalid context" every time when polling the cable status: [<c015ebb4>] (__might_sleep) from [<c0413d60>] (__pm_runtime_resume+0x9c/0xa0) [<c0413d60>] (__pm_runtime_resume) from [<c04d0bc4>] (otg_timer+0x3c/0x254) [<c04d0bc4>] (otg_timer) from [<c0191180>] (call_timer_fn+0xfc/0x41c) [<c0191180>] (call_timer_fn) from [<c01915c0>] (expire_timers+0x120/0x210) [<c01915c0>] (expire_timers) from [<c0191acc>] (run_timer_softirq+0xa4/0xdc) [<c0191acc>] (run_timer_softirq) from [<c010168c>] (__do_softirq+0x12c/0x594) I did not notice that as I did not have CONFIG_DEBUG_ATOMIC_SLEEP enabled. And looks like also musb_gadget_queue() suffers from the same problem. Let's fix the issue by using a list of delayed work then call it on resume. Note that we want to do this only when musb core and it's parent devices are awake, and we need to make sure the DSPS glue timer is stopped as noted by Johan Hovold <johan@kernel.org>. Note that we already are re-enabling the timer with mod_timer() in dsps_musb_enable(). Later on we may be able to remove other delayed work in the musb driver and just do it from pending_resume_work. But this should be done only for delayed work that does not have other timing requirements beyond just being run on resume. Fixes: 65b3f50ed6fa ("usb: musb: Add PM runtime support for MUSB DSPS glue layer") Reported-by: Johan Hovold <johan@kernel.org> Reviewed-by: Johan Hovold <johan@kernel.org> Tested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Signed-off-by: Tony Lindgren <tony@atomide.com> Signed-off-by: Bin Liu <b-liu@ti.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2016-11-16 19:21:23 +00:00
struct musb *musb = dev_to_musb(dev);
unsigned long flags;
int error;
usb: musb: Idle path retention and offmode support for OMAP3 This patch supports the retention and offmode support in the idle path for musb driver using runtime pm APIs. This is restricted to support offmode and retention only when device not connected.When device/cable connected with gadget driver loaded,configured to no idle/standby which will not allow the core transition to retention or off. There is no context save/restore done by hardware for musb in OMAP3 and OMAP4,driver has to take care of saving and restoring the context during offmode. Musb has a requirement of configuring sysconfig register to force idle/standby mode and set the ENFORCE bit in module STANDBY register for retention and offmode support. Runtime pm and hwmod frameworks will take care of configuring to force idle/standby when pm_runtime_put_sync is called and back to no idle/standby when pm_runeime_get_sync is called. Compile, boot tested and also tested the retention in the idle path on OMAP3630Zoom3. And tested the global suspend/resume with offmode enabled. Usb basic functionality tested on OMAP4430SDP. There is some problem with idle path offmode in mainline, I could not test with offmode. But I have tested this patch with resetting the controller in the idle path when wakeup from retention just to make sure that the context is lost, and restore path is working fine. Removed .suspend/.resume fnction pointers and functions because there is no need of having these functions as all required work is done at runtime in the driver. There is no need to call the runtime pm api with glue driver device as glue layer device is the parent of musb core device, when runtime apis are called for the child, parent device runtime functionality will be invoked. Design overview: pm_runtime_get_sync: When called with musb core device takes care of enabling the clock, calling runtime callback function of omap2430 glue layer, runtime call back of musb driver and configure the musb sysconfig to no idle/standby pm_runtime_put: Takes care of calling runtime callback function of omap2430 glue layer, runtime call back of musb driver, Configure the musb sysconfig to force idle/standby and disable the clock. During musb driver load: Call pm_runtime_get_sync. End of musb driver load: Call pm_runtime_put During gadget driver load: Call pm_runtime_get_sync, End of gadget driver load: Call pm_runtime_put if there is no device or cable is connected. During unload of the gadget driver:Call pm_runtime_get_sync if cable/device is not connected. End of the gadget driver unload : pm_runtime_put During unload of musb driver : Call pm_runtime_get_sync End of unload: Call pm_runtime_put On connect of usb cable/device -> transceiver notification(VBUS and ID-GND): pm_runtime_get_sync only if the gadget driver loaded. On disconnect of the cable/device -> Disconnect Notification: pm_runtime_put if the gadget driver is loaded. Signed-off-by: Hema HK <hemahk@ti.com> Signed-off-by: Felipe Balbi <balbi@ti.com>
2011-02-28 08:49:34 +00:00
/*
* When pm_runtime_get_sync called for the first time in driver
* init, some of the structure is still not initialized which is
* used in restore function. But clock needs to be
* enabled before any register access, so
* pm_runtime_get_sync has to be called.
* Also context restore without save does not make
* any sense
*/
if (!musb->is_initialized)
return 0;
musb_restore_context(musb);
usb: musb: Idle path retention and offmode support for OMAP3 This patch supports the retention and offmode support in the idle path for musb driver using runtime pm APIs. This is restricted to support offmode and retention only when device not connected.When device/cable connected with gadget driver loaded,configured to no idle/standby which will not allow the core transition to retention or off. There is no context save/restore done by hardware for musb in OMAP3 and OMAP4,driver has to take care of saving and restoring the context during offmode. Musb has a requirement of configuring sysconfig register to force idle/standby mode and set the ENFORCE bit in module STANDBY register for retention and offmode support. Runtime pm and hwmod frameworks will take care of configuring to force idle/standby when pm_runtime_put_sync is called and back to no idle/standby when pm_runeime_get_sync is called. Compile, boot tested and also tested the retention in the idle path on OMAP3630Zoom3. And tested the global suspend/resume with offmode enabled. Usb basic functionality tested on OMAP4430SDP. There is some problem with idle path offmode in mainline, I could not test with offmode. But I have tested this patch with resetting the controller in the idle path when wakeup from retention just to make sure that the context is lost, and restore path is working fine. Removed .suspend/.resume fnction pointers and functions because there is no need of having these functions as all required work is done at runtime in the driver. There is no need to call the runtime pm api with glue driver device as glue layer device is the parent of musb core device, when runtime apis are called for the child, parent device runtime functionality will be invoked. Design overview: pm_runtime_get_sync: When called with musb core device takes care of enabling the clock, calling runtime callback function of omap2430 glue layer, runtime call back of musb driver and configure the musb sysconfig to no idle/standby pm_runtime_put: Takes care of calling runtime callback function of omap2430 glue layer, runtime call back of musb driver, Configure the musb sysconfig to force idle/standby and disable the clock. During musb driver load: Call pm_runtime_get_sync. End of musb driver load: Call pm_runtime_put During gadget driver load: Call pm_runtime_get_sync, End of gadget driver load: Call pm_runtime_put if there is no device or cable is connected. During unload of the gadget driver:Call pm_runtime_get_sync if cable/device is not connected. End of the gadget driver unload : pm_runtime_put During unload of musb driver : Call pm_runtime_get_sync End of unload: Call pm_runtime_put On connect of usb cable/device -> transceiver notification(VBUS and ID-GND): pm_runtime_get_sync only if the gadget driver loaded. On disconnect of the cable/device -> Disconnect Notification: pm_runtime_put if the gadget driver is loaded. Signed-off-by: Hema HK <hemahk@ti.com> Signed-off-by: Felipe Balbi <balbi@ti.com>
2011-02-28 08:49:34 +00:00
usb: musb: Fix sleeping function called from invalid context for hdrc glue Commit 65b3f50ed6fa ("usb: musb: Add PM runtime support for MUSB DSPS glue layer") wrongly added a call for pm_runtime_get_sync to otg_timer that runs in softirq context. That causes a "BUG: sleeping function called from invalid context" every time when polling the cable status: [<c015ebb4>] (__might_sleep) from [<c0413d60>] (__pm_runtime_resume+0x9c/0xa0) [<c0413d60>] (__pm_runtime_resume) from [<c04d0bc4>] (otg_timer+0x3c/0x254) [<c04d0bc4>] (otg_timer) from [<c0191180>] (call_timer_fn+0xfc/0x41c) [<c0191180>] (call_timer_fn) from [<c01915c0>] (expire_timers+0x120/0x210) [<c01915c0>] (expire_timers) from [<c0191acc>] (run_timer_softirq+0xa4/0xdc) [<c0191acc>] (run_timer_softirq) from [<c010168c>] (__do_softirq+0x12c/0x594) I did not notice that as I did not have CONFIG_DEBUG_ATOMIC_SLEEP enabled. And looks like also musb_gadget_queue() suffers from the same problem. Let's fix the issue by using a list of delayed work then call it on resume. Note that we want to do this only when musb core and it's parent devices are awake, and we need to make sure the DSPS glue timer is stopped as noted by Johan Hovold <johan@kernel.org>. Note that we already are re-enabling the timer with mod_timer() in dsps_musb_enable(). Later on we may be able to remove other delayed work in the musb driver and just do it from pending_resume_work. But this should be done only for delayed work that does not have other timing requirements beyond just being run on resume. Fixes: 65b3f50ed6fa ("usb: musb: Add PM runtime support for MUSB DSPS glue layer") Reported-by: Johan Hovold <johan@kernel.org> Reviewed-by: Johan Hovold <johan@kernel.org> Tested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Signed-off-by: Tony Lindgren <tony@atomide.com> Signed-off-by: Bin Liu <b-liu@ti.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2016-11-16 19:21:23 +00:00
spin_lock_irqsave(&musb->lock, flags);
error = musb_run_resume_work(musb);
if (error)
dev_err(musb->controller, "resume work failed with %i\n",
error);
musb->is_runtime_suspended = 0;
spin_unlock_irqrestore(&musb->lock, flags);
usb: musb: Idle path retention and offmode support for OMAP3 This patch supports the retention and offmode support in the idle path for musb driver using runtime pm APIs. This is restricted to support offmode and retention only when device not connected.When device/cable connected with gadget driver loaded,configured to no idle/standby which will not allow the core transition to retention or off. There is no context save/restore done by hardware for musb in OMAP3 and OMAP4,driver has to take care of saving and restoring the context during offmode. Musb has a requirement of configuring sysconfig register to force idle/standby mode and set the ENFORCE bit in module STANDBY register for retention and offmode support. Runtime pm and hwmod frameworks will take care of configuring to force idle/standby when pm_runtime_put_sync is called and back to no idle/standby when pm_runeime_get_sync is called. Compile, boot tested and also tested the retention in the idle path on OMAP3630Zoom3. And tested the global suspend/resume with offmode enabled. Usb basic functionality tested on OMAP4430SDP. There is some problem with idle path offmode in mainline, I could not test with offmode. But I have tested this patch with resetting the controller in the idle path when wakeup from retention just to make sure that the context is lost, and restore path is working fine. Removed .suspend/.resume fnction pointers and functions because there is no need of having these functions as all required work is done at runtime in the driver. There is no need to call the runtime pm api with glue driver device as glue layer device is the parent of musb core device, when runtime apis are called for the child, parent device runtime functionality will be invoked. Design overview: pm_runtime_get_sync: When called with musb core device takes care of enabling the clock, calling runtime callback function of omap2430 glue layer, runtime call back of musb driver and configure the musb sysconfig to no idle/standby pm_runtime_put: Takes care of calling runtime callback function of omap2430 glue layer, runtime call back of musb driver, Configure the musb sysconfig to force idle/standby and disable the clock. During musb driver load: Call pm_runtime_get_sync. End of musb driver load: Call pm_runtime_put During gadget driver load: Call pm_runtime_get_sync, End of gadget driver load: Call pm_runtime_put if there is no device or cable is connected. During unload of the gadget driver:Call pm_runtime_get_sync if cable/device is not connected. End of the gadget driver unload : pm_runtime_put During unload of musb driver : Call pm_runtime_get_sync End of unload: Call pm_runtime_put On connect of usb cable/device -> transceiver notification(VBUS and ID-GND): pm_runtime_get_sync only if the gadget driver loaded. On disconnect of the cable/device -> Disconnect Notification: pm_runtime_put if the gadget driver is loaded. Signed-off-by: Hema HK <hemahk@ti.com> Signed-off-by: Felipe Balbi <balbi@ti.com>
2011-02-28 08:49:34 +00:00
return 0;
}
static const struct dev_pm_ops musb_dev_pm_ops = {
.suspend = musb_suspend,
.resume = musb_resume,
usb: musb: Idle path retention and offmode support for OMAP3 This patch supports the retention and offmode support in the idle path for musb driver using runtime pm APIs. This is restricted to support offmode and retention only when device not connected.When device/cable connected with gadget driver loaded,configured to no idle/standby which will not allow the core transition to retention or off. There is no context save/restore done by hardware for musb in OMAP3 and OMAP4,driver has to take care of saving and restoring the context during offmode. Musb has a requirement of configuring sysconfig register to force idle/standby mode and set the ENFORCE bit in module STANDBY register for retention and offmode support. Runtime pm and hwmod frameworks will take care of configuring to force idle/standby when pm_runtime_put_sync is called and back to no idle/standby when pm_runeime_get_sync is called. Compile, boot tested and also tested the retention in the idle path on OMAP3630Zoom3. And tested the global suspend/resume with offmode enabled. Usb basic functionality tested on OMAP4430SDP. There is some problem with idle path offmode in mainline, I could not test with offmode. But I have tested this patch with resetting the controller in the idle path when wakeup from retention just to make sure that the context is lost, and restore path is working fine. Removed .suspend/.resume fnction pointers and functions because there is no need of having these functions as all required work is done at runtime in the driver. There is no need to call the runtime pm api with glue driver device as glue layer device is the parent of musb core device, when runtime apis are called for the child, parent device runtime functionality will be invoked. Design overview: pm_runtime_get_sync: When called with musb core device takes care of enabling the clock, calling runtime callback function of omap2430 glue layer, runtime call back of musb driver and configure the musb sysconfig to no idle/standby pm_runtime_put: Takes care of calling runtime callback function of omap2430 glue layer, runtime call back of musb driver, Configure the musb sysconfig to force idle/standby and disable the clock. During musb driver load: Call pm_runtime_get_sync. End of musb driver load: Call pm_runtime_put During gadget driver load: Call pm_runtime_get_sync, End of gadget driver load: Call pm_runtime_put if there is no device or cable is connected. During unload of the gadget driver:Call pm_runtime_get_sync if cable/device is not connected. End of the gadget driver unload : pm_runtime_put During unload of musb driver : Call pm_runtime_get_sync End of unload: Call pm_runtime_put On connect of usb cable/device -> transceiver notification(VBUS and ID-GND): pm_runtime_get_sync only if the gadget driver loaded. On disconnect of the cable/device -> Disconnect Notification: pm_runtime_put if the gadget driver is loaded. Signed-off-by: Hema HK <hemahk@ti.com> Signed-off-by: Felipe Balbi <balbi@ti.com>
2011-02-28 08:49:34 +00:00
.runtime_suspend = musb_runtime_suspend,
.runtime_resume = musb_runtime_resume,
};
#define MUSB_DEV_PM_OPS (&musb_dev_pm_ops)
#else
#define MUSB_DEV_PM_OPS NULL
#endif
static struct platform_driver musb_driver = {
.driver = {
.name = musb_driver_name,
.bus = &platform_bus_type,
.pm = MUSB_DEV_PM_OPS,
.dev_groups = musb_groups,
},
.probe = musb_probe,
.remove = musb_remove,
};
module_platform_driver(musb_driver);