Re: [patch 2.6.24-git] 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 [EMAIL PROTECTED] --- drivers/net/enc28j60.c | 81 ++--- 1 files changed, 57 insertions(+), 24 deletions(-) --- a/drivers/net/enc28j60.c +++ b/drivers/net/enc28j60.c @@ -400,26 +400,31 @@ enc28j60_packet_write(struct enc28j60_ne mutex_unlock(priv-lock); } -/* - * Wait until the PHY operation is complete. - */ -static int wait_phy_ready(struct enc28j60_net *priv) +static unsigned long msec20_to_jiffies; + +static int poll_ready(struct enc28j60_net *priv, u8 reg, u8 mask, u8 val) { - unsigned long timeout = jiffies + 20 * HZ / 1000; - int ret = 1; + unsigned long timeout = jiffies + msec20_to_jiffies; /* 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 (netif_msg_drv(priv)) - printk(KERN_DEBUG DRV_NAME - : PHY ready timeout!\n); - ret = 0; - break; + dev_dbg(priv-spi-dev, + reg %02x ready timeout!\n, reg); + return -ETIMEDOUT; } 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 en 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) { u8 reg; @@ -612,8 +643,8 @@ static int enc28j60_hw_init(struct enc28 priv-tx_retry_count = 0; priv-max_pk_counter = 0; priv-rxfilter = RXFILTER_NORMAL; - /* enable address auto increment */ - nolock_regb_write(priv, ECON2, ECON2_AUTOINC); + /* enable address auto increment and voltage regulator powersave */ + nolock_regb_write(priv, ECON2, ECON2_AUTOINC | ECON2_VRPS); nolock_rxfifo_init(priv, RXSTART_INIT, RXEND_INIT); nolock_txfifo_init(priv, TXSTART_INIT, TXEND_INIT); @@ -690,7 +721,7 @@ static int enc28j60_hw_init(struct enc28 static void enc28j60_hw_enable(struct enc28j60_net *priv) { - /* enable interrutps */ + /* enable interrupts */ if (netif_msg_hw(priv)) printk(KERN_DEBUG DRV_NAME : %s() enabling interrupts.\n, __FUNCTION__); @@ -726,15 +757,12 @@ enc28j60_setlink(struct net_device *ndev int ret = 0; 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); - if (!enc28j60_hw_init(priv)) { - if (netif_msg_drv(priv)) - dev_err(ndev-dev, - hw_reset() failed\n); - ret = -EINVAL; - } - } else { + else
Re: [patch 2.6.24-git] net/enc28j60: low power mode
On Thursday 07 February 2008, Claudio Lanconelli wrote: Sorry, let me repeat what I said in previous mail. I propose you to add set_lowpower(true) in the enc28j60_probe() As the current patch does... and in the enc28j60_net_close() after enc28j60_hw_disable(). Probably we don't need to set_lowpower(false) in enc28j60_net_open() since it performs a soft reset with enc28j60_hw_init() (not sure). The current patch sets the device in low power mode in hw_disable(), and takes it out of that mode in hw_enable(). I can move them; and the only soft thing about this chip's reset is when it starts from a protocol command not the reset command. Furthermore, as you suggested, we also need to remove hw_init() from the setlink() because hw_init() is called when we bring link up. --- enc28j60.c 20 Dec 2007 10:47:01 - 1.22 +++ enc28j60.c 7 Feb 2008 11:07:20 - @@ -740,12 +740,6 @@ if (!priv-hw_enable) { if (autoneg == AUTONEG_DISABLE speed == SPEED_10) { priv-full_duplex = (duplex == DUPLEX_FULL); - if (!enc28j60_hw_init(priv)) { - if (netif_msg_drv(priv)) - dev_err(ndev-dev, - hw_reset() failed\n); - ret = -EINVAL; - } Right. Without the patch mangling presumably done by your mailer. ;) } else { if (netif_msg_link(priv)) dev_warn(ndev-dev, Can you update your low power patch with these modifications? Done -- see my next patch. -- To unsubscribe from this list: send the line unsubscribe netdev in the body of a message to [EMAIL PROTECTED] More majordomo info at http://vger.kernel.org/majordomo-info.html
Re: [patch 2.6.24-git] net/enc28j60: low power mode
David Brownell wrote: The driver seemed to already have some goofage there: # ifconfig eth1 up net eth1: link down net eth1: link down net eth1: normal mode net eth1: multicast mode net eth1: multicast mode # Without low power patch I have: # ifconfig eth0 up net eth0: link down net eth0: normal mode net eth0: multicast mode net eth0: multicast mode # net eth0: link up - Half duplex I'd normally expect it not to go down when told to go up, and then only to do the multicast thing once ... multicast is called by network stacks, no control by the driver. The driver just print message. I don't know why enc28j60_set_multicast_list() are called more than once. When you do an ifconfig up the driver reset the chip, so you see link down before link up message. In such cases If I dump the counters with ifconfig I got rx error counter 0 and the RX and TX packets counters are freezed. Actually I don't know what causes the freeze, it needs investigation. The cause can be the rx error condition or the power down/up commands. May be receiving packets while it's going to wakeup causes problems. The enc28j60_setlink() was odd too. It insists the link be down before changing duplex, then brings the link up ... so I had to put it down again to maintain the lowpower if not up invariant. But the way it brings the link up is to enc28j60_hw_init(), which it also does when bringing the link up. So there's no need to bring the link up when changing duplex ... I don't follow you anymore. To change duplex mode the link must be down. enc28j60_setlink() reinitialize the chip with new duplex mode, enc28j60_hw_init() never brings link up. I propose you to add set_lowpower(true) in the enc28j60_probe() and in the enc28j60_net_close() after enc28j60_hw_disable(). Probably we don't need to set_lowpower(false) in enc28j60_net_open() since it performs a soft reset with enc28j60_hw_init(). Do you agree? use if(netif_msg_drv(priv)) ... Doing so we can switch on/off messages runtime using ethtool. OK, although there's still a role for -DDEBUG compile-time mesage removal. It's ok to use if(netif_msg_drv(priv)) dev_dbv(... This driver also abuses __FUNCTION__ (general policy: don't use it) Why? Then there should be a single routine for all such busy-wait loops, so each such usage doesn't need to be open-coded. Less space, and more obviously correct. I'll add one and make phy_read() use it too. Ok -- To unsubscribe from this list: send the line unsubscribe netdev in the body of a message to [EMAIL PROTECTED] More majordomo info at http://vger.kernel.org/majordomo-info.html
Re: [patch 2.6.24-git] net/enc28j60: low power mode
Sorry, let me repeat what I said in previous mail. I propose you to add set_lowpower(true) in the enc28j60_probe() and in the enc28j60_net_close() after enc28j60_hw_disable(). Probably we don't need to set_lowpower(false) in enc28j60_net_open() since it performs a soft reset with enc28j60_hw_init() (not sure). Furthermore, as you suggested, we also need to remove hw_init() from the setlink() because hw_init() is called when we bring link up. --- enc28j60.c 20 Dec 2007 10:47:01 - 1.22 +++ enc28j60.c 7 Feb 2008 11:07:20 - @@ -740,12 +740,6 @@ if (!priv-hw_enable) { if (autoneg == AUTONEG_DISABLE speed == SPEED_10) { priv-full_duplex = (duplex == DUPLEX_FULL); - if (!enc28j60_hw_init(priv)) { - if (netif_msg_drv(priv)) - dev_err(ndev-dev, - hw_reset() failed\n); - ret = -EINVAL; - } } else { if (netif_msg_link(priv)) dev_warn(ndev-dev, Can you update your low power patch with these modifications? -- To unsubscribe from this list: send the line unsubscribe netdev in the body of a message to [EMAIL PROTECTED] More majordomo info at http://vger.kernel.org/majordomo-info.html
Re: [patch 2.6.24-git] 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. Low power mode cuts that power usage by a factor of around 100. Good idea, but with your patch applied, after some ifconfig down - ifconfig up cycle, the enc28j60 is left in an unknown state and it doesn' Rx/Tx anything. The driver seemed to already have some goofage there: # ifconfig eth1 up net eth1: link down net eth1: link down net eth1: normal mode net eth1: multicast mode net eth1: multicast mode # I'd normally expect it not to go down when told to go up, and then only to do the multicast thing once ... In such cases If I dump the counters with ifconfig I got rx error counter 0 and the RX and TX packets counters are freezed. Actually I don't know what causes the freeze, it needs investigation. The cause can be the rx error condition or the power down/up commands. May be receiving packets while it's going to wakeup causes problems. The enc28j60_setlink() was odd too. It insists the link be down before changing duplex, then brings the link up ... so I had to put it down again to maintain the lowpower if not up invariant. But the way it brings the link up is to enc28j60_hw_init(), which it also does when bringing the link up. So there's no need to bring the link up when changing duplex ... +/* + * Low power mode shrinks power consumption about 100x, so we'd like + * the chip to be in that mode whenever it's inactive. + */ +static void enc28j60_lowpower(struct enc28j60_net *priv, bool is_low) +{ + int tmp; + + dev_dbg(priv-spi-dev, %s power...\n, is_low ? low : high); use if(netif_msg_drv(priv)) ... Doing so we can switch on/off messages runtime using ethtool. OK, although there's still a role for -DDEBUG compile-time mesage removal. printk(... instead of dev_dbg(), please. Actually, I had to resist sending a patch converting this driver to stop abusing printk in all those locations it should have been using dev_*() messaging instead. And to use the dev_*() messaging correctly ... the above shouldn't have said net eth1, but either eth1: or enc28j60 spi1.0. The general policy in the kernel is to avoid using printk for driver messaging. Couple messages to the hardware, so that when there are two instances it's easy to tell which chip is affected. That happens for free with the driver and device prefix of the dev_*() messages. In absense of interface renaming, network devices sometimes use eth1 style message prefixes ... after a previous message coupling that to the particular hardware. This driver also abuses __FUNCTION__ (general policy: don't use it) and underuses the -DDEBUG compile-time string removal. + + mutex_lock(priv-lock); + if (is_low) { + nolock_reg_bfclr(priv, ECON1, ECON1_RXEN); + for (;;) { + tmp = nolock_regb_read(priv, ESTAT); + if (!(tmp ESTAT_RXBUSY)) + break; + } Avoid infinite waiting loops, please. Look at enc28j60_phy_read() for example. Then there should be a single routine for all such busy-wait loops, so each such usage doesn't need to be open-coded. Less space, and more obviously correct. I'll add one and make phy_read() use it too. + for (;;) { + tmp = nolock_regb_read(priv, ECON1); + if (!(tmp ECON1_TXRTS)) + break; + } idem + /* ECON2_VRPS was set during initialization */ + nolock_reg_bfset(priv, ECON2, ECON2_PWRSV); + } else { + nolock_reg_bfclr(priv, ECON2, ECON2_PWRSV); + for (;;) { + tmp = nolock_regb_read(priv, ESTAT); + if (tmp ESTAT_CLKRDY) + break; + } idem -- To unsubscribe from this list: send the line unsubscribe netdev in the body of a message to [EMAIL PROTECTED] More majordomo info at http://vger.kernel.org/majordomo-info.html
Re: [patch 2.6.24-git] net/enc28j60: low power mode
On Wednesday 06 February 2008, Claudio Lanconelli wrote: + for (;;) { + tmp = nolock_regb_read(priv, ESTAT); + if (!(tmp ESTAT_RXBUSY)) + break; + } Avoid infinite waiting loops, please. Look at enc28j60_phy_read() for example. Updated patch is appended. == CUT HERE 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 [EMAIL PROTECTED] --- drivers/net/enc28j60.c | 70 ++--- 1 files changed, 55 insertions(+), 15 deletions(-) --- a/drivers/net/enc28j60.c +++ b/drivers/net/enc28j60.c @@ -400,26 +400,31 @@ enc28j60_packet_write(struct enc28j60_ne mutex_unlock(priv-lock); } -/* - * Wait until the PHY operation is complete. - */ -static int wait_phy_ready(struct enc28j60_net *priv) +static unsigned long msec20_to_jiffies; + +static int poll_ready(struct enc28j60_net *priv, u8 reg, u8 mask, u8 val) { - unsigned long timeout = jiffies + 20 * HZ / 1000; - int ret = 1; + unsigned long timeout = jiffies + msec20_to_jiffies; /* 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 (netif_msg_drv(priv)) - printk(KERN_DEBUG DRV_NAME - : PHY ready timeout!\n); - ret = 0; - break; + dev_dbg(priv-spi-dev, + reg %02x ready timeout!\n, reg); + return -ETIMEDOUT; } 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,31 @@ static void nolock_txfifo_init(struct en 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. + */ +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) { u8 reg; @@ -612,8 +642,8 @@ static int enc28j60_hw_init(struct enc28 priv-tx_retry_count = 0; priv-max_pk_counter = 0; priv-rxfilter = RXFILTER_NORMAL; - /* enable address auto increment */ - nolock_regb_write(priv, ECON2, ECON2_AUTOINC); + /* enable address auto increment and voltage regulator powersave */ + nolock_regb_write(priv, ECON2, ECON2_AUTOINC | ECON2_VRPS); nolock_rxfifo_init(priv, RXSTART_INIT, RXEND_INIT); nolock_txfifo_init(priv, TXSTART_INIT, TXEND_INIT); @@ -690,7 +720,9 @@ static int enc28j60_hw_init(struct enc28 static void enc28j60_hw_enable(struct enc28j60_net *priv) { - /* enable interrutps */ + enc28j60_lowpower(priv, false); + + /* enable interrupts */ if (netif_msg_hw(priv)) printk(KERN_DEBUG DRV_NAME : %s() enabling interrupts.\n, __FUNCTION__); @@ -717,6 +749,8 @@ static void enc28j60_hw_disable(struct e nolock_reg_bfclr(priv, ECON1, ECON1_RXEN); priv-hw_enable = false; mutex_unlock(priv-lock); + + enc28j60_lowpower(priv, true); } static int @@ -734,6 +768,8 @@ enc28j60_setlink(struct net_device *ndev hw_reset() failed\n); ret = -EINVAL; } +