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 <jun.li@nxp.com> Signed-off-by: Peter Chen <peter.chen@nxp.com>
This commit is contained in:
parent
71fcb8bdf5
commit
05559f10ed
@ -6,6 +6,7 @@ config USB_CHIPIDEA
|
|||||||
select EXTCON
|
select EXTCON
|
||||||
select RESET_CONTROLLER
|
select RESET_CONTROLLER
|
||||||
select USB_ULPI_BUS
|
select USB_ULPI_BUS
|
||||||
|
select USB_ROLE_SWITCH
|
||||||
help
|
help
|
||||||
Say Y here if your system has a dual role high speed USB
|
Say Y here if your system has a dual role high speed USB
|
||||||
controller based on ChipIdea silicon IP. It supports:
|
controller based on ChipIdea silicon IP. It supports:
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
#include <linux/usb/gadget.h>
|
#include <linux/usb/gadget.h>
|
||||||
#include <linux/usb/otg-fsm.h>
|
#include <linux/usb/otg-fsm.h>
|
||||||
#include <linux/usb/otg.h>
|
#include <linux/usb/otg.h>
|
||||||
|
#include <linux/usb/role.h>
|
||||||
#include <linux/ulpi/interface.h>
|
#include <linux/ulpi/interface.h>
|
||||||
|
|
||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
@ -217,6 +218,7 @@ struct ci_hdrc {
|
|||||||
ktime_t hr_timeouts[NUM_OTG_FSM_TIMERS];
|
ktime_t hr_timeouts[NUM_OTG_FSM_TIMERS];
|
||||||
unsigned enabled_otg_timer_bits;
|
unsigned enabled_otg_timer_bits;
|
||||||
enum otg_fsm_timer next_otg_timer;
|
enum otg_fsm_timer next_otg_timer;
|
||||||
|
struct usb_role_switch *role_switch;
|
||||||
struct work_struct work;
|
struct work_struct work;
|
||||||
struct workqueue_struct *wq;
|
struct workqueue_struct *wq;
|
||||||
|
|
||||||
@ -290,6 +292,16 @@ static inline void ci_role_stop(struct ci_hdrc *ci)
|
|||||||
ci->roles[role]->stop(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
|
* hw_read_id_reg: reads from a identification register
|
||||||
* @ci: the controller
|
* @ci: the controller
|
||||||
|
@ -600,6 +600,71 @@ static int ci_cable_notifier(struct notifier_block *nb, unsigned long event,
|
|||||||
return NOTIFY_DONE;
|
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,
|
static int ci_get_platdata(struct device *dev,
|
||||||
struct ci_hdrc_platform_data *platdata)
|
struct ci_hdrc_platform_data *platdata)
|
||||||
{
|
{
|
||||||
@ -726,6 +791,9 @@ static int ci_get_platdata(struct device *dev,
|
|||||||
cable->connected = false;
|
cable->connected = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (device_property_read_bool(dev, "usb-role-switch"))
|
||||||
|
ci_role_switch.fwnode = dev->fwnode;
|
||||||
|
|
||||||
platdata->pctl = devm_pinctrl_get(dev);
|
platdata->pctl = devm_pinctrl_get(dev);
|
||||||
if (!IS_ERR(platdata->pctl)) {
|
if (!IS_ERR(platdata->pctl)) {
|
||||||
struct pinctrl_state *p;
|
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->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET]) {
|
||||||
if (ci->is_otg) {
|
if (ci->is_otg) {
|
||||||
ci->role = ci_otg_role(ci);
|
ci->role = ci_otg_role(ci);
|
||||||
@ -1105,6 +1182,9 @@ static int ci_hdrc_probe(struct platform_device *pdev)
|
|||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
stop:
|
stop:
|
||||||
|
if (ci->role_switch)
|
||||||
|
usb_role_switch_unregister(ci->role_switch);
|
||||||
|
deinit_otg:
|
||||||
if (ci->is_otg && ci->roles[CI_ROLE_GADGET])
|
if (ci->is_otg && ci->roles[CI_ROLE_GADGET])
|
||||||
ci_hdrc_otg_destroy(ci);
|
ci_hdrc_otg_destroy(ci);
|
||||||
deinit_gadget:
|
deinit_gadget:
|
||||||
@ -1123,6 +1203,9 @@ static int ci_hdrc_remove(struct platform_device *pdev)
|
|||||||
{
|
{
|
||||||
struct ci_hdrc *ci = platform_get_drvdata(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) {
|
if (ci->supports_runtime_pm) {
|
||||||
pm_runtime_get_sync(&pdev->dev);
|
pm_runtime_get_sync(&pdev->dev);
|
||||||
pm_runtime_disable(&pdev->dev);
|
pm_runtime_disable(&pdev->dev);
|
||||||
|
@ -35,7 +35,7 @@ u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask)
|
|||||||
* detection overwrite OTGSC register value
|
* detection overwrite OTGSC register value
|
||||||
*/
|
*/
|
||||||
cable = &ci->platdata->vbus_extcon;
|
cable = &ci->platdata->vbus_extcon;
|
||||||
if (!IS_ERR(cable->edev)) {
|
if (!IS_ERR(cable->edev) || ci->role_switch) {
|
||||||
if (cable->changed)
|
if (cable->changed)
|
||||||
val |= OTGSC_BSVIS;
|
val |= OTGSC_BSVIS;
|
||||||
else
|
else
|
||||||
@ -53,7 +53,7 @@ u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask)
|
|||||||
}
|
}
|
||||||
|
|
||||||
cable = &ci->platdata->id_extcon;
|
cable = &ci->platdata->id_extcon;
|
||||||
if (!IS_ERR(cable->edev)) {
|
if (!IS_ERR(cable->edev) || ci->role_switch) {
|
||||||
if (cable->changed)
|
if (cable->changed)
|
||||||
val |= OTGSC_IDIS;
|
val |= OTGSC_IDIS;
|
||||||
else
|
else
|
||||||
@ -83,7 +83,7 @@ void hw_write_otgsc(struct ci_hdrc *ci, u32 mask, u32 data)
|
|||||||
struct ci_hdrc_cable *cable;
|
struct ci_hdrc_cable *cable;
|
||||||
|
|
||||||
cable = &ci->platdata->vbus_extcon;
|
cable = &ci->platdata->vbus_extcon;
|
||||||
if (!IS_ERR(cable->edev)) {
|
if (!IS_ERR(cable->edev) || ci->role_switch) {
|
||||||
if (data & mask & OTGSC_BSVIS)
|
if (data & mask & OTGSC_BSVIS)
|
||||||
cable->changed = false;
|
cable->changed = false;
|
||||||
|
|
||||||
@ -97,7 +97,7 @@ void hw_write_otgsc(struct ci_hdrc *ci, u32 mask, u32 data)
|
|||||||
}
|
}
|
||||||
|
|
||||||
cable = &ci->platdata->id_extcon;
|
cable = &ci->platdata->id_extcon;
|
||||||
if (!IS_ERR(cable->edev)) {
|
if (!IS_ERR(cable->edev) || ci->role_switch) {
|
||||||
if (data & mask & OTGSC_IDIS)
|
if (data & mask & OTGSC_IDIS)
|
||||||
cable->changed = false;
|
cable->changed = false;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user