diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2020-08-05 20:13:21 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2020-08-05 20:13:21 -0700 |
commit | 47ec5303d73ea344e84f46660fff693c57641386 (patch) | |
tree | a2252debab749de29620c43285295d60c4741119 /drivers/net/dsa/qca8k.c | |
parent | 8186749621ed6b8fc42644c399e8c755a2b6f630 (diff) | |
parent | c1055b76ad00aed0e8b79417080f212d736246b6 (diff) | |
download | linux-47ec5303d73ea344e84f46660fff693c57641386.tar.gz linux-47ec5303d73ea344e84f46660fff693c57641386.tar.bz2 linux-47ec5303d73ea344e84f46660fff693c57641386.zip |
Merge git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next
Pull networking updates from David Miller:
1) Support 6Ghz band in ath11k driver, from Rajkumar Manoharan.
2) Support UDP segmentation in code TSO code, from Eric Dumazet.
3) Allow flashing different flash images in cxgb4 driver, from Vishal
Kulkarni.
4) Add drop frames counter and flow status to tc flower offloading,
from Po Liu.
5) Support n-tuple filters in cxgb4, from Vishal Kulkarni.
6) Various new indirect call avoidance, from Eric Dumazet and Brian
Vazquez.
7) Fix BPF verifier failures on 32-bit pointer arithmetic, from
Yonghong Song.
8) Support querying and setting hardware address of a port function via
devlink, use this in mlx5, from Parav Pandit.
9) Support hw ipsec offload on bonding slaves, from Jarod Wilson.
10) Switch qca8k driver over to phylink, from Jonathan McDowell.
11) In bpftool, show list of processes holding BPF FD references to
maps, programs, links, and btf objects. From Andrii Nakryiko.
12) Several conversions over to generic power management, from Vaibhav
Gupta.
13) Add support for SO_KEEPALIVE et al. to bpf_setsockopt(), from Dmitry
Yakunin.
14) Various https url conversions, from Alexander A. Klimov.
15) Timestamping and PHC support for mscc PHY driver, from Antoine
Tenart.
16) Support bpf iterating over tcp and udp sockets, from Yonghong Song.
17) Support 5GBASE-T i40e NICs, from Aleksandr Loktionov.
18) Add kTLS RX HW offload support to mlx5e, from Tariq Toukan.
19) Fix the ->ndo_start_xmit() return type to be netdev_tx_t in several
drivers. From Luc Van Oostenryck.
20) XDP support for xen-netfront, from Denis Kirjanov.
21) Support receive buffer autotuning in MPTCP, from Florian Westphal.
22) Support EF100 chip in sfc driver, from Edward Cree.
23) Add XDP support to mvpp2 driver, from Matteo Croce.
24) Support MPTCP in sock_diag, from Paolo Abeni.
25) Commonize UDP tunnel offloading code by creating udp_tunnel_nic
infrastructure, from Jakub Kicinski.
26) Several pci_ --> dma_ API conversions, from Christophe JAILLET.
27) Add FLOW_ACTION_POLICE support to mlxsw, from Ido Schimmel.
28) Add SK_LOOKUP bpf program type, from Jakub Sitnicki.
29) Refactor a lot of networking socket option handling code in order to
avoid set_fs() calls, from Christoph Hellwig.
30) Add rfc4884 support to icmp code, from Willem de Bruijn.
31) Support TBF offload in dpaa2-eth driver, from Ioana Ciornei.
32) Support XDP_REDIRECT in qede driver, from Alexander Lobakin.
33) Support PCI relaxed ordering in mlx5 driver, from Aya Levin.
34) Support TCP syncookies in MPTCP, from Flowian Westphal.
35) Fix several tricky cases of PMTU handling wrt. briding, from Stefano
Brivio.
* git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next: (2056 commits)
net: thunderx: initialize VF's mailbox mutex before first usage
usb: hso: remove bogus check for EINPROGRESS
usb: hso: no complaint about kmalloc failure
hso: fix bailout in error case of probe
ip_tunnel_core: Fix build for archs without _HAVE_ARCH_IPV6_CSUM
selftests/net: relax cpu affinity requirement in msg_zerocopy test
mptcp: be careful on subflow creation
selftests: rtnetlink: make kci_test_encap() return sub-test result
selftests: rtnetlink: correct the final return value for the test
net: dsa: sja1105: use detected device id instead of DT one on mismatch
tipc: set ub->ifindex for local ipv6 address
ipv6: add ipv6_dev_find()
net: openvswitch: silence suspicious RCU usage warning
Revert "vxlan: fix tos value before xmit"
ptp: only allow phase values lower than 1 period
farsync: switch from 'pci_' to 'dma_' API
wan: wanxl: switch from 'pci_' to 'dma_' API
hv_netvsc: do not use VF device if link is down
dpaa2-eth: Fix passing zero to 'PTR_ERR' warning
net: macb: Properly handle phylink on at91sam9x
...
Diffstat (limited to 'drivers/net/dsa/qca8k.c')
-rw-r--r-- | drivers/net/dsa/qca8k.c | 558 |
1 files changed, 458 insertions, 100 deletions
diff --git a/drivers/net/dsa/qca8k.c b/drivers/net/dsa/qca8k.c index d2b5ab403e06..f1e484477e35 100644 --- a/drivers/net/dsa/qca8k.c +++ b/drivers/net/dsa/qca8k.c @@ -14,6 +14,7 @@ #include <linux/of_platform.h> #include <linux/if_bridge.h> #include <linux/mdio.h> +#include <linux/phylink.h> #include <linux/gpio/consumer.h> #include <linux/etherdevice.h> @@ -407,64 +408,121 @@ qca8k_fdb_flush(struct qca8k_priv *priv) mutex_unlock(&priv->reg_mutex); } -static void -qca8k_mib_init(struct qca8k_priv *priv) +static int +qca8k_vlan_access(struct qca8k_priv *priv, enum qca8k_vlan_cmd cmd, u16 vid) { + u32 reg; + + /* Set the command and VLAN index */ + reg = QCA8K_VTU_FUNC1_BUSY; + reg |= cmd; + reg |= vid << QCA8K_VTU_FUNC1_VID_S; + + /* Write the function register triggering the table access */ + qca8k_write(priv, QCA8K_REG_VTU_FUNC1, reg); + + /* wait for completion */ + if (qca8k_busy_wait(priv, QCA8K_REG_VTU_FUNC1, QCA8K_VTU_FUNC1_BUSY)) + return -ETIMEDOUT; + + /* Check for table full violation when adding an entry */ + if (cmd == QCA8K_VLAN_LOAD) { + reg = qca8k_read(priv, QCA8K_REG_VTU_FUNC1); + 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); - qca8k_reg_set(priv, QCA8K_REG_MIB, QCA8K_MIB_FLUSH | QCA8K_MIB_BUSY); - qca8k_busy_wait(priv, QCA8K_REG_MIB, QCA8K_MIB_BUSY); - qca8k_reg_set(priv, QCA8K_REG_MIB, QCA8K_MIB_CPU_KEEP); - qca8k_write(priv, QCA8K_REG_MODULE_EN, QCA8K_MODULE_EN_MIB); + ret = qca8k_vlan_access(priv, QCA8K_VLAN_READ, vid); + if (ret < 0) + goto out; + + reg = qca8k_read(priv, QCA8K_REG_VTU_FUNC0); + reg |= QCA8K_VTU_FUNC0_VALID | QCA8K_VTU_FUNC0_IVL_EN; + reg &= ~(QCA8K_VTU_FUNC0_EG_MODE_MASK << QCA8K_VTU_FUNC0_EG_MODE_S(port)); + if (untagged) + reg |= QCA8K_VTU_FUNC0_EG_MODE_UNTAG << + QCA8K_VTU_FUNC0_EG_MODE_S(port); + else + reg |= QCA8K_VTU_FUNC0_EG_MODE_TAG << + QCA8K_VTU_FUNC0_EG_MODE_S(port); + + qca8k_write(priv, QCA8K_REG_VTU_FUNC0, reg); + ret = qca8k_vlan_access(priv, QCA8K_VLAN_LOAD, vid); + +out: mutex_unlock(&priv->reg_mutex); + + return ret; } static int -qca8k_set_pad_ctrl(struct qca8k_priv *priv, int port, int mode) +qca8k_vlan_del(struct qca8k_priv *priv, u8 port, u16 vid) { - u32 reg, val; + u32 reg, mask; + int ret, i; + bool del; - switch (port) { - case 0: - reg = QCA8K_REG_PORT0_PAD_CTRL; - break; - case 6: - reg = QCA8K_REG_PORT6_PAD_CTRL; - break; - default: - pr_err("Can't set PAD_CTRL on port %d\n", port); - return -EINVAL; + mutex_lock(&priv->reg_mutex); + ret = qca8k_vlan_access(priv, QCA8K_VLAN_READ, vid); + if (ret < 0) + goto out; + + reg = qca8k_read(priv, QCA8K_REG_VTU_FUNC0); + reg &= ~(3 << QCA8K_VTU_FUNC0_EG_MODE_S(port)); + reg |= QCA8K_VTU_FUNC0_EG_MODE_NOT << + QCA8K_VTU_FUNC0_EG_MODE_S(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_NOT; + mask <<= QCA8K_VTU_FUNC0_EG_MODE_S(i); + + if ((reg & mask) != mask) { + del = false; + break; + } } - /* Configure a port to be directly connected to an external - * PHY or MAC. - */ - switch (mode) { - case PHY_INTERFACE_MODE_RGMII: - /* RGMII mode means no delay so don't enable the delay */ - val = QCA8K_PORT_PAD_RGMII_EN; - qca8k_write(priv, reg, val); - break; - case PHY_INTERFACE_MODE_RGMII_ID: - /* RGMII_ID needs internal delay. This is enabled through - * PORT5_PAD_CTRL for all ports, rather than individual port - * registers - */ - qca8k_write(priv, reg, - QCA8K_PORT_PAD_RGMII_EN | - QCA8K_PORT_PAD_RGMII_TX_DELAY(QCA8K_MAX_DELAY) | - QCA8K_PORT_PAD_RGMII_RX_DELAY(QCA8K_MAX_DELAY)); - qca8k_write(priv, QCA8K_REG_PORT5_PAD_CTRL, - QCA8K_PORT_PAD_RGMII_RX_DELAY_EN); - break; - case PHY_INTERFACE_MODE_SGMII: - qca8k_write(priv, reg, QCA8K_PORT_PAD_SGMII_EN); - break; - default: - pr_err("xMII mode %d not supported\n", mode); - return -EINVAL; + if (del) { + ret = qca8k_vlan_access(priv, QCA8K_VLAN_PURGE, vid); + } else { + qca8k_write(priv, QCA8K_REG_VTU_FUNC0, reg); + ret = qca8k_vlan_access(priv, QCA8K_VLAN_LOAD, vid); } - return 0; +out: + mutex_unlock(&priv->reg_mutex); + + return ret; +} + +static void +qca8k_mib_init(struct qca8k_priv *priv) +{ + mutex_lock(&priv->reg_mutex); + qca8k_reg_set(priv, QCA8K_REG_MIB, QCA8K_MIB_FLUSH | QCA8K_MIB_BUSY); + qca8k_busy_wait(priv, QCA8K_REG_MIB, QCA8K_MIB_BUSY); + qca8k_reg_set(priv, QCA8K_REG_MIB, QCA8K_MIB_CPU_KEEP); + qca8k_write(priv, QCA8K_REG_MODULE_EN, QCA8K_MODULE_EN_MIB); + mutex_unlock(&priv->reg_mutex); } static void @@ -639,9 +697,7 @@ static int qca8k_setup(struct dsa_switch *ds) { struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv; - phy_interface_t phy_mode = PHY_INTERFACE_MODE_NA; int ret, i; - u32 mask; /* Make sure that port 0 is the cpu port */ if (!dsa_is_cpu_port(ds, 0)) { @@ -661,24 +717,9 @@ qca8k_setup(struct dsa_switch *ds) if (ret) return ret; - /* Initialize CPU port pad mode (xMII type, delays...) */ - ret = of_get_phy_mode(dsa_to_port(ds, QCA8K_CPU_PORT)->dn, &phy_mode); - if (ret) { - pr_err("Can't find phy-mode for master device\n"); - return ret; - } - ret = qca8k_set_pad_ctrl(priv, QCA8K_CPU_PORT, phy_mode); - if (ret < 0) - return ret; - - /* Enable CPU Port, force it to maximum bandwidth and full-duplex */ - mask = QCA8K_PORT_STATUS_SPEED_1000 | QCA8K_PORT_STATUS_TXFLOW | - QCA8K_PORT_STATUS_RXFLOW | QCA8K_PORT_STATUS_DUPLEX; - qca8k_write(priv, QCA8K_REG_PORT_STATUS(QCA8K_CPU_PORT), mask); + /* Enable CPU Port */ qca8k_reg_set(priv, QCA8K_REG_GLOBAL_FW_CTRL0, QCA8K_GLOBAL_FW_CTRL0_CPU_PORT_EN); - qca8k_port_set_status(priv, QCA8K_CPU_PORT, 1); - priv->port_sts[QCA8K_CPU_PORT].enabled = 1; /* Enable MIB counters */ qca8k_mib_init(priv); @@ -693,10 +734,9 @@ qca8k_setup(struct dsa_switch *ds) qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i), QCA8K_PORT_LOOKUP_MEMBER, 0); - /* Disable MAC by default on all user ports */ + /* Disable MAC by default on all ports */ for (i = 1; i < QCA8K_NUM_PORTS; i++) - if (dsa_is_user_port(ds, i)) - qca8k_port_set_status(priv, i, 0); + qca8k_port_set_status(priv, i, 0); /* Forward all unknown frames to CPU port for Linux processing */ qca8k_write(priv, QCA8K_REG_GLOBAL_FW_CTRL1, @@ -713,7 +753,7 @@ qca8k_setup(struct dsa_switch *ds) QCA8K_PORT_LOOKUP_MEMBER, dsa_user_ports(ds)); } - /* Invividual user ports get connected to CPU port only */ + /* Individual user ports get connected to CPU port only */ if (dsa_is_user_port(ds, i)) { int shift = 16 * (i % 2); @@ -729,58 +769,273 @@ qca8k_setup(struct dsa_switch *ds) * default egress vid */ qca8k_rmw(priv, QCA8K_EGRESS_VLAN(i), - 0xffff << shift, 1 << shift); + 0xfff << shift, + QCA8K_PORT_VID_DEF << shift); qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(i), - QCA8K_PORT_VLAN_CVID(1) | - QCA8K_PORT_VLAN_SVID(1)); + QCA8K_PORT_VLAN_CVID(QCA8K_PORT_VID_DEF) | + QCA8K_PORT_VLAN_SVID(QCA8K_PORT_VID_DEF)); } } + /* Setup our port MTUs to match power on defaults */ + for (i = 0; i < QCA8K_NUM_PORTS; i++) + priv->port_mtu[i] = ETH_FRAME_LEN + ETH_FCS_LEN; + qca8k_write(priv, QCA8K_MAX_FRAME_SIZE, ETH_FRAME_LEN + ETH_FCS_LEN); + /* Flush the FDB table */ qca8k_fdb_flush(priv); + /* We don't have interrupts for link changes, so we need to poll */ + ds->pcs_poll = true; + return 0; } static void -qca8k_adjust_link(struct dsa_switch *ds, int port, struct phy_device *phy) +qca8k_phylink_mac_config(struct dsa_switch *ds, int port, unsigned int mode, + const struct phylink_link_state *state) { struct qca8k_priv *priv = ds->priv; - u32 reg; + u32 reg, val; + + switch (port) { + case 0: /* 1st CPU port */ + if (state->interface != PHY_INTERFACE_MODE_RGMII && + state->interface != PHY_INTERFACE_MODE_RGMII_ID && + state->interface != PHY_INTERFACE_MODE_SGMII) + return; + + reg = QCA8K_REG_PORT0_PAD_CTRL; + break; + case 1: + case 2: + case 3: + case 4: + case 5: + /* Internal PHY, nothing to do */ + return; + case 6: /* 2nd CPU port / external PHY */ + if (state->interface != PHY_INTERFACE_MODE_RGMII && + state->interface != PHY_INTERFACE_MODE_RGMII_ID && + state->interface != PHY_INTERFACE_MODE_SGMII && + state->interface != PHY_INTERFACE_MODE_1000BASEX) + return; + + reg = QCA8K_REG_PORT6_PAD_CTRL; + break; + default: + dev_err(ds->dev, "%s: unsupported port: %i\n", __func__, port); + return; + } + + if (port != 6 && phylink_autoneg_inband(mode)) { + dev_err(ds->dev, "%s: in-band negotiation unsupported\n", + __func__); + return; + } + + switch (state->interface) { + case PHY_INTERFACE_MODE_RGMII: + /* RGMII mode means no delay so don't enable the delay */ + qca8k_write(priv, reg, QCA8K_PORT_PAD_RGMII_EN); + break; + case PHY_INTERFACE_MODE_RGMII_ID: + /* RGMII_ID needs internal delay. This is enabled through + * PORT5_PAD_CTRL for all ports, rather than individual port + * registers + */ + qca8k_write(priv, reg, + QCA8K_PORT_PAD_RGMII_EN | + QCA8K_PORT_PAD_RGMII_TX_DELAY(QCA8K_MAX_DELAY) | + QCA8K_PORT_PAD_RGMII_RX_DELAY(QCA8K_MAX_DELAY)); + qca8k_write(priv, QCA8K_REG_PORT5_PAD_CTRL, + QCA8K_PORT_PAD_RGMII_RX_DELAY_EN); + break; + case PHY_INTERFACE_MODE_SGMII: + case PHY_INTERFACE_MODE_1000BASEX: + /* Enable SGMII on the port */ + qca8k_write(priv, reg, QCA8K_PORT_PAD_SGMII_EN); - /* Force fixed-link setting for CPU port, skip others. */ - if (!phy_is_pseudo_fixed_link(phy)) + /* Enable/disable SerDes auto-negotiation as necessary */ + val = qca8k_read(priv, QCA8K_REG_PWS); + if (phylink_autoneg_inband(mode)) + val &= ~QCA8K_PWS_SERDES_AEN_DIS; + else + val |= QCA8K_PWS_SERDES_AEN_DIS; + qca8k_write(priv, QCA8K_REG_PWS, val); + + /* Configure the SGMII parameters */ + val = qca8k_read(priv, QCA8K_REG_SGMII_CTRL); + + val |= QCA8K_SGMII_EN_PLL | QCA8K_SGMII_EN_RX | + QCA8K_SGMII_EN_TX | QCA8K_SGMII_EN_SD; + + if (dsa_is_cpu_port(ds, port)) { + /* CPU port, we're talking to the CPU MAC, be a PHY */ + val &= ~QCA8K_SGMII_MODE_CTRL_MASK; + val |= QCA8K_SGMII_MODE_CTRL_PHY; + } else if (state->interface == PHY_INTERFACE_MODE_SGMII) { + val &= ~QCA8K_SGMII_MODE_CTRL_MASK; + val |= QCA8K_SGMII_MODE_CTRL_MAC; + } else if (state->interface == PHY_INTERFACE_MODE_1000BASEX) { + val &= ~QCA8K_SGMII_MODE_CTRL_MASK; + val |= QCA8K_SGMII_MODE_CTRL_BASEX; + } + + qca8k_write(priv, QCA8K_REG_SGMII_CTRL, val); + break; + default: + dev_err(ds->dev, "xMII mode %s not supported for port %d\n", + phy_modes(state->interface), port); return; + } +} + +static void +qca8k_phylink_validate(struct dsa_switch *ds, int port, + unsigned long *supported, + struct phylink_link_state *state) +{ + __ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, }; - /* Set port speed */ - switch (phy->speed) { - case 10: - reg = QCA8K_PORT_STATUS_SPEED_10; + switch (port) { + case 0: /* 1st CPU port */ + if (state->interface != PHY_INTERFACE_MODE_NA && + state->interface != PHY_INTERFACE_MODE_RGMII && + state->interface != PHY_INTERFACE_MODE_RGMII_ID && + state->interface != PHY_INTERFACE_MODE_SGMII) + goto unsupported; break; - case 100: - reg = QCA8K_PORT_STATUS_SPEED_100; + case 1: + case 2: + case 3: + case 4: + case 5: + /* Internal PHY */ + if (state->interface != PHY_INTERFACE_MODE_NA && + state->interface != PHY_INTERFACE_MODE_GMII) + goto unsupported; break; - case 1000: - reg = QCA8K_PORT_STATUS_SPEED_1000; + case 6: /* 2nd CPU port / external PHY */ + if (state->interface != PHY_INTERFACE_MODE_NA && + state->interface != PHY_INTERFACE_MODE_RGMII && + state->interface != PHY_INTERFACE_MODE_RGMII_ID && + state->interface != PHY_INTERFACE_MODE_SGMII && + state->interface != PHY_INTERFACE_MODE_1000BASEX) + goto unsupported; break; default: - dev_dbg(priv->dev, "port%d link speed %dMbps not supported.\n", - port, phy->speed); +unsupported: + linkmode_zero(supported); return; } - /* Set duplex mode */ - if (phy->duplex == DUPLEX_FULL) - reg |= QCA8K_PORT_STATUS_DUPLEX; + phylink_set_port_modes(mask); + phylink_set(mask, Autoneg); + + phylink_set(mask, 1000baseT_Full); + phylink_set(mask, 10baseT_Half); + phylink_set(mask, 10baseT_Full); + phylink_set(mask, 100baseT_Half); + phylink_set(mask, 100baseT_Full); - /* Force flow control */ - if (dsa_is_cpu_port(ds, port)) - reg |= QCA8K_PORT_STATUS_RXFLOW | QCA8K_PORT_STATUS_TXFLOW; + if (state->interface == PHY_INTERFACE_MODE_1000BASEX) + phylink_set(mask, 1000baseX_Full); + + phylink_set(mask, Pause); + phylink_set(mask, Asym_Pause); + + linkmode_and(supported, supported, mask); + linkmode_and(state->advertising, state->advertising, mask); +} + +static int +qca8k_phylink_mac_link_state(struct dsa_switch *ds, int port, + struct phylink_link_state *state) +{ + struct qca8k_priv *priv = ds->priv; + u32 reg; + + reg = qca8k_read(priv, QCA8K_REG_PORT_STATUS(port)); + + state->link = !!(reg & QCA8K_PORT_STATUS_LINK_UP); + state->an_complete = state->link; + state->an_enabled = !!(reg & QCA8K_PORT_STATUS_LINK_AUTO); + state->duplex = (reg & QCA8K_PORT_STATUS_DUPLEX) ? DUPLEX_FULL : + DUPLEX_HALF; + + switch (reg & QCA8K_PORT_STATUS_SPEED) { + case QCA8K_PORT_STATUS_SPEED_10: + state->speed = SPEED_10; + break; + case QCA8K_PORT_STATUS_SPEED_100: + state->speed = SPEED_100; + break; + case QCA8K_PORT_STATUS_SPEED_1000: + state->speed = SPEED_1000; + break; + default: + state->speed = SPEED_UNKNOWN; + break; + } + + state->pause = MLO_PAUSE_NONE; + if (reg & QCA8K_PORT_STATUS_RXFLOW) + state->pause |= MLO_PAUSE_RX; + if (reg & QCA8K_PORT_STATUS_TXFLOW) + state->pause |= MLO_PAUSE_TX; + + return 1; +} + +static void +qca8k_phylink_mac_link_down(struct dsa_switch *ds, int port, unsigned int mode, + phy_interface_t interface) +{ + struct qca8k_priv *priv = ds->priv; - /* Force link down before changing MAC options */ qca8k_port_set_status(priv, port, 0); +} + +static void +qca8k_phylink_mac_link_up(struct dsa_switch *ds, int port, unsigned int mode, + phy_interface_t interface, struct phy_device *phydev, + int speed, int duplex, bool tx_pause, bool rx_pause) +{ + struct qca8k_priv *priv = ds->priv; + u32 reg; + + if (phylink_autoneg_inband(mode)) { + reg = QCA8K_PORT_STATUS_LINK_AUTO; + } else { + switch (speed) { + case SPEED_10: + reg = QCA8K_PORT_STATUS_SPEED_10; + break; + case SPEED_100: + reg = QCA8K_PORT_STATUS_SPEED_100; + break; + case SPEED_1000: + reg = QCA8K_PORT_STATUS_SPEED_1000; + break; + default: + reg = QCA8K_PORT_STATUS_LINK_AUTO; + break; + } + + if (duplex == DUPLEX_FULL) + reg |= QCA8K_PORT_STATUS_DUPLEX; + + if (rx_pause || dsa_is_cpu_port(ds, port)) + reg |= QCA8K_PORT_STATUS_RXFLOW; + + if (tx_pause || dsa_is_cpu_port(ds, port)) + reg |= QCA8K_PORT_STATUS_TXFLOW; + } + + reg |= QCA8K_PORT_STATUS_TXMAC | QCA8K_PORT_STATUS_RXMAC; + qca8k_write(priv, QCA8K_REG_PORT_STATUS(port), reg); - qca8k_port_set_status(priv, port, 1); } static void @@ -937,13 +1192,11 @@ qca8k_port_enable(struct dsa_switch *ds, int port, { struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv; - if (!dsa_is_user_port(ds, port)) - return 0; - qca8k_port_set_status(priv, port, 1); priv->port_sts[port].enabled = 1; - phy_support_asym_pause(phy); + if (dsa_is_user_port(ds, port)) + phy_support_asym_pause(phy); return 0; } @@ -958,12 +1211,36 @@ qca8k_port_disable(struct dsa_switch *ds, int port) } static int +qca8k_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu) +{ + struct qca8k_priv *priv = ds->priv; + int i, mtu = 0; + + priv->port_mtu[port] = new_mtu; + + for (i = 0; i < QCA8K_NUM_PORTS; i++) + if (priv->port_mtu[port] > mtu) + mtu = priv->port_mtu[port]; + + /* Include L2 header / FCS length */ + qca8k_write(priv, QCA8K_MAX_FRAME_SIZE, mtu + ETH_HLEN + ETH_FCS_LEN); + + return 0; +} + +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) { /* Set the vid to the port vlan id if no vid is set */ if (!vid) - vid = 1; + vid = QCA8K_PORT_VID_DEF; return qca8k_fdb_add(priv, addr, port_mask, vid, QCA8K_ATU_STATUS_STATIC); @@ -987,7 +1264,7 @@ qca8k_port_fdb_del(struct dsa_switch *ds, int port, u16 port_mask = BIT(port); if (!vid) - vid = 1; + vid = QCA8K_PORT_VID_DEF; return qca8k_fdb_del(priv, addr, port_mask, vid); } @@ -1016,6 +1293,76 @@ qca8k_port_fdb_dump(struct dsa_switch *ds, int port, return 0; } +static int +qca8k_port_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering) +{ + struct qca8k_priv *priv = ds->priv; + + if (vlan_filtering) { + qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port), + QCA8K_PORT_LOOKUP_VLAN_MODE, + QCA8K_PORT_LOOKUP_VLAN_MODE_SECURE); + } else { + qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port), + QCA8K_PORT_LOOKUP_VLAN_MODE, + QCA8K_PORT_LOOKUP_VLAN_MODE_NONE); + } + + return 0; +} + +static int +qca8k_port_vlan_prepare(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan) +{ + return 0; +} + +static void +qca8k_port_vlan_add(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan) +{ + bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED; + bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID; + struct qca8k_priv *priv = ds->priv; + int ret = 0; + u16 vid; + + for (vid = vlan->vid_begin; vid <= vlan->vid_end && !ret; ++vid) + ret = qca8k_vlan_add(priv, port, vid, untagged); + + if (ret) + dev_err(priv->dev, "Failed to add VLAN to port %d (%d)", port, ret); + + if (pvid) { + int shift = 16 * (port % 2); + + qca8k_rmw(priv, QCA8K_EGRESS_VLAN(port), + 0xfff << shift, + vlan->vid_end << shift); + qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(port), + QCA8K_PORT_VLAN_CVID(vlan->vid_end) | + QCA8K_PORT_VLAN_SVID(vlan->vid_end)); + } +} + +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 = 0; + u16 vid; + + for (vid = vlan->vid_begin; vid <= vlan->vid_end && !ret; ++vid) + ret = qca8k_vlan_del(priv, port, vid); + + if (ret) + dev_err(priv->dev, "Failed to delete VLAN from port %d (%d)", port, ret); + + return ret; +} + static enum dsa_tag_protocol qca8k_get_tag_protocol(struct dsa_switch *ds, int port, enum dsa_tag_protocol mp) @@ -1026,7 +1373,6 @@ qca8k_get_tag_protocol(struct dsa_switch *ds, int port, static const struct dsa_switch_ops qca8k_switch_ops = { .get_tag_protocol = qca8k_get_tag_protocol, .setup = qca8k_setup, - .adjust_link = qca8k_adjust_link, .get_strings = qca8k_get_strings, .get_ethtool_stats = qca8k_get_ethtool_stats, .get_sset_count = qca8k_get_sset_count, @@ -1034,12 +1380,23 @@ static const struct dsa_switch_ops qca8k_switch_ops = { .set_mac_eee = qca8k_set_mac_eee, .port_enable = qca8k_port_enable, .port_disable = qca8k_port_disable, + .port_change_mtu = qca8k_port_change_mtu, + .port_max_mtu = qca8k_port_max_mtu, .port_stp_state_set = qca8k_port_stp_state_set, .port_bridge_join = qca8k_port_bridge_join, .port_bridge_leave = qca8k_port_bridge_leave, .port_fdb_add = qca8k_port_fdb_add, .port_fdb_del = qca8k_port_fdb_del, .port_fdb_dump = qca8k_port_fdb_dump, + .port_vlan_filtering = qca8k_port_vlan_filtering, + .port_vlan_prepare = qca8k_port_vlan_prepare, + .port_vlan_add = qca8k_port_vlan_add, + .port_vlan_del = qca8k_port_vlan_del, + .phylink_validate = qca8k_phylink_validate, + .phylink_mac_link_state = qca8k_phylink_mac_link_state, + .phylink_mac_config = qca8k_phylink_mac_config, + .phylink_mac_link_down = qca8k_phylink_mac_link_down, + .phylink_mac_link_up = qca8k_phylink_mac_link_up, }; static int @@ -1085,6 +1442,7 @@ qca8k_sw_probe(struct mdio_device *mdiodev) priv->ds->dev = &mdiodev->dev; priv->ds->num_ports = QCA8K_NUM_PORTS; + priv->ds->configure_vlan_while_not_filtering = true; priv->ds->priv = priv; priv->ops = qca8k_switch_ops; priv->ds->ops = &priv->ops; |