]>
Commit | Line | Data |
---|---|---|
cf29b9af RM |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Thunderbolt Time Management Unit (TMU) support | |
4 | * | |
5 | * Copyright (C) 2019, Intel Corporation | |
6 | * Authors: Mika Westerberg <mika.westerberg@linux.intel.com> | |
7 | * Rajmohan Mani <rajmohan.mani@intel.com> | |
8 | */ | |
9 | ||
10 | #include <linux/delay.h> | |
11 | ||
12 | #include "tb.h" | |
13 | ||
14 | static const char *tb_switch_tmu_mode_name(const struct tb_switch *sw) | |
15 | { | |
16 | bool root_switch = !tb_route(sw); | |
17 | ||
18 | switch (sw->tmu.rate) { | |
19 | case TB_SWITCH_TMU_RATE_OFF: | |
20 | return "off"; | |
21 | ||
22 | case TB_SWITCH_TMU_RATE_HIFI: | |
23 | /* Root switch does not have upstream directionality */ | |
24 | if (root_switch) | |
25 | return "HiFi"; | |
26 | if (sw->tmu.unidirectional) | |
27 | return "uni-directional, HiFi"; | |
28 | return "bi-directional, HiFi"; | |
29 | ||
30 | case TB_SWITCH_TMU_RATE_NORMAL: | |
31 | if (root_switch) | |
32 | return "normal"; | |
33 | return "uni-directional, normal"; | |
34 | ||
35 | default: | |
36 | return "unknown"; | |
37 | } | |
38 | } | |
39 | ||
40 | static bool tb_switch_tmu_ucap_supported(struct tb_switch *sw) | |
41 | { | |
42 | int ret; | |
43 | u32 val; | |
44 | ||
45 | ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, | |
46 | sw->tmu.cap + TMU_RTR_CS_0, 1); | |
47 | if (ret) | |
48 | return false; | |
49 | ||
50 | return !!(val & TMU_RTR_CS_0_UCAP); | |
51 | } | |
52 | ||
53 | static int tb_switch_tmu_rate_read(struct tb_switch *sw) | |
54 | { | |
55 | int ret; | |
56 | u32 val; | |
57 | ||
58 | ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, | |
59 | sw->tmu.cap + TMU_RTR_CS_3, 1); | |
60 | if (ret) | |
61 | return ret; | |
62 | ||
63 | val >>= TMU_RTR_CS_3_TS_PACKET_INTERVAL_SHIFT; | |
64 | return val; | |
65 | } | |
66 | ||
67 | static int tb_switch_tmu_rate_write(struct tb_switch *sw, int rate) | |
68 | { | |
69 | int ret; | |
70 | u32 val; | |
71 | ||
72 | ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, | |
73 | sw->tmu.cap + TMU_RTR_CS_3, 1); | |
74 | if (ret) | |
75 | return ret; | |
76 | ||
77 | val &= ~TMU_RTR_CS_3_TS_PACKET_INTERVAL_MASK; | |
78 | val |= rate << TMU_RTR_CS_3_TS_PACKET_INTERVAL_SHIFT; | |
79 | ||
80 | return tb_sw_write(sw, &val, TB_CFG_SWITCH, | |
81 | sw->tmu.cap + TMU_RTR_CS_3, 1); | |
82 | } | |
83 | ||
84 | static int tb_port_tmu_write(struct tb_port *port, u8 offset, u32 mask, | |
85 | u32 value) | |
86 | { | |
87 | u32 data; | |
88 | int ret; | |
89 | ||
90 | ret = tb_port_read(port, &data, TB_CFG_PORT, port->cap_tmu + offset, 1); | |
91 | if (ret) | |
92 | return ret; | |
93 | ||
94 | data &= ~mask; | |
95 | data |= value; | |
96 | ||
97 | return tb_port_write(port, &data, TB_CFG_PORT, | |
98 | port->cap_tmu + offset, 1); | |
99 | } | |
100 | ||
101 | static int tb_port_tmu_set_unidirectional(struct tb_port *port, | |
102 | bool unidirectional) | |
103 | { | |
104 | u32 val; | |
105 | ||
106 | if (!port->sw->tmu.has_ucap) | |
107 | return 0; | |
108 | ||
109 | val = unidirectional ? TMU_ADP_CS_3_UDM : 0; | |
110 | return tb_port_tmu_write(port, TMU_ADP_CS_3, TMU_ADP_CS_3_UDM, val); | |
111 | } | |
112 | ||
113 | static inline int tb_port_tmu_unidirectional_disable(struct tb_port *port) | |
114 | { | |
115 | return tb_port_tmu_set_unidirectional(port, false); | |
116 | } | |
117 | ||
118 | static bool tb_port_tmu_is_unidirectional(struct tb_port *port) | |
119 | { | |
120 | int ret; | |
121 | u32 val; | |
122 | ||
123 | ret = tb_port_read(port, &val, TB_CFG_PORT, | |
124 | port->cap_tmu + TMU_ADP_CS_3, 1); | |
125 | if (ret) | |
126 | return false; | |
127 | ||
128 | return val & TMU_ADP_CS_3_UDM; | |
129 | } | |
130 | ||
131 | static int tb_switch_tmu_set_time_disruption(struct tb_switch *sw, bool set) | |
132 | { | |
133 | int ret; | |
134 | u32 val; | |
135 | ||
136 | ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, | |
137 | sw->tmu.cap + TMU_RTR_CS_0, 1); | |
138 | if (ret) | |
139 | return ret; | |
140 | ||
141 | if (set) | |
142 | val |= TMU_RTR_CS_0_TD; | |
143 | else | |
144 | val &= ~TMU_RTR_CS_0_TD; | |
145 | ||
146 | return tb_sw_write(sw, &val, TB_CFG_SWITCH, | |
147 | sw->tmu.cap + TMU_RTR_CS_0, 1); | |
148 | } | |
149 | ||
150 | /** | |
151 | * tb_switch_tmu_init() - Initialize switch TMU structures | |
152 | * @sw: Switch to initialized | |
153 | * | |
154 | * This function must be called before other TMU related functions to | |
155 | * makes the internal structures are filled in correctly. Does not | |
156 | * change any hardware configuration. | |
157 | */ | |
158 | int tb_switch_tmu_init(struct tb_switch *sw) | |
159 | { | |
160 | struct tb_port *port; | |
161 | int ret; | |
162 | ||
163 | if (tb_switch_is_icm(sw)) | |
164 | return 0; | |
165 | ||
166 | ret = tb_switch_find_cap(sw, TB_SWITCH_CAP_TMU); | |
167 | if (ret > 0) | |
168 | sw->tmu.cap = ret; | |
169 | ||
170 | tb_switch_for_each_port(sw, port) { | |
171 | int cap; | |
172 | ||
173 | cap = tb_port_find_cap(port, TB_PORT_CAP_TIME1); | |
174 | if (cap > 0) | |
175 | port->cap_tmu = cap; | |
176 | } | |
177 | ||
178 | ret = tb_switch_tmu_rate_read(sw); | |
179 | if (ret < 0) | |
180 | return ret; | |
181 | ||
182 | sw->tmu.rate = ret; | |
183 | ||
184 | sw->tmu.has_ucap = tb_switch_tmu_ucap_supported(sw); | |
185 | if (sw->tmu.has_ucap) { | |
186 | tb_sw_dbg(sw, "TMU: supports uni-directional mode\n"); | |
187 | ||
188 | if (tb_route(sw)) { | |
189 | struct tb_port *up = tb_upstream_port(sw); | |
190 | ||
191 | sw->tmu.unidirectional = | |
192 | tb_port_tmu_is_unidirectional(up); | |
193 | } | |
194 | } else { | |
195 | sw->tmu.unidirectional = false; | |
196 | } | |
197 | ||
198 | tb_sw_dbg(sw, "TMU: current mode: %s\n", tb_switch_tmu_mode_name(sw)); | |
199 | return 0; | |
200 | } | |
201 | ||
202 | /** | |
203 | * tb_switch_tmu_post_time() - Update switch local time | |
204 | * @sw: Switch whose time to update | |
205 | * | |
206 | * Updates switch local time using time posting procedure. | |
207 | */ | |
208 | int tb_switch_tmu_post_time(struct tb_switch *sw) | |
209 | { | |
210 | unsigned int post_local_time_offset, post_time_offset; | |
211 | struct tb_switch *root_switch = sw->tb->root_switch; | |
212 | u64 hi, mid, lo, local_time, post_time; | |
213 | int i, ret, retries = 100; | |
214 | u32 gm_local_time[3]; | |
215 | ||
216 | if (!tb_route(sw)) | |
217 | return 0; | |
218 | ||
219 | if (!tb_switch_is_usb4(sw)) | |
220 | return 0; | |
221 | ||
222 | /* Need to be able to read the grand master time */ | |
223 | if (!root_switch->tmu.cap) | |
224 | return 0; | |
225 | ||
226 | ret = tb_sw_read(root_switch, gm_local_time, TB_CFG_SWITCH, | |
227 | root_switch->tmu.cap + TMU_RTR_CS_1, | |
228 | ARRAY_SIZE(gm_local_time)); | |
229 | if (ret) | |
230 | return ret; | |
231 | ||
232 | for (i = 0; i < ARRAY_SIZE(gm_local_time); i++) | |
233 | tb_sw_dbg(root_switch, "local_time[%d]=0x%08x\n", i, | |
234 | gm_local_time[i]); | |
235 | ||
236 | /* Convert to nanoseconds (drop fractional part) */ | |
237 | hi = gm_local_time[2] & TMU_RTR_CS_3_LOCAL_TIME_NS_MASK; | |
238 | mid = gm_local_time[1]; | |
239 | lo = (gm_local_time[0] & TMU_RTR_CS_1_LOCAL_TIME_NS_MASK) >> | |
240 | TMU_RTR_CS_1_LOCAL_TIME_NS_SHIFT; | |
241 | local_time = hi << 48 | mid << 16 | lo; | |
242 | ||
243 | /* Tell the switch that time sync is disrupted for a while */ | |
244 | ret = tb_switch_tmu_set_time_disruption(sw, true); | |
245 | if (ret) | |
246 | return ret; | |
247 | ||
248 | post_local_time_offset = sw->tmu.cap + TMU_RTR_CS_22; | |
249 | post_time_offset = sw->tmu.cap + TMU_RTR_CS_24; | |
250 | ||
251 | /* | |
252 | * Write the Grandmaster time to the Post Local Time registers | |
253 | * of the new switch. | |
254 | */ | |
255 | ret = tb_sw_write(sw, &local_time, TB_CFG_SWITCH, | |
256 | post_local_time_offset, 2); | |
257 | if (ret) | |
258 | goto out; | |
259 | ||
260 | /* | |
261 | * Have the new switch update its local time (by writing 1 to | |
262 | * the post_time registers) and wait for the completion of the | |
263 | * same (post_time register becomes 0). This means the time has | |
264 | * been converged properly. | |
265 | */ | |
266 | post_time = 1; | |
267 | ||
268 | ret = tb_sw_write(sw, &post_time, TB_CFG_SWITCH, post_time_offset, 2); | |
269 | if (ret) | |
270 | goto out; | |
271 | ||
272 | do { | |
273 | usleep_range(5, 10); | |
274 | ret = tb_sw_read(sw, &post_time, TB_CFG_SWITCH, | |
275 | post_time_offset, 2); | |
276 | if (ret) | |
277 | goto out; | |
278 | } while (--retries && post_time); | |
279 | ||
280 | if (!retries) { | |
281 | ret = -ETIMEDOUT; | |
282 | goto out; | |
283 | } | |
284 | ||
285 | tb_sw_dbg(sw, "TMU: updated local time to %#llx\n", local_time); | |
286 | ||
287 | out: | |
288 | tb_switch_tmu_set_time_disruption(sw, false); | |
289 | return ret; | |
290 | } | |
291 | ||
292 | /** | |
293 | * tb_switch_tmu_disable() - Disable TMU of a switch | |
294 | * @sw: Switch whose TMU to disable | |
295 | * | |
296 | * Turns off TMU of @sw if it is enabled. If not enabled does nothing. | |
297 | */ | |
298 | int tb_switch_tmu_disable(struct tb_switch *sw) | |
299 | { | |
300 | int ret; | |
301 | ||
302 | if (!tb_switch_is_usb4(sw)) | |
303 | return 0; | |
304 | ||
305 | /* Already disabled? */ | |
306 | if (sw->tmu.rate == TB_SWITCH_TMU_RATE_OFF) | |
307 | return 0; | |
308 | ||
309 | if (sw->tmu.unidirectional) { | |
310 | struct tb_switch *parent = tb_switch_parent(sw); | |
311 | struct tb_port *up, *down; | |
312 | ||
313 | up = tb_upstream_port(sw); | |
314 | down = tb_port_at(tb_route(sw), parent); | |
315 | ||
316 | /* The switch may be unplugged so ignore any errors */ | |
317 | tb_port_tmu_unidirectional_disable(up); | |
318 | ret = tb_port_tmu_unidirectional_disable(down); | |
319 | if (ret) | |
320 | return ret; | |
321 | } | |
322 | ||
323 | tb_switch_tmu_rate_write(sw, TB_SWITCH_TMU_RATE_OFF); | |
324 | ||
325 | sw->tmu.unidirectional = false; | |
326 | sw->tmu.rate = TB_SWITCH_TMU_RATE_OFF; | |
327 | ||
328 | tb_sw_dbg(sw, "TMU: disabled\n"); | |
329 | return 0; | |
330 | } | |
331 | ||
332 | /** | |
333 | * tb_switch_tmu_enable() - Enable TMU on a switch | |
334 | * @sw: Switch whose TMU to enable | |
335 | * | |
336 | * Enables TMU of a switch to be in bi-directional, HiFi mode. In this mode | |
337 | * all tunneling should work. | |
338 | */ | |
339 | int tb_switch_tmu_enable(struct tb_switch *sw) | |
340 | { | |
341 | int ret; | |
342 | ||
343 | if (!tb_switch_is_usb4(sw)) | |
344 | return 0; | |
345 | ||
346 | if (tb_switch_tmu_is_enabled(sw)) | |
347 | return 0; | |
348 | ||
349 | ret = tb_switch_tmu_set_time_disruption(sw, true); | |
350 | if (ret) | |
351 | return ret; | |
352 | ||
353 | /* Change mode to bi-directional */ | |
354 | if (tb_route(sw) && sw->tmu.unidirectional) { | |
355 | struct tb_switch *parent = tb_switch_parent(sw); | |
356 | struct tb_port *up, *down; | |
357 | ||
358 | up = tb_upstream_port(sw); | |
359 | down = tb_port_at(tb_route(sw), parent); | |
360 | ||
361 | ret = tb_port_tmu_unidirectional_disable(down); | |
362 | if (ret) | |
363 | return ret; | |
364 | ||
365 | ret = tb_switch_tmu_rate_write(sw, TB_SWITCH_TMU_RATE_HIFI); | |
366 | if (ret) | |
367 | return ret; | |
368 | ||
369 | ret = tb_port_tmu_unidirectional_disable(up); | |
370 | if (ret) | |
371 | return ret; | |
372 | } else { | |
373 | ret = tb_switch_tmu_rate_write(sw, TB_SWITCH_TMU_RATE_HIFI); | |
374 | if (ret) | |
375 | return ret; | |
376 | } | |
377 | ||
378 | sw->tmu.unidirectional = false; | |
379 | sw->tmu.rate = TB_SWITCH_TMU_RATE_HIFI; | |
380 | tb_sw_dbg(sw, "TMU: mode set to: %s\n", tb_switch_tmu_mode_name(sw)); | |
381 | ||
382 | return tb_switch_tmu_set_time_disruption(sw, false); | |
383 | } |