usb: chipidea: add role switch class support
authorLi Jun <jun.li@nxp.com>
Mon, 26 Aug 2019 10:25:12 +0000 (18:25 +0800)
committerPeter Chen <peter.chen@nxp.com>
Wed, 28 Aug 2019 02:39:53 +0000 (10:39 +0800)
USB role is fully controlled by usb role switch consumer(e.g. typec),
usb port can be at host mode(USB_ROLE_HOST), device mode connected to
host(USB_ROLE_DEVICE), or not connecting any partner(USB_ROLE_NONE).

Signed-off-by: Li Jun <jun.li@nxp.com>
Signed-off-by: Peter Chen <peter.chen@nxp.com>
drivers/usb/chipidea/Kconfig
drivers/usb/chipidea/ci.h
drivers/usb/chipidea/core.c
drivers/usb/chipidea/otg.c

index eb37ebfcb123cf6247801b1d748b5b591da25fd8..ae850b3fddf2c452c7f060cfa2b73dbb4659eade 100644 (file)
@@ -6,6 +6,7 @@ config USB_CHIPIDEA
        select EXTCON
        select RESET_CONTROLLER
        select USB_ULPI_BUS
+       select USB_ROLE_SWITCH
        help
          Say Y here if your system has a dual role high speed USB
          controller based on ChipIdea silicon IP. It supports:
index 6a2cc5cd0281d8d728fb4f083798b2ea6bf55cc8..6911aef500e98f3154235a5d7b85fe867e83c947 100644 (file)
@@ -16,6 +16,7 @@
 #include <linux/usb/gadget.h>
 #include <linux/usb/otg-fsm.h>
 #include <linux/usb/otg.h>
+#include <linux/usb/role.h>
 #include <linux/ulpi/interface.h>
 
 /******************************************************************************
@@ -217,6 +218,7 @@ struct ci_hdrc {
        ktime_t                         hr_timeouts[NUM_OTG_FSM_TIMERS];
        unsigned                        enabled_otg_timer_bits;
        enum otg_fsm_timer              next_otg_timer;
+       struct usb_role_switch          *role_switch;
        struct work_struct              work;
        struct workqueue_struct         *wq;
 
@@ -290,6 +292,16 @@ static inline void ci_role_stop(struct ci_hdrc *ci)
        ci->roles[role]->stop(ci);
 }
 
+static inline enum usb_role ci_role_to_usb_role(struct ci_hdrc *ci)
+{
+       if (ci->role == CI_ROLE_HOST)
+               return USB_ROLE_HOST;
+       else if (ci->role == CI_ROLE_GADGET && ci->vbus_active)
+               return USB_ROLE_DEVICE;
+       else
+               return USB_ROLE_NONE;
+}
+
 /**
  * hw_read_id_reg: reads from a identification register
  * @ci: the controller
index 215c655295b895f3646de9925bf54b7f7e996e24..98ee575ee500eb04ef753dabcea10f3ef2eaff16 100644 (file)
@@ -600,6 +600,71 @@ static int ci_cable_notifier(struct notifier_block *nb, unsigned long event,
        return NOTIFY_DONE;
 }
 
+static enum usb_role ci_usb_role_switch_get(struct device *dev)
+{
+       struct ci_hdrc *ci = dev_get_drvdata(dev);
+       enum usb_role role;
+       unsigned long flags;
+
+       spin_lock_irqsave(&ci->lock, flags);
+       role = ci_role_to_usb_role(ci);
+       spin_unlock_irqrestore(&ci->lock, flags);
+
+       return role;
+}
+
+static int ci_usb_role_switch_set(struct device *dev, enum usb_role role)
+{
+       struct ci_hdrc *ci = dev_get_drvdata(dev);
+       struct ci_hdrc_cable *cable = NULL;
+       enum usb_role current_role = ci_role_to_usb_role(ci);
+       unsigned long flags;
+
+       if (current_role == role)
+               return 0;
+
+       pm_runtime_get_sync(ci->dev);
+       /* Stop current role */
+       spin_lock_irqsave(&ci->lock, flags);
+       if (current_role == USB_ROLE_DEVICE)
+               cable = &ci->platdata->vbus_extcon;
+       else if (current_role == USB_ROLE_HOST)
+               cable = &ci->platdata->id_extcon;
+
+       if (cable) {
+               cable->changed = true;
+               cable->connected = false;
+               ci_irq(ci->irq, ci);
+               spin_unlock_irqrestore(&ci->lock, flags);
+               if (ci->wq && role != USB_ROLE_NONE)
+                       flush_workqueue(ci->wq);
+               spin_lock_irqsave(&ci->lock, flags);
+       }
+
+       cable = NULL;
+
+       /* Start target role */
+       if (role == USB_ROLE_DEVICE)
+               cable = &ci->platdata->vbus_extcon;
+       else if (role == USB_ROLE_HOST)
+               cable = &ci->platdata->id_extcon;
+
+       if (cable) {
+               cable->changed = true;
+               cable->connected = true;
+               ci_irq(ci->irq, ci);
+       }
+       spin_unlock_irqrestore(&ci->lock, flags);
+       pm_runtime_put_sync(ci->dev);
+
+       return 0;
+}
+
+static struct usb_role_switch_desc ci_role_switch = {
+       .set = ci_usb_role_switch_set,
+       .get = ci_usb_role_switch_get,
+};
+
 static int ci_get_platdata(struct device *dev,
                struct ci_hdrc_platform_data *platdata)
 {
@@ -726,6 +791,9 @@ static int ci_get_platdata(struct device *dev,
                        cable->connected = false;
        }
 
+       if (device_property_read_bool(dev, "usb-role-switch"))
+               ci_role_switch.fwnode = dev->fwnode;
+
        platdata->pctl = devm_pinctrl_get(dev);
        if (!IS_ERR(platdata->pctl)) {
                struct pinctrl_state *p;
@@ -1047,6 +1115,15 @@ static int ci_hdrc_probe(struct platform_device *pdev)
                }
        }
 
+       if (ci_role_switch.fwnode) {
+               ci->role_switch = usb_role_switch_register(dev,
+                                       &ci_role_switch);
+               if (IS_ERR(ci->role_switch)) {
+                       ret = PTR_ERR(ci->role_switch);
+                       goto deinit_otg;
+               }
+       }
+
        if (ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET]) {
                if (ci->is_otg) {
                        ci->role = ci_otg_role(ci);
@@ -1105,6 +1182,9 @@ static int ci_hdrc_probe(struct platform_device *pdev)
        return 0;
 
 stop:
+       if (ci->role_switch)
+               usb_role_switch_unregister(ci->role_switch);
+deinit_otg:
        if (ci->is_otg && ci->roles[CI_ROLE_GADGET])
                ci_hdrc_otg_destroy(ci);
 deinit_gadget:
@@ -1123,6 +1203,9 @@ static int ci_hdrc_remove(struct platform_device *pdev)
 {
        struct ci_hdrc *ci = platform_get_drvdata(pdev);
 
+       if (ci->role_switch)
+               usb_role_switch_unregister(ci->role_switch);
+
        if (ci->supports_runtime_pm) {
                pm_runtime_get_sync(&pdev->dev);
                pm_runtime_disable(&pdev->dev);
index f25d4827fd49c41179326f517b8aeaa7999f54dd..fbfb02e05c97946f6b3b5622dcfa9da36ebdb2fd 100644 (file)
@@ -35,7 +35,7 @@ u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask)
         * detection overwrite OTGSC register value
         */
        cable = &ci->platdata->vbus_extcon;
-       if (!IS_ERR(cable->edev)) {
+       if (!IS_ERR(cable->edev) || ci->role_switch) {
                if (cable->changed)
                        val |= OTGSC_BSVIS;
                else
@@ -53,7 +53,7 @@ u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask)
        }
 
        cable = &ci->platdata->id_extcon;
-       if (!IS_ERR(cable->edev)) {
+       if (!IS_ERR(cable->edev) || ci->role_switch) {
                if (cable->changed)
                        val |= OTGSC_IDIS;
                else
@@ -83,7 +83,7 @@ void hw_write_otgsc(struct ci_hdrc *ci, u32 mask, u32 data)
        struct ci_hdrc_cable *cable;
 
        cable = &ci->platdata->vbus_extcon;
-       if (!IS_ERR(cable->edev)) {
+       if (!IS_ERR(cable->edev) || ci->role_switch) {
                if (data & mask & OTGSC_BSVIS)
                        cable->changed = false;
 
@@ -97,7 +97,7 @@ void hw_write_otgsc(struct ci_hdrc *ci, u32 mask, u32 data)
        }
 
        cable = &ci->platdata->id_extcon;
-       if (!IS_ERR(cable->edev)) {
+       if (!IS_ERR(cable->edev) || ci->role_switch) {
                if (data & mask & OTGSC_IDIS)
                        cable->changed = false;