From 05559f10ed797b79f7fa47313682c48919a2b111 Mon Sep 17 00:00:00 2001 From: Li Jun Date: Mon, 26 Aug 2019 18:25:12 +0800 Subject: usb: chipidea: add role switch class support 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 Signed-off-by: Peter Chen --- drivers/usb/chipidea/core.c | 83 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) (limited to 'drivers/usb/chipidea/core.c') diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 215c655295b8..98ee575ee500 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -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); -- cgit v1.2.3