The main design flow is the same with msm otg driver, that is the id and
vbus interrupt are handled at core driver, others are handled by
individual drivers.

- At former design, when switch usb role from device->host, it will call
udc_stop, it will remove the gadget driver, so when switch role
from host->device, it can't add gadget driver any more.
At new design, when role switch occurs, the gadget just calls
usb_gadget_vbus_disconnect/usb_gadget_vbus_connect as well as
reset controller, it will not free any device/gadget structure

- Add vbus connect and disconnect to core interrupt handler, it can
notify udc driver by calling usb_gadget_vbus_disconnect
/usb_gadget_vbus_connect.

Signed-off-by: Peter Chen <peter.c...@freescale.com>
---
 drivers/usb/chipidea/bits.h |   10 +++
 drivers/usb/chipidea/ci.h   |    6 ++
 drivers/usb/chipidea/core.c |  188 ++++++++++++++++++++++++++++++++++++++----
 drivers/usb/chipidea/otg.c  |    1 +
 drivers/usb/chipidea/udc.c  |    2 +
 5 files changed, 189 insertions(+), 18 deletions(-)

diff --git a/drivers/usb/chipidea/bits.h b/drivers/usb/chipidea/bits.h
index 050de85..ba9c6ef 100644
--- a/drivers/usb/chipidea/bits.h
+++ b/drivers/usb/chipidea/bits.h
@@ -65,11 +65,21 @@
 #define OTGSC_ASVIS          BIT(18)
 #define OTGSC_BSVIS          BIT(19)
 #define OTGSC_BSEIS          BIT(20)
+#define OTGSC_1MSIS          BIT(21)
+#define OTGSC_DPIS           BIT(22)
 #define OTGSC_IDIE           BIT(24)
 #define OTGSC_AVVIE          BIT(25)
 #define OTGSC_ASVIE          BIT(26)
 #define OTGSC_BSVIE          BIT(27)
 #define OTGSC_BSEIE          BIT(28)
+#define OTGSC_1MSIE          BIT(29)
+#define OTGSC_DPIE           BIT(30)
+#define OTGSC_INT_EN_BITS      (OTGSC_IDIE | OTGSC_AVVIE | OTGSC_ASVIE \
+                               | OTGSC_BSVIE | OTGSC_BSEIE | OTGSC_1MSIE \
+                               | OTGSC_DPIE)
+#define OTGSC_INT_STATUS_BITS  (OTGSC_IDIS | OTGSC_AVVIS | OTGSC_ASVIS \
+                               | OTGSC_BSVIS | OTGSC_BSEIS | OTGSC_1MSIS \
+                               | OTGSC_DPIS)
 
 /* USBMODE */
 #define USBMODE_CM            (0x03UL <<  0)
diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h
index d738603..d32f932 100644
--- a/drivers/usb/chipidea/ci.h
+++ b/drivers/usb/chipidea/ci.h
@@ -139,6 +139,7 @@ struct ci13xxx {
        enum ci_role                    role;
        bool                            is_otg;
        struct work_struct              work;
+       struct delayed_work             dwork;
        struct workqueue_struct         *wq;
 
        struct dma_pool                 *qh_pool;
@@ -164,6 +165,11 @@ struct ci13xxx {
        bool                            global_phy;
        struct usb_phy                  *transceiver;
        struct usb_hcd                  *hcd;
+       /* events handled at ci_role_work */
+#define ID             0
+#define B_SESS_VALID   1
+       unsigned long events;
+       struct usb_otg                  otg;
 };
 
 static inline struct ci_role_driver *ci_role(struct ci13xxx *ci)
diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c
index f69d029..87544ad 100644
--- a/drivers/usb/chipidea/core.c
+++ b/drivers/usb/chipidea/core.c
@@ -73,6 +73,7 @@
 #include "bits.h"
 #include "host.h"
 #include "debug.h"
+#include "otg.h"
 
 /* Controller register map */
 static uintptr_t ci_regs_nolpm[] = {
@@ -264,25 +265,131 @@ static enum ci_role ci_otg_role(struct ci13xxx *ci)
        return role;
 }
 
+#define CI_WAIT_VBUS_STABLE_TIMEOUT 500
 /**
- * ci_role_work - perform role changing based on ID pin
- * @work: work struct
+ * ci_wait_vbus_stable
+ * When usb role switches, we need to turn on/off internal vbus
+ * regulaor, sometimes, the real vbus value may not lower fast
+ * enough due to capacitance, and we do want the vbus lower
+ * than 0.8v if it is device mode, and higher than 4.4v, if it
+ * is host mode.
+ *
+ * @low: true, Wait vbus lower than OTGSC_BSV
+ *     : false, Wait vbus higher than OTGSC_AVV
  */
-static void ci_role_work(struct work_struct *work)
+static void ci_wait_vbus_stable(struct ci13xxx *ci, bool low)
+{
+       unsigned long timeout;
+       u32 otgsc = hw_read(ci, OP_OTGSC, ~0);
+
+       timeout = jiffies + CI_WAIT_VBUS_STABLE_TIMEOUT;
+
+       if (low) {
+               while (otgsc & OTGSC_BSV) {
+                       if (time_after(jiffies, timeout)) {
+                               dev_err(ci->dev, "wait vbus lower than\
+                                               OTGSC_BSV timeout!\n");
+                               return;
+                       }
+                       msleep(20);
+                       otgsc = hw_read(ci, OP_OTGSC, ~0);
+               }
+       } else {
+               while (!(otgsc & OTGSC_AVV)) {
+                       if (time_after(jiffies, timeout)) {
+                               dev_err(ci->dev, "wait vbus higher than\
+                                               OTGSC_AVV timeout!\n");
+                               return;
+                       }
+                       msleep(20);
+                       otgsc = hw_read(ci, OP_OTGSC, ~0);
+               }
+
+       }
+}
+
+static void ci_handle_id_switch(struct ci13xxx *ci)
 {
-       struct ci13xxx *ci = container_of(work, struct ci13xxx, work);
        enum ci_role role = ci_otg_role(ci);
 
        if (role != ci->role) {
                dev_dbg(ci->dev, "switching from %s to %s\n",
                        ci_role(ci)->name, ci->roles[role]->name);
 
-               ci_role_stop(ci);
-               ci_role_start(ci, role);
-               enable_irq(ci->irq);
+               /* 1. Finish the current role */
+               if (ci->role == CI_ROLE_GADGET) {
+                       usb_gadget_vbus_disconnect(&ci->gadget);
+                       /* host doesn't care B_SESSION_VALID event */
+                       hw_write(ci, OP_OTGSC, OTGSC_BSVIE, ~OTGSC_BSVIE);
+                       hw_write(ci, OP_OTGSC, OTGSC_BSVIS, OTGSC_BSVIS);
+                       ci->role = CI_ROLE_END;
+                       /* reset controller */
+                       hw_device_reset(ci, USBMODE_CM_IDLE);
+               } else if (ci->role == CI_ROLE_HOST) {
+                       ci_role_stop(ci);
+                       /* reset controller */
+                       hw_device_reset(ci, USBMODE_CM_IDLE);
+               }
+
+               /* 2. Turn on/off vbus according to coming role */
+               if (ci_otg_role(ci) == CI_ROLE_GADGET) {
+                       otg_set_vbus(&ci->otg, false);
+                       ci_wait_vbus_stable(ci, true);
+               } else if (ci_otg_role(ci) == CI_ROLE_HOST) {
+                       otg_set_vbus(&ci->otg, true);
+                       ci_wait_vbus_stable(ci, false);
+               }
+
+               /* 3. Begin the new role */
+               if (ci_otg_role(ci) == CI_ROLE_GADGET) {
+                       ci->role = role;
+                       hw_write(ci, OP_OTGSC, OTGSC_BSVIS, OTGSC_BSVIS);
+                       hw_write(ci, OP_OTGSC, OTGSC_BSVIE, OTGSC_BSVIE);
+               } else if (ci_otg_role(ci) == CI_ROLE_HOST) {
+                       ci_role_start(ci, role);
+               }
        }
 }
 
+static void ci_handle_vbus_change(struct ci13xxx *ci)
+{
+       u32 otgsc = hw_read(ci, OP_OTGSC, ~0);
+
+       if (otgsc & OTGSC_BSV)
+               usb_gadget_vbus_connect(&ci->gadget);
+       else
+               usb_gadget_vbus_disconnect(&ci->gadget);
+}
+
+/**
+ * ci_otg_work - perform otg (vbus/id) event handle
+ * @work: work struct
+ */
+static void ci_otg_work(struct work_struct *work)
+{
+       struct ci13xxx *ci = container_of(work, struct ci13xxx, work);
+
+       if (test_bit(ID, &ci->events)) {
+               clear_bit(ID, &ci->events);
+               ci_handle_id_switch(ci);
+       } else if (test_bit(B_SESS_VALID, &ci->events)) {
+               clear_bit(B_SESS_VALID, &ci->events);
+               ci_handle_vbus_change(ci);
+       } else
+               dev_err(ci->dev, "unexpected event occurs at %s\n", __func__);
+
+       enable_irq(ci->irq);
+}
+
+static void ci_delayed_work(struct work_struct *work)
+{
+       struct delayed_work *dwork = to_delayed_work(work);
+       struct ci13xxx *ci = container_of(dwork, struct ci13xxx, dwork);
+
+       otg_set_vbus(&ci->otg, true);
+
+}
+
 static ssize_t show_role(struct device *dev, struct device_attribute *attr,
                         char *buf)
 {
@@ -321,19 +428,36 @@ static irqreturn_t ci_irq(int irq, void *data)
        irqreturn_t ret = IRQ_NONE;
        u32 otgsc = 0;
 
-       if (ci->is_otg)
-               otgsc = hw_read(ci, OP_OTGSC, ~0);
+       otgsc = hw_read(ci, OP_OTGSC, ~0);
 
-       if (ci->role != CI_ROLE_END)
-               ret = ci_role(ci)->irq(ci);
-
-       if (ci->is_otg && (otgsc & OTGSC_IDIS)) {
+       /*
+        * Handle id change interrupt, it indicates device/host function
+        * switch.
+        */
+       if (ci->is_otg && (otgsc & OTGSC_IDIE) && (otgsc & OTGSC_IDIS)) {
+               set_bit(ID, &ci->events);
                hw_write(ci, OP_OTGSC, OTGSC_IDIS, OTGSC_IDIS);
                disable_irq_nosync(ci->irq);
                queue_work(ci->wq, &ci->work);
-               ret = IRQ_HANDLED;
+               return IRQ_HANDLED;
+       }
+
+       /*
+        * Handle vbus change interrupt, it indicates device connection
+        * and disconnection events.
+        */
+       if ((otgsc & OTGSC_BSVIE) && (otgsc & OTGSC_BSVIS)) {
+               set_bit(B_SESS_VALID, &ci->events);
+               hw_write(ci, OP_OTGSC, OTGSC_BSVIS, OTGSC_BSVIS);
+               disable_irq_nosync(ci->irq);
+               queue_work(ci->wq, &ci->work);
+               return IRQ_HANDLED;
        }
 
+       /* Handle device/host interrupt */
+       if (ci->role != CI_ROLE_END)
+               ret = ci_role(ci)->irq(ci);
+
        return ret;
 }
 
@@ -397,6 +521,7 @@ static int __devinit ci_hdrc_probe(struct platform_device 
*pdev)
        struct resource *res;
        void __iomem    *base;
        int             ret;
+       u32             otgsc;
 
        if (!dev->platform_data) {
                dev_err(dev, "platform data missing\n");
@@ -421,6 +546,7 @@ static int __devinit ci_hdrc_probe(struct platform_device 
*pdev)
                return -ENOMEM;
        }
 
+       ci->events = 0;
        ci->dev = dev;
        ci->platdata = dev->platform_data;
        if (ci->platdata->phy)
@@ -442,12 +568,20 @@ static int __devinit ci_hdrc_probe(struct platform_device 
*pdev)
                return -ENODEV;
        }
 
-       INIT_WORK(&ci->work, ci_role_work);
+       INIT_WORK(&ci->work, ci_otg_work);
+       INIT_DELAYED_WORK(&ci->dwork, ci_delayed_work);
        ci->wq = create_singlethread_workqueue("ci_otg");
        if (!ci->wq) {
                dev_err(dev, "can't create workqueue\n");
                return -ENODEV;
        }
+       /* Disable all interrupts bits */
+       hw_write(ci, OP_USBINTR, 0xffffffff, 0);
+       hw_write(ci, OP_OTGSC, OTGSC_INT_EN_BITS, 0);
+
+       /* Clear all interrupts status bits*/
+       hw_write(ci, OP_USBSTS, 0xffffffff, 0xffffffff);
+       hw_write(ci, OP_OTGSC, OTGSC_INT_STATUS_BITS, OTGSC_INT_STATUS_BITS);
 
        /* initialize role(s) before the interrupt is requested */
        ret = ci_hdrc_host_init(ci);
@@ -465,6 +599,7 @@ static int __devinit ci_hdrc_probe(struct platform_device 
*pdev)
        }
 
        if (ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET]) {
+               dev_dbg(dev, "support otg\n");
                ci->is_otg = true;
                /* ID pin needs 1ms debouce time, we delay 2ms for safe */
                mdelay(2);
@@ -475,13 +610,29 @@ static int __devinit ci_hdrc_probe(struct platform_device 
*pdev)
                        : CI_ROLE_GADGET;
        }
 
+       if (ci->is_otg)
+               /* if otg is supported, create struct usb_otg */
+               ci_hdrc_otg_init(ci);
+
        ret = ci_role_start(ci, ci->role);
        if (ret) {
-               dev_err(dev, "can't start %s role\n", ci_role(ci)->name);
+               dev_err(dev, "can't start %s role, ret=%d\n",
+                               ci_role(ci)->name, ret);
                ret = -ENODEV;
                goto rm_wq;
        }
 
+       otgsc = hw_read(ci, OP_OTGSC, ~0);
+       /*
+        * if it is device mode:
+        * - Enable vbus detect
+        * - If it has already connected to host, notify udc
+        */
+       if (ci->role == CI_ROLE_GADGET) {
+               hw_write(ci, OP_OTGSC, OTGSC_BSVIE, OTGSC_BSVIE);
+               ci_handle_vbus_change(ci);
+       }
+
        platform_set_drvdata(pdev, ci);
        ret = request_irq(ci->irq, ci_irq, IRQF_SHARED, ci->platdata->name,
                          ci);
@@ -492,8 +643,9 @@ static int __devinit ci_hdrc_probe(struct platform_device 
*pdev)
        if (ret)
                goto rm_attr;
 
-       if (ci->is_otg)
-               hw_write(ci, OP_OTGSC, OTGSC_IDIE, OTGSC_IDIE);
+       /* Handle the situation that usb device at the MicroB to A cable */
+       if (ci->is_otg && !(otgsc & OTGSC_ID))
+               queue_delayed_work(ci->wq, &ci->dwork, msecs_to_jiffies(500));
 
        return ret;
 
diff --git a/drivers/usb/chipidea/otg.c b/drivers/usb/chipidea/otg.c
index 7dea3b3..01ae162 100644
--- a/drivers/usb/chipidea/otg.c
+++ b/drivers/usb/chipidea/otg.c
@@ -55,6 +55,7 @@ int ci_hdrc_otg_init(struct ci13xxx *ci)
        ci->otg.set_host = ci_otg_set_host;
        if (!IS_ERR_OR_NULL(ci->transceiver))
                ci->transceiver->otg = &ci->otg;
+       hw_write(ci, OP_OTGSC, OTGSC_IDIE, OTGSC_IDIE);
 
        return 0;
 }
diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c
index d214448..b52cb10 100644
--- a/drivers/usb/chipidea/udc.c
+++ b/drivers/usb/chipidea/udc.c
@@ -1371,6 +1371,7 @@ static int ci13xxx_vbus_session(struct usb_gadget 
*_gadget, int is_active)
                        pm_runtime_get_sync(&_gadget->dev);
                        hw_device_reset(ci, USBMODE_CM_DC);
                        hw_device_state(ci, ci->ep0out->qh.dma);
+                       dev_dbg(ci->dev, "Connected to host\n");
                } else {
                        hw_device_state(ci, 0);
                        if (ci->platdata->notify_event)
@@ -1378,6 +1379,7 @@ static int ci13xxx_vbus_session(struct usb_gadget 
*_gadget, int is_active)
                                CI13XXX_CONTROLLER_STOPPED_EVENT);
                        _gadget_stop_activity(&ci->gadget);
                        pm_runtime_put_sync(&_gadget->dev);
+                       dev_dbg(ci->dev, "disconnected from host\n");
                }
        }
 
-- 
1.7.0.4


--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to