xref: /openbmc/linux/drivers/thunderbolt/cap.c (revision 3c8b228d)
1b2441318SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
2e2b8785eSAndreas Noever /*
315c6784cSMika Westerberg  * Thunderbolt driver - capabilities lookup
4e2b8785eSAndreas Noever  *
5e2b8785eSAndreas Noever  * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com>
615c6784cSMika Westerberg  * Copyright (C) 2018, Intel Corporation
7e2b8785eSAndreas Noever  */
8e2b8785eSAndreas Noever 
9e2b8785eSAndreas Noever #include <linux/slab.h>
10e2b8785eSAndreas Noever #include <linux/errno.h>
11e2b8785eSAndreas Noever 
12e2b8785eSAndreas Noever #include "tb.h"
13e2b8785eSAndreas Noever 
14da2da04bSMika Westerberg #define CAP_OFFSET_MAX		0xff
15da2da04bSMika Westerberg #define VSE_CAP_OFFSET_MAX	0xffff
168b0110d9SMika Westerberg #define TMU_ACCESS_EN		BIT(20)
17e2b8785eSAndreas Noever 
188b0110d9SMika Westerberg static int tb_port_enable_tmu(struct tb_port *port, bool enable)
19e2b8785eSAndreas Noever {
208b0110d9SMika Westerberg 	struct tb_switch *sw = port->sw;
218b0110d9SMika Westerberg 	u32 value, offset;
228b0110d9SMika Westerberg 	int ret;
23e2b8785eSAndreas Noever 
24da2da04bSMika Westerberg 	/*
258b0110d9SMika Westerberg 	 * Legacy devices need to have TMU access enabled before port
268b0110d9SMika Westerberg 	 * space can be fully accessed.
27da2da04bSMika Westerberg 	 */
2817a8f815SMika Westerberg 	if (tb_switch_is_light_ridge(sw))
298b0110d9SMika Westerberg 		offset = 0x26;
3017a8f815SMika Westerberg 	else if (tb_switch_is_eagle_ridge(sw))
318b0110d9SMika Westerberg 		offset = 0x2a;
32e2b8785eSAndreas Noever 	else
338b0110d9SMika Westerberg 		return 0;
348b0110d9SMika Westerberg 
358b0110d9SMika Westerberg 	ret = tb_sw_read(sw, &value, TB_CFG_SWITCH, offset, 1);
368b0110d9SMika Westerberg 	if (ret)
378b0110d9SMika Westerberg 		return ret;
388b0110d9SMika Westerberg 
398b0110d9SMika Westerberg 	if (enable)
408b0110d9SMika Westerberg 		value |= TMU_ACCESS_EN;
418b0110d9SMika Westerberg 	else
428b0110d9SMika Westerberg 		value &= ~TMU_ACCESS_EN;
438b0110d9SMika Westerberg 
448b0110d9SMika Westerberg 	return tb_sw_write(sw, &value, TB_CFG_SWITCH, offset, 1);
458b0110d9SMika Westerberg }
468b0110d9SMika Westerberg 
47ffd003b2SMika Westerberg static void tb_port_dummy_read(struct tb_port *port)
48ffd003b2SMika Westerberg {
49ffd003b2SMika Westerberg 	/*
50ffd003b2SMika Westerberg 	 * When reading from next capability pointer location in port
51ffd003b2SMika Westerberg 	 * config space the read data is not cleared on LR. To avoid
52ffd003b2SMika Westerberg 	 * reading stale data on next read perform one dummy read after
53ffd003b2SMika Westerberg 	 * port capabilities are walked.
54ffd003b2SMika Westerberg 	 */
5517a8f815SMika Westerberg 	if (tb_switch_is_light_ridge(port->sw)) {
56ffd003b2SMika Westerberg 		u32 dummy;
57ffd003b2SMika Westerberg 
58ffd003b2SMika Westerberg 		tb_port_read(port, &dummy, TB_CFG_PORT, 0, 1);
59ffd003b2SMika Westerberg 	}
60ffd003b2SMika Westerberg }
61ffd003b2SMika Westerberg 
623c8b228dSMika Westerberg /**
633c8b228dSMika Westerberg  * tb_port_next_cap() - Return next capability in the linked list
643c8b228dSMika Westerberg  * @port: Port to find the capability for
653c8b228dSMika Westerberg  * @offset: Previous capability offset (%0 for start)
663c8b228dSMika Westerberg  *
673c8b228dSMika Westerberg  * Returns dword offset of the next capability in port config space
683c8b228dSMika Westerberg  * capability list and returns it. Passing %0 returns the first entry in
693c8b228dSMika Westerberg  * the capability list. If no next capability is found returns %0. In case
703c8b228dSMika Westerberg  * of failure returns negative errno.
713c8b228dSMika Westerberg  */
723c8b228dSMika Westerberg int tb_port_next_cap(struct tb_port *port, unsigned int offset)
733c8b228dSMika Westerberg {
743c8b228dSMika Westerberg 	struct tb_cap_any header;
753c8b228dSMika Westerberg 	int ret;
763c8b228dSMika Westerberg 
773c8b228dSMika Westerberg 	if (!offset)
783c8b228dSMika Westerberg 		return port->config.first_cap_offset;
793c8b228dSMika Westerberg 
803c8b228dSMika Westerberg 	ret = tb_port_read(port, &header, TB_CFG_PORT, offset, 1);
813c8b228dSMika Westerberg 	if (ret)
823c8b228dSMika Westerberg 		return ret;
833c8b228dSMika Westerberg 
843c8b228dSMika Westerberg 	return header.basic.next;
853c8b228dSMika Westerberg }
863c8b228dSMika Westerberg 
878b0110d9SMika Westerberg static int __tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap)
888b0110d9SMika Westerberg {
893c8b228dSMika Westerberg 	int offset = 0;
90da2da04bSMika Westerberg 
91da2da04bSMika Westerberg 	do {
92da2da04bSMika Westerberg 		struct tb_cap_any header;
93da2da04bSMika Westerberg 		int ret;
94da2da04bSMika Westerberg 
953c8b228dSMika Westerberg 		offset = tb_port_next_cap(port, offset);
963c8b228dSMika Westerberg 		if (offset < 0)
973c8b228dSMika Westerberg 			return offset;
983c8b228dSMika Westerberg 
99da2da04bSMika Westerberg 		ret = tb_port_read(port, &header, TB_CFG_PORT, offset, 1);
100da2da04bSMika Westerberg 		if (ret)
101da2da04bSMika Westerberg 			return ret;
102da2da04bSMika Westerberg 
103da2da04bSMika Westerberg 		if (header.basic.cap == cap)
104da2da04bSMika Westerberg 			return offset;
1053c8b228dSMika Westerberg 	} while (offset > 0);
106da2da04bSMika Westerberg 
107da2da04bSMika Westerberg 	return -ENOENT;
108e2b8785eSAndreas Noever }
109e2b8785eSAndreas Noever 
1108b0110d9SMika Westerberg /**
1118b0110d9SMika Westerberg  * tb_port_find_cap() - Find port capability
1128b0110d9SMika Westerberg  * @port: Port to find the capability for
1138b0110d9SMika Westerberg  * @cap: Capability to look
1148b0110d9SMika Westerberg  *
1158b0110d9SMika Westerberg  * Returns offset to start of capability or %-ENOENT if no such
1168b0110d9SMika Westerberg  * capability was found. Negative errno is returned if there was an
1178b0110d9SMika Westerberg  * error.
1188b0110d9SMika Westerberg  */
1198b0110d9SMika Westerberg int tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap)
1208b0110d9SMika Westerberg {
1218b0110d9SMika Westerberg 	int ret;
1228b0110d9SMika Westerberg 
1238b0110d9SMika Westerberg 	ret = tb_port_enable_tmu(port, true);
1248b0110d9SMika Westerberg 	if (ret)
1258b0110d9SMika Westerberg 		return ret;
1268b0110d9SMika Westerberg 
1278b0110d9SMika Westerberg 	ret = __tb_port_find_cap(port, cap);
1288b0110d9SMika Westerberg 
129ffd003b2SMika Westerberg 	tb_port_dummy_read(port);
1308b0110d9SMika Westerberg 	tb_port_enable_tmu(port, false);
1318b0110d9SMika Westerberg 
1328b0110d9SMika Westerberg 	return ret;
1338b0110d9SMika Westerberg }
1348b0110d9SMika Westerberg 
135aa43a9dcSRajmohan Mani /**
136aa43a9dcSRajmohan Mani  * tb_switch_find_cap() - Find switch capability
137aa43a9dcSRajmohan Mani  * @sw Switch to find the capability for
138aa43a9dcSRajmohan Mani  * @cap: Capability to look
139aa43a9dcSRajmohan Mani  *
140aa43a9dcSRajmohan Mani  * Returns offset to start of capability or %-ENOENT if no such
141aa43a9dcSRajmohan Mani  * capability was found. Negative errno is returned if there was an
142aa43a9dcSRajmohan Mani  * error.
143aa43a9dcSRajmohan Mani  */
144aa43a9dcSRajmohan Mani int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap)
145e2b8785eSAndreas Noever {
146da2da04bSMika Westerberg 	int offset = sw->config.first_cap_offset;
147da2da04bSMika Westerberg 
148da2da04bSMika Westerberg 	while (offset > 0 && offset < CAP_OFFSET_MAX) {
149da2da04bSMika Westerberg 		struct tb_cap_any header;
150da2da04bSMika Westerberg 		int ret;
151da2da04bSMika Westerberg 
152da2da04bSMika Westerberg 		ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 1);
153da2da04bSMika Westerberg 		if (ret)
154da2da04bSMika Westerberg 			return ret;
155da2da04bSMika Westerberg 
156da2da04bSMika Westerberg 		if (header.basic.cap == cap)
157da2da04bSMika Westerberg 			return offset;
158da2da04bSMika Westerberg 
159da2da04bSMika Westerberg 		offset = header.basic.next;
160e2b8785eSAndreas Noever 	}
161da2da04bSMika Westerberg 
162da2da04bSMika Westerberg 	return -ENOENT;
163e2b8785eSAndreas Noever }
164e2b8785eSAndreas Noever 
165e2b8785eSAndreas Noever /**
166da2da04bSMika Westerberg  * tb_switch_find_vse_cap() - Find switch vendor specific capability
167da2da04bSMika Westerberg  * @sw: Switch to find the capability for
168da2da04bSMika Westerberg  * @vsec: Vendor specific capability to look
169e2b8785eSAndreas Noever  *
170da2da04bSMika Westerberg  * Functions enumerates vendor specific capabilities (VSEC) of a switch
171da2da04bSMika Westerberg  * and returns offset when capability matching @vsec is found. If no
172da2da04bSMika Westerberg  * such capability is found returns %-ENOENT. In case of error returns
173da2da04bSMika Westerberg  * negative errno.
174e2b8785eSAndreas Noever  */
175da2da04bSMika Westerberg int tb_switch_find_vse_cap(struct tb_switch *sw, enum tb_switch_vse_cap vsec)
176e2b8785eSAndreas Noever {
177e2b8785eSAndreas Noever 	struct tb_cap_any header;
178da2da04bSMika Westerberg 	int offset;
179da2da04bSMika Westerberg 
180da2da04bSMika Westerberg 	offset = tb_switch_find_cap(sw, TB_SWITCH_CAP_VSE);
181da2da04bSMika Westerberg 	if (offset < 0)
182e2b8785eSAndreas Noever 		return offset;
183da2da04bSMika Westerberg 
184da2da04bSMika Westerberg 	while (offset > 0 && offset < VSE_CAP_OFFSET_MAX) {
185da2da04bSMika Westerberg 		int ret;
186da2da04bSMika Westerberg 
187da2da04bSMika Westerberg 		ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 2);
188da2da04bSMika Westerberg 		if (ret)
189da2da04bSMika Westerberg 			return ret;
190da2da04bSMika Westerberg 
191da2da04bSMika Westerberg 		/*
192da2da04bSMika Westerberg 		 * Extended vendor specific capabilities come in two
193da2da04bSMika Westerberg 		 * flavors: short and long. The latter is used when
194da2da04bSMika Westerberg 		 * offset is over 0xff.
195da2da04bSMika Westerberg 		 */
196da2da04bSMika Westerberg 		if (offset >= CAP_OFFSET_MAX) {
197da2da04bSMika Westerberg 			if (header.extended_long.vsec_id == vsec)
198da2da04bSMika Westerberg 				return offset;
199da2da04bSMika Westerberg 			offset = header.extended_long.next;
200da2da04bSMika Westerberg 		} else {
201da2da04bSMika Westerberg 			if (header.extended_short.vsec_id == vsec)
202da2da04bSMika Westerberg 				return offset;
203da2da04bSMika Westerberg 			if (!header.extended_short.length)
204da2da04bSMika Westerberg 				return -ENOENT;
205da2da04bSMika Westerberg 			offset = header.extended_short.next;
206e2b8785eSAndreas Noever 		}
207e2b8785eSAndreas Noever 	}
208da2da04bSMika Westerberg 
209da2da04bSMika Westerberg 	return -ENOENT;
210e2b8785eSAndreas Noever }
211