#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)
{
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)) {
/* 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.
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.