[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20240717193725.469192-3-vtpieter@gmail.com>
Date: Wed, 17 Jul 2024 21:37:23 +0200
From: vtpieter@...il.com
To: devicetree@...r.kernel.org,
woojung.huh@...rochip.com,
UNGLinuxDriver@...rochip.com,
netdev@...r.kernel.org
Cc: o.rempel@...gutronix.de,
Pieter Van Trappen <pieter.van.trappen@...n.ch>
Subject: [PATCH 2/4] net: dsa: microchip: ksz8795: add Wake on LAN support
From: Pieter Van Trappen <pieter.van.trappen@...n.ch>
Add WoL support for KSZ8795 family of switches. This code was tested
with a KSZ8794 chip.
KSZ8795 family of switches supports multiple PHY events:
- wake on Link Up
- wake on Energy Detect.
- wake on Magic Packet.
Since current UAPI can't differentiate between Link Up and Energy
Detect, map these to WAKE_PHY.
Strongly based on existing KSZ9477 code but there's too many
differences, such as the indirect register access, to generalize this
code to ksz_common. Some registers names have been changed to increase
standardization between those code bases.
Signed-off-by: Pieter Van Trappen <pieter.van.trappen@...n.ch>
---
drivers/net/dsa/microchip/ksz8.h | 5 +
drivers/net/dsa/microchip/ksz8795.c | 220 ++++++++++++++++++++++++
drivers/net/dsa/microchip/ksz8795_reg.h | 13 +-
drivers/net/dsa/microchip/ksz_common.c | 5 +
drivers/net/dsa/microchip/ksz_common.h | 1 +
5 files changed, 239 insertions(+), 5 deletions(-)
diff --git a/drivers/net/dsa/microchip/ksz8.h b/drivers/net/dsa/microchip/ksz8.h
index ae43077e76c3..4cece61181e9 100644
--- a/drivers/net/dsa/microchip/ksz8.h
+++ b/drivers/net/dsa/microchip/ksz8.h
@@ -59,5 +59,10 @@ void ksz8_phylink_mac_link_up(struct phylink_config *config,
phy_interface_t interface, int speed, int duplex,
bool tx_pause, bool rx_pause);
int ksz8_all_queues_split(struct ksz_device *dev, int queues);
+void ksz8_get_wol(struct ksz_device *dev, int port,
+ struct ethtool_wolinfo *wol);
+int ksz8_set_wol(struct ksz_device *dev, int port,
+ struct ethtool_wolinfo *wol);
+void ksz8_wol_pre_shutdown(struct ksz_device *dev, bool *wol_enabled);
#endif
diff --git a/drivers/net/dsa/microchip/ksz8795.c b/drivers/net/dsa/microchip/ksz8795.c
index d27b9c36d73f..49081e9a8cb0 100644
--- a/drivers/net/dsa/microchip/ksz8795.c
+++ b/drivers/net/dsa/microchip/ksz8795.c
@@ -58,6 +58,26 @@ static int ksz8_ind_write8(struct ksz_device *dev, u8 table, u16 addr, u8 data)
return ret;
}
+static int ksz8_ind_read8(struct ksz_device *dev, u8 table, u16 addr, u8 *val)
+{
+ const u16 *regs;
+ u16 ctrl_addr;
+ int ret = 0;
+
+ regs = dev->info->regs;
+
+ mutex_lock(&dev->alu_mutex);
+
+ ctrl_addr = IND_ACC_TABLE(table | TABLE_READ) | addr;
+ ret = ksz_write16(dev, regs[REG_IND_CTRL_0], ctrl_addr);
+ if (!ret)
+ ret = ksz_read8(dev, regs[REG_IND_BYTE], val);
+
+ mutex_unlock(&dev->alu_mutex);
+
+ return ret;
+}
+
int ksz8_reset_switch(struct ksz_device *dev)
{
if (ksz_is_ksz88x3(dev)) {
@@ -127,6 +147,200 @@ int ksz8_change_mtu(struct ksz_device *dev, int port, int mtu)
return -EOPNOTSUPP;
}
+/**
+ * ksz8_handle_wake_reason - Handle wake reason on a specified port.
+ * @dev: The device structure.
+ * @port: The port number.
+ *
+ * This function reads the PME (Power Management Event) status register of a
+ * specified port to determine the wake reason. If there is no wake event, it
+ * returns early. Otherwise, it logs the wake reason which could be due to a
+ * "Magic Packet", "Link Up", or "Energy Detect" event. The PME status register
+ * is then cleared to acknowledge the handling of the wake event; followed by
+ * clearing the global Interrupt Status Register.
+ *
+ * Return: 0 on success, or an error code on failure.
+ */
+static int ksz8_handle_wake_reason(struct ksz_device *dev, int port)
+{
+ u8 pme_status;
+ int ret;
+
+ ret = ksz8_ind_read8(dev, TABLE_PME_PORT(port), REG_IND_PORT_PME_STATUS, &pme_status);
+ if (ret)
+ return ret;
+
+ if (!pme_status)
+ return 0;
+
+ dev_dbg(dev->dev, "Wake event on port %d due to:%s%s%s\n", port,
+ pme_status & PME_WOL_MAGICPKT ? " \"Magic Packet\"" : "",
+ pme_status & PME_WOL_LINKUP ? " \"Link Up\"" : "",
+ pme_status & PME_WOL_ENERGY ? " \"Energy detect\"" : "");
+
+ ret = ksz8_ind_write8(dev, TABLE_PME_PORT(port), REG_IND_PORT_PME_STATUS, pme_status);
+ if (ret)
+ return ret;
+
+ ksz_read8(dev, REG_INT_STATUS, &pme_status);
+ return ksz_write8(dev, REG_INT_STATUS, pme_status && INT_PME);
+}
+
+/**
+ * ksz8_get_wol - Get Wake-on-LAN settings for a specified port.
+ * @dev: The device structure.
+ * @port: The port number.
+ * @wol: Pointer to ethtool Wake-on-LAN settings structure.
+ *
+ * This function checks the PME 'wakeup-source' property from the
+ * device tree. If enabled, it sets the supported and active WoL
+ * flags.
+ */
+void ksz8_get_wol(struct ksz_device *dev, int port,
+ struct ethtool_wolinfo *wol)
+{
+ u8 pme_ctrl;
+ int ret;
+
+ if (!dev->wakeup_source)
+ return;
+
+ wol->supported = WAKE_PHY;
+
+ /* Check if the current MAC address on this port can be set
+ * as global for WAKE_MAGIC support. The result may vary
+ * dynamically based on other ports configurations.
+ */
+ if (ksz_is_port_mac_global_usable(dev->ds, port))
+ wol->supported |= WAKE_MAGIC;
+
+ ret = ksz8_ind_read8(dev, TABLE_PME_PORT(port), REG_IND_PORT_PME_CTRL, &pme_ctrl);
+ if (ret)
+ return;
+
+ if (pme_ctrl & PME_WOL_MAGICPKT)
+ wol->wolopts |= WAKE_MAGIC;
+ if (pme_ctrl & (PME_WOL_LINKUP | PME_WOL_ENERGY))
+ wol->wolopts |= WAKE_PHY;
+}
+
+/**
+ * ksz8_set_wol - Set Wake-on-LAN settings for a specified port.
+ * @dev: The device structure.
+ * @port: The port number.
+ * @wol: Pointer to ethtool Wake-on-LAN settings structure.
+ *
+ * This function configures Wake-on-LAN (WoL) settings for a specified port.
+ * It validates the provided WoL options, checks if PME is enabled via the
+ * switch's device tree property, clears any previous wake reasons,
+ * and sets the Magic Packet flag in the port's PME control register if
+ * specified.
+ *
+ * Return: 0 on success, or other error codes on failure.
+ */
+int ksz8_set_wol(struct ksz_device *dev, int port,
+ struct ethtool_wolinfo *wol)
+{
+ u8 pme_ctrl = 0, pme_ctrl_old = 0;
+ bool magic_switched_off;
+ bool magic_switched_on;
+ int ret;
+
+ if (wol->wolopts & ~(WAKE_PHY | WAKE_MAGIC))
+ return -EINVAL;
+
+ if (!dev->wakeup_source)
+ return -EOPNOTSUPP;
+
+ ret = ksz8_handle_wake_reason(dev, port);
+ if (ret)
+ return ret;
+
+ if (wol->wolopts & WAKE_MAGIC)
+ pme_ctrl |= PME_WOL_MAGICPKT;
+ if (wol->wolopts & WAKE_PHY)
+ pme_ctrl |= PME_WOL_LINKUP | PME_WOL_ENERGY;
+
+ ret = ksz8_ind_read8(dev, TABLE_PME_PORT(port), REG_IND_PORT_PME_CTRL, &pme_ctrl_old);
+ if (ret)
+ return ret;
+
+ if (pme_ctrl_old == pme_ctrl)
+ return 0;
+
+ magic_switched_off = (pme_ctrl_old & PME_WOL_MAGICPKT) &&
+ !(pme_ctrl & PME_WOL_MAGICPKT);
+ magic_switched_on = !(pme_ctrl_old & PME_WOL_MAGICPKT) &&
+ (pme_ctrl & PME_WOL_MAGICPKT);
+
+ /* To keep reference count of MAC address, we should do this
+ * operation only on change of WOL settings.
+ */
+ if (magic_switched_on) {
+ ret = ksz_switch_macaddr_get(dev->ds, port, NULL);
+ if (ret)
+ return ret;
+ } else if (magic_switched_off) {
+ ksz_switch_macaddr_put(dev->ds);
+ }
+
+ ret = ksz8_ind_write8(dev, TABLE_PME_PORT(port), REG_IND_PORT_PME_CTRL, pme_ctrl);
+ if (ret) {
+ if (magic_switched_on)
+ ksz_switch_macaddr_put(dev->ds);
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
+ * ksz9477_wol_pre_shutdown - Prepares the switch device for shutdown while
+ * considering Wake-on-LAN (WoL) settings.
+ * @dev: The switch device structure.
+ * @wol_enabled: Pointer to a boolean which will be set to true if WoL is
+ * enabled on any port.
+ *
+ * This function prepares the switch device for a safe shutdown while taking
+ * into account the Wake-on-LAN (WoL) settings on the user ports. It updates
+ * the wol_enabled flag accordingly to reflect whether WoL is active on any
+ * port. It also sets the PME output pin enable with the polarity specified
+ * through the device-tree.
+ */
+void ksz8_wol_pre_shutdown(struct ksz_device *dev, bool *wol_enabled)
+{
+ struct dsa_port *dp;
+ int ret;
+ u8 pme_pin_en = SW_PME_OUTPUT_ENABLE;
+
+ *wol_enabled = false;
+
+ if (!dev->wakeup_source)
+ return;
+
+ dsa_switch_for_each_user_port(dp, dev->ds) {
+ u8 pme_ctrl = 0;
+
+ ret = ksz8_ind_read8(dev, TABLE_PME_PORT(dp->index),
+ REG_IND_PORT_PME_CTRL, &pme_ctrl);
+ if (!ret && pme_ctrl)
+ *wol_enabled = true;
+
+ /* make sure there are no pending wake events which would
+ * prevent the device from going to sleep/shutdown.
+ */
+ ksz8_handle_wake_reason(dev, dp->index);
+ }
+
+ /* Now we are save to enable PME pin. */
+ if (*wol_enabled) {
+ if (dev->pme_active_high)
+ pme_pin_en |= SW_PME_ACTIVE_HIGH;
+ ksz8_ind_write8(dev, TABLE_PME, REG_IND_GLOB_PME_CTRL, pme_pin_en);
+ ksz_write8(dev, REG_INT_ENABLE, INT_PME);
+ }
+}
+
static int ksz8_port_queue_split(struct ksz_device *dev, int port, int queues)
{
u8 mask_4q, mask_2q;
@@ -1829,6 +2043,12 @@ int ksz8_setup(struct dsa_switch *ds)
for (i = 0; i < (dev->info->num_vlans / 4); i++)
ksz8_r_vlan_entries(dev, i);
+ /* Make sure PME (WoL) is not enabled. If requested, it will be
+ * enabled by ksz8_wol_pre_shutdown(). Otherwise, some PMICs do not
+ * like PME events changes before shutdown.
+ */
+ ksz_write8(dev, REG_INT_ENABLE, 0);
+
return ksz8_handle_global_errata(ds);
}
diff --git a/drivers/net/dsa/microchip/ksz8795_reg.h b/drivers/net/dsa/microchip/ksz8795_reg.h
index 69566a5d9cda..884e899145dd 100644
--- a/drivers/net/dsa/microchip/ksz8795_reg.h
+++ b/drivers/net/dsa/microchip/ksz8795_reg.h
@@ -337,6 +337,7 @@
#define TABLE_EEE (TABLE_EEE_V << TABLE_EXT_SELECT_S)
#define TABLE_ACL (TABLE_ACL_V << TABLE_EXT_SELECT_S)
#define TABLE_PME (TABLE_PME_V << TABLE_EXT_SELECT_S)
+#define TABLE_PME_PORT(port) (TABLE_PME | (u8)((port) + 1))
#define TABLE_LINK_MD (TABLE_LINK_MD << TABLE_EXT_SELECT_S)
#define TABLE_READ BIT(4)
#define TABLE_SELECT_S 2
@@ -359,8 +360,6 @@
#define REG_IND_DATA_1 0x77
#define REG_IND_DATA_0 0x78
-#define REG_IND_DATA_PME_EEE_ACL 0xA0
-
#define REG_INT_STATUS 0x7C
#define REG_INT_ENABLE 0x7D
@@ -589,12 +588,16 @@
/* PME */
+#define REG_IND_GLOB_PME_CTRL 0x3
+#define REG_IND_PORT_PME_STATUS 0x3
+#define REG_IND_PORT_PME_CTRL 0x7
+
#define SW_PME_OUTPUT_ENABLE BIT(1)
#define SW_PME_ACTIVE_HIGH BIT(0)
-#define PORT_MAGIC_PACKET_DETECT BIT(2)
-#define PORT_LINK_UP_DETECT BIT(1)
-#define PORT_ENERGY_DETECT BIT(0)
+#define PME_WOL_MAGICPKT BIT(2)
+#define PME_WOL_LINKUP BIT(1)
+#define PME_WOL_ENERGY BIT(0)
/* ACL */
diff --git a/drivers/net/dsa/microchip/ksz_common.c b/drivers/net/dsa/microchip/ksz_common.c
index b074b4bb0629..61403898c1f4 100644
--- a/drivers/net/dsa/microchip/ksz_common.c
+++ b/drivers/net/dsa/microchip/ksz_common.c
@@ -307,6 +307,9 @@ static const struct ksz_dev_ops ksz8_dev_ops = {
.init = ksz8_switch_init,
.exit = ksz8_switch_exit,
.change_mtu = ksz8_change_mtu,
+ .get_wol = ksz8_get_wol,
+ .set_wol = ksz8_set_wol,
+ .wol_pre_shutdown = ksz8_wol_pre_shutdown,
};
static void ksz9477_phylink_mac_link_up(struct phylink_config *config,
@@ -4459,6 +4462,8 @@ int ksz_switch_register(struct ksz_device *dev)
dev->wakeup_source = of_property_read_bool(dev->dev->of_node,
"wakeup-source");
+ dev->pme_active_high = of_property_read_bool(dev->dev->of_node,
+ "microchip,pme-active-high");
}
ret = dsa_register_switch(dev->ds);
diff --git a/drivers/net/dsa/microchip/ksz_common.h b/drivers/net/dsa/microchip/ksz_common.h
index 5f0a628b9849..1d7f2f18ee1f 100644
--- a/drivers/net/dsa/microchip/ksz_common.h
+++ b/drivers/net/dsa/microchip/ksz_common.h
@@ -174,6 +174,7 @@ struct ksz_device {
bool synclko_125;
bool synclko_disable;
bool wakeup_source;
+ bool pme_active_high;
struct vlan_table *vlan_cache;
--
2.43.0
Powered by blists - more mailing lists