diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/usb/Kconfig | 2 | ||||
-rw-r--r-- | drivers/usb/Makefile | 2 | ||||
-rw-r--r-- | drivers/usb/roles/Kconfig | 14 | ||||
-rw-r--r-- | drivers/usb/roles/Makefile | 1 | ||||
-rw-r--r-- | drivers/usb/roles/intel-xhci-usb-role-switch.c | 192 |
5 files changed, 211 insertions, 0 deletions
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 | ||
172 | source "drivers/usb/typec/Kconfig" | 172 | source "drivers/usb/typec/Kconfig" |
173 | 173 | ||
174 | source "drivers/usb/roles/Kconfig" | ||
175 | |||
174 | config USB_LED_TRIG | 176 | config 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/ | |||
65 | obj-$(CONFIG_USBIP_CORE) += usbip/ | 65 | obj-$(CONFIG_USBIP_CORE) += usbip/ |
66 | 66 | ||
67 | obj-$(CONFIG_TYPEC) += typec/ | 67 | obj-$(CONFIG_TYPEC) += typec/ |
68 | |||
69 | obj-$(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 @@ | |||
1 | if USB_ROLE_SWITCH | ||
2 | |||
3 | config 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 | |||
14 | endif # 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 | |||
36 | struct intel_xhci_usb_data { | ||
37 | struct usb_role_switch *role_sw; | ||
38 | void __iomem *base; | ||
39 | }; | ||
40 | |||
41 | struct 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 | */ | ||
51 | static const struct intel_xhci_acpi_match allow_userspace_ctrl_ids[] = { | ||
52 | { "INT33F4", 3 }, /* X-Powers AXP288 PMIC */ | ||
53 | }; | ||
54 | |||
55 | static 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 | |||
112 | static 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 | |||
130 | static struct usb_role_switch_desc sw_desc = { | ||
131 | .set = intel_xhci_usb_set_role, | ||
132 | .get = intel_xhci_usb_get_role, | ||
133 | }; | ||
134 | |||
135 | static 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 | |||
165 | static 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 | |||
173 | static const struct platform_device_id intel_xhci_usb_table[] = { | ||
174 | { .name = DRV_NAME }, | ||
175 | {} | ||
176 | }; | ||
177 | MODULE_DEVICE_TABLE(platform, intel_xhci_usb_table); | ||
178 | |||
179 | static 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 | |||
188 | module_platform_driver(intel_xhci_usb_driver); | ||
189 | |||
190 | MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); | ||
191 | MODULE_DESCRIPTION("Intel XHCI USB role switch driver"); | ||
192 | MODULE_LICENSE("GPL"); | ||