Commit | Line | Data |
---|---|---|
12a14f2f MW |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * CLx support | |
4 | * | |
5 | * Copyright (C) 2020 - 2023, Intel Corporation | |
6 | * Authors: Gil Fine <gil.fine@intel.com> | |
7 | * Mika Westerberg <mika.westerberg@linux.intel.com> | |
8 | */ | |
9 | ||
10 | #include <linux/module.h> | |
11 | ||
12 | #include "tb.h" | |
13 | ||
14 | static bool clx_enabled = true; | |
15 | module_param_named(clx, clx_enabled, bool, 0444); | |
16 | MODULE_PARM_DESC(clx, "allow low power states on the high-speed lanes (default: true)"); | |
17 | ||
768e6fe6 MW |
18 | static const char *clx_name(unsigned int clx) |
19 | { | |
fd4d58d1 MW |
20 | switch (clx) { |
21 | case TB_CL0S | TB_CL1 | TB_CL2: | |
768e6fe6 | 22 | return "CL0s/CL1/CL2"; |
fd4d58d1 MW |
23 | case TB_CL1 | TB_CL2: |
24 | return "CL1/CL2"; | |
25 | case TB_CL0S | TB_CL2: | |
26 | return "CL0s/CL2"; | |
27 | case TB_CL0S | TB_CL1: | |
768e6fe6 | 28 | return "CL0s/CL1"; |
fd4d58d1 | 29 | case TB_CL0S: |
768e6fe6 | 30 | return "CL0s"; |
fd4d58d1 MW |
31 | case 0: |
32 | return "disabled"; | |
33 | default: | |
34 | return "unknown"; | |
35 | } | |
768e6fe6 MW |
36 | } |
37 | ||
12a14f2f MW |
38 | static int tb_port_pm_secondary_set(struct tb_port *port, bool secondary) |
39 | { | |
40 | u32 phy; | |
41 | int ret; | |
42 | ||
43 | ret = tb_port_read(port, &phy, TB_CFG_PORT, | |
44 | port->cap_phy + LANE_ADP_CS_1, 1); | |
45 | if (ret) | |
46 | return ret; | |
47 | ||
48 | if (secondary) | |
49 | phy |= LANE_ADP_CS_1_PMS; | |
50 | else | |
51 | phy &= ~LANE_ADP_CS_1_PMS; | |
52 | ||
53 | return tb_port_write(port, &phy, TB_CFG_PORT, | |
54 | port->cap_phy + LANE_ADP_CS_1, 1); | |
55 | } | |
56 | ||
57 | static int tb_port_pm_secondary_enable(struct tb_port *port) | |
58 | { | |
59 | return tb_port_pm_secondary_set(port, true); | |
60 | } | |
61 | ||
62 | static int tb_port_pm_secondary_disable(struct tb_port *port) | |
63 | { | |
64 | return tb_port_pm_secondary_set(port, false); | |
65 | } | |
66 | ||
67 | /* Called for USB4 or Titan Ridge routers only */ | |
35627353 | 68 | static bool tb_port_clx_supported(struct tb_port *port, unsigned int clx) |
12a14f2f MW |
69 | { |
70 | u32 val, mask = 0; | |
71 | bool ret; | |
72 | ||
73 | /* Don't enable CLx in case of two single-lane links */ | |
74 | if (!port->bonded && port->dual_link_port) | |
75 | return false; | |
76 | ||
77 | /* Don't enable CLx in case of inter-domain link */ | |
78 | if (port->xdomain) | |
79 | return false; | |
80 | ||
81 | if (tb_switch_is_usb4(port->sw)) { | |
82 | if (!usb4_port_clx_supported(port)) | |
83 | return false; | |
84 | } else if (!tb_lc_is_clx_supported(port)) { | |
85 | return false; | |
86 | } | |
87 | ||
35627353 MW |
88 | if (clx & TB_CL0S) |
89 | mask |= LANE_ADP_CS_0_CL0S_SUPPORT; | |
90 | if (clx & TB_CL1) | |
91 | mask |= LANE_ADP_CS_0_CL1_SUPPORT; | |
92 | if (clx & TB_CL2) | |
12a14f2f MW |
93 | mask |= LANE_ADP_CS_0_CL2_SUPPORT; |
94 | ||
95 | ret = tb_port_read(port, &val, TB_CFG_PORT, | |
96 | port->cap_phy + LANE_ADP_CS_0, 1); | |
97 | if (ret) | |
98 | return false; | |
99 | ||
100 | return !!(val & mask); | |
101 | } | |
102 | ||
35627353 | 103 | static int tb_port_clx_set(struct tb_port *port, unsigned int clx, bool enable) |
12a14f2f | 104 | { |
35627353 | 105 | u32 phy, mask = 0; |
12a14f2f MW |
106 | int ret; |
107 | ||
35627353 MW |
108 | if (clx & TB_CL0S) |
109 | mask |= LANE_ADP_CS_1_CL0S_ENABLE; | |
110 | if (clx & TB_CL1) | |
111 | mask |= LANE_ADP_CS_1_CL1_ENABLE; | |
fd4d58d1 MW |
112 | if (clx & TB_CL2) |
113 | mask |= LANE_ADP_CS_1_CL2_ENABLE; | |
35627353 MW |
114 | |
115 | if (!mask) | |
12a14f2f MW |
116 | return -EOPNOTSUPP; |
117 | ||
118 | ret = tb_port_read(port, &phy, TB_CFG_PORT, | |
119 | port->cap_phy + LANE_ADP_CS_1, 1); | |
120 | if (ret) | |
121 | return ret; | |
122 | ||
123 | if (enable) | |
124 | phy |= mask; | |
125 | else | |
126 | phy &= ~mask; | |
127 | ||
128 | return tb_port_write(port, &phy, TB_CFG_PORT, | |
129 | port->cap_phy + LANE_ADP_CS_1, 1); | |
130 | } | |
131 | ||
35627353 | 132 | static int tb_port_clx_disable(struct tb_port *port, unsigned int clx) |
12a14f2f MW |
133 | { |
134 | return tb_port_clx_set(port, clx, false); | |
135 | } | |
136 | ||
35627353 | 137 | static int tb_port_clx_enable(struct tb_port *port, unsigned int clx) |
12a14f2f MW |
138 | { |
139 | return tb_port_clx_set(port, clx, true); | |
140 | } | |
141 | ||
768e6fe6 MW |
142 | static int tb_port_clx(struct tb_port *port) |
143 | { | |
144 | u32 val; | |
145 | int ret; | |
146 | ||
147 | if (!tb_port_clx_supported(port, TB_CL0S | TB_CL1 | TB_CL2)) | |
148 | return 0; | |
149 | ||
150 | ret = tb_port_read(port, &val, TB_CFG_PORT, | |
151 | port->cap_phy + LANE_ADP_CS_1, 1); | |
152 | if (ret) | |
153 | return ret; | |
154 | ||
155 | if (val & LANE_ADP_CS_1_CL0S_ENABLE) | |
156 | ret |= TB_CL0S; | |
157 | if (val & LANE_ADP_CS_1_CL1_ENABLE) | |
158 | ret |= TB_CL1; | |
159 | if (val & LANE_ADP_CS_1_CL2_ENABLE) | |
160 | ret |= TB_CL2; | |
161 | ||
162 | return ret; | |
163 | } | |
164 | ||
12a14f2f MW |
165 | /** |
166 | * tb_port_clx_is_enabled() - Is given CL state enabled | |
167 | * @port: USB4 port to check | |
35627353 | 168 | * @clx: Mask of CL states to check |
12a14f2f MW |
169 | * |
170 | * Returns true if any of the given CL states is enabled for @port. | |
171 | */ | |
35627353 | 172 | bool tb_port_clx_is_enabled(struct tb_port *port, unsigned int clx) |
12a14f2f | 173 | { |
768e6fe6 MW |
174 | return !!(tb_port_clx(port) & clx); |
175 | } | |
12a14f2f | 176 | |
35c9ab4f MW |
177 | /** |
178 | * tb_switch_clx_is_supported() - Is CLx supported on this type of router | |
179 | * @sw: The router to check CLx support for | |
180 | */ | |
181 | static bool tb_switch_clx_is_supported(const struct tb_switch *sw) | |
182 | { | |
183 | if (!clx_enabled) | |
184 | return false; | |
185 | ||
186 | if (sw->quirks & QUIRK_NO_CLX) | |
187 | return false; | |
188 | ||
189 | /* | |
190 | * CLx is not enabled and validated on Intel USB4 platforms | |
191 | * before Alder Lake. | |
192 | */ | |
193 | if (tb_switch_is_tiger_lake(sw)) | |
194 | return false; | |
195 | ||
196 | return tb_switch_is_usb4(sw) || tb_switch_is_titan_ridge(sw); | |
197 | } | |
198 | ||
768e6fe6 MW |
199 | /** |
200 | * tb_switch_clx_init() - Initialize router CL states | |
201 | * @sw: Router | |
202 | * | |
203 | * Can be called for any router. Initializes the current CL state by | |
204 | * reading it from the hardware. | |
205 | * | |
206 | * Returns %0 in case of success and negative errno in case of failure. | |
207 | */ | |
208 | int tb_switch_clx_init(struct tb_switch *sw) | |
209 | { | |
210 | struct tb_port *up, *down; | |
211 | unsigned int clx, tmp; | |
12a14f2f | 212 | |
768e6fe6 MW |
213 | if (tb_switch_is_icm(sw)) |
214 | return 0; | |
12a14f2f | 215 | |
768e6fe6 MW |
216 | if (!tb_route(sw)) |
217 | return 0; | |
12a14f2f | 218 | |
768e6fe6 MW |
219 | if (!tb_switch_clx_is_supported(sw)) |
220 | return 0; | |
221 | ||
222 | up = tb_upstream_port(sw); | |
223 | down = tb_switch_downstream_port(sw); | |
224 | ||
225 | clx = tb_port_clx(up); | |
226 | tmp = tb_port_clx(down); | |
227 | if (clx != tmp) | |
228 | tb_sw_warn(sw, "CLx: inconsistent configuration %#x != %#x\n", | |
229 | clx, tmp); | |
230 | ||
231 | tb_sw_dbg(sw, "CLx: current mode: %s\n", clx_name(clx)); | |
232 | ||
233 | sw->clx = clx; | |
234 | return 0; | |
12a14f2f MW |
235 | } |
236 | ||
237 | static int tb_switch_pm_secondary_resolve(struct tb_switch *sw) | |
238 | { | |
239 | struct tb_port *up, *down; | |
240 | int ret; | |
241 | ||
242 | if (!tb_route(sw)) | |
243 | return 0; | |
244 | ||
245 | up = tb_upstream_port(sw); | |
246 | down = tb_switch_downstream_port(sw); | |
247 | ret = tb_port_pm_secondary_enable(up); | |
248 | if (ret) | |
249 | return ret; | |
250 | ||
251 | return tb_port_pm_secondary_disable(down); | |
252 | } | |
253 | ||
254 | static int tb_switch_mask_clx_objections(struct tb_switch *sw) | |
255 | { | |
256 | int up_port = sw->config.upstream_port_number; | |
257 | u32 offset, val[2], mask_obj, unmask_obj; | |
258 | int ret, i; | |
259 | ||
260 | /* Only Titan Ridge of pre-USB4 devices support CLx states */ | |
261 | if (!tb_switch_is_titan_ridge(sw)) | |
262 | return 0; | |
263 | ||
264 | if (!tb_route(sw)) | |
265 | return 0; | |
266 | ||
267 | /* | |
268 | * In Titan Ridge there are only 2 dual-lane Thunderbolt ports: | |
269 | * Port A consists of lane adapters 1,2 and | |
270 | * Port B consists of lane adapters 3,4 | |
271 | * If upstream port is A, (lanes are 1,2), we mask objections from | |
272 | * port B (lanes 3,4) and unmask objections from Port A and vice-versa. | |
273 | */ | |
274 | if (up_port == 1) { | |
275 | mask_obj = TB_LOW_PWR_C0_PORT_B_MASK; | |
276 | unmask_obj = TB_LOW_PWR_C1_PORT_A_MASK; | |
277 | offset = TB_LOW_PWR_C1_CL1; | |
278 | } else { | |
279 | mask_obj = TB_LOW_PWR_C1_PORT_A_MASK; | |
280 | unmask_obj = TB_LOW_PWR_C0_PORT_B_MASK; | |
281 | offset = TB_LOW_PWR_C3_CL1; | |
282 | } | |
283 | ||
284 | ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, | |
285 | sw->cap_lp + offset, ARRAY_SIZE(val)); | |
286 | if (ret) | |
287 | return ret; | |
288 | ||
289 | for (i = 0; i < ARRAY_SIZE(val); i++) { | |
290 | val[i] |= mask_obj; | |
291 | val[i] &= ~unmask_obj; | |
292 | } | |
293 | ||
294 | return tb_sw_write(sw, &val, TB_CFG_SWITCH, | |
295 | sw->cap_lp + offset, ARRAY_SIZE(val)); | |
296 | } | |
297 | ||
35627353 MW |
298 | static bool validate_mask(unsigned int clx) |
299 | { | |
300 | /* Previous states need to be enabled */ | |
35627353 MW |
301 | if (clx & TB_CL1) |
302 | return (clx & TB_CL0S) == TB_CL0S; | |
303 | return true; | |
304 | } | |
305 | ||
4f9a4f25 MW |
306 | /** |
307 | * tb_switch_clx_enable() - Enable CLx on upstream port of specified router | |
308 | * @sw: Router to enable CLx for | |
309 | * @clx: The CLx state to enable | |
310 | * | |
9650de73 MW |
311 | * CLx is enabled only if both sides of the link support CLx, and if both sides |
312 | * of the link are not configured as two single lane links and only if the link | |
313 | * is not inter-domain link. The complete set of conditions is described in CM | |
314 | * Guide 1.0 section 8.1. | |
4f9a4f25 | 315 | * |
9650de73 | 316 | * Returns %0 on success or an error code on failure. |
4f9a4f25 | 317 | */ |
35627353 | 318 | int tb_switch_clx_enable(struct tb_switch *sw, unsigned int clx) |
12a14f2f MW |
319 | { |
320 | bool up_clx_support, down_clx_support; | |
35627353 | 321 | struct tb_switch *parent_sw; |
12a14f2f MW |
322 | struct tb_port *up, *down; |
323 | int ret; | |
324 | ||
53ba2e16 | 325 | if (!clx || sw->clx == clx) |
4a420eb1 MW |
326 | return 0; |
327 | ||
35627353 MW |
328 | if (!validate_mask(clx)) |
329 | return -EINVAL; | |
4f9a4f25 | 330 | |
35627353 MW |
331 | parent_sw = tb_switch_parent(sw); |
332 | if (!parent_sw) | |
4f9a4f25 MW |
333 | return 0; |
334 | ||
35627353 MW |
335 | if (!tb_switch_clx_is_supported(parent_sw) || |
336 | !tb_switch_clx_is_supported(sw)) | |
12a14f2f MW |
337 | return 0; |
338 | ||
fd4d58d1 MW |
339 | /* Only support CL2 for v2 routers */ |
340 | if ((clx & TB_CL2) && | |
341 | (usb4_switch_version(parent_sw) < 2 || | |
342 | usb4_switch_version(sw) < 2)) | |
35627353 MW |
343 | return -EOPNOTSUPP; |
344 | ||
12a14f2f MW |
345 | ret = tb_switch_pm_secondary_resolve(sw); |
346 | if (ret) | |
347 | return ret; | |
348 | ||
349 | up = tb_upstream_port(sw); | |
350 | down = tb_switch_downstream_port(sw); | |
351 | ||
352 | up_clx_support = tb_port_clx_supported(up, clx); | |
353 | down_clx_support = tb_port_clx_supported(down, clx); | |
354 | ||
b5d15961 | 355 | tb_port_dbg(up, "CLx: %s %ssupported\n", clx_name(clx), |
12a14f2f | 356 | up_clx_support ? "" : "not "); |
b5d15961 | 357 | tb_port_dbg(down, "CLx: %s %ssupported\n", clx_name(clx), |
12a14f2f MW |
358 | down_clx_support ? "" : "not "); |
359 | ||
360 | if (!up_clx_support || !down_clx_support) | |
361 | return -EOPNOTSUPP; | |
362 | ||
363 | ret = tb_port_clx_enable(up, clx); | |
364 | if (ret) | |
365 | return ret; | |
366 | ||
367 | ret = tb_port_clx_enable(down, clx); | |
368 | if (ret) { | |
369 | tb_port_clx_disable(up, clx); | |
370 | return ret; | |
371 | } | |
372 | ||
373 | ret = tb_switch_mask_clx_objections(sw); | |
374 | if (ret) { | |
375 | tb_port_clx_disable(up, clx); | |
376 | tb_port_clx_disable(down, clx); | |
377 | return ret; | |
378 | } | |
379 | ||
35627353 | 380 | sw->clx |= clx; |
12a14f2f | 381 | |
b5d15961 | 382 | tb_sw_dbg(sw, "CLx: %s enabled\n", clx_name(clx)); |
12a14f2f MW |
383 | return 0; |
384 | } | |
385 | ||
386 | /** | |
4f9a4f25 MW |
387 | * tb_switch_clx_disable() - Disable CLx on upstream port of specified router |
388 | * @sw: Router to disable CLx for | |
35627353 MW |
389 | * |
390 | * Disables all CL states of the given router. Can be called on any | |
391 | * router and if the states were not enabled already does nothing. | |
12a14f2f | 392 | * |
4a420eb1 MW |
393 | * Returns the CL states that were disabled or negative errno in case of |
394 | * failure. | |
12a14f2f | 395 | */ |
35627353 | 396 | int tb_switch_clx_disable(struct tb_switch *sw) |
12a14f2f | 397 | { |
35627353 | 398 | unsigned int clx = sw->clx; |
4f9a4f25 MW |
399 | struct tb_port *up, *down; |
400 | int ret; | |
12a14f2f | 401 | |
12a14f2f MW |
402 | if (!tb_switch_clx_is_supported(sw)) |
403 | return 0; | |
404 | ||
35627353 MW |
405 | if (!clx) |
406 | return 0; | |
407 | ||
9e4f5b2a MW |
408 | if (sw->is_unplugged) |
409 | return clx; | |
410 | ||
12a14f2f MW |
411 | up = tb_upstream_port(sw); |
412 | down = tb_switch_downstream_port(sw); | |
35627353 | 413 | |
12a14f2f MW |
414 | ret = tb_port_clx_disable(up, clx); |
415 | if (ret) | |
416 | return ret; | |
417 | ||
418 | ret = tb_port_clx_disable(down, clx); | |
419 | if (ret) | |
420 | return ret; | |
421 | ||
35627353 | 422 | sw->clx = 0; |
12a14f2f | 423 | |
b5d15961 | 424 | tb_sw_dbg(sw, "CLx: %s disabled\n", clx_name(clx)); |
4a420eb1 | 425 | return clx; |
12a14f2f | 426 | } |