mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git
synced 2025-01-11 16:29:05 +00:00
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: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Claudio Lanconelli <lanconelli.claudio@eptar.com> Signed-off-by: Jeff Garzik <jgarzik@redhat.com>
This commit is contained in:
parent
6fd65882f5
commit
7dac6f8df6
@ -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;
|
||||||
|
|
||||||
|
static int poll_ready(struct enc28j60_net *priv, u8 reg, u8 mask, u8 val)
|
||||||
|
{
|
||||||
|
unsigned long timeout = jiffies + msec20_to_jiffies;
|
||||||
|
|
||||||
|
/* 20 msec timeout read */
|
||||||
|
while ((nolock_regb_read(priv, reg) & mask) != val) {
|
||||||
|
if (time_after(jiffies, timeout)) {
|
||||||
|
if (netif_msg_drv(priv))
|
||||||
|
dev_dbg(&priv->spi->dev,
|
||||||
|
"reg %02x ready timeout!\n", reg);
|
||||||
|
return -ETIMEDOUT;
|
||||||
|
}
|
||||||
|
cpu_relax();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Wait until the PHY operation is complete.
|
* Wait until the PHY operation is complete.
|
||||||
*/
|
*/
|
||||||
static int wait_phy_ready(struct enc28j60_net *priv)
|
static int wait_phy_ready(struct enc28j60_net *priv)
|
||||||
{
|
{
|
||||||
unsigned long timeout = jiffies + 20 * HZ / 1000;
|
return poll_ready(priv, MISTAT, MISTAT_BUSY, 0) ? 0 : 1;
|
||||||
int ret = 1;
|
|
||||||
|
|
||||||
/* 20 msec timeout read */
|
|
||||||
while (nolock_regb_read(priv, MISTAT) & MISTAT_BUSY) {
|
|
||||||
if (time_after(jiffies, timeout)) {
|
|
||||||
if (netif_msg_drv(priv))
|
|
||||||
printk(KERN_DEBUG DRV_NAME
|
|
||||||
": PHY ready timeout!\n");
|
|
||||||
ret = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
cpu_relax();
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user