Commit 7eef636e authored by David S. Miller's avatar David S. Miller
Browse files

Merge branch 'broadcom-phy-wol'



Florian Fainelli says:

====================
Support for Wake-on-LAN for Broadcom PHYs

This patch series adds support for Wake-on-LAN to the Broadcom PHY
driver. Specifically the BCM54210E/B50212E are capable of supporting
Wake-on-LAN using an external pin typically wired up to a system's GPIO.

These PHY operate a programmable Ethernet MAC destination address
comparator which will fire up an interrupt whenever a match is received.
Because of that, it was necessary to introduce patch #1 which allows the
PHY driver's ->suspend() routine to be called unconditionally. This is
necessary in our case because we need a hook point into the device
suspend/resume flow to enable the wake-up interrupt as late as possible.

Patch #2 adds support for the Broadcom PHY library and driver for
Wake-on-LAN proper with the WAKE_UCAST, WAKE_MCAST, WAKE_BCAST,
WAKE_MAGIC and WAKE_MAGICSECURE. Note that WAKE_FILTER is supportable,
however this will require further discussions and be submitted as a RFC
series later on.

Patch #3 updates the GENET driver to defer to the PHY for Wake-on-LAN if
the PHY supports it, thus allowing the MAC to be powered down to
conserve power.

Changes in v3:

- collected Reviewed-by tags
- explicitly use return 0 in bcm54xx_phy_probe() (Paolo)

Changes in v2:

- introduce PHY_ALWAYS_CALL_SUSPEND and only have the Broadcom PHY
  driver set this flag to minimize changes to the suspend flow to only
  drivers that need it

- corrected possibly uninitialized variable in bcm54xx_set_wakeup_irq
  (Simon)
====================

Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents ba79e9a7 7e400ff3
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -42,6 +42,12 @@ void bcmgenet_get_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
	struct bcmgenet_priv *priv = netdev_priv(dev);
	struct device *kdev = &priv->pdev->dev;

	if (dev->phydev) {
		phy_ethtool_get_wol(dev->phydev, wol);
		if (wol->supported)
			return;
	}

	if (!device_can_wakeup(kdev)) {
		wol->supported = 0;
		wol->wolopts = 0;
@@ -63,6 +69,14 @@ int bcmgenet_set_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
{
	struct bcmgenet_priv *priv = netdev_priv(dev);
	struct device *kdev = &priv->pdev->dev;
	int ret;

	/* Try Wake-on-LAN from the PHY first */
	if (dev->phydev) {
		ret = phy_ethtool_set_wol(dev->phydev, wol);
		if (ret != -EOPNOTSUPP)
			return ret;
	}

	if (!device_can_wakeup(kdev))
		return -ENOTSUPP;
+212 −0
Original line number Diff line number Diff line
@@ -6,12 +6,14 @@
#include "bcm-phy-lib.h"
#include <linux/bitfield.h>
#include <linux/brcmphy.h>
#include <linux/etherdevice.h>
#include <linux/export.h>
#include <linux/mdio.h>
#include <linux/module.h>
#include <linux/phy.h>
#include <linux/ethtool.h>
#include <linux/ethtool_netlink.h>
#include <linux/netdevice.h>

#define MII_BCM_CHANNEL_WIDTH     0x2000
#define BCM_CL45VEN_EEE_ADV       0x3c
@@ -816,6 +818,216 @@ int bcm_phy_cable_test_get_status_rdb(struct phy_device *phydev,
}
EXPORT_SYMBOL_GPL(bcm_phy_cable_test_get_status_rdb);

#define BCM54XX_WOL_SUPPORTED_MASK	(WAKE_UCAST | \
					 WAKE_MCAST | \
					 WAKE_BCAST | \
					 WAKE_MAGIC | \
					 WAKE_MAGICSECURE)

int bcm_phy_set_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol)
{
	struct net_device *ndev = phydev->attached_dev;
	u8 da[ETH_ALEN], mask[ETH_ALEN];
	unsigned int i;
	u16 ctl;
	int ret;

	/* Allow a MAC driver to play through its own Wake-on-LAN
	 * implementation
	 */
	if (wol->wolopts & ~BCM54XX_WOL_SUPPORTED_MASK)
		return -EOPNOTSUPP;

	/* The PHY supports passwords of 4, 6 and 8 bytes in size, but Linux's
	 * ethtool only supports 6, for now.
	 */
	BUILD_BUG_ON(sizeof(wol->sopass) != ETH_ALEN);

	/* Clear previous interrupts */
	ret = bcm_phy_read_exp(phydev, BCM54XX_WOL_INT_STATUS);
	if (ret < 0)
		return ret;

	ret = bcm_phy_read_exp(phydev, BCM54XX_WOL_MAIN_CTL);
	if (ret < 0)
		return ret;

	ctl = ret;

	if (!wol->wolopts) {
		if (phy_interrupt_is_valid(phydev))
			disable_irq_wake(phydev->irq);

		/* Leave all interrupts disabled */
		ret = bcm_phy_write_exp(phydev, BCM54XX_WOL_INT_MASK,
					BCM54XX_WOL_ALL_INTRS);
		if (ret < 0)
			return ret;

		/* Disable the global Wake-on-LAN enable bit */
		ctl &= ~BCM54XX_WOL_EN;

		return bcm_phy_write_exp(phydev, BCM54XX_WOL_MAIN_CTL, ctl);
	}

	/* Clear the previously configured mode and mask mode for Wake-on-LAN */
	ctl &= ~(BCM54XX_WOL_MODE_MASK << BCM54XX_WOL_MODE_SHIFT);
	ctl &= ~(BCM54XX_WOL_MASK_MODE_MASK << BCM54XX_WOL_MASK_MODE_SHIFT);
	ctl &= ~BCM54XX_WOL_DIR_PKT_EN;
	ctl &= ~(BCM54XX_WOL_SECKEY_OPT_MASK << BCM54XX_WOL_SECKEY_OPT_SHIFT);

	/* When using WAKE_MAGIC, we program the magic pattern filter to match
	 * the device's MAC address and we accept any MAC DA in the Ethernet
	 * frame.
	 *
	 * When using WAKE_UCAST, WAKE_BCAST or WAKE_MCAST, we program the
	 * following:
	 * - WAKE_UCAST -> MAC DA is the device's MAC with a perfect match
	 * - WAKE_MCAST -> MAC DA is X1:XX:XX:XX:XX:XX where XX is don't care
	 * - WAKE_BCAST -> MAC DA is FF:FF:FF:FF:FF:FF with a perfect match
	 *
	 * Note that the Broadcast MAC DA is inherently going to match the
	 * multicast pattern being matched.
	 */
	memset(mask, 0, sizeof(mask));

	if (wol->wolopts & WAKE_MCAST) {
		memset(da, 0, sizeof(da));
		memset(mask, 0xff, sizeof(mask));
		da[0] = 0x01;
		mask[0] = ~da[0];
	} else {
		if (wol->wolopts & WAKE_UCAST) {
			ether_addr_copy(da, ndev->dev_addr);
		} else if (wol->wolopts & WAKE_BCAST) {
			eth_broadcast_addr(da);
		} else if (wol->wolopts & WAKE_MAGICSECURE) {
			ether_addr_copy(da, wol->sopass);
		} else if (wol->wolopts & WAKE_MAGIC) {
			memset(da, 0, sizeof(da));
			memset(mask, 0xff, sizeof(mask));
		}
	}

	for (i = 0; i < ETH_ALEN / 2; i++) {
		if (wol->wolopts & (WAKE_MAGIC | WAKE_MAGICSECURE)) {
			ret = bcm_phy_write_exp(phydev,
						BCM54XX_WOL_MPD_DATA1(2 - i),
						ndev->dev_addr[i * 2] << 8 |
						ndev->dev_addr[i * 2 + 1]);
			if (ret < 0)
				return ret;
		}

		ret = bcm_phy_write_exp(phydev, BCM54XX_WOL_MPD_DATA2(2 - i),
					da[i * 2] << 8 | da[i * 2 + 1]);
		if (ret < 0)
			return ret;

		ret = bcm_phy_write_exp(phydev, BCM54XX_WOL_MASK(2 - i),
					mask[i * 2] << 8 | mask[i * 2 + 1]);
		if (ret)
			return ret;
	}

	if (wol->wolopts & WAKE_MAGICSECURE) {
		ctl |= BCM54XX_WOL_SECKEY_OPT_6B <<
		       BCM54XX_WOL_SECKEY_OPT_SHIFT;
		ctl |= BCM54XX_WOL_MODE_SINGLE_MPDSEC << BCM54XX_WOL_MODE_SHIFT;
		ctl |= BCM54XX_WOL_MASK_MODE_DA_FF <<
		       BCM54XX_WOL_MASK_MODE_SHIFT;
	} else {
		if (wol->wolopts & WAKE_MAGIC)
			ctl |= BCM54XX_WOL_MODE_SINGLE_MPD;
		else
			ctl |= BCM54XX_WOL_DIR_PKT_EN;
		ctl |= BCM54XX_WOL_MASK_MODE_DA_ONLY <<
		       BCM54XX_WOL_MASK_MODE_SHIFT;
	}

	/* Globally enable Wake-on-LAN */
	ctl |= BCM54XX_WOL_EN | BCM54XX_WOL_CRC_CHK;

	ret = bcm_phy_write_exp(phydev, BCM54XX_WOL_MAIN_CTL, ctl);
	if (ret < 0)
		return ret;

	/* Enable WOL interrupt on LED4 */
	ret = bcm_phy_read_exp(phydev, BCM54XX_TOP_MISC_LED_CTL);
	if (ret < 0)
		return ret;

	ret |= BCM54XX_LED4_SEL_INTR;
	ret = bcm_phy_write_exp(phydev, BCM54XX_TOP_MISC_LED_CTL, ret);
	if (ret < 0)
		return ret;

	/* Enable all Wake-on-LAN interrupt sources */
	ret = bcm_phy_write_exp(phydev, BCM54XX_WOL_INT_MASK, 0);
	if (ret < 0)
		return ret;

	if (phy_interrupt_is_valid(phydev))
		enable_irq_wake(phydev->irq);

	return 0;
}
EXPORT_SYMBOL_GPL(bcm_phy_set_wol);

void bcm_phy_get_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol)
{
	struct net_device *ndev = phydev->attached_dev;
	u8 da[ETH_ALEN];
	unsigned int i;
	int ret;
	u16 ctl;

	wol->supported = BCM54XX_WOL_SUPPORTED_MASK;
	wol->wolopts = 0;

	ret = bcm_phy_read_exp(phydev, BCM54XX_WOL_MAIN_CTL);
	if (ret < 0)
		return;

	ctl = ret;

	if (!(ctl & BCM54XX_WOL_EN))
		return;

	for (i = 0; i < sizeof(da) / 2; i++) {
		ret = bcm_phy_read_exp(phydev,
				       BCM54XX_WOL_MPD_DATA2(2 - i));
		if (ret < 0)
			return;

		da[i * 2] = ret >> 8;
		da[i * 2 + 1] = ret & 0xff;
	}

	if (ctl & BCM54XX_WOL_DIR_PKT_EN) {
		if (is_broadcast_ether_addr(da))
			wol->wolopts |= WAKE_BCAST;
		else if (is_multicast_ether_addr(da))
			wol->wolopts |= WAKE_MCAST;
		else if (ether_addr_equal(da, ndev->dev_addr))
			wol->wolopts |= WAKE_UCAST;
	} else {
		ctl = (ctl >> BCM54XX_WOL_MODE_SHIFT) & BCM54XX_WOL_MODE_MASK;
		switch (ctl) {
		case BCM54XX_WOL_MODE_SINGLE_MPD:
			wol->wolopts |= WAKE_MAGIC;
			break;
		case BCM54XX_WOL_MODE_SINGLE_MPDSEC:
			wol->wolopts |= WAKE_MAGICSECURE;
			memcpy(wol->sopass, da, sizeof(da));
			break;
		default:
			break;
		}
	}
}
EXPORT_SYMBOL_GPL(bcm_phy_get_wol);

MODULE_DESCRIPTION("Broadcom PHY Library");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Broadcom Corporation");
+5 −0
Original line number Diff line number Diff line
@@ -9,6 +9,8 @@
#include <linux/brcmphy.h>
#include <linux/phy.h>

struct ethtool_wolinfo;

/* 28nm only register definitions */
#define MISC_ADDR(base, channel)	base, channel

@@ -111,4 +113,7 @@ static inline void bcm_ptp_stop(struct bcm_ptp_private *priv)
}
#endif

int bcm_phy_set_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol);
void bcm_phy_get_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol);

#endif /* _LINUX_BCM_PHY_LIB_H */
+123 −3
Original line number Diff line number Diff line
@@ -14,8 +14,12 @@
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/phy.h>
#include <linux/pm_wakeup.h>
#include <linux/brcmphy.h>
#include <linux/of.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/gpio/consumer.h>

#define BRCM_PHY_MODEL(phydev) \
	((phydev)->drv->phy_id & (phydev)->drv->phy_id_mask)
@@ -30,8 +34,17 @@ MODULE_LICENSE("GPL");
struct bcm54xx_phy_priv {
	u64	*stats;
	struct bcm_ptp_private *ptp;
	int	wake_irq;
	bool	wake_irq_enabled;
};

static bool bcm54xx_phy_can_wakeup(struct phy_device *phydev)
{
	struct bcm54xx_phy_priv *priv = phydev->priv;

	return phy_interrupt_is_valid(phydev) || priv->wake_irq >= 0;
}

static int bcm54xx_config_clock_delay(struct phy_device *phydev)
{
	int rc, val;
@@ -413,6 +426,16 @@ static int bcm54xx_config_init(struct phy_device *phydev)

	bcm54xx_ptp_config_init(phydev);

	/* Acknowledge any left over interrupt and charge the device for
	 * wake-up.
	 */
	err = bcm_phy_read_exp(phydev, BCM54XX_WOL_INT_STATUS);
	if (err < 0)
		return err;

	if (err)
		pm_wakeup_event(&phydev->mdio.dev, 0);

	return 0;
}

@@ -437,12 +460,39 @@ static int bcm54xx_iddq_set(struct phy_device *phydev, bool enable)
	return ret;
}

static int bcm54xx_set_wakeup_irq(struct phy_device *phydev, bool state)
{
	struct bcm54xx_phy_priv *priv = phydev->priv;
	int ret = 0;

	if (!bcm54xx_phy_can_wakeup(phydev))
		return ret;

	if (priv->wake_irq_enabled != state) {
		if (state)
			ret = enable_irq_wake(priv->wake_irq);
		else
			ret = disable_irq_wake(priv->wake_irq);
		priv->wake_irq_enabled = state;
	}

	return ret;
}

static int bcm54xx_suspend(struct phy_device *phydev)
{
	int ret;
	int ret = 0;

	bcm54xx_ptp_stop(phydev);

	/* Acknowledge any Wake-on-LAN interrupt prior to suspend */
	ret = bcm_phy_read_exp(phydev, BCM54XX_WOL_INT_STATUS);
	if (ret < 0)
		return ret;

	if (phydev->wol_enabled)
		return bcm54xx_set_wakeup_irq(phydev, true);

	/* We cannot use a read/modify/write here otherwise the PHY gets into
	 * a bad state where its LEDs keep flashing, thus defeating the purpose
	 * of low power mode.
@@ -456,7 +506,13 @@ static int bcm54xx_suspend(struct phy_device *phydev)

static int bcm54xx_resume(struct phy_device *phydev)
{
	int ret;
	int ret = 0;

	if (phydev->wol_enabled) {
		ret = bcm54xx_set_wakeup_irq(phydev, false);
		if (ret)
			return ret;
	}

	ret = bcm54xx_iddq_set(phydev, false);
	if (ret < 0)
@@ -801,14 +857,54 @@ static int brcm_fet_suspend(struct phy_device *phydev)
	return err;
}

static void bcm54xx_phy_get_wol(struct phy_device *phydev,
				struct ethtool_wolinfo *wol)
{
	/* We cannot wake-up if we do not have a dedicated PHY interrupt line
	 * or an out of band GPIO descriptor for wake-up. Zeroing
	 * wol->supported allows the caller (MAC driver) to play through and
	 * offer its own Wake-on-LAN scheme if available.
	 */
	if (!bcm54xx_phy_can_wakeup(phydev)) {
		wol->supported = 0;
		return;
	}

	bcm_phy_get_wol(phydev, wol);
}

static int bcm54xx_phy_set_wol(struct phy_device *phydev,
			       struct ethtool_wolinfo *wol)
{
	int ret;

	/* We cannot wake-up if we do not have a dedicated PHY interrupt line
	 * or an out of band GPIO descriptor for wake-up. Returning -EOPNOTSUPP
	 * allows the caller (MAC driver) to play through and offer its own
	 * Wake-on-LAN scheme if available.
	 */
	if (!bcm54xx_phy_can_wakeup(phydev))
		return -EOPNOTSUPP;

	ret = bcm_phy_set_wol(phydev, wol);
	if (ret < 0)
		return ret;

	return 0;
}

static int bcm54xx_phy_probe(struct phy_device *phydev)
{
	struct bcm54xx_phy_priv *priv;
	struct gpio_desc *wakeup_gpio;
	int ret = 0;

	priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

	priv->wake_irq = -ENXIO;

	phydev->priv = priv;

	priv->stats = devm_kcalloc(&phydev->mdio.dev,
@@ -821,7 +917,28 @@ static int bcm54xx_phy_probe(struct phy_device *phydev)
	if (IS_ERR(priv->ptp))
		return PTR_ERR(priv->ptp);

	/* We cannot utilize the _optional variant here since we want to know
	 * whether the GPIO descriptor exists or not to advertise Wake-on-LAN
	 * support or not.
	 */
	wakeup_gpio = devm_gpiod_get(&phydev->mdio.dev, "wakeup", GPIOD_IN);
	if (PTR_ERR(wakeup_gpio) == -EPROBE_DEFER)
		return PTR_ERR(wakeup_gpio);

	if (!IS_ERR(wakeup_gpio)) {
		priv->wake_irq = gpiod_to_irq(wakeup_gpio);
		ret = irq_set_irq_type(priv->wake_irq, IRQ_TYPE_LEVEL_LOW);
		if (ret)
			return ret;
	}

	/* If we do not have a main interrupt or a side-band wake-up interrupt,
	 * then the device cannot be marked as wake-up capable.
	 */
	if (!bcm54xx_phy_can_wakeup(phydev))
		return 0;

	return device_init_wakeup(&phydev->mdio.dev, true);
}

static void bcm54xx_get_stats(struct phy_device *phydev,
@@ -894,6 +1011,7 @@ static struct phy_driver broadcom_drivers[] = {
	.phy_id_mask	= 0xfffffff0,
	.name		= "Broadcom BCM54210E",
	/* PHY_GBIT_FEATURES */
	.flags		= PHY_ALWAYS_CALL_SUSPEND,
	.get_sset_count	= bcm_phy_get_sset_count,
	.get_strings	= bcm_phy_get_strings,
	.get_stats	= bcm54xx_get_stats,
@@ -904,6 +1022,8 @@ static struct phy_driver broadcom_drivers[] = {
	.link_change_notify	= bcm54xx_link_change_notify,
	.suspend	= bcm54xx_suspend,
	.resume		= bcm54xx_resume,
	.get_wol	= bcm54xx_phy_get_wol,
	.set_wol	= bcm54xx_phy_set_wol,
}, {
	.phy_id		= PHY_ID_BCM5461,
	.phy_id_mask	= 0xfffffff0,
+3 −2
Original line number Diff line number Diff line
@@ -1860,9 +1860,10 @@ int phy_suspend(struct phy_device *phydev)
	if (phydev->suspended)
		return 0;

	/* If the device has WOL enabled, we cannot suspend the PHY */
	phy_ethtool_get_wol(phydev, &wol);
	if (wol.wolopts || (netdev && netdev->wol_enabled))
	phydev->wol_enabled = wol.wolopts || (netdev && netdev->wol_enabled);
	/* If the device has WOL enabled, we cannot suspend the PHY */
	if (phydev->wol_enabled && !(phydrv->flags & PHY_ALWAYS_CALL_SUSPEND))
		return -EBUSY;

	if (!phydrv || !phydrv->suspend)
Loading