xref: /linux/drivers/thunderbolt/cap.c (revision 6093a688a07da07808f0122f9aa2a3eed250d853)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Thunderbolt driver - capabilities lookup
4  *
5  * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
6  * Copyright (C) 2018, Intel Corporation
7  */
8 
9 #include <linux/slab.h>
10 #include <linux/errno.h>
11 
12 #include "tb.h"
13 
14 #define CAP_OFFSET_MAX		0xff
15 #define VSE_CAP_OFFSET_MAX	0xffff
16 #define TMU_ACCESS_EN		BIT(20)
17 
18 static int tb_port_enable_tmu(struct tb_port *port, bool enable)
19 {
20 	struct tb_switch *sw = port->sw;
21 	u32 value, offset;
22 	int ret;
23 
24 	/*
25 	 * Legacy devices need to have TMU access enabled before port
26 	 * space can be fully accessed.
27 	 */
28 	if (tb_switch_is_light_ridge(sw))
29 		offset = 0x26;
30 	else if (tb_switch_is_eagle_ridge(sw))
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;
41 	else
42 		value &= ~TMU_ACCESS_EN;
43 
44 	return tb_sw_write(sw, &value, TB_CFG_SWITCH, offset, 1);
45 }
46 
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 	 */
55 	if (tb_switch_is_light_ridge(port->sw)) {
56 		u32 dummy;
57 
58 		tb_port_read(port, &dummy, TB_CFG_PORT, 0, 1);
59 	}
60 }
61 
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  * Finds dword offset of the next capability in port config space
68  * capability list. When passed %0 in @offset parameter, first entry
69  * will be returned, if it exists.
70  *
71  * Return:
72  * * Double word offset of the first or next capability - On success.
73  * * %0 - If no next capability is found.
74  * * Negative errno - Another error occurred.
75  */
76 int tb_port_next_cap(struct tb_port *port, unsigned int offset)
77 {
78 	struct tb_cap_any header;
79 	int ret;
80 
81 	if (!offset)
82 		return port->config.first_cap_offset;
83 
84 	ret = tb_port_read(port, &header, TB_CFG_PORT, offset, 1);
85 	if (ret)
86 		return ret;
87 
88 	return header.basic.next;
89 }
90 
91 static int __tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap)
92 {
93 	int offset = 0;
94 
95 	do {
96 		struct tb_cap_any header;
97 		int ret;
98 
99 		offset = tb_port_next_cap(port, offset);
100 		if (offset < 0)
101 			return offset;
102 
103 		ret = tb_port_read(port, &header, TB_CFG_PORT, offset, 1);
104 		if (ret)
105 			return ret;
106 
107 		if (header.basic.cap == cap)
108 			return offset;
109 	} while (offset > 0);
110 
111 	return -ENOENT;
112 }
113 
114 /**
115  * tb_port_find_cap() - Find port capability
116  * @port: Port to find the capability for
117  * @cap: Capability to look
118  *
119  * Return:
120  * * Offset to the start of capability - On success.
121  * * %-ENOENT - If no such capability was found.
122  * * Negative errno - Another error occurred.
123  */
124 int tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap)
125 {
126 	int ret;
127 
128 	ret = tb_port_enable_tmu(port, true);
129 	if (ret)
130 		return ret;
131 
132 	ret = __tb_port_find_cap(port, cap);
133 
134 	tb_port_dummy_read(port);
135 	tb_port_enable_tmu(port, false);
136 
137 	return ret;
138 }
139 
140 /**
141  * tb_switch_next_cap() - Return next capability in the linked list
142  * @sw: Switch to find the capability for
143  * @offset: Previous capability offset (%0 for start)
144  *
145  * Finds dword offset of the next capability in port config space
146  * capability list. When passed %0 in @offset parameter, first entry
147  * will be returned, if it exists.
148  *
149  * Return:
150  * * Double word offset of the first or next capability - On success.
151  * * %0 - If no next capability is found.
152  * * Negative errno - Another error occurred.
153  */
154 int tb_switch_next_cap(struct tb_switch *sw, unsigned int offset)
155 {
156 	struct tb_cap_any header;
157 	int ret;
158 
159 	if (!offset)
160 		return sw->config.first_cap_offset;
161 
162 	ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 2);
163 	if (ret)
164 		return ret;
165 
166 	switch (header.basic.cap) {
167 	case TB_SWITCH_CAP_TMU:
168 		ret = header.basic.next;
169 		break;
170 
171 	case TB_SWITCH_CAP_VSE:
172 		if (!header.extended_short.length)
173 			ret = header.extended_long.next;
174 		else
175 			ret = header.extended_short.next;
176 		break;
177 
178 	default:
179 		tb_sw_dbg(sw, "unknown capability %#x at %#x\n",
180 			  header.basic.cap, offset);
181 		ret = -EINVAL;
182 		break;
183 	}
184 
185 	return ret >= VSE_CAP_OFFSET_MAX ? 0 : ret;
186 }
187 
188 /**
189  * tb_switch_find_cap() - Find switch capability
190  * @sw: Switch to find the capability for
191  * @cap: Capability to look
192  *
193  * Return:
194  * * Offset to the start of capability - On success.
195  * * %-ENOENT - If no such capability was found.
196  * * Negative errno - Another error occurred.
197  */
198 int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap)
199 {
200 	int offset = 0;
201 
202 	do {
203 		struct tb_cap_any header;
204 		int ret;
205 
206 		offset = tb_switch_next_cap(sw, offset);
207 		if (offset < 0)
208 			return offset;
209 
210 		ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 1);
211 		if (ret)
212 			return ret;
213 
214 		if (header.basic.cap == cap)
215 			return offset;
216 	} while (offset);
217 
218 	return -ENOENT;
219 }
220 
221 /**
222  * tb_switch_find_vse_cap() - Find switch vendor specific capability
223  * @sw: Switch to find the capability for
224  * @vsec: Vendor specific capability to look
225  *
226  * This function enumerates vendor specific capabilities (VSEC) of a
227  * switch and returns offset when capability matching @vsec is found.
228  *
229  * Return:
230  * * Offset of capability - On success.
231  * * %-ENOENT - If capability was not found.
232  * * Negative errno - Another error occurred.
233  */
234 int tb_switch_find_vse_cap(struct tb_switch *sw, enum tb_switch_vse_cap vsec)
235 {
236 	int offset = 0;
237 
238 	do {
239 		struct tb_cap_any header;
240 		int ret;
241 
242 		offset = tb_switch_next_cap(sw, offset);
243 		if (offset < 0)
244 			return offset;
245 
246 		ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 1);
247 		if (ret)
248 			return ret;
249 
250 		if (header.extended_short.cap == TB_SWITCH_CAP_VSE &&
251 		    header.extended_short.vsec_id == vsec)
252 			return offset;
253 	} while (offset);
254 
255 	return -ENOENT;
256 }
257