diff options
Diffstat (limited to 'drivers/usb/roles/intel-xhci-usb-role-switch.c')
-rw-r--r-- | drivers/usb/roles/intel-xhci-usb-role-switch.c | 54 |
1 files changed, 45 insertions, 9 deletions
diff --git a/drivers/usb/roles/intel-xhci-usb-role-switch.c b/drivers/usb/roles/intel-xhci-usb-role-switch.c index 277de96181f9..409851306e99 100644 --- a/drivers/usb/roles/intel-xhci-usb-role-switch.c +++ b/drivers/usb/roles/intel-xhci-usb-role-switch.c @@ -19,6 +19,7 @@ #include <linux/module.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> +#include <linux/property.h> #include <linux/usb/role.h> /* register definition */ @@ -26,6 +27,12 @@ #define SW_VBUS_VALID BIT(24) #define SW_IDPIN_EN BIT(21) #define SW_IDPIN BIT(20) +#define SW_SWITCH_EN BIT(16) + +#define DRD_CONFIG_DYNAMIC 0 +#define DRD_CONFIG_STATIC_HOST 1 +#define DRD_CONFIG_STATIC_DEVICE 2 +#define DRD_CONFIG_MASK 3 #define DUAL_ROLE_CFG1 0x6c #define HOST_MODE BIT(29) @@ -37,6 +44,11 @@ struct intel_xhci_usb_data { struct usb_role_switch *role_sw; void __iomem *base; + bool enable_sw_switch; +}; + +static const struct software_node intel_xhci_usb_node = { + "intel-xhci-usb-sw", }; static int intel_xhci_usb_set_role(struct device *dev, enum usb_role role) @@ -45,6 +57,7 @@ static int intel_xhci_usb_set_role(struct device *dev, enum usb_role role) unsigned long timeout; acpi_status status; u32 glk, val; + u32 drd_config = DRD_CONFIG_DYNAMIC; /* * On many CHT devices ACPI event (_AEI) handlers read / modify / @@ -59,24 +72,35 @@ static int intel_xhci_usb_set_role(struct device *dev, enum usb_role role) pm_runtime_get_sync(dev); - /* Set idpin value as requested */ + /* + * Set idpin value as requested. + * Since some devices rely on firmware setting DRD_CONFIG and + * SW_SWITCH_EN bits to be zero for role switch, + * do not set these bits for those devices. + */ val = readl(data->base + DUAL_ROLE_CFG0); switch (role) { case USB_ROLE_NONE: val |= SW_IDPIN; val &= ~SW_VBUS_VALID; + drd_config = DRD_CONFIG_DYNAMIC; break; case USB_ROLE_HOST: val &= ~SW_IDPIN; val &= ~SW_VBUS_VALID; + drd_config = DRD_CONFIG_STATIC_HOST; break; case USB_ROLE_DEVICE: val |= SW_IDPIN; val |= SW_VBUS_VALID; + drd_config = DRD_CONFIG_STATIC_DEVICE; break; } val |= SW_IDPIN_EN; - + if (data->enable_sw_switch) { + val &= ~DRD_CONFIG_MASK; + val |= SW_SWITCH_EN | drd_config; + } writel(val, data->base + DUAL_ROLE_CFG0); acpi_release_global_lock(glk); @@ -122,17 +146,13 @@ static enum usb_role intel_xhci_usb_get_role(struct device *dev) return role; } -static const struct usb_role_switch_desc sw_desc = { - .set = intel_xhci_usb_set_role, - .get = intel_xhci_usb_get_role, - .allow_userspace_control = true, -}; - static int intel_xhci_usb_probe(struct platform_device *pdev) { + struct usb_role_switch_desc sw_desc = { }; struct device *dev = &pdev->dev; struct intel_xhci_usb_data *data; struct resource *res; + int ret; data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); if (!data) @@ -147,9 +167,23 @@ static int intel_xhci_usb_probe(struct platform_device *pdev) platform_set_drvdata(pdev, data); + ret = software_node_register(&intel_xhci_usb_node); + if (ret) + return ret; + + sw_desc.set = intel_xhci_usb_set_role, + sw_desc.get = intel_xhci_usb_get_role, + sw_desc.allow_userspace_control = true, + sw_desc.fwnode = software_node_fwnode(&intel_xhci_usb_node); + + data->enable_sw_switch = !device_property_read_bool(dev, + "sw_switch_disable"); + data->role_sw = usb_role_switch_register(dev, &sw_desc); - if (IS_ERR(data->role_sw)) + if (IS_ERR(data->role_sw)) { + fwnode_handle_put(sw_desc.fwnode); return PTR_ERR(data->role_sw); + } pm_runtime_set_active(dev); pm_runtime_enable(dev); @@ -164,6 +198,8 @@ static int intel_xhci_usb_remove(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); usb_role_switch_unregister(data->role_sw); + fwnode_handle_put(software_node_fwnode(&intel_xhci_usb_node)); + return 0; } |