usb: dwc2: gadget: Add support for extcon notification
authorDongwoo Lee <dwoo08.lee@samsung.com>
Tue, 10 Oct 2017 06:06:57 +0000 (15:06 +0900)
committerJaehoon Chung <jh80.chung@samsung.com>
Tue, 29 Jan 2019 02:25:32 +0000 (11:25 +0900)
To provide extcon notification for the device which has no separated
peripherals for recognizing and notifying cable connection, this patch
adds support for extcon notification into dwc2 gadget interrupt
handler. If we set 'g-extcon-notify' property to dts, gadget driver
will directly generate extcon event for cable connection.

Change-Id: I6fd12c026e3e867a31d25a157921abb34bbd98dd
Signed-off-by: Dongwoo Lee <dwoo08.lee@samsung.com>
drivers/usb/dwc2/core.h
drivers/usb/dwc2/gadget.c

index a6bcc7b57e33d94af5281351bc1f2740815220ec..cbe1b560b649cfa45334d940557383b8bc2125b3 100644 (file)
@@ -871,6 +871,13 @@ struct dwc2_hsotg {
        u32 g_rx_fifo_sz;
        u32 g_np_g_tx_fifo_sz;
        u32 g_tx_fifo_sz[MAX_EPS_CHANNELS];
+       u32 g_extcon_notify;
+
+#if IS_ENABLED(CONFIG_EXTCON)
+       struct extcon_dev *edev;
+       struct work_struct extcon_work;
+       unsigned int extcon_state;
+#endif /* CONFIG_EXTCON */
 #endif /* CONFIG_USB_DWC2_PERIPHERAL || CONFIG_USB_DWC2_DUAL_ROLE */
 };
 
index b16c06d9c4d0ccf5640b2417aa0e8fefd08d0946..c232f2dcee07775695d5cf41790282b21c95f0e3 100644 (file)
@@ -26,6 +26,7 @@
 #include <linux/io.h>
 #include <linux/slab.h>
 #include <linux/of_platform.h>
+#include <linux/extcon.h>
 
 #include <linux/usb/ch9.h>
 #include <linux/usb/gadget.h>
 #include "core.h"
 #include "hw.h"
 
+#if IS_ENABLED(CONFIG_EXTCON)
+static const unsigned int supported_cable[] = {
+       EXTCON_USB,
+       EXTCON_NONE,
+};
+
+static void dwc2_gadget_extcon_notify(struct dwc2_hsotg *hsotg, bool on)
+{
+       hsotg->extcon_state = on;
+       queue_work(system_power_efficient_wq, &hsotg->extcon_work);
+}
+
+static void dwc2_gadget_extcon_work(struct work_struct *work)
+{
+       struct dwc2_hsotg *hsotg = container_of(work, struct dwc2_hsotg,
+                                               extcon_work);
+
+       extcon_set_cable_state_(hsotg->edev, EXTCON_USB, hsotg->extcon_state);
+}
+
+static int dwc2_gadget_extcon_init(struct dwc2_hsotg *hsotg)
+{
+       struct device *dev = hsotg->dev;
+       struct extcon_dev *edev;
+       int ret;
+
+       if (!hsotg->g_extcon_notify)
+               return 0;
+
+       edev = devm_extcon_dev_allocate(dev, supported_cable);
+       if (IS_ERR(edev))
+               return -ENODEV;
+
+       ret = devm_extcon_dev_register(dev, edev);
+       if (ret)
+               return ret;
+
+       hsotg->edev = edev;
+
+       INIT_WORK(&hsotg->extcon_work, dwc2_gadget_extcon_work);
+
+       return 0;
+}
+#else
+static inline void dwc2_gadget_extcon_notify(struct dwc2_hsotg *hsotg, bool on)
+{
+}
+
+static inline int dwc2_gadget_extcon_init(struct dwc2_hsotg *hsotg)
+{
+       return 0;
+}
+#endif /* CONFIG_EXTCON */
+
 /* conversion functions */
 static inline struct dwc2_hsotg_req *our_req(struct usb_request *req)
 {
@@ -2504,12 +2559,31 @@ irq_retry:
 
                if (usb_status & GOTGCTL_BSESVLD && connected)
                        dwc2_hsotg_core_init_disconnected(hsotg, true);
+
+               if (hsotg->g_extcon_notify) {
+                       /*
+                        * USBRST interrupt can be generated by not only
+                        * disconnection but also other reason. So, we
+                        * should check the connection state with usb_status.
+                        * Fortunately, GOTGCTL_ASESVLD and GOTGCTL_BSESVLD
+                        * is activated only if the cable is connected. So,
+                        * we can recognize the disconnection situation by
+                        * checking both bits are not set.
+                        */
+                       u32 flags = GOTGCTL_ASESVLD | GOTGCTL_BSESVLD;
+
+                       if (!(usb_status & flags))
+                               dwc2_gadget_extcon_notify(hsotg, false);
+               }
        }
 
        if (gintsts & GINTSTS_ENUMDONE) {
                dwc2_writel(GINTSTS_ENUMDONE, hsotg->regs + GINTSTS);
 
                dwc2_hsotg_irq_enumdone(hsotg);
+
+               if (hsotg->g_extcon_notify)
+                       dwc2_gadget_extcon_notify(hsotg, true);
        }
 
        if (gintsts & (GINTSTS_OEPINT | GINTSTS_IEPINT)) {
@@ -3509,6 +3583,10 @@ static void dwc2_hsotg_of_probe(struct dwc2_hsotg *hsotg)
        /* Enable dma if requested in device tree */
        hsotg->g_using_dma = of_property_read_bool(np, "g-use-dma");
 
+#if IS_ENABLED(CONFIG_EXTCON)
+       hsotg->g_extcon_notify = of_property_read_bool(np, "g-extcon-notify");
+#endif
+
        /*
        * Register TX periodic fifo size per endpoint.
        * EP0 is excluded since it has no fifo configuration.
@@ -3579,6 +3657,12 @@ int dwc2_gadget_init(struct dwc2_hsotg *hsotg, int irq)
        else if (hsotg->dr_mode == USB_DR_MODE_PERIPHERAL)
                hsotg->op_state = OTG_STATE_B_PERIPHERAL;
 
+       ret = dwc2_gadget_extcon_init(hsotg);
+       if (ret) {
+               dev_err(dev, "failed to initialize extcon device");
+               return ret;
+       }
+
        /*
         * Force Device mode before initialization.
         * This allows correctly configuring fifo for device mode.