xref: /openbmc/linux/drivers/thunderbolt/cap.c (revision 4366979f)
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 
tb_port_enable_tmu(struct tb_port * port,bool enable)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 
tb_port_dummy_read(struct tb_port * port)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  */
tb_port_next_cap(struct tb_port * port,unsigned int offset)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 
__tb_port_find_cap(struct tb_port * port,enum tb_port_cap cap)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  */
tb_port_find_cap(struct tb_port * port,enum tb_port_cap cap)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 /**
1366de057efSMika Westerberg  * tb_switch_next_cap() - Return next capability in the linked list
1376de057efSMika Westerberg  * @sw: Switch to find the capability for
1386de057efSMika Westerberg  * @offset: Previous capability offset (%0 for start)
1396de057efSMika Westerberg  *
1406de057efSMika Westerberg  * Finds dword offset of the next capability in router config space
1416de057efSMika Westerberg  * capability list and returns it. Passing %0 returns the first entry in
1426de057efSMika Westerberg  * the capability list. If no next capability is found returns %0. In case
1436de057efSMika Westerberg  * of failure returns negative errno.
1446de057efSMika Westerberg  */
tb_switch_next_cap(struct tb_switch * sw,unsigned int offset)1456de057efSMika Westerberg int tb_switch_next_cap(struct tb_switch *sw, unsigned int offset)
1466de057efSMika Westerberg {
1476de057efSMika Westerberg 	struct tb_cap_any header;
1486de057efSMika Westerberg 	int ret;
1496de057efSMika Westerberg 
1506de057efSMika Westerberg 	if (!offset)
1516de057efSMika Westerberg 		return sw->config.first_cap_offset;
1526de057efSMika Westerberg 
1536de057efSMika Westerberg 	ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 2);
1546de057efSMika Westerberg 	if (ret)
1556de057efSMika Westerberg 		return ret;
1566de057efSMika Westerberg 
1576de057efSMika Westerberg 	switch (header.basic.cap) {
1586de057efSMika Westerberg 	case TB_SWITCH_CAP_TMU:
1596de057efSMika Westerberg 		ret = header.basic.next;
1606de057efSMika Westerberg 		break;
1616de057efSMika Westerberg 
1626de057efSMika Westerberg 	case TB_SWITCH_CAP_VSE:
1636de057efSMika Westerberg 		if (!header.extended_short.length)
1646de057efSMika Westerberg 			ret = header.extended_long.next;
1656de057efSMika Westerberg 		else
1666de057efSMika Westerberg 			ret = header.extended_short.next;
1676de057efSMika Westerberg 		break;
1686de057efSMika Westerberg 
1696de057efSMika Westerberg 	default:
1706de057efSMika Westerberg 		tb_sw_dbg(sw, "unknown capability %#x at %#x\n",
1716de057efSMika Westerberg 			  header.basic.cap, offset);
1726de057efSMika Westerberg 		ret = -EINVAL;
1736de057efSMika Westerberg 		break;
1746de057efSMika Westerberg 	}
1756de057efSMika Westerberg 
1766de057efSMika Westerberg 	return ret >= VSE_CAP_OFFSET_MAX ? 0 : ret;
1776de057efSMika Westerberg }
1786de057efSMika Westerberg 
1796de057efSMika Westerberg /**
180aa43a9dcSRajmohan Mani  * tb_switch_find_cap() - Find switch capability
181*4366979fSLee Jones  * @sw: Switch to find the capability for
182aa43a9dcSRajmohan Mani  * @cap: Capability to look
183aa43a9dcSRajmohan Mani  *
184aa43a9dcSRajmohan Mani  * Returns offset to start of capability or %-ENOENT if no such
185aa43a9dcSRajmohan Mani  * capability was found. Negative errno is returned if there was an
186aa43a9dcSRajmohan Mani  * error.
187aa43a9dcSRajmohan Mani  */
tb_switch_find_cap(struct tb_switch * sw,enum tb_switch_cap cap)188aa43a9dcSRajmohan Mani int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap)
189e2b8785eSAndreas Noever {
1906de057efSMika Westerberg 	int offset = 0;
191da2da04bSMika Westerberg 
1926de057efSMika Westerberg 	do {
193da2da04bSMika Westerberg 		struct tb_cap_any header;
194da2da04bSMika Westerberg 		int ret;
195da2da04bSMika Westerberg 
1966de057efSMika Westerberg 		offset = tb_switch_next_cap(sw, offset);
1976de057efSMika Westerberg 		if (offset < 0)
1986de057efSMika Westerberg 			return offset;
1996de057efSMika Westerberg 
200da2da04bSMika Westerberg 		ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 1);
201da2da04bSMika Westerberg 		if (ret)
202da2da04bSMika Westerberg 			return ret;
203da2da04bSMika Westerberg 
204da2da04bSMika Westerberg 		if (header.basic.cap == cap)
205da2da04bSMika Westerberg 			return offset;
2066de057efSMika Westerberg 	} while (offset);
207da2da04bSMika Westerberg 
208da2da04bSMika Westerberg 	return -ENOENT;
209e2b8785eSAndreas Noever }
210e2b8785eSAndreas Noever 
211e2b8785eSAndreas Noever /**
212da2da04bSMika Westerberg  * tb_switch_find_vse_cap() - Find switch vendor specific capability
213da2da04bSMika Westerberg  * @sw: Switch to find the capability for
214da2da04bSMika Westerberg  * @vsec: Vendor specific capability to look
215e2b8785eSAndreas Noever  *
216da2da04bSMika Westerberg  * Functions enumerates vendor specific capabilities (VSEC) of a switch
217da2da04bSMika Westerberg  * and returns offset when capability matching @vsec is found. If no
218da2da04bSMika Westerberg  * such capability is found returns %-ENOENT. In case of error returns
219da2da04bSMika Westerberg  * negative errno.
220e2b8785eSAndreas Noever  */
tb_switch_find_vse_cap(struct tb_switch * sw,enum tb_switch_vse_cap vsec)221da2da04bSMika Westerberg int tb_switch_find_vse_cap(struct tb_switch *sw, enum tb_switch_vse_cap vsec)
222e2b8785eSAndreas Noever {
2236de057efSMika Westerberg 	int offset = 0;
224da2da04bSMika Westerberg 
2256de057efSMika Westerberg 	do {
2266de057efSMika Westerberg 		struct tb_cap_any header;
2276de057efSMika Westerberg 		int ret;
2286de057efSMika Westerberg 
2296de057efSMika Westerberg 		offset = tb_switch_next_cap(sw, offset);
230da2da04bSMika Westerberg 		if (offset < 0)
231e2b8785eSAndreas Noever 			return offset;
232da2da04bSMika Westerberg 
2336de057efSMika Westerberg 		ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 1);
234da2da04bSMika Westerberg 		if (ret)
235da2da04bSMika Westerberg 			return ret;
236da2da04bSMika Westerberg 
2376de057efSMika Westerberg 		if (header.extended_short.cap == TB_SWITCH_CAP_VSE &&
2386de057efSMika Westerberg 		    header.extended_short.vsec_id == vsec)
239da2da04bSMika Westerberg 			return offset;
2406de057efSMika Westerberg 	} while (offset);
241da2da04bSMika Westerberg 
242da2da04bSMika Westerberg 	return -ENOENT;
243e2b8785eSAndreas Noever }
244