summaryrefslogtreecommitdiffstats
path: root/drivers/net/dsa
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/dsa')
-rw-r--r--drivers/net/dsa/bcm_sf2.c236
-rw-r--r--drivers/net/dsa/bcm_sf2.h56
-rw-r--r--drivers/net/dsa/bcm_sf2_regs.h43
3 files changed, 335 insertions, 0 deletions
diff --git a/drivers/net/dsa/bcm_sf2.c b/drivers/net/dsa/bcm_sf2.c
index 9d56515f4c4d..4f32b8a530bf 100644
--- a/drivers/net/dsa/bcm_sf2.c
+++ b/drivers/net/dsa/bcm_sf2.c
@@ -25,6 +25,8 @@
#include <linux/ethtool.h>
#include <linux/if_bridge.h>
#include <linux/brcmphy.h>
+#include <linux/etherdevice.h>
+#include <net/switchdev.h>
#include "bcm_sf2.h"
#include "bcm_sf2_regs.h"
@@ -555,6 +557,236 @@ static int bcm_sf2_sw_br_set_stp_state(struct dsa_switch *ds, int port,
return 0;
}
+/* Address Resolution Logic routines */
+static int bcm_sf2_arl_op_wait(struct bcm_sf2_priv *priv)
+{
+ unsigned int timeout = 10;
+ u32 reg;
+
+ do {
+ reg = core_readl(priv, CORE_ARLA_RWCTL);
+ if (!(reg & ARL_STRTDN))
+ return 0;
+
+ usleep_range(1000, 2000);
+ } while (timeout--);
+
+ return -ETIMEDOUT;
+}
+
+static int bcm_sf2_arl_rw_op(struct bcm_sf2_priv *priv, unsigned int op)
+{
+ u32 cmd;
+
+ if (op > ARL_RW)
+ return -EINVAL;
+
+ cmd = core_readl(priv, CORE_ARLA_RWCTL);
+ cmd &= ~IVL_SVL_SELECT;
+ cmd |= ARL_STRTDN;
+ if (op)
+ cmd |= ARL_RW;
+ else
+ cmd &= ~ARL_RW;
+ core_writel(priv, cmd, CORE_ARLA_RWCTL);
+
+ return bcm_sf2_arl_op_wait(priv);
+}
+
+static int bcm_sf2_arl_read(struct bcm_sf2_priv *priv, u64 mac,
+ u16 vid, struct bcm_sf2_arl_entry *ent, u8 *idx,
+ bool is_valid)
+{
+ unsigned int i;
+ int ret;
+
+ ret = bcm_sf2_arl_op_wait(priv);
+ if (ret)
+ return ret;
+
+ /* Read the 4 bins */
+ for (i = 0; i < 4; i++) {
+ u64 mac_vid;
+ u32 fwd_entry;
+
+ mac_vid = core_readq(priv, CORE_ARLA_MACVID_ENTRY(i));
+ fwd_entry = core_readl(priv, CORE_ARLA_FWD_ENTRY(i));
+ bcm_sf2_arl_to_entry(ent, mac_vid, fwd_entry);
+
+ if (ent->is_valid && is_valid) {
+ *idx = i;
+ return 0;
+ }
+
+ /* This is the MAC we just deleted */
+ if (!is_valid && (mac_vid & mac))
+ return 0;
+ }
+
+ return -ENOENT;
+}
+
+static int bcm_sf2_arl_op(struct bcm_sf2_priv *priv, int op, int port,
+ const unsigned char *addr, u16 vid, bool is_valid)
+{
+ struct bcm_sf2_arl_entry ent;
+ u32 fwd_entry;
+ u64 mac, mac_vid = 0;
+ u8 idx = 0;
+ int ret;
+
+ /* Convert the array into a 64-bit MAC */
+ mac = bcm_sf2_mac_to_u64(addr);
+
+ /* Perform a read for the given MAC and VID */
+ core_writeq(priv, mac, CORE_ARLA_MAC);
+ core_writel(priv, vid, CORE_ARLA_VID);
+
+ /* Issue a read operation for this MAC */
+ ret = bcm_sf2_arl_rw_op(priv, 1);
+ if (ret)
+ return ret;
+
+ ret = bcm_sf2_arl_read(priv, mac, vid, &ent, &idx, is_valid);
+ /* If this is a read, just finish now */
+ if (op)
+ return ret;
+
+ /* We could not find a matching MAC, so reset to a new entry */
+ if (ret) {
+ fwd_entry = 0;
+ idx = 0;
+ }
+
+ memset(&ent, 0, sizeof(ent));
+ ent.port = port;
+ ent.is_valid = is_valid;
+ ent.vid = vid;
+ ent.is_static = true;
+ memcpy(ent.mac, addr, ETH_ALEN);
+ bcm_sf2_arl_from_entry(&mac_vid, &fwd_entry, &ent);
+
+ core_writeq(priv, mac_vid, CORE_ARLA_MACVID_ENTRY(idx));
+ core_writel(priv, fwd_entry, CORE_ARLA_FWD_ENTRY(idx));
+
+ ret = bcm_sf2_arl_rw_op(priv, 0);
+ if (ret)
+ return ret;
+
+ /* Re-read the entry to check */
+ return bcm_sf2_arl_read(priv, mac, vid, &ent, &idx, is_valid);
+}
+
+static int bcm_sf2_sw_fdb_prepare(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_fdb *fdb,
+ struct switchdev_trans *trans)
+{
+ /* We do not need to do anything specific here yet */
+ return 0;
+}
+
+static int bcm_sf2_sw_fdb_add(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_fdb *fdb,
+ struct switchdev_trans *trans)
+{
+ struct bcm_sf2_priv *priv = ds_to_priv(ds);
+
+ return bcm_sf2_arl_op(priv, 0, port, fdb->addr, fdb->vid, true);
+}
+
+static int bcm_sf2_sw_fdb_del(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_fdb *fdb)
+{
+ struct bcm_sf2_priv *priv = ds_to_priv(ds);
+
+ return bcm_sf2_arl_op(priv, 0, port, fdb->addr, fdb->vid, false);
+}
+
+static int bcm_sf2_arl_search_wait(struct bcm_sf2_priv *priv)
+{
+ unsigned timeout = 1000;
+ u32 reg;
+
+ do {
+ reg = core_readl(priv, CORE_ARLA_SRCH_CTL);
+ if (!(reg & ARLA_SRCH_STDN))
+ return 0;
+
+ if (reg & ARLA_SRCH_VLID)
+ return 0;
+
+ usleep_range(1000, 2000);
+ } while (timeout--);
+
+ return -ETIMEDOUT;
+}
+
+static void bcm_sf2_arl_search_rd(struct bcm_sf2_priv *priv, u8 idx,
+ struct bcm_sf2_arl_entry *ent)
+{
+ u64 mac_vid;
+ u32 fwd_entry;
+
+ mac_vid = core_readq(priv, CORE_ARLA_SRCH_RSLT_MACVID(idx));
+ fwd_entry = core_readl(priv, CORE_ARLA_SRCH_RSLT(idx));
+ bcm_sf2_arl_to_entry(ent, mac_vid, fwd_entry);
+}
+
+static int bcm_sf2_sw_fdb_copy(struct net_device *dev, int port,
+ const struct bcm_sf2_arl_entry *ent,
+ struct switchdev_obj_port_fdb *fdb,
+ int (*cb)(struct switchdev_obj *obj))
+{
+ if (!ent->is_valid)
+ return 0;
+
+ if (port != ent->port)
+ return 0;
+
+ ether_addr_copy(fdb->addr, ent->mac);
+ fdb->vid = ent->vid;
+ fdb->ndm_state = ent->is_static ? NUD_NOARP : NUD_REACHABLE;
+
+ return cb(&fdb->obj);
+}
+
+static int bcm_sf2_sw_fdb_dump(struct dsa_switch *ds, int port,
+ struct switchdev_obj_port_fdb *fdb,
+ int (*cb)(struct switchdev_obj *obj))
+{
+ struct bcm_sf2_priv *priv = ds_to_priv(ds);
+ struct net_device *dev = ds->ports[port];
+ struct bcm_sf2_arl_entry results[2];
+ unsigned int count = 0;
+ int ret;
+
+ /* Start search operation */
+ core_writel(priv, ARLA_SRCH_STDN, CORE_ARLA_SRCH_CTL);
+
+ do {
+ ret = bcm_sf2_arl_search_wait(priv);
+ if (ret)
+ return ret;
+
+ /* Read both entries, then return their values back */
+ bcm_sf2_arl_search_rd(priv, 0, &results[0]);
+ ret = bcm_sf2_sw_fdb_copy(dev, port, &results[0], fdb, cb);
+ if (ret)
+ return ret;
+
+ bcm_sf2_arl_search_rd(priv, 1, &results[1]);
+ ret = bcm_sf2_sw_fdb_copy(dev, port, &results[1], fdb, cb);
+ if (ret)
+ return ret;
+
+ if (!results[0].is_valid && !results[1].is_valid)
+ break;
+
+ } while (count++ < CORE_ARLA_NUM_ENTRIES);
+
+ return 0;
+}
+
static irqreturn_t bcm_sf2_switch_0_isr(int irq, void *dev_id)
{
struct bcm_sf2_priv *priv = dev_id;
@@ -1076,6 +1308,10 @@ static struct dsa_switch_driver bcm_sf2_switch_driver = {
.port_join_bridge = bcm_sf2_sw_br_join,
.port_leave_bridge = bcm_sf2_sw_br_leave,
.port_stp_update = bcm_sf2_sw_br_set_stp_state,
+ .port_fdb_prepare = bcm_sf2_sw_fdb_prepare,
+ .port_fdb_add = bcm_sf2_sw_fdb_add,
+ .port_fdb_del = bcm_sf2_sw_fdb_del,
+ .port_fdb_dump = bcm_sf2_sw_fdb_dump,
};
static int __init bcm_sf2_init(void)
diff --git a/drivers/net/dsa/bcm_sf2.h b/drivers/net/dsa/bcm_sf2.h
index 789d7b7737da..cc98abc0aaf3 100644
--- a/drivers/net/dsa/bcm_sf2.h
+++ b/drivers/net/dsa/bcm_sf2.h
@@ -19,6 +19,8 @@
#include <linux/mutex.h>
#include <linux/mii.h>
#include <linux/ethtool.h>
+#include <linux/types.h>
+#include <linux/bitops.h>
#include <net/dsa.h>
@@ -50,6 +52,60 @@ struct bcm_sf2_port_status {
u32 vlan_ctl_mask;
};
+struct bcm_sf2_arl_entry {
+ u8 port;
+ u8 mac[ETH_ALEN];
+ u16 vid;
+ u8 is_valid:1;
+ u8 is_age:1;
+ u8 is_static:1;
+};
+
+static inline void bcm_sf2_mac_from_u64(u64 src, u8 *dst)
+{
+ unsigned int i;
+
+ for (i = 0; i < ETH_ALEN; i++)
+ dst[ETH_ALEN - 1 - i] = (src >> (8 * i)) & 0xff;
+}
+
+static inline u64 bcm_sf2_mac_to_u64(const u8 *src)
+{
+ unsigned int i;
+ u64 dst = 0;
+
+ for (i = 0; i < ETH_ALEN; i++)
+ dst |= (u64)src[ETH_ALEN - 1 - i] << (8 * i);
+
+ return dst;
+}
+
+static inline void bcm_sf2_arl_to_entry(struct bcm_sf2_arl_entry *ent,
+ u64 mac_vid, u32 fwd_entry)
+{
+ memset(ent, 0, sizeof(*ent));
+ ent->port = fwd_entry & PORTID_MASK;
+ ent->is_valid = !!(fwd_entry & ARL_VALID);
+ ent->is_age = !!(fwd_entry & ARL_AGE);
+ ent->is_static = !!(fwd_entry & ARL_STATIC);
+ bcm_sf2_mac_from_u64(mac_vid, ent->mac);
+ ent->vid = mac_vid >> VID_SHIFT;
+}
+
+static inline void bcm_sf2_arl_from_entry(u64 *mac_vid, u32 *fwd_entry,
+ const struct bcm_sf2_arl_entry *ent)
+{
+ *mac_vid = bcm_sf2_mac_to_u64(ent->mac);
+ *mac_vid |= (u64)(ent->vid & VID_MASK) << VID_SHIFT;
+ *fwd_entry = ent->port & PORTID_MASK;
+ if (ent->is_valid)
+ *fwd_entry |= ARL_VALID;
+ if (ent->is_static)
+ *fwd_entry |= ARL_STATIC;
+ if (ent->is_age)
+ *fwd_entry |= ARL_AGE;
+}
+
struct bcm_sf2_priv {
/* Base registers, keep those in order with BCM_SF2_REGS_NAME */
void __iomem *core;
diff --git a/drivers/net/dsa/bcm_sf2_regs.h b/drivers/net/dsa/bcm_sf2_regs.h
index fa4e6e78c9ea..97780d43b5c0 100644
--- a/drivers/net/dsa/bcm_sf2_regs.h
+++ b/drivers/net/dsa/bcm_sf2_regs.h
@@ -231,6 +231,49 @@
#define CORE_BRCM_HDR_RX_DIS 0x0980
#define CORE_BRCM_HDR_TX_DIS 0x0988
+#define CORE_ARLA_NUM_ENTRIES 1024
+
+#define CORE_ARLA_RWCTL 0x1400
+#define ARL_RW (1 << 0)
+#define IVL_SVL_SELECT (1 << 6)
+#define ARL_STRTDN (1 << 7)
+
+#define CORE_ARLA_MAC 0x1408
+#define CORE_ARLA_VID 0x1420
+#define ARLA_VIDTAB_INDX_MASK 0x1fff
+
+#define CORE_ARLA_MACVID0 0x1440
+#define MAC_MASK 0xffffffffff
+#define VID_SHIFT 48
+#define VID_MASK 0xfff
+
+#define CORE_ARLA_FWD_ENTRY0 0x1460
+#define PORTID_MASK 0x1ff
+#define ARL_CON_SHIFT 9
+#define ARL_CON_MASK 0x3
+#define ARL_PRI_SHIFT 11
+#define ARL_PRI_MASK 0x7
+#define ARL_AGE (1 << 14)
+#define ARL_STATIC (1 << 15)
+#define ARL_VALID (1 << 16)
+
+#define CORE_ARLA_MACVID_ENTRY(x) (CORE_ARLA_MACVID0 + ((x) * 0x40))
+#define CORE_ARLA_FWD_ENTRY(x) (CORE_ARLA_FWD_ENTRY0 + ((x) * 0x40))
+
+#define CORE_ARLA_SRCH_CTL 0x1540
+#define ARLA_SRCH_VLID (1 << 0)
+#define IVL_SVL_SELECT (1 << 6)
+#define ARLA_SRCH_STDN (1 << 7)
+
+#define CORE_ARLA_SRCH_ADR 0x1544
+#define ARLA_SRCH_ADR_VALID (1 << 15)
+
+#define CORE_ARLA_SRCH_RSLT_0_MACVID 0x1580
+#define CORE_ARLA_SRCH_RSLT_0 0x15a0
+
+#define CORE_ARLA_SRCH_RSLT_MACVID(x) (CORE_ARLA_SRCH_RSLT_0_MACVID + ((x) * 0x40))
+#define CORE_ARLA_SRCH_RSLT(x) (CORE_ARLA_SRCH_RSLT_0 + ((x) * 0x40))
+
#define CORE_MEM_PSM_VDD_CTRL 0x2380
#define P_TXQ_PSM_VDD_SHIFT 2
#define P_TXQ_PSM_VDD_MASK 0x3