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 18 struct tb_cap_any { 19 union { 20 struct tb_cap_basic basic; 21 struct tb_cap_extended_short extended_short; 22 struct tb_cap_extended_long extended_long; 23 }; 24 } __packed; 25 26 static int tb_port_enable_tmu(struct tb_port *port, bool enable) 27 { 28 struct tb_switch *sw = port->sw; 29 u32 value, offset; 30 int ret; 31 32 /* 33 * Legacy devices need to have TMU access enabled before port 34 * space can be fully accessed. 35 */ 36 if (tb_switch_is_light_ridge(sw)) 37 offset = 0x26; 38 else if (tb_switch_is_eagle_ridge(sw)) 39 offset = 0x2a; 40 else 41 return 0; 42 43 ret = tb_sw_read(sw, &value, TB_CFG_SWITCH, offset, 1); 44 if (ret) 45 return ret; 46 47 if (enable) 48 value |= TMU_ACCESS_EN; 49 else 50 value &= ~TMU_ACCESS_EN; 51 52 return tb_sw_write(sw, &value, TB_CFG_SWITCH, offset, 1); 53 } 54 55 static void tb_port_dummy_read(struct tb_port *port) 56 { 57 /* 58 * When reading from next capability pointer location in port 59 * config space the read data is not cleared on LR. To avoid 60 * reading stale data on next read perform one dummy read after 61 * port capabilities are walked. 62 */ 63 if (tb_switch_is_light_ridge(port->sw)) { 64 u32 dummy; 65 66 tb_port_read(port, &dummy, TB_CFG_PORT, 0, 1); 67 } 68 } 69 70 static int __tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap) 71 { 72 u32 offset = 1; 73 74 do { 75 struct tb_cap_any header; 76 int ret; 77 78 ret = tb_port_read(port, &header, TB_CFG_PORT, offset, 1); 79 if (ret) 80 return ret; 81 82 if (header.basic.cap == cap) 83 return offset; 84 85 offset = header.basic.next; 86 } while (offset); 87 88 return -ENOENT; 89 } 90 91 /** 92 * tb_port_find_cap() - Find port capability 93 * @port: Port to find the capability for 94 * @cap: Capability to look 95 * 96 * Returns offset to start of capability or %-ENOENT if no such 97 * capability was found. Negative errno is returned if there was an 98 * error. 99 */ 100 int tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap) 101 { 102 int ret; 103 104 ret = tb_port_enable_tmu(port, true); 105 if (ret) 106 return ret; 107 108 ret = __tb_port_find_cap(port, cap); 109 110 tb_port_dummy_read(port); 111 tb_port_enable_tmu(port, false); 112 113 return ret; 114 } 115 116 /** 117 * tb_switch_find_cap() - Find switch capability 118 * @sw Switch to find the capability for 119 * @cap: Capability to look 120 * 121 * Returns offset to start of capability or %-ENOENT if no such 122 * capability was found. Negative errno is returned if there was an 123 * error. 124 */ 125 int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap) 126 { 127 int offset = sw->config.first_cap_offset; 128 129 while (offset > 0 && offset < CAP_OFFSET_MAX) { 130 struct tb_cap_any header; 131 int ret; 132 133 ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 1); 134 if (ret) 135 return ret; 136 137 if (header.basic.cap == cap) 138 return offset; 139 140 offset = header.basic.next; 141 } 142 143 return -ENOENT; 144 } 145 146 /** 147 * tb_switch_find_vse_cap() - Find switch vendor specific capability 148 * @sw: Switch to find the capability for 149 * @vsec: Vendor specific capability to look 150 * 151 * Functions enumerates vendor specific capabilities (VSEC) of a switch 152 * and returns offset when capability matching @vsec is found. If no 153 * such capability is found returns %-ENOENT. In case of error returns 154 * negative errno. 155 */ 156 int tb_switch_find_vse_cap(struct tb_switch *sw, enum tb_switch_vse_cap vsec) 157 { 158 struct tb_cap_any header; 159 int offset; 160 161 offset = tb_switch_find_cap(sw, TB_SWITCH_CAP_VSE); 162 if (offset < 0) 163 return offset; 164 165 while (offset > 0 && offset < VSE_CAP_OFFSET_MAX) { 166 int ret; 167 168 ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 2); 169 if (ret) 170 return ret; 171 172 /* 173 * Extended vendor specific capabilities come in two 174 * flavors: short and long. The latter is used when 175 * offset is over 0xff. 176 */ 177 if (offset >= CAP_OFFSET_MAX) { 178 if (header.extended_long.vsec_id == vsec) 179 return offset; 180 offset = header.extended_long.next; 181 } else { 182 if (header.extended_short.vsec_id == vsec) 183 return offset; 184 if (!header.extended_short.length) 185 return -ENOENT; 186 offset = header.extended_short.next; 187 } 188 } 189 190 return -ENOENT; 191 } 192