summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHans de Goede <hdegoede@redhat.com>2018-03-20 08:57:10 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2018-03-22 08:49:27 -0400
commitf6fb9ec02be1c1718596622263a88ff5490d2e95 (patch)
treeb81b1094f5218b7ed70069e985e6ee6e984b63a8
parentfa31b3cb2ae143aa6e26974fcbe75689da60bdbe (diff)
usb: roles: Add Intel xHCI USB role switch driver
Various Intel SoCs (Cherry Trail, Broxton and others) have an internal USB role switch for swiching the OTG USB data lines between the xHCI host controller and the dwc3 gadget controller. Note on some Cherry Trail systems there is ACPI/AML code listening to edge interrupts on the id-pin (through an _AIE ACPI method) and switching the role between ROLE_HOST and ROLE_NONE based on the id-pin. Note it does not set the role to ROLE_DEVICE, because device-mode is usually not used under Windows. The presence of AML code which modifies the cfg0 reg (on some systems) means that our read/write/modify of cfg0 may race with the AML code doing the same to avoid this we take the global ACPI lock while doing the read/write/modify. Signed-off-by: Hans de Goede <hdegoede@redhat.com> Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com> Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r--MAINTAINERS6
-rw-r--r--drivers/usb/Kconfig2
-rw-r--r--drivers/usb/Makefile2
-rw-r--r--drivers/usb/roles/Kconfig14
-rw-r--r--drivers/usb/roles/Makefile1
-rw-r--r--drivers/usb/roles/intel-xhci-usb-role-switch.c192
6 files changed, 217 insertions, 0 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index 2bee7ac161e8..d5065cf747e3 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14408,6 +14408,12 @@ S: Maintained
14408F: Documentation/hid/hiddev.txt 14408F: Documentation/hid/hiddev.txt
14409F: drivers/hid/usbhid/ 14409F: drivers/hid/usbhid/
14410 14410
14411USB INTEL XHCI ROLE MUX DRIVER
14412M: Hans de Goede <hdegoede@redhat.com>
14413L: linux-usb@vger.kernel.org
14414S: Maintained
14415F: drivers/usb/roles/intel-xhci-usb-role-switch.c
14416
14411USB ISP116X DRIVER 14417USB ISP116X DRIVER
14412M: Olav Kongas <ok@artecdesign.ee> 14418M: Olav Kongas <ok@artecdesign.ee>
14413L: linux-usb@vger.kernel.org 14419L: linux-usb@vger.kernel.org
diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig
index f278958e04ca..75f7fb151f71 100644
--- a/drivers/usb/Kconfig
+++ b/drivers/usb/Kconfig
@@ -171,6 +171,8 @@ source "drivers/usb/gadget/Kconfig"
171 171
172source "drivers/usb/typec/Kconfig" 172source "drivers/usb/typec/Kconfig"
173 173
174source "drivers/usb/roles/Kconfig"
175
174config USB_LED_TRIG 176config USB_LED_TRIG
175 bool "USB LED Triggers" 177 bool "USB LED Triggers"
176 depends on LEDS_CLASS && LEDS_TRIGGERS 178 depends on LEDS_CLASS && LEDS_TRIGGERS
diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile
index 060643a1b5c8..7d1b8c82b208 100644
--- a/drivers/usb/Makefile
+++ b/drivers/usb/Makefile
@@ -65,3 +65,5 @@ obj-$(CONFIG_USB_COMMON) += common/
65obj-$(CONFIG_USBIP_CORE) += usbip/ 65obj-$(CONFIG_USBIP_CORE) += usbip/
66 66
67obj-$(CONFIG_TYPEC) += typec/ 67obj-$(CONFIG_TYPEC) += typec/
68
69obj-$(CONFIG_USB_ROLE_SWITCH) += roles/
diff --git a/drivers/usb/roles/Kconfig b/drivers/usb/roles/Kconfig
new file mode 100644
index 000000000000..f5a5e6f79f1b
--- /dev/null
+++ b/drivers/usb/roles/Kconfig
@@ -0,0 +1,14 @@
1if USB_ROLE_SWITCH
2
3config USB_ROLES_INTEL_XHCI
4 tristate "Intel XHCI USB Role Switch"
5 depends on ACPI && X86
6 help
7 Driver for the internal USB role switch for switching the USB data
8 lines between the xHCI host controller and the dwc3 gadget controller
9 found on various Intel SoCs.
10
11 To compile the driver as a module, choose M here: the module will
12 be called intel-xhci-usb-role-switch.
13
14endif # USB_ROLE_SWITCH
diff --git a/drivers/usb/roles/Makefile b/drivers/usb/roles/Makefile
new file mode 100644
index 000000000000..e44b179ba275
--- /dev/null
+++ b/drivers/usb/roles/Makefile
@@ -0,0 +1 @@
obj-$(CONFIG_USB_ROLES_INTEL_XHCI) += intel-xhci-usb-role-switch.o
diff --git a/drivers/usb/roles/intel-xhci-usb-role-switch.c b/drivers/usb/roles/intel-xhci-usb-role-switch.c
new file mode 100644
index 000000000000..58c1b60a33c1
--- /dev/null
+++ b/drivers/usb/roles/intel-xhci-usb-role-switch.c
@@ -0,0 +1,192 @@
1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Intel XHCI (Cherry Trail, Broxton and others) USB OTG role switch driver
4 *
5 * Copyright (c) 2016-2017 Hans de Goede <hdegoede@redhat.com>
6 *
7 * Loosely based on android x86 kernel code which is:
8 *
9 * Copyright (C) 2014 Intel Corp.
10 *
11 * Author: Wu, Hao
12 */
13
14#include <linux/acpi.h>
15#include <linux/delay.h>
16#include <linux/err.h>
17#include <linux/io.h>
18#include <linux/kernel.h>
19#include <linux/module.h>
20#include <linux/platform_device.h>
21#include <linux/usb/role.h>
22
23/* register definition */
24#define DUAL_ROLE_CFG0 0x68
25#define SW_VBUS_VALID BIT(24)
26#define SW_IDPIN_EN BIT(21)
27#define SW_IDPIN BIT(20)
28
29#define DUAL_ROLE_CFG1 0x6c
30#define HOST_MODE BIT(29)
31
32#define DUAL_ROLE_CFG1_POLL_TIMEOUT 1000
33
34#define DRV_NAME "intel_xhci_usb_sw"
35
36struct intel_xhci_usb_data {
37 struct usb_role_switch *role_sw;
38 void __iomem *base;
39};
40
41struct intel_xhci_acpi_match {
42 const char *hid;
43 int hrv;
44};
45
46/*
47 * ACPI IDs for PMICs which do not support separate data and power role
48 * detection (USB ACA detection for micro USB OTG), we allow userspace to
49 * change the role manually on these.
50 */
51static const struct intel_xhci_acpi_match allow_userspace_ctrl_ids[] = {
52 { "INT33F4", 3 }, /* X-Powers AXP288 PMIC */
53};
54
55static int intel_xhci_usb_set_role(struct device *dev, enum usb_role role)
56{
57 struct intel_xhci_usb_data *data = dev_get_drvdata(dev);
58 unsigned long timeout;
59 acpi_status status;
60 u32 glk, val;
61
62 /*
63 * On many CHT devices ACPI event (_AEI) handlers read / modify /
64 * write the cfg0 register, just like we do. Take the ACPI lock
65 * to avoid us racing with the AML code.
66 */
67 status = acpi_acquire_global_lock(ACPI_WAIT_FOREVER, &glk);
68 if (ACPI_FAILURE(status) && status != AE_NOT_CONFIGURED) {
69 dev_err(dev, "Error could not acquire lock\n");
70 return -EIO;
71 }
72
73 /* Set idpin value as requested */
74 val = readl(data->base + DUAL_ROLE_CFG0);
75 switch (role) {
76 case USB_ROLE_NONE:
77 val |= SW_IDPIN;
78 val &= ~SW_VBUS_VALID;
79 break;
80 case USB_ROLE_HOST:
81 val &= ~SW_IDPIN;
82 val &= ~SW_VBUS_VALID;
83 break;
84 case USB_ROLE_DEVICE:
85 val |= SW_IDPIN;
86 val |= SW_VBUS_VALID;
87 break;
88 }
89 val |= SW_IDPIN_EN;
90
91 writel(val, data->base + DUAL_ROLE_CFG0);
92
93 acpi_release_global_lock(glk);
94
95 /* In most case it takes about 600ms to finish mode switching */
96 timeout = jiffies + msecs_to_jiffies(DUAL_ROLE_CFG1_POLL_TIMEOUT);
97
98 /* Polling on CFG1 register to confirm mode switch.*/
99 do {
100 val = readl(data->base + DUAL_ROLE_CFG1);
101 if (!!(val & HOST_MODE) == (role == USB_ROLE_HOST))
102 return 0;
103
104 /* Interval for polling is set to about 5 - 10 ms */
105 usleep_range(5000, 10000);
106 } while (time_before(jiffies, timeout));
107
108 dev_warn(dev, "Timeout waiting for role-switch\n");
109 return -ETIMEDOUT;
110}
111
112static enum usb_role intel_xhci_usb_get_role(struct device *dev)
113{
114 struct intel_xhci_usb_data *data = dev_get_drvdata(dev);
115 enum usb_role role;
116 u32 val;
117
118 val = readl(data->base + DUAL_ROLE_CFG0);
119
120 if (!(val & SW_IDPIN))
121 role = USB_ROLE_HOST;
122 else if (val & SW_VBUS_VALID)
123 role = USB_ROLE_DEVICE;
124 else
125 role = USB_ROLE_NONE;
126
127 return role;
128}
129
130static struct usb_role_switch_desc sw_desc = {
131 .set = intel_xhci_usb_set_role,
132 .get = intel_xhci_usb_get_role,
133};
134
135static int intel_xhci_usb_probe(struct platform_device *pdev)
136{
137 struct device *dev = &pdev->dev;
138 struct intel_xhci_usb_data *data;
139 struct resource *res;
140 int i;
141
142 data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
143 if (!data)
144 return -ENOMEM;
145
146 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
147 data->base = devm_ioremap_nocache(dev, res->start, resource_size(res));
148 if (IS_ERR(data->base))
149 return PTR_ERR(data->base);
150
151 for (i = 0; i < ARRAY_SIZE(allow_userspace_ctrl_ids); i++)
152 if (acpi_dev_present(allow_userspace_ctrl_ids[i].hid, "1",
153 allow_userspace_ctrl_ids[i].hrv))
154 sw_desc.allow_userspace_control = true;
155
156 platform_set_drvdata(pdev, data);
157
158 data->role_sw = usb_role_switch_register(dev, &sw_desc);
159 if (IS_ERR(data->role_sw))
160 return PTR_ERR(data->role_sw);
161
162 return 0;
163}
164
165static int intel_xhci_usb_remove(struct platform_device *pdev)
166{
167 struct intel_xhci_usb_data *data = platform_get_drvdata(pdev);
168
169 usb_role_switch_unregister(data->role_sw);
170 return 0;
171}
172
173static const struct platform_device_id intel_xhci_usb_table[] = {
174 { .name = DRV_NAME },
175 {}
176};
177MODULE_DEVICE_TABLE(platform, intel_xhci_usb_table);
178
179static struct platform_driver intel_xhci_usb_driver = {
180 .driver = {
181 .name = DRV_NAME,
182 },
183 .id_table = intel_xhci_usb_table,
184 .probe = intel_xhci_usb_probe,
185 .remove = intel_xhci_usb_remove,
186};
187
188module_platform_driver(intel_xhci_usb_driver);
189
190MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
191MODULE_DESCRIPTION("Intel XHCI USB role switch driver");
192MODULE_LICENSE("GPL");