Commit | Line | Data |
---|---|---|
f6fb9ec0 HG |
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> | |
cb296846 | 21 | #include <linux/pm_runtime.h> |
2be1fb64 | 22 | #include <linux/property.h> |
f6fb9ec0 HG |
23 | #include <linux/usb/role.h> |
24 | ||
25 | /* register definition */ | |
26 | #define DUAL_ROLE_CFG0 0x68 | |
27 | #define SW_VBUS_VALID BIT(24) | |
28 | #define SW_IDPIN_EN BIT(21) | |
29 | #define SW_IDPIN BIT(20) | |
2be1fb64 SG |
30 | #define SW_SWITCH_EN BIT(16) |
31 | ||
32 | #define DRD_CONFIG_DYNAMIC 0 | |
33 | #define DRD_CONFIG_STATIC_HOST 1 | |
34 | #define DRD_CONFIG_STATIC_DEVICE 2 | |
35 | #define DRD_CONFIG_MASK 3 | |
f6fb9ec0 HG |
36 | |
37 | #define DUAL_ROLE_CFG1 0x6c | |
38 | #define HOST_MODE BIT(29) | |
39 | ||
40 | #define DUAL_ROLE_CFG1_POLL_TIMEOUT 1000 | |
41 | ||
42 | #define DRV_NAME "intel_xhci_usb_sw" | |
43 | ||
44 | struct intel_xhci_usb_data { | |
bce3052f | 45 | struct device *dev; |
f6fb9ec0 HG |
46 | struct usb_role_switch *role_sw; |
47 | void __iomem *base; | |
2be1fb64 | 48 | bool enable_sw_switch; |
f6fb9ec0 HG |
49 | }; |
50 | ||
d2a90ebb HK |
51 | static const struct software_node intel_xhci_usb_node = { |
52 | "intel-xhci-usb-sw", | |
53 | }; | |
54 | ||
bce3052f HK |
55 | static int intel_xhci_usb_set_role(struct usb_role_switch *sw, |
56 | enum usb_role role) | |
f6fb9ec0 | 57 | { |
bce3052f | 58 | struct intel_xhci_usb_data *data = usb_role_switch_get_drvdata(sw); |
f6fb9ec0 HG |
59 | unsigned long timeout; |
60 | acpi_status status; | |
61 | u32 glk, val; | |
2be1fb64 | 62 | u32 drd_config = DRD_CONFIG_DYNAMIC; |
f6fb9ec0 HG |
63 | |
64 | /* | |
65 | * On many CHT devices ACPI event (_AEI) handlers read / modify / | |
66 | * write the cfg0 register, just like we do. Take the ACPI lock | |
67 | * to avoid us racing with the AML code. | |
68 | */ | |
69 | status = acpi_acquire_global_lock(ACPI_WAIT_FOREVER, &glk); | |
70 | if (ACPI_FAILURE(status) && status != AE_NOT_CONFIGURED) { | |
bce3052f | 71 | dev_err(data->dev, "Error could not acquire lock\n"); |
f6fb9ec0 HG |
72 | return -EIO; |
73 | } | |
74 | ||
bce3052f | 75 | pm_runtime_get_sync(data->dev); |
cb296846 | 76 | |
2be1fb64 SG |
77 | /* |
78 | * Set idpin value as requested. | |
79 | * Since some devices rely on firmware setting DRD_CONFIG and | |
80 | * SW_SWITCH_EN bits to be zero for role switch, | |
81 | * do not set these bits for those devices. | |
82 | */ | |
f6fb9ec0 HG |
83 | val = readl(data->base + DUAL_ROLE_CFG0); |
84 | switch (role) { | |
85 | case USB_ROLE_NONE: | |
86 | val |= SW_IDPIN; | |
87 | val &= ~SW_VBUS_VALID; | |
2be1fb64 | 88 | drd_config = DRD_CONFIG_DYNAMIC; |
f6fb9ec0 HG |
89 | break; |
90 | case USB_ROLE_HOST: | |
91 | val &= ~SW_IDPIN; | |
92 | val &= ~SW_VBUS_VALID; | |
2be1fb64 | 93 | drd_config = DRD_CONFIG_STATIC_HOST; |
f6fb9ec0 HG |
94 | break; |
95 | case USB_ROLE_DEVICE: | |
96 | val |= SW_IDPIN; | |
97 | val |= SW_VBUS_VALID; | |
2be1fb64 | 98 | drd_config = DRD_CONFIG_STATIC_DEVICE; |
f6fb9ec0 HG |
99 | break; |
100 | } | |
101 | val |= SW_IDPIN_EN; | |
2be1fb64 SG |
102 | if (data->enable_sw_switch) { |
103 | val &= ~DRD_CONFIG_MASK; | |
104 | val |= SW_SWITCH_EN | drd_config; | |
105 | } | |
f6fb9ec0 HG |
106 | writel(val, data->base + DUAL_ROLE_CFG0); |
107 | ||
108 | acpi_release_global_lock(glk); | |
109 | ||
110 | /* In most case it takes about 600ms to finish mode switching */ | |
111 | timeout = jiffies + msecs_to_jiffies(DUAL_ROLE_CFG1_POLL_TIMEOUT); | |
112 | ||
113 | /* Polling on CFG1 register to confirm mode switch.*/ | |
114 | do { | |
115 | val = readl(data->base + DUAL_ROLE_CFG1); | |
cb296846 | 116 | if (!!(val & HOST_MODE) == (role == USB_ROLE_HOST)) { |
bce3052f | 117 | pm_runtime_put(data->dev); |
f6fb9ec0 | 118 | return 0; |
cb296846 | 119 | } |
f6fb9ec0 HG |
120 | |
121 | /* Interval for polling is set to about 5 - 10 ms */ | |
122 | usleep_range(5000, 10000); | |
123 | } while (time_before(jiffies, timeout)); | |
124 | ||
bce3052f | 125 | pm_runtime_put(data->dev); |
cb296846 | 126 | |
bce3052f | 127 | dev_warn(data->dev, "Timeout waiting for role-switch\n"); |
f6fb9ec0 HG |
128 | return -ETIMEDOUT; |
129 | } | |
130 | ||
bce3052f | 131 | static enum usb_role intel_xhci_usb_get_role(struct usb_role_switch *sw) |
f6fb9ec0 | 132 | { |
bce3052f | 133 | struct intel_xhci_usb_data *data = usb_role_switch_get_drvdata(sw); |
f6fb9ec0 HG |
134 | enum usb_role role; |
135 | u32 val; | |
136 | ||
bce3052f | 137 | pm_runtime_get_sync(data->dev); |
f6fb9ec0 | 138 | val = readl(data->base + DUAL_ROLE_CFG0); |
bce3052f | 139 | pm_runtime_put(data->dev); |
f6fb9ec0 HG |
140 | |
141 | if (!(val & SW_IDPIN)) | |
142 | role = USB_ROLE_HOST; | |
143 | else if (val & SW_VBUS_VALID) | |
144 | role = USB_ROLE_DEVICE; | |
145 | else | |
146 | role = USB_ROLE_NONE; | |
147 | ||
148 | return role; | |
149 | } | |
150 | ||
f6fb9ec0 HG |
151 | static int intel_xhci_usb_probe(struct platform_device *pdev) |
152 | { | |
d2a90ebb | 153 | struct usb_role_switch_desc sw_desc = { }; |
f6fb9ec0 HG |
154 | struct device *dev = &pdev->dev; |
155 | struct intel_xhci_usb_data *data; | |
156 | struct resource *res; | |
d2a90ebb | 157 | int ret; |
f6fb9ec0 HG |
158 | |
159 | data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); | |
160 | if (!data) | |
161 | return -ENOMEM; | |
162 | ||
163 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
548f4726 WY |
164 | if (!res) |
165 | return -EINVAL; | |
4bdc0d67 | 166 | data->base = devm_ioremap(dev, res->start, resource_size(res)); |
97eba326 WY |
167 | if (!data->base) |
168 | return -ENOMEM; | |
f6fb9ec0 | 169 | |
f6fb9ec0 HG |
170 | platform_set_drvdata(pdev, data); |
171 | ||
d2a90ebb HK |
172 | ret = software_node_register(&intel_xhci_usb_node); |
173 | if (ret) | |
174 | return ret; | |
175 | ||
176 | sw_desc.set = intel_xhci_usb_set_role, | |
177 | sw_desc.get = intel_xhci_usb_get_role, | |
178 | sw_desc.allow_userspace_control = true, | |
179 | sw_desc.fwnode = software_node_fwnode(&intel_xhci_usb_node); | |
bce3052f | 180 | sw_desc.driver_data = data; |
d2a90ebb | 181 | |
bce3052f | 182 | data->dev = dev; |
2be1fb64 SG |
183 | data->enable_sw_switch = !device_property_read_bool(dev, |
184 | "sw_switch_disable"); | |
185 | ||
f6fb9ec0 | 186 | data->role_sw = usb_role_switch_register(dev, &sw_desc); |
d2a90ebb HK |
187 | if (IS_ERR(data->role_sw)) { |
188 | fwnode_handle_put(sw_desc.fwnode); | |
f6fb9ec0 | 189 | return PTR_ERR(data->role_sw); |
d2a90ebb | 190 | } |
f6fb9ec0 | 191 | |
cb296846 HK |
192 | pm_runtime_set_active(dev); |
193 | pm_runtime_enable(dev); | |
194 | ||
f6fb9ec0 HG |
195 | return 0; |
196 | } | |
197 | ||
198 | static int intel_xhci_usb_remove(struct platform_device *pdev) | |
199 | { | |
200 | struct intel_xhci_usb_data *data = platform_get_drvdata(pdev); | |
201 | ||
009b1948 WAZ |
202 | pm_runtime_disable(&pdev->dev); |
203 | ||
f6fb9ec0 | 204 | usb_role_switch_unregister(data->role_sw); |
d2a90ebb HK |
205 | fwnode_handle_put(software_node_fwnode(&intel_xhci_usb_node)); |
206 | ||
f6fb9ec0 HG |
207 | return 0; |
208 | } | |
209 | ||
210 | static const struct platform_device_id intel_xhci_usb_table[] = { | |
211 | { .name = DRV_NAME }, | |
212 | {} | |
213 | }; | |
214 | MODULE_DEVICE_TABLE(platform, intel_xhci_usb_table); | |
215 | ||
216 | static struct platform_driver intel_xhci_usb_driver = { | |
217 | .driver = { | |
218 | .name = DRV_NAME, | |
219 | }, | |
220 | .id_table = intel_xhci_usb_table, | |
221 | .probe = intel_xhci_usb_probe, | |
222 | .remove = intel_xhci_usb_remove, | |
223 | }; | |
224 | ||
225 | module_platform_driver(intel_xhci_usb_driver); | |
226 | ||
227 | MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); | |
228 | MODULE_DESCRIPTION("Intel XHCI USB role switch driver"); | |
229 | MODULE_LICENSE("GPL"); |