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