From 3bb0844e7bcd0fb0bcfab6202b5edd349ef5250a Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Wed, 27 Jul 2022 13:35:10 +0200 Subject: [PATCH 01/14] net: dsa: qca8k: cache match data to speed up access Using of_device_get_match_data is expensive. Cache match data to speed up access and rework user of match data to use the new cached value. Signed-off-by: Christian Marangi Reviewed-by: Vladimir Oltean Signed-off-by: Jakub Kicinski --- drivers/net/dsa/qca/qca8k.c | 35 +++++++++++------------------------ drivers/net/dsa/qca/qca8k.h | 1 + 2 files changed, 12 insertions(+), 24 deletions(-) diff --git a/drivers/net/dsa/qca/qca8k.c b/drivers/net/dsa/qca/qca8k.c index 1cbb05b0323f0..64524a721221a 100644 --- a/drivers/net/dsa/qca/qca8k.c +++ b/drivers/net/dsa/qca/qca8k.c @@ -1462,8 +1462,8 @@ static int qca8k_find_cpu_port(struct dsa_switch *ds) static int qca8k_setup_of_pws_reg(struct qca8k_priv *priv) { + const struct qca8k_match_data *data = priv->info; struct device_node *node = priv->dev->of_node; - const struct qca8k_match_data *data; u32 val = 0; int ret; @@ -1472,8 +1472,6 @@ qca8k_setup_of_pws_reg(struct qca8k_priv *priv) * Should be applied by default but we set this just to make sure. */ if (priv->switch_id == QCA8K_ID_QCA8327) { - data = of_device_get_match_data(priv->dev); - /* Set the correct package of 148 pin for QCA8327 */ if (data->reduced_package) val |= QCA8327_PWS_PACKAGE148_EN; @@ -1996,23 +1994,19 @@ static void qca8k_setup_pcs(struct qca8k_priv *priv, struct qca8k_pcs *qpcs, static void qca8k_get_strings(struct dsa_switch *ds, int port, u32 stringset, uint8_t *data) { - const struct qca8k_match_data *match_data; struct qca8k_priv *priv = ds->priv; int i; if (stringset != ETH_SS_STATS) return; - match_data = of_device_get_match_data(priv->dev); - - for (i = 0; i < match_data->mib_count; i++) + for (i = 0; i < priv->info->mib_count; i++) strncpy(data + i * ETH_GSTRING_LEN, ar8327_mib[i].name, ETH_GSTRING_LEN); } static void qca8k_mib_autocast_handler(struct dsa_switch *ds, struct sk_buff *skb) { - const struct qca8k_match_data *match_data; struct qca8k_mib_eth_data *mib_eth_data; struct qca8k_priv *priv = ds->priv; const struct qca8k_mib_desc *mib; @@ -2031,10 +2025,9 @@ static void qca8k_mib_autocast_handler(struct dsa_switch *ds, struct sk_buff *sk if (port != mib_eth_data->req_port) goto exit; - match_data = device_get_match_data(priv->dev); data = mib_eth_data->data; - for (i = 0; i < match_data->mib_count; i++) { + for (i = 0; i < priv->info->mib_count; i++) { mib = &ar8327_mib[i]; /* First 3 mib are present in the skb head */ @@ -2106,7 +2099,6 @@ qca8k_get_ethtool_stats(struct dsa_switch *ds, int port, uint64_t *data) { struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv; - const struct qca8k_match_data *match_data; const struct qca8k_mib_desc *mib; u32 reg, i, val; u32 hi = 0; @@ -2116,9 +2108,7 @@ qca8k_get_ethtool_stats(struct dsa_switch *ds, int port, qca8k_get_ethtool_stats_eth(ds, port, data) > 0) return; - match_data = of_device_get_match_data(priv->dev); - - for (i = 0; i < match_data->mib_count; i++) { + for (i = 0; i < priv->info->mib_count; i++) { mib = &ar8327_mib[i]; reg = QCA8K_PORT_MIB_COUNTER(port) + mib->offset; @@ -2141,15 +2131,12 @@ qca8k_get_ethtool_stats(struct dsa_switch *ds, int port, static int qca8k_get_sset_count(struct dsa_switch *ds, int port, int sset) { - const struct qca8k_match_data *match_data; struct qca8k_priv *priv = ds->priv; if (sset != ETH_SS_STATS) return 0; - match_data = of_device_get_match_data(priv->dev); - - return match_data->mib_count; + return priv->info->mib_count; } static int @@ -3093,14 +3080,11 @@ static const struct dsa_switch_ops qca8k_switch_ops = { static int qca8k_read_switch_id(struct qca8k_priv *priv) { - const struct qca8k_match_data *data; u32 val; u8 id; int ret; - /* get the switches ID from the compatible */ - data = of_device_get_match_data(priv->dev); - if (!data) + if (!priv->info) return -ENODEV; ret = qca8k_read(priv, QCA8K_REG_MASK_CTRL, &val); @@ -3108,8 +3092,10 @@ static int qca8k_read_switch_id(struct qca8k_priv *priv) return -ENODEV; id = QCA8K_MASK_CTRL_DEVICE_ID(val); - if (id != data->id) { - dev_err(priv->dev, "Switch id detected %x but expected %x", id, data->id); + if (id != priv->info->id) { + dev_err(priv->dev, + "Switch id detected %x but expected %x", + id, priv->info->id); return -ENODEV; } @@ -3134,6 +3120,7 @@ qca8k_sw_probe(struct mdio_device *mdiodev) if (!priv) return -ENOMEM; + priv->info = of_device_get_match_data(priv->dev); priv->bus = mdiodev->bus; priv->dev = &mdiodev->dev; diff --git a/drivers/net/dsa/qca/qca8k.h b/drivers/net/dsa/qca/qca8k.h index ec58d0e80a70f..0b990b46890ac 100644 --- a/drivers/net/dsa/qca/qca8k.h +++ b/drivers/net/dsa/qca/qca8k.h @@ -401,6 +401,7 @@ struct qca8k_priv { struct qca8k_mdio_cache mdio_cache; struct qca8k_pcs pcs_port_0; struct qca8k_pcs pcs_port_6; + const struct qca8k_match_data *info; }; struct qca8k_mib_desc { From 533c64bca62a8654f00698bc893f639013e38c7b Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Wed, 27 Jul 2022 13:35:11 +0200 Subject: [PATCH 02/14] net: dsa: qca8k: make mib autocast feature optional Some switch may not support mib autocast feature and require the legacy way of reading the regs directly. Make the mib autocast feature optional and permit to declare support for it using match_data struct in a dedicated qca8k_info_ops struct. Signed-off-by: Christian Marangi Reviewed-by: Vladimir Oltean Signed-off-by: Jakub Kicinski --- drivers/net/dsa/qca/qca8k.c | 11 +++++++++-- drivers/net/dsa/qca/qca8k.h | 5 +++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/drivers/net/dsa/qca/qca8k.c b/drivers/net/dsa/qca/qca8k.c index 64524a721221a..02a4765f267ed 100644 --- a/drivers/net/dsa/qca/qca8k.c +++ b/drivers/net/dsa/qca/qca8k.c @@ -2104,8 +2104,8 @@ qca8k_get_ethtool_stats(struct dsa_switch *ds, int port, u32 hi = 0; int ret; - if (priv->mgmt_master && - qca8k_get_ethtool_stats_eth(ds, port, data) > 0) + if (priv->mgmt_master && priv->info->ops->autocast_mib && + priv->info->ops->autocast_mib(ds, port, data) > 0) return; for (i = 0; i < priv->info->mib_count; i++) { @@ -3243,20 +3243,27 @@ static int qca8k_resume(struct device *dev) static SIMPLE_DEV_PM_OPS(qca8k_pm_ops, qca8k_suspend, qca8k_resume); +static const struct qca8k_info_ops qca8xxx_ops = { + .autocast_mib = qca8k_get_ethtool_stats_eth, +}; + static const struct qca8k_match_data qca8327 = { .id = QCA8K_ID_QCA8327, .reduced_package = true, .mib_count = QCA8K_QCA832X_MIB_COUNT, + .ops = &qca8xxx_ops, }; static const struct qca8k_match_data qca8328 = { .id = QCA8K_ID_QCA8327, .mib_count = QCA8K_QCA832X_MIB_COUNT, + .ops = &qca8xxx_ops, }; static const struct qca8k_match_data qca833x = { .id = QCA8K_ID_QCA8337, .mib_count = QCA8K_QCA833X_MIB_COUNT, + .ops = &qca8xxx_ops, }; static const struct of_device_id qca8k_of_match[] = { diff --git a/drivers/net/dsa/qca/qca8k.h b/drivers/net/dsa/qca/qca8k.h index 0b990b46890ac..377ce8c729142 100644 --- a/drivers/net/dsa/qca/qca8k.h +++ b/drivers/net/dsa/qca/qca8k.h @@ -324,10 +324,15 @@ enum qca8k_mid_cmd { QCA8K_MIB_CAST = 3, }; +struct qca8k_info_ops { + int (*autocast_mib)(struct dsa_switch *ds, int port, u64 *data); +}; + struct qca8k_match_data { u8 id; bool reduced_package; u8 mib_count; + const struct qca8k_info_ops *ops; }; enum { From 027152b830434e3632ad5dd678cc5d4740358dbb Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Wed, 27 Jul 2022 13:35:12 +0200 Subject: [PATCH 03/14] net: dsa: qca8k: move mib struct to common code The same MIB struct is used by drivers based on qca8k family switch. Move it to common code to make it accessible also by other drivers. Signed-off-by: Christian Marangi Reviewed-by: Vladimir Oltean Signed-off-by: Jakub Kicinski --- drivers/net/dsa/qca/Makefile | 1 + drivers/net/dsa/qca/{qca8k.c => qca8k-8xxx.c} | 51 --------------- drivers/net/dsa/qca/qca8k-common.c | 63 +++++++++++++++++++ drivers/net/dsa/qca/qca8k.h | 3 + 4 files changed, 67 insertions(+), 51 deletions(-) rename drivers/net/dsa/qca/{qca8k.c => qca8k-8xxx.c} (98%) create mode 100644 drivers/net/dsa/qca/qca8k-common.c diff --git a/drivers/net/dsa/qca/Makefile b/drivers/net/dsa/qca/Makefile index 40bb7c27285b8..701f1d199e934 100644 --- a/drivers/net/dsa/qca/Makefile +++ b/drivers/net/dsa/qca/Makefile @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_NET_DSA_AR9331) += ar9331.o obj-$(CONFIG_NET_DSA_QCA8K) += qca8k.o +qca8k-y += qca8k-common.o qca8k-8xxx.o diff --git a/drivers/net/dsa/qca/qca8k.c b/drivers/net/dsa/qca/qca8k-8xxx.c similarity index 98% rename from drivers/net/dsa/qca/qca8k.c rename to drivers/net/dsa/qca/qca8k-8xxx.c index 02a4765f267ed..e9c4a54bc97a7 100644 --- a/drivers/net/dsa/qca/qca8k.c +++ b/drivers/net/dsa/qca/qca8k-8xxx.c @@ -24,57 +24,6 @@ #include "qca8k.h" -#define MIB_DESC(_s, _o, _n) \ - { \ - .size = (_s), \ - .offset = (_o), \ - .name = (_n), \ - } - -static const struct qca8k_mib_desc ar8327_mib[] = { - MIB_DESC(1, 0x00, "RxBroad"), - MIB_DESC(1, 0x04, "RxPause"), - MIB_DESC(1, 0x08, "RxMulti"), - MIB_DESC(1, 0x0c, "RxFcsErr"), - MIB_DESC(1, 0x10, "RxAlignErr"), - MIB_DESC(1, 0x14, "RxRunt"), - MIB_DESC(1, 0x18, "RxFragment"), - MIB_DESC(1, 0x1c, "Rx64Byte"), - MIB_DESC(1, 0x20, "Rx128Byte"), - MIB_DESC(1, 0x24, "Rx256Byte"), - MIB_DESC(1, 0x28, "Rx512Byte"), - MIB_DESC(1, 0x2c, "Rx1024Byte"), - MIB_DESC(1, 0x30, "Rx1518Byte"), - MIB_DESC(1, 0x34, "RxMaxByte"), - MIB_DESC(1, 0x38, "RxTooLong"), - MIB_DESC(2, 0x3c, "RxGoodByte"), - MIB_DESC(2, 0x44, "RxBadByte"), - MIB_DESC(1, 0x4c, "RxOverFlow"), - MIB_DESC(1, 0x50, "Filtered"), - MIB_DESC(1, 0x54, "TxBroad"), - MIB_DESC(1, 0x58, "TxPause"), - MIB_DESC(1, 0x5c, "TxMulti"), - MIB_DESC(1, 0x60, "TxUnderRun"), - MIB_DESC(1, 0x64, "Tx64Byte"), - MIB_DESC(1, 0x68, "Tx128Byte"), - MIB_DESC(1, 0x6c, "Tx256Byte"), - MIB_DESC(1, 0x70, "Tx512Byte"), - MIB_DESC(1, 0x74, "Tx1024Byte"), - MIB_DESC(1, 0x78, "Tx1518Byte"), - MIB_DESC(1, 0x7c, "TxMaxByte"), - MIB_DESC(1, 0x80, "TxOverSize"), - MIB_DESC(2, 0x84, "TxByte"), - MIB_DESC(1, 0x8c, "TxCollision"), - MIB_DESC(1, 0x90, "TxAbortCol"), - MIB_DESC(1, 0x94, "TxMultiCol"), - MIB_DESC(1, 0x98, "TxSingleCol"), - MIB_DESC(1, 0x9c, "TxExcDefer"), - MIB_DESC(1, 0xa0, "TxDefer"), - MIB_DESC(1, 0xa4, "TxLateCol"), - MIB_DESC(1, 0xa8, "RXUnicast"), - MIB_DESC(1, 0xac, "TXUnicast"), -}; - static void qca8k_split_addr(u32 regaddr, u16 *r1, u16 *r2, u16 *page) { diff --git a/drivers/net/dsa/qca/qca8k-common.c b/drivers/net/dsa/qca/qca8k-common.c new file mode 100644 index 0000000000000..7a63e96c8c08f --- /dev/null +++ b/drivers/net/dsa/qca/qca8k-common.c @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2009 Felix Fietkau + * Copyright (C) 2011-2012 Gabor Juhos + * Copyright (c) 2015, 2019, The Linux Foundation. All rights reserved. + * Copyright (c) 2016 John Crispin + */ + +#include +#include + +#include "qca8k.h" + +#define MIB_DESC(_s, _o, _n) \ + { \ + .size = (_s), \ + .offset = (_o), \ + .name = (_n), \ + } + +const struct qca8k_mib_desc ar8327_mib[] = { + MIB_DESC(1, 0x00, "RxBroad"), + MIB_DESC(1, 0x04, "RxPause"), + MIB_DESC(1, 0x08, "RxMulti"), + MIB_DESC(1, 0x0c, "RxFcsErr"), + MIB_DESC(1, 0x10, "RxAlignErr"), + MIB_DESC(1, 0x14, "RxRunt"), + MIB_DESC(1, 0x18, "RxFragment"), + MIB_DESC(1, 0x1c, "Rx64Byte"), + MIB_DESC(1, 0x20, "Rx128Byte"), + MIB_DESC(1, 0x24, "Rx256Byte"), + MIB_DESC(1, 0x28, "Rx512Byte"), + MIB_DESC(1, 0x2c, "Rx1024Byte"), + MIB_DESC(1, 0x30, "Rx1518Byte"), + MIB_DESC(1, 0x34, "RxMaxByte"), + MIB_DESC(1, 0x38, "RxTooLong"), + MIB_DESC(2, 0x3c, "RxGoodByte"), + MIB_DESC(2, 0x44, "RxBadByte"), + MIB_DESC(1, 0x4c, "RxOverFlow"), + MIB_DESC(1, 0x50, "Filtered"), + MIB_DESC(1, 0x54, "TxBroad"), + MIB_DESC(1, 0x58, "TxPause"), + MIB_DESC(1, 0x5c, "TxMulti"), + MIB_DESC(1, 0x60, "TxUnderRun"), + MIB_DESC(1, 0x64, "Tx64Byte"), + MIB_DESC(1, 0x68, "Tx128Byte"), + MIB_DESC(1, 0x6c, "Tx256Byte"), + MIB_DESC(1, 0x70, "Tx512Byte"), + MIB_DESC(1, 0x74, "Tx1024Byte"), + MIB_DESC(1, 0x78, "Tx1518Byte"), + MIB_DESC(1, 0x7c, "TxMaxByte"), + MIB_DESC(1, 0x80, "TxOverSize"), + MIB_DESC(2, 0x84, "TxByte"), + MIB_DESC(1, 0x8c, "TxCollision"), + MIB_DESC(1, 0x90, "TxAbortCol"), + MIB_DESC(1, 0x94, "TxMultiCol"), + MIB_DESC(1, 0x98, "TxSingleCol"), + MIB_DESC(1, 0x9c, "TxExcDefer"), + MIB_DESC(1, 0xa0, "TxDefer"), + MIB_DESC(1, 0xa4, "TxLateCol"), + MIB_DESC(1, 0xa8, "RXUnicast"), + MIB_DESC(1, 0xac, "TXUnicast"), +}; diff --git a/drivers/net/dsa/qca/qca8k.h b/drivers/net/dsa/qca/qca8k.h index 377ce8c729142..0f274bb350f83 100644 --- a/drivers/net/dsa/qca/qca8k.h +++ b/drivers/net/dsa/qca/qca8k.h @@ -422,4 +422,7 @@ struct qca8k_fdb { u8 mac[6]; }; +/* Common setup function */ +extern const struct qca8k_mib_desc ar8327_mib[]; + #endif /* __QCA8K_H */ From d5f901eab2e9dfed1095995dfc98f231f4fd2971 Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Wed, 27 Jul 2022 13:35:13 +0200 Subject: [PATCH 04/14] net: dsa: qca8k: move qca8k read/write/rmw and reg table to common code The same reg table and read/write/rmw function are used by drivers based on qca8k family switch. Move them to common code to make it accessible also by other drivers. Signed-off-by: Christian Marangi Reviewed-by: Vladimir Oltean Signed-off-by: Jakub Kicinski --- drivers/net/dsa/qca/qca8k-8xxx.c | 42 ------------------------------ drivers/net/dsa/qca/qca8k-common.c | 38 +++++++++++++++++++++++++++ drivers/net/dsa/qca/qca8k.h | 6 +++++ 3 files changed, 44 insertions(+), 42 deletions(-) diff --git a/drivers/net/dsa/qca/qca8k-8xxx.c b/drivers/net/dsa/qca/qca8k-8xxx.c index e9c4a54bc97a7..2c31b7925aae2 100644 --- a/drivers/net/dsa/qca/qca8k-8xxx.c +++ b/drivers/net/dsa/qca/qca8k-8xxx.c @@ -133,24 +133,6 @@ qca8k_set_page(struct qca8k_priv *priv, u16 page) return 0; } -static int -qca8k_read(struct qca8k_priv *priv, u32 reg, u32 *val) -{ - return regmap_read(priv->regmap, reg, val); -} - -static int -qca8k_write(struct qca8k_priv *priv, u32 reg, u32 val) -{ - return regmap_write(priv->regmap, reg, val); -} - -static int -qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val) -{ - return regmap_update_bits(priv->regmap, reg, mask, write_val); -} - static void qca8k_rw_reg_ack_handler(struct dsa_switch *ds, struct sk_buff *skb) { struct qca8k_mgmt_eth_data *mgmt_eth_data; @@ -483,30 +465,6 @@ qca8k_regmap_update_bits(void *ctx, uint32_t reg, uint32_t mask, uint32_t write_ return ret; } -static const struct regmap_range qca8k_readable_ranges[] = { - regmap_reg_range(0x0000, 0x00e4), /* Global control */ - regmap_reg_range(0x0100, 0x0168), /* EEE control */ - regmap_reg_range(0x0200, 0x0270), /* Parser control */ - regmap_reg_range(0x0400, 0x0454), /* ACL */ - regmap_reg_range(0x0600, 0x0718), /* Lookup */ - regmap_reg_range(0x0800, 0x0b70), /* QM */ - regmap_reg_range(0x0c00, 0x0c80), /* PKT */ - regmap_reg_range(0x0e00, 0x0e98), /* L3 */ - regmap_reg_range(0x1000, 0x10ac), /* MIB - Port0 */ - regmap_reg_range(0x1100, 0x11ac), /* MIB - Port1 */ - regmap_reg_range(0x1200, 0x12ac), /* MIB - Port2 */ - regmap_reg_range(0x1300, 0x13ac), /* MIB - Port3 */ - regmap_reg_range(0x1400, 0x14ac), /* MIB - Port4 */ - regmap_reg_range(0x1500, 0x15ac), /* MIB - Port5 */ - regmap_reg_range(0x1600, 0x16ac), /* MIB - Port6 */ - -}; - -static const struct regmap_access_table qca8k_readable_table = { - .yes_ranges = qca8k_readable_ranges, - .n_yes_ranges = ARRAY_SIZE(qca8k_readable_ranges), -}; - static struct regmap_config qca8k_regmap_config = { .reg_bits = 16, .val_bits = 32, diff --git a/drivers/net/dsa/qca/qca8k-common.c b/drivers/net/dsa/qca/qca8k-common.c index 7a63e96c8c08f..880a49de22b1a 100644 --- a/drivers/net/dsa/qca/qca8k-common.c +++ b/drivers/net/dsa/qca/qca8k-common.c @@ -61,3 +61,41 @@ const struct qca8k_mib_desc ar8327_mib[] = { MIB_DESC(1, 0xa8, "RXUnicast"), MIB_DESC(1, 0xac, "TXUnicast"), }; + +int qca8k_read(struct qca8k_priv *priv, u32 reg, u32 *val) +{ + return regmap_read(priv->regmap, reg, val); +} + +int qca8k_write(struct qca8k_priv *priv, u32 reg, u32 val) +{ + return regmap_write(priv->regmap, reg, val); +} + +int qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val) +{ + return regmap_update_bits(priv->regmap, reg, mask, write_val); +} + +static const struct regmap_range qca8k_readable_ranges[] = { + regmap_reg_range(0x0000, 0x00e4), /* Global control */ + regmap_reg_range(0x0100, 0x0168), /* EEE control */ + regmap_reg_range(0x0200, 0x0270), /* Parser control */ + regmap_reg_range(0x0400, 0x0454), /* ACL */ + regmap_reg_range(0x0600, 0x0718), /* Lookup */ + regmap_reg_range(0x0800, 0x0b70), /* QM */ + regmap_reg_range(0x0c00, 0x0c80), /* PKT */ + regmap_reg_range(0x0e00, 0x0e98), /* L3 */ + regmap_reg_range(0x1000, 0x10ac), /* MIB - Port0 */ + regmap_reg_range(0x1100, 0x11ac), /* MIB - Port1 */ + regmap_reg_range(0x1200, 0x12ac), /* MIB - Port2 */ + regmap_reg_range(0x1300, 0x13ac), /* MIB - Port3 */ + regmap_reg_range(0x1400, 0x14ac), /* MIB - Port4 */ + regmap_reg_range(0x1500, 0x15ac), /* MIB - Port5 */ + regmap_reg_range(0x1600, 0x16ac), /* MIB - Port6 */ +}; + +const struct regmap_access_table qca8k_readable_table = { + .yes_ranges = qca8k_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(qca8k_readable_ranges), +}; diff --git a/drivers/net/dsa/qca/qca8k.h b/drivers/net/dsa/qca/qca8k.h index 0f274bb350f83..c9b753743dfd9 100644 --- a/drivers/net/dsa/qca/qca8k.h +++ b/drivers/net/dsa/qca/qca8k.h @@ -424,5 +424,11 @@ struct qca8k_fdb { /* Common setup function */ extern const struct qca8k_mib_desc ar8327_mib[]; +extern const struct regmap_access_table qca8k_readable_table; + +/* Common read/write/rmw function */ +int qca8k_read(struct qca8k_priv *priv, u32 reg, u32 *val); +int qca8k_write(struct qca8k_priv *priv, u32 reg, u32 val); +int qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val); #endif /* __QCA8K_H */ From 910746444313dc463396cd63024cdf54ef04ef39 Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Wed, 27 Jul 2022 13:35:14 +0200 Subject: [PATCH 05/14] net: dsa: qca8k: move qca8k bulk read/write helper to common code The same ATU function are used by drivers based on qca8k family switch. Move the bulk read/write helper to common code to declare these shared ATU functions in common code. These helper will be dropped when regmap correctly support bulk read/write. Signed-off-by: Christian Marangi Reviewed-by: Vladimir Oltean Signed-off-by: Jakub Kicinski --- drivers/net/dsa/qca/qca8k-8xxx.c | 39 ++---------------------------- drivers/net/dsa/qca/qca8k-common.c | 39 ++++++++++++++++++++++++++++++ drivers/net/dsa/qca/qca8k.h | 8 ++++++ 3 files changed, 49 insertions(+), 37 deletions(-) diff --git a/drivers/net/dsa/qca/qca8k-8xxx.c b/drivers/net/dsa/qca/qca8k-8xxx.c index 2c31b7925aae2..8a5529844c7a5 100644 --- a/drivers/net/dsa/qca/qca8k-8xxx.c +++ b/drivers/net/dsa/qca/qca8k-8xxx.c @@ -342,43 +342,6 @@ qca8k_regmap_update_bits_eth(struct qca8k_priv *priv, u32 reg, u32 mask, u32 wri return qca8k_write_eth(priv, reg, &val, sizeof(val)); } -static int -qca8k_bulk_read(struct qca8k_priv *priv, u32 reg, u32 *val, int len) -{ - int i, count = len / sizeof(u32), ret; - - if (priv->mgmt_master && !qca8k_read_eth(priv, reg, val, len)) - return 0; - - for (i = 0; i < count; i++) { - ret = regmap_read(priv->regmap, reg + (i * 4), val + i); - if (ret < 0) - return ret; - } - - return 0; -} - -static int -qca8k_bulk_write(struct qca8k_priv *priv, u32 reg, u32 *val, int len) -{ - int i, count = len / sizeof(u32), ret; - u32 tmp; - - if (priv->mgmt_master && !qca8k_write_eth(priv, reg, val, len)) - return 0; - - for (i = 0; i < count; i++) { - tmp = val[i]; - - ret = regmap_write(priv->regmap, reg + (i * 4), tmp); - if (ret < 0) - return ret; - } - - return 0; -} - static int qca8k_regmap_read(void *ctx, uint32_t reg, uint32_t *val) { @@ -3152,6 +3115,8 @@ static SIMPLE_DEV_PM_OPS(qca8k_pm_ops, static const struct qca8k_info_ops qca8xxx_ops = { .autocast_mib = qca8k_get_ethtool_stats_eth, + .read_eth = qca8k_read_eth, + .write_eth = qca8k_write_eth, }; static const struct qca8k_match_data qca8327 = { diff --git a/drivers/net/dsa/qca/qca8k-common.c b/drivers/net/dsa/qca/qca8k-common.c index 880a49de22b1a..a47f876033bad 100644 --- a/drivers/net/dsa/qca/qca8k-common.c +++ b/drivers/net/dsa/qca/qca8k-common.c @@ -99,3 +99,42 @@ const struct regmap_access_table qca8k_readable_table = { .yes_ranges = qca8k_readable_ranges, .n_yes_ranges = ARRAY_SIZE(qca8k_readable_ranges), }; + +/* TODO: remove these extra ops when we can support regmap bulk read/write */ +int qca8k_bulk_read(struct qca8k_priv *priv, u32 reg, u32 *val, int len) +{ + int i, count = len / sizeof(u32), ret; + + if (priv->mgmt_master && priv->info->ops->read_eth && + !priv->info->ops->read_eth(priv, reg, val, len)) + return 0; + + for (i = 0; i < count; i++) { + ret = regmap_read(priv->regmap, reg + (i * 4), val + i); + if (ret < 0) + return ret; + } + + return 0; +} + +/* TODO: remove these extra ops when we can support regmap bulk read/write */ +int qca8k_bulk_write(struct qca8k_priv *priv, u32 reg, u32 *val, int len) +{ + int i, count = len / sizeof(u32), ret; + u32 tmp; + + if (priv->mgmt_master && priv->info->ops->write_eth && + !priv->info->ops->write_eth(priv, reg, val, len)) + return 0; + + for (i = 0; i < count; i++) { + tmp = val[i]; + + ret = regmap_write(priv->regmap, reg + (i * 4), tmp); + if (ret < 0) + return ret; + } + + return 0; +} diff --git a/drivers/net/dsa/qca/qca8k.h b/drivers/net/dsa/qca/qca8k.h index c9b753743dfd9..df32c1e3a7975 100644 --- a/drivers/net/dsa/qca/qca8k.h +++ b/drivers/net/dsa/qca/qca8k.h @@ -324,8 +324,13 @@ enum qca8k_mid_cmd { QCA8K_MIB_CAST = 3, }; +struct qca8k_priv; + struct qca8k_info_ops { int (*autocast_mib)(struct dsa_switch *ds, int port, u64 *data); + /* TODO: remove these extra ops when we can support regmap bulk read/write */ + int (*read_eth)(struct qca8k_priv *priv, u32 reg, u32 *val, int len); + int (*write_eth)(struct qca8k_priv *priv, u32 reg, u32 *val, int len); }; struct qca8k_match_data { @@ -431,4 +436,7 @@ int qca8k_read(struct qca8k_priv *priv, u32 reg, u32 *val); int qca8k_write(struct qca8k_priv *priv, u32 reg, u32 val); int qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val); +int qca8k_bulk_read(struct qca8k_priv *priv, u32 reg, u32 *val, int len); +int qca8k_bulk_write(struct qca8k_priv *priv, u32 reg, u32 *val, int len); + #endif /* __QCA8K_H */ From fce1ec0c4e2d03d9c62ffc615a42bdba78eb4c14 Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Wed, 27 Jul 2022 13:35:15 +0200 Subject: [PATCH 06/14] net: dsa: qca8k: move mib init function to common code The same mib function is used by drivers based on qca8k family switch. Move it to common code to make it accessible also by other drivers. Signed-off-by: Christian Marangi Reviewed-by: Vladimir Oltean Signed-off-by: Jakub Kicinski --- drivers/net/dsa/qca/qca8k-8xxx.c | 37 ------------------------------ drivers/net/dsa/qca/qca8k-common.c | 35 ++++++++++++++++++++++++++++ drivers/net/dsa/qca/qca8k.h | 4 ++++ 3 files changed, 39 insertions(+), 37 deletions(-) diff --git a/drivers/net/dsa/qca/qca8k-8xxx.c b/drivers/net/dsa/qca/qca8k-8xxx.c index 8a5529844c7a5..b4daa097a7fcc 100644 --- a/drivers/net/dsa/qca/qca8k-8xxx.c +++ b/drivers/net/dsa/qca/qca8k-8xxx.c @@ -441,15 +441,6 @@ static struct regmap_config qca8k_regmap_config = { .cache_type = REGCACHE_NONE, /* Explicitly disable CACHE */ }; -static int -qca8k_busy_wait(struct qca8k_priv *priv, u32 reg, u32 mask) -{ - u32 val; - - return regmap_read_poll_timeout(priv->regmap, reg, val, !(val & mask), 0, - QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC); -} - static int qca8k_fdb_read(struct qca8k_priv *priv, struct qca8k_fdb *fdb) { @@ -777,34 +768,6 @@ qca8k_vlan_del(struct qca8k_priv *priv, u8 port, u16 vid) return ret; } -static int -qca8k_mib_init(struct qca8k_priv *priv) -{ - int ret; - - mutex_lock(&priv->reg_mutex); - ret = regmap_update_bits(priv->regmap, QCA8K_REG_MIB, - QCA8K_MIB_FUNC | QCA8K_MIB_BUSY, - FIELD_PREP(QCA8K_MIB_FUNC, QCA8K_MIB_FLUSH) | - QCA8K_MIB_BUSY); - if (ret) - goto exit; - - ret = qca8k_busy_wait(priv, QCA8K_REG_MIB, QCA8K_MIB_BUSY); - if (ret) - goto exit; - - ret = regmap_set_bits(priv->regmap, QCA8K_REG_MIB, QCA8K_MIB_CPU_KEEP); - if (ret) - goto exit; - - ret = qca8k_write(priv, QCA8K_REG_MODULE_EN, QCA8K_MODULE_EN_MIB); - -exit: - mutex_unlock(&priv->reg_mutex); - return ret; -} - static void qca8k_port_set_status(struct qca8k_priv *priv, int port, int enable) { diff --git a/drivers/net/dsa/qca/qca8k-common.c b/drivers/net/dsa/qca/qca8k-common.c index a47f876033bad..7da3547f9d97f 100644 --- a/drivers/net/dsa/qca/qca8k-common.c +++ b/drivers/net/dsa/qca/qca8k-common.c @@ -138,3 +138,38 @@ int qca8k_bulk_write(struct qca8k_priv *priv, u32 reg, u32 *val, int len) return 0; } + +int qca8k_busy_wait(struct qca8k_priv *priv, u32 reg, u32 mask) +{ + u32 val; + + return regmap_read_poll_timeout(priv->regmap, reg, val, !(val & mask), 0, + QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC); +} + +int qca8k_mib_init(struct qca8k_priv *priv) +{ + int ret; + + mutex_lock(&priv->reg_mutex); + ret = regmap_update_bits(priv->regmap, QCA8K_REG_MIB, + QCA8K_MIB_FUNC | QCA8K_MIB_BUSY, + FIELD_PREP(QCA8K_MIB_FUNC, QCA8K_MIB_FLUSH) | + QCA8K_MIB_BUSY); + if (ret) + goto exit; + + ret = qca8k_busy_wait(priv, QCA8K_REG_MIB, QCA8K_MIB_BUSY); + if (ret) + goto exit; + + ret = regmap_set_bits(priv->regmap, QCA8K_REG_MIB, QCA8K_MIB_CPU_KEEP); + if (ret) + goto exit; + + ret = qca8k_write(priv, QCA8K_REG_MODULE_EN, QCA8K_MODULE_EN_MIB); + +exit: + mutex_unlock(&priv->reg_mutex); + return ret; +} diff --git a/drivers/net/dsa/qca/qca8k.h b/drivers/net/dsa/qca/qca8k.h index df32c1e3a7975..f55ef97f826f6 100644 --- a/drivers/net/dsa/qca/qca8k.h +++ b/drivers/net/dsa/qca/qca8k.h @@ -430,6 +430,7 @@ struct qca8k_fdb { /* Common setup function */ extern const struct qca8k_mib_desc ar8327_mib[]; extern const struct regmap_access_table qca8k_readable_table; +int qca8k_mib_init(struct qca8k_priv *priv); /* Common read/write/rmw function */ int qca8k_read(struct qca8k_priv *priv, u32 reg, u32 *val); @@ -439,4 +440,7 @@ int qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val); int qca8k_bulk_read(struct qca8k_priv *priv, u32 reg, u32 *val, int len); int qca8k_bulk_write(struct qca8k_priv *priv, u32 reg, u32 *val, int len); +/* Common ops function */ +int qca8k_busy_wait(struct qca8k_priv *priv, u32 reg, u32 mask); + #endif /* __QCA8K_H */ From 472fcea160f27a5d9b7526093d9d8d89ba0b6137 Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Wed, 27 Jul 2022 13:35:16 +0200 Subject: [PATCH 07/14] net: dsa: qca8k: move port set status/eee/ethtool stats function to common code The same logic to disable/enable port, set eee and get ethtool stats is used by drivers based on qca8k family switch. Move it to common code to make it accessible also by other drivers. While at it also drop unnecessary qca8k_priv cast for void pointers. Signed-off-by: Christian Marangi Reviewed-by: Vladimir Oltean Signed-off-by: Jakub Kicinski --- drivers/net/dsa/qca/qca8k-8xxx.c | 105 ----------------------------- drivers/net/dsa/qca/qca8k-common.c | 102 ++++++++++++++++++++++++++++ drivers/net/dsa/qca/qca8k.h | 11 +++ 3 files changed, 113 insertions(+), 105 deletions(-) diff --git a/drivers/net/dsa/qca/qca8k-8xxx.c b/drivers/net/dsa/qca/qca8k-8xxx.c index b4daa097a7fcc..9729aabbd83d6 100644 --- a/drivers/net/dsa/qca/qca8k-8xxx.c +++ b/drivers/net/dsa/qca/qca8k-8xxx.c @@ -768,21 +768,6 @@ qca8k_vlan_del(struct qca8k_priv *priv, u8 port, u16 vid) return ret; } -static void -qca8k_port_set_status(struct qca8k_priv *priv, int port, int enable) -{ - u32 mask = QCA8K_PORT_STATUS_TXMAC | QCA8K_PORT_STATUS_RXMAC; - - /* Port 0 and 6 have no internal PHY */ - if (port > 0 && port < 6) - mask |= QCA8K_PORT_STATUS_LINK_AUTO; - - if (enable) - regmap_set_bits(priv->regmap, QCA8K_REG_PORT_STATUS(port), mask); - else - regmap_clear_bits(priv->regmap, QCA8K_REG_PORT_STATUS(port), mask); -} - static int qca8k_phy_eth_busy_wait(struct qca8k_mgmt_eth_data *mgmt_eth_data, struct sk_buff *read_skb, u32 *val) @@ -1824,20 +1809,6 @@ static void qca8k_setup_pcs(struct qca8k_priv *priv, struct qca8k_pcs *qpcs, qpcs->port = port; } -static void -qca8k_get_strings(struct dsa_switch *ds, int port, u32 stringset, uint8_t *data) -{ - struct qca8k_priv *priv = ds->priv; - int i; - - if (stringset != ETH_SS_STATS) - return; - - for (i = 0; i < priv->info->mib_count; i++) - strncpy(data + i * ETH_GSTRING_LEN, ar8327_mib[i].name, - ETH_GSTRING_LEN); -} - static void qca8k_mib_autocast_handler(struct dsa_switch *ds, struct sk_buff *skb) { struct qca8k_mib_eth_data *mib_eth_data; @@ -1927,82 +1898,6 @@ qca8k_get_ethtool_stats_eth(struct dsa_switch *ds, int port, u64 *data) return ret; } -static void -qca8k_get_ethtool_stats(struct dsa_switch *ds, int port, - uint64_t *data) -{ - struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv; - const struct qca8k_mib_desc *mib; - u32 reg, i, val; - u32 hi = 0; - int ret; - - if (priv->mgmt_master && priv->info->ops->autocast_mib && - priv->info->ops->autocast_mib(ds, port, data) > 0) - return; - - for (i = 0; i < priv->info->mib_count; i++) { - mib = &ar8327_mib[i]; - reg = QCA8K_PORT_MIB_COUNTER(port) + mib->offset; - - ret = qca8k_read(priv, reg, &val); - if (ret < 0) - continue; - - if (mib->size == 2) { - ret = qca8k_read(priv, reg + 4, &hi); - if (ret < 0) - continue; - } - - data[i] = val; - if (mib->size == 2) - data[i] |= (u64)hi << 32; - } -} - -static int -qca8k_get_sset_count(struct dsa_switch *ds, int port, int sset) -{ - struct qca8k_priv *priv = ds->priv; - - if (sset != ETH_SS_STATS) - return 0; - - return priv->info->mib_count; -} - -static int -qca8k_set_mac_eee(struct dsa_switch *ds, int port, struct ethtool_eee *eee) -{ - struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv; - u32 lpi_en = QCA8K_REG_EEE_CTRL_LPI_EN(port); - u32 reg; - int ret; - - mutex_lock(&priv->reg_mutex); - ret = qca8k_read(priv, QCA8K_REG_EEE_CTRL, ®); - if (ret < 0) - goto exit; - - if (eee->eee_enabled) - reg |= lpi_en; - else - reg &= ~lpi_en; - ret = qca8k_write(priv, QCA8K_REG_EEE_CTRL, reg); - -exit: - mutex_unlock(&priv->reg_mutex); - return ret; -} - -static int -qca8k_get_mac_eee(struct dsa_switch *ds, int port, struct ethtool_eee *e) -{ - /* Nothing to do on the port's MAC */ - return 0; -} - static void qca8k_port_stp_state_set(struct dsa_switch *ds, int port, u8 state) { diff --git a/drivers/net/dsa/qca/qca8k-common.c b/drivers/net/dsa/qca/qca8k-common.c index 7da3547f9d97f..c662fdbae51f4 100644 --- a/drivers/net/dsa/qca/qca8k-common.c +++ b/drivers/net/dsa/qca/qca8k-common.c @@ -173,3 +173,105 @@ int qca8k_mib_init(struct qca8k_priv *priv) mutex_unlock(&priv->reg_mutex); return ret; } + +void qca8k_port_set_status(struct qca8k_priv *priv, int port, int enable) +{ + u32 mask = QCA8K_PORT_STATUS_TXMAC | QCA8K_PORT_STATUS_RXMAC; + + /* Port 0 and 6 have no internal PHY */ + if (port > 0 && port < 6) + mask |= QCA8K_PORT_STATUS_LINK_AUTO; + + if (enable) + regmap_set_bits(priv->regmap, QCA8K_REG_PORT_STATUS(port), mask); + else + regmap_clear_bits(priv->regmap, QCA8K_REG_PORT_STATUS(port), mask); +} + +void qca8k_get_strings(struct dsa_switch *ds, int port, u32 stringset, + uint8_t *data) +{ + struct qca8k_priv *priv = ds->priv; + int i; + + if (stringset != ETH_SS_STATS) + return; + + for (i = 0; i < priv->info->mib_count; i++) + strncpy(data + i * ETH_GSTRING_LEN, ar8327_mib[i].name, + ETH_GSTRING_LEN); +} + +void qca8k_get_ethtool_stats(struct dsa_switch *ds, int port, + uint64_t *data) +{ + struct qca8k_priv *priv = ds->priv; + const struct qca8k_mib_desc *mib; + u32 reg, i, val; + u32 hi = 0; + int ret; + + if (priv->mgmt_master && priv->info->ops->autocast_mib && + priv->info->ops->autocast_mib(ds, port, data) > 0) + return; + + for (i = 0; i < priv->info->mib_count; i++) { + mib = &ar8327_mib[i]; + reg = QCA8K_PORT_MIB_COUNTER(port) + mib->offset; + + ret = qca8k_read(priv, reg, &val); + if (ret < 0) + continue; + + if (mib->size == 2) { + ret = qca8k_read(priv, reg + 4, &hi); + if (ret < 0) + continue; + } + + data[i] = val; + if (mib->size == 2) + data[i] |= (u64)hi << 32; + } +} + +int qca8k_get_sset_count(struct dsa_switch *ds, int port, int sset) +{ + struct qca8k_priv *priv = ds->priv; + + if (sset != ETH_SS_STATS) + return 0; + + return priv->info->mib_count; +} + +int qca8k_set_mac_eee(struct dsa_switch *ds, int port, + struct ethtool_eee *eee) +{ + u32 lpi_en = QCA8K_REG_EEE_CTRL_LPI_EN(port); + struct qca8k_priv *priv = ds->priv; + u32 reg; + int ret; + + mutex_lock(&priv->reg_mutex); + ret = qca8k_read(priv, QCA8K_REG_EEE_CTRL, ®); + if (ret < 0) + goto exit; + + if (eee->eee_enabled) + reg |= lpi_en; + else + reg &= ~lpi_en; + ret = qca8k_write(priv, QCA8K_REG_EEE_CTRL, reg); + +exit: + mutex_unlock(&priv->reg_mutex); + return ret; +} + +int qca8k_get_mac_eee(struct dsa_switch *ds, int port, + struct ethtool_eee *e) +{ + /* Nothing to do on the port's MAC */ + return 0; +} diff --git a/drivers/net/dsa/qca/qca8k.h b/drivers/net/dsa/qca/qca8k.h index f55ef97f826f6..c73cbdfc6ac65 100644 --- a/drivers/net/dsa/qca/qca8k.h +++ b/drivers/net/dsa/qca/qca8k.h @@ -431,6 +431,7 @@ struct qca8k_fdb { extern const struct qca8k_mib_desc ar8327_mib[]; extern const struct regmap_access_table qca8k_readable_table; int qca8k_mib_init(struct qca8k_priv *priv); +void qca8k_port_set_status(struct qca8k_priv *priv, int port, int enable); /* Common read/write/rmw function */ int qca8k_read(struct qca8k_priv *priv, u32 reg, u32 *val); @@ -443,4 +444,14 @@ int qca8k_bulk_write(struct qca8k_priv *priv, u32 reg, u32 *val, int len); /* Common ops function */ int qca8k_busy_wait(struct qca8k_priv *priv, u32 reg, u32 mask); +/* Common ethtool stats function */ +void qca8k_get_strings(struct dsa_switch *ds, int port, u32 stringset, uint8_t *data); +void qca8k_get_ethtool_stats(struct dsa_switch *ds, int port, + uint64_t *data); +int qca8k_get_sset_count(struct dsa_switch *ds, int port, int sset); + +/* Common eee function */ +int qca8k_set_mac_eee(struct dsa_switch *ds, int port, struct ethtool_eee *eee); +int qca8k_get_mac_eee(struct dsa_switch *ds, int port, struct ethtool_eee *e); + #endif /* __QCA8K_H */ From fd3cae2f3ac190d06e48f43739237e02f9dc51ff Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Wed, 27 Jul 2022 13:35:17 +0200 Subject: [PATCH 08/14] net: dsa: qca8k: move bridge functions to common code The same bridge functions are used by drivers based on qca8k family switch. Move them to common code to make them accessible also by other drivers. While at it also drop unnecessary qca8k_priv cast for void pointers. Signed-off-by: Christian Marangi Reviewed-by: Vladimir Oltean Signed-off-by: Jakub Kicinski --- drivers/net/dsa/qca/qca8k-8xxx.c | 93 ------------------------------ drivers/net/dsa/qca/qca8k-common.c | 93 ++++++++++++++++++++++++++++++ drivers/net/dsa/qca/qca8k.h | 9 +++ 3 files changed, 102 insertions(+), 93 deletions(-) diff --git a/drivers/net/dsa/qca/qca8k-8xxx.c b/drivers/net/dsa/qca/qca8k-8xxx.c index 9729aabbd83d6..11d3116733af3 100644 --- a/drivers/net/dsa/qca/qca8k-8xxx.c +++ b/drivers/net/dsa/qca/qca8k-8xxx.c @@ -1898,99 +1898,6 @@ qca8k_get_ethtool_stats_eth(struct dsa_switch *ds, int port, u64 *data) return ret; } -static void -qca8k_port_stp_state_set(struct dsa_switch *ds, int port, u8 state) -{ - struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv; - u32 stp_state; - - switch (state) { - case BR_STATE_DISABLED: - stp_state = QCA8K_PORT_LOOKUP_STATE_DISABLED; - break; - case BR_STATE_BLOCKING: - stp_state = QCA8K_PORT_LOOKUP_STATE_BLOCKING; - break; - case BR_STATE_LISTENING: - stp_state = QCA8K_PORT_LOOKUP_STATE_LISTENING; - break; - case BR_STATE_LEARNING: - stp_state = QCA8K_PORT_LOOKUP_STATE_LEARNING; - break; - case BR_STATE_FORWARDING: - default: - stp_state = QCA8K_PORT_LOOKUP_STATE_FORWARD; - break; - } - - qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port), - QCA8K_PORT_LOOKUP_STATE_MASK, stp_state); -} - -static int qca8k_port_bridge_join(struct dsa_switch *ds, int port, - struct dsa_bridge bridge, - bool *tx_fwd_offload, - struct netlink_ext_ack *extack) -{ - struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv; - int port_mask, cpu_port; - int i, ret; - - cpu_port = dsa_to_port(ds, port)->cpu_dp->index; - port_mask = BIT(cpu_port); - - for (i = 0; i < QCA8K_NUM_PORTS; i++) { - if (dsa_is_cpu_port(ds, i)) - continue; - if (!dsa_port_offloads_bridge(dsa_to_port(ds, i), &bridge)) - continue; - /* Add this port to the portvlan mask of the other ports - * in the bridge - */ - ret = regmap_set_bits(priv->regmap, - QCA8K_PORT_LOOKUP_CTRL(i), - BIT(port)); - if (ret) - return ret; - if (i != port) - port_mask |= BIT(i); - } - - /* Add all other ports to this ports portvlan mask */ - ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port), - QCA8K_PORT_LOOKUP_MEMBER, port_mask); - - return ret; -} - -static void qca8k_port_bridge_leave(struct dsa_switch *ds, int port, - struct dsa_bridge bridge) -{ - struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv; - int cpu_port, i; - - cpu_port = dsa_to_port(ds, port)->cpu_dp->index; - - for (i = 0; i < QCA8K_NUM_PORTS; i++) { - if (dsa_is_cpu_port(ds, i)) - continue; - if (!dsa_port_offloads_bridge(dsa_to_port(ds, i), &bridge)) - continue; - /* Remove this port to the portvlan mask of the other ports - * in the bridge - */ - regmap_clear_bits(priv->regmap, - QCA8K_PORT_LOOKUP_CTRL(i), - BIT(port)); - } - - /* Set the cpu port to be the only one in the portvlan mask of - * this port - */ - qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port), - QCA8K_PORT_LOOKUP_MEMBER, BIT(cpu_port)); -} - static void qca8k_port_fast_age(struct dsa_switch *ds, int port) { diff --git a/drivers/net/dsa/qca/qca8k-common.c b/drivers/net/dsa/qca/qca8k-common.c index c662fdbae51f4..13069c9ba3e6c 100644 --- a/drivers/net/dsa/qca/qca8k-common.c +++ b/drivers/net/dsa/qca/qca8k-common.c @@ -8,6 +8,7 @@ #include #include +#include #include "qca8k.h" @@ -275,3 +276,95 @@ int qca8k_get_mac_eee(struct dsa_switch *ds, int port, /* Nothing to do on the port's MAC */ return 0; } + +void qca8k_port_stp_state_set(struct dsa_switch *ds, int port, u8 state) +{ + struct qca8k_priv *priv = ds->priv; + u32 stp_state; + + switch (state) { + case BR_STATE_DISABLED: + stp_state = QCA8K_PORT_LOOKUP_STATE_DISABLED; + break; + case BR_STATE_BLOCKING: + stp_state = QCA8K_PORT_LOOKUP_STATE_BLOCKING; + break; + case BR_STATE_LISTENING: + stp_state = QCA8K_PORT_LOOKUP_STATE_LISTENING; + break; + case BR_STATE_LEARNING: + stp_state = QCA8K_PORT_LOOKUP_STATE_LEARNING; + break; + case BR_STATE_FORWARDING: + default: + stp_state = QCA8K_PORT_LOOKUP_STATE_FORWARD; + break; + } + + qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port), + QCA8K_PORT_LOOKUP_STATE_MASK, stp_state); +} + +int qca8k_port_bridge_join(struct dsa_switch *ds, int port, + struct dsa_bridge bridge, + bool *tx_fwd_offload, + struct netlink_ext_ack *extack) +{ + struct qca8k_priv *priv = ds->priv; + int port_mask, cpu_port; + int i, ret; + + cpu_port = dsa_to_port(ds, port)->cpu_dp->index; + port_mask = BIT(cpu_port); + + for (i = 0; i < QCA8K_NUM_PORTS; i++) { + if (dsa_is_cpu_port(ds, i)) + continue; + if (!dsa_port_offloads_bridge(dsa_to_port(ds, i), &bridge)) + continue; + /* Add this port to the portvlan mask of the other ports + * in the bridge + */ + ret = regmap_set_bits(priv->regmap, + QCA8K_PORT_LOOKUP_CTRL(i), + BIT(port)); + if (ret) + return ret; + if (i != port) + port_mask |= BIT(i); + } + + /* Add all other ports to this ports portvlan mask */ + ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port), + QCA8K_PORT_LOOKUP_MEMBER, port_mask); + + return ret; +} + +void qca8k_port_bridge_leave(struct dsa_switch *ds, int port, + struct dsa_bridge bridge) +{ + struct qca8k_priv *priv = ds->priv; + int cpu_port, i; + + cpu_port = dsa_to_port(ds, port)->cpu_dp->index; + + for (i = 0; i < QCA8K_NUM_PORTS; i++) { + if (dsa_is_cpu_port(ds, i)) + continue; + if (!dsa_port_offloads_bridge(dsa_to_port(ds, i), &bridge)) + continue; + /* Remove this port to the portvlan mask of the other ports + * in the bridge + */ + regmap_clear_bits(priv->regmap, + QCA8K_PORT_LOOKUP_CTRL(i), + BIT(port)); + } + + /* Set the cpu port to be the only one in the portvlan mask of + * this port + */ + qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port), + QCA8K_PORT_LOOKUP_MEMBER, BIT(cpu_port)); +} diff --git a/drivers/net/dsa/qca/qca8k.h b/drivers/net/dsa/qca/qca8k.h index c73cbdfc6ac65..edb2b23a02b98 100644 --- a/drivers/net/dsa/qca/qca8k.h +++ b/drivers/net/dsa/qca/qca8k.h @@ -454,4 +454,13 @@ int qca8k_get_sset_count(struct dsa_switch *ds, int port, int sset); int qca8k_set_mac_eee(struct dsa_switch *ds, int port, struct ethtool_eee *eee); int qca8k_get_mac_eee(struct dsa_switch *ds, int port, struct ethtool_eee *e); +/* Common bridge function */ +void qca8k_port_stp_state_set(struct dsa_switch *ds, int port, u8 state); +int qca8k_port_bridge_join(struct dsa_switch *ds, int port, + struct dsa_bridge bridge, + bool *tx_fwd_offload, + struct netlink_ext_ack *extack); +void qca8k_port_bridge_leave(struct dsa_switch *ds, int port, + struct dsa_bridge bridge); + #endif /* __QCA8K_H */ From b3a302b171f73425b41de8d3357fae3fa7057322 Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Wed, 27 Jul 2022 13:35:18 +0200 Subject: [PATCH 09/14] net: dsa: qca8k: move set age/MTU/port enable/disable functions to common code The same set age, MTU and port enable/disable function are used by driver based on qca8k family switch. Move them to common code to make them accessible also by other drivers. While at it also drop unnecessary qca8k_priv cast for void pointers. Signed-off-by: Christian Marangi Reviewed-by: Vladimir Oltean Signed-off-by: Jakub Kicinski --- drivers/net/dsa/qca/qca8k-8xxx.c | 88 ------------------------------ drivers/net/dsa/qca/qca8k-common.c | 85 +++++++++++++++++++++++++++++ drivers/net/dsa/qca/qca8k.h | 12 ++++ 3 files changed, 97 insertions(+), 88 deletions(-) diff --git a/drivers/net/dsa/qca/qca8k-8xxx.c b/drivers/net/dsa/qca/qca8k-8xxx.c index 11d3116733af3..1815863502c9f 100644 --- a/drivers/net/dsa/qca/qca8k-8xxx.c +++ b/drivers/net/dsa/qca/qca8k-8xxx.c @@ -1908,94 +1908,6 @@ qca8k_port_fast_age(struct dsa_switch *ds, int port) mutex_unlock(&priv->reg_mutex); } -static int -qca8k_set_ageing_time(struct dsa_switch *ds, unsigned int msecs) -{ - struct qca8k_priv *priv = ds->priv; - unsigned int secs = msecs / 1000; - u32 val; - - /* AGE_TIME reg is set in 7s step */ - val = secs / 7; - - /* Handle case with 0 as val to NOT disable - * learning - */ - if (!val) - val = 1; - - return regmap_update_bits(priv->regmap, QCA8K_REG_ATU_CTRL, QCA8K_ATU_AGE_TIME_MASK, - QCA8K_ATU_AGE_TIME(val)); -} - -static int -qca8k_port_enable(struct dsa_switch *ds, int port, - struct phy_device *phy) -{ - struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv; - - qca8k_port_set_status(priv, port, 1); - priv->port_enabled_map |= BIT(port); - - if (dsa_is_user_port(ds, port)) - phy_support_asym_pause(phy); - - return 0; -} - -static void -qca8k_port_disable(struct dsa_switch *ds, int port) -{ - struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv; - - qca8k_port_set_status(priv, port, 0); - priv->port_enabled_map &= ~BIT(port); -} - -static int -qca8k_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu) -{ - struct qca8k_priv *priv = ds->priv; - int ret; - - /* We have only have a general MTU setting. - * DSA always set the CPU port's MTU to the largest MTU of the slave - * ports. - * Setting MTU just for the CPU port is sufficient to correctly set a - * value for every port. - */ - if (!dsa_is_cpu_port(ds, port)) - return 0; - - /* To change the MAX_FRAME_SIZE the cpu ports must be off or - * the switch panics. - * Turn off both cpu ports before applying the new value to prevent - * this. - */ - if (priv->port_enabled_map & BIT(0)) - qca8k_port_set_status(priv, 0, 0); - - if (priv->port_enabled_map & BIT(6)) - qca8k_port_set_status(priv, 6, 0); - - /* Include L2 header / FCS length */ - ret = qca8k_write(priv, QCA8K_MAX_FRAME_SIZE, new_mtu + ETH_HLEN + ETH_FCS_LEN); - - if (priv->port_enabled_map & BIT(0)) - qca8k_port_set_status(priv, 0, 1); - - if (priv->port_enabled_map & BIT(6)) - qca8k_port_set_status(priv, 6, 1); - - return ret; -} - -static int -qca8k_port_max_mtu(struct dsa_switch *ds, int port) -{ - return QCA8K_MAX_MTU; -} - static int qca8k_port_fdb_insert(struct qca8k_priv *priv, const u8 *addr, u16 port_mask, u16 vid) diff --git a/drivers/net/dsa/qca/qca8k-common.c b/drivers/net/dsa/qca/qca8k-common.c index 13069c9ba3e6c..e25df4b23b9ae 100644 --- a/drivers/net/dsa/qca/qca8k-common.c +++ b/drivers/net/dsa/qca/qca8k-common.c @@ -368,3 +368,88 @@ void qca8k_port_bridge_leave(struct dsa_switch *ds, int port, qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port), QCA8K_PORT_LOOKUP_MEMBER, BIT(cpu_port)); } + +int qca8k_set_ageing_time(struct dsa_switch *ds, unsigned int msecs) +{ + struct qca8k_priv *priv = ds->priv; + unsigned int secs = msecs / 1000; + u32 val; + + /* AGE_TIME reg is set in 7s step */ + val = secs / 7; + + /* Handle case with 0 as val to NOT disable + * learning + */ + if (!val) + val = 1; + + return regmap_update_bits(priv->regmap, QCA8K_REG_ATU_CTRL, + QCA8K_ATU_AGE_TIME_MASK, + QCA8K_ATU_AGE_TIME(val)); +} + +int qca8k_port_enable(struct dsa_switch *ds, int port, + struct phy_device *phy) +{ + struct qca8k_priv *priv = ds->priv; + + qca8k_port_set_status(priv, port, 1); + priv->port_enabled_map |= BIT(port); + + if (dsa_is_user_port(ds, port)) + phy_support_asym_pause(phy); + + return 0; +} + +void qca8k_port_disable(struct dsa_switch *ds, int port) +{ + struct qca8k_priv *priv = ds->priv; + + qca8k_port_set_status(priv, port, 0); + priv->port_enabled_map &= ~BIT(port); +} + +int qca8k_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu) +{ + struct qca8k_priv *priv = ds->priv; + int ret; + + /* We have only have a general MTU setting. + * DSA always set the CPU port's MTU to the largest MTU of the slave + * ports. + * Setting MTU just for the CPU port is sufficient to correctly set a + * value for every port. + */ + if (!dsa_is_cpu_port(ds, port)) + return 0; + + /* To change the MAX_FRAME_SIZE the cpu ports must be off or + * the switch panics. + * Turn off both cpu ports before applying the new value to prevent + * this. + */ + if (priv->port_enabled_map & BIT(0)) + qca8k_port_set_status(priv, 0, 0); + + if (priv->port_enabled_map & BIT(6)) + qca8k_port_set_status(priv, 6, 0); + + /* Include L2 header / FCS length */ + ret = qca8k_write(priv, QCA8K_MAX_FRAME_SIZE, new_mtu + + ETH_HLEN + ETH_FCS_LEN); + + if (priv->port_enabled_map & BIT(0)) + qca8k_port_set_status(priv, 0, 1); + + if (priv->port_enabled_map & BIT(6)) + qca8k_port_set_status(priv, 6, 1); + + return ret; +} + +int qca8k_port_max_mtu(struct dsa_switch *ds, int port) +{ + return QCA8K_MAX_MTU; +} diff --git a/drivers/net/dsa/qca/qca8k.h b/drivers/net/dsa/qca/qca8k.h index edb2b23a02b98..e7d4df253b8c0 100644 --- a/drivers/net/dsa/qca/qca8k.h +++ b/drivers/net/dsa/qca/qca8k.h @@ -463,4 +463,16 @@ int qca8k_port_bridge_join(struct dsa_switch *ds, int port, void qca8k_port_bridge_leave(struct dsa_switch *ds, int port, struct dsa_bridge bridge); +/* Common port enable/disable function */ +int qca8k_port_enable(struct dsa_switch *ds, int port, + struct phy_device *phy); +void qca8k_port_disable(struct dsa_switch *ds, int port); + +/* Common MTU function */ +int qca8k_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu); +int qca8k_port_max_mtu(struct dsa_switch *ds, int port); + +/* Common fast age function */ +int qca8k_set_ageing_time(struct dsa_switch *ds, unsigned int msecs); + #endif /* __QCA8K_H */ From 2e5bd96eea86a246b4de3bf756f7a11b43e6187d Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Wed, 27 Jul 2022 13:35:19 +0200 Subject: [PATCH 10/14] net: dsa: qca8k: move port FDB/MDB function to common code The same port FDB/MDB function are used by drivers based on qca8k family switch. Move them to common code to make them accessible also by other drivers. Also drop bulk read/write functions and make them static Signed-off-by: Christian Marangi Reviewed-by: Vladimir Oltean Signed-off-by: Jakub Kicinski --- drivers/net/dsa/qca/qca8k-8xxx.c | 306 ----------------------------- drivers/net/dsa/qca/qca8k-common.c | 297 +++++++++++++++++++++++++++- drivers/net/dsa/qca/qca8k.h | 25 ++- 3 files changed, 317 insertions(+), 311 deletions(-) diff --git a/drivers/net/dsa/qca/qca8k-8xxx.c b/drivers/net/dsa/qca/qca8k-8xxx.c index 1815863502c9f..bf4dd1cd0f9d8 100644 --- a/drivers/net/dsa/qca/qca8k-8xxx.c +++ b/drivers/net/dsa/qca/qca8k-8xxx.c @@ -441,217 +441,6 @@ static struct regmap_config qca8k_regmap_config = { .cache_type = REGCACHE_NONE, /* Explicitly disable CACHE */ }; -static int -qca8k_fdb_read(struct qca8k_priv *priv, struct qca8k_fdb *fdb) -{ - u32 reg[3]; - int ret; - - /* load the ARL table into an array */ - ret = qca8k_bulk_read(priv, QCA8K_REG_ATU_DATA0, reg, sizeof(reg)); - if (ret) - return ret; - - /* vid - 83:72 */ - fdb->vid = FIELD_GET(QCA8K_ATU_VID_MASK, reg[2]); - /* aging - 67:64 */ - fdb->aging = FIELD_GET(QCA8K_ATU_STATUS_MASK, reg[2]); - /* portmask - 54:48 */ - fdb->port_mask = FIELD_GET(QCA8K_ATU_PORT_MASK, reg[1]); - /* mac - 47:0 */ - fdb->mac[0] = FIELD_GET(QCA8K_ATU_ADDR0_MASK, reg[1]); - fdb->mac[1] = FIELD_GET(QCA8K_ATU_ADDR1_MASK, reg[1]); - fdb->mac[2] = FIELD_GET(QCA8K_ATU_ADDR2_MASK, reg[0]); - fdb->mac[3] = FIELD_GET(QCA8K_ATU_ADDR3_MASK, reg[0]); - fdb->mac[4] = FIELD_GET(QCA8K_ATU_ADDR4_MASK, reg[0]); - fdb->mac[5] = FIELD_GET(QCA8K_ATU_ADDR5_MASK, reg[0]); - - return 0; -} - -static void -qca8k_fdb_write(struct qca8k_priv *priv, u16 vid, u8 port_mask, const u8 *mac, - u8 aging) -{ - u32 reg[3] = { 0 }; - - /* vid - 83:72 */ - reg[2] = FIELD_PREP(QCA8K_ATU_VID_MASK, vid); - /* aging - 67:64 */ - reg[2] |= FIELD_PREP(QCA8K_ATU_STATUS_MASK, aging); - /* portmask - 54:48 */ - reg[1] = FIELD_PREP(QCA8K_ATU_PORT_MASK, port_mask); - /* mac - 47:0 */ - reg[1] |= FIELD_PREP(QCA8K_ATU_ADDR0_MASK, mac[0]); - reg[1] |= FIELD_PREP(QCA8K_ATU_ADDR1_MASK, mac[1]); - reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR2_MASK, mac[2]); - reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR3_MASK, mac[3]); - reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR4_MASK, mac[4]); - reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR5_MASK, mac[5]); - - /* load the array into the ARL table */ - qca8k_bulk_write(priv, QCA8K_REG_ATU_DATA0, reg, sizeof(reg)); -} - -static int -qca8k_fdb_access(struct qca8k_priv *priv, enum qca8k_fdb_cmd cmd, int port) -{ - u32 reg; - int ret; - - /* Set the command and FDB index */ - reg = QCA8K_ATU_FUNC_BUSY; - reg |= cmd; - if (port >= 0) { - reg |= QCA8K_ATU_FUNC_PORT_EN; - reg |= FIELD_PREP(QCA8K_ATU_FUNC_PORT_MASK, port); - } - - /* Write the function register triggering the table access */ - ret = qca8k_write(priv, QCA8K_REG_ATU_FUNC, reg); - if (ret) - return ret; - - /* wait for completion */ - ret = qca8k_busy_wait(priv, QCA8K_REG_ATU_FUNC, QCA8K_ATU_FUNC_BUSY); - if (ret) - return ret; - - /* Check for table full violation when adding an entry */ - if (cmd == QCA8K_FDB_LOAD) { - ret = qca8k_read(priv, QCA8K_REG_ATU_FUNC, ®); - if (ret < 0) - return ret; - if (reg & QCA8K_ATU_FUNC_FULL) - return -1; - } - - return 0; -} - -static int -qca8k_fdb_next(struct qca8k_priv *priv, struct qca8k_fdb *fdb, int port) -{ - int ret; - - qca8k_fdb_write(priv, fdb->vid, fdb->port_mask, fdb->mac, fdb->aging); - ret = qca8k_fdb_access(priv, QCA8K_FDB_NEXT, port); - if (ret < 0) - return ret; - - return qca8k_fdb_read(priv, fdb); -} - -static int -qca8k_fdb_add(struct qca8k_priv *priv, const u8 *mac, u16 port_mask, - u16 vid, u8 aging) -{ - int ret; - - mutex_lock(&priv->reg_mutex); - qca8k_fdb_write(priv, vid, port_mask, mac, aging); - ret = qca8k_fdb_access(priv, QCA8K_FDB_LOAD, -1); - mutex_unlock(&priv->reg_mutex); - - return ret; -} - -static int -qca8k_fdb_del(struct qca8k_priv *priv, const u8 *mac, u16 port_mask, u16 vid) -{ - int ret; - - mutex_lock(&priv->reg_mutex); - qca8k_fdb_write(priv, vid, port_mask, mac, 0); - ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1); - mutex_unlock(&priv->reg_mutex); - - return ret; -} - -static void -qca8k_fdb_flush(struct qca8k_priv *priv) -{ - mutex_lock(&priv->reg_mutex); - qca8k_fdb_access(priv, QCA8K_FDB_FLUSH, -1); - mutex_unlock(&priv->reg_mutex); -} - -static int -qca8k_fdb_search_and_insert(struct qca8k_priv *priv, u8 port_mask, - const u8 *mac, u16 vid) -{ - struct qca8k_fdb fdb = { 0 }; - int ret; - - mutex_lock(&priv->reg_mutex); - - qca8k_fdb_write(priv, vid, 0, mac, 0); - ret = qca8k_fdb_access(priv, QCA8K_FDB_SEARCH, -1); - if (ret < 0) - goto exit; - - ret = qca8k_fdb_read(priv, &fdb); - if (ret < 0) - goto exit; - - /* Rule exist. Delete first */ - if (!fdb.aging) { - ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1); - if (ret) - goto exit; - } - - /* Add port to fdb portmask */ - fdb.port_mask |= port_mask; - - qca8k_fdb_write(priv, vid, fdb.port_mask, mac, fdb.aging); - ret = qca8k_fdb_access(priv, QCA8K_FDB_LOAD, -1); - -exit: - mutex_unlock(&priv->reg_mutex); - return ret; -} - -static int -qca8k_fdb_search_and_del(struct qca8k_priv *priv, u8 port_mask, - const u8 *mac, u16 vid) -{ - struct qca8k_fdb fdb = { 0 }; - int ret; - - mutex_lock(&priv->reg_mutex); - - qca8k_fdb_write(priv, vid, 0, mac, 0); - ret = qca8k_fdb_access(priv, QCA8K_FDB_SEARCH, -1); - if (ret < 0) - goto exit; - - /* Rule doesn't exist. Why delete? */ - if (!fdb.aging) { - ret = -EINVAL; - goto exit; - } - - ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1); - if (ret) - goto exit; - - /* Only port in the rule is this port. Don't re insert */ - if (fdb.port_mask == port_mask) - goto exit; - - /* Remove port from port mask */ - fdb.port_mask &= ~port_mask; - - qca8k_fdb_write(priv, vid, fdb.port_mask, mac, fdb.aging); - ret = qca8k_fdb_access(priv, QCA8K_FDB_LOAD, -1); - -exit: - mutex_unlock(&priv->reg_mutex); - return ret; -} - static int qca8k_vlan_access(struct qca8k_priv *priv, enum qca8k_vlan_cmd cmd, u16 vid) { @@ -1898,101 +1687,6 @@ qca8k_get_ethtool_stats_eth(struct dsa_switch *ds, int port, u64 *data) return ret; } -static void -qca8k_port_fast_age(struct dsa_switch *ds, int port) -{ - struct qca8k_priv *priv = ds->priv; - - mutex_lock(&priv->reg_mutex); - qca8k_fdb_access(priv, QCA8K_FDB_FLUSH_PORT, port); - mutex_unlock(&priv->reg_mutex); -} - -static int -qca8k_port_fdb_insert(struct qca8k_priv *priv, const u8 *addr, - u16 port_mask, u16 vid) -{ - /* Set the vid to the port vlan id if no vid is set */ - if (!vid) - vid = QCA8K_PORT_VID_DEF; - - return qca8k_fdb_add(priv, addr, port_mask, vid, - QCA8K_ATU_STATUS_STATIC); -} - -static int -qca8k_port_fdb_add(struct dsa_switch *ds, int port, - const unsigned char *addr, u16 vid, - struct dsa_db db) -{ - struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv; - u16 port_mask = BIT(port); - - return qca8k_port_fdb_insert(priv, addr, port_mask, vid); -} - -static int -qca8k_port_fdb_del(struct dsa_switch *ds, int port, - const unsigned char *addr, u16 vid, - struct dsa_db db) -{ - struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv; - u16 port_mask = BIT(port); - - if (!vid) - vid = QCA8K_PORT_VID_DEF; - - return qca8k_fdb_del(priv, addr, port_mask, vid); -} - -static int -qca8k_port_fdb_dump(struct dsa_switch *ds, int port, - dsa_fdb_dump_cb_t *cb, void *data) -{ - struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv; - struct qca8k_fdb _fdb = { 0 }; - int cnt = QCA8K_NUM_FDB_RECORDS; - bool is_static; - int ret = 0; - - mutex_lock(&priv->reg_mutex); - while (cnt-- && !qca8k_fdb_next(priv, &_fdb, port)) { - if (!_fdb.aging) - break; - is_static = (_fdb.aging == QCA8K_ATU_STATUS_STATIC); - ret = cb(_fdb.mac, _fdb.vid, is_static, data); - if (ret) - break; - } - mutex_unlock(&priv->reg_mutex); - - return 0; -} - -static int -qca8k_port_mdb_add(struct dsa_switch *ds, int port, - const struct switchdev_obj_port_mdb *mdb, - struct dsa_db db) -{ - struct qca8k_priv *priv = ds->priv; - const u8 *addr = mdb->addr; - u16 vid = mdb->vid; - - return qca8k_fdb_search_and_insert(priv, BIT(port), addr, vid); -} - -static int -qca8k_port_mdb_del(struct dsa_switch *ds, int port, - const struct switchdev_obj_port_mdb *mdb, - struct dsa_db db) -{ - struct qca8k_priv *priv = ds->priv; - const u8 *addr = mdb->addr; - u16 vid = mdb->vid; - - return qca8k_fdb_search_and_del(priv, BIT(port), addr, vid); -} - static int qca8k_port_mirror_add(struct dsa_switch *ds, int port, struct dsa_mall_mirror_tc_entry *mirror, diff --git a/drivers/net/dsa/qca/qca8k-common.c b/drivers/net/dsa/qca/qca8k-common.c index e25df4b23b9ae..8e92837781ee5 100644 --- a/drivers/net/dsa/qca/qca8k-common.c +++ b/drivers/net/dsa/qca/qca8k-common.c @@ -102,7 +102,7 @@ const struct regmap_access_table qca8k_readable_table = { }; /* TODO: remove these extra ops when we can support regmap bulk read/write */ -int qca8k_bulk_read(struct qca8k_priv *priv, u32 reg, u32 *val, int len) +static int qca8k_bulk_read(struct qca8k_priv *priv, u32 reg, u32 *val, int len) { int i, count = len / sizeof(u32), ret; @@ -120,7 +120,7 @@ int qca8k_bulk_read(struct qca8k_priv *priv, u32 reg, u32 *val, int len) } /* TODO: remove these extra ops when we can support regmap bulk read/write */ -int qca8k_bulk_write(struct qca8k_priv *priv, u32 reg, u32 *val, int len) +static int qca8k_bulk_write(struct qca8k_priv *priv, u32 reg, u32 *val, int len) { int i, count = len / sizeof(u32), ret; u32 tmp; @@ -148,6 +148,211 @@ int qca8k_busy_wait(struct qca8k_priv *priv, u32 reg, u32 mask) QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC); } +static int qca8k_fdb_read(struct qca8k_priv *priv, struct qca8k_fdb *fdb) +{ + u32 reg[3]; + int ret; + + /* load the ARL table into an array */ + ret = qca8k_bulk_read(priv, QCA8K_REG_ATU_DATA0, reg, sizeof(reg)); + if (ret) + return ret; + + /* vid - 83:72 */ + fdb->vid = FIELD_GET(QCA8K_ATU_VID_MASK, reg[2]); + /* aging - 67:64 */ + fdb->aging = FIELD_GET(QCA8K_ATU_STATUS_MASK, reg[2]); + /* portmask - 54:48 */ + fdb->port_mask = FIELD_GET(QCA8K_ATU_PORT_MASK, reg[1]); + /* mac - 47:0 */ + fdb->mac[0] = FIELD_GET(QCA8K_ATU_ADDR0_MASK, reg[1]); + fdb->mac[1] = FIELD_GET(QCA8K_ATU_ADDR1_MASK, reg[1]); + fdb->mac[2] = FIELD_GET(QCA8K_ATU_ADDR2_MASK, reg[0]); + fdb->mac[3] = FIELD_GET(QCA8K_ATU_ADDR3_MASK, reg[0]); + fdb->mac[4] = FIELD_GET(QCA8K_ATU_ADDR4_MASK, reg[0]); + fdb->mac[5] = FIELD_GET(QCA8K_ATU_ADDR5_MASK, reg[0]); + + return 0; +} + +static void qca8k_fdb_write(struct qca8k_priv *priv, u16 vid, u8 port_mask, + const u8 *mac, u8 aging) +{ + u32 reg[3] = { 0 }; + + /* vid - 83:72 */ + reg[2] = FIELD_PREP(QCA8K_ATU_VID_MASK, vid); + /* aging - 67:64 */ + reg[2] |= FIELD_PREP(QCA8K_ATU_STATUS_MASK, aging); + /* portmask - 54:48 */ + reg[1] = FIELD_PREP(QCA8K_ATU_PORT_MASK, port_mask); + /* mac - 47:0 */ + reg[1] |= FIELD_PREP(QCA8K_ATU_ADDR0_MASK, mac[0]); + reg[1] |= FIELD_PREP(QCA8K_ATU_ADDR1_MASK, mac[1]); + reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR2_MASK, mac[2]); + reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR3_MASK, mac[3]); + reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR4_MASK, mac[4]); + reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR5_MASK, mac[5]); + + /* load the array into the ARL table */ + qca8k_bulk_write(priv, QCA8K_REG_ATU_DATA0, reg, sizeof(reg)); +} + +static int qca8k_fdb_access(struct qca8k_priv *priv, enum qca8k_fdb_cmd cmd, + int port) +{ + u32 reg; + int ret; + + /* Set the command and FDB index */ + reg = QCA8K_ATU_FUNC_BUSY; + reg |= cmd; + if (port >= 0) { + reg |= QCA8K_ATU_FUNC_PORT_EN; + reg |= FIELD_PREP(QCA8K_ATU_FUNC_PORT_MASK, port); + } + + /* Write the function register triggering the table access */ + ret = qca8k_write(priv, QCA8K_REG_ATU_FUNC, reg); + if (ret) + return ret; + + /* wait for completion */ + ret = qca8k_busy_wait(priv, QCA8K_REG_ATU_FUNC, QCA8K_ATU_FUNC_BUSY); + if (ret) + return ret; + + /* Check for table full violation when adding an entry */ + if (cmd == QCA8K_FDB_LOAD) { + ret = qca8k_read(priv, QCA8K_REG_ATU_FUNC, ®); + if (ret < 0) + return ret; + if (reg & QCA8K_ATU_FUNC_FULL) + return -1; + } + + return 0; +} + +static int qca8k_fdb_next(struct qca8k_priv *priv, struct qca8k_fdb *fdb, + int port) +{ + int ret; + + qca8k_fdb_write(priv, fdb->vid, fdb->port_mask, fdb->mac, fdb->aging); + ret = qca8k_fdb_access(priv, QCA8K_FDB_NEXT, port); + if (ret < 0) + return ret; + + return qca8k_fdb_read(priv, fdb); +} + +static int qca8k_fdb_add(struct qca8k_priv *priv, const u8 *mac, + u16 port_mask, u16 vid, u8 aging) +{ + int ret; + + mutex_lock(&priv->reg_mutex); + qca8k_fdb_write(priv, vid, port_mask, mac, aging); + ret = qca8k_fdb_access(priv, QCA8K_FDB_LOAD, -1); + mutex_unlock(&priv->reg_mutex); + + return ret; +} + +static int qca8k_fdb_del(struct qca8k_priv *priv, const u8 *mac, + u16 port_mask, u16 vid) +{ + int ret; + + mutex_lock(&priv->reg_mutex); + qca8k_fdb_write(priv, vid, port_mask, mac, 0); + ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1); + mutex_unlock(&priv->reg_mutex); + + return ret; +} + +void qca8k_fdb_flush(struct qca8k_priv *priv) +{ + mutex_lock(&priv->reg_mutex); + qca8k_fdb_access(priv, QCA8K_FDB_FLUSH, -1); + mutex_unlock(&priv->reg_mutex); +} + +static int qca8k_fdb_search_and_insert(struct qca8k_priv *priv, u8 port_mask, + const u8 *mac, u16 vid) +{ + struct qca8k_fdb fdb = { 0 }; + int ret; + + mutex_lock(&priv->reg_mutex); + + qca8k_fdb_write(priv, vid, 0, mac, 0); + ret = qca8k_fdb_access(priv, QCA8K_FDB_SEARCH, -1); + if (ret < 0) + goto exit; + + ret = qca8k_fdb_read(priv, &fdb); + if (ret < 0) + goto exit; + + /* Rule exist. Delete first */ + if (!fdb.aging) { + ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1); + if (ret) + goto exit; + } + + /* Add port to fdb portmask */ + fdb.port_mask |= port_mask; + + qca8k_fdb_write(priv, vid, fdb.port_mask, mac, fdb.aging); + ret = qca8k_fdb_access(priv, QCA8K_FDB_LOAD, -1); + +exit: + mutex_unlock(&priv->reg_mutex); + return ret; +} + +static int qca8k_fdb_search_and_del(struct qca8k_priv *priv, u8 port_mask, + const u8 *mac, u16 vid) +{ + struct qca8k_fdb fdb = { 0 }; + int ret; + + mutex_lock(&priv->reg_mutex); + + qca8k_fdb_write(priv, vid, 0, mac, 0); + ret = qca8k_fdb_access(priv, QCA8K_FDB_SEARCH, -1); + if (ret < 0) + goto exit; + + /* Rule doesn't exist. Why delete? */ + if (!fdb.aging) { + ret = -EINVAL; + goto exit; + } + + ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1); + if (ret) + goto exit; + + /* Only port in the rule is this port. Don't re insert */ + if (fdb.port_mask == port_mask) + goto exit; + + /* Remove port from port mask */ + fdb.port_mask &= ~port_mask; + + qca8k_fdb_write(priv, vid, fdb.port_mask, mac, fdb.aging); + ret = qca8k_fdb_access(priv, QCA8K_FDB_LOAD, -1); + +exit: + mutex_unlock(&priv->reg_mutex); + return ret; +} + int qca8k_mib_init(struct qca8k_priv *priv) { int ret; @@ -369,6 +574,15 @@ void qca8k_port_bridge_leave(struct dsa_switch *ds, int port, QCA8K_PORT_LOOKUP_MEMBER, BIT(cpu_port)); } +void qca8k_port_fast_age(struct dsa_switch *ds, int port) +{ + struct qca8k_priv *priv = ds->priv; + + mutex_lock(&priv->reg_mutex); + qca8k_fdb_access(priv, QCA8K_FDB_FLUSH_PORT, port); + mutex_unlock(&priv->reg_mutex); +} + int qca8k_set_ageing_time(struct dsa_switch *ds, unsigned int msecs) { struct qca8k_priv *priv = ds->priv; @@ -453,3 +667,82 @@ int qca8k_port_max_mtu(struct dsa_switch *ds, int port) { return QCA8K_MAX_MTU; } + +int qca8k_port_fdb_insert(struct qca8k_priv *priv, const u8 *addr, + u16 port_mask, u16 vid) +{ + /* Set the vid to the port vlan id if no vid is set */ + if (!vid) + vid = QCA8K_PORT_VID_DEF; + + return qca8k_fdb_add(priv, addr, port_mask, vid, + QCA8K_ATU_STATUS_STATIC); +} + +int qca8k_port_fdb_add(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid, + struct dsa_db db) +{ + struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv; + u16 port_mask = BIT(port); + + return qca8k_port_fdb_insert(priv, addr, port_mask, vid); +} + +int qca8k_port_fdb_del(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid, + struct dsa_db db) +{ + struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv; + u16 port_mask = BIT(port); + + if (!vid) + vid = QCA8K_PORT_VID_DEF; + + return qca8k_fdb_del(priv, addr, port_mask, vid); +} + +int qca8k_port_fdb_dump(struct dsa_switch *ds, int port, + dsa_fdb_dump_cb_t *cb, void *data) +{ + struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv; + struct qca8k_fdb _fdb = { 0 }; + int cnt = QCA8K_NUM_FDB_RECORDS; + bool is_static; + int ret = 0; + + mutex_lock(&priv->reg_mutex); + while (cnt-- && !qca8k_fdb_next(priv, &_fdb, port)) { + if (!_fdb.aging) + break; + is_static = (_fdb.aging == QCA8K_ATU_STATUS_STATIC); + ret = cb(_fdb.mac, _fdb.vid, is_static, data); + if (ret) + break; + } + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +int qca8k_port_mdb_add(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_mdb *mdb, + struct dsa_db db) +{ + struct qca8k_priv *priv = ds->priv; + const u8 *addr = mdb->addr; + u16 vid = mdb->vid; + + return qca8k_fdb_search_and_insert(priv, BIT(port), addr, vid); +} + +int qca8k_port_mdb_del(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_mdb *mdb, + struct dsa_db db) +{ + struct qca8k_priv *priv = ds->priv; + const u8 *addr = mdb->addr; + u16 vid = mdb->vid; + + return qca8k_fdb_search_and_del(priv, BIT(port), addr, vid); +} diff --git a/drivers/net/dsa/qca/qca8k.h b/drivers/net/dsa/qca/qca8k.h index e7d4df253b8c0..9da7928de83ca 100644 --- a/drivers/net/dsa/qca/qca8k.h +++ b/drivers/net/dsa/qca/qca8k.h @@ -438,11 +438,9 @@ int qca8k_read(struct qca8k_priv *priv, u32 reg, u32 *val); int qca8k_write(struct qca8k_priv *priv, u32 reg, u32 val); int qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val); -int qca8k_bulk_read(struct qca8k_priv *priv, u32 reg, u32 *val, int len); -int qca8k_bulk_write(struct qca8k_priv *priv, u32 reg, u32 *val, int len); - /* Common ops function */ int qca8k_busy_wait(struct qca8k_priv *priv, u32 reg, u32 mask); +void qca8k_fdb_flush(struct qca8k_priv *priv); /* Common ethtool stats function */ void qca8k_get_strings(struct dsa_switch *ds, int port, u32 stringset, uint8_t *data); @@ -473,6 +471,27 @@ int qca8k_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu); int qca8k_port_max_mtu(struct dsa_switch *ds, int port); /* Common fast age function */ +void qca8k_port_fast_age(struct dsa_switch *ds, int port); int qca8k_set_ageing_time(struct dsa_switch *ds, unsigned int msecs); +/* Common FDB function */ +int qca8k_port_fdb_insert(struct qca8k_priv *priv, const u8 *addr, + u16 port_mask, u16 vid); +int qca8k_port_fdb_add(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid, + struct dsa_db db); +int qca8k_port_fdb_del(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid, + struct dsa_db db); +int qca8k_port_fdb_dump(struct dsa_switch *ds, int port, + dsa_fdb_dump_cb_t *cb, void *data); + +/* Common MDB function */ +int qca8k_port_mdb_add(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_mdb *mdb, + struct dsa_db db); +int qca8k_port_mdb_del(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_mdb *mdb, + struct dsa_db db); + #endif /* __QCA8K_H */ From 742d37a84d3f7bb60d9b2d9ada9ad4e599f65ebf Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Wed, 27 Jul 2022 13:35:20 +0200 Subject: [PATCH 11/14] net: dsa: qca8k: move port mirror functions to common code The same port mirror functions are used by drivers based on qca8k family switch. Move them to common code to make them accessible also by other drivers. Signed-off-by: Christian Marangi Reviewed-by: Vladimir Oltean Signed-off-by: Jakub Kicinski --- drivers/net/dsa/qca/qca8k-8xxx.c | 93 ------------------------------ drivers/net/dsa/qca/qca8k-common.c | 91 +++++++++++++++++++++++++++++ drivers/net/dsa/qca/qca8k.h | 7 +++ 3 files changed, 98 insertions(+), 93 deletions(-) diff --git a/drivers/net/dsa/qca/qca8k-8xxx.c b/drivers/net/dsa/qca/qca8k-8xxx.c index bf4dd1cd0f9d8..f6ea5a82da9ef 100644 --- a/drivers/net/dsa/qca/qca8k-8xxx.c +++ b/drivers/net/dsa/qca/qca8k-8xxx.c @@ -1687,99 +1687,6 @@ qca8k_get_ethtool_stats_eth(struct dsa_switch *ds, int port, u64 *data) return ret; } -static int -qca8k_port_mirror_add(struct dsa_switch *ds, int port, - struct dsa_mall_mirror_tc_entry *mirror, - bool ingress, struct netlink_ext_ack *extack) -{ - struct qca8k_priv *priv = ds->priv; - int monitor_port, ret; - u32 reg, val; - - /* Check for existent entry */ - if ((ingress ? priv->mirror_rx : priv->mirror_tx) & BIT(port)) - return -EEXIST; - - ret = regmap_read(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0, &val); - if (ret) - return ret; - - /* QCA83xx can have only one port set to mirror mode. - * Check that the correct port is requested and return error otherwise. - * When no mirror port is set, the values is set to 0xF - */ - monitor_port = FIELD_GET(QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, val); - if (monitor_port != 0xF && monitor_port != mirror->to_local_port) - return -EEXIST; - - /* Set the monitor port */ - val = FIELD_PREP(QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, - mirror->to_local_port); - ret = regmap_update_bits(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0, - QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, val); - if (ret) - return ret; - - if (ingress) { - reg = QCA8K_PORT_LOOKUP_CTRL(port); - val = QCA8K_PORT_LOOKUP_ING_MIRROR_EN; - } else { - reg = QCA8K_REG_PORT_HOL_CTRL1(port); - val = QCA8K_PORT_HOL_CTRL1_EG_MIRROR_EN; - } - - ret = regmap_update_bits(priv->regmap, reg, val, val); - if (ret) - return ret; - - /* Track mirror port for tx and rx to decide when the - * mirror port has to be disabled. - */ - if (ingress) - priv->mirror_rx |= BIT(port); - else - priv->mirror_tx |= BIT(port); - - return 0; -} - -static void -qca8k_port_mirror_del(struct dsa_switch *ds, int port, - struct dsa_mall_mirror_tc_entry *mirror) -{ - struct qca8k_priv *priv = ds->priv; - u32 reg, val; - int ret; - - if (mirror->ingress) { - reg = QCA8K_PORT_LOOKUP_CTRL(port); - val = QCA8K_PORT_LOOKUP_ING_MIRROR_EN; - } else { - reg = QCA8K_REG_PORT_HOL_CTRL1(port); - val = QCA8K_PORT_HOL_CTRL1_EG_MIRROR_EN; - } - - ret = regmap_clear_bits(priv->regmap, reg, val); - if (ret) - goto err; - - if (mirror->ingress) - priv->mirror_rx &= ~BIT(port); - else - priv->mirror_tx &= ~BIT(port); - - /* No port set to send packet to mirror port. Disable mirror port */ - if (!priv->mirror_rx && !priv->mirror_tx) { - val = FIELD_PREP(QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, 0xF); - ret = regmap_update_bits(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0, - QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, val); - if (ret) - goto err; - } -err: - dev_err(priv->dev, "Failed to del mirror port from %d", port); -} - static int qca8k_port_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering, struct netlink_ext_ack *extack) diff --git a/drivers/net/dsa/qca/qca8k-common.c b/drivers/net/dsa/qca/qca8k-common.c index 8e92837781ee5..c0a088418c20e 100644 --- a/drivers/net/dsa/qca/qca8k-common.c +++ b/drivers/net/dsa/qca/qca8k-common.c @@ -746,3 +746,94 @@ int qca8k_port_mdb_del(struct dsa_switch *ds, int port, return qca8k_fdb_search_and_del(priv, BIT(port), addr, vid); } + +int qca8k_port_mirror_add(struct dsa_switch *ds, int port, + struct dsa_mall_mirror_tc_entry *mirror, + bool ingress, struct netlink_ext_ack *extack) +{ + struct qca8k_priv *priv = ds->priv; + int monitor_port, ret; + u32 reg, val; + + /* Check for existent entry */ + if ((ingress ? priv->mirror_rx : priv->mirror_tx) & BIT(port)) + return -EEXIST; + + ret = regmap_read(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0, &val); + if (ret) + return ret; + + /* QCA83xx can have only one port set to mirror mode. + * Check that the correct port is requested and return error otherwise. + * When no mirror port is set, the values is set to 0xF + */ + monitor_port = FIELD_GET(QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, val); + if (monitor_port != 0xF && monitor_port != mirror->to_local_port) + return -EEXIST; + + /* Set the monitor port */ + val = FIELD_PREP(QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, + mirror->to_local_port); + ret = regmap_update_bits(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0, + QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, val); + if (ret) + return ret; + + if (ingress) { + reg = QCA8K_PORT_LOOKUP_CTRL(port); + val = QCA8K_PORT_LOOKUP_ING_MIRROR_EN; + } else { + reg = QCA8K_REG_PORT_HOL_CTRL1(port); + val = QCA8K_PORT_HOL_CTRL1_EG_MIRROR_EN; + } + + ret = regmap_update_bits(priv->regmap, reg, val, val); + if (ret) + return ret; + + /* Track mirror port for tx and rx to decide when the + * mirror port has to be disabled. + */ + if (ingress) + priv->mirror_rx |= BIT(port); + else + priv->mirror_tx |= BIT(port); + + return 0; +} + +void qca8k_port_mirror_del(struct dsa_switch *ds, int port, + struct dsa_mall_mirror_tc_entry *mirror) +{ + struct qca8k_priv *priv = ds->priv; + u32 reg, val; + int ret; + + if (mirror->ingress) { + reg = QCA8K_PORT_LOOKUP_CTRL(port); + val = QCA8K_PORT_LOOKUP_ING_MIRROR_EN; + } else { + reg = QCA8K_REG_PORT_HOL_CTRL1(port); + val = QCA8K_PORT_HOL_CTRL1_EG_MIRROR_EN; + } + + ret = regmap_clear_bits(priv->regmap, reg, val); + if (ret) + goto err; + + if (mirror->ingress) + priv->mirror_rx &= ~BIT(port); + else + priv->mirror_tx &= ~BIT(port); + + /* No port set to send packet to mirror port. Disable mirror port */ + if (!priv->mirror_rx && !priv->mirror_tx) { + val = FIELD_PREP(QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, 0xF); + ret = regmap_update_bits(priv->regmap, QCA8K_REG_GLOBAL_FW_CTRL0, + QCA8K_GLOBAL_FW_CTRL0_MIRROR_PORT_NUM, val); + if (ret) + goto err; + } +err: + dev_err(priv->dev, "Failed to del mirror port from %d", port); +} diff --git a/drivers/net/dsa/qca/qca8k.h b/drivers/net/dsa/qca/qca8k.h index 9da7928de83ca..3a2131b7abfa2 100644 --- a/drivers/net/dsa/qca/qca8k.h +++ b/drivers/net/dsa/qca/qca8k.h @@ -494,4 +494,11 @@ int qca8k_port_mdb_del(struct dsa_switch *ds, int port, const struct switchdev_obj_port_mdb *mdb, struct dsa_db db); +/* Common port mirror function */ +int qca8k_port_mirror_add(struct dsa_switch *ds, int port, + struct dsa_mall_mirror_tc_entry *mirror, + bool ingress, struct netlink_ext_ack *extack); +void qca8k_port_mirror_del(struct dsa_switch *ds, int port, + struct dsa_mall_mirror_tc_entry *mirror); + #endif /* __QCA8K_H */ From c5290f636624b98e76a82bd63ffec0a8a9daa620 Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Wed, 27 Jul 2022 13:35:21 +0200 Subject: [PATCH 12/14] net: dsa: qca8k: move port VLAN functions to common code The same port VLAN functions are used by drivers based on qca8k family switch. Move them to common code to make them accessible also by other drivers. Also drop exposing busy_wait and make it static. Signed-off-by: Christian Marangi Reviewed-by: Vladimir Oltean Signed-off-by: Jakub Kicinski --- drivers/net/dsa/qca/qca8k-8xxx.c | 182 ----------------------------- drivers/net/dsa/qca/qca8k-common.c | 179 +++++++++++++++++++++++++++- drivers/net/dsa/qca/qca8k.h | 10 +- 3 files changed, 187 insertions(+), 184 deletions(-) diff --git a/drivers/net/dsa/qca/qca8k-8xxx.c b/drivers/net/dsa/qca/qca8k-8xxx.c index f6ea5a82da9ef..545f86e3c6ed4 100644 --- a/drivers/net/dsa/qca/qca8k-8xxx.c +++ b/drivers/net/dsa/qca/qca8k-8xxx.c @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include @@ -441,122 +440,6 @@ static struct regmap_config qca8k_regmap_config = { .cache_type = REGCACHE_NONE, /* Explicitly disable CACHE */ }; -static int -qca8k_vlan_access(struct qca8k_priv *priv, enum qca8k_vlan_cmd cmd, u16 vid) -{ - u32 reg; - int ret; - - /* Set the command and VLAN index */ - reg = QCA8K_VTU_FUNC1_BUSY; - reg |= cmd; - reg |= FIELD_PREP(QCA8K_VTU_FUNC1_VID_MASK, vid); - - /* Write the function register triggering the table access */ - ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC1, reg); - if (ret) - return ret; - - /* wait for completion */ - ret = qca8k_busy_wait(priv, QCA8K_REG_VTU_FUNC1, QCA8K_VTU_FUNC1_BUSY); - if (ret) - return ret; - - /* Check for table full violation when adding an entry */ - if (cmd == QCA8K_VLAN_LOAD) { - ret = qca8k_read(priv, QCA8K_REG_VTU_FUNC1, ®); - if (ret < 0) - return ret; - if (reg & QCA8K_VTU_FUNC1_FULL) - return -ENOMEM; - } - - return 0; -} - -static int -qca8k_vlan_add(struct qca8k_priv *priv, u8 port, u16 vid, bool untagged) -{ - u32 reg; - int ret; - - /* - We do the right thing with VLAN 0 and treat it as untagged while - preserving the tag on egress. - */ - if (vid == 0) - return 0; - - mutex_lock(&priv->reg_mutex); - ret = qca8k_vlan_access(priv, QCA8K_VLAN_READ, vid); - if (ret < 0) - goto out; - - ret = qca8k_read(priv, QCA8K_REG_VTU_FUNC0, ®); - if (ret < 0) - goto out; - reg |= QCA8K_VTU_FUNC0_VALID | QCA8K_VTU_FUNC0_IVL_EN; - reg &= ~QCA8K_VTU_FUNC0_EG_MODE_PORT_MASK(port); - if (untagged) - reg |= QCA8K_VTU_FUNC0_EG_MODE_PORT_UNTAG(port); - else - reg |= QCA8K_VTU_FUNC0_EG_MODE_PORT_TAG(port); - - ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC0, reg); - if (ret) - goto out; - ret = qca8k_vlan_access(priv, QCA8K_VLAN_LOAD, vid); - -out: - mutex_unlock(&priv->reg_mutex); - - return ret; -} - -static int -qca8k_vlan_del(struct qca8k_priv *priv, u8 port, u16 vid) -{ - u32 reg, mask; - int ret, i; - bool del; - - mutex_lock(&priv->reg_mutex); - ret = qca8k_vlan_access(priv, QCA8K_VLAN_READ, vid); - if (ret < 0) - goto out; - - ret = qca8k_read(priv, QCA8K_REG_VTU_FUNC0, ®); - if (ret < 0) - goto out; - reg &= ~QCA8K_VTU_FUNC0_EG_MODE_PORT_MASK(port); - reg |= QCA8K_VTU_FUNC0_EG_MODE_PORT_NOT(port); - - /* Check if we're the last member to be removed */ - del = true; - for (i = 0; i < QCA8K_NUM_PORTS; i++) { - mask = QCA8K_VTU_FUNC0_EG_MODE_PORT_NOT(i); - - if ((reg & mask) != mask) { - del = false; - break; - } - } - - if (del) { - ret = qca8k_vlan_access(priv, QCA8K_VLAN_PURGE, vid); - } else { - ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC0, reg); - if (ret) - goto out; - ret = qca8k_vlan_access(priv, QCA8K_VLAN_LOAD, vid); - } - -out: - mutex_unlock(&priv->reg_mutex); - - return ret; -} - static int qca8k_phy_eth_busy_wait(struct qca8k_mgmt_eth_data *mgmt_eth_data, struct sk_buff *read_skb, u32 *val) @@ -1687,71 +1570,6 @@ qca8k_get_ethtool_stats_eth(struct dsa_switch *ds, int port, u64 *data) return ret; } -static int -qca8k_port_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering, - struct netlink_ext_ack *extack) -{ - struct qca8k_priv *priv = ds->priv; - int ret; - - if (vlan_filtering) { - ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port), - QCA8K_PORT_LOOKUP_VLAN_MODE_MASK, - QCA8K_PORT_LOOKUP_VLAN_MODE_SECURE); - } else { - ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port), - QCA8K_PORT_LOOKUP_VLAN_MODE_MASK, - QCA8K_PORT_LOOKUP_VLAN_MODE_NONE); - } - - return ret; -} - -static int -qca8k_port_vlan_add(struct dsa_switch *ds, int port, - const struct switchdev_obj_port_vlan *vlan, - struct netlink_ext_ack *extack) -{ - bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED; - bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID; - struct qca8k_priv *priv = ds->priv; - int ret; - - ret = qca8k_vlan_add(priv, port, vlan->vid, untagged); - if (ret) { - dev_err(priv->dev, "Failed to add VLAN to port %d (%d)", port, ret); - return ret; - } - - if (pvid) { - ret = qca8k_rmw(priv, QCA8K_EGRESS_VLAN(port), - QCA8K_EGREES_VLAN_PORT_MASK(port), - QCA8K_EGREES_VLAN_PORT(port, vlan->vid)); - if (ret) - return ret; - - ret = qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(port), - QCA8K_PORT_VLAN_CVID(vlan->vid) | - QCA8K_PORT_VLAN_SVID(vlan->vid)); - } - - return ret; -} - -static int -qca8k_port_vlan_del(struct dsa_switch *ds, int port, - const struct switchdev_obj_port_vlan *vlan) -{ - struct qca8k_priv *priv = ds->priv; - int ret; - - ret = qca8k_vlan_del(priv, port, vlan->vid); - if (ret) - dev_err(priv->dev, "Failed to delete VLAN from port %d (%d)", port, ret); - - return ret; -} - static u32 qca8k_get_phy_flags(struct dsa_switch *ds, int port) { struct qca8k_priv *priv = ds->priv; diff --git a/drivers/net/dsa/qca/qca8k-common.c b/drivers/net/dsa/qca/qca8k-common.c index c0a088418c20e..0e5ce532accd6 100644 --- a/drivers/net/dsa/qca/qca8k-common.c +++ b/drivers/net/dsa/qca/qca8k-common.c @@ -140,7 +140,7 @@ static int qca8k_bulk_write(struct qca8k_priv *priv, u32 reg, u32 *val, int len) return 0; } -int qca8k_busy_wait(struct qca8k_priv *priv, u32 reg, u32 mask) +static int qca8k_busy_wait(struct qca8k_priv *priv, u32 reg, u32 mask) { u32 val; @@ -353,6 +353,120 @@ static int qca8k_fdb_search_and_del(struct qca8k_priv *priv, u8 port_mask, return ret; } +static int qca8k_vlan_access(struct qca8k_priv *priv, + enum qca8k_vlan_cmd cmd, u16 vid) +{ + u32 reg; + int ret; + + /* Set the command and VLAN index */ + reg = QCA8K_VTU_FUNC1_BUSY; + reg |= cmd; + reg |= FIELD_PREP(QCA8K_VTU_FUNC1_VID_MASK, vid); + + /* Write the function register triggering the table access */ + ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC1, reg); + if (ret) + return ret; + + /* wait for completion */ + ret = qca8k_busy_wait(priv, QCA8K_REG_VTU_FUNC1, QCA8K_VTU_FUNC1_BUSY); + if (ret) + return ret; + + /* Check for table full violation when adding an entry */ + if (cmd == QCA8K_VLAN_LOAD) { + ret = qca8k_read(priv, QCA8K_REG_VTU_FUNC1, ®); + if (ret < 0) + return ret; + if (reg & QCA8K_VTU_FUNC1_FULL) + return -ENOMEM; + } + + return 0; +} + +static int qca8k_vlan_add(struct qca8k_priv *priv, u8 port, u16 vid, + bool untagged) +{ + u32 reg; + int ret; + + /* We do the right thing with VLAN 0 and treat it as untagged while + * preserving the tag on egress. + */ + if (vid == 0) + return 0; + + mutex_lock(&priv->reg_mutex); + ret = qca8k_vlan_access(priv, QCA8K_VLAN_READ, vid); + if (ret < 0) + goto out; + + ret = qca8k_read(priv, QCA8K_REG_VTU_FUNC0, ®); + if (ret < 0) + goto out; + reg |= QCA8K_VTU_FUNC0_VALID | QCA8K_VTU_FUNC0_IVL_EN; + reg &= ~QCA8K_VTU_FUNC0_EG_MODE_PORT_MASK(port); + if (untagged) + reg |= QCA8K_VTU_FUNC0_EG_MODE_PORT_UNTAG(port); + else + reg |= QCA8K_VTU_FUNC0_EG_MODE_PORT_TAG(port); + + ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC0, reg); + if (ret) + goto out; + ret = qca8k_vlan_access(priv, QCA8K_VLAN_LOAD, vid); + +out: + mutex_unlock(&priv->reg_mutex); + + return ret; +} + +static int qca8k_vlan_del(struct qca8k_priv *priv, u8 port, u16 vid) +{ + u32 reg, mask; + int ret, i; + bool del; + + mutex_lock(&priv->reg_mutex); + ret = qca8k_vlan_access(priv, QCA8K_VLAN_READ, vid); + if (ret < 0) + goto out; + + ret = qca8k_read(priv, QCA8K_REG_VTU_FUNC0, ®); + if (ret < 0) + goto out; + reg &= ~QCA8K_VTU_FUNC0_EG_MODE_PORT_MASK(port); + reg |= QCA8K_VTU_FUNC0_EG_MODE_PORT_NOT(port); + + /* Check if we're the last member to be removed */ + del = true; + for (i = 0; i < QCA8K_NUM_PORTS; i++) { + mask = QCA8K_VTU_FUNC0_EG_MODE_PORT_NOT(i); + + if ((reg & mask) != mask) { + del = false; + break; + } + } + + if (del) { + ret = qca8k_vlan_access(priv, QCA8K_VLAN_PURGE, vid); + } else { + ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC0, reg); + if (ret) + goto out; + ret = qca8k_vlan_access(priv, QCA8K_VLAN_LOAD, vid); + } + +out: + mutex_unlock(&priv->reg_mutex); + + return ret; +} + int qca8k_mib_init(struct qca8k_priv *priv) { int ret; @@ -837,3 +951,66 @@ void qca8k_port_mirror_del(struct dsa_switch *ds, int port, err: dev_err(priv->dev, "Failed to del mirror port from %d", port); } + +int qca8k_port_vlan_filtering(struct dsa_switch *ds, int port, + bool vlan_filtering, + struct netlink_ext_ack *extack) +{ + struct qca8k_priv *priv = ds->priv; + int ret; + + if (vlan_filtering) { + ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port), + QCA8K_PORT_LOOKUP_VLAN_MODE_MASK, + QCA8K_PORT_LOOKUP_VLAN_MODE_SECURE); + } else { + ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port), + QCA8K_PORT_LOOKUP_VLAN_MODE_MASK, + QCA8K_PORT_LOOKUP_VLAN_MODE_NONE); + } + + return ret; +} + +int qca8k_port_vlan_add(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan, + struct netlink_ext_ack *extack) +{ + bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED; + bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID; + struct qca8k_priv *priv = ds->priv; + int ret; + + ret = qca8k_vlan_add(priv, port, vlan->vid, untagged); + if (ret) { + dev_err(priv->dev, "Failed to add VLAN to port %d (%d)", port, ret); + return ret; + } + + if (pvid) { + ret = qca8k_rmw(priv, QCA8K_EGRESS_VLAN(port), + QCA8K_EGREES_VLAN_PORT_MASK(port), + QCA8K_EGREES_VLAN_PORT(port, vlan->vid)); + if (ret) + return ret; + + ret = qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(port), + QCA8K_PORT_VLAN_CVID(vlan->vid) | + QCA8K_PORT_VLAN_SVID(vlan->vid)); + } + + return ret; +} + +int qca8k_port_vlan_del(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan) +{ + struct qca8k_priv *priv = ds->priv; + int ret; + + ret = qca8k_vlan_del(priv, port, vlan->vid); + if (ret) + dev_err(priv->dev, "Failed to delete VLAN from port %d (%d)", port, ret); + + return ret; +} diff --git a/drivers/net/dsa/qca/qca8k.h b/drivers/net/dsa/qca/qca8k.h index 3a2131b7abfa2..91f7abc5beb17 100644 --- a/drivers/net/dsa/qca/qca8k.h +++ b/drivers/net/dsa/qca/qca8k.h @@ -439,7 +439,6 @@ int qca8k_write(struct qca8k_priv *priv, u32 reg, u32 val); int qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val); /* Common ops function */ -int qca8k_busy_wait(struct qca8k_priv *priv, u32 reg, u32 mask); void qca8k_fdb_flush(struct qca8k_priv *priv); /* Common ethtool stats function */ @@ -501,4 +500,13 @@ int qca8k_port_mirror_add(struct dsa_switch *ds, int port, void qca8k_port_mirror_del(struct dsa_switch *ds, int port, struct dsa_mall_mirror_tc_entry *mirror); +/* Common port VLAN function */ +int qca8k_port_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering, + struct netlink_ext_ack *extack); +int qca8k_port_vlan_add(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan, + struct netlink_ext_ack *extack); +int qca8k_port_vlan_del(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan); + #endif /* __QCA8K_H */ From e9bbf019af44b204b71ef8edf224002550aab641 Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Wed, 27 Jul 2022 13:35:22 +0200 Subject: [PATCH 13/14] net: dsa: qca8k: move port LAG functions to common code The same port LAG functions are used by drivers based on qca8k family switch. Move them to common code to make them accessible also by other drivers. Signed-off-by: Christian Marangi Reviewed-by: Vladimir Oltean Signed-off-by: Jakub Kicinski --- drivers/net/dsa/qca/qca8k-8xxx.c | 168 ----------------------------- drivers/net/dsa/qca/qca8k-common.c | 165 ++++++++++++++++++++++++++++ drivers/net/dsa/qca/qca8k.h | 6 ++ 3 files changed, 171 insertions(+), 168 deletions(-) diff --git a/drivers/net/dsa/qca/qca8k-8xxx.c b/drivers/net/dsa/qca/qca8k-8xxx.c index 545f86e3c6ed4..4d6ea47a44698 100644 --- a/drivers/net/dsa/qca/qca8k-8xxx.c +++ b/drivers/net/dsa/qca/qca8k-8xxx.c @@ -1593,174 +1593,6 @@ qca8k_get_tag_protocol(struct dsa_switch *ds, int port, return DSA_TAG_PROTO_QCA; } -static bool -qca8k_lag_can_offload(struct dsa_switch *ds, struct dsa_lag lag, - struct netdev_lag_upper_info *info) -{ - struct dsa_port *dp; - int members = 0; - - if (!lag.id) - return false; - - dsa_lag_foreach_port(dp, ds->dst, &lag) - /* Includes the port joining the LAG */ - members++; - - if (members > QCA8K_NUM_PORTS_FOR_LAG) - return false; - - if (info->tx_type != NETDEV_LAG_TX_TYPE_HASH) - return false; - - if (info->hash_type != NETDEV_LAG_HASH_L2 && - info->hash_type != NETDEV_LAG_HASH_L23) - return false; - - return true; -} - -static int -qca8k_lag_setup_hash(struct dsa_switch *ds, struct dsa_lag lag, - struct netdev_lag_upper_info *info) -{ - struct net_device *lag_dev = lag.dev; - struct qca8k_priv *priv = ds->priv; - bool unique_lag = true; - unsigned int i; - u32 hash = 0; - - switch (info->hash_type) { - case NETDEV_LAG_HASH_L23: - hash |= QCA8K_TRUNK_HASH_SIP_EN; - hash |= QCA8K_TRUNK_HASH_DIP_EN; - fallthrough; - case NETDEV_LAG_HASH_L2: - hash |= QCA8K_TRUNK_HASH_SA_EN; - hash |= QCA8K_TRUNK_HASH_DA_EN; - break; - default: /* We should NEVER reach this */ - return -EOPNOTSUPP; - } - - /* Check if we are the unique configured LAG */ - dsa_lags_foreach_id(i, ds->dst) - if (i != lag.id && dsa_lag_by_id(ds->dst, i)) { - unique_lag = false; - break; - } - - /* Hash Mode is global. Make sure the same Hash Mode - * is set to all the 4 possible lag. - * If we are the unique LAG we can set whatever hash - * mode we want. - * To change hash mode it's needed to remove all LAG - * and change the mode with the latest. - */ - if (unique_lag) { - priv->lag_hash_mode = hash; - } else if (priv->lag_hash_mode != hash) { - netdev_err(lag_dev, "Error: Mismatched Hash Mode across different lag is not supported\n"); - return -EOPNOTSUPP; - } - - return regmap_update_bits(priv->regmap, QCA8K_TRUNK_HASH_EN_CTRL, - QCA8K_TRUNK_HASH_MASK, hash); -} - -static int -qca8k_lag_refresh_portmap(struct dsa_switch *ds, int port, - struct dsa_lag lag, bool delete) -{ - struct qca8k_priv *priv = ds->priv; - int ret, id, i; - u32 val; - - /* DSA LAG IDs are one-based, hardware is zero-based */ - id = lag.id - 1; - - /* Read current port member */ - ret = regmap_read(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL0, &val); - if (ret) - return ret; - - /* Shift val to the correct trunk */ - val >>= QCA8K_REG_GOL_TRUNK_SHIFT(id); - val &= QCA8K_REG_GOL_TRUNK_MEMBER_MASK; - if (delete) - val &= ~BIT(port); - else - val |= BIT(port); - - /* Update port member. With empty portmap disable trunk */ - ret = regmap_update_bits(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL0, - QCA8K_REG_GOL_TRUNK_MEMBER(id) | - QCA8K_REG_GOL_TRUNK_EN(id), - !val << QCA8K_REG_GOL_TRUNK_SHIFT(id) | - val << QCA8K_REG_GOL_TRUNK_SHIFT(id)); - - /* Search empty member if adding or port on deleting */ - for (i = 0; i < QCA8K_NUM_PORTS_FOR_LAG; i++) { - ret = regmap_read(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL(id), &val); - if (ret) - return ret; - - val >>= QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(id, i); - val &= QCA8K_REG_GOL_TRUNK_ID_MEM_ID_MASK; - - if (delete) { - /* If port flagged to be disabled assume this member is - * empty - */ - if (val != QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN_MASK) - continue; - - val &= QCA8K_REG_GOL_TRUNK_ID_MEM_ID_PORT_MASK; - if (val != port) - continue; - } else { - /* If port flagged to be enabled assume this member is - * already set - */ - if (val == QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN_MASK) - continue; - } - - /* We have found the member to add/remove */ - break; - } - - /* Set port in the correct port mask or disable port if in delete mode */ - return regmap_update_bits(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL(id), - QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN(id, i) | - QCA8K_REG_GOL_TRUNK_ID_MEM_ID_PORT(id, i), - !delete << QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(id, i) | - port << QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(id, i)); -} - -static int -qca8k_port_lag_join(struct dsa_switch *ds, int port, struct dsa_lag lag, - struct netdev_lag_upper_info *info) -{ - int ret; - - if (!qca8k_lag_can_offload(ds, lag, info)) - return -EOPNOTSUPP; - - ret = qca8k_lag_setup_hash(ds, lag, info); - if (ret) - return ret; - - return qca8k_lag_refresh_portmap(ds, port, lag, false); -} - -static int -qca8k_port_lag_leave(struct dsa_switch *ds, int port, - struct dsa_lag lag) -{ - return qca8k_lag_refresh_portmap(ds, port, lag, true); -} - static void qca8k_master_change(struct dsa_switch *ds, const struct net_device *master, bool operational) diff --git a/drivers/net/dsa/qca/qca8k-common.c b/drivers/net/dsa/qca/qca8k-common.c index 0e5ce532accd6..c881a95441dd3 100644 --- a/drivers/net/dsa/qca/qca8k-common.c +++ b/drivers/net/dsa/qca/qca8k-common.c @@ -1014,3 +1014,168 @@ int qca8k_port_vlan_del(struct dsa_switch *ds, int port, return ret; } + +static bool qca8k_lag_can_offload(struct dsa_switch *ds, + struct dsa_lag lag, + struct netdev_lag_upper_info *info) +{ + struct dsa_port *dp; + int members = 0; + + if (!lag.id) + return false; + + dsa_lag_foreach_port(dp, ds->dst, &lag) + /* Includes the port joining the LAG */ + members++; + + if (members > QCA8K_NUM_PORTS_FOR_LAG) + return false; + + if (info->tx_type != NETDEV_LAG_TX_TYPE_HASH) + return false; + + if (info->hash_type != NETDEV_LAG_HASH_L2 && + info->hash_type != NETDEV_LAG_HASH_L23) + return false; + + return true; +} + +static int qca8k_lag_setup_hash(struct dsa_switch *ds, + struct dsa_lag lag, + struct netdev_lag_upper_info *info) +{ + struct net_device *lag_dev = lag.dev; + struct qca8k_priv *priv = ds->priv; + bool unique_lag = true; + unsigned int i; + u32 hash = 0; + + switch (info->hash_type) { + case NETDEV_LAG_HASH_L23: + hash |= QCA8K_TRUNK_HASH_SIP_EN; + hash |= QCA8K_TRUNK_HASH_DIP_EN; + fallthrough; + case NETDEV_LAG_HASH_L2: + hash |= QCA8K_TRUNK_HASH_SA_EN; + hash |= QCA8K_TRUNK_HASH_DA_EN; + break; + default: /* We should NEVER reach this */ + return -EOPNOTSUPP; + } + + /* Check if we are the unique configured LAG */ + dsa_lags_foreach_id(i, ds->dst) + if (i != lag.id && dsa_lag_by_id(ds->dst, i)) { + unique_lag = false; + break; + } + + /* Hash Mode is global. Make sure the same Hash Mode + * is set to all the 4 possible lag. + * If we are the unique LAG we can set whatever hash + * mode we want. + * To change hash mode it's needed to remove all LAG + * and change the mode with the latest. + */ + if (unique_lag) { + priv->lag_hash_mode = hash; + } else if (priv->lag_hash_mode != hash) { + netdev_err(lag_dev, "Error: Mismatched Hash Mode across different lag is not supported\n"); + return -EOPNOTSUPP; + } + + return regmap_update_bits(priv->regmap, QCA8K_TRUNK_HASH_EN_CTRL, + QCA8K_TRUNK_HASH_MASK, hash); +} + +static int qca8k_lag_refresh_portmap(struct dsa_switch *ds, int port, + struct dsa_lag lag, bool delete) +{ + struct qca8k_priv *priv = ds->priv; + int ret, id, i; + u32 val; + + /* DSA LAG IDs are one-based, hardware is zero-based */ + id = lag.id - 1; + + /* Read current port member */ + ret = regmap_read(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL0, &val); + if (ret) + return ret; + + /* Shift val to the correct trunk */ + val >>= QCA8K_REG_GOL_TRUNK_SHIFT(id); + val &= QCA8K_REG_GOL_TRUNK_MEMBER_MASK; + if (delete) + val &= ~BIT(port); + else + val |= BIT(port); + + /* Update port member. With empty portmap disable trunk */ + ret = regmap_update_bits(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL0, + QCA8K_REG_GOL_TRUNK_MEMBER(id) | + QCA8K_REG_GOL_TRUNK_EN(id), + !val << QCA8K_REG_GOL_TRUNK_SHIFT(id) | + val << QCA8K_REG_GOL_TRUNK_SHIFT(id)); + + /* Search empty member if adding or port on deleting */ + for (i = 0; i < QCA8K_NUM_PORTS_FOR_LAG; i++) { + ret = regmap_read(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL(id), &val); + if (ret) + return ret; + + val >>= QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(id, i); + val &= QCA8K_REG_GOL_TRUNK_ID_MEM_ID_MASK; + + if (delete) { + /* If port flagged to be disabled assume this member is + * empty + */ + if (val != QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN_MASK) + continue; + + val &= QCA8K_REG_GOL_TRUNK_ID_MEM_ID_PORT_MASK; + if (val != port) + continue; + } else { + /* If port flagged to be enabled assume this member is + * already set + */ + if (val == QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN_MASK) + continue; + } + + /* We have found the member to add/remove */ + break; + } + + /* Set port in the correct port mask or disable port if in delete mode */ + return regmap_update_bits(priv->regmap, QCA8K_REG_GOL_TRUNK_CTRL(id), + QCA8K_REG_GOL_TRUNK_ID_MEM_ID_EN(id, i) | + QCA8K_REG_GOL_TRUNK_ID_MEM_ID_PORT(id, i), + !delete << QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(id, i) | + port << QCA8K_REG_GOL_TRUNK_ID_MEM_ID_SHIFT(id, i)); +} + +int qca8k_port_lag_join(struct dsa_switch *ds, int port, struct dsa_lag lag, + struct netdev_lag_upper_info *info) +{ + int ret; + + if (!qca8k_lag_can_offload(ds, lag, info)) + return -EOPNOTSUPP; + + ret = qca8k_lag_setup_hash(ds, lag, info); + if (ret) + return ret; + + return qca8k_lag_refresh_portmap(ds, port, lag, false); +} + +int qca8k_port_lag_leave(struct dsa_switch *ds, int port, + struct dsa_lag lag) +{ + return qca8k_lag_refresh_portmap(ds, port, lag, true); +} diff --git a/drivers/net/dsa/qca/qca8k.h b/drivers/net/dsa/qca/qca8k.h index 91f7abc5beb17..e87bfee837c19 100644 --- a/drivers/net/dsa/qca/qca8k.h +++ b/drivers/net/dsa/qca/qca8k.h @@ -509,4 +509,10 @@ int qca8k_port_vlan_add(struct dsa_switch *ds, int port, int qca8k_port_vlan_del(struct dsa_switch *ds, int port, const struct switchdev_obj_port_vlan *vlan); +/* Common port LAG function */ +int qca8k_port_lag_join(struct dsa_switch *ds, int port, struct dsa_lag lag, + struct netdev_lag_upper_info *info); +int qca8k_port_lag_leave(struct dsa_switch *ds, int port, + struct dsa_lag lag); + #endif /* __QCA8K_H */ From 9d1bcb1f293f1391302a109c9819c3705c804700 Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Wed, 27 Jul 2022 13:35:23 +0200 Subject: [PATCH 14/14] net: dsa: qca8k: move read_switch_id function to common code The same function to read the switch id is used by drivers based on qca8k family switch. Move them to common code to make them accessible also by other drivers. Signed-off-by: Christian Marangi Reviewed-by: Vladimir Oltean Signed-off-by: Jakub Kicinski --- drivers/net/dsa/qca/qca8k-8xxx.c | 29 ----------------------------- drivers/net/dsa/qca/qca8k-common.c | 29 +++++++++++++++++++++++++++++ drivers/net/dsa/qca/qca8k.h | 1 + 3 files changed, 30 insertions(+), 29 deletions(-) diff --git a/drivers/net/dsa/qca/qca8k-8xxx.c b/drivers/net/dsa/qca/qca8k-8xxx.c index 4d6ea47a44698..1d3e7782a71f2 100644 --- a/drivers/net/dsa/qca/qca8k-8xxx.c +++ b/drivers/net/dsa/qca/qca8k-8xxx.c @@ -1876,35 +1876,6 @@ static const struct dsa_switch_ops qca8k_switch_ops = { .connect_tag_protocol = qca8k_connect_tag_protocol, }; -static int qca8k_read_switch_id(struct qca8k_priv *priv) -{ - u32 val; - u8 id; - int ret; - - if (!priv->info) - return -ENODEV; - - ret = qca8k_read(priv, QCA8K_REG_MASK_CTRL, &val); - if (ret < 0) - return -ENODEV; - - id = QCA8K_MASK_CTRL_DEVICE_ID(val); - if (id != priv->info->id) { - dev_err(priv->dev, - "Switch id detected %x but expected %x", - id, priv->info->id); - return -ENODEV; - } - - priv->switch_id = id; - - /* Save revision to communicate to the internal PHY driver */ - priv->switch_revision = QCA8K_MASK_CTRL_REV_ID(val); - - return 0; -} - static int qca8k_sw_probe(struct mdio_device *mdiodev) { diff --git a/drivers/net/dsa/qca/qca8k-common.c b/drivers/net/dsa/qca/qca8k-common.c index c881a95441dd3..bba95613e2183 100644 --- a/drivers/net/dsa/qca/qca8k-common.c +++ b/drivers/net/dsa/qca/qca8k-common.c @@ -1179,3 +1179,32 @@ int qca8k_port_lag_leave(struct dsa_switch *ds, int port, { return qca8k_lag_refresh_portmap(ds, port, lag, true); } + +int qca8k_read_switch_id(struct qca8k_priv *priv) +{ + u32 val; + u8 id; + int ret; + + if (!priv->info) + return -ENODEV; + + ret = qca8k_read(priv, QCA8K_REG_MASK_CTRL, &val); + if (ret < 0) + return -ENODEV; + + id = QCA8K_MASK_CTRL_DEVICE_ID(val); + if (id != priv->info->id) { + dev_err(priv->dev, + "Switch id detected %x but expected %x", + id, priv->info->id); + return -ENODEV; + } + + priv->switch_id = id; + + /* Save revision to communicate to the internal PHY driver */ + priv->switch_revision = QCA8K_MASK_CTRL_REV_ID(val); + + return 0; +} diff --git a/drivers/net/dsa/qca/qca8k.h b/drivers/net/dsa/qca/qca8k.h index e87bfee837c19..e36ecc9777f43 100644 --- a/drivers/net/dsa/qca/qca8k.h +++ b/drivers/net/dsa/qca/qca8k.h @@ -432,6 +432,7 @@ extern const struct qca8k_mib_desc ar8327_mib[]; extern const struct regmap_access_table qca8k_readable_table; int qca8k_mib_init(struct qca8k_priv *priv); void qca8k_port_set_status(struct qca8k_priv *priv, int port, int enable); +int qca8k_read_switch_id(struct qca8k_priv *priv); /* Common read/write/rmw function */ int qca8k_read(struct qca8k_priv *priv, u32 reg, u32 *val);