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 AN |
17 | |
18 | struct tb_cap_any { | |
19 | union { | |
20 | struct tb_cap_basic basic; | |
21 | struct tb_cap_extended_short extended_short; | |
22 | struct tb_cap_extended_long extended_long; | |
23 | }; | |
24 | } __packed; | |
25 | ||
8b0110d9 | 26 | static int tb_port_enable_tmu(struct tb_port *port, bool enable) |
e2b8785e | 27 | { |
8b0110d9 MW |
28 | struct tb_switch *sw = port->sw; |
29 | u32 value, offset; | |
30 | int ret; | |
e2b8785e | 31 | |
da2da04b | 32 | /* |
8b0110d9 MW |
33 | * Legacy devices need to have TMU access enabled before port |
34 | * space can be fully accessed. | |
da2da04b | 35 | */ |
17a8f815 | 36 | if (tb_switch_is_light_ridge(sw)) |
8b0110d9 | 37 | offset = 0x26; |
17a8f815 | 38 | else if (tb_switch_is_eagle_ridge(sw)) |
8b0110d9 MW |
39 | offset = 0x2a; |
40 | else | |
41 | return 0; | |
42 | ||
43 | ret = tb_sw_read(sw, &value, TB_CFG_SWITCH, offset, 1); | |
44 | if (ret) | |
45 | return ret; | |
46 | ||
47 | if (enable) | |
48 | value |= TMU_ACCESS_EN; | |
e2b8785e | 49 | else |
8b0110d9 MW |
50 | value &= ~TMU_ACCESS_EN; |
51 | ||
52 | return tb_sw_write(sw, &value, TB_CFG_SWITCH, offset, 1); | |
53 | } | |
54 | ||
ffd003b2 MW |
55 | static void tb_port_dummy_read(struct tb_port *port) |
56 | { | |
57 | /* | |
58 | * When reading from next capability pointer location in port | |
59 | * config space the read data is not cleared on LR. To avoid | |
60 | * reading stale data on next read perform one dummy read after | |
61 | * port capabilities are walked. | |
62 | */ | |
17a8f815 | 63 | if (tb_switch_is_light_ridge(port->sw)) { |
ffd003b2 MW |
64 | u32 dummy; |
65 | ||
66 | tb_port_read(port, &dummy, TB_CFG_PORT, 0, 1); | |
67 | } | |
68 | } | |
69 | ||
8b0110d9 MW |
70 | static int __tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap) |
71 | { | |
72 | u32 offset = 1; | |
da2da04b MW |
73 | |
74 | do { | |
75 | struct tb_cap_any header; | |
76 | int ret; | |
77 | ||
78 | ret = tb_port_read(port, &header, TB_CFG_PORT, offset, 1); | |
79 | if (ret) | |
80 | return ret; | |
81 | ||
82 | if (header.basic.cap == cap) | |
83 | return offset; | |
84 | ||
85 | offset = header.basic.next; | |
86 | } while (offset); | |
87 | ||
88 | return -ENOENT; | |
e2b8785e AN |
89 | } |
90 | ||
8b0110d9 MW |
91 | /** |
92 | * tb_port_find_cap() - Find port capability | |
93 | * @port: Port to find the capability for | |
94 | * @cap: Capability to look | |
95 | * | |
96 | * Returns offset to start of capability or %-ENOENT if no such | |
97 | * capability was found. Negative errno is returned if there was an | |
98 | * error. | |
99 | */ | |
100 | int tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap) | |
101 | { | |
102 | int ret; | |
103 | ||
104 | ret = tb_port_enable_tmu(port, true); | |
105 | if (ret) | |
106 | return ret; | |
107 | ||
108 | ret = __tb_port_find_cap(port, cap); | |
109 | ||
ffd003b2 | 110 | tb_port_dummy_read(port); |
8b0110d9 MW |
111 | tb_port_enable_tmu(port, false); |
112 | ||
113 | return ret; | |
114 | } | |
115 | ||
aa43a9dc RM |
116 | /** |
117 | * tb_switch_find_cap() - Find switch capability | |
118 | * @sw Switch to find the capability for | |
119 | * @cap: Capability to look | |
120 | * | |
121 | * Returns offset to start of capability or %-ENOENT if no such | |
122 | * capability was found. Negative errno is returned if there was an | |
123 | * error. | |
124 | */ | |
125 | int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap) | |
e2b8785e | 126 | { |
da2da04b MW |
127 | int offset = sw->config.first_cap_offset; |
128 | ||
129 | while (offset > 0 && offset < CAP_OFFSET_MAX) { | |
130 | struct tb_cap_any header; | |
131 | int ret; | |
132 | ||
133 | ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 1); | |
134 | if (ret) | |
135 | return ret; | |
136 | ||
137 | if (header.basic.cap == cap) | |
138 | return offset; | |
139 | ||
140 | offset = header.basic.next; | |
e2b8785e | 141 | } |
da2da04b MW |
142 | |
143 | return -ENOENT; | |
e2b8785e AN |
144 | } |
145 | ||
146 | /** | |
da2da04b MW |
147 | * tb_switch_find_vse_cap() - Find switch vendor specific capability |
148 | * @sw: Switch to find the capability for | |
149 | * @vsec: Vendor specific capability to look | |
e2b8785e | 150 | * |
da2da04b MW |
151 | * Functions enumerates vendor specific capabilities (VSEC) of a switch |
152 | * and returns offset when capability matching @vsec is found. If no | |
153 | * such capability is found returns %-ENOENT. In case of error returns | |
154 | * negative errno. | |
e2b8785e | 155 | */ |
da2da04b | 156 | int tb_switch_find_vse_cap(struct tb_switch *sw, enum tb_switch_vse_cap vsec) |
e2b8785e | 157 | { |
e2b8785e | 158 | struct tb_cap_any header; |
da2da04b MW |
159 | int offset; |
160 | ||
161 | offset = tb_switch_find_cap(sw, TB_SWITCH_CAP_VSE); | |
162 | if (offset < 0) | |
163 | return offset; | |
164 | ||
165 | while (offset > 0 && offset < VSE_CAP_OFFSET_MAX) { | |
166 | int ret; | |
167 | ||
168 | ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 2); | |
169 | if (ret) | |
170 | return ret; | |
171 | ||
172 | /* | |
173 | * Extended vendor specific capabilities come in two | |
174 | * flavors: short and long. The latter is used when | |
175 | * offset is over 0xff. | |
176 | */ | |
177 | if (offset >= CAP_OFFSET_MAX) { | |
178 | if (header.extended_long.vsec_id == vsec) | |
e2b8785e | 179 | return offset; |
da2da04b MW |
180 | offset = header.extended_long.next; |
181 | } else { | |
182 | if (header.extended_short.vsec_id == vsec) | |
183 | return offset; | |
184 | if (!header.extended_short.length) | |
185 | return -ENOENT; | |
186 | offset = header.extended_short.next; | |
e2b8785e | 187 | } |
e2b8785e | 188 | } |
da2da04b MW |
189 | |
190 | return -ENOENT; | |
e2b8785e | 191 | } |