Signed-off-by: Ilias Apalodimas <ilias.apalodi...@linaro.org>
---
 drivers/net/ethernet/ti/cpsw.c      | 503 +++++++++++++++++++++++++++++++-----
 drivers/net/ethernet/ti/cpsw_priv.h |   9 +-
 2 files changed, 450 insertions(+), 62 deletions(-)

diff --git a/drivers/net/ethernet/ti/cpsw.c b/drivers/net/ethernet/ti/cpsw.c
index b16e7cf..8f8ebd8 100644
--- a/drivers/net/ethernet/ti/cpsw.c
+++ b/drivers/net/ethernet/ti/cpsw.c
@@ -18,12 +18,10 @@
 #include <linux/clk.h>
 #include <linux/timer.h>
 #include <linux/module.h>
-#include <linux/platform_device.h>
 #include <linux/irqreturn.h>
 #include <linux/interrupt.h>
 #include <linux/if_ether.h>
 #include <linux/etherdevice.h>
-#include <linux/netdevice.h>
 #include <linux/net_tstamp.h>
 #include <linux/phy.h>
 #include <linux/workqueue.h>
@@ -42,6 +40,7 @@
 #include "cpsw.h"
 #include "cpsw_ale.h"
 #include "cpsw_priv.h"
+#include "cpsw_switchdev.h"
 #include "cpts.h"
 #include "davinci_cpdma.h"
 
@@ -146,9 +145,6 @@ do {                                                        
        \
 #define CPSW_CMINTMAX_INTVL    (1000 / CPSW_CMINTMIN_CNT)
 #define CPSW_CMINTMIN_INTVL    ((1000 / CPSW_CMINTMAX_CNT) + 1)
 
-#define cpsw_slave_index(cpsw, priv)                           \
-               ((cpsw->data.dual_emac) ? priv->emac_port :     \
-               cpsw->data.active_slave)
 #define IRQ_NUM                        2
 #define CPSW_MAX_QUEUES                8
 #define CPSW_CPDMA_DESCS_POOL_SIZE_DEFAULT 256
@@ -360,6 +356,13 @@ struct cpsw_hw_stats {
        u32     rxdmaoverruns;
 };
 
+struct cpsw_switchdev_event_work {
+       struct work_struct work;
+       struct switchdev_notifier_fdb_info fdb_info;
+       struct cpsw_priv *priv;
+       unsigned long event;
+};
+
 #define CPSW_STAT(m)           CPSW_STATS,                             \
                                sizeof(((struct cpsw_hw_stats *)0)->m), \
                                offsetof(struct cpsw_hw_stats, m)
@@ -433,18 +436,22 @@ static const struct cpsw_stats cpsw_gstrings_ch_stats[] = 
{
                struct cpsw_slave *slave;                               \
                struct cpsw_common *cpsw = (priv)->cpsw;                \
                int n;                                                  \
-               if (cpsw->data.dual_emac)                               \
-                       (func)((cpsw)->slaves + priv->emac_port, ##arg);\
-               else                                                    \
+               if (cpsw->data.switch_mode) {                           \
+                       if (priv->emac_port == HOST_PORT_NUM)           \
+                               break;                                  \
+                       (func)((cpsw)->slaves + (priv->emac_port - 1),  \
+                              ##arg);\
+               } else {                                                \
                        for (n = cpsw->data.slaves,                     \
                                        slave = cpsw->slaves;           \
                                        n; n--)                         \
                                (func)(slave++, ##arg);                 \
+               }                                                       \
        } while (0)
 
 #define cpsw_dual_emac_src_port_detect(cpsw, status, ndev, skb)                
\
        do {                                                            \
-               if (!cpsw->data.dual_emac)                              \
+               if (!cpsw->data.switch_mode)                            \
                        break;                                          \
                if (CPDMA_RX_SOURCE_PORT(status) == 1) {                \
                        ndev = cpsw->slaves[0].ndev;                    \
@@ -456,11 +463,13 @@ static const struct cpsw_stats cpsw_gstrings_ch_stats[] = 
{
        } while (0)
 #define cpsw_add_mcast(cpsw, priv, addr)                               \
        do {                                                            \
-               if (cpsw->data.dual_emac) {                             \
+               if (cpsw->data.switch_mode) {                           \
                        struct cpsw_slave *slave = cpsw->slaves +       \
-                                               priv->emac_port;        \
+                                               (priv->emac_port - 1);  \
                        int slave_port = cpsw_get_slave_port(           \
                                                slave->slave_num);      \
+                       if (priv->emac_port == HOST_PORT_NUM)           \
+                               break;                                  \
                        cpsw_ale_add_mcast(cpsw->ale, addr,             \
                                1 << slave_port | ALE_PORT_HOST,        \
                                ALE_VLAN, slave->port_vlan, 0);         \
@@ -476,13 +485,41 @@ static inline int cpsw_get_slave_port(u32 slave_num)
        return slave_num + 1;
 }
 
+static int cpsw_is_dual_mac(u8 switch_mode)
+{
+       return switch_mode == CPSW_DUAL_EMAC;
+}
+
+static int cpsw_is_switchdev(u8 switch_mode)
+{
+       return switch_mode == CPSW_SWITCHDEV;
+}
+
+static int cpsw_is_switch(u8 switch_mode)
+{
+       return switch_mode == CPSW_TI_SWITCH;
+}
+
+static int cpsw_slave_index(struct cpsw_priv *priv)
+{
+       struct cpsw_common *cpsw = priv->cpsw;
+
+#if IS_ENABLED(CONFIG_TI_CPSW_SWITCHDEV)
+       if (priv->emac_port == HOST_PORT_NUM)
+               return -1;
+#endif
+
+       return cpsw->data.switch_mode ? priv->emac_port - 1 :
+               cpsw->data.active_slave;
+}
+
 static void cpsw_set_promiscious(struct net_device *ndev, bool enable)
 {
        struct cpsw_common *cpsw = ndev_to_cpsw(ndev);
        struct cpsw_ale *ale = cpsw->ale;
        int i;
 
-       if (cpsw->data.dual_emac) {
+       if (cpsw_is_dual_mac(cpsw->data.switch_mode)) {
                bool flag = false;
 
                /* Enabling promiscuous mode for one interface will be
@@ -508,7 +545,7 @@ static void cpsw_set_promiscious(struct net_device *ndev, 
bool enable)
                        cpsw_ale_control_set(ale, 0, ALE_BYPASS, 0);
                        dev_dbg(&ndev->dev, "promiscuity disabled\n");
                }
-       } else {
+       } else if (cpsw_is_switch(cpsw->data.switch_mode)) {
                if (enable) {
                        unsigned long timeout = jiffies + HZ;
 
@@ -548,6 +585,18 @@ static void cpsw_set_promiscious(struct net_device *ndev, 
bool enable)
                        }
                        dev_dbg(&ndev->dev, "promiscuity disabled\n");
                }
+       } else if (cpsw_is_switchdev(cpsw->data.switch_mode)) {
+               /* When interfaces are placed into a bridge they'll switch to
+                * promiscuous mode. In switchdev case ALE_P0_UNI_FLOOD is
+                * changed whether the cpu port participates in the bridge
+                */
+               struct cpsw_priv *priv = netdev_priv(ndev);
+               int slave_idx = cpsw_slave_index(priv);
+               int slave_num;
+
+               slave_num = cpsw_get_slave_port(slave_idx);
+               cpsw_ale_control_set(ale, slave_num, ALE_PORT_NOLEARN, 0);
+               cpsw_ale_control_set(ale, slave_num, ALE_PORT_NO_SA_UPDATE, 0);
        }
 }
 
@@ -555,10 +604,11 @@ static void cpsw_ndo_set_rx_mode(struct net_device *ndev)
 {
        struct cpsw_priv *priv = netdev_priv(ndev);
        struct cpsw_common *cpsw = priv->cpsw;
+       int slave_no = cpsw_slave_index(priv);
        int vid;
 
-       if (cpsw->data.dual_emac)
-               vid = cpsw->slaves[priv->emac_port].port_vlan;
+       if (cpsw_is_dual_mac(cpsw->data.switch_mode))
+               vid = cpsw->slaves[slave_no].port_vlan;
        else
                vid = cpsw->data.default_vlan;
 
@@ -629,8 +679,9 @@ static void cpsw_tx_handler(void *token, int len, int 
status)
 static void cpsw_rx_vlan_encap(struct sk_buff *skb)
 {
        struct cpsw_priv *priv = netdev_priv(skb->dev);
-       struct cpsw_common *cpsw = priv->cpsw;
        u32 rx_vlan_encap_hdr = *((u32 *)skb->data);
+       struct cpsw_common *cpsw = priv->cpsw;
+       int slave_no = cpsw_slave_index(priv);
        u16 vtag, vid, prio, pkt_type;
 
        /* Remove VLAN header encapsulation word */
@@ -651,8 +702,8 @@ static void cpsw_rx_vlan_encap(struct sk_buff *skb)
        if (!vid)
                return;
        /* Ignore default vlans in dual mac mode */
-       if (cpsw->data.dual_emac &&
-           vid == cpsw->slaves[priv->emac_port].port_vlan)
+       if (cpsw_is_dual_mac(cpsw->data.switch_mode) &&
+           vid == cpsw->slaves[slave_no].port_vlan)
                return;
 
        prio = (rx_vlan_encap_hdr >>
@@ -681,9 +732,9 @@ static void cpsw_rx_handler(void *token, int len, int 
status)
        cpsw_dual_emac_src_port_detect(cpsw, status, ndev, skb);
 
        if (unlikely(status < 0) || unlikely(!netif_running(ndev))) {
-               /* In dual emac mode check for all interfaces */
-               if (cpsw->data.dual_emac && cpsw->usage_count &&
-                   (status >= 0)) {
+               /* In any other that switch mode check for all interfaces */
+               if (!cpsw_is_switch(cpsw->data.switch_mode) &&
+                   cpsw->usage_count && status >= 0) {
                        /* The packet received is for the interface which
                         * is already down and the other interface is up
                         * and running, instead of freeing which results
@@ -699,6 +750,11 @@ static void cpsw_rx_handler(void *token, int len, int 
status)
                return;
        }
 
+#if IS_ENABLED(CONFIG_TI_CPSW_SWITCHDEV)
+       if (cpsw_is_switchdev(cpsw->data.switch_mode))
+               skb->offload_fwd_mark = 1;
+#endif
+
        new_skb = netdev_alloc_skb_ip_align(ndev, cpsw->rx_packet_max);
        if (new_skb) {
                skb_copy_queue_mapping(new_skb, skb);
@@ -1206,11 +1262,10 @@ static inline int cpsw_tx_packet_submit(struct 
cpsw_priv *priv,
                                        struct sk_buff *skb,
                                        struct cpdma_chan *txch)
 {
-       struct cpsw_common *cpsw = priv->cpsw;
-
        skb_tx_timestamp(skb);
+
        return cpdma_chan_submit(txch, skb, skb->data, skb->len,
-                                priv->emac_port + cpsw->data.dual_emac);
+                                priv->emac_port);
 }
 
 static inline void cpsw_add_dual_emac_def_ale_entries(
@@ -1283,7 +1338,7 @@ static void cpsw_slave_open(struct cpsw_slave *slave, 
struct cpsw_priv *priv)
 
        slave_port = cpsw_get_slave_port(slave->slave_num);
 
-       if (cpsw->data.dual_emac)
+       if (cpsw_is_dual_mac(cpsw->data.switch_mode))
                cpsw_add_dual_emac_def_ale_entries(priv, slave, slave_port);
        else
                cpsw_ale_add_mcast(cpsw->ale, priv->ndev->broadcast,
@@ -1362,8 +1417,8 @@ static void cpsw_init_host_port(struct cpsw_priv *priv)
        control_reg = readl(&cpsw->regs->control);
        control_reg |= CPSW_VLAN_AWARE | CPSW_RX_VLAN_ENCAP;
        writel(control_reg, &cpsw->regs->control);
-       fifo_mode = (cpsw->data.dual_emac) ? CPSW_FIFO_DUAL_MAC_MODE :
-                    CPSW_FIFO_NORMAL_MODE;
+       fifo_mode = cpsw_is_dual_mac(cpsw->data.switch_mode) ?
+               CPSW_FIFO_DUAL_MAC_MODE : CPSW_FIFO_NORMAL_MODE;
        writel(fifo_mode, &cpsw->host_port_regs->tx_in_ctl);
 
        /* setup host port priority mapping */
@@ -1374,7 +1429,7 @@ static void cpsw_init_host_port(struct cpsw_priv *priv)
        cpsw_ale_control_set(cpsw->ale, HOST_PORT_NUM,
                             ALE_PORT_STATE, ALE_PORT_STATE_FORWARD);
 
-       if (!cpsw->data.dual_emac) {
+       if (!cpsw_is_dual_mac(cpsw->data.switch_mode)) {
                cpsw_ale_add_ucast(cpsw->ale, priv->mac_addr, HOST_PORT_NUM,
                                   0, 0);
                cpsw_ale_add_mcast(cpsw->ale, priv->ndev->broadcast,
@@ -1474,14 +1529,19 @@ static int cpsw_ndo_open(struct net_device *ndev)
        /* Initialize host and slave ports */
        if (!cpsw->usage_count)
                cpsw_init_host_port(priv);
-       for_each_slave(priv, cpsw_slave_open, priv);
 
-       /* Add default VLAN */
-       if (!cpsw->data.dual_emac)
-               cpsw_add_default_vlan(priv);
-       else
-               cpsw_ale_add_vlan(cpsw->ale, cpsw->data.default_vlan,
-                                 ALE_ALL_PORTS, ALE_ALL_PORTS, 0, 0);
+       if (!IS_ENABLED(CONFIG_TI_CPSW_SWITCHDEV) ||
+           (IS_ENABLED(CONFIG_TI_CPSW_SWITCHDEV) &&
+            priv->emac_port != HOST_PORT_NUM)) {
+               for_each_slave(priv, cpsw_slave_open, priv);
+
+               /* Add default VLAN */
+               if (cpsw_is_dual_mac(cpsw->data.switch_mode))
+                       cpsw_ale_add_vlan(cpsw->ale, cpsw->data.default_vlan,
+                                         ALE_ALL_PORTS, ALE_ALL_PORTS, 0, 0);
+               else
+                       cpsw_add_default_vlan(priv);
+       }
 
        /* initialize shared resources for every ndev */
        if (!cpsw->usage_count) {
@@ -1575,6 +1635,13 @@ static netdev_tx_t cpsw_ndo_start_xmit(struct sk_buff 
*skb,
        struct cpdma_chan *txch;
        int ret, q_idx;
 
+#if IS_ENABLED(CONFIG_TI_CPSW_SWITCHDEV)
+       if (priv->emac_port == HOST_PORT_NUM) {
+               dev_kfree_skb_any(skb);
+               return NETDEV_TX_OK;
+       }
+#endif
+
        if (skb_padto(skb, CPSW_MIN_PACKET_SIZE)) {
                cpsw_err(priv, tx_err, "packet pad failed\n");
                ndev->stats.tx_dropped++;
@@ -1655,8 +1722,12 @@ static void cpsw_hwtstamp_v2(struct cpsw_priv *priv)
        struct cpsw_slave *slave;
        struct cpsw_common *cpsw = priv->cpsw;
        u32 ctrl, mtype;
+       int slave_no = cpsw_slave_index(priv);
+
+       if (slave_no < 0)
+               return;
 
-       slave = &cpsw->slaves[cpsw_slave_index(cpsw, priv)];
+       slave = &cpsw->slaves[slave_no];
 
        ctrl = slave_read(slave, CPSW2_CONTROL);
        switch (cpsw->version) {
@@ -1791,11 +1862,14 @@ static int cpsw_ndo_ioctl(struct net_device *dev, 
struct ifreq *req, int cmd)
 {
        struct cpsw_priv *priv = netdev_priv(dev);
        struct cpsw_common *cpsw = priv->cpsw;
-       int slave_no = cpsw_slave_index(cpsw, priv);
+       int slave_no = cpsw_slave_index(priv);
 
        if (!netif_running(dev))
                return -EINVAL;
 
+       if (slave_no < 0)
+               return -EOPNOTSUPP;
+
        switch (cmd) {
        case SIOCSHWTSTAMP:
                return cpsw_hwtstamp_set(dev, req);
@@ -1832,6 +1906,7 @@ static int cpsw_ndo_set_mac_address(struct net_device 
*ndev, void *p)
        struct cpsw_priv *priv = netdev_priv(ndev);
        struct sockaddr *addr = (struct sockaddr *)p;
        struct cpsw_common *cpsw = priv->cpsw;
+       int slave_no = cpsw_slave_index(priv);
        int flags = 0;
        u16 vid = 0;
        int ret;
@@ -1845,8 +1920,8 @@ static int cpsw_ndo_set_mac_address(struct net_device 
*ndev, void *p)
                return ret;
        }
 
-       if (cpsw->data.dual_emac) {
-               vid = cpsw->slaves[priv->emac_port].port_vlan;
+       if (cpsw_is_dual_mac(cpsw->data.switch_mode)) {
+               vid = cpsw->slaves[slave_no].port_vlan;
                flags = ALE_VLAN;
        }
 
@@ -1884,8 +1959,11 @@ static inline int cpsw_add_vlan_ale_entry(struct 
cpsw_priv *priv,
        u32 port_mask;
        struct cpsw_common *cpsw = priv->cpsw;
 
-       if (cpsw->data.dual_emac) {
-               port_mask = (1 << (priv->emac_port + 1)) | ALE_PORT_HOST;
+       if (cpsw_is_switchdev(cpsw->data.switch_mode))
+               return -EOPNOTSUPP;
+
+       if (cpsw_is_dual_mac(cpsw->data.switch_mode)) {
+               port_mask = (1 << priv->emac_port) | ALE_PORT_HOST;
 
                if (priv->ndev->flags & IFF_ALLMULTI)
                        unreg_mcast_mask = port_mask;
@@ -1929,6 +2007,9 @@ static int cpsw_ndo_vlan_rx_add_vid(struct net_device 
*ndev,
        struct cpsw_common *cpsw = priv->cpsw;
        int ret;
 
+       if (cpsw_is_switchdev(cpsw->data.switch_mode))
+               return 0;
+
        if (vid == cpsw->data.default_vlan)
                return 0;
 
@@ -1938,7 +2019,7 @@ static int cpsw_ndo_vlan_rx_add_vid(struct net_device 
*ndev,
                return ret;
        }
 
-       if (cpsw->data.dual_emac) {
+       if (cpsw_is_dual_mac(cpsw->data.switch_mode)) {
                /* In dual EMAC, reserved VLAN id should not be used for
                 * creating VLAN interfaces as this can break the dual
                 * EMAC port separation
@@ -1965,6 +2046,9 @@ static int cpsw_ndo_vlan_rx_kill_vid(struct net_device 
*ndev,
        struct cpsw_common *cpsw = priv->cpsw;
        int ret;
 
+       if (cpsw_is_switchdev(cpsw->data.switch_mode))
+               return 0;
+
        if (vid == cpsw->data.default_vlan)
                return 0;
 
@@ -1974,7 +2058,7 @@ static int cpsw_ndo_vlan_rx_kill_vid(struct net_device 
*ndev,
                return ret;
        }
 
-       if (cpsw->data.dual_emac) {
+       if (cpsw_is_dual_mac(cpsw->data.switch_mode)) {
                int i;
 
                for (i = 0; i < cpsw->data.slaves; i++) {
@@ -1999,6 +2083,24 @@ static int cpsw_ndo_vlan_rx_kill_vid(struct net_device 
*ndev,
        return ret;
 }
 
+static int cpsw_ndo_get_phys_port_name(struct net_device *ndev, char *name,
+                                      size_t len)
+{
+       struct cpsw_priv *priv = netdev_priv(ndev);
+       struct cpsw_common *cpsw = priv->cpsw;
+       int err;
+
+       if (!cpsw_is_switchdev(cpsw->data.switch_mode))
+               return -EOPNOTSUPP;
+
+       err = snprintf(name, len, "p%d", priv->emac_port);
+
+       if (err >= len)
+               return -EINVAL;
+
+       return 0;
+}
+
 static int cpsw_ndo_set_tx_maxrate(struct net_device *ndev, int queue, u32 
rate)
 {
        struct cpsw_priv *priv = netdev_priv(ndev);
@@ -2065,6 +2167,7 @@ static const struct net_device_ops cpsw_netdev_ops = {
 #endif
        .ndo_vlan_rx_add_vid    = cpsw_ndo_vlan_rx_add_vid,
        .ndo_vlan_rx_kill_vid   = cpsw_ndo_vlan_rx_kill_vid,
+       .ndo_get_phys_port_name = cpsw_ndo_get_phys_port_name,
 };
 
 static int cpsw_get_regs_len(struct net_device *ndev)
@@ -2152,7 +2255,10 @@ static int cpsw_get_link_ksettings(struct net_device 
*ndev,
 {
        struct cpsw_priv *priv = netdev_priv(ndev);
        struct cpsw_common *cpsw = priv->cpsw;
-       int slave_no = cpsw_slave_index(cpsw, priv);
+       int slave_no = cpsw_slave_index(priv);
+
+       if (slave_no < 0)
+               return -EOPNOTSUPP;
 
        if (!cpsw->slaves[slave_no].phy)
                return -EOPNOTSUPP;
@@ -2166,7 +2272,10 @@ static int cpsw_set_link_ksettings(struct net_device 
*ndev,
 {
        struct cpsw_priv *priv = netdev_priv(ndev);
        struct cpsw_common *cpsw = priv->cpsw;
-       int slave_no = cpsw_slave_index(cpsw, priv);
+       int slave_no = cpsw_slave_index(priv);
+
+       if (slave_no < 0)
+               return -EOPNOTSUPP;
 
        if (cpsw->slaves[slave_no].phy)
                return phy_ethtool_ksettings_set(cpsw->slaves[slave_no].phy,
@@ -2179,7 +2288,10 @@ static void cpsw_get_wol(struct net_device *ndev, struct 
ethtool_wolinfo *wol)
 {
        struct cpsw_priv *priv = netdev_priv(ndev);
        struct cpsw_common *cpsw = priv->cpsw;
-       int slave_no = cpsw_slave_index(cpsw, priv);
+       int slave_no = cpsw_slave_index(priv);
+
+       if (slave_no < 0)
+               return;
 
        wol->supported = 0;
        wol->wolopts = 0;
@@ -2192,7 +2304,10 @@ static int cpsw_set_wol(struct net_device *ndev, struct 
ethtool_wolinfo *wol)
 {
        struct cpsw_priv *priv = netdev_priv(ndev);
        struct cpsw_common *cpsw = priv->cpsw;
-       int slave_no = cpsw_slave_index(cpsw, priv);
+       int slave_no = cpsw_slave_index(priv);
+
+       if (slave_no < 0)
+               return -EOPNOTSUPP;
 
        if (cpsw->slaves[slave_no].phy)
                return phy_ethtool_set_wol(cpsw->slaves[slave_no].phy, wol);
@@ -2451,7 +2566,10 @@ static int cpsw_get_eee(struct net_device *ndev, struct 
ethtool_eee *edata)
 {
        struct cpsw_priv *priv = netdev_priv(ndev);
        struct cpsw_common *cpsw = priv->cpsw;
-       int slave_no = cpsw_slave_index(cpsw, priv);
+       int slave_no = cpsw_slave_index(priv);
+
+       if (slave_no < 0)
+               return -EOPNOTSUPP;
 
        if (cpsw->slaves[slave_no].phy)
                return phy_ethtool_get_eee(cpsw->slaves[slave_no].phy, edata);
@@ -2463,7 +2581,10 @@ static int cpsw_set_eee(struct net_device *ndev, struct 
ethtool_eee *edata)
 {
        struct cpsw_priv *priv = netdev_priv(ndev);
        struct cpsw_common *cpsw = priv->cpsw;
-       int slave_no = cpsw_slave_index(cpsw, priv);
+       int slave_no = cpsw_slave_index(priv);
+
+       if (slave_no < 0)
+               return -EOPNOTSUPP;
 
        if (cpsw->slaves[slave_no].phy)
                return phy_ethtool_set_eee(cpsw->slaves[slave_no].phy, edata);
@@ -2475,7 +2596,10 @@ static int cpsw_nway_reset(struct net_device *ndev)
 {
        struct cpsw_priv *priv = netdev_priv(ndev);
        struct cpsw_common *cpsw = priv->cpsw;
-       int slave_no = cpsw_slave_index(cpsw, priv);
+       int slave_no = cpsw_slave_index(priv);
+
+       if (slave_no < 0)
+               return -EOPNOTSUPP;
 
        if (cpsw->slaves[slave_no].phy)
                return genphy_restart_aneg(cpsw->slaves[slave_no].phy);
@@ -2626,7 +2750,11 @@ static int cpsw_probe_dt(struct cpsw_platform_data *data,
        data->mac_control = prop;
 
        if (of_property_read_bool(node, "dual_emac"))
-               data->dual_emac = 1;
+               data->switch_mode = CPSW_DUAL_EMAC;
+
+       /* switchdev overrides DTS */
+       if (IS_ENABLED(CONFIG_TI_CPSW_SWITCHDEV))
+               data->switch_mode = CPSW_SWITCHDEV;
 
        /*
         * Populate all the child nodes here...
@@ -2707,7 +2835,7 @@ static int cpsw_probe_dt(struct cpsw_platform_data *data,
                        if (ret)
                                return ret;
                }
-               if (data->dual_emac) {
+               if (cpsw_is_dual_mac(data->switch_mode)) {
                        if (of_property_read_u32(slave_node, 
"dual_emac_res_vlan",
                                                 &prop)) {
                                dev_err(&pdev->dev, "Missing dual_emac_res_vlan 
in DT.\n");
@@ -2787,9 +2915,13 @@ static int cpsw_probe_dual_emac(struct cpsw_priv *priv)
        }
        memcpy(ndev->dev_addr, priv_sl2->mac_addr, ETH_ALEN);
 
-       priv_sl2->emac_port = 1;
+       priv_sl2->emac_port = 2;
        cpsw->slaves[1].ndev = ndev;
        ndev->features |= NETIF_F_HW_VLAN_CTAG_FILTER;
+       if (cpsw_is_switchdev(cpsw->data.switch_mode)) {
+               ndev->features |= NETIF_F_NETNS_LOCAL;
+               cpsw_port_switchdev_init(ndev);
+       }
 
        ndev->netdev_ops = &cpsw_netdev_ops;
        ndev->ethtool_ops = &cpsw_ethtool_ops;
@@ -2806,6 +2938,49 @@ static int cpsw_probe_dual_emac(struct cpsw_priv *priv)
        return ret;
 }
 
+static int cpsw_probe_cpu_port(struct cpsw_common *cpsw)
+{
+       struct cpsw_priv *priv_sl2;
+       struct net_device *ndev;
+       int ret = 0;
+
+       ndev = alloc_etherdev_mq(sizeof(struct cpsw_priv), CPSW_MAX_QUEUES);
+       if (!ndev) {
+               dev_err(cpsw->dev, "cpsw: error allocating net_device\n");
+               return -ENOMEM;
+       }
+
+       priv_sl2 = netdev_priv(ndev);
+       priv_sl2->cpsw = cpsw;
+       priv_sl2->ndev = ndev;
+       priv_sl2->dev  = &ndev->dev;
+       priv_sl2->msg_enable = netif_msg_init(debug_level, CPSW_DEBUG);
+
+       random_ether_addr(priv_sl2->mac_addr);
+       dev_info(cpsw->dev, "cpu port: Random MACID = %pM\n",
+                priv_sl2->mac_addr);
+
+       memcpy(ndev->dev_addr, priv_sl2->mac_addr, ETH_ALEN);
+
+       priv_sl2->emac_port = HOST_PORT_NUM;
+       ndev->features |= NETIF_F_HW_VLAN_CTAG_FILTER | NETIF_F_NETNS_LOCAL;
+
+       ndev->netdev_ops = &cpsw_netdev_ops;
+
+       /* register the network device */
+       SET_NETDEV_DEV(ndev, cpsw->dev);
+       cpsw_port_switchdev_init(ndev);
+       ret = register_netdev(ndev);
+       if (ret) {
+               dev_err(cpsw->dev, "cpsw: error registering net device\n");
+               free_netdev(ndev);
+               ret = -ENODEV;
+       }
+       cpsw->master = ndev;
+
+       return ret;
+}
+
 #define CPSW_QUIRK_IRQ         BIT(0)
 
 static const struct platform_device_id cpsw_devtype[] = {
@@ -2844,6 +3019,187 @@ static const struct of_device_id cpsw_of_mtable[] = {
 };
 MODULE_DEVICE_TABLE(of, cpsw_of_mtable);
 
+static bool cpsw_port_dev_check(const struct net_device *dev)
+{
+       return dev->netdev_ops == &cpsw_netdev_ops;
+}
+
+static void cpsw_fdb_offload_notify(struct net_device *ndev,
+                                   struct switchdev_notifier_fdb_info *rcv)
+{
+       struct switchdev_notifier_fdb_info info;
+
+       info.addr = rcv->addr;
+       info.vid = rcv->vid;
+       call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED,
+                                ndev, &info.info);
+}
+
+static void cpsw_switchdev_event_work(struct work_struct *work)
+{
+       struct cpsw_switchdev_event_work *switchdev_work =
+               container_of(work, struct cpsw_switchdev_event_work, work);
+       struct cpsw_priv *priv = switchdev_work->priv;
+       struct switchdev_notifier_fdb_info *fdb;
+       struct cpsw_common *cpsw = priv->cpsw;
+
+       rtnl_lock();
+       switch (switchdev_work->event) {
+       case SWITCHDEV_FDB_ADD_TO_DEVICE:
+               fdb = &switchdev_work->fdb_info;
+               cpsw_ale_add_ucast(cpsw->ale, (u8 *)fdb->addr, priv->emac_port,
+                                  ALE_VLAN | ALE_SECURE, fdb->vid);
+               cpsw_fdb_offload_notify(priv->ndev, fdb);
+               break;
+       case SWITCHDEV_FDB_DEL_TO_DEVICE:
+               fdb = &switchdev_work->fdb_info;
+               cpsw_ale_del_ucast(cpsw->ale, (u8 *)fdb->addr, priv->emac_port,
+                                  ALE_VLAN | ALE_SECURE, fdb->vid);
+               break;
+       default:
+               break;
+       }
+       rtnl_unlock();
+
+       kfree(switchdev_work->fdb_info.addr);
+       kfree(switchdev_work);
+       dev_put(priv->ndev);
+}
+
+/* called under rcu_read_lock() */
+static int cpsw_switchdev_event(struct notifier_block *unused,
+                               unsigned long event, void *ptr)
+{
+       struct net_device *ndev = switchdev_notifier_info_to_dev(ptr);
+       struct switchdev_notifier_fdb_info *fdb_info = ptr;
+       struct cpsw_switchdev_event_work *switchdev_work;
+       struct cpsw_priv *priv = netdev_priv(ndev);
+
+       if (!cpsw_port_dev_check(ndev))
+               return NOTIFY_DONE;
+
+       switchdev_work = kzalloc(sizeof(*switchdev_work), GFP_ATOMIC);
+       if (WARN_ON(!switchdev_work))
+               return NOTIFY_BAD;
+
+       INIT_WORK(&switchdev_work->work, cpsw_switchdev_event_work);
+       switchdev_work->priv = priv;
+       switchdev_work->event = event;
+
+       switch (event) {
+       case SWITCHDEV_FDB_ADD_TO_DEVICE:
+       case SWITCHDEV_FDB_DEL_TO_DEVICE:
+               memcpy(&switchdev_work->fdb_info, ptr,
+                      sizeof(switchdev_work->fdb_info));
+               switchdev_work->fdb_info.addr = kzalloc(ETH_ALEN, GFP_ATOMIC);
+               ether_addr_copy((u8 *)switchdev_work->fdb_info.addr,
+                               fdb_info->addr);
+               dev_hold(ndev);
+               break;
+       default:
+               kfree(switchdev_work);
+               return NOTIFY_DONE;
+       }
+
+       queue_work(system_long_wq, &switchdev_work->work);
+
+       return NOTIFY_DONE;
+}
+
+static struct notifier_block cpsw_switchdev_notifier = {
+       .notifier_call = cpsw_switchdev_event,
+};
+
+static void cpsw_netdevice_port_link(struct net_device *ndev)
+{
+       struct cpsw_priv *priv = netdev_priv(ndev);
+       struct cpsw_common *cpsw = priv->cpsw;
+
+       if (priv->emac_port != HOST_PORT_NUM)
+               return;
+
+       cpsw_ale_control_set(cpsw->ale, HOST_PORT_NUM, ALE_P0_UNI_FLOOD, 1);
+}
+
+static void cpsw_netdevice_port_unlink(struct net_device *ndev)
+{
+       struct cpsw_priv *priv = netdev_priv(ndev);
+       struct cpsw_common *cpsw = priv->cpsw;
+
+       if (priv->emac_port != HOST_PORT_NUM)
+               return;
+
+       cpsw_ale_control_set(cpsw->ale, HOST_PORT_NUM, ALE_P0_UNI_FLOOD, 0);
+}
+
+/* netdev notifier */
+static int cpsw_netdevice_event(struct notifier_block *unused,
+                               unsigned long event, void *ptr)
+{
+       struct net_device *ndev = netdev_notifier_info_to_dev(ptr);
+       struct netdev_notifier_changeupper_info *info;
+
+       switch (event) {
+       case NETDEV_CHANGEUPPER:
+               info = ptr;
+               if (!info->master)
+                       goto out;
+               if (info->linking)
+                       cpsw_netdevice_port_link(ndev);
+               else
+                       cpsw_netdevice_port_unlink(ndev);
+               break;
+       default:
+               return NOTIFY_DONE;
+       }
+
+out:
+       return NOTIFY_DONE;
+}
+
+static struct notifier_block cpsw_netdevice_nb __read_mostly = {
+       .notifier_call = cpsw_netdevice_event,
+};
+
+static int cpsw_register_notifiers(struct cpsw_priv *priv)
+{
+       int ret;
+
+       ret = register_netdevice_notifier(&cpsw_netdevice_nb);
+       if (ret) {
+               cpsw_err(priv, probe, "can't register netdevice notifier\n");
+               return ret;
+       }
+
+       ret = register_switchdev_notifier(&cpsw_switchdev_notifier);
+       if (ret) {
+               cpsw_err(priv, probe, "can't register switchdev notifier\n");
+               goto unreg_netdevice;
+       }
+
+       return ret;
+
+unreg_netdevice:
+       ret = unregister_netdevice_notifier(&cpsw_netdevice_nb);
+
+       return ret;
+}
+
+static int cpsw_unregister_notifiers(struct cpsw_priv *priv)
+{
+       int ret;
+
+       ret = unregister_switchdev_notifier(&cpsw_switchdev_notifier);
+       if (ret)
+               dev_err(priv->dev, "can't unregister switchdev notifier\n");
+
+       ret += unregister_netdevice_notifier(&cpsw_netdevice_nb);
+       if (ret)
+               dev_err(priv->dev, "can't unregister netdevice notifier\n");
+
+       return ret;
+}
+
 static int cpsw_probe(struct platform_device *pdev)
 {
        struct clk                      *clk;
@@ -2935,7 +3291,11 @@ static int cpsw_probe(struct platform_device *pdev)
                cpsw->slaves[i].slave_num = i;
 
        cpsw->slaves[0].ndev = ndev;
-       priv->emac_port = 0;
+
+       if (cpsw_is_switch(cpsw->data.switch_mode))
+               priv->emac_port = HOST_PORT_NUM;
+       else
+               priv->emac_port = 1;
 
        clk = devm_clk_get(&pdev->dev, "fck");
        if (IS_ERR(clk)) {
@@ -3076,8 +3436,17 @@ static int cpsw_probe(struct platform_device *pdev)
                        cpsw->quirk_irq = true;
        }
 
-       ndev->features |= NETIF_F_HW_VLAN_CTAG_FILTER | NETIF_F_HW_VLAN_CTAG_RX;
+       if (cpsw_is_switchdev(cpsw->data.switch_mode)) {
+               ret = cpsw_probe_cpu_port(cpsw);
+               if (ret) {
+                       cpsw_err(priv, probe, "error probe cpu interface\n");
+                       goto clean_dma_ret;
+               }
+               cpsw_port_switchdev_init(ndev);
+               ndev->features |= NETIF_F_NETNS_LOCAL;
+       }
 
+       ndev->features |= NETIF_F_HW_VLAN_CTAG_FILTER | NETIF_F_HW_VLAN_CTAG_RX;
        ndev->netdev_ops = &cpsw_netdev_ops;
        ndev->ethtool_ops = &cpsw_ethtool_ops;
        netif_napi_add(ndev, &cpsw->napi_rx, cpsw_rx_poll, CPSW_POLL_WEIGHT);
@@ -3093,7 +3462,7 @@ static int cpsw_probe(struct platform_device *pdev)
                goto clean_dma_ret;
        }
 
-       if (cpsw->data.dual_emac) {
+       if (!cpsw_is_switch(cpsw->data.switch_mode)) {
                ret = cpsw_probe_dual_emac(priv);
                if (ret) {
                        cpsw_err(priv, probe, "error probe slave 2 emac 
interface\n");
@@ -3139,6 +3508,12 @@ static int cpsw_probe(struct platform_device *pdev)
                goto clean_dma_ret;
        }
 
+       if (cpsw_is_switchdev(cpsw->data.switch_mode)) {
+               ret = cpsw_register_notifiers(priv);
+               if (ret)
+                       goto clean_dma_ret;
+       }
+
        cpsw_notice(priv, probe,
                    "initialized device (regs %pa, irq %d, pool size %d)\n",
                    &ss_res->start, ndev->irq, dma_params.descs_pool_size);
@@ -3164,7 +3539,8 @@ static int cpsw_probe(struct platform_device *pdev)
 static int cpsw_remove(struct platform_device *pdev)
 {
        struct net_device *ndev = platform_get_drvdata(pdev);
-       struct cpsw_common *cpsw = ndev_to_cpsw(ndev);
+       struct cpsw_priv *priv = netdev_priv(ndev);
+       struct cpsw_common *cpsw = priv->cpsw;
        int ret;
 
        ret = pm_runtime_get_sync(&pdev->dev);
@@ -3173,7 +3549,10 @@ static int cpsw_remove(struct platform_device *pdev)
                return ret;
        }
 
-       if (cpsw->data.dual_emac)
+       if (cpsw_is_switchdev(cpsw->data.switch_mode))
+               ret = cpsw_unregister_notifiers(priv);
+
+       if (cpsw->data.switch_mode)
                unregister_netdev(cpsw->slaves[1].ndev);
        unregister_netdev(ndev);
 
@@ -3182,8 +3561,10 @@ static int cpsw_remove(struct platform_device *pdev)
        cpsw_remove_dt(pdev);
        pm_runtime_put_sync(&pdev->dev);
        pm_runtime_disable(&pdev->dev);
-       if (cpsw->data.dual_emac)
+       if (cpsw->data.switch_mode)
                free_netdev(cpsw->slaves[1].ndev);
+       if (cpsw->master)
+               free_netdev(cpsw->master);
        free_netdev(ndev);
        return 0;
 }
@@ -3195,7 +3576,7 @@ static int cpsw_suspend(struct device *dev)
        struct net_device       *ndev = platform_get_drvdata(pdev);
        struct cpsw_common      *cpsw = ndev_to_cpsw(ndev);
 
-       if (cpsw->data.dual_emac) {
+       if (cpsw->data.switch_mode) {
                int i;
 
                for (i = 0; i < cpsw->data.slaves; i++) {
@@ -3224,7 +3605,7 @@ static int cpsw_resume(struct device *dev)
 
        /* shut up ASSERT_RTNL() warning in netif_set_real_num_tx/rx_queues */
        rtnl_lock();
-       if (cpsw->data.dual_emac) {
+       if (cpsw->data.switch_mode) {
                int i;
 
                for (i = 0; i < cpsw->data.slaves; i++) {
diff --git a/drivers/net/ethernet/ti/cpsw_priv.h 
b/drivers/net/ethernet/ti/cpsw_priv.h
index 3b02a83..4be5ffc 100644
--- a/drivers/net/ethernet/ti/cpsw_priv.h
+++ b/drivers/net/ethernet/ti/cpsw_priv.h
@@ -30,6 +30,12 @@
 #define CPSW2_TX_PRI_MAP    0x18 /* Tx Header Priority to Switch Pri Mapping */
 #define CPSW2_TS_SEQ_MTYPE  0x1c /* Time Sync Sequence ID Offset and Msg Type 
*/
 
+enum {
+       CPSW_TI_SWITCH,
+       CPSW_DUAL_EMAC,
+       CPSW_SWITCHDEV,
+};
+
 struct cpsw_slave_data {
        struct  device_node *phy_node;
        char    phy_id[MII_BUS_ID_SIZE];
@@ -48,7 +54,7 @@ struct cpsw_platform_data {
        u32     bd_ram_size;  /*buffer descriptor ram size */
        u32     mac_control;    /* Mac control register */
        u16     default_vlan;   /* Def VLAN for ALE lookup in VLAN aware mode*/
-       bool    dual_emac;      /* Enable Dual EMAC mode */
+       u8      switch_mode;    /* Enable Dual EMAC/switchdev mode */
 };
 
 struct cpsw_slave {
@@ -80,6 +86,7 @@ struct cpsw_common {
        u32                             coal_intvl;
        u32                             bus_freq_mhz;
        int                             rx_packet_max;
+       struct net_device               *master; /* used for switchdev */
        struct cpsw_slave               *slaves;
        struct cpdma_ctlr               *dma;
        struct cpsw_vector              txv[CPSW_MAX_QUEUES];
-- 
2.7.4

Reply via email to