1*fbafdd3bSAlvin Šipraga // SPDX-License-Identifier: GPL-2.0 2*fbafdd3bSAlvin Šipraga /* Look-up table query interface for the rtl8365mb switch family 3*fbafdd3bSAlvin Šipraga * 4*fbafdd3bSAlvin Šipraga * Copyright (C) 2022 Alvin Šipraga <alsi@bang-olufsen.dk> 5*fbafdd3bSAlvin Šipraga */ 6*fbafdd3bSAlvin Šipraga 7*fbafdd3bSAlvin Šipraga #include "rtl8365mb_table.h" 8*fbafdd3bSAlvin Šipraga #include <linux/regmap.h> 9*fbafdd3bSAlvin Šipraga 10*fbafdd3bSAlvin Šipraga /* Table access control register */ 11*fbafdd3bSAlvin Šipraga #define RTL8365MB_TABLE_CTRL_REG 0x0500 12*fbafdd3bSAlvin Šipraga /* Should be one of rtl8365mb_table enum members */ 13*fbafdd3bSAlvin Šipraga #define RTL8365MB_TABLE_CTRL_TABLE_MASK GENMASK(2, 0) 14*fbafdd3bSAlvin Šipraga /* Should be one of rtl8365mb_table_op enum members */ 15*fbafdd3bSAlvin Šipraga #define RTL8365MB_TABLE_CTRL_OP_MASK GENMASK(3, 3) 16*fbafdd3bSAlvin Šipraga /* Should be one of rtl8365mb_table_l2_method enum members */ 17*fbafdd3bSAlvin Šipraga #define RTL8365MB_TABLE_CTRL_METHOD_MASK GENMASK(6, 4) 18*fbafdd3bSAlvin Šipraga #define RTL8365MB_TABLE_CTRL_PORT_MASK GENMASK(11, 8) 19*fbafdd3bSAlvin Šipraga 20*fbafdd3bSAlvin Šipraga /* Table access address register */ 21*fbafdd3bSAlvin Šipraga #define RTL8365MB_TABLE_ACCESS_ADDR_REG 0x0501 22*fbafdd3bSAlvin Šipraga #define RTL8365MB_TABLE_ADDR_MASK GENMASK(12, 0) 23*fbafdd3bSAlvin Šipraga 24*fbafdd3bSAlvin Šipraga /* Table status register */ 25*fbafdd3bSAlvin Šipraga #define RTL8365MB_TABLE_STATUS_REG 0x0502 26*fbafdd3bSAlvin Šipraga #define RTL8365MB_TABLE_STATUS_ADDRESS_MASK GENMASK(10, 0) 27*fbafdd3bSAlvin Šipraga /* set for L3, unset for L2 */ 28*fbafdd3bSAlvin Šipraga #define RTL8365MB_TABLE_STATUS_ADDR_TYPE_MASK GENMASK(11, 11) 29*fbafdd3bSAlvin Šipraga #define RTL8365MB_TABLE_STATUS_HIT_STATUS_MASK GENMASK(12, 12) 30*fbafdd3bSAlvin Šipraga #define RTL8365MB_TABLE_STATUS_BUSY_FLAG_MASK GENMASK(13, 13) 31*fbafdd3bSAlvin Šipraga #define RTL8365MB_TABLE_STATUS_ADDRESS_EXT_MASK GENMASK(14, 14) 32*fbafdd3bSAlvin Šipraga 33*fbafdd3bSAlvin Šipraga /* Table read/write registers */ 34*fbafdd3bSAlvin Šipraga #define RTL8365MB_TABLE_WRITE_BASE 0x0510 35*fbafdd3bSAlvin Šipraga #define RTL8365MB_TABLE_WRITE_REG(_x) \ 36*fbafdd3bSAlvin Šipraga (RTL8365MB_TABLE_WRITE_BASE + (_x)) 37*fbafdd3bSAlvin Šipraga #define RTL8365MB_TABLE_READ_BASE 0x0520 38*fbafdd3bSAlvin Šipraga #define RTL8365MB_TABLE_READ_REG(_x) \ 39*fbafdd3bSAlvin Šipraga (RTL8365MB_TABLE_READ_BASE + (_x)) 40*fbafdd3bSAlvin Šipraga #define RTL8365MB_TABLE_10TH_DATA_MASK GENMASK(3, 0) 41*fbafdd3bSAlvin Šipraga #define RTL8365MB_TABLE_WRITE_10TH_REG \ 42*fbafdd3bSAlvin Šipraga RTL8365MB_TABLE_WRITE_REG(RTL8365MB_TABLE_ENTRY_MAX_SIZE - 1) 43*fbafdd3bSAlvin Šipraga 44*fbafdd3bSAlvin Šipraga static int rtl8365mb_table_poll_busy(struct realtek_priv *priv) 45*fbafdd3bSAlvin Šipraga { 46*fbafdd3bSAlvin Šipraga u32 val; 47*fbafdd3bSAlvin Šipraga 48*fbafdd3bSAlvin Šipraga return regmap_read_poll_timeout(priv->map_nolock, 49*fbafdd3bSAlvin Šipraga RTL8365MB_TABLE_STATUS_REG, val, 50*fbafdd3bSAlvin Šipraga !FIELD_GET(RTL8365MB_TABLE_STATUS_BUSY_FLAG_MASK, val), 51*fbafdd3bSAlvin Šipraga 10, 10000); 52*fbafdd3bSAlvin Šipraga } 53*fbafdd3bSAlvin Šipraga 54*fbafdd3bSAlvin Šipraga int rtl8365mb_table_query(struct realtek_priv *priv, 55*fbafdd3bSAlvin Šipraga enum rtl8365mb_table table, 56*fbafdd3bSAlvin Šipraga enum rtl8365mb_table_op op, u16 *addr, 57*fbafdd3bSAlvin Šipraga enum rtl8365mb_table_l2_method method, 58*fbafdd3bSAlvin Šipraga u16 port, u16 *data, size_t size) 59*fbafdd3bSAlvin Šipraga { 60*fbafdd3bSAlvin Šipraga bool addr_as_input = true; 61*fbafdd3bSAlvin Šipraga bool write_data = false; 62*fbafdd3bSAlvin Šipraga int ret = 0; 63*fbafdd3bSAlvin Šipraga u32 cmd; 64*fbafdd3bSAlvin Šipraga u32 val; 65*fbafdd3bSAlvin Šipraga u32 hit; 66*fbafdd3bSAlvin Šipraga 67*fbafdd3bSAlvin Šipraga /* Prepare target table and operation (read or write) */ 68*fbafdd3bSAlvin Šipraga cmd = 0; 69*fbafdd3bSAlvin Šipraga cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_TABLE_MASK, table); 70*fbafdd3bSAlvin Šipraga cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_OP_MASK, op); 71*fbafdd3bSAlvin Šipraga if (op == RTL8365MB_TABLE_OP_READ && table == RTL8365MB_TABLE_L2) { 72*fbafdd3bSAlvin Šipraga cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_METHOD_MASK, method); 73*fbafdd3bSAlvin Šipraga switch (method) { 74*fbafdd3bSAlvin Šipraga case RTL8365MB_TABLE_L2_METHOD_MAC: 75*fbafdd3bSAlvin Šipraga /* 76*fbafdd3bSAlvin Šipraga * Method MAC requires as input the same L2 table format 77*fbafdd3bSAlvin Šipraga * you'll get as result. However, it might only use mac 78*fbafdd3bSAlvin Šipraga * address and FID/VID fields. 79*fbafdd3bSAlvin Šipraga */ 80*fbafdd3bSAlvin Šipraga write_data = true; 81*fbafdd3bSAlvin Šipraga 82*fbafdd3bSAlvin Šipraga /* METHOD_MAC does not use addr as input, but may return 83*fbafdd3bSAlvin Šipraga * the matched index. 84*fbafdd3bSAlvin Šipraga */ 85*fbafdd3bSAlvin Šipraga addr_as_input = false; 86*fbafdd3bSAlvin Šipraga 87*fbafdd3bSAlvin Šipraga break; 88*fbafdd3bSAlvin Šipraga case RTL8365MB_TABLE_L2_METHOD_ADDR: 89*fbafdd3bSAlvin Šipraga case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT: 90*fbafdd3bSAlvin Šipraga case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC: 91*fbafdd3bSAlvin Šipraga case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_MC: 92*fbafdd3bSAlvin Šipraga break; 93*fbafdd3bSAlvin Šipraga case RTL8365MB_TABLE_L2_METHOD_ADDR_NEXT_UC_PORT: 94*fbafdd3bSAlvin Šipraga cmd |= FIELD_PREP(RTL8365MB_TABLE_CTRL_PORT_MASK, port); 95*fbafdd3bSAlvin Šipraga break; 96*fbafdd3bSAlvin Šipraga default: 97*fbafdd3bSAlvin Šipraga return -EINVAL; 98*fbafdd3bSAlvin Šipraga } 99*fbafdd3bSAlvin Šipraga } else if (op == RTL8365MB_TABLE_OP_WRITE) { 100*fbafdd3bSAlvin Šipraga write_data = true; 101*fbafdd3bSAlvin Šipraga 102*fbafdd3bSAlvin Šipraga /* Writing to L2 does not use addr as input, as the table index 103*fbafdd3bSAlvin Šipraga * is derived from key fields. 104*fbafdd3bSAlvin Šipraga */ 105*fbafdd3bSAlvin Šipraga if (table == RTL8365MB_TABLE_L2) 106*fbafdd3bSAlvin Šipraga addr_as_input = false; 107*fbafdd3bSAlvin Šipraga } 108*fbafdd3bSAlvin Šipraga 109*fbafdd3bSAlvin Šipraga /* To prevent concurrent access to the look-up tables, take the regmap 110*fbafdd3bSAlvin Šipraga * lock manually and access via the map_nolock regmap. 111*fbafdd3bSAlvin Šipraga */ 112*fbafdd3bSAlvin Šipraga mutex_lock(&priv->map_lock); 113*fbafdd3bSAlvin Šipraga 114*fbafdd3bSAlvin Šipraga /* Protect from a busy table access (i.e. previous access timeouts) */ 115*fbafdd3bSAlvin Šipraga ret = rtl8365mb_table_poll_busy(priv); 116*fbafdd3bSAlvin Šipraga if (ret) 117*fbafdd3bSAlvin Šipraga goto out; 118*fbafdd3bSAlvin Šipraga 119*fbafdd3bSAlvin Šipraga /* Write entry data if writing to the table (or L2_METHOD_MAC) */ 120*fbafdd3bSAlvin Šipraga if (write_data) { 121*fbafdd3bSAlvin Šipraga /* bulk write data up to 9th word */ 122*fbafdd3bSAlvin Šipraga ret = regmap_bulk_write(priv->map_nolock, 123*fbafdd3bSAlvin Šipraga RTL8365MB_TABLE_WRITE_BASE, 124*fbafdd3bSAlvin Šipraga data, 125*fbafdd3bSAlvin Šipraga min_t(size_t, size, 126*fbafdd3bSAlvin Šipraga RTL8365MB_TABLE_ENTRY_MAX_SIZE - 127*fbafdd3bSAlvin Šipraga 1)); 128*fbafdd3bSAlvin Šipraga if (ret) 129*fbafdd3bSAlvin Šipraga goto out; 130*fbafdd3bSAlvin Šipraga 131*fbafdd3bSAlvin Šipraga /* 10th register uses only 4 least significant bits */ 132*fbafdd3bSAlvin Šipraga if (size == RTL8365MB_TABLE_ENTRY_MAX_SIZE) { 133*fbafdd3bSAlvin Šipraga val = FIELD_PREP(RTL8365MB_TABLE_10TH_DATA_MASK, 134*fbafdd3bSAlvin Šipraga data[size - 1]); 135*fbafdd3bSAlvin Šipraga ret = regmap_update_bits(priv->map_nolock, 136*fbafdd3bSAlvin Šipraga RTL8365MB_TABLE_WRITE_10TH_REG, 137*fbafdd3bSAlvin Šipraga RTL8365MB_TABLE_10TH_DATA_MASK, 138*fbafdd3bSAlvin Šipraga val); 139*fbafdd3bSAlvin Šipraga } 140*fbafdd3bSAlvin Šipraga 141*fbafdd3bSAlvin Šipraga if (ret) 142*fbafdd3bSAlvin Šipraga goto out; 143*fbafdd3bSAlvin Šipraga } 144*fbafdd3bSAlvin Šipraga 145*fbafdd3bSAlvin Šipraga /* Write address (if needed) */ 146*fbafdd3bSAlvin Šipraga if (addr_as_input) { 147*fbafdd3bSAlvin Šipraga ret = regmap_write(priv->map_nolock, 148*fbafdd3bSAlvin Šipraga RTL8365MB_TABLE_ACCESS_ADDR_REG, 149*fbafdd3bSAlvin Šipraga FIELD_PREP(RTL8365MB_TABLE_ADDR_MASK, 150*fbafdd3bSAlvin Šipraga *addr)); 151*fbafdd3bSAlvin Šipraga if (ret) 152*fbafdd3bSAlvin Šipraga goto out; 153*fbafdd3bSAlvin Šipraga } 154*fbafdd3bSAlvin Šipraga 155*fbafdd3bSAlvin Šipraga /* Execute */ 156*fbafdd3bSAlvin Šipraga ret = regmap_write(priv->map_nolock, RTL8365MB_TABLE_CTRL_REG, cmd); 157*fbafdd3bSAlvin Šipraga if (ret) 158*fbafdd3bSAlvin Šipraga goto out; 159*fbafdd3bSAlvin Šipraga 160*fbafdd3bSAlvin Šipraga /* Poll for completion */ 161*fbafdd3bSAlvin Šipraga ret = rtl8365mb_table_poll_busy(priv); 162*fbafdd3bSAlvin Šipraga if (ret) 163*fbafdd3bSAlvin Šipraga goto out; 164*fbafdd3bSAlvin Šipraga 165*fbafdd3bSAlvin Šipraga /* For both reads and writes to the L2 table, check status */ 166*fbafdd3bSAlvin Šipraga if (table == RTL8365MB_TABLE_L2) { 167*fbafdd3bSAlvin Šipraga ret = regmap_read(priv->map_nolock, RTL8365MB_TABLE_STATUS_REG, 168*fbafdd3bSAlvin Šipraga &val); 169*fbafdd3bSAlvin Šipraga if (ret) 170*fbafdd3bSAlvin Šipraga goto out; 171*fbafdd3bSAlvin Šipraga 172*fbafdd3bSAlvin Šipraga /* Did the query find an entry? */ 173*fbafdd3bSAlvin Šipraga hit = FIELD_GET(RTL8365MB_TABLE_STATUS_HIT_STATUS_MASK, val); 174*fbafdd3bSAlvin Šipraga if (!hit) { 175*fbafdd3bSAlvin Šipraga ret = -ENOENT; 176*fbafdd3bSAlvin Šipraga goto out; 177*fbafdd3bSAlvin Šipraga } 178*fbafdd3bSAlvin Šipraga 179*fbafdd3bSAlvin Šipraga /* If so, extract the address */ 180*fbafdd3bSAlvin Šipraga *addr = 0; 181*fbafdd3bSAlvin Šipraga *addr |= FIELD_GET(RTL8365MB_TABLE_STATUS_ADDRESS_MASK, val); 182*fbafdd3bSAlvin Šipraga *addr |= FIELD_GET(RTL8365MB_TABLE_STATUS_ADDRESS_EXT_MASK, val) 183*fbafdd3bSAlvin Šipraga << 11; 184*fbafdd3bSAlvin Šipraga /* only set if it is a L3 address */ 185*fbafdd3bSAlvin Šipraga *addr |= FIELD_GET(RTL8365MB_TABLE_STATUS_ADDR_TYPE_MASK, val) 186*fbafdd3bSAlvin Šipraga << 12; 187*fbafdd3bSAlvin Šipraga } 188*fbafdd3bSAlvin Šipraga 189*fbafdd3bSAlvin Šipraga /* Finally, get the table entry if we were reading */ 190*fbafdd3bSAlvin Šipraga if (op == RTL8365MB_TABLE_OP_READ) { 191*fbafdd3bSAlvin Šipraga ret = regmap_bulk_read(priv->map_nolock, 192*fbafdd3bSAlvin Šipraga RTL8365MB_TABLE_READ_BASE, 193*fbafdd3bSAlvin Šipraga data, size); 194*fbafdd3bSAlvin Šipraga if (ret) 195*fbafdd3bSAlvin Šipraga goto out; 196*fbafdd3bSAlvin Šipraga 197*fbafdd3bSAlvin Šipraga /* For the biggest table entries, the uppermost table 198*fbafdd3bSAlvin Šipraga * entry register has space for only one nibble. Mask 199*fbafdd3bSAlvin Šipraga * out the remainder bits. Empirically I saw nothing 200*fbafdd3bSAlvin Šipraga * wrong with omitting this mask, but it may prevent 201*fbafdd3bSAlvin Šipraga * unwanted behaviour. FYI. 202*fbafdd3bSAlvin Šipraga */ 203*fbafdd3bSAlvin Šipraga if (size == RTL8365MB_TABLE_ENTRY_MAX_SIZE) { 204*fbafdd3bSAlvin Šipraga val = FIELD_GET(RTL8365MB_TABLE_10TH_DATA_MASK, 205*fbafdd3bSAlvin Šipraga data[size - 1]); 206*fbafdd3bSAlvin Šipraga data[size - 1] = val; 207*fbafdd3bSAlvin Šipraga } 208*fbafdd3bSAlvin Šipraga } 209*fbafdd3bSAlvin Šipraga 210*fbafdd3bSAlvin Šipraga out: 211*fbafdd3bSAlvin Šipraga mutex_unlock(&priv->map_lock); 212*fbafdd3bSAlvin Šipraga 213*fbafdd3bSAlvin Šipraga return ret; 214*fbafdd3bSAlvin Šipraga } 215