Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7dac6f8d authored by David Brownell's avatar David Brownell Committed by Jeff Garzik
Browse files

net/enc28j60: low power mode



Keep enc28j60 chips in low-power mode when they're not in use.
At typically 120 mA, these chips run hot even when idle; this
low power mode cuts that power usage by a factor of around 100.

This version provides a generic routine to poll a register until
its masked value equals some value ... e.g. bit set or cleared.
It's basically what the previous wait_phy_ready() did, but this
version is generalized to support the handshaking needed to
enter and exit low power mode.

Signed-off-by: default avatarDavid Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: default avatarClaudio Lanconelli <lanconelli.claudio@eptar.com>
Signed-off-by: default avatarJeff Garzik <jgarzik@redhat.com>
parent 6fd65882
Loading
Loading
Loading
Loading
+58 −24
Original line number Original line Diff line number Diff line
@@ -400,26 +400,31 @@ enc28j60_packet_write(struct enc28j60_net *priv, int len, const u8 *data)
	mutex_unlock(&priv->lock);
	mutex_unlock(&priv->lock);
}
}


/*
static unsigned long msec20_to_jiffies;
 * Wait until the PHY operation is complete.

 */
static int poll_ready(struct enc28j60_net *priv, u8 reg, u8 mask, u8 val)
static int wait_phy_ready(struct enc28j60_net *priv)
{
{
	unsigned long timeout = jiffies + 20 * HZ / 1000;
	unsigned long timeout = jiffies + msec20_to_jiffies;
	int ret = 1;


	/* 20 msec timeout read */
	/* 20 msec timeout read */
	while (nolock_regb_read(priv, MISTAT) & MISTAT_BUSY) {
	while ((nolock_regb_read(priv, reg) & mask) != val) {
		if (time_after(jiffies, timeout)) {
		if (time_after(jiffies, timeout)) {
			if (netif_msg_drv(priv))
			if (netif_msg_drv(priv))
				printk(KERN_DEBUG DRV_NAME
				dev_dbg(&priv->spi->dev,
					": PHY ready timeout!\n");
					"reg %02x ready timeout!\n", reg);
			ret = 0;
			return -ETIMEDOUT;
			break;
		}
		}
		cpu_relax();
		cpu_relax();
	}
	}
	return ret;
	return 0;
}

/*
 * Wait until the PHY operation is complete.
 */
static int wait_phy_ready(struct enc28j60_net *priv)
{
	return poll_ready(priv, MISTAT, MISTAT_BUSY, 0) ? 0 : 1;
}
}


/*
/*
@@ -594,6 +599,32 @@ static void nolock_txfifo_init(struct enc28j60_net *priv, u16 start, u16 end)
	nolock_regw_write(priv, ETXNDL, end);
	nolock_regw_write(priv, ETXNDL, end);
}
}


/*
 * Low power mode shrinks power consumption about 100x, so we'd like
 * the chip to be in that mode whenever it's inactive.  (However, we
 * can't stay in lowpower mode during suspend with WOL active.)
 */
static void enc28j60_lowpower(struct enc28j60_net *priv, bool is_low)
{
	if (netif_msg_drv(priv))
		dev_dbg(&priv->spi->dev, "%s power...\n",
				is_low ? "low" : "high");

	mutex_lock(&priv->lock);
	if (is_low) {
		nolock_reg_bfclr(priv, ECON1, ECON1_RXEN);
		poll_ready(priv, ESTAT, ESTAT_RXBUSY, 0);
		poll_ready(priv, ECON1, ECON1_TXRTS, 0);
		/* ECON2_VRPS was set during initialization */
		nolock_reg_bfset(priv, ECON2, ECON2_PWRSV);
	} else {
		nolock_reg_bfclr(priv, ECON2, ECON2_PWRSV);
		poll_ready(priv, ESTAT, ESTAT_CLKRDY, ESTAT_CLKRDY);
		/* caller sets ECON1_RXEN */
	}
	mutex_unlock(&priv->lock);
}

static int enc28j60_hw_init(struct enc28j60_net *priv)
static int enc28j60_hw_init(struct enc28j60_net *priv)
{
{
	u8 reg;
	u8 reg;
@@ -612,8 +643,8 @@ static int enc28j60_hw_init(struct enc28j60_net *priv)
	priv->tx_retry_count = 0;
	priv->tx_retry_count = 0;
	priv->max_pk_counter = 0;
	priv->max_pk_counter = 0;
	priv->rxfilter = RXFILTER_NORMAL;
	priv->rxfilter = RXFILTER_NORMAL;
	/* enable address auto increment */
	/* enable address auto increment and voltage regulator powersave */
	nolock_regb_write(priv, ECON2, ECON2_AUTOINC);
	nolock_regb_write(priv, ECON2, ECON2_AUTOINC | ECON2_VRPS);


	nolock_rxfifo_init(priv, RXSTART_INIT, RXEND_INIT);
	nolock_rxfifo_init(priv, RXSTART_INIT, RXEND_INIT);
	nolock_txfifo_init(priv, TXSTART_INIT, TXEND_INIT);
	nolock_txfifo_init(priv, TXSTART_INIT, TXEND_INIT);
@@ -690,7 +721,7 @@ static int enc28j60_hw_init(struct enc28j60_net *priv)


static void enc28j60_hw_enable(struct enc28j60_net *priv)
static void enc28j60_hw_enable(struct enc28j60_net *priv)
{
{
	/* enable interrutps */
	/* enable interrupts */
	if (netif_msg_hw(priv))
	if (netif_msg_hw(priv))
		printk(KERN_DEBUG DRV_NAME ": %s() enabling interrupts.\n",
		printk(KERN_DEBUG DRV_NAME ": %s() enabling interrupts.\n",
			__FUNCTION__);
			__FUNCTION__);
@@ -726,15 +757,12 @@ enc28j60_setlink(struct net_device *ndev, u8 autoneg, u16 speed, u8 duplex)
	int ret = 0;
	int ret = 0;


	if (!priv->hw_enable) {
	if (!priv->hw_enable) {
		if (autoneg == AUTONEG_DISABLE && speed == SPEED_10) {
		/* link is in low power mode now; duplex setting
		 * will take effect on next enc28j60_hw_init().
		 */
		if (autoneg == AUTONEG_DISABLE && speed == SPEED_10)
			priv->full_duplex = (duplex == DUPLEX_FULL);
			priv->full_duplex = (duplex == DUPLEX_FULL);
			if (!enc28j60_hw_init(priv)) {
		else {
				if (netif_msg_drv(priv))
					dev_err(&ndev->dev,
						"hw_reset() failed\n");
				ret = -EINVAL;
			}
		} else {
			if (netif_msg_link(priv))
			if (netif_msg_link(priv))
				dev_warn(&ndev->dev,
				dev_warn(&ndev->dev,
					"unsupported link setting\n");
					"unsupported link setting\n");
@@ -1307,7 +1335,8 @@ static int enc28j60_net_open(struct net_device *dev)
		}
		}
		return -EADDRNOTAVAIL;
		return -EADDRNOTAVAIL;
	}
	}
	/* Reset the hardware here */
	/* Reset the hardware here (and take it out of low power mode) */
	enc28j60_lowpower(priv, false);
	enc28j60_hw_disable(priv);
	enc28j60_hw_disable(priv);
	if (!enc28j60_hw_init(priv)) {
	if (!enc28j60_hw_init(priv)) {
		if (netif_msg_ifup(priv))
		if (netif_msg_ifup(priv))
@@ -1337,6 +1366,7 @@ static int enc28j60_net_close(struct net_device *dev)
		printk(KERN_DEBUG DRV_NAME ": %s() enter\n", __FUNCTION__);
		printk(KERN_DEBUG DRV_NAME ": %s() enter\n", __FUNCTION__);


	enc28j60_hw_disable(priv);
	enc28j60_hw_disable(priv);
	enc28j60_lowpower(priv, true);
	netif_stop_queue(dev);
	netif_stop_queue(dev);


	return 0;
	return 0;
@@ -1537,6 +1567,8 @@ static int __devinit enc28j60_probe(struct spi_device *spi)
	dev->watchdog_timeo = TX_TIMEOUT;
	dev->watchdog_timeo = TX_TIMEOUT;
	SET_ETHTOOL_OPS(dev, &enc28j60_ethtool_ops);
	SET_ETHTOOL_OPS(dev, &enc28j60_ethtool_ops);


	enc28j60_lowpower(priv, true);

	ret = register_netdev(dev);
	ret = register_netdev(dev);
	if (ret) {
	if (ret) {
		if (netif_msg_probe(priv))
		if (netif_msg_probe(priv))
@@ -1581,6 +1613,8 @@ static struct spi_driver enc28j60_driver = {


static int __init enc28j60_init(void)
static int __init enc28j60_init(void)
{
{
	msec20_to_jiffies = msecs_to_jiffies(20);

	return spi_register_driver(&enc28j60_driver);
	return spi_register_driver(&enc28j60_driver);
}
}