Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
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
18static 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
47static 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 */
76int 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
91static 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 */
124int 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 */
154int 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 */
198int 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 */
234int 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}