]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/commitdiff
MUSB: TUSB OTG enumeration support
authorDavid Brownell <dbrownell@users.sourceforge.net>
Thu, 14 Sep 2006 07:27:27 +0000 (10:27 +0300)
committerTony Lindgren <tony@atomide.com>
Thu, 14 Sep 2006 13:21:35 +0000 (16:21 +0300)
This gives TUSB host side enumeration support for low speed devices (like
USB mice), through a Mini-A connector that's not removed ... basically, a
non-OTG configuration, with the ID pin always grounded.

Basically it punts the "turn VBUS power on/off" to board-specific logic,
and implements it for TUSB using software switching (rather than having
the controller do it).

There are various issues, notably

    (a) remote wakeup not getting passed to the root hub then down
to the device (mouse) that issued the wakeup;

    (b) strange "vbus error" reports on device connection if nothing
     is hooked up at driver initialization; and

    (c) full or high speed devices see spurious disconnect events
right after they've been reset (hw bug?);

Includes various small cleanups too, notably starting to obey the OTG
state machine and use the OTG timer.

Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>
drivers/usb/musb/davinci.c
drivers/usb/musb/debug.h
drivers/usb/musb/musb_procfs.c
drivers/usb/musb/musbdefs.h
drivers/usb/musb/plat_uds.c
drivers/usb/musb/tusb6010.c
drivers/usb/musb/virthub.c

index fbbf4cbd4725ae0a351ada1f9ad9ef94a6664da2..4acf7e895f5d075989f233bbf9f8a66685911901 100644 (file)
@@ -229,6 +229,11 @@ static void davinci_vbus_power(struct musb *musb, int is_on, int sleeping)
                sleeping ? "immediate" : "deferred");
 }
 
+static void davinci_set_vbus(struct musb *musb, int is_on)
+{
+       return davinci_vbus_power(musb, is_on, 0);
+}
+
 static irqreturn_t davinci_interrupt(int irq, void *__hci, struct pt_regs *r)
 {
        unsigned long   flags;
@@ -334,6 +339,9 @@ int __devinit musb_platform_init(struct musb *musb)
 #endif
        davinci_vbus_power(musb, musb->board_mode == MUSB_HOST, 1);
 
+       if (is_host_enabled(musb))
+               musb->board_set_vbus = davinci_set_vbus;
+
        /* reset the controller */
        musb_writel(tibase, DAVINCI_USB_CTRL_REG, 0x1);
 
index 2c3c35baf1b0a53d16e8ef890031c45dac009cc6..ef0c4a7d42994ada6bd6cf7ec78d5a63741db3b7 100644 (file)
@@ -60,4 +60,6 @@ static inline int _dbg_level(unsigned l)
 
 #define DBG(level,fmt,args...) xprintk(level,KERN_DEBUG,fmt, ## args)
 
+extern const char *otg_state_string(struct musb *);
+
 #endif                         //  __MUSB_LINUX_DEBUG_H__
index 7b687e34f7b10e7fffc76e886e057c6231e41fe6..a5df442020dcfadaac8d8b2c017dd1eabbe327e8 100644 (file)
@@ -48,9 +48,9 @@
 #include "davinci.h"
 
 
-static const char *state_string(enum usb_otg_state state)
+const char *otg_state_string(struct musb *musb)
 {
-       switch (state) {
+       switch (musb->xceiv.state) {
        case OTG_STATE_A_IDLE:          return "a_idle";
        case OTG_STATE_A_WAIT_VRISE:    return "a_wait_vrise";
        case OTG_STATE_A_WAIT_BCON:     return "a_wait_bcon";
@@ -485,8 +485,9 @@ static int dump_header_stats(struct musb *pThis, char *buffer)
                return count;
        buffer += count;
 
-       code = sprintf(buffer, "OTG state: %s\n",
-                       state_string(pThis->xceiv.state));
+       code = sprintf(buffer, "OTG state: %s; %sactive\n",
+                       otg_state_string(pThis),
+                       pThis->is_active ? "" : "in");
        if (code < 0)
                return code;
        buffer += code;
@@ -544,7 +545,7 @@ static int dump_header_stats(struct musb *pThis, char *buffer)
 #ifdef CONFIG_USB_TUSB6010
        code = sprintf(buffer,
                        "TUSB6010: devconf %08x, phy enable %08x drive %08x"
-                       "\n\totg %08x timer %08x"
+                       "\n\totg %03x timer %08x"
                        "\n\tprcm conf %08x mgmt %08x; intmask %08x"
                        "\n",
                        musb_readl(pThis->ctrl_base, TUSB_DEV_CONF),
index fe1803463a33a19a25f2a4b305d15ec2edc1bf06..0319bd5388e6e449a789baf1482c04d32b5af276 100644 (file)
@@ -399,9 +399,13 @@ struct musb {
        struct list_head        in_bulk;        /* of musb_qh */
        struct list_head        out_bulk;       /* of musb_qh */
        struct musb_qh          *periodic[32];  /* tree of interrupt+iso */
-
 #endif
 
+       /* called with IRQs blocked; ON/nonzero implies starting a session,
+        * and waiting at least a_wait_vrise_tmout.
+        */
+       void                    (*board_set_vbus)(struct musb *, int is_on);
+
        struct dma_controller   *pDmaController;
 
        struct device           *controller;
@@ -489,6 +493,11 @@ struct musb {
 #endif
 };
 
+static inline void musb_set_vbus(struct musb *musb, int is_on)
+{
+       musb->board_set_vbus(musb, is_on);
+}
+
 #ifdef CONFIG_USB_GADGET_MUSB_HDRC
 static inline struct musb *gadget_to_musb(struct usb_gadget *g)
 {
index 97c6a03b804d2b5f1e27dce7c035ef0a11fead1a..b7318faeaa101e87fe4ef2b0d4cfc0e5a005a8ca 100644 (file)
@@ -416,6 +416,7 @@ static irqreturn_t musb_stage0_irq(struct musb * pThis, u8 bIntrUSB,
                pThis->bEnd0Stage = MGC_END0_START;
                pThis->xceiv.state = OTG_STATE_A_IDLE;
                MUSB_HST_MODE(pThis);
+               musb_set_vbus(pThis, 1);
 
                handled = IRQ_HANDLED;
 
@@ -454,12 +455,9 @@ static irqreturn_t musb_stage0_irq(struct musb * pThis, u8 bIntrUSB,
                                }; s; }),
                                pThis->vbuserr_retry);
 
-               /* after hw goes to A_IDLE, try connecting again */
-               pThis->xceiv.state = OTG_STATE_A_IDLE;
-               if (pThis->vbuserr_retry--)
-                       musb_writeb(pBase, MGC_O_HDRC_DEVCTL,
-                                       MGC_M_DEVCTL_SESSION);
-               return IRQ_HANDLED;
+               /* go through A_WAIT_VFALL then start a new session */
+               musb_set_vbus(pThis, 0);
+               handled = IRQ_HANDLED;
        } else
                pThis->vbuserr_retry = VBUSERR_RETRY_COUNT;
 
@@ -498,14 +496,10 @@ static irqreturn_t musb_stage0_irq(struct musb * pThis, u8 bIntrUSB,
                        pThis->xceiv.state = OTG_STATE_B_HOST;
                        break;
                default:
-                       DBG(2, "connect in state %d\n", pThis->xceiv.state);
-                       /* FALLTHROUGH */
-               case OTG_STATE_A_WAIT_BCON:
-               case OTG_STATE_A_WAIT_VRISE:
                        pThis->xceiv.state = OTG_STATE_A_HOST;
                        break;
                }
-               DBG(1, "CONNECT (host state %d)\n", pThis->xceiv.state);
+               DBG(1, "CONNECT (%s)\n", otg_state_string(pThis));
                otg_input_changed(pThis, devctl, FALSE, TRUE, FALSE);
        }
 #endif /* CONFIG_USB_MUSB_HDRC_HCD */
@@ -678,8 +672,10 @@ void musb_start(struct musb * pThis)
 
        switch (pThis->board_mode) {
        case MUSB_HOST:
+               musb_set_vbus(pThis, 1);
+               break;
        case MUSB_OTG:
-               musb_writeb(pBase, MGC_O_HDRC_DEVCTL, MGC_M_DEVCTL_SESSION);
+               WARN("how to start OTG session?\n");
                break;
        case MUSB_PERIPHERAL:
                state = musb_readb(pBase, MGC_O_HDRC_DEVCTL);
index 3b087d2e8b81de4bc84f3f694e2b1ba52d88d8b0..bf16d2d510d1b107cfcefd77b680d3330fdd01c5 100644 (file)
@@ -179,7 +179,7 @@ static int tusb_set_power(struct otg_transceiver *x, unsigned mA)
  * (to be fixed in rev3 silicon) ... symptoms include disconnect
  * or looping suspend/resume cycles
  */
-void tusb_set_clock_source(struct musb *musb, int mode)
+static void tusb_set_clock_source(struct musb *musb, int mode)
 {
        void __iomem    *base = musb->ctrl_base;
        u32             reg;
@@ -209,8 +209,17 @@ static void tusb_allow_idle(struct musb *musb, u32 wakeup_enables)
        wakeup_enables |= TUSB_PRCM_WNORCS;
        musb_writel(base, TUSB_PRCM_WAKEUP_MASK, ~wakeup_enables);
 
+// FIXME issue 4, when host (driving vbus), enable hipower comparator
+
+       /* REVISIT writeup of WLD implies that if WLD set and ID is grounded,
+        * TUSB_PHY_OTG_CTRL.TUSB_PHY_OTG_CTRL_OTG_ID_PULLUP must be cleared.
+        * Presumably that's mostly to save power, hence WLD is immaterial ...
+        */
+
        reg = musb_readl(base, TUSB_PRCM_MNGMT);
-       reg &= ~TUSB_PRCM_MNGMT_OTG_VBUS_DET_EN; /* REVISIT leave alone? */
+       /* issue 4: when driving vbus, leave hipower comparator active */
+       if (!is_host_active(musb))
+               reg &= ~TUSB_PRCM_MNGMT_OTG_VBUS_DET_EN;
        reg |= TUSB_PRCM_MNGMT_OTG_SESS_END_EN
                        | TUSB_PRCM_MNGMT_PM_IDLE
                        | TUSB_PRCM_MNGMT_DEV_IDLE;
@@ -296,8 +305,60 @@ void musb_platform_try_idle(struct musb *musb)
        if (musb->is_active)
                del_timer(&musb_idle_timer);
        else
-               mod_timer(&musb_idle_timer, jiffies +
-                       (is_host_active(musb) ? msecs_to_jiffies(3) : 0));
+               mod_timer(&musb_idle_timer, jiffies + msecs_to_jiffies(3));
+}
+
+/* ticks of 60 MHz clock */
+#define DEVCLOCK               60000000
+#define OTG_TIMER_MS(msecs)    ((msecs) \
+               ? (TUSB_DEV_OTG_TIMER_VAL((DEVCLOCK/1000)*(msecs)) \
+                               | TUSB_DEV_OTG_TIMER_ENABLE) \
+               : 0)
+
+static void tusb_set_vbus(struct musb *musb, int is_on)
+{
+       void __iomem    *base = musb->ctrl_base;
+       u32             conf, prcm, timer;
+       u8              devctl;
+
+       /* we control CPEN in software not hardware ... */
+
+       prcm = musb_readl(base, TUSB_PRCM_MNGMT);
+       conf = musb_readl(base, TUSB_DEV_CONF);
+       devctl = musb_readb(musb->pRegs, MGC_O_HDRC_DEVCTL);
+
+       if (is_on) {
+               musb->is_active = 1;
+               prcm |= TUSB_PRCM_MNGMT_5V_CPEN;
+               timer = OTG_TIMER_MS(OTG_TIME_A_WAIT_VRISE);
+
+               musb->xceiv.default_a = 1;
+               musb->xceiv.state = OTG_STATE_A_WAIT_VRISE;
+
+               conf |= TUSB_DEV_CONF_USB_HOST_MODE;
+
+       } else {
+               prcm &= ~TUSB_PRCM_MNGMT_5V_CPEN;
+               timer = 0;
+
+               if (musb->xceiv.default_a) {
+                       musb->xceiv.state = OTG_STATE_A_WAIT_VFALL;
+                       devctl &= ~MGC_M_DEVCTL_SESSION;
+               } else
+                       musb->is_active = 0;
+       }
+       prcm &= ~(TUSB_PRCM_MNGMT_15_SW_EN | TUSB_PRCM_MNGMT_33_SW_EN);
+
+       musb_writel(base, TUSB_PRCM_MNGMT, prcm);
+       musb_writel(base, TUSB_DEV_OTG_TIMER, timer);
+       musb_writel(base, TUSB_DEV_CONF, conf);
+       musb_writeb(musb->pRegs, MGC_O_HDRC_DEVCTL, devctl);
+
+       DBG(1, "VBUS %s, devctl %02x otg %3x conf %08x prcm %08x\n",
+               is_on ? "on" : "off",
+               musb_readb(musb->pRegs, MGC_O_HDRC_DEVCTL),
+               musb_readl(base, TUSB_DEV_OTG_STAT),
+               conf, prcm);
 }
 
 static inline void
@@ -309,25 +370,22 @@ tusb_otg_ints(struct musb *musb, u32 int_src, void __iomem *base)
        if ((int_src & TUSB_INT_SRC_ID_STATUS_CHNG)) {
                int     default_a;
 
-               default_a = is_host_enabled(musb)
-                       && (otg_stat & TUSB_DEV_OTG_STAT_ID_STATUS);
+               if (is_otg_enabled(musb))
+                       default_a = !!(otg_stat & TUSB_DEV_OTG_STAT_ID_STATUS);
+               else
+                       default_a = is_host_enabled(musb);
                if (default_a != musb->xceiv.default_a) {
                        musb->xceiv.default_a = default_a;
-                       if (musb->xceiv.default_a) {
-                               musb->xceiv.state = OTG_STATE_A_IDLE;
-                               /* REVISIT start the session? */
-                       } else
-                               musb->xceiv.state = OTG_STATE_B_IDLE;
-                       DBG(1, "Default-%c\n", musb->xceiv.default_a
-                                       ? 'A' : 'B');
-                       musb->is_active = 1;
+                       tusb_set_vbus(musb, default_a);
                }
        }
 
        /* VBUS state change */
        if (int_src & TUSB_INT_SRC_VBUS_SENSE_CHNG) {
-               /* no vbus ~= disconnect */
-               if (!is_host_enabled(musb) || !musb->xceiv.default_a) {
+
+               /* B-dev state machine:  no vbus ~= disconnect */
+               if ((is_otg_enabled(musb) && !musb->xceiv.default_a)
+                               || !is_host_enabled(musb)) {
 
                        /* REVISIT use the b_sess_valid comparator, not
                         * lowpower one; TUSB_DEV_OTG_STAT_SESS_VALID ?
@@ -345,15 +403,82 @@ tusb_otg_ints(struct musb *musb, u32 int_src, void __iomem *base)
                                        ? "b_peripheral" : "b_idle");
 
                        schedule_work(&musb->irq_work);
+
+               } else /* A-dev state machine */ {
+                       DBG(4, "vbus change, %s, otg %03x\n",
+                               otg_state_string(musb), otg_stat);
+
+                       switch (musb->xceiv.state) {
+                       case OTG_STATE_A_WAIT_VRISE:
+                               /* ignore; A-session-valid < VBUS_VALID/2,
+                                * we monitor this with the timer
+                                */
+                               break;
+                       case OTG_STATE_A_WAIT_VFALL:
+                               /* REVISIT this irq triggers at too high a
+                                * voltage ... we probably need to use the
+                                * OTG timer to wait for session end.
+                                */
+                               if (musb->vbuserr_retry) {
+                                       musb->vbuserr_retry--;
+                                       tusb_set_vbus(musb, 1);
+                               }
+                               break;
+                       default:
+                               break;
+                       }
                }
        }
 
        /* OTG timer expiration */
        if (int_src & TUSB_INT_SRC_OTG_TIMEOUT) {
-               DBG(3, "tusb: OTG timer expired\n");
-               musb_writel(base, TUSB_DEV_OTG_TIMER,
-                       musb_readl(base, TUSB_DEV_OTG_TIMER)
-                       | TUSB_DEV_OTG_TIMER_ENABLE);
+               DBG(4, "%s timer, %03x\n", otg_state_string(musb), otg_stat);
+
+               switch (musb->xceiv.state) {
+               case OTG_STATE_A_WAIT_VRISE:
+                       /* VBUS has probably been valid for a while now */
+                       if (otg_stat & TUSB_DEV_OTG_STAT_VBUS_VALID) {
+                               u8      devctl;
+
+                               devctl = musb_readb(musb->pRegs,
+                                               MGC_O_HDRC_DEVCTL);
+                               if ((devctl & MGC_M_DEVCTL_VBUS)
+                                               != MGC_M_DEVCTL_VBUS) {
+                                       DBG(2, "devctl %02x\n", devctl);
+                                       break;
+                               }
+
+                               /* request a session, then DEVCTL_HM will
+                                * be set by the controller
+                                */
+                               devctl |= MGC_M_DEVCTL_SESSION;
+                               musb_writeb(musb->pRegs, MGC_O_HDRC_DEVCTL,
+                                               devctl);
+                               musb->xceiv.state = OTG_STATE_A_WAIT_BCON;
+
+                               /* timeout 0 == infinite (like non-OTG hosts) */
+                               if (OTG_TIME_A_WAIT_BCON)
+                                       musb_writel(base, TUSB_DEV_OTG_TIMER,
+                                               OTG_TIMER_MS(OTG_TIME_A_WAIT_BCON));
+                               else
+                                       musb_writel(base, TUSB_DEV_OTG_TIMER, 0);
+                       } else {
+                               ERR("vbus rise time too slow\n");
+                               tusb_set_vbus(musb, 0);
+                       }
+                       break;
+               case OTG_STATE_A_WAIT_BCON:
+                       if (OTG_TIME_A_WAIT_BCON)
+                               tusb_set_vbus(musb, 0);
+                       break;
+               case OTG_STATE_A_SUSPEND:
+                       break;
+               case OTG_STATE_B_WAIT_ACON:
+                       break;
+               default:
+                       break;
+               }
+               musb_writel(base, TUSB_DEV_OTG_TIMER, 0);
        }
 }
 
@@ -402,6 +527,8 @@ static irqreturn_t tusb_interrupt(int irq, void *__hci, struct pt_regs *r)
                }
                DBG(3, "wake %sactive %02x\n",
                                musb->is_active ? "" : "in", reg);
+
+               // REVISIT host side TUSB_PRCM_WHOSTDISCON, TUSB_PRCM_WBUS
        }
 
        /* OTG state change reports (annoyingly) not issued by Mentor core */
@@ -485,13 +612,6 @@ void musb_platform_enable(struct musb * musb)
        /* Acknowledge pending interrupt(s) */
        musb_writel(base, TUSB_INT_SRC_CLEAR, ~TUSB_INT_MASK_RESERVED_BITS);
 
-#if 0
-       /* Set OTG timer for about one second */
-       musb_writel(base, TUSB_DEV_OTG_TIMER,
-                               TUSB_DEV_OTG_TIMER_ENABLE |
-               TUSB_DEV_OTG_TIMER_VAL(0x3c00000));
-#endif
-
        /* Only 0 clock cycles for minimum interrupt de-assertion time and
         * interrupt polarity active low seems to work reliably here */
        musb_writel(base, TUSB_INT_CTRL_CONF,
@@ -511,7 +631,9 @@ void musb_platform_enable(struct musb * musb)
  */
 void musb_platform_disable(struct musb *musb)
 {
-       if (is_dma_capable()) {
+       /* FIXME stop DMA, IRQs, timers, ... */
+
+       if (is_dma_capable() && !dma_off) {
                printk(KERN_WARNING "%s %s: dma still active\n",
                                __FILE__, __FUNCTION__);
                dma_off = 1;
@@ -574,6 +696,7 @@ static int tusb_start(struct musb *musb)
        void __iomem    *base = musb->ctrl_base;
        int             ret = -1;
        unsigned long   flags;
+       u32             reg;
 
        if (musb->board_set_power)
                ret = musb->board_set_power(1);
@@ -616,6 +739,15 @@ static int tusb_start(struct musb *musb)
                TUSB_PRCM_MNGMT_OTG_ID_PULLUP);
        tusb_setup_cpu_interface(musb);
 
+       /* simplify:  always sense/pullup ID pins, as if in OTG mode */
+       reg = musb_readl(base, TUSB_PHY_OTG_CTRL);
+       reg |= TUSB_PHY_OTG_CTRL_WRPROTECT | TUSB_PHY_OTG_CTRL_OTG_ID_PULLUP;
+       musb_writel(base, TUSB_PHY_OTG_CTRL, reg);
+
+       reg = musb_readl(base, TUSB_PHY_OTG_CTRL_ENABLE);
+       reg |= TUSB_PHY_OTG_CTRL_WRPROTECT | TUSB_PHY_OTG_CTRL_OTG_ID_PULLUP;
+       musb_writel(base, TUSB_PHY_OTG_CTRL_ENABLE, reg);
+
        spin_unlock_irqrestore(&musb->Lock, flags);
 
        return 0;
@@ -659,7 +791,11 @@ int __devinit musb_platform_init(struct musb *musb)
                return -ENODEV;
        }
        musb->isr = tusb_interrupt;
-       musb->xceiv.set_power = tusb_set_power;
+
+       if (is_host_enabled(musb))
+               musb->board_set_vbus = tusb_set_vbus;
+       if (is_peripheral_enabled(musb))
+               musb->xceiv.set_power = tusb_set_power;
 
        setup_timer(&musb_idle_timer, musb_do_idle, (unsigned long) musb);
 
index 04d54e3bdc08ad41d00decfb4523e37006f627e2..63f31fd1cd543cd49062b3de98c4b17d731da332 100644 (file)
@@ -212,6 +212,9 @@ int musb_hub_control(
                        musb_port_suspend(musb, FALSE);
                        break;
                case USB_PORT_FEAT_POWER:
+                       if (!(is_otg_enabled(musb) && hcd->self.is_b_host))
+                               musb_set_vbus(musb, 0);
+                       break;
                case USB_PORT_FEAT_C_CONNECTION:
                case USB_PORT_FEAT_C_ENABLE:
                case USB_PORT_FEAT_C_OVER_CURRENT: