Commit | Line | Data |
---|---|---|
cae5f515 MW |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * USB4 port device | |
4 | * | |
5 | * Copyright (C) 2021, Intel Corporation | |
6 | * Author: Mika Westerberg <mika.westerberg@linux.intel.com> | |
7 | */ | |
8 | ||
cae5f515 | 9 | #include <linux/pm_runtime.h> |
5dddb416 HK |
10 | #include <linux/component.h> |
11 | #include <linux/property.h> | |
cae5f515 MW |
12 | |
13 | #include "tb.h" | |
14 | ||
5dddb416 HK |
15 | static int connector_bind(struct device *dev, struct device *connector, void *data) |
16 | { | |
17 | int ret; | |
18 | ||
19 | ret = sysfs_create_link(&dev->kobj, &connector->kobj, "connector"); | |
20 | if (ret) | |
21 | return ret; | |
22 | ||
23 | ret = sysfs_create_link(&connector->kobj, &dev->kobj, dev_name(dev)); | |
24 | if (ret) | |
25 | sysfs_remove_link(&dev->kobj, "connector"); | |
26 | ||
27 | return ret; | |
28 | } | |
29 | ||
30 | static void connector_unbind(struct device *dev, struct device *connector, void *data) | |
31 | { | |
32 | sysfs_remove_link(&connector->kobj, dev_name(dev)); | |
33 | sysfs_remove_link(&dev->kobj, "connector"); | |
34 | } | |
35 | ||
36 | static const struct component_ops connector_ops = { | |
37 | .bind = connector_bind, | |
38 | .unbind = connector_unbind, | |
39 | }; | |
40 | ||
cae5f515 MW |
41 | static ssize_t link_show(struct device *dev, struct device_attribute *attr, |
42 | char *buf) | |
43 | { | |
44 | struct usb4_port *usb4 = tb_to_usb4_port_device(dev); | |
45 | struct tb_port *port = usb4->port; | |
46 | struct tb *tb = port->sw->tb; | |
47 | const char *link; | |
48 | ||
49 | if (mutex_lock_interruptible(&tb->lock)) | |
50 | return -ERESTARTSYS; | |
51 | ||
52 | if (tb_is_upstream_port(port)) | |
53 | link = port->sw->link_usb4 ? "usb4" : "tbt"; | |
54 | else if (tb_port_has_remote(port)) | |
55 | link = port->remote->sw->link_usb4 ? "usb4" : "tbt"; | |
56 | else | |
57 | link = "none"; | |
58 | ||
59 | mutex_unlock(&tb->lock); | |
60 | ||
61 | return sysfs_emit(buf, "%s\n", link); | |
62 | } | |
63 | static DEVICE_ATTR_RO(link); | |
64 | ||
65 | static struct attribute *common_attrs[] = { | |
66 | &dev_attr_link.attr, | |
67 | NULL | |
68 | }; | |
69 | ||
70 | static const struct attribute_group common_group = { | |
71 | .attrs = common_attrs, | |
72 | }; | |
73 | ||
3fb10ea4 RM |
74 | static int usb4_port_offline(struct usb4_port *usb4) |
75 | { | |
76 | struct tb_port *port = usb4->port; | |
77 | int ret; | |
78 | ||
79 | ret = tb_acpi_power_on_retimers(port); | |
80 | if (ret) | |
81 | return ret; | |
82 | ||
83 | ret = usb4_port_router_offline(port); | |
84 | if (ret) { | |
85 | tb_acpi_power_off_retimers(port); | |
86 | return ret; | |
87 | } | |
88 | ||
89 | ret = tb_retimer_scan(port, false); | |
90 | if (ret) { | |
91 | usb4_port_router_online(port); | |
92 | tb_acpi_power_off_retimers(port); | |
93 | } | |
94 | ||
95 | return ret; | |
96 | } | |
97 | ||
98 | static void usb4_port_online(struct usb4_port *usb4) | |
99 | { | |
100 | struct tb_port *port = usb4->port; | |
101 | ||
102 | usb4_port_router_online(port); | |
103 | tb_acpi_power_off_retimers(port); | |
104 | } | |
105 | ||
106 | static ssize_t offline_show(struct device *dev, | |
107 | struct device_attribute *attr, char *buf) | |
108 | { | |
109 | struct usb4_port *usb4 = tb_to_usb4_port_device(dev); | |
110 | ||
111 | return sysfs_emit(buf, "%d\n", usb4->offline); | |
112 | } | |
113 | ||
114 | static ssize_t offline_store(struct device *dev, | |
115 | struct device_attribute *attr, const char *buf, size_t count) | |
116 | { | |
117 | struct usb4_port *usb4 = tb_to_usb4_port_device(dev); | |
118 | struct tb_port *port = usb4->port; | |
119 | struct tb *tb = port->sw->tb; | |
120 | bool val; | |
121 | int ret; | |
122 | ||
123 | ret = kstrtobool(buf, &val); | |
124 | if (ret) | |
125 | return ret; | |
126 | ||
127 | pm_runtime_get_sync(&usb4->dev); | |
128 | ||
129 | if (mutex_lock_interruptible(&tb->lock)) { | |
130 | ret = -ERESTARTSYS; | |
131 | goto out_rpm; | |
132 | } | |
133 | ||
134 | if (val == usb4->offline) | |
135 | goto out_unlock; | |
136 | ||
137 | /* Offline mode works only for ports that are not connected */ | |
138 | if (tb_port_has_remote(port)) { | |
139 | ret = -EBUSY; | |
140 | goto out_unlock; | |
141 | } | |
142 | ||
143 | if (val) { | |
144 | ret = usb4_port_offline(usb4); | |
145 | if (ret) | |
146 | goto out_unlock; | |
147 | } else { | |
148 | usb4_port_online(usb4); | |
149 | tb_retimer_remove_all(port); | |
150 | } | |
151 | ||
152 | usb4->offline = val; | |
153 | tb_port_dbg(port, "%s offline mode\n", val ? "enter" : "exit"); | |
154 | ||
155 | out_unlock: | |
156 | mutex_unlock(&tb->lock); | |
157 | out_rpm: | |
158 | pm_runtime_mark_last_busy(&usb4->dev); | |
159 | pm_runtime_put_autosuspend(&usb4->dev); | |
160 | ||
161 | return ret ? ret : count; | |
162 | } | |
163 | static DEVICE_ATTR_RW(offline); | |
164 | ||
165 | static ssize_t rescan_store(struct device *dev, | |
166 | struct device_attribute *attr, const char *buf, size_t count) | |
167 | { | |
168 | struct usb4_port *usb4 = tb_to_usb4_port_device(dev); | |
169 | struct tb_port *port = usb4->port; | |
170 | struct tb *tb = port->sw->tb; | |
171 | bool val; | |
172 | int ret; | |
173 | ||
174 | ret = kstrtobool(buf, &val); | |
175 | if (ret) | |
176 | return ret; | |
177 | ||
178 | if (!val) | |
179 | return count; | |
180 | ||
181 | pm_runtime_get_sync(&usb4->dev); | |
182 | ||
183 | if (mutex_lock_interruptible(&tb->lock)) { | |
184 | ret = -ERESTARTSYS; | |
185 | goto out_rpm; | |
186 | } | |
187 | ||
188 | /* Must be in offline mode already */ | |
189 | if (!usb4->offline) { | |
190 | ret = -EINVAL; | |
191 | goto out_unlock; | |
192 | } | |
193 | ||
194 | tb_retimer_remove_all(port); | |
195 | ret = tb_retimer_scan(port, true); | |
196 | ||
197 | out_unlock: | |
198 | mutex_unlock(&tb->lock); | |
199 | out_rpm: | |
200 | pm_runtime_mark_last_busy(&usb4->dev); | |
201 | pm_runtime_put_autosuspend(&usb4->dev); | |
202 | ||
203 | return ret ? ret : count; | |
204 | } | |
205 | static DEVICE_ATTR_WO(rescan); | |
206 | ||
207 | static struct attribute *service_attrs[] = { | |
208 | &dev_attr_offline.attr, | |
209 | &dev_attr_rescan.attr, | |
210 | NULL | |
211 | }; | |
212 | ||
213 | static umode_t service_attr_is_visible(struct kobject *kobj, | |
214 | struct attribute *attr, int n) | |
215 | { | |
216 | struct device *dev = kobj_to_dev(kobj); | |
217 | struct usb4_port *usb4 = tb_to_usb4_port_device(dev); | |
218 | ||
219 | /* | |
220 | * Always need some platform help to cycle the modes so that | |
221 | * retimers can be accessed through the sideband. | |
222 | */ | |
223 | return usb4->can_offline ? attr->mode : 0; | |
224 | } | |
225 | ||
226 | static const struct attribute_group service_group = { | |
227 | .attrs = service_attrs, | |
228 | .is_visible = service_attr_is_visible, | |
229 | }; | |
230 | ||
cae5f515 MW |
231 | static const struct attribute_group *usb4_port_device_groups[] = { |
232 | &common_group, | |
3fb10ea4 | 233 | &service_group, |
cae5f515 MW |
234 | NULL |
235 | }; | |
236 | ||
237 | static void usb4_port_device_release(struct device *dev) | |
238 | { | |
239 | struct usb4_port *usb4 = container_of(dev, struct usb4_port, dev); | |
240 | ||
241 | kfree(usb4); | |
242 | } | |
243 | ||
244 | struct device_type usb4_port_device_type = { | |
245 | .name = "usb4_port", | |
246 | .groups = usb4_port_device_groups, | |
247 | .release = usb4_port_device_release, | |
248 | }; | |
249 | ||
250 | /** | |
251 | * usb4_port_device_add() - Add USB4 port device | |
252 | * @port: Lane 0 adapter port to add the USB4 port | |
253 | * | |
254 | * Creates and registers a USB4 port device for @port. Returns the new | |
255 | * USB4 port device pointer or ERR_PTR() in case of error. | |
256 | */ | |
257 | struct usb4_port *usb4_port_device_add(struct tb_port *port) | |
258 | { | |
259 | struct usb4_port *usb4; | |
260 | int ret; | |
261 | ||
262 | usb4 = kzalloc(sizeof(*usb4), GFP_KERNEL); | |
263 | if (!usb4) | |
264 | return ERR_PTR(-ENOMEM); | |
265 | ||
266 | usb4->port = port; | |
267 | usb4->dev.type = &usb4_port_device_type; | |
268 | usb4->dev.parent = &port->sw->dev; | |
269 | dev_set_name(&usb4->dev, "usb4_port%d", port->port); | |
270 | ||
271 | ret = device_register(&usb4->dev); | |
272 | if (ret) { | |
273 | put_device(&usb4->dev); | |
274 | return ERR_PTR(ret); | |
275 | } | |
276 | ||
5dddb416 HK |
277 | if (dev_fwnode(&usb4->dev)) { |
278 | ret = component_add(&usb4->dev, &connector_ops); | |
279 | if (ret) { | |
280 | dev_err(&usb4->dev, "failed to add component\n"); | |
281 | device_unregister(&usb4->dev); | |
282 | } | |
283 | } | |
284 | ||
cae5f515 MW |
285 | pm_runtime_no_callbacks(&usb4->dev); |
286 | pm_runtime_set_active(&usb4->dev); | |
287 | pm_runtime_enable(&usb4->dev); | |
288 | pm_runtime_set_autosuspend_delay(&usb4->dev, TB_AUTOSUSPEND_DELAY); | |
289 | pm_runtime_mark_last_busy(&usb4->dev); | |
290 | pm_runtime_use_autosuspend(&usb4->dev); | |
291 | ||
292 | return usb4; | |
293 | } | |
294 | ||
295 | /** | |
296 | * usb4_port_device_remove() - Removes USB4 port device | |
297 | * @usb4: USB4 port device | |
298 | * | |
299 | * Unregisters the USB4 port device from the system. The device will be | |
300 | * released when the last reference is dropped. | |
301 | */ | |
302 | void usb4_port_device_remove(struct usb4_port *usb4) | |
303 | { | |
5dddb416 HK |
304 | if (dev_fwnode(&usb4->dev)) |
305 | component_del(&usb4->dev, &connector_ops); | |
cae5f515 MW |
306 | device_unregister(&usb4->dev); |
307 | } | |
3fb10ea4 RM |
308 | |
309 | /** | |
310 | * usb4_port_device_resume() - Resumes USB4 port device | |
311 | * @usb4: USB4 port device | |
312 | * | |
313 | * Used to resume USB4 port device after sleep state. | |
314 | */ | |
315 | int usb4_port_device_resume(struct usb4_port *usb4) | |
316 | { | |
317 | return usb4->offline ? usb4_port_offline(usb4) : 0; | |
318 | } |