GNU Linux-libre 4.19.286-gnu1
[releases.git] / drivers / usb / roles / intel-xhci-usb-role-switch.c
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/pm_runtime.h>
22 #include <linux/usb/role.h>
23
24 /* register definition */
25 #define DUAL_ROLE_CFG0                  0x68
26 #define SW_VBUS_VALID                   BIT(24)
27 #define SW_IDPIN_EN                     BIT(21)
28 #define SW_IDPIN                        BIT(20)
29
30 #define DUAL_ROLE_CFG1                  0x6c
31 #define HOST_MODE                       BIT(29)
32
33 #define DUAL_ROLE_CFG1_POLL_TIMEOUT     1000
34
35 #define DRV_NAME                        "intel_xhci_usb_sw"
36
37 struct intel_xhci_usb_data {
38         struct usb_role_switch *role_sw;
39         void __iomem *base;
40 };
41
42 static int intel_xhci_usb_set_role(struct device *dev, enum usb_role role)
43 {
44         struct intel_xhci_usb_data *data = dev_get_drvdata(dev);
45         unsigned long timeout;
46         acpi_status status;
47         u32 glk, val;
48
49         /*
50          * On many CHT devices ACPI event (_AEI) handlers read / modify /
51          * write the cfg0 register, just like we do. Take the ACPI lock
52          * to avoid us racing with the AML code.
53          */
54         status = acpi_acquire_global_lock(ACPI_WAIT_FOREVER, &glk);
55         if (ACPI_FAILURE(status) && status != AE_NOT_CONFIGURED) {
56                 dev_err(dev, "Error could not acquire lock\n");
57                 return -EIO;
58         }
59
60         pm_runtime_get_sync(dev);
61
62         /* Set idpin value as requested */
63         val = readl(data->base + DUAL_ROLE_CFG0);
64         switch (role) {
65         case USB_ROLE_NONE:
66                 val |= SW_IDPIN;
67                 val &= ~SW_VBUS_VALID;
68                 break;
69         case USB_ROLE_HOST:
70                 val &= ~SW_IDPIN;
71                 val &= ~SW_VBUS_VALID;
72                 break;
73         case USB_ROLE_DEVICE:
74                 val |= SW_IDPIN;
75                 val |= SW_VBUS_VALID;
76                 break;
77         }
78         val |= SW_IDPIN_EN;
79
80         writel(val, data->base + DUAL_ROLE_CFG0);
81
82         acpi_release_global_lock(glk);
83
84         /* In most case it takes about 600ms to finish mode switching */
85         timeout = jiffies + msecs_to_jiffies(DUAL_ROLE_CFG1_POLL_TIMEOUT);
86
87         /* Polling on CFG1 register to confirm mode switch.*/
88         do {
89                 val = readl(data->base + DUAL_ROLE_CFG1);
90                 if (!!(val & HOST_MODE) == (role == USB_ROLE_HOST)) {
91                         pm_runtime_put(dev);
92                         return 0;
93                 }
94
95                 /* Interval for polling is set to about 5 - 10 ms */
96                 usleep_range(5000, 10000);
97         } while (time_before(jiffies, timeout));
98
99         pm_runtime_put(dev);
100
101         dev_warn(dev, "Timeout waiting for role-switch\n");
102         return -ETIMEDOUT;
103 }
104
105 static enum usb_role intel_xhci_usb_get_role(struct device *dev)
106 {
107         struct intel_xhci_usb_data *data = dev_get_drvdata(dev);
108         enum usb_role role;
109         u32 val;
110
111         pm_runtime_get_sync(dev);
112         val = readl(data->base + DUAL_ROLE_CFG0);
113         pm_runtime_put(dev);
114
115         if (!(val & SW_IDPIN))
116                 role = USB_ROLE_HOST;
117         else if (val & SW_VBUS_VALID)
118                 role = USB_ROLE_DEVICE;
119         else
120                 role = USB_ROLE_NONE;
121
122         return role;
123 }
124
125 static const struct usb_role_switch_desc sw_desc = {
126         .set = intel_xhci_usb_set_role,
127         .get = intel_xhci_usb_get_role,
128         .allow_userspace_control = true,
129 };
130
131 static int intel_xhci_usb_probe(struct platform_device *pdev)
132 {
133         struct device *dev = &pdev->dev;
134         struct intel_xhci_usb_data *data;
135         struct resource *res;
136
137         data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
138         if (!data)
139                 return -ENOMEM;
140
141         res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
142         if (!res)
143                 return -EINVAL;
144         data->base = devm_ioremap_nocache(dev, res->start, resource_size(res));
145         if (!data->base)
146                 return -ENOMEM;
147
148         platform_set_drvdata(pdev, data);
149
150         data->role_sw = usb_role_switch_register(dev, &sw_desc);
151         if (IS_ERR(data->role_sw))
152                 return PTR_ERR(data->role_sw);
153
154         pm_runtime_set_active(dev);
155         pm_runtime_enable(dev);
156
157         return 0;
158 }
159
160 static int intel_xhci_usb_remove(struct platform_device *pdev)
161 {
162         struct intel_xhci_usb_data *data = platform_get_drvdata(pdev);
163
164         pm_runtime_disable(&pdev->dev);
165
166         usb_role_switch_unregister(data->role_sw);
167         return 0;
168 }
169
170 static const struct platform_device_id intel_xhci_usb_table[] = {
171         { .name = DRV_NAME },
172         {}
173 };
174 MODULE_DEVICE_TABLE(platform, intel_xhci_usb_table);
175
176 static struct platform_driver intel_xhci_usb_driver = {
177         .driver = {
178                 .name = DRV_NAME,
179         },
180         .id_table = intel_xhci_usb_table,
181         .probe = intel_xhci_usb_probe,
182         .remove = intel_xhci_usb_remove,
183 };
184
185 module_platform_driver(intel_xhci_usb_driver);
186
187 MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
188 MODULE_DESCRIPTION("Intel XHCI USB role switch driver");
189 MODULE_LICENSE("GPL");