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