]> pilppa.com Git - linux-2.6-omap-h63xx.git/commitdiff
twl4030-usb updates/fixes
authorDavid Brownell <dbrownell@users.sourceforge.net>
Thu, 16 Oct 2008 19:32:43 +0000 (12:32 -0700)
committerTony Lindgren <tony@atomide.com>
Thu, 16 Oct 2008 20:07:12 +0000 (13:07 -0700)
Simplify handling of VBUS and ID status and the USB_PRES interrupt.
This seems to resolve the problems I previously saw on Beagle and
Overo boards.

 - Read the status directly, instead of trying to infer it from
   dodgey side effects of IRQ handling and trigger tweaking.

 - Stop trying to arrange those dodgey side effects; just leave
   the IRQ enabled at all times.

 - Check that status as part of driver probe, so the PHY can
   always be active by the time it's needed.  (BUGFIX!)

 - Re-sequence probe() to be less racey, and to avoid duplicating
   logic found elsewhere (notably irq handling and PHY activation).

 - Make the irq_chip be the exclusive owner of PWR_EDR (finally).

 - Get rid of needless work_struct ... in this irq handling thread,
   it's safe and cheap to call sysfs_notify() directly.

Stop enabling IRQs on the USB subchip.  There's no IRQ handler for
them, and since ALT_INT_REROUTE isn't being set they'll never
arrive ... so there's no point to enabling them.

Also add some comments about other things such an otg_transciever
driver should be doing, fixing minor omissions; and remove some
unnecessary file inclusions.

Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Tony Lindgren <tony@atomide.com>
drivers/i2c/chips/twl4030-usb.c

index 46538ed93b2590f469f62705fedeb7aa6abbac5d..f0d10772850cd9f75a78ff23a5b261690279349b 100644 (file)
 
 #include <linux/module.h>
 #include <linux/init.h>
-#include <linux/time.h>
 #include <linux/interrupt.h>
-#include <linux/irq.h>
 #include <linux/platform_device.h>
 #include <linux/spinlock.h>
 #include <linux/workqueue.h>
 #include <linux/io.h>
-#include <linux/usb.h>
-#include <linux/usb/ch9.h>
-#include <linux/usb/gadget.h>
+#include <linux/delay.h>
 #include <linux/usb/otg.h>
 #include <linux/i2c/twl4030.h>
 #include <mach/usb.h>
 #define PMBR1                          0x0D
 #define GPIO_USB_4PIN_ULPI_2430C       (3 << 0)
 
-/* In module TWL4030_MODULE_INT */
-#define REG_PWR_EDR1                   0x05
-#define USB_PRES_FALLING               (1 << 4)
-#define USB_PRES_RISING                        (1 << 5)
 
 /* bits in OTG_CTRL */
 #define        OTG_XCEIV_OUTPUTS \
        OTG_CTRL_BITS)
 
 
+enum linkstat {
+       USB_LINK_UNKNOWN = 0,
+       USB_LINK_NONE,
+       USB_LINK_VBUS,
+       USB_LINK_ID,
+};
+
 struct twl4030_usb {
-       struct work_struct      irq_work;
        struct otg_transceiver  otg;
        struct device           *dev;
 
@@ -267,8 +265,8 @@ struct twl4030_usb {
        /* pin configuration */
        enum twl4030_usb_mode   usb_mode;
 
-       unsigned                vbus:1;
        int                     irq;
+       u8                      linkstat;
        u8                      asleep;
        bool                    irq_enabled;
 };
@@ -326,21 +324,27 @@ static inline int twl4030_usb_write(struct twl4030_usb *twl,
        return ret;
 }
 
-static inline int twl4030_usb_read(struct twl4030_usb *twl, u8 address)
+static inline int twl4030_readb(struct twl4030_usb *twl, u8 module, u8 address)
 {
        u8 data;
        int ret = 0;
 
-       ret = twl4030_i2c_read_u8(TWL4030_MODULE_USB, &data, address);
+       ret = twl4030_i2c_read_u8(module, &data, address);
        if (ret >= 0)
                ret = data;
        else
                dev_warn(twl->dev,
-                       "TWL4030:USB:Read[0x%x] Error %d\n", address, ret);
+                       "TWL4030:readb[0x%x,0x%x] Error %d\n",
+                                       module, address, ret);
 
        return ret;
 }
 
+static inline int twl4030_usb_read(struct twl4030_usb *twl, u8 address)
+{
+       return twl4030_readb(twl, TWL4030_MODULE_USB, address);
+}
+
 /*-------------------------------------------------------------------------*/
 
 static inline int
@@ -357,6 +361,39 @@ twl4030_usb_clear_bits(struct twl4030_usb *twl, u8 reg, u8 bits)
 
 /*-------------------------------------------------------------------------*/
 
+static enum linkstat twl4030_usb_linkstat(struct twl4030_usb *twl)
+{
+       int     status;
+       int     linkstat = USB_LINK_UNKNOWN;
+
+       /* STS_HW_CONDITIONS */
+       status = twl4030_readb(twl, TWL4030_MODULE_PM_MASTER, 0x0f);
+       if (status < 0)
+               dev_err(twl->dev, "USB link status err %d\n", status);
+       else if (status & BIT(7))
+               linkstat = USB_LINK_VBUS;
+       else if (status & BIT(2))
+               linkstat = USB_LINK_ID;
+       else
+               linkstat = USB_LINK_NONE;
+
+       dev_dbg(twl->dev, "HW_CONDITIONS 0x%02x/%d; link %d\n",
+                       status, status, linkstat);
+
+       spin_lock_irq(&twl->lock);
+       twl->linkstat = linkstat;
+       if (linkstat == USB_LINK_ID) {
+               twl->otg.default_a = true;
+               twl->otg.state = OTG_STATE_A_IDLE;
+       } else {
+               twl->otg.default_a = false;
+               twl->otg.state = OTG_STATE_B_IDLE;
+       }
+       spin_unlock_irq(&twl->lock);
+
+       return linkstat;
+}
+
 static void twl4030_usb_set_mode(struct twl4030_usb *twl, int mode)
 {
        twl->usb_mode = mode;
@@ -410,24 +447,6 @@ static void twl4030_i2c_access(struct twl4030_usb *twl, int on)
        }
 }
 
-static void usb_irq_enable(struct twl4030_usb *twl, int trigger)
-{
-       set_irq_type(twl->irq, trigger);
-
-       if (!twl->irq_enabled) {
-               enable_irq(twl->irq);
-               twl->irq_enabled = true;
-       }
-}
-
-static void usb_irq_disable(struct twl4030_usb *twl)
-{
-       if (twl->irq_enabled) {
-               disable_irq(twl->irq);
-               twl->irq_enabled = false;
-       }
-}
-
 static void twl4030_phy_power(struct twl4030_usb *twl, int on)
 {
        u8 pwr;
@@ -448,16 +467,9 @@ static void twl4030_phy_power(struct twl4030_usb *twl, int on)
 
 static void twl4030_phy_suspend(struct twl4030_usb *twl, int controller_off)
 {
-       if (controller_off)
-               usb_irq_disable(twl);
-
        if (twl->asleep)
                return;
 
-       if (!controller_off)
-               /* enable rising edge interrupt to detect cable attach */
-               usb_irq_enable(twl, IRQ_TYPE_EDGE_RISING);
-
        twl4030_phy_power(twl, 0);
        twl->asleep = 1;
 }
@@ -467,9 +479,6 @@ static void twl4030_phy_resume(struct twl4030_usb *twl)
        if (!twl->asleep)
                return;
 
-       /* enable falling edge interrupt to detect cable detach */
-       usb_irq_enable(twl, IRQ_TYPE_EDGE_FALLING);
-
        twl4030_phy_power(twl, 1);
        twl4030_i2c_access(twl, 1);
        twl4030_usb_set_mode(twl, twl->usb_mode);
@@ -514,25 +523,18 @@ static ssize_t twl4030_usb_vbus_show(struct device *dev,
        int ret = -EINVAL;
 
        spin_lock_irqsave(&twl->lock, flags);
-       ret = sprintf(buf, "%s\n", twl->vbus ? "on" : "off");
+       ret = sprintf(buf, "%s\n",
+                       (twl->linkstat == USB_LINK_VBUS) ? "on" : "off");
        spin_unlock_irqrestore(&twl->lock, flags);
 
        return ret;
 }
 static DEVICE_ATTR(vbus, 0444, twl4030_usb_vbus_show, NULL);
 
-static void twl4030_usb_irq_work(struct work_struct *work)
-{
-       struct twl4030_usb *twl = container_of(work,
-                       struct twl4030_usb, irq_work);
-
-       sysfs_notify(&twl->dev->kobj, NULL, "vbus");
-}
-
 static irqreturn_t twl4030_usb_irq(int irq, void *_twl)
 {
        struct twl4030_usb *twl = _twl;
-       u8 val;
+       int status;
 
 #ifdef CONFIG_LOCKDEP
        /* WORKAROUND for lockdep forcing IRQF_DISABLED on us, which
@@ -542,27 +544,28 @@ static irqreturn_t twl4030_usb_irq(int irq, void *_twl)
        local_irq_enable();
 #endif
 
-       /* FIXME stop accessing PWR_EDR1 ... if nothing else, we
-        * know which edges we told the IRQ to trigger on.  And
-        * there seem to be OTG_specific registers and irqs that
-        * provide the right info without guessing like this:
-        * USB_INT_STS, ID_STATUS, STS_HW_CONDITIONS, etc.
-        */
-
-       /* action based on cable attach or detach */
-       WARN_ON(twl4030_i2c_read_u8(TWL4030_MODULE_INT,
-                               &val, REG_PWR_EDR1) < 0);
-
-       if (val & USB_PRES_RISING) {
-               twl4030_phy_resume(twl);
-               twl4030charger_usb_en(1);
-               twl->vbus = 1;
-       } else {
-               twl4030charger_usb_en(0);
-               twl->vbus = 0;
-               twl4030_phy_suspend(twl, 0);
+       status = twl4030_usb_linkstat(twl);
+       if (status != USB_LINK_UNKNOWN) {
+
+               /* FIXME add a set_power() method so that B-devices can
+                * configure the charger appropriately.  It's not always
+                * correct to consume VBUS power, and how much current to
+                * consume is a function of the USB configuration chosen
+                * by the host.
+                *
+                * REVISIT usb_gadget_vbus_connect(...) as needed, ditto
+                * its disconnect() sibling, when changing to/from the
+                * USB_LINK_VBUS state.  musb_hdrc won't care until it
+                * starts to handle softconnect right.
+                */
+               twl4030charger_usb_en(status == USB_LINK_VBUS);
+
+               if (status == USB_LINK_NONE)
+                       twl4030_phy_suspend(twl, 0);
+               else
+                       twl4030_phy_resume(twl);
        }
-       schedule_work(&twl->irq_work);
+       sysfs_notify(&twl->dev->kobj, NULL, "vbus");
 
        return IRQ_HANDLED;
 }
@@ -608,11 +611,6 @@ static int twl4030_set_peripheral(struct otg_transceiver *x,
 
        twl->otg.state = OTG_STATE_B_IDLE;
 
-       twl4030_usb_set_bits(twl, USB_INT_EN_RISE,
-                       USB_INT_SESSVALID | USB_INT_VBUSVALID);
-       twl4030_usb_set_bits(twl, USB_INT_EN_FALL,
-                       USB_INT_SESSVALID | USB_INT_VBUSVALID);
-
        return 0;
 }
 
@@ -639,8 +637,7 @@ static int twl4030_set_host(struct otg_transceiver *x, struct usb_bus *host)
        twl4030_usb_set_bits(twl, TWL4030_OTG_CTRL,
                        TWL4030_OTG_CTRL_DMPULLDOWN
                                | TWL4030_OTG_CTRL_DPPULLDOWN);
-       twl4030_usb_set_bits(twl, USB_INT_EN_RISE, USB_INT_IDGND);
-       twl4030_usb_set_bits(twl, USB_INT_EN_FALL, USB_INT_IDGND);
+
        twl4030_usb_set_bits(twl, FUNC_CTRL, FUNC_CTRL_SUSPENDM);
        twl4030_usb_set_bits(twl, TWL4030_OTG_CTRL, TWL4030_OTG_CTRL_DRVVBUS);
 
@@ -651,8 +648,7 @@ static int __init twl4030_usb_probe(struct platform_device *pdev)
 {
        struct twl4030_usb_data *pdata = pdev->dev.platform_data;
        struct twl4030_usb      *twl;
-       int status;
-       u8                      vbus;
+       int                     status;
 
        twl = kzalloc(sizeof *twl, GFP_KERNEL);
        if (!twl)
@@ -663,26 +659,38 @@ static int __init twl4030_usb_probe(struct platform_device *pdev)
                return -EINVAL;
        }
 
-       WARN_ON(twl4030_i2c_read_u8(TWL4030_MODULE_INT,
-                               &vbus, REG_PWR_EDR1) < 0);
-       vbus &= USB_PRES_RISING;
-
        twl->dev                = &pdev->dev;
        twl->irq                = platform_get_irq(pdev, 0);
+       twl->otg.dev            = twl->dev;
+       twl->otg.label          = "twl4030";
        twl->otg.set_host       = twl4030_set_host;
        twl->otg.set_peripheral = twl4030_set_peripheral;
        twl->otg.set_suspend    = twl4030_set_suspend;
        twl->usb_mode           = pdata->usb_mode;
-       twl->vbus               = vbus ? 1 : 0;
+       twl->asleep             = 1;
 
        /* init spinlock for workqueue */
        spin_lock_init(&twl->lock);
 
-       /* init irq workqueue before request_irq */
-       INIT_WORK(&twl->irq_work, twl4030_usb_irq_work);
+       twl4030_usb_ldo_init(twl);
+       otg_set_transceiver(&twl->otg);
+
+       platform_set_drvdata(pdev, twl);
+       if (device_create_file(&pdev->dev, &dev_attr_vbus))
+               dev_warn(&pdev->dev, "could not create sysfs file\n");
 
+       /* Our job is to use irqs and status from the power module
+        * to keep the transceiver disabled when nothing's connected.
+        *
+        * FIXME we actually shouldn't start enabling it until the
+        * USB controller drivers have said they're ready, by calling
+        * set_host() and/or set_peripheral() ... OTG_capable boards
+        * need both handles, otherwise just one suffices.
+        */
        twl->irq_enabled = true;
-       status = request_irq(twl->irq, twl4030_usb_irq, 0, "twl4030_usb", twl);
+       status = request_irq(twl->irq, twl4030_usb_irq,
+                       IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
+                       "twl4030_usb", twl);
        if (status < 0) {
                dev_dbg(&pdev->dev, "can't get IRQ %d, err %d\n",
                        twl->irq, status);
@@ -690,25 +698,17 @@ static int __init twl4030_usb_probe(struct platform_device *pdev)
                return status;
        }
 
-       twl4030_usb_ldo_init(twl);
-       twl4030_phy_power(twl, 1);
-       twl4030_i2c_access(twl, 1);
-       twl4030_usb_set_mode(twl, twl->usb_mode);
-
-       twl->asleep = 0;
-
-       if (twl->usb_mode == T2_USB_MODE_ULPI) {
-               twl4030_i2c_access(twl, 0);
-               twl4030_phy_suspend(twl, 0);
-       }
+       /* The IRQ handler just handles changes from the previous states
+        * of the ID and VBUS pins ... in probe() we must initialize that
+        * previous state.  The easy way:  fake an IRQ.
+        *
+        * REVISIT:  a real IRQ might have happened already, if PREEMPT is
+        * enabled.  Else the IRQ may not yet be configured or enabled,
+        * because of scheduling delays.
+        */
+       twl4030_usb_irq(twl->irq, twl);
 
-       otg_set_transceiver(&twl->otg);
-       platform_set_drvdata(pdev, twl);
        dev_info(&pdev->dev, "Initialized TWL4030 USB module\n");
-
-       if (device_create_file(&pdev->dev, &dev_attr_vbus))
-               dev_warn(&pdev->dev, "could not create sysfs file\n");
-
        return 0;
 }
 
@@ -717,7 +717,6 @@ static int __exit twl4030_usb_remove(struct platform_device *pdev)
        struct twl4030_usb *twl = platform_get_drvdata(pdev);
        int val;
 
-       usb_irq_disable(twl);
        free_irq(twl->irq, twl);
        device_remove_file(twl->dev, &dev_attr_vbus);