1027152b8SChristian Marangi // SPDX-License-Identifier: GPL-2.0 2027152b8SChristian Marangi /* 3027152b8SChristian Marangi * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name> 4027152b8SChristian Marangi * Copyright (C) 2011-2012 Gabor Juhos <juhosg@openwrt.org> 5027152b8SChristian Marangi * Copyright (c) 2015, 2019, The Linux Foundation. All rights reserved. 6027152b8SChristian Marangi * Copyright (c) 2016 John Crispin <john@phrozen.org> 7027152b8SChristian Marangi */ 8027152b8SChristian Marangi 9027152b8SChristian Marangi #include <linux/netdevice.h> 10027152b8SChristian Marangi #include <net/dsa.h> 11fd3cae2fSChristian Marangi #include <linux/if_bridge.h> 12027152b8SChristian Marangi 13027152b8SChristian Marangi #include "qca8k.h" 14027152b8SChristian Marangi 15027152b8SChristian Marangi #define MIB_DESC(_s, _o, _n) \ 16027152b8SChristian Marangi { \ 17027152b8SChristian Marangi .size = (_s), \ 18027152b8SChristian Marangi .offset = (_o), \ 19027152b8SChristian Marangi .name = (_n), \ 20027152b8SChristian Marangi } 21027152b8SChristian Marangi 22027152b8SChristian Marangi const struct qca8k_mib_desc ar8327_mib[] = { 23027152b8SChristian Marangi MIB_DESC(1, 0x00, "RxBroad"), 24027152b8SChristian Marangi MIB_DESC(1, 0x04, "RxPause"), 25027152b8SChristian Marangi MIB_DESC(1, 0x08, "RxMulti"), 26027152b8SChristian Marangi MIB_DESC(1, 0x0c, "RxFcsErr"), 27027152b8SChristian Marangi MIB_DESC(1, 0x10, "RxAlignErr"), 28027152b8SChristian Marangi MIB_DESC(1, 0x14, "RxRunt"), 29027152b8SChristian Marangi MIB_DESC(1, 0x18, "RxFragment"), 30027152b8SChristian Marangi MIB_DESC(1, 0x1c, "Rx64Byte"), 31027152b8SChristian Marangi MIB_DESC(1, 0x20, "Rx128Byte"), 32027152b8SChristian Marangi MIB_DESC(1, 0x24, "Rx256Byte"), 33027152b8SChristian Marangi MIB_DESC(1, 0x28, "Rx512Byte"), 34027152b8SChristian Marangi MIB_DESC(1, 0x2c, "Rx1024Byte"), 35027152b8SChristian Marangi MIB_DESC(1, 0x30, "Rx1518Byte"), 36027152b8SChristian Marangi MIB_DESC(1, 0x34, "RxMaxByte"), 37027152b8SChristian Marangi MIB_DESC(1, 0x38, "RxTooLong"), 38027152b8SChristian Marangi MIB_DESC(2, 0x3c, "RxGoodByte"), 39027152b8SChristian Marangi MIB_DESC(2, 0x44, "RxBadByte"), 40027152b8SChristian Marangi MIB_DESC(1, 0x4c, "RxOverFlow"), 41027152b8SChristian Marangi MIB_DESC(1, 0x50, "Filtered"), 42027152b8SChristian Marangi MIB_DESC(1, 0x54, "TxBroad"), 43027152b8SChristian Marangi MIB_DESC(1, 0x58, "TxPause"), 44027152b8SChristian Marangi MIB_DESC(1, 0x5c, "TxMulti"), 45027152b8SChristian Marangi MIB_DESC(1, 0x60, "TxUnderRun"), 46027152b8SChristian Marangi MIB_DESC(1, 0x64, "Tx64Byte"), 47027152b8SChristian Marangi MIB_DESC(1, 0x68, "Tx128Byte"), 48027152b8SChristian Marangi MIB_DESC(1, 0x6c, "Tx256Byte"), 49027152b8SChristian Marangi MIB_DESC(1, 0x70, "Tx512Byte"), 50027152b8SChristian Marangi MIB_DESC(1, 0x74, "Tx1024Byte"), 51027152b8SChristian Marangi MIB_DESC(1, 0x78, "Tx1518Byte"), 52027152b8SChristian Marangi MIB_DESC(1, 0x7c, "TxMaxByte"), 53027152b8SChristian Marangi MIB_DESC(1, 0x80, "TxOverSize"), 54027152b8SChristian Marangi MIB_DESC(2, 0x84, "TxByte"), 55027152b8SChristian Marangi MIB_DESC(1, 0x8c, "TxCollision"), 56027152b8SChristian Marangi MIB_DESC(1, 0x90, "TxAbortCol"), 57027152b8SChristian Marangi MIB_DESC(1, 0x94, "TxMultiCol"), 58027152b8SChristian Marangi MIB_DESC(1, 0x98, "TxSingleCol"), 59027152b8SChristian Marangi MIB_DESC(1, 0x9c, "TxExcDefer"), 60027152b8SChristian Marangi MIB_DESC(1, 0xa0, "TxDefer"), 61027152b8SChristian Marangi MIB_DESC(1, 0xa4, "TxLateCol"), 62027152b8SChristian Marangi MIB_DESC(1, 0xa8, "RXUnicast"), 63027152b8SChristian Marangi MIB_DESC(1, 0xac, "TXUnicast"), 64027152b8SChristian Marangi }; 65d5f901eaSChristian Marangi 66d5f901eaSChristian Marangi int qca8k_read(struct qca8k_priv *priv, u32 reg, u32 *val) 67d5f901eaSChristian Marangi { 68d5f901eaSChristian Marangi return regmap_read(priv->regmap, reg, val); 69d5f901eaSChristian Marangi } 70d5f901eaSChristian Marangi 71d5f901eaSChristian Marangi int qca8k_write(struct qca8k_priv *priv, u32 reg, u32 val) 72d5f901eaSChristian Marangi { 73d5f901eaSChristian Marangi return regmap_write(priv->regmap, reg, val); 74d5f901eaSChristian Marangi } 75d5f901eaSChristian Marangi 76d5f901eaSChristian Marangi int qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val) 77d5f901eaSChristian Marangi { 78d5f901eaSChristian Marangi return regmap_update_bits(priv->regmap, reg, mask, write_val); 79d5f901eaSChristian Marangi } 80d5f901eaSChristian Marangi 81d5f901eaSChristian Marangi static const struct regmap_range qca8k_readable_ranges[] = { 82d5f901eaSChristian Marangi regmap_reg_range(0x0000, 0x00e4), /* Global control */ 83d5f901eaSChristian Marangi regmap_reg_range(0x0100, 0x0168), /* EEE control */ 84d5f901eaSChristian Marangi regmap_reg_range(0x0200, 0x0270), /* Parser control */ 85d5f901eaSChristian Marangi regmap_reg_range(0x0400, 0x0454), /* ACL */ 86d5f901eaSChristian Marangi regmap_reg_range(0x0600, 0x0718), /* Lookup */ 87d5f901eaSChristian Marangi regmap_reg_range(0x0800, 0x0b70), /* QM */ 88d5f901eaSChristian Marangi regmap_reg_range(0x0c00, 0x0c80), /* PKT */ 89d5f901eaSChristian Marangi regmap_reg_range(0x0e00, 0x0e98), /* L3 */ 90d5f901eaSChristian Marangi regmap_reg_range(0x1000, 0x10ac), /* MIB - Port0 */ 91d5f901eaSChristian Marangi regmap_reg_range(0x1100, 0x11ac), /* MIB - Port1 */ 92d5f901eaSChristian Marangi regmap_reg_range(0x1200, 0x12ac), /* MIB - Port2 */ 93d5f901eaSChristian Marangi regmap_reg_range(0x1300, 0x13ac), /* MIB - Port3 */ 94d5f901eaSChristian Marangi regmap_reg_range(0x1400, 0x14ac), /* MIB - Port4 */ 95d5f901eaSChristian Marangi regmap_reg_range(0x1500, 0x15ac), /* MIB - Port5 */ 96d5f901eaSChristian Marangi regmap_reg_range(0x1600, 0x16ac), /* MIB - Port6 */ 97d5f901eaSChristian Marangi }; 98d5f901eaSChristian Marangi 99d5f901eaSChristian Marangi const struct regmap_access_table qca8k_readable_table = { 100d5f901eaSChristian Marangi .yes_ranges = qca8k_readable_ranges, 101d5f901eaSChristian Marangi .n_yes_ranges = ARRAY_SIZE(qca8k_readable_ranges), 102d5f901eaSChristian Marangi }; 10391074644SChristian Marangi 104c5290f63SChristian Marangi static int qca8k_busy_wait(struct qca8k_priv *priv, u32 reg, u32 mask) 105fce1ec0cSChristian Marangi { 106fce1ec0cSChristian Marangi u32 val; 107fce1ec0cSChristian Marangi 108fce1ec0cSChristian Marangi return regmap_read_poll_timeout(priv->regmap, reg, val, !(val & mask), 0, 109fce1ec0cSChristian Marangi QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC); 110fce1ec0cSChristian Marangi } 111fce1ec0cSChristian Marangi 1122e5bd96eSChristian Marangi static int qca8k_fdb_read(struct qca8k_priv *priv, struct qca8k_fdb *fdb) 1132e5bd96eSChristian Marangi { 114e03cea60SChristian Marangi u32 reg[QCA8K_ATU_TABLE_SIZE]; 1152e5bd96eSChristian Marangi int ret; 1162e5bd96eSChristian Marangi 1172e5bd96eSChristian Marangi /* load the ARL table into an array */ 118c766e077SChristian Marangi ret = regmap_bulk_read(priv->regmap, QCA8K_REG_ATU_DATA0, reg, 119c766e077SChristian Marangi QCA8K_ATU_TABLE_SIZE); 1202e5bd96eSChristian Marangi if (ret) 1212e5bd96eSChristian Marangi return ret; 1222e5bd96eSChristian Marangi 1232e5bd96eSChristian Marangi /* vid - 83:72 */ 1242e5bd96eSChristian Marangi fdb->vid = FIELD_GET(QCA8K_ATU_VID_MASK, reg[2]); 1252e5bd96eSChristian Marangi /* aging - 67:64 */ 1262e5bd96eSChristian Marangi fdb->aging = FIELD_GET(QCA8K_ATU_STATUS_MASK, reg[2]); 1272e5bd96eSChristian Marangi /* portmask - 54:48 */ 1282e5bd96eSChristian Marangi fdb->port_mask = FIELD_GET(QCA8K_ATU_PORT_MASK, reg[1]); 1292e5bd96eSChristian Marangi /* mac - 47:0 */ 1302e5bd96eSChristian Marangi fdb->mac[0] = FIELD_GET(QCA8K_ATU_ADDR0_MASK, reg[1]); 1312e5bd96eSChristian Marangi fdb->mac[1] = FIELD_GET(QCA8K_ATU_ADDR1_MASK, reg[1]); 1322e5bd96eSChristian Marangi fdb->mac[2] = FIELD_GET(QCA8K_ATU_ADDR2_MASK, reg[0]); 1332e5bd96eSChristian Marangi fdb->mac[3] = FIELD_GET(QCA8K_ATU_ADDR3_MASK, reg[0]); 1342e5bd96eSChristian Marangi fdb->mac[4] = FIELD_GET(QCA8K_ATU_ADDR4_MASK, reg[0]); 1352e5bd96eSChristian Marangi fdb->mac[5] = FIELD_GET(QCA8K_ATU_ADDR5_MASK, reg[0]); 1362e5bd96eSChristian Marangi 1372e5bd96eSChristian Marangi return 0; 1382e5bd96eSChristian Marangi } 1392e5bd96eSChristian Marangi 1402e5bd96eSChristian Marangi static void qca8k_fdb_write(struct qca8k_priv *priv, u16 vid, u8 port_mask, 1412e5bd96eSChristian Marangi const u8 *mac, u8 aging) 1422e5bd96eSChristian Marangi { 143e03cea60SChristian Marangi u32 reg[QCA8K_ATU_TABLE_SIZE] = { 0 }; 1442e5bd96eSChristian Marangi 1452e5bd96eSChristian Marangi /* vid - 83:72 */ 1462e5bd96eSChristian Marangi reg[2] = FIELD_PREP(QCA8K_ATU_VID_MASK, vid); 1472e5bd96eSChristian Marangi /* aging - 67:64 */ 1482e5bd96eSChristian Marangi reg[2] |= FIELD_PREP(QCA8K_ATU_STATUS_MASK, aging); 1492e5bd96eSChristian Marangi /* portmask - 54:48 */ 1502e5bd96eSChristian Marangi reg[1] = FIELD_PREP(QCA8K_ATU_PORT_MASK, port_mask); 1512e5bd96eSChristian Marangi /* mac - 47:0 */ 1522e5bd96eSChristian Marangi reg[1] |= FIELD_PREP(QCA8K_ATU_ADDR0_MASK, mac[0]); 1532e5bd96eSChristian Marangi reg[1] |= FIELD_PREP(QCA8K_ATU_ADDR1_MASK, mac[1]); 1542e5bd96eSChristian Marangi reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR2_MASK, mac[2]); 1552e5bd96eSChristian Marangi reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR3_MASK, mac[3]); 1562e5bd96eSChristian Marangi reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR4_MASK, mac[4]); 1572e5bd96eSChristian Marangi reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR5_MASK, mac[5]); 1582e5bd96eSChristian Marangi 1592e5bd96eSChristian Marangi /* load the array into the ARL table */ 160c766e077SChristian Marangi regmap_bulk_write(priv->regmap, QCA8K_REG_ATU_DATA0, reg, 161c766e077SChristian Marangi QCA8K_ATU_TABLE_SIZE); 1622e5bd96eSChristian Marangi } 1632e5bd96eSChristian Marangi 1642e5bd96eSChristian Marangi static int qca8k_fdb_access(struct qca8k_priv *priv, enum qca8k_fdb_cmd cmd, 1652e5bd96eSChristian Marangi int port) 1662e5bd96eSChristian Marangi { 1672e5bd96eSChristian Marangi u32 reg; 1682e5bd96eSChristian Marangi int ret; 1692e5bd96eSChristian Marangi 1702e5bd96eSChristian Marangi /* Set the command and FDB index */ 1712e5bd96eSChristian Marangi reg = QCA8K_ATU_FUNC_BUSY; 1722e5bd96eSChristian Marangi reg |= cmd; 1732e5bd96eSChristian Marangi if (port >= 0) { 1742e5bd96eSChristian Marangi reg |= QCA8K_ATU_FUNC_PORT_EN; 1752e5bd96eSChristian Marangi reg |= FIELD_PREP(QCA8K_ATU_FUNC_PORT_MASK, port); 1762e5bd96eSChristian Marangi } 1772e5bd96eSChristian Marangi 1782e5bd96eSChristian Marangi /* Write the function register triggering the table access */ 1792e5bd96eSChristian Marangi ret = qca8k_write(priv, QCA8K_REG_ATU_FUNC, reg); 1802e5bd96eSChristian Marangi if (ret) 1812e5bd96eSChristian Marangi return ret; 1822e5bd96eSChristian Marangi 1832e5bd96eSChristian Marangi /* wait for completion */ 1842e5bd96eSChristian Marangi ret = qca8k_busy_wait(priv, QCA8K_REG_ATU_FUNC, QCA8K_ATU_FUNC_BUSY); 1852e5bd96eSChristian Marangi if (ret) 1862e5bd96eSChristian Marangi return ret; 1872e5bd96eSChristian Marangi 1882e5bd96eSChristian Marangi /* Check for table full violation when adding an entry */ 1892e5bd96eSChristian Marangi if (cmd == QCA8K_FDB_LOAD) { 1902e5bd96eSChristian Marangi ret = qca8k_read(priv, QCA8K_REG_ATU_FUNC, ®); 1912e5bd96eSChristian Marangi if (ret < 0) 1922e5bd96eSChristian Marangi return ret; 1932e5bd96eSChristian Marangi if (reg & QCA8K_ATU_FUNC_FULL) 1942e5bd96eSChristian Marangi return -1; 1952e5bd96eSChristian Marangi } 1962e5bd96eSChristian Marangi 1972e5bd96eSChristian Marangi return 0; 1982e5bd96eSChristian Marangi } 1992e5bd96eSChristian Marangi 2002e5bd96eSChristian Marangi static int qca8k_fdb_next(struct qca8k_priv *priv, struct qca8k_fdb *fdb, 2012e5bd96eSChristian Marangi int port) 2022e5bd96eSChristian Marangi { 2032e5bd96eSChristian Marangi int ret; 2042e5bd96eSChristian Marangi 2052e5bd96eSChristian Marangi qca8k_fdb_write(priv, fdb->vid, fdb->port_mask, fdb->mac, fdb->aging); 2062e5bd96eSChristian Marangi ret = qca8k_fdb_access(priv, QCA8K_FDB_NEXT, port); 2072e5bd96eSChristian Marangi if (ret < 0) 2082e5bd96eSChristian Marangi return ret; 2092e5bd96eSChristian Marangi 2102e5bd96eSChristian Marangi return qca8k_fdb_read(priv, fdb); 2112e5bd96eSChristian Marangi } 2122e5bd96eSChristian Marangi 2132e5bd96eSChristian Marangi static int qca8k_fdb_add(struct qca8k_priv *priv, const u8 *mac, 2142e5bd96eSChristian Marangi u16 port_mask, u16 vid, u8 aging) 2152e5bd96eSChristian Marangi { 2162e5bd96eSChristian Marangi int ret; 2172e5bd96eSChristian Marangi 2182e5bd96eSChristian Marangi mutex_lock(&priv->reg_mutex); 2192e5bd96eSChristian Marangi qca8k_fdb_write(priv, vid, port_mask, mac, aging); 2202e5bd96eSChristian Marangi ret = qca8k_fdb_access(priv, QCA8K_FDB_LOAD, -1); 2212e5bd96eSChristian Marangi mutex_unlock(&priv->reg_mutex); 2222e5bd96eSChristian Marangi 2232e5bd96eSChristian Marangi return ret; 2242e5bd96eSChristian Marangi } 2252e5bd96eSChristian Marangi 2262e5bd96eSChristian Marangi static int qca8k_fdb_del(struct qca8k_priv *priv, const u8 *mac, 2272e5bd96eSChristian Marangi u16 port_mask, u16 vid) 2282e5bd96eSChristian Marangi { 2292e5bd96eSChristian Marangi int ret; 2302e5bd96eSChristian Marangi 2312e5bd96eSChristian Marangi mutex_lock(&priv->reg_mutex); 2322e5bd96eSChristian Marangi qca8k_fdb_write(priv, vid, port_mask, mac, 0); 2332e5bd96eSChristian Marangi ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1); 2342e5bd96eSChristian Marangi mutex_unlock(&priv->reg_mutex); 2352e5bd96eSChristian Marangi 2362e5bd96eSChristian Marangi return ret; 2372e5bd96eSChristian Marangi } 2382e5bd96eSChristian Marangi 2392e5bd96eSChristian Marangi void qca8k_fdb_flush(struct qca8k_priv *priv) 2402e5bd96eSChristian Marangi { 2412e5bd96eSChristian Marangi mutex_lock(&priv->reg_mutex); 2422e5bd96eSChristian Marangi qca8k_fdb_access(priv, QCA8K_FDB_FLUSH, -1); 2432e5bd96eSChristian Marangi mutex_unlock(&priv->reg_mutex); 2442e5bd96eSChristian Marangi } 2452e5bd96eSChristian Marangi 2462e5bd96eSChristian Marangi static int qca8k_fdb_search_and_insert(struct qca8k_priv *priv, u8 port_mask, 24780248d41SChristian Marangi const u8 *mac, u16 vid, u8 aging) 2482e5bd96eSChristian Marangi { 2492e5bd96eSChristian Marangi struct qca8k_fdb fdb = { 0 }; 2502e5bd96eSChristian Marangi int ret; 2512e5bd96eSChristian Marangi 2522e5bd96eSChristian Marangi mutex_lock(&priv->reg_mutex); 2532e5bd96eSChristian Marangi 2542e5bd96eSChristian Marangi qca8k_fdb_write(priv, vid, 0, mac, 0); 2552e5bd96eSChristian Marangi ret = qca8k_fdb_access(priv, QCA8K_FDB_SEARCH, -1); 2562e5bd96eSChristian Marangi if (ret < 0) 2572e5bd96eSChristian Marangi goto exit; 2582e5bd96eSChristian Marangi 2592e5bd96eSChristian Marangi ret = qca8k_fdb_read(priv, &fdb); 2602e5bd96eSChristian Marangi if (ret < 0) 2612e5bd96eSChristian Marangi goto exit; 2622e5bd96eSChristian Marangi 2632e5bd96eSChristian Marangi /* Rule exist. Delete first */ 26480248d41SChristian Marangi if (fdb.aging) { 2652e5bd96eSChristian Marangi ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1); 2662e5bd96eSChristian Marangi if (ret) 2672e5bd96eSChristian Marangi goto exit; 26880248d41SChristian Marangi } else { 26980248d41SChristian Marangi fdb.aging = aging; 2702e5bd96eSChristian Marangi } 2712e5bd96eSChristian Marangi 2722e5bd96eSChristian Marangi /* Add port to fdb portmask */ 2732e5bd96eSChristian Marangi fdb.port_mask |= port_mask; 2742e5bd96eSChristian Marangi 2752e5bd96eSChristian Marangi qca8k_fdb_write(priv, vid, fdb.port_mask, mac, fdb.aging); 2762e5bd96eSChristian Marangi ret = qca8k_fdb_access(priv, QCA8K_FDB_LOAD, -1); 2772e5bd96eSChristian Marangi 2782e5bd96eSChristian Marangi exit: 2792e5bd96eSChristian Marangi mutex_unlock(&priv->reg_mutex); 2802e5bd96eSChristian Marangi return ret; 2812e5bd96eSChristian Marangi } 2822e5bd96eSChristian Marangi 2832e5bd96eSChristian Marangi static int qca8k_fdb_search_and_del(struct qca8k_priv *priv, u8 port_mask, 2842e5bd96eSChristian Marangi const u8 *mac, u16 vid) 2852e5bd96eSChristian Marangi { 2862e5bd96eSChristian Marangi struct qca8k_fdb fdb = { 0 }; 2872e5bd96eSChristian Marangi int ret; 2882e5bd96eSChristian Marangi 2892e5bd96eSChristian Marangi mutex_lock(&priv->reg_mutex); 2902e5bd96eSChristian Marangi 2912e5bd96eSChristian Marangi qca8k_fdb_write(priv, vid, 0, mac, 0); 2922e5bd96eSChristian Marangi ret = qca8k_fdb_access(priv, QCA8K_FDB_SEARCH, -1); 2932e5bd96eSChristian Marangi if (ret < 0) 2942e5bd96eSChristian Marangi goto exit; 2952e5bd96eSChristian Marangi 296*ae70dcb9SChristian Marangi ret = qca8k_fdb_read(priv, &fdb); 297*ae70dcb9SChristian Marangi if (ret < 0) 298*ae70dcb9SChristian Marangi goto exit; 299*ae70dcb9SChristian Marangi 3002e5bd96eSChristian Marangi /* Rule doesn't exist. Why delete? */ 3012e5bd96eSChristian Marangi if (!fdb.aging) { 3022e5bd96eSChristian Marangi ret = -EINVAL; 3032e5bd96eSChristian Marangi goto exit; 3042e5bd96eSChristian Marangi } 3052e5bd96eSChristian Marangi 3062e5bd96eSChristian Marangi ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1); 3072e5bd96eSChristian Marangi if (ret) 3082e5bd96eSChristian Marangi goto exit; 3092e5bd96eSChristian Marangi 3102e5bd96eSChristian Marangi /* Only port in the rule is this port. Don't re insert */ 3112e5bd96eSChristian Marangi if (fdb.port_mask == port_mask) 3122e5bd96eSChristian Marangi goto exit; 3132e5bd96eSChristian Marangi 3142e5bd96eSChristian Marangi /* Remove port from port mask */ 3152e5bd96eSChristian Marangi fdb.port_mask &= ~port_mask; 3162e5bd96eSChristian Marangi 3172e5bd96eSChristian Marangi qca8k_fdb_write(priv, vid, fdb.port_mask, mac, fdb.aging); 3182e5bd96eSChristian Marangi ret = qca8k_fdb_access(priv, QCA8K_FDB_LOAD, -1); 3192e5bd96eSChristian Marangi 3202e5bd96eSChristian Marangi exit: 3212e5bd96eSChristian Marangi mutex_unlock(&priv->reg_mutex); 3222e5bd96eSChristian Marangi return ret; 3232e5bd96eSChristian Marangi } 3242e5bd96eSChristian Marangi 325c5290f63SChristian Marangi static int qca8k_vlan_access(struct qca8k_priv *priv, 326c5290f63SChristian Marangi enum qca8k_vlan_cmd cmd, u16 vid) 327c5290f63SChristian Marangi { 328c5290f63SChristian Marangi u32 reg; 329c5290f63SChristian Marangi int ret; 330c5290f63SChristian Marangi 331c5290f63SChristian Marangi /* Set the command and VLAN index */ 332c5290f63SChristian Marangi reg = QCA8K_VTU_FUNC1_BUSY; 333c5290f63SChristian Marangi reg |= cmd; 334c5290f63SChristian Marangi reg |= FIELD_PREP(QCA8K_VTU_FUNC1_VID_MASK, vid); 335c5290f63SChristian Marangi 336c5290f63SChristian Marangi /* Write the function register triggering the table access */ 337c5290f63SChristian Marangi ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC1, reg); 338c5290f63SChristian Marangi if (ret) 339c5290f63SChristian Marangi return ret; 340c5290f63SChristian Marangi 341c5290f63SChristian Marangi /* wait for completion */ 342c5290f63SChristian Marangi ret = qca8k_busy_wait(priv, QCA8K_REG_VTU_FUNC1, QCA8K_VTU_FUNC1_BUSY); 343c5290f63SChristian Marangi if (ret) 344c5290f63SChristian Marangi return ret; 345c5290f63SChristian Marangi 346c5290f63SChristian Marangi /* Check for table full violation when adding an entry */ 347c5290f63SChristian Marangi if (cmd == QCA8K_VLAN_LOAD) { 348c5290f63SChristian Marangi ret = qca8k_read(priv, QCA8K_REG_VTU_FUNC1, ®); 349c5290f63SChristian Marangi if (ret < 0) 350c5290f63SChristian Marangi return ret; 351c5290f63SChristian Marangi if (reg & QCA8K_VTU_FUNC1_FULL) 352c5290f63SChristian Marangi return -ENOMEM; 353c5290f63SChristian Marangi } 354c5290f63SChristian Marangi 355c5290f63SChristian Marangi return 0; 356c5290f63SChristian Marangi } 357c5290f63SChristian Marangi 358c5290f63SChristian Marangi static int qca8k_vlan_add(struct qca8k_priv *priv, u8 port, u16 vid, 359c5290f63SChristian Marangi bool untagged) 360c5290f63SChristian Marangi { 361c5290f63SChristian Marangi u32 reg; 362c5290f63SChristian Marangi int ret; 363c5290f63SChristian Marangi 364c5290f63SChristian Marangi /* We do the right thing with VLAN 0 and treat it as untagged while 365c5290f63SChristian Marangi * preserving the tag on egress. 366c5290f63SChristian Marangi */ 367c5290f63SChristian Marangi if (vid == 0) 368c5290f63SChristian Marangi return 0; 369c5290f63SChristian Marangi 370c5290f63SChristian Marangi mutex_lock(&priv->reg_mutex); 371c5290f63SChristian Marangi ret = qca8k_vlan_access(priv, QCA8K_VLAN_READ, vid); 372c5290f63SChristian Marangi if (ret < 0) 373c5290f63SChristian Marangi goto out; 374c5290f63SChristian Marangi 375c5290f63SChristian Marangi ret = qca8k_read(priv, QCA8K_REG_VTU_FUNC0, ®); 376c5290f63SChristian Marangi if (ret < 0) 377c5290f63SChristian Marangi goto out; 378c5290f63SChristian Marangi reg |= QCA8K_VTU_FUNC0_VALID | QCA8K_VTU_FUNC0_IVL_EN; 379c5290f63SChristian Marangi reg &= ~QCA8K_VTU_FUNC0_EG_MODE_PORT_MASK(port); 380c5290f63SChristian Marangi if (untagged) 381c5290f63SChristian Marangi reg |= QCA8K_VTU_FUNC0_EG_MODE_PORT_UNTAG(port); 382c5290f63SChristian Marangi else 383c5290f63SChristian Marangi reg |= QCA8K_VTU_FUNC0_EG_MODE_PORT_TAG(port); 384c5290f63SChristian Marangi 385c5290f63SChristian Marangi ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC0, reg); 386c5290f63SChristian Marangi if (ret) 387c5290f63SChristian Marangi goto out; 388c5290f63SChristian Marangi ret = qca8k_vlan_access(priv, QCA8K_VLAN_LOAD, vid); 389c5290f63SChristian Marangi 390c5290f63SChristian Marangi out: 391c5290f63SChristian Marangi mutex_unlock(&priv->reg_mutex); 392c5290f63SChristian Marangi 393c5290f63SChristian Marangi return ret; 394c5290f63SChristian Marangi } 395c5290f63SChristian Marangi 396c5290f63SChristian Marangi static int qca8k_vlan_del(struct qca8k_priv *priv, u8 port, u16 vid) 397c5290f63SChristian Marangi { 398c5290f63SChristian Marangi u32 reg, mask; 399c5290f63SChristian Marangi int ret, i; 400c5290f63SChristian Marangi bool del; 401c5290f63SChristian Marangi 402c5290f63SChristian Marangi mutex_lock(&priv->reg_mutex); 403c5290f63SChristian Marangi ret = qca8k_vlan_access(priv, QCA8K_VLAN_READ, vid); 404c5290f63SChristian Marangi if (ret < 0) 405c5290f63SChristian Marangi goto out; 406c5290f63SChristian Marangi 407c5290f63SChristian Marangi ret = qca8k_read(priv, QCA8K_REG_VTU_FUNC0, ®); 408c5290f63SChristian Marangi if (ret < 0) 409c5290f63SChristian Marangi goto out; 410c5290f63SChristian Marangi reg &= ~QCA8K_VTU_FUNC0_EG_MODE_PORT_MASK(port); 411c5290f63SChristian Marangi reg |= QCA8K_VTU_FUNC0_EG_MODE_PORT_NOT(port); 412c5290f63SChristian Marangi 413c5290f63SChristian Marangi /* Check if we're the last member to be removed */ 414c5290f63SChristian Marangi del = true; 415c5290f63SChristian Marangi for (i = 0; i < QCA8K_NUM_PORTS; i++) { 416c5290f63SChristian Marangi mask = QCA8K_VTU_FUNC0_EG_MODE_PORT_NOT(i); 417c5290f63SChristian Marangi 418c5290f63SChristian Marangi if ((reg & mask) != mask) { 419c5290f63SChristian Marangi del = false; 420c5290f63SChristian Marangi break; 421c5290f63SChristian Marangi } 422c5290f63SChristian Marangi } 423c5290f63SChristian Marangi 424c5290f63SChristian Marangi if (del) { 425c5290f63SChristian Marangi ret = qca8k_vlan_access(priv, QCA8K_VLAN_PURGE, vid); 426c5290f63SChristian Marangi } else { 427c5290f63SChristian Marangi ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC0, reg); 428c5290f63SChristian Marangi if (ret) 429c5290f63SChristian Marangi goto out; 430c5290f63SChristian Marangi ret = qca8k_vlan_access(priv, QCA8K_VLAN_LOAD, vid); 431c5290f63SChristian Marangi } 432c5290f63SChristian Marangi 433c5290f63SChristian Marangi out: 434c5290f63SChristian Marangi mutex_unlock(&priv->reg_mutex); 435c5290f63SChristian Marangi 436c5290f63SChristian Marangi return ret; 437c5290f63SChristian Marangi } 438c5290f63SChristian Marangi 439fce1ec0cSChristian Marangi int qca8k_mib_init(struct qca8k_priv *priv) 440fce1ec0cSChristian Marangi { 441fce1ec0cSChristian Marangi int ret; 442fce1ec0cSChristian Marangi 443fce1ec0cSChristian Marangi mutex_lock(&priv->reg_mutex); 444fce1ec0cSChristian Marangi ret = regmap_update_bits(priv->regmap, QCA8K_REG_MIB, 445fce1ec0cSChristian Marangi QCA8K_MIB_FUNC | QCA8K_MIB_BUSY, 446fce1ec0cSChristian Marangi FIELD_PREP(QCA8K_MIB_FUNC, QCA8K_MIB_FLUSH) | 447fce1ec0cSChristian Marangi QCA8K_MIB_BUSY); 448fce1ec0cSChristian Marangi if (ret) 449fce1ec0cSChristian Marangi goto exit; 450fce1ec0cSChristian Marangi 451fce1ec0cSChristian Marangi ret = qca8k_busy_wait(priv, QCA8K_REG_MIB, QCA8K_MIB_BUSY); 452fce1ec0cSChristian Marangi if (ret) 453fce1ec0cSChristian Marangi goto exit; 454fce1ec0cSChristian Marangi 455fce1ec0cSChristian Marangi ret = regmap_set_bits(priv->regmap, QCA8K_REG_MIB, QCA8K_MIB_CPU_KEEP); 456fce1ec0cSChristian Marangi if (ret) 457fce1ec0cSChristian Marangi goto exit; 458fce1ec0cSChristian Marangi 459fce1ec0cSChristian Marangi ret = qca8k_write(priv, QCA8K_REG_MODULE_EN, QCA8K_MODULE_EN_MIB); 460fce1ec0cSChristian Marangi 461fce1ec0cSChristian Marangi exit: 462fce1ec0cSChristian Marangi mutex_unlock(&priv->reg_mutex); 463fce1ec0cSChristian Marangi return ret; 464fce1ec0cSChristian Marangi } 465472fcea1SChristian Marangi 466472fcea1SChristian Marangi void qca8k_port_set_status(struct qca8k_priv *priv, int port, int enable) 467472fcea1SChristian Marangi { 468472fcea1SChristian Marangi u32 mask = QCA8K_PORT_STATUS_TXMAC | QCA8K_PORT_STATUS_RXMAC; 469472fcea1SChristian Marangi 470472fcea1SChristian Marangi /* Port 0 and 6 have no internal PHY */ 471472fcea1SChristian Marangi if (port > 0 && port < 6) 472472fcea1SChristian Marangi mask |= QCA8K_PORT_STATUS_LINK_AUTO; 473472fcea1SChristian Marangi 474472fcea1SChristian Marangi if (enable) 475472fcea1SChristian Marangi regmap_set_bits(priv->regmap, QCA8K_REG_PORT_STATUS(port), mask); 476472fcea1SChristian Marangi else 477472fcea1SChristian Marangi regmap_clear_bits(priv->regmap, QCA8K_REG_PORT_STATUS(port), mask); 478472fcea1SChristian Marangi } 479472fcea1SChristian Marangi 480472fcea1SChristian Marangi void qca8k_get_strings(struct dsa_switch *ds, int port, u32 stringset, 481472fcea1SChristian Marangi uint8_t *data) 482472fcea1SChristian Marangi { 483472fcea1SChristian Marangi struct qca8k_priv *priv = ds->priv; 484472fcea1SChristian Marangi int i; 485472fcea1SChristian Marangi 486472fcea1SChristian Marangi if (stringset != ETH_SS_STATS) 487472fcea1SChristian Marangi return; 488472fcea1SChristian Marangi 489472fcea1SChristian Marangi for (i = 0; i < priv->info->mib_count; i++) 490472fcea1SChristian Marangi strncpy(data + i * ETH_GSTRING_LEN, ar8327_mib[i].name, 491472fcea1SChristian Marangi ETH_GSTRING_LEN); 492472fcea1SChristian Marangi } 493472fcea1SChristian Marangi 494472fcea1SChristian Marangi void qca8k_get_ethtool_stats(struct dsa_switch *ds, int port, 495472fcea1SChristian Marangi uint64_t *data) 496472fcea1SChristian Marangi { 497472fcea1SChristian Marangi struct qca8k_priv *priv = ds->priv; 498472fcea1SChristian Marangi const struct qca8k_mib_desc *mib; 499472fcea1SChristian Marangi u32 reg, i, val; 500472fcea1SChristian Marangi u32 hi = 0; 501472fcea1SChristian Marangi int ret; 502472fcea1SChristian Marangi 503472fcea1SChristian Marangi if (priv->mgmt_master && priv->info->ops->autocast_mib && 504472fcea1SChristian Marangi priv->info->ops->autocast_mib(ds, port, data) > 0) 505472fcea1SChristian Marangi return; 506472fcea1SChristian Marangi 507472fcea1SChristian Marangi for (i = 0; i < priv->info->mib_count; i++) { 508472fcea1SChristian Marangi mib = &ar8327_mib[i]; 509472fcea1SChristian Marangi reg = QCA8K_PORT_MIB_COUNTER(port) + mib->offset; 510472fcea1SChristian Marangi 511472fcea1SChristian Marangi ret = qca8k_read(priv, reg, &val); 512472fcea1SChristian Marangi if (ret < 0) 513472fcea1SChristian Marangi continue; 514472fcea1SChristian Marangi 515472fcea1SChristian Marangi if (mib->size == 2) { 516472fcea1SChristian Marangi ret = qca8k_read(priv, reg + 4, &hi); 517472fcea1SChristian Marangi if (ret < 0) 518472fcea1SChristian Marangi continue; 519472fcea1SChristian Marangi } 520472fcea1SChristian Marangi 521472fcea1SChristian Marangi data[i] = val; 522472fcea1SChristian Marangi if (mib->size == 2) 523472fcea1SChristian Marangi data[i] |= (u64)hi << 32; 524472fcea1SChristian Marangi } 525472fcea1SChristian Marangi } 526472fcea1SChristian Marangi 527472fcea1SChristian Marangi int qca8k_get_sset_count(struct dsa_switch *ds, int port, int sset) 528472fcea1SChristian Marangi { 529472fcea1SChristian Marangi struct qca8k_priv *priv = ds->priv; 530472fcea1SChristian Marangi 531472fcea1SChristian Marangi if (sset != ETH_SS_STATS) 532472fcea1SChristian Marangi return 0; 533472fcea1SChristian Marangi 534472fcea1SChristian Marangi return priv->info->mib_count; 535472fcea1SChristian Marangi } 536472fcea1SChristian Marangi 537472fcea1SChristian Marangi int qca8k_set_mac_eee(struct dsa_switch *ds, int port, 538472fcea1SChristian Marangi struct ethtool_eee *eee) 539472fcea1SChristian Marangi { 540472fcea1SChristian Marangi u32 lpi_en = QCA8K_REG_EEE_CTRL_LPI_EN(port); 541472fcea1SChristian Marangi struct qca8k_priv *priv = ds->priv; 542472fcea1SChristian Marangi u32 reg; 543472fcea1SChristian Marangi int ret; 544472fcea1SChristian Marangi 545472fcea1SChristian Marangi mutex_lock(&priv->reg_mutex); 546472fcea1SChristian Marangi ret = qca8k_read(priv, QCA8K_REG_EEE_CTRL, ®); 547472fcea1SChristian Marangi if (ret < 0) 548472fcea1SChristian Marangi goto exit; 549472fcea1SChristian Marangi 550472fcea1SChristian Marangi if (eee->eee_enabled) 551472fcea1SChristian Marangi reg |= lpi_en; 552472fcea1SChristian Marangi else 553472fcea1SChristian Marangi reg &= ~lpi_en; 554472fcea1SChristian Marangi ret = qca8k_write(priv, QCA8K_REG_EEE_CTRL, reg); 555472fcea1SChristian Marangi 556472fcea1SChristian Marangi exit: 557472fcea1SChristian Marangi mutex_unlock(&priv->reg_mutex); 558472fcea1SChristian Marangi return ret; 559472fcea1SChristian Marangi } 560472fcea1SChristian Marangi 561472fcea1SChristian Marangi int qca8k_get_mac_eee(struct dsa_switch *ds, int port, 562472fcea1SChristian Marangi struct ethtool_eee *e) 563472fcea1SChristian Marangi { 564472fcea1SChristian Marangi /* Nothing to do on the port's MAC */ 565472fcea1SChristian Marangi return 0; 566472fcea1SChristian Marangi } 567fd3cae2fSChristian Marangi 568fd3cae2fSChristian Marangi void qca8k_port_stp_state_set(struct dsa_switch *ds, int port, u8 state) 569fd3cae2fSChristian Marangi { 570fd3cae2fSChristian Marangi struct qca8k_priv *priv = ds->priv; 571fd3cae2fSChristian Marangi u32 stp_state; 572fd3cae2fSChristian Marangi 573fd3cae2fSChristian Marangi switch (state) { 574fd3cae2fSChristian Marangi case BR_STATE_DISABLED: 575fd3cae2fSChristian Marangi stp_state = QCA8K_PORT_LOOKUP_STATE_DISABLED; 576fd3cae2fSChristian Marangi break; 577fd3cae2fSChristian Marangi case BR_STATE_BLOCKING: 578fd3cae2fSChristian Marangi stp_state = QCA8K_PORT_LOOKUP_STATE_BLOCKING; 579fd3cae2fSChristian Marangi break; 580fd3cae2fSChristian Marangi case BR_STATE_LISTENING: 581fd3cae2fSChristian Marangi stp_state = QCA8K_PORT_LOOKUP_STATE_LISTENING; 582fd3cae2fSChristian Marangi break; 583fd3cae2fSChristian Marangi case BR_STATE_LEARNING: 584fd3cae2fSChristian Marangi stp_state = QCA8K_PORT_LOOKUP_STATE_LEARNING; 585fd3cae2fSChristian Marangi break; 586fd3cae2fSChristian Marangi case BR_STATE_FORWARDING: 587fd3cae2fSChristian Marangi default: 588fd3cae2fSChristian Marangi stp_state = QCA8K_PORT_LOOKUP_STATE_FORWARD; 589fd3cae2fSChristian Marangi break; 590fd3cae2fSChristian Marangi } 591fd3cae2fSChristian Marangi 592fd3cae2fSChristian Marangi qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port), 593fd3cae2fSChristian Marangi QCA8K_PORT_LOOKUP_STATE_MASK, stp_state); 594fd3cae2fSChristian Marangi } 595fd3cae2fSChristian Marangi 596fd3cae2fSChristian Marangi int qca8k_port_bridge_join(struct dsa_switch *ds, int port, 597fd3cae2fSChristian Marangi struct dsa_bridge bridge, 598fd3cae2fSChristian Marangi bool *tx_fwd_offload, 599fd3cae2fSChristian Marangi struct netlink_ext_ack *extack) 600fd3cae2fSChristian Marangi { 601fd3cae2fSChristian Marangi struct qca8k_priv *priv = ds->priv; 602fd3cae2fSChristian Marangi int port_mask, cpu_port; 603fd3cae2fSChristian Marangi int i, ret; 604fd3cae2fSChristian Marangi 605fd3cae2fSChristian Marangi cpu_port = dsa_to_port(ds, port)->cpu_dp->index; 606fd3cae2fSChristian Marangi port_mask = BIT(cpu_port); 607fd3cae2fSChristian Marangi 608fd3cae2fSChristian Marangi for (i = 0; i < QCA8K_NUM_PORTS; i++) { 609fd3cae2fSChristian Marangi if (dsa_is_cpu_port(ds, i)) 610fd3cae2fSChristian Marangi continue; 611fd3cae2fSChristian Marangi if (!dsa_port_offloads_bridge(dsa_to_port(ds, i), &bridge)) 612fd3cae2fSChristian Marangi continue; 613fd3cae2fSChristian Marangi /* Add this port to the portvlan mask of the other ports 614fd3cae2fSChristian Marangi * in the bridge 615fd3cae2fSChristian Marangi */ 616fd3cae2fSChristian Marangi ret = regmap_set_bits(priv->regmap, 617fd3cae2fSChristian Marangi QCA8K_PORT_LOOKUP_CTRL(i), 618fd3cae2fSChristian Marangi BIT(port)); 619fd3cae2fSChristian Marangi if (ret) 620fd3cae2fSChristian Marangi return ret; 621fd3cae2fSChristian Marangi if (i != port) 622fd3cae2fSChristian Marangi port_mask |= BIT(i); 623fd3cae2fSChristian Marangi } 624fd3cae2fSChristian Marangi 625fd3cae2fSChristian Marangi /* Add all other ports to this ports portvlan mask */ 626fd3cae2fSChristian Marangi ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port), 627fd3cae2fSChristian Marangi QCA8K_PORT_LOOKUP_MEMBER, port_mask); 628fd3cae2fSChristian Marangi 629fd3cae2fSChristian Marangi return ret; 630fd3cae2fSChristian Marangi } 631fd3cae2fSChristian Marangi 632fd3cae2fSChristian Marangi void qca8k_port_bridge_leave(struct dsa_switch *ds, int port, 633fd3cae2fSChristian Marangi struct dsa_bridge bridge) 634fd3cae2fSChristian Marangi { 635fd3cae2fSChristian Marangi struct qca8k_priv *priv = ds->priv; 636fd3cae2fSChristian Marangi int cpu_port, i; 637fd3cae2fSChristian Marangi 638fd3cae2fSChristian Marangi cpu_port = dsa_to_port(ds, port)->cpu_dp->index; 639fd3cae2fSChristian Marangi 640fd3cae2fSChristian Marangi for (i = 0; i < QCA8K_NUM_PORTS; i++) { 641fd3cae2fSChristian Marangi if (dsa_is_cpu_port(ds, i)) 642fd3cae2fSChristian Marangi continue; 643fd3cae2fSChristian Marangi if (!dsa_port_offloads_bridge(dsa_to_port(ds, i), &bridge)) 644fd3cae2fSChristian Marangi continue; 645fd3cae2fSChristian Marangi /* Remove this port to the portvlan mask of the other ports 646fd3cae2fSChristian Marangi * in the bridge 647fd3cae2fSChristian Marangi */ 648fd3cae2fSChristian Marangi regmap_clear_bits(priv->regmap, 649fd3cae2fSChristian Marangi QCA8K_PORT_LOOKUP_CTRL(i), 650fd3cae2fSChristian Marangi BIT(port)); 651fd3cae2fSChristian Marangi } 652fd3cae2fSChristian Marangi 653fd3cae2fSChristian Marangi /* Set the cpu port to be the only one in the portvlan mask of 654fd3cae2fSChristian Marangi * this port 655fd3cae2fSChristian Marangi */ 656fd3cae2fSChristian Marangi qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port), 657fd3cae2fSChristian Marangi QCA8K_PORT_LOOKUP_MEMBER, BIT(cpu_port)); 658fd3cae2fSChristian Marangi } 659b3a302b1SChristian Marangi 6602e5bd96eSChristian Marangi void qca8k_port_fast_age(struct dsa_switch *ds, int port) 6612e5bd96eSChristian Marangi { 6622e5bd96eSChristian Marangi struct qca8k_priv *priv = ds->priv; 6632e5bd96eSChristian Marangi 6642e5bd96eSChristian Marangi mutex_lock(&priv->reg_mutex); 6652e5bd96eSChristian Marangi qca8k_fdb_access(priv, QCA8K_FDB_FLUSH_PORT, port); 6662e5bd96eSChristian Marangi mutex_unlock(&priv->reg_mutex); 6672e5bd96eSChristian Marangi } 6682e5bd96eSChristian Marangi 669b3a302b1SChristian Marangi int qca8k_set_ageing_time(struct dsa_switch *ds, unsigned int msecs) 670b3a302b1SChristian Marangi { 671b3a302b1SChristian Marangi struct qca8k_priv *priv = ds->priv; 672b3a302b1SChristian Marangi unsigned int secs = msecs / 1000; 673b3a302b1SChristian Marangi u32 val; 674b3a302b1SChristian Marangi 675b3a302b1SChristian Marangi /* AGE_TIME reg is set in 7s step */ 676b3a302b1SChristian Marangi val = secs / 7; 677b3a302b1SChristian Marangi 678b3a302b1SChristian Marangi /* Handle case with 0 as val to NOT disable 679b3a302b1SChristian Marangi * learning 680b3a302b1SChristian Marangi */ 681b3a302b1SChristian Marangi if (!val) 682b3a302b1SChristian Marangi val = 1; 683b3a302b1SChristian Marangi 684b3a302b1SChristian Marangi return regmap_update_bits(priv->regmap, QCA8K_REG_ATU_CTRL, 685b3a302b1SChristian Marangi QCA8K_ATU_AGE_TIME_MASK, 686b3a302b1SChristian Marangi QCA8K_ATU_AGE_TIME(val)); 687b3a302b1SChristian Marangi } 688b3a302b1SChristian Marangi 689b3a302b1SChristian Marangi int qca8k_port_enable(struct dsa_switch *ds, int port, 690b3a302b1SChristian Marangi struct phy_device *phy) 691b3a302b1SChristian Marangi { 692b3a302b1SChristian Marangi struct qca8k_priv *priv = ds->priv; 693b3a302b1SChristian Marangi 694b3a302b1SChristian Marangi qca8k_port_set_status(priv, port, 1); 695b3a302b1SChristian Marangi priv->port_enabled_map |= BIT(port); 696b3a302b1SChristian Marangi 697b3a302b1SChristian Marangi if (dsa_is_user_port(ds, port)) 698b3a302b1SChristian Marangi phy_support_asym_pause(phy); 699b3a302b1SChristian Marangi 700b3a302b1SChristian Marangi return 0; 701b3a302b1SChristian Marangi } 702b3a302b1SChristian Marangi 703b3a302b1SChristian Marangi void qca8k_port_disable(struct dsa_switch *ds, int port) 704b3a302b1SChristian Marangi { 705b3a302b1SChristian Marangi struct qca8k_priv *priv = ds->priv; 706b3a302b1SChristian Marangi 707b3a302b1SChristian Marangi qca8k_port_set_status(priv, port, 0); 708b3a302b1SChristian Marangi priv->port_enabled_map &= ~BIT(port); 709b3a302b1SChristian Marangi } 710b3a302b1SChristian Marangi 711b3a302b1SChristian Marangi int qca8k_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu) 712b3a302b1SChristian Marangi { 713b3a302b1SChristian Marangi struct qca8k_priv *priv = ds->priv; 714b3a302b1SChristian Marangi int ret; 715b3a302b1SChristian Marangi 716b3a302b1SChristian Marangi /* We have only have a general MTU setting. 717b3a302b1SChristian Marangi * DSA always set the CPU port's MTU to the largest MTU of the slave 718b3a302b1SChristian Marangi * ports. 719b3a302b1SChristian Marangi * Setting MTU just for the CPU port is sufficient to correctly set a 720b3a302b1SChristian Marangi * value for every port. 721b3a302b1SChristian Marangi */ 722b3a302b1SChristian Marangi if (!dsa_is_cpu_port(ds, port)) 723b3a302b1SChristian Marangi return 0; 724b3a302b1SChristian Marangi 725b3a302b1SChristian Marangi /* To change the MAX_FRAME_SIZE the cpu ports must be off or 726b3a302b1SChristian Marangi * the switch panics. 727b3a302b1SChristian Marangi * Turn off both cpu ports before applying the new value to prevent 728b3a302b1SChristian Marangi * this. 729b3a302b1SChristian Marangi */ 730b3a302b1SChristian Marangi if (priv->port_enabled_map & BIT(0)) 731b3a302b1SChristian Marangi qca8k_port_set_status(priv, 0, 0); 732b3a302b1SChristian Marangi 733b3a302b1SChristian Marangi if (priv->port_enabled_map & BIT(6)) 734b3a302b1SChristian Marangi qca8k_port_set_status(priv, 6, 0); 735b3a302b1SChristian Marangi 736b3a302b1SChristian Marangi /* Include L2 header / FCS length */ 737b3a302b1SChristian Marangi ret = qca8k_write(priv, QCA8K_MAX_FRAME_SIZE, new_mtu + 738b3a302b1SChristian Marangi ETH_HLEN + ETH_FCS_LEN); 739b3a302b1SChristian Marangi 740b3a302b1SChristian Marangi if (priv->port_enabled_map & BIT(0)) 741b3a302b1SChristian Marangi qca8k_port_set_status(priv, 0, 1); 742b3a302b1SChristian Marangi 743b3a302b1SChristian Marangi if (priv->port_enabled_map & BIT(6)) 744b3a302b1SChristian Marangi qca8k_port_set_status(priv, 6, 1); 745b3a302b1SChristian Marangi 746b3a302b1SChristian Marangi return ret; 747b3a302b1SChristian Marangi } 748b3a302b1SChristian Marangi 749b3a302b1SChristian Marangi int qca8k_port_max_mtu(struct dsa_switch *ds, int port) 750b3a302b1SChristian Marangi { 751b3a302b1SChristian Marangi return QCA8K_MAX_MTU; 752b3a302b1SChristian Marangi } 7532e5bd96eSChristian Marangi 7542e5bd96eSChristian Marangi int qca8k_port_fdb_insert(struct qca8k_priv *priv, const u8 *addr, 7552e5bd96eSChristian Marangi u16 port_mask, u16 vid) 7562e5bd96eSChristian Marangi { 7572e5bd96eSChristian Marangi /* Set the vid to the port vlan id if no vid is set */ 7582e5bd96eSChristian Marangi if (!vid) 7592e5bd96eSChristian Marangi vid = QCA8K_PORT_VID_DEF; 7602e5bd96eSChristian Marangi 7612e5bd96eSChristian Marangi return qca8k_fdb_add(priv, addr, port_mask, vid, 7622e5bd96eSChristian Marangi QCA8K_ATU_STATUS_STATIC); 7632e5bd96eSChristian Marangi } 7642e5bd96eSChristian Marangi 7652e5bd96eSChristian Marangi int qca8k_port_fdb_add(struct dsa_switch *ds, int port, 7662e5bd96eSChristian Marangi const unsigned char *addr, u16 vid, 7672e5bd96eSChristian Marangi struct dsa_db db) 7682e5bd96eSChristian Marangi { 76992db9e2eSAtin Bainada struct qca8k_priv *priv = ds->priv; 7702e5bd96eSChristian Marangi u16 port_mask = BIT(port); 7712e5bd96eSChristian Marangi 7722e5bd96eSChristian Marangi return qca8k_port_fdb_insert(priv, addr, port_mask, vid); 7732e5bd96eSChristian Marangi } 7742e5bd96eSChristian Marangi 7752e5bd96eSChristian Marangi int qca8k_port_fdb_del(struct dsa_switch *ds, int port, 7762e5bd96eSChristian Marangi const unsigned char *addr, u16 vid, 7772e5bd96eSChristian Marangi struct dsa_db db) 7782e5bd96eSChristian Marangi { 77992db9e2eSAtin Bainada struct qca8k_priv *priv = ds->priv; 7802e5bd96eSChristian Marangi u16 port_mask = BIT(port); 7812e5bd96eSChristian Marangi 7822e5bd96eSChristian Marangi if (!vid) 7832e5bd96eSChristian Marangi vid = QCA8K_PORT_VID_DEF; 7842e5bd96eSChristian Marangi 7852e5bd96eSChristian Marangi return qca8k_fdb_del(priv, addr, port_mask, vid); 7862e5bd96eSChristian Marangi } 7872e5bd96eSChristian Marangi 7882e5bd96eSChristian Marangi int qca8k_port_fdb_dump(struct dsa_switch *ds, int port, 7892e5bd96eSChristian Marangi dsa_fdb_dump_cb_t *cb, void *data) 7902e5bd96eSChristian Marangi { 79192db9e2eSAtin Bainada struct qca8k_priv *priv = ds->priv; 7922e5bd96eSChristian Marangi struct qca8k_fdb _fdb = { 0 }; 7932e5bd96eSChristian Marangi int cnt = QCA8K_NUM_FDB_RECORDS; 7942e5bd96eSChristian Marangi bool is_static; 7952e5bd96eSChristian Marangi int ret = 0; 7962e5bd96eSChristian Marangi 7972e5bd96eSChristian Marangi mutex_lock(&priv->reg_mutex); 7982e5bd96eSChristian Marangi while (cnt-- && !qca8k_fdb_next(priv, &_fdb, port)) { 7992e5bd96eSChristian Marangi if (!_fdb.aging) 8002e5bd96eSChristian Marangi break; 8012e5bd96eSChristian Marangi is_static = (_fdb.aging == QCA8K_ATU_STATUS_STATIC); 8022e5bd96eSChristian Marangi ret = cb(_fdb.mac, _fdb.vid, is_static, data); 8032e5bd96eSChristian Marangi if (ret) 8042e5bd96eSChristian Marangi break; 8052e5bd96eSChristian Marangi } 8062e5bd96eSChristian Marangi mutex_unlock(&priv->reg_mutex); 8072e5bd96eSChristian Marangi 8082e5bd96eSChristian Marangi return 0; 8092e5bd96eSChristian Marangi } 8102e5bd96eSChristian Marangi 8112e5bd96eSChristian Marangi int qca8k_port_mdb_add(struct dsa_switch *ds, int port, 8122e5bd96eSChristian Marangi const struct switchdev_obj_port_mdb *mdb, 8132e5bd96eSChristian Marangi struct dsa_db db) 8142e5bd96eSChristian Marangi { 8152e5bd96eSChristian Marangi struct qca8k_priv *priv = ds->priv; 8162e5bd96eSChristian Marangi const u8 *addr = mdb->addr; 8172e5bd96eSChristian Marangi u16 vid = mdb->vid; 8182e5bd96eSChristian Marangi 81980248d41SChristian Marangi return qca8k_fdb_search_and_insert(priv, BIT(port), addr, vid, 82080248d41SChristian Marangi QCA8K_ATU_STATUS_STATIC); 8212e5bd96eSChristian Marangi } 8222e5bd96eSChristian Marangi 8232e5bd96eSChristian Marangi int qca8k_port_mdb_del(struct dsa_switch *ds, int port, 8242e5bd96eSChristian Marangi const struct switchdev_obj_port_mdb *mdb, 8252e5bd96eSChristian Marangi struct dsa_db db) 8262e5bd96eSChristian Marangi { 8272e5bd96eSChristian Marangi struct qca8k_priv *priv = ds->priv; 8282e5bd96eSChristian Marangi const u8 *addr = mdb->addr; 8292e5bd96eSChristian Marangi u16 vid = mdb->vid; 8302e5bd96eSChristian Marangi 8312e5bd96eSChristian Marangi return qca8k_fdb_search_and_del(priv, BIT(port), addr, vid); 8322e5bd96eSChristian Marangi } 833742d37a8SChristian Marangi 834742d37a8SChristian Marangi int qca8k_port_mirror_add(struct dsa_switch *ds, int port, 835742d37a8SChristian Marangi struct dsa_mall_mirror_tc_entry *mirror, 836742d37a8SChristian Marangi bool ingress, struct netlink_ext_ack *extack) 837742d37a8SChristian Marangi { 838742d37a8SChristian Marangi struct qca8k_priv *priv = ds->priv; 839742d37a8SChristian Marangi int monitor_port, ret; 840742d37a8SChristian Marangi u32 reg, val; 841742d37a8SChristian Marangi 842742d37a8SChristian Marangi /* Check for existent entry */ 843742d37a8SChristian Marangi if ((ingress ? priv->mirror_rx : priv->mirror_tx) & BIT(port)) 844742d37a8SChristian Marangi return -EEXIST; 845742d37a8SChristian Marangi 846742d37a8SChristian Marangi ret = regmap_read(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0, &val); 847742d37a8SChristian Marangi if (ret) 848742d37a8SChristian Marangi return ret; 849742d37a8SChristian Marangi 850742d37a8SChristian Marangi /* QCA83xx can have only one port set to mirror mode. 851742d37a8SChristian Marangi * Check that the correct port is requested and return error otherwise. 852742d37a8SChristian Marangi * When no mirror port is set, the values is set to 0xF 853742d37a8SChristian Marangi */ 854742d37a8SChristian Marangi monitor_port = FIELD_GET(QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, val); 855742d37a8SChristian Marangi if (monitor_port != 0xF && monitor_port != mirror->to_local_port) 856742d37a8SChristian Marangi return -EEXIST; 857742d37a8SChristian Marangi 858742d37a8SChristian Marangi /* Set the monitor port */ 859742d37a8SChristian Marangi val = FIELD_PREP(QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, 860742d37a8SChristian Marangi mirror->to_local_port); 861742d37a8SChristian Marangi ret = regmap_update_bits(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0, 862742d37a8SChristian Marangi QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, val); 863742d37a8SChristian Marangi if (ret) 864742d37a8SChristian Marangi return ret; 865742d37a8SChristian Marangi 866742d37a8SChristian Marangi if (ingress) { 867742d37a8SChristian Marangi reg = QCA8K_PORT_LOOKUP_CTRL(port); 868742d37a8SChristian Marangi val = QCA8K_PORT_LOOKUP_ING_MIRROR_EN; 869742d37a8SChristian Marangi } else { 870742d37a8SChristian Marangi reg = QCA8K_REG_PORT_HOL_CTRL1(port); 871742d37a8SChristian Marangi val = QCA8K_PORT_HOL_CTRL1_EG_MIRROR_EN; 872742d37a8SChristian Marangi } 873742d37a8SChristian Marangi 874742d37a8SChristian Marangi ret = regmap_update_bits(priv->regmap, reg, val, val); 875742d37a8SChristian Marangi if (ret) 876742d37a8SChristian Marangi return ret; 877742d37a8SChristian Marangi 878742d37a8SChristian Marangi /* Track mirror port for tx and rx to decide when the 879742d37a8SChristian Marangi * mirror port has to be disabled. 880742d37a8SChristian Marangi */ 881742d37a8SChristian Marangi if (ingress) 882742d37a8SChristian Marangi priv->mirror_rx |= BIT(port); 883742d37a8SChristian Marangi else 884742d37a8SChristian Marangi priv->mirror_tx |= BIT(port); 885742d37a8SChristian Marangi 886742d37a8SChristian Marangi return 0; 887742d37a8SChristian Marangi } 888742d37a8SChristian Marangi 889742d37a8SChristian Marangi void qca8k_port_mirror_del(struct dsa_switch *ds, int port, 890742d37a8SChristian Marangi struct dsa_mall_mirror_tc_entry *mirror) 891742d37a8SChristian Marangi { 892742d37a8SChristian Marangi struct qca8k_priv *priv = ds->priv; 893742d37a8SChristian Marangi u32 reg, val; 894742d37a8SChristian Marangi int ret; 895742d37a8SChristian Marangi 896742d37a8SChristian Marangi if (mirror->ingress) { 897742d37a8SChristian Marangi reg = QCA8K_PORT_LOOKUP_CTRL(port); 898742d37a8SChristian Marangi val = QCA8K_PORT_LOOKUP_ING_MIRROR_EN; 899742d37a8SChristian Marangi } else { 900742d37a8SChristian Marangi reg = QCA8K_REG_PORT_HOL_CTRL1(port); 901742d37a8SChristian Marangi val = QCA8K_PORT_HOL_CTRL1_EG_MIRROR_EN; 902742d37a8SChristian Marangi } 903742d37a8SChristian Marangi 904742d37a8SChristian Marangi ret = regmap_clear_bits(priv->regmap, reg, val); 905742d37a8SChristian Marangi if (ret) 906742d37a8SChristian Marangi goto err; 907742d37a8SChristian Marangi 908742d37a8SChristian Marangi if (mirror->ingress) 909742d37a8SChristian Marangi priv->mirror_rx &= ~BIT(port); 910742d37a8SChristian Marangi else 911742d37a8SChristian Marangi priv->mirror_tx &= ~BIT(port); 912742d37a8SChristian Marangi 913742d37a8SChristian Marangi /* No port set to send packet to mirror port. Disable mirror port */ 914742d37a8SChristian Marangi if (!priv->mirror_rx && !priv->mirror_tx) { 915742d37a8SChristian Marangi val = FIELD_PREP(QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, 0xF); 916742d37a8SChristian Marangi ret = regmap_update_bits(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0, 917742d37a8SChristian Marangi QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, val); 918742d37a8SChristian Marangi if (ret) 919742d37a8SChristian Marangi goto err; 920742d37a8SChristian Marangi } 921742d37a8SChristian Marangi err: 922742d37a8SChristian Marangi dev_err(priv->dev, "Failed to del mirror port from %d", port); 923742d37a8SChristian Marangi } 924c5290f63SChristian Marangi 925c5290f63SChristian Marangi int qca8k_port_vlan_filtering(struct dsa_switch *ds, int port, 926c5290f63SChristian Marangi bool vlan_filtering, 927c5290f63SChristian Marangi struct netlink_ext_ack *extack) 928c5290f63SChristian Marangi { 929c5290f63SChristian Marangi struct qca8k_priv *priv = ds->priv; 930c5290f63SChristian Marangi int ret; 931c5290f63SChristian Marangi 932c5290f63SChristian Marangi if (vlan_filtering) { 933c5290f63SChristian Marangi ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port), 934c5290f63SChristian Marangi QCA8K_PORT_LOOKUP_VLAN_MODE_MASK, 935c5290f63SChristian Marangi QCA8K_PORT_LOOKUP_VLAN_MODE_SECURE); 936c5290f63SChristian Marangi } else { 937c5290f63SChristian Marangi ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port), 938c5290f63SChristian Marangi QCA8K_PORT_LOOKUP_VLAN_MODE_MASK, 939c5290f63SChristian Marangi QCA8K_PORT_LOOKUP_VLAN_MODE_NONE); 940c5290f63SChristian Marangi } 941c5290f63SChristian Marangi 942c5290f63SChristian Marangi return ret; 943c5290f63SChristian Marangi } 944c5290f63SChristian Marangi 945c5290f63SChristian Marangi int qca8k_port_vlan_add(struct dsa_switch *ds, int port, 946c5290f63SChristian Marangi const struct switchdev_obj_port_vlan *vlan, 947c5290f63SChristian Marangi struct netlink_ext_ack *extack) 948c5290f63SChristian Marangi { 949c5290f63SChristian Marangi bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED; 950c5290f63SChristian Marangi bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID; 951c5290f63SChristian Marangi struct qca8k_priv *priv = ds->priv; 952c5290f63SChristian Marangi int ret; 953c5290f63SChristian Marangi 954c5290f63SChristian Marangi ret = qca8k_vlan_add(priv, port, vlan->vid, untagged); 955c5290f63SChristian Marangi if (ret) { 956c5290f63SChristian Marangi dev_err(priv->dev, "Failed to add VLAN to port %d (%d)", port, ret); 957c5290f63SChristian Marangi return ret; 958c5290f63SChristian Marangi } 959c5290f63SChristian Marangi 960c5290f63SChristian Marangi if (pvid) { 961c5290f63SChristian Marangi ret = qca8k_rmw(priv, QCA8K_EGRESS_VLAN(port), 962c5290f63SChristian Marangi QCA8K_EGREES_VLAN_PORT_MASK(port), 963c5290f63SChristian Marangi QCA8K_EGREES_VLAN_PORT(port, vlan->vid)); 964c5290f63SChristian Marangi if (ret) 965c5290f63SChristian Marangi return ret; 966c5290f63SChristian Marangi 967c5290f63SChristian Marangi ret = qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(port), 968c5290f63SChristian Marangi QCA8K_PORT_VLAN_CVID(vlan->vid) | 969c5290f63SChristian Marangi QCA8K_PORT_VLAN_SVID(vlan->vid)); 970c5290f63SChristian Marangi } 971c5290f63SChristian Marangi 972c5290f63SChristian Marangi return ret; 973c5290f63SChristian Marangi } 974c5290f63SChristian Marangi 975c5290f63SChristian Marangi int qca8k_port_vlan_del(struct dsa_switch *ds, int port, 976c5290f63SChristian Marangi const struct switchdev_obj_port_vlan *vlan) 977c5290f63SChristian Marangi { 978c5290f63SChristian Marangi struct qca8k_priv *priv = ds->priv; 979c5290f63SChristian Marangi int ret; 980c5290f63SChristian Marangi 981c5290f63SChristian Marangi ret = qca8k_vlan_del(priv, port, vlan->vid); 982c5290f63SChristian Marangi if (ret) 983c5290f63SChristian Marangi dev_err(priv->dev, "Failed to delete VLAN from port %d (%d)", port, ret); 984c5290f63SChristian Marangi 985c5290f63SChristian Marangi return ret; 986c5290f63SChristian Marangi } 987e9bbf019SChristian Marangi 988e9bbf019SChristian Marangi static bool qca8k_lag_can_offload(struct dsa_switch *ds, 989e9bbf019SChristian Marangi struct dsa_lag lag, 9902e359b00SVladimir Oltean struct netdev_lag_upper_info *info, 9912e359b00SVladimir Oltean struct netlink_ext_ack *extack) 992e9bbf019SChristian Marangi { 993e9bbf019SChristian Marangi struct dsa_port *dp; 994e9bbf019SChristian Marangi int members = 0; 995e9bbf019SChristian Marangi 996e9bbf019SChristian Marangi if (!lag.id) 997e9bbf019SChristian Marangi return false; 998e9bbf019SChristian Marangi 999e9bbf019SChristian Marangi dsa_lag_foreach_port(dp, ds->dst, &lag) 1000e9bbf019SChristian Marangi /* Includes the port joining the LAG */ 1001e9bbf019SChristian Marangi members++; 1002e9bbf019SChristian Marangi 10032e359b00SVladimir Oltean if (members > QCA8K_NUM_PORTS_FOR_LAG) { 10042e359b00SVladimir Oltean NL_SET_ERR_MSG_MOD(extack, 10052e359b00SVladimir Oltean "Cannot offload more than 4 LAG ports"); 1006e9bbf019SChristian Marangi return false; 10072e359b00SVladimir Oltean } 1008e9bbf019SChristian Marangi 10092e359b00SVladimir Oltean if (info->tx_type != NETDEV_LAG_TX_TYPE_HASH) { 10102e359b00SVladimir Oltean NL_SET_ERR_MSG_MOD(extack, 10112e359b00SVladimir Oltean "Can only offload LAG using hash TX type"); 1012e9bbf019SChristian Marangi return false; 10132e359b00SVladimir Oltean } 1014e9bbf019SChristian Marangi 1015e9bbf019SChristian Marangi if (info->hash_type != NETDEV_LAG_HASH_L2 && 10162e359b00SVladimir Oltean info->hash_type != NETDEV_LAG_HASH_L23) { 10172e359b00SVladimir Oltean NL_SET_ERR_MSG_MOD(extack, 10182e359b00SVladimir Oltean "Can only offload L2 or L2+L3 TX hash"); 1019e9bbf019SChristian Marangi return false; 10202e359b00SVladimir Oltean } 1021e9bbf019SChristian Marangi 1022e9bbf019SChristian Marangi return true; 1023e9bbf019SChristian Marangi } 1024e9bbf019SChristian Marangi 1025e9bbf019SChristian Marangi static int qca8k_lag_setup_hash(struct dsa_switch *ds, 1026e9bbf019SChristian Marangi struct dsa_lag lag, 1027e9bbf019SChristian Marangi struct netdev_lag_upper_info *info) 1028e9bbf019SChristian Marangi { 1029e9bbf019SChristian Marangi struct net_device *lag_dev = lag.dev; 1030e9bbf019SChristian Marangi struct qca8k_priv *priv = ds->priv; 1031e9bbf019SChristian Marangi bool unique_lag = true; 1032e9bbf019SChristian Marangi unsigned int i; 1033e9bbf019SChristian Marangi u32 hash = 0; 1034e9bbf019SChristian Marangi 1035e9bbf019SChristian Marangi switch (info->hash_type) { 1036e9bbf019SChristian Marangi case NETDEV_LAG_HASH_L23: 1037e9bbf019SChristian Marangi hash |= QCA8K_TRUNK_HASH_SIP_EN; 1038e9bbf019SChristian Marangi hash |= QCA8K_TRUNK_HASH_DIP_EN; 1039e9bbf019SChristian Marangi fallthrough; 1040e9bbf019SChristian Marangi case NETDEV_LAG_HASH_L2: 1041e9bbf019SChristian Marangi hash |= QCA8K_TRUNK_HASH_SA_EN; 1042e9bbf019SChristian Marangi hash |= QCA8K_TRUNK_HASH_DA_EN; 1043e9bbf019SChristian Marangi break; 1044e9bbf019SChristian Marangi default: /* We should NEVER reach this */ 1045e9bbf019SChristian Marangi return -EOPNOTSUPP; 1046e9bbf019SChristian Marangi } 1047e9bbf019SChristian Marangi 1048e9bbf019SChristian Marangi /* Check if we are the unique configured LAG */ 1049e9bbf019SChristian Marangi dsa_lags_foreach_id(i, ds->dst) 1050e9bbf019SChristian Marangi if (i != lag.id && dsa_lag_by_id(ds->dst, i)) { 1051e9bbf019SChristian Marangi unique_lag = false; 1052e9bbf019SChristian Marangi break; 1053e9bbf019SChristian Marangi } 1054e9bbf019SChristian Marangi 1055e9bbf019SChristian Marangi /* Hash Mode is global. Make sure the same Hash Mode 1056e9bbf019SChristian Marangi * is set to all the 4 possible lag. 1057e9bbf019SChristian Marangi * If we are the unique LAG we can set whatever hash 1058e9bbf019SChristian Marangi * mode we want. 1059e9bbf019SChristian Marangi * To change hash mode it's needed to remove all LAG 1060e9bbf019SChristian Marangi * and change the mode with the latest. 1061e9bbf019SChristian Marangi */ 1062e9bbf019SChristian Marangi if (unique_lag) { 1063e9bbf019SChristian Marangi priv->lag_hash_mode = hash; 1064e9bbf019SChristian Marangi } else if (priv->lag_hash_mode != hash) { 1065e9bbf019SChristian Marangi netdev_err(lag_dev, "Error: Mismatched Hash Mode across different lag is not supported\n"); 1066e9bbf019SChristian Marangi return -EOPNOTSUPP; 1067e9bbf019SChristian Marangi } 1068e9bbf019SChristian Marangi 1069e9bbf019SChristian Marangi return regmap_update_bits(priv->regmap, QCA8K_TRUNK_HASH_EN_CTRL, 1070e9bbf019SChristian Marangi QCA8K_TRUNK_HASH_MASK, hash); 1071e9bbf019SChristian Marangi } 1072e9bbf019SChristian Marangi 1073e9bbf019SChristian Marangi static int qca8k_lag_refresh_portmap(struct dsa_switch *ds, int port, 1074e9bbf019SChristian Marangi struct dsa_lag lag, bool delete) 1075e9bbf019SChristian Marangi { 1076e9bbf019SChristian Marangi struct qca8k_priv *priv = ds->priv; 1077e9bbf019SChristian Marangi int ret, id, i; 1078e9bbf019SChristian Marangi u32 val; 1079e9bbf019SChristian Marangi 1080e9bbf019SChristian Marangi /* DSA LAG IDs are one-based, hardware is zero-based */ 1081e9bbf019SChristian Marangi id = lag.id - 1; 1082e9bbf019SChristian Marangi 1083e9bbf019SChristian Marangi /* Read current port member */ 1084e9bbf019SChristian Marangi ret = regmap_read(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL0, &val); 1085e9bbf019SChristian Marangi if (ret) 1086e9bbf019SChristian Marangi return ret; 1087e9bbf019SChristian Marangi 1088e9bbf019SChristian Marangi /* Shift val to the correct trunk */ 1089e9bbf019SChristian Marangi val >>= QCA8K_REG_GOL_TRUNK_SHIFT(id); 1090e9bbf019SChristian Marangi val &= QCA8K_REG_GOL_TRUNK_MEMBER_MASK; 1091e9bbf019SChristian Marangi if (delete) 1092e9bbf019SChristian Marangi val &= ~BIT(port); 1093e9bbf019SChristian Marangi else 1094e9bbf019SChristian Marangi val |= BIT(port); 1095e9bbf019SChristian Marangi 1096e9bbf019SChristian Marangi /* Update port member. With empty portmap disable trunk */ 1097e9bbf019SChristian Marangi ret = regmap_update_bits(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL0, 1098e9bbf019SChristian Marangi QCA8K_REG_GOL_TRUNK_MEMBER(id) | 1099e9bbf019SChristian Marangi QCA8K_REG_GOL_TRUNK_EN(id), 1100e9bbf019SChristian Marangi !val << QCA8K_REG_GOL_TRUNK_SHIFT(id) | 1101e9bbf019SChristian Marangi val << QCA8K_REG_GOL_TRUNK_SHIFT(id)); 1102e9bbf019SChristian Marangi 1103e9bbf019SChristian Marangi /* Search empty member if adding or port on deleting */ 1104e9bbf019SChristian Marangi for (i = 0; i < QCA8K_NUM_PORTS_FOR_LAG; i++) { 1105e9bbf019SChristian Marangi ret = regmap_read(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL(id), &val); 1106e9bbf019SChristian Marangi if (ret) 1107e9bbf019SChristian Marangi return ret; 1108e9bbf019SChristian Marangi 1109e9bbf019SChristian Marangi val >>= QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(id, i); 1110e9bbf019SChristian Marangi val &= QCA8K_REG_GOL_TRUNK_ID_MEM_ID_MASK; 1111e9bbf019SChristian Marangi 1112e9bbf019SChristian Marangi if (delete) { 1113e9bbf019SChristian Marangi /* If port flagged to be disabled assume this member is 1114e9bbf019SChristian Marangi * empty 1115e9bbf019SChristian Marangi */ 1116e9bbf019SChristian Marangi if (val != QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN_MASK) 1117e9bbf019SChristian Marangi continue; 1118e9bbf019SChristian Marangi 1119e9bbf019SChristian Marangi val &= QCA8K_REG_GOL_TRUNK_ID_MEM_ID_PORT_MASK; 1120e9bbf019SChristian Marangi if (val != port) 1121e9bbf019SChristian Marangi continue; 1122e9bbf019SChristian Marangi } else { 1123e9bbf019SChristian Marangi /* If port flagged to be enabled assume this member is 1124e9bbf019SChristian Marangi * already set 1125e9bbf019SChristian Marangi */ 1126e9bbf019SChristian Marangi if (val == QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN_MASK) 1127e9bbf019SChristian Marangi continue; 1128e9bbf019SChristian Marangi } 1129e9bbf019SChristian Marangi 1130e9bbf019SChristian Marangi /* We have found the member to add/remove */ 1131e9bbf019SChristian Marangi break; 1132e9bbf019SChristian Marangi } 1133e9bbf019SChristian Marangi 1134e9bbf019SChristian Marangi /* Set port in the correct port mask or disable port if in delete mode */ 1135e9bbf019SChristian Marangi return regmap_update_bits(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL(id), 1136e9bbf019SChristian Marangi QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN(id, i) | 1137e9bbf019SChristian Marangi QCA8K_REG_GOL_TRUNK_ID_MEM_ID_PORT(id, i), 1138e9bbf019SChristian Marangi !delete << QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(id, i) | 1139e9bbf019SChristian Marangi port << QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(id, i)); 1140e9bbf019SChristian Marangi } 1141e9bbf019SChristian Marangi 1142e9bbf019SChristian Marangi int qca8k_port_lag_join(struct dsa_switch *ds, int port, struct dsa_lag lag, 11432e359b00SVladimir Oltean struct netdev_lag_upper_info *info, 11442e359b00SVladimir Oltean struct netlink_ext_ack *extack) 1145e9bbf019SChristian Marangi { 1146e9bbf019SChristian Marangi int ret; 1147e9bbf019SChristian Marangi 11482e359b00SVladimir Oltean if (!qca8k_lag_can_offload(ds, lag, info, extack)) 1149e9bbf019SChristian Marangi return -EOPNOTSUPP; 1150e9bbf019SChristian Marangi 1151e9bbf019SChristian Marangi ret = qca8k_lag_setup_hash(ds, lag, info); 1152e9bbf019SChristian Marangi if (ret) 1153e9bbf019SChristian Marangi return ret; 1154e9bbf019SChristian Marangi 1155e9bbf019SChristian Marangi return qca8k_lag_refresh_portmap(ds, port, lag, false); 1156e9bbf019SChristian Marangi } 1157e9bbf019SChristian Marangi 1158e9bbf019SChristian Marangi int qca8k_port_lag_leave(struct dsa_switch *ds, int port, 1159e9bbf019SChristian Marangi struct dsa_lag lag) 1160e9bbf019SChristian Marangi { 1161e9bbf019SChristian Marangi return qca8k_lag_refresh_portmap(ds, port, lag, true); 1162e9bbf019SChristian Marangi } 11639d1bcb1fSChristian Marangi 11649d1bcb1fSChristian Marangi int qca8k_read_switch_id(struct qca8k_priv *priv) 11659d1bcb1fSChristian Marangi { 11669d1bcb1fSChristian Marangi u32 val; 11679d1bcb1fSChristian Marangi u8 id; 11689d1bcb1fSChristian Marangi int ret; 11699d1bcb1fSChristian Marangi 11709d1bcb1fSChristian Marangi if (!priv->info) 11719d1bcb1fSChristian Marangi return -ENODEV; 11729d1bcb1fSChristian Marangi 11739d1bcb1fSChristian Marangi ret = qca8k_read(priv, QCA8K_REG_MASK_CTRL, &val); 11749d1bcb1fSChristian Marangi if (ret < 0) 11759d1bcb1fSChristian Marangi return -ENODEV; 11769d1bcb1fSChristian Marangi 11779d1bcb1fSChristian Marangi id = QCA8K_MASK_CTRL_DEVICE_ID(val); 11789d1bcb1fSChristian Marangi if (id != priv->info->id) { 11799d1bcb1fSChristian Marangi dev_err(priv->dev, 11809d1bcb1fSChristian Marangi "Switch id detected %x but expected %x", 11819d1bcb1fSChristian Marangi id, priv->info->id); 11829d1bcb1fSChristian Marangi return -ENODEV; 11839d1bcb1fSChristian Marangi } 11849d1bcb1fSChristian Marangi 11859d1bcb1fSChristian Marangi priv->switch_id = id; 11869d1bcb1fSChristian Marangi 11879d1bcb1fSChristian Marangi /* Save revision to communicate to the internal PHY driver */ 11889d1bcb1fSChristian Marangi priv->switch_revision = QCA8K_MASK_CTRL_REV_ID(val); 11899d1bcb1fSChristian Marangi 11909d1bcb1fSChristian Marangi return 0; 11919d1bcb1fSChristian Marangi } 1192