summaryrefslogtreecommitdiff
path: root/drivers/net/dsa/microchip/ksz_common.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/dsa/microchip/ksz_common.c')
-rw-r--r--drivers/net/dsa/microchip/ksz_common.c273
1 files changed, 265 insertions, 8 deletions
diff --git a/drivers/net/dsa/microchip/ksz_common.c b/drivers/net/dsa/microchip/ksz_common.c
index 173ad8f04671..3fed406fb46a 100644
--- a/drivers/net/dsa/microchip/ksz_common.c
+++ b/drivers/net/dsa/microchip/ksz_common.c
@@ -16,6 +16,7 @@
#include <linux/etherdevice.h>
#include <linux/if_bridge.h>
#include <linux/if_vlan.h>
+#include <linux/if_hsr.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/of.h>
@@ -318,6 +319,9 @@ static const struct ksz_dev_ops ksz9477_dev_ops = {
.mdb_del = ksz9477_mdb_del,
.change_mtu = ksz9477_change_mtu,
.phylink_mac_link_up = ksz9477_phylink_mac_link_up,
+ .get_wol = ksz9477_get_wol,
+ .set_wol = ksz9477_set_wol,
+ .wol_pre_shutdown = ksz9477_wol_pre_shutdown,
.config_cpu_port = ksz9477_config_cpu_port,
.tc_cbs_set_cinc = ksz9477_tc_cbs_set_cinc,
.enable_stp_addr = ksz9477_enable_stp_addr,
@@ -364,6 +368,7 @@ static const struct ksz_dev_ops lan937x_dev_ops = {
};
static const u16 ksz8795_regs[] = {
+ [REG_SW_MAC_ADDR] = 0x68,
[REG_IND_CTRL_0] = 0x6E,
[REG_IND_DATA_8] = 0x70,
[REG_IND_DATA_CHECK] = 0x72,
@@ -439,6 +444,7 @@ static const u8 ksz8795_shifts[] = {
};
static const u16 ksz8863_regs[] = {
+ [REG_SW_MAC_ADDR] = 0x70,
[REG_IND_CTRL_0] = 0x79,
[REG_IND_DATA_8] = 0x7B,
[REG_IND_DATA_CHECK] = 0x7B,
@@ -492,6 +498,7 @@ static u8 ksz8863_shifts[] = {
};
static const u16 ksz9477_regs[] = {
+ [REG_SW_MAC_ADDR] = 0x0302,
[P_STP_CTRL] = 0x0B04,
[S_START_CTRL] = 0x0300,
[S_BROADCAST_CTRL] = 0x0332,
@@ -1942,14 +1949,14 @@ static int ksz_irq_phy_setup(struct ksz_device *dev)
ret = irq;
goto out;
}
- ds->slave_mii_bus->irq[phy] = irq;
+ ds->user_mii_bus->irq[phy] = irq;
}
}
return 0;
out:
while (phy--)
if (BIT(phy) & ds->phys_mii_mask)
- irq_dispose_mapping(ds->slave_mii_bus->irq[phy]);
+ irq_dispose_mapping(ds->user_mii_bus->irq[phy]);
return ret;
}
@@ -1961,7 +1968,7 @@ static void ksz_irq_phy_free(struct ksz_device *dev)
for (phy = 0; phy < KSZ_MAX_NUM_PORTS; phy++)
if (BIT(phy) & ds->phys_mii_mask)
- irq_dispose_mapping(ds->slave_mii_bus->irq[phy]);
+ irq_dispose_mapping(ds->user_mii_bus->irq[phy]);
}
static int ksz_mdio_register(struct ksz_device *dev)
@@ -1984,12 +1991,12 @@ static int ksz_mdio_register(struct ksz_device *dev)
bus->priv = dev;
bus->read = ksz_sw_mdio_read;
bus->write = ksz_sw_mdio_write;
- bus->name = "ksz slave smi";
+ bus->name = "ksz user smi";
snprintf(bus->id, MII_BUS_ID_SIZE, "SMI-%d", ds->index);
bus->parent = ds->dev;
bus->phy_mask = ~ds->phys_mii_mask;
- ds->slave_mii_bus = bus;
+ ds->user_mii_bus = bus;
if (dev->irq > 0) {
ret = ksz_irq_phy_setup(dev);
@@ -2341,7 +2348,7 @@ static void ksz_mib_read_work(struct work_struct *work)
if (!p->read) {
const struct dsa_port *dp = dsa_to_port(dev->ds, i);
- if (!netif_carrier_ok(dp->slave))
+ if (!netif_carrier_ok(dp->user))
mib->cnt_ptr = dev->info->reg_mib_cnt;
}
port_r_cnt(dev, i);
@@ -2461,7 +2468,7 @@ static void ksz_get_ethtool_stats(struct dsa_switch *ds, int port,
mutex_lock(&mib->cnt_mutex);
/* Only read dropped counters if no link. */
- if (!netif_carrier_ok(dp->slave))
+ if (!netif_carrier_ok(dp->user))
mib->cnt_ptr = dev->info->reg_mib_cnt;
port_r_cnt(dev, port);
memcpy(buf, mib->counters, dev->info->mib_cnt * sizeof(u64));
@@ -2571,7 +2578,7 @@ static int ksz_port_setup(struct dsa_switch *ds, int port)
if (!dsa_is_user_port(ds, port))
return 0;
- /* setup slave port */
+ /* setup user port */
dev->dev_ops->port_setup(dev, port, false);
/* port_stp_state_set() will be called after to enable the port so
@@ -3539,6 +3546,224 @@ static int ksz_setup_tc(struct dsa_switch *ds, int port,
}
}
+static void ksz_get_wol(struct dsa_switch *ds, int port,
+ struct ethtool_wolinfo *wol)
+{
+ struct ksz_device *dev = ds->priv;
+
+ if (dev->dev_ops->get_wol)
+ dev->dev_ops->get_wol(dev, port, wol);
+}
+
+static int ksz_set_wol(struct dsa_switch *ds, int port,
+ struct ethtool_wolinfo *wol)
+{
+ struct ksz_device *dev = ds->priv;
+
+ if (dev->dev_ops->set_wol)
+ return dev->dev_ops->set_wol(dev, port, wol);
+
+ return -EOPNOTSUPP;
+}
+
+static int ksz_port_set_mac_address(struct dsa_switch *ds, int port,
+ const unsigned char *addr)
+{
+ struct dsa_port *dp = dsa_to_port(ds, port);
+ struct ethtool_wolinfo wol;
+
+ if (dp->hsr_dev) {
+ dev_err(ds->dev,
+ "Cannot change MAC address on port %d with active HSR offload\n",
+ port);
+ return -EBUSY;
+ }
+
+ ksz_get_wol(ds, dp->index, &wol);
+ if (wol.wolopts & WAKE_MAGIC) {
+ dev_err(ds->dev,
+ "Cannot change MAC address on port %d with active Wake on Magic Packet\n",
+ port);
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+/**
+ * ksz_is_port_mac_global_usable - Check if the MAC address on a given port
+ * can be used as a global address.
+ * @ds: Pointer to the DSA switch structure.
+ * @port: The port number on which the MAC address is to be checked.
+ *
+ * This function examines the MAC address set on the specified port and
+ * determines if it can be used as a global address for the switch.
+ *
+ * Return: true if the port's MAC address can be used as a global address, false
+ * otherwise.
+ */
+bool ksz_is_port_mac_global_usable(struct dsa_switch *ds, int port)
+{
+ struct net_device *user = dsa_to_port(ds, port)->user;
+ const unsigned char *addr = user->dev_addr;
+ struct ksz_switch_macaddr *switch_macaddr;
+ struct ksz_device *dev = ds->priv;
+
+ ASSERT_RTNL();
+
+ switch_macaddr = dev->switch_macaddr;
+ if (switch_macaddr && !ether_addr_equal(switch_macaddr->addr, addr))
+ return false;
+
+ return true;
+}
+
+/**
+ * ksz_switch_macaddr_get - Program the switch's MAC address register.
+ * @ds: DSA switch instance.
+ * @port: Port number.
+ * @extack: Netlink extended acknowledgment.
+ *
+ * This function programs the switch's MAC address register with the MAC address
+ * of the requesting user port. This single address is used by the switch for
+ * multiple features like HSR self-address filtering and WoL. Other user ports
+ * can share ownership of this address as long as their MAC address is the same.
+ * The MAC addresses of user ports must not change while they have ownership of
+ * the switch MAC address.
+ *
+ * Return: 0 on success, or other error codes on failure.
+ */
+int ksz_switch_macaddr_get(struct dsa_switch *ds, int port,
+ struct netlink_ext_ack *extack)
+{
+ struct net_device *user = dsa_to_port(ds, port)->user;
+ const unsigned char *addr = user->dev_addr;
+ struct ksz_switch_macaddr *switch_macaddr;
+ struct ksz_device *dev = ds->priv;
+ const u16 *regs = dev->info->regs;
+ int i, ret;
+
+ /* Make sure concurrent MAC address changes are blocked */
+ ASSERT_RTNL();
+
+ switch_macaddr = dev->switch_macaddr;
+ if (switch_macaddr) {
+ if (!ether_addr_equal(switch_macaddr->addr, addr)) {
+ NL_SET_ERR_MSG_FMT_MOD(extack,
+ "Switch already configured for MAC address %pM",
+ switch_macaddr->addr);
+ return -EBUSY;
+ }
+
+ refcount_inc(&switch_macaddr->refcount);
+ return 0;
+ }
+
+ switch_macaddr = kzalloc(sizeof(*switch_macaddr), GFP_KERNEL);
+ if (!switch_macaddr)
+ return -ENOMEM;
+
+ ether_addr_copy(switch_macaddr->addr, addr);
+ refcount_set(&switch_macaddr->refcount, 1);
+ dev->switch_macaddr = switch_macaddr;
+
+ /* Program the switch MAC address to hardware */
+ for (i = 0; i < ETH_ALEN; i++) {
+ ret = ksz_write8(dev, regs[REG_SW_MAC_ADDR] + i, addr[i]);
+ if (ret)
+ goto macaddr_drop;
+ }
+
+ return 0;
+
+macaddr_drop:
+ dev->switch_macaddr = NULL;
+ refcount_set(&switch_macaddr->refcount, 0);
+ kfree(switch_macaddr);
+
+ return ret;
+}
+
+void ksz_switch_macaddr_put(struct dsa_switch *ds)
+{
+ struct ksz_switch_macaddr *switch_macaddr;
+ struct ksz_device *dev = ds->priv;
+ const u16 *regs = dev->info->regs;
+ int i;
+
+ /* Make sure concurrent MAC address changes are blocked */
+ ASSERT_RTNL();
+
+ switch_macaddr = dev->switch_macaddr;
+ if (!refcount_dec_and_test(&switch_macaddr->refcount))
+ return;
+
+ for (i = 0; i < ETH_ALEN; i++)
+ ksz_write8(dev, regs[REG_SW_MAC_ADDR] + i, 0);
+
+ dev->switch_macaddr = NULL;
+ kfree(switch_macaddr);
+}
+
+static int ksz_hsr_join(struct dsa_switch *ds, int port, struct net_device *hsr,
+ struct netlink_ext_ack *extack)
+{
+ struct ksz_device *dev = ds->priv;
+ enum hsr_version ver;
+ int ret;
+
+ ret = hsr_get_version(hsr, &ver);
+ if (ret)
+ return ret;
+
+ if (dev->chip_id != KSZ9477_CHIP_ID) {
+ NL_SET_ERR_MSG_MOD(extack, "Chip does not support HSR offload");
+ return -EOPNOTSUPP;
+ }
+
+ /* KSZ9477 can support HW offloading of only 1 HSR device */
+ if (dev->hsr_dev && hsr != dev->hsr_dev) {
+ NL_SET_ERR_MSG_MOD(extack, "Offload supported for a single HSR");
+ return -EOPNOTSUPP;
+ }
+
+ /* KSZ9477 only supports HSR v0 and v1 */
+ if (!(ver == HSR_V0 || ver == HSR_V1)) {
+ NL_SET_ERR_MSG_MOD(extack, "Only HSR v0 and v1 supported");
+ return -EOPNOTSUPP;
+ }
+
+ /* Self MAC address filtering, to avoid frames traversing
+ * the HSR ring more than once.
+ */
+ ret = ksz_switch_macaddr_get(ds, port, extack);
+ if (ret)
+ return ret;
+
+ ksz9477_hsr_join(ds, port, hsr);
+ dev->hsr_dev = hsr;
+ dev->hsr_ports |= BIT(port);
+
+ return 0;
+}
+
+static int ksz_hsr_leave(struct dsa_switch *ds, int port,
+ struct net_device *hsr)
+{
+ struct ksz_device *dev = ds->priv;
+
+ WARN_ON(dev->chip_id != KSZ9477_CHIP_ID);
+
+ ksz9477_hsr_leave(ds, port, hsr);
+ dev->hsr_ports &= ~BIT(port);
+ if (!dev->hsr_ports)
+ dev->hsr_dev = NULL;
+
+ ksz_switch_macaddr_put(ds);
+
+ return 0;
+}
+
static const struct dsa_switch_ops ksz_switch_ops = {
.get_tag_protocol = ksz_get_tag_protocol,
.connect_tag_protocol = ksz_connect_tag_protocol,
@@ -3558,6 +3783,9 @@ static const struct dsa_switch_ops ksz_switch_ops = {
.get_sset_count = ksz_sset_count,
.port_bridge_join = ksz_port_bridge_join,
.port_bridge_leave = ksz_port_bridge_leave,
+ .port_hsr_join = ksz_hsr_join,
+ .port_hsr_leave = ksz_hsr_leave,
+ .port_set_mac_address = ksz_port_set_mac_address,
.port_stp_state_set = ksz_port_stp_state_set,
.port_teardown = ksz_port_teardown,
.port_pre_bridge_flags = ksz_port_pre_bridge_flags,
@@ -3577,6 +3805,8 @@ static const struct dsa_switch_ops ksz_switch_ops = {
.get_pause_stats = ksz_get_pause_stats,
.port_change_mtu = ksz_change_mtu,
.port_max_mtu = ksz_max_mtu,
+ .get_wol = ksz_get_wol,
+ .set_wol = ksz_set_wol,
.get_ts_info = ksz_get_ts_info,
.port_hwtstamp_get = ksz_hwtstamp_get,
.port_hwtstamp_set = ksz_hwtstamp_set,
@@ -3616,6 +3846,30 @@ struct ksz_device *ksz_switch_alloc(struct device *base, void *priv)
}
EXPORT_SYMBOL(ksz_switch_alloc);
+/**
+ * ksz_switch_shutdown - Shutdown routine for the switch device.
+ * @dev: The switch device structure.
+ *
+ * This function is responsible for initiating a shutdown sequence for the
+ * switch device. It invokes the reset operation defined in the device
+ * operations, if available, to reset the switch. Subsequently, it calls the
+ * DSA framework's shutdown function to ensure a proper shutdown of the DSA
+ * switch.
+ */
+void ksz_switch_shutdown(struct ksz_device *dev)
+{
+ bool wol_enabled = false;
+
+ if (dev->dev_ops->wol_pre_shutdown)
+ dev->dev_ops->wol_pre_shutdown(dev, &wol_enabled);
+
+ if (dev->dev_ops->reset && !wol_enabled)
+ dev->dev_ops->reset(dev);
+
+ dsa_switch_shutdown(dev->ds);
+}
+EXPORT_SYMBOL(ksz_switch_shutdown);
+
static void ksz_parse_rgmii_delay(struct ksz_device *dev, int port_num,
struct device_node *port_dn)
{
@@ -4009,6 +4263,9 @@ int ksz_switch_register(struct ksz_device *dev)
dev_err(dev->dev, "inconsistent synclko settings\n");
return -EINVAL;
}
+
+ dev->wakeup_source = of_property_read_bool(dev->dev->of_node,
+ "wakeup-source");
}
ret = dsa_register_switch(dev->ds);