Commit | Line | Data |
---|---|---|
b2441318 | 1 | // SPDX-License-Identifier: GPL-2.0 |
e2b8785e | 2 | /* |
15c6784c | 3 | * Thunderbolt driver - capabilities lookup |
e2b8785e AN |
4 | * |
5 | * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com> | |
15c6784c | 6 | * Copyright (C) 2018, Intel Corporation |
e2b8785e AN |
7 | */ |
8 | ||
9 | #include <linux/slab.h> | |
10 | #include <linux/errno.h> | |
11 | ||
12 | #include "tb.h" | |
13 | ||
da2da04b MW |
14 | #define CAP_OFFSET_MAX 0xff |
15 | #define VSE_CAP_OFFSET_MAX 0xffff | |
8b0110d9 | 16 | #define TMU_ACCESS_EN BIT(20) |
e2b8785e | 17 | |
8b0110d9 | 18 | static int tb_port_enable_tmu(struct tb_port *port, bool enable) |
e2b8785e | 19 | { |
8b0110d9 MW |
20 | struct tb_switch *sw = port->sw; |
21 | u32 value, offset; | |
22 | int ret; | |
e2b8785e | 23 | |
da2da04b | 24 | /* |
8b0110d9 MW |
25 | * Legacy devices need to have TMU access enabled before port |
26 | * space can be fully accessed. | |
da2da04b | 27 | */ |
17a8f815 | 28 | if (tb_switch_is_light_ridge(sw)) |
8b0110d9 | 29 | offset = 0x26; |
17a8f815 | 30 | else if (tb_switch_is_eagle_ridge(sw)) |
8b0110d9 MW |
31 | offset = 0x2a; |
32 | else | |
33 | return 0; | |
34 | ||
35 | ret = tb_sw_read(sw, &value, TB_CFG_SWITCH, offset, 1); | |
36 | if (ret) | |
37 | return ret; | |
38 | ||
39 | if (enable) | |
40 | value |= TMU_ACCESS_EN; | |
e2b8785e | 41 | else |
8b0110d9 MW |
42 | value &= ~TMU_ACCESS_EN; |
43 | ||
44 | return tb_sw_write(sw, &value, TB_CFG_SWITCH, offset, 1); | |
45 | } | |
46 | ||
ffd003b2 MW |
47 | static void tb_port_dummy_read(struct tb_port *port) |
48 | { | |
49 | /* | |
50 | * When reading from next capability pointer location in port | |
51 | * config space the read data is not cleared on LR. To avoid | |
52 | * reading stale data on next read perform one dummy read after | |
53 | * port capabilities are walked. | |
54 | */ | |
17a8f815 | 55 | if (tb_switch_is_light_ridge(port->sw)) { |
ffd003b2 MW |
56 | u32 dummy; |
57 | ||
58 | tb_port_read(port, &dummy, TB_CFG_PORT, 0, 1); | |
59 | } | |
60 | } | |
61 | ||
3c8b228d MW |
62 | /** |
63 | * tb_port_next_cap() - Return next capability in the linked list | |
64 | * @port: Port to find the capability for | |
65 | * @offset: Previous capability offset (%0 for start) | |
66 | * | |
67 | * Returns dword offset of the next capability in port config space | |
68 | * capability list and returns it. Passing %0 returns the first entry in | |
69 | * the capability list. If no next capability is found returns %0. In case | |
70 | * of failure returns negative errno. | |
71 | */ | |
72 | int tb_port_next_cap(struct tb_port *port, unsigned int offset) | |
73 | { | |
74 | struct tb_cap_any header; | |
75 | int ret; | |
76 | ||
77 | if (!offset) | |
78 | return port->config.first_cap_offset; | |
79 | ||
80 | ret = tb_port_read(port, &header, TB_CFG_PORT, offset, 1); | |
81 | if (ret) | |
82 | return ret; | |
83 | ||
84 | return header.basic.next; | |
85 | } | |
86 | ||
8b0110d9 MW |
87 | static int __tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap) |
88 | { | |
3c8b228d | 89 | int offset = 0; |
da2da04b MW |
90 | |
91 | do { | |
92 | struct tb_cap_any header; | |
93 | int ret; | |
94 | ||
3c8b228d MW |
95 | offset = tb_port_next_cap(port, offset); |
96 | if (offset < 0) | |
97 | return offset; | |
98 | ||
da2da04b MW |
99 | ret = tb_port_read(port, &header, TB_CFG_PORT, offset, 1); |
100 | if (ret) | |
101 | return ret; | |
102 | ||
103 | if (header.basic.cap == cap) | |
104 | return offset; | |
3c8b228d | 105 | } while (offset > 0); |
da2da04b MW |
106 | |
107 | return -ENOENT; | |
e2b8785e AN |
108 | } |
109 | ||
8b0110d9 MW |
110 | /** |
111 | * tb_port_find_cap() - Find port capability | |
112 | * @port: Port to find the capability for | |
113 | * @cap: Capability to look | |
114 | * | |
115 | * Returns offset to start of capability or %-ENOENT if no such | |
116 | * capability was found. Negative errno is returned if there was an | |
117 | * error. | |
118 | */ | |
119 | int tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap) | |
120 | { | |
121 | int ret; | |
122 | ||
123 | ret = tb_port_enable_tmu(port, true); | |
124 | if (ret) | |
125 | return ret; | |
126 | ||
127 | ret = __tb_port_find_cap(port, cap); | |
128 | ||
ffd003b2 | 129 | tb_port_dummy_read(port); |
8b0110d9 MW |
130 | tb_port_enable_tmu(port, false); |
131 | ||
132 | return ret; | |
133 | } | |
134 | ||
6de057ef MW |
135 | /** |
136 | * tb_switch_next_cap() - Return next capability in the linked list | |
137 | * @sw: Switch to find the capability for | |
138 | * @offset: Previous capability offset (%0 for start) | |
139 | * | |
140 | * Finds dword offset of the next capability in router config space | |
141 | * capability list and returns it. Passing %0 returns the first entry in | |
142 | * the capability list. If no next capability is found returns %0. In case | |
143 | * of failure returns negative errno. | |
144 | */ | |
145 | int tb_switch_next_cap(struct tb_switch *sw, unsigned int offset) | |
146 | { | |
147 | struct tb_cap_any header; | |
148 | int ret; | |
149 | ||
150 | if (!offset) | |
151 | return sw->config.first_cap_offset; | |
152 | ||
153 | ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 2); | |
154 | if (ret) | |
155 | return ret; | |
156 | ||
157 | switch (header.basic.cap) { | |
158 | case TB_SWITCH_CAP_TMU: | |
159 | ret = header.basic.next; | |
160 | break; | |
161 | ||
162 | case TB_SWITCH_CAP_VSE: | |
163 | if (!header.extended_short.length) | |
164 | ret = header.extended_long.next; | |
165 | else | |
166 | ret = header.extended_short.next; | |
167 | break; | |
168 | ||
169 | default: | |
170 | tb_sw_dbg(sw, "unknown capability %#x at %#x\n", | |
171 | header.basic.cap, offset); | |
172 | ret = -EINVAL; | |
173 | break; | |
174 | } | |
175 | ||
176 | return ret >= VSE_CAP_OFFSET_MAX ? 0 : ret; | |
177 | } | |
178 | ||
aa43a9dc RM |
179 | /** |
180 | * tb_switch_find_cap() - Find switch capability | |
4366979f | 181 | * @sw: Switch to find the capability for |
aa43a9dc RM |
182 | * @cap: Capability to look |
183 | * | |
184 | * Returns offset to start of capability or %-ENOENT if no such | |
185 | * capability was found. Negative errno is returned if there was an | |
186 | * error. | |
187 | */ | |
188 | int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap) | |
e2b8785e | 189 | { |
6de057ef | 190 | int offset = 0; |
da2da04b | 191 | |
6de057ef | 192 | do { |
da2da04b MW |
193 | struct tb_cap_any header; |
194 | int ret; | |
195 | ||
6de057ef MW |
196 | offset = tb_switch_next_cap(sw, offset); |
197 | if (offset < 0) | |
198 | return offset; | |
199 | ||
da2da04b MW |
200 | ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 1); |
201 | if (ret) | |
202 | return ret; | |
203 | ||
204 | if (header.basic.cap == cap) | |
205 | return offset; | |
6de057ef | 206 | } while (offset); |
da2da04b MW |
207 | |
208 | return -ENOENT; | |
e2b8785e AN |
209 | } |
210 | ||
211 | /** | |
da2da04b MW |
212 | * tb_switch_find_vse_cap() - Find switch vendor specific capability |
213 | * @sw: Switch to find the capability for | |
214 | * @vsec: Vendor specific capability to look | |
e2b8785e | 215 | * |
da2da04b MW |
216 | * Functions enumerates vendor specific capabilities (VSEC) of a switch |
217 | * and returns offset when capability matching @vsec is found. If no | |
218 | * such capability is found returns %-ENOENT. In case of error returns | |
219 | * negative errno. | |
e2b8785e | 220 | */ |
da2da04b | 221 | int tb_switch_find_vse_cap(struct tb_switch *sw, enum tb_switch_vse_cap vsec) |
e2b8785e | 222 | { |
6de057ef | 223 | int offset = 0; |
da2da04b | 224 | |
6de057ef MW |
225 | do { |
226 | struct tb_cap_any header; | |
da2da04b MW |
227 | int ret; |
228 | ||
6de057ef MW |
229 | offset = tb_switch_next_cap(sw, offset); |
230 | if (offset < 0) | |
231 | return offset; | |
232 | ||
233 | ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 1); | |
da2da04b MW |
234 | if (ret) |
235 | return ret; | |
236 | ||
6de057ef MW |
237 | if (header.extended_short.cap == TB_SWITCH_CAP_VSE && |
238 | header.extended_short.vsec_id == vsec) | |
239 | return offset; | |
240 | } while (offset); | |
da2da04b MW |
241 | |
242 | return -ENOENT; | |
e2b8785e | 243 | } |