diff mbox

[3/4] ath5k: Wakeup fixes

Message ID 20090731180805.GD7963@makis (mailing list archive)
State Not Applicable, archived
Headers show

Commit Message

Nick Kossifidis July 31, 2009, 6:08 p.m. UTC
* Don't put chip to full sleep because there are problems during
   wakeup. Instead hold MAC/Baseband on warm reset state via a new
   function ath5k_hw_on_hold.

 * Durring attach preserve pcicfg bits when enabling pci core
   sw retry fix.

 * Minor cleanups

---
 drivers/net/wireless/ath/ath5k/ath5k.h  |    1 +
 drivers/net/wireless/ath/ath5k/attach.c |    4 +-
 drivers/net/wireless/ath/ath5k/base.c   |   44 +++++----
 drivers/net/wireless/ath/ath5k/reset.c  |  155 +++++++++++++++++++++++--------
 4 files changed, 141 insertions(+), 63 deletions(-)

--
To unsubscribe from this list: send the line "unsubscribe linux-wireless" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Comments

Luis Rodriguez July 31, 2009, 6:14 p.m. UTC | #1
On Fri, Jul 31, 2009 at 11:08 AM, Nick
Kossifidis<mick@madwifi-project.org> wrote:
>  * Don't put chip to full sleep because there are problems during
>   wakeup. Instead hold MAC/Baseband on warm reset state via a new
>   function ath5k_hw_on_hold.
>
>  * Durring attach preserve pcicfg bits when enabling pci core
>   sw retry fix.
>
>  * Minor cleanups

Can you address these changes separately? I see you tend to itemize
the things you change, even when you just make one change. Please
consider addressing one change per commit and just ensure the why is
crystal clear.

  Luis

> ---
>  drivers/net/wireless/ath/ath5k/ath5k.h  |    1 +
>  drivers/net/wireless/ath/ath5k/attach.c |    4 +-
>  drivers/net/wireless/ath/ath5k/base.c   |   44 +++++----
>  drivers/net/wireless/ath/ath5k/reset.c  |  155 +++++++++++++++++++++++--------
>  4 files changed, 141 insertions(+), 63 deletions(-)
>
> diff --git a/drivers/net/wireless/ath/ath5k/ath5k.h b/drivers/net/wireless/ath/ath5k/ath5k.h
> index 9137511..1047a6c 100644
> --- a/drivers/net/wireless/ath/ath5k/ath5k.h
> +++ b/drivers/net/wireless/ath/ath5k/ath5k.h
> @@ -1157,6 +1157,7 @@ extern void ath5k_unregister_leds(struct ath5k_softc *sc);
>
>  /* Reset Functions */
>  extern int ath5k_hw_nic_wakeup(struct ath5k_hw *ah, int flags, bool initial);
> +extern int ath5k_hw_on_hold(struct ath5k_hw *ah);
>  extern int ath5k_hw_reset(struct ath5k_hw *ah, enum nl80211_iftype op_mode, struct ieee80211_channel *channel, bool change_channel);
>  /* Power management functions */
>  extern int ath5k_hw_set_power(struct ath5k_hw *ah, enum ath5k_power_mode mode, bool set_chip, u16 sleep_duration);
> diff --git a/drivers/net/wireless/ath/ath5k/attach.c b/drivers/net/wireless/ath/ath5k/attach.c
> index 6263065..65d438b 100644
> --- a/drivers/net/wireless/ath/ath5k/attach.c
> +++ b/drivers/net/wireless/ath/ath5k/attach.c
> @@ -145,7 +145,7 @@ struct ath5k_hw *ath5k_hw_attach(struct ath5k_softc *sc, u8 mac_version)
>                goto err_free;
>
>        /* Bring device out of sleep and reset it's units */
> -       ret = ath5k_hw_nic_wakeup(ah, CHANNEL_B, true);
> +       ret = ath5k_hw_nic_wakeup(ah, 0, true);
>        if (ret)
>                goto err_free;
>
> @@ -261,7 +261,7 @@ struct ath5k_hw *ath5k_hw_attach(struct ath5k_softc *sc, u8 mac_version)
>
>        /* Enable pci core retry fix on Hainan (5213A) and later chips */
>        if (srev >= AR5K_SREV_AR5213A)
> -               ath5k_hw_reg_write(ah, AR5K_PCICFG_RETRY_FIX, AR5K_PCICFG);
> +               AR5K_REG_ENABLE_BITS(ah, AR5K_PCICFG, AR5K_PCICFG_RETRY_FIX);
>
>        /*
>         * Get card capabilities, calibration values etc
> diff --git a/drivers/net/wireless/ath/ath5k/base.c b/drivers/net/wireless/ath/ath5k/base.c
> index 7db32ce..b64731b 100644
> --- a/drivers/net/wireless/ath/ath5k/base.c
> +++ b/drivers/net/wireless/ath/ath5k/base.c
> @@ -2448,27 +2448,29 @@ ath5k_stop_hw(struct ath5k_softc *sc)
>        ret = ath5k_stop_locked(sc);
>        if (ret == 0 && !test_bit(ATH_STAT_INVALID, sc->status)) {
>                /*
> -                * Set the chip in full sleep mode.  Note that we are
> -                * careful to do this only when bringing the interface
> -                * completely to a stop.  When the chip is in this state
> -                * it must be carefully woken up or references to
> -                * registers in the PCI clock domain may freeze the bus
> -                * (and system).  This varies by chip and is mostly an
> -                * issue with newer parts that go to sleep more quickly.
> -                */
> -               if (sc->ah->ah_mac_srev >= 0x78) {
> -                       /*
> -                        * XXX
> -                        * don't put newer MAC revisions > 7.8 to sleep because
> -                        * of the above mentioned problems
> -                        */
> -                       ATH5K_DBG(sc, ATH5K_DEBUG_RESET, "mac version > 7.8, "
> -                               "not putting device to sleep\n");
> -               } else {
> -                       ATH5K_DBG(sc, ATH5K_DEBUG_RESET,
> -                               "putting device to full sleep\n");
> -                       ath5k_hw_set_power(sc->ah, AR5K_PM_FULL_SLEEP, true, 0);
> -               }
> +                * Don't set the card in full sleep mode!
> +                *
> +                * a) When the device is in this state it must be carefully
> +                * woken up or references to registers in the PCI clock
> +                * domain may freeze the bus (and system).  This varies
> +                * by chip and is mostly an issue with newer parts
> +                * (madwifi sources mentioned srev >= 0x78) that go to
> +                * sleep more quickly.
> +                *
> +                * b) On older chips full sleep results a weird behaviour
> +                * during wakeup. I tested various cards with srev < 0x78
> +                * and they don't wake up after module reload, a second
> +                * module reload is needed to bring the card up again.
> +                *
> +                * Until we figure out what's going on don't enable
> +                * full chip reset on any chip (this is what Legacy HAL
> +                * and Sam's HAL do anyway). Instead Perform a full reset
> +                * on the device (same as initial state after attach) and
> +                * leave it idle (keep MAC/BB on warm reset) */
> +               ret = ath5k_hw_on_hold(sc->ah);
> +
> +               ATH5K_DBG(sc, ATH5K_DEBUG_RESET,
> +                               "putting device to sleep\n");
>        }
>        ath5k_txbuf_free(sc, sc->bbuf);
>
> diff --git a/drivers/net/wireless/ath/ath5k/reset.c b/drivers/net/wireless/ath/ath5k/reset.c
> index 86733fd..34e13c7 100644
> --- a/drivers/net/wireless/ath/ath5k/reset.c
> +++ b/drivers/net/wireless/ath/ath5k/reset.c
> @@ -258,29 +258,35 @@ int ath5k_hw_set_power(struct ath5k_hw *ah, enum ath5k_power_mode mode,
>                if (!set_chip)
>                        goto commit;
>
> -               /* Preserve sleep duration */
>                data = ath5k_hw_reg_read(ah, AR5K_SLEEP_CTL);
> +
> +               /* If card is down we 'll get 0xffff... so we
> +                * need to clean this up before we write the register
> +                */
>                if (data & 0xffc00000)
>                        data = 0;
>                else
> -                       data = data & 0xfffcffff;
> +                       /* Preserve sleep duration etc */
> +                       data = data & ~AR5K_SLEEP_CTL_SLE;
>
> -               ath5k_hw_reg_write(ah, data, AR5K_SLEEP_CTL);
> +               ath5k_hw_reg_write(ah, data | AR5K_SLEEP_CTL_SLE_WAKE,
> +                                                       AR5K_SLEEP_CTL);
>                udelay(15);
>
> -               for (i = 50; i > 0; i--) {
> +               for (i = 200; i > 0; i--) {
>                        /* Check if the chip did wake up */
>                        if ((ath5k_hw_reg_read(ah, AR5K_PCICFG) &
>                                        AR5K_PCICFG_SPWR_DN) == 0)
>                                break;
>
>                        /* Wait a bit and retry */
> -                       udelay(200);
> -                       ath5k_hw_reg_write(ah, data, AR5K_SLEEP_CTL);
> +                       udelay(50);
> +                       ath5k_hw_reg_write(ah, data | AR5K_SLEEP_CTL_SLE_WAKE,
> +                                                       AR5K_SLEEP_CTL);
>                }
>
>                /* Fail if the chip didn't wake up */
> -               if (i <= 0)
> +               if (i == 0)
>                        return -EIO;
>
>                break;
> @@ -296,6 +302,64 @@ commit:
>  }
>
>  /*
> + * Put device on hold
> + *
> + * Put MAC and Baseband on warm reset and
> + * keep that state (don't clean sleep control
> + * register). After this MAC and Baseband are
> + * disabled and a full reset is needed to come
> + * back. This way we save as much power as possible
> + * without puting the card on full sleep.
> + */
> +int ath5k_hw_on_hold(struct ath5k_hw *ah)
> +{
> +       struct pci_dev *pdev = ah->ah_sc->pdev;
> +       u32 bus_flags;
> +       int ret;
> +
> +       /* Make sure device is awake */
> +       ret = ath5k_hw_set_power(ah, AR5K_PM_AWAKE, true, 0);
> +       if (ret) {
> +               ATH5K_ERR(ah->ah_sc, "failed to wakeup the MAC Chip\n");
> +               return ret;
> +       }
> +
> +       /*
> +        * Put chipset on warm reset...
> +        *
> +        * Note: puting PCI core on warm reset on PCI-E cards
> +        * results card to hang and always return 0xffff... so
> +        * we ingore that flag for PCI-E cards. On PCI cards
> +        * this flag gets cleared after 64 PCI clocks.
> +        */
> +       bus_flags = (pdev->is_pcie) ? 0 : AR5K_RESET_CTL_PCI;
> +
> +       if (ah->ah_version == AR5K_AR5210) {
> +               ret = ath5k_hw_nic_reset(ah, AR5K_RESET_CTL_PCU |
> +                       AR5K_RESET_CTL_MAC | AR5K_RESET_CTL_DMA |
> +                       AR5K_RESET_CTL_PHY | AR5K_RESET_CTL_PCI);
> +                       mdelay(2);
> +       } else {
> +               ret = ath5k_hw_nic_reset(ah, AR5K_RESET_CTL_PCU |
> +                       AR5K_RESET_CTL_BASEBAND | bus_flags);
> +       }
> +
> +       if (ret) {
> +               ATH5K_ERR(ah->ah_sc, "failed to put device on warm reset\n");
> +               return -EIO;
> +       }
> +
> +       /* ...wakeup again!*/
> +       ret = ath5k_hw_set_power(ah, AR5K_PM_AWAKE, true, 0);
> +       if (ret) {
> +               ATH5K_ERR(ah->ah_sc, "failed to put device on hold\n");
> +               return ret;
> +       }
> +
> +       return ret;
> +}
> +
> +/*
>  * Bring up MAC + PHY Chips and program PLL
>  * TODO: Half/Quarter rate support
>  */
> @@ -318,6 +382,50 @@ int ath5k_hw_nic_wakeup(struct ath5k_hw *ah, int flags, bool initial)
>                return ret;
>        }
>
> +       /*
> +        * Put chipset on warm reset...
> +        *
> +        * Note: puting PCI core on warm reset on PCI-E cards
> +        * results card to hang and always return 0xffff... so
> +        * we ingore that flag for PCI-E cards. On PCI cards
> +        * this flag gets cleared after 64 PCI clocks.
> +        */
> +       bus_flags = (pdev->is_pcie) ? 0 : AR5K_RESET_CTL_PCI;
> +
> +       if (ah->ah_version == AR5K_AR5210) {
> +               ret = ath5k_hw_nic_reset(ah, AR5K_RESET_CTL_PCU |
> +                       AR5K_RESET_CTL_MAC | AR5K_RESET_CTL_DMA |
> +                       AR5K_RESET_CTL_PHY | AR5K_RESET_CTL_PCI);
> +                       mdelay(2);
> +       } else {
> +               ret = ath5k_hw_nic_reset(ah, AR5K_RESET_CTL_PCU |
> +                       AR5K_RESET_CTL_BASEBAND | bus_flags);
> +       }
> +
> +       if (ret) {
> +               ATH5K_ERR(ah->ah_sc, "failed to reset the MAC Chip\n");
> +               return -EIO;
> +       }
> +
> +       /* ...wakeup again!...*/
> +       ret = ath5k_hw_set_power(ah, AR5K_PM_AWAKE, true, 0);
> +       if (ret) {
> +               ATH5K_ERR(ah->ah_sc, "failed to resume the MAC Chip\n");
> +               return ret;
> +       }
> +
> +       /* ...clear reset control register and pull device out of
> +        * warm reset */
> +       if (ath5k_hw_nic_reset(ah, 0)) {
> +               ATH5K_ERR(ah->ah_sc, "failed to warm reset the MAC Chip\n");
> +               return -EIO;
> +       }
> +
> +       /* On initialization skip PLL programming since we don't have
> +        * a channel / mode set yet */
> +       if (initial)
> +               return 0;
> +
>        if (ah->ah_version != AR5K_AR5210) {
>                /*
>                 * Get channel mode flags
> @@ -383,39 +491,6 @@ int ath5k_hw_nic_wakeup(struct ath5k_hw *ah, int flags, bool initial)
>                                        AR5K_PHY_TURBO);
>        }
>
> -       /* reseting PCI on PCI-E cards results card to hang
> -        * and always return 0xffff... so we ingore that flag
> -        * for PCI-E cards */
> -       bus_flags = (pdev->is_pcie) ? 0 : AR5K_RESET_CTL_PCI;
> -
> -       /* Reset chipset */
> -       if (ah->ah_version == AR5K_AR5210) {
> -               ret = ath5k_hw_nic_reset(ah, AR5K_RESET_CTL_PCU |
> -                       AR5K_RESET_CTL_MAC | AR5K_RESET_CTL_DMA |
> -                       AR5K_RESET_CTL_PHY | AR5K_RESET_CTL_PCI);
> -                       mdelay(2);
> -       } else {
> -               ret = ath5k_hw_nic_reset(ah, AR5K_RESET_CTL_PCU |
> -                       AR5K_RESET_CTL_BASEBAND | bus_flags);
> -       }
> -       if (ret) {
> -               ATH5K_ERR(ah->ah_sc, "failed to reset the MAC Chip\n");
> -               return -EIO;
> -       }
> -
> -       /* ...wakeup again!*/
> -       ret = ath5k_hw_set_power(ah, AR5K_PM_AWAKE, true, 0);
> -       if (ret) {
> -               ATH5K_ERR(ah->ah_sc, "failed to resume the MAC Chip\n");
> -               return ret;
> -       }
> -
> -       /* ...final warm reset */
> -       if (ath5k_hw_nic_reset(ah, 0)) {
> -               ATH5K_ERR(ah->ah_sc, "failed to warm reset the MAC Chip\n");
> -               return -EIO;
> -       }
> -
>        if (ah->ah_version != AR5K_AR5210) {
>
>                /* ...update PLL if needed */
>
Nick Kossifidis July 31, 2009, 6:39 p.m. UTC | #2
2009/7/31 Luis R. Rodriguez <mcgrof@gmail.com>:
> On Fri, Jul 31, 2009 at 11:08 AM, Nick
> Kossifidis<mick@madwifi-project.org> wrote:
>>  * Don't put chip to full sleep because there are problems during
>>   wakeup. Instead hold MAC/Baseband on warm reset state via a new
>>   function ath5k_hw_on_hold.
>>
>>  * Durring attach preserve pcicfg bits when enabling pci core
>>   sw retry fix.
>>
>>  * Minor cleanups
>
> Can you address these changes separately? I see you tend to itemize
> the things you change, even when you just make one change. Please
> consider addressing one change per commit and just ensure the why is
> crystal clear.
>
>  Luis
>

Cleanup doesn't count as a change, the only real change is the first one.
I 'm trying not to pollute the logs. This patch fixes an issue reported when
card doesn't wake up.
Luis Rodriguez July 31, 2009, 7:01 p.m. UTC | #3
On Fri, Jul 31, 2009 at 11:39 AM, Nick Kossifidis<mickflemm@gmail.com> wrote:
> 2009/7/31 Luis R. Rodriguez <mcgrof@gmail.com>:
>> On Fri, Jul 31, 2009 at 11:08 AM, Nick
>> Kossifidis<mick@madwifi-project.org> wrote:
>>>  * Don't put chip to full sleep because there are problems during
>>>   wakeup. Instead hold MAC/Baseband on warm reset state via a new
>>>   function ath5k_hw_on_hold.
>>>
>>>  * Durring attach preserve pcicfg bits when enabling pci core
>>>   sw retry fix.
>>>
>>>  * Minor cleanups
>>
>> Can you address these changes separately? I see you tend to itemize
>> the things you change, even when you just make one change. Please
>> consider addressing one change per commit and just ensure the why is
>> crystal clear.
>>
>>  Luis
>>
>
> Cleanup doesn't count as a change, the only real change is the first one.
> I 'm trying not to pollute the logs.

The purpose of the patch review cycle is to make it very easy to
review code changes, the more you split your work the easier it is to
review, that is not polluting the logs in any way.

> This patch fixes an issue reported when
> card doesn't wake up.

This is not mentioned in the commit log, please add that to the commit
log and refer to a URL if possible.

  Luis
--
To unsubscribe from this list: send the line "unsubscribe linux-wireless" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Ben Greear July 31, 2009, 7:03 p.m. UTC | #4
On 07/31/2009 11:08 AM, Nick Kossifidis wrote:
>   * Don't put chip to full sleep because there are problems during
>     wakeup. Instead hold MAC/Baseband on warm reset state via a new
>     function ath5k_hw_on_hold.
>
>   * Durring attach preserve pcicfg bits when enabling pci core
>     sw retry fix.
>
>   * Minor cleanups

I patched just this patch (not your other 3) into 2.6.31-rc4 and it seems
to fix the wakeup problems I was seeing on module unload/load.

Thanks,
Ben
Luis Rodriguez July 31, 2009, 7:05 p.m. UTC | #5
On Fri, Jul 31, 2009 at 12:03 PM, Ben Greear<greearb@candelatech.com> wrote:
> On 07/31/2009 11:08 AM, Nick Kossifidis wrote:
>>
>>  * Don't put chip to full sleep because there are problems during
>>    wakeup. Instead hold MAC/Baseband on warm reset state via a new
>>    function ath5k_hw_on_hold.
>>
>>  * Durring attach preserve pcicfg bits when enabling pci core
>>    sw retry fix.
>>
>>  * Minor cleanups
>
> I patched just this patch (not your other 3) into 2.6.31-rc4 and it seems
> to fix the wakeup problems I was seeing on module unload/load.

And now you can throw in a Tested-by in the commit log. The commit log
will make more sense as if you refer to the issue we can refer to it
later in the future and see it was tested perhaps by the same user who
reported the issue.

  Luis
--
To unsubscribe from this list: send the line "unsubscribe linux-wireless" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/drivers/net/wireless/ath/ath5k/ath5k.h b/drivers/net/wireless/ath/ath5k/ath5k.h
index 9137511..1047a6c 100644
--- a/drivers/net/wireless/ath/ath5k/ath5k.h
+++ b/drivers/net/wireless/ath/ath5k/ath5k.h
@@ -1157,6 +1157,7 @@  extern void ath5k_unregister_leds(struct ath5k_softc *sc);
 
 /* Reset Functions */
 extern int ath5k_hw_nic_wakeup(struct ath5k_hw *ah, int flags, bool initial);
+extern int ath5k_hw_on_hold(struct ath5k_hw *ah);
 extern int ath5k_hw_reset(struct ath5k_hw *ah, enum nl80211_iftype op_mode, struct ieee80211_channel *channel, bool change_channel);
 /* Power management functions */
 extern int ath5k_hw_set_power(struct ath5k_hw *ah, enum ath5k_power_mode mode, bool set_chip, u16 sleep_duration);
diff --git a/drivers/net/wireless/ath/ath5k/attach.c b/drivers/net/wireless/ath/ath5k/attach.c
index 6263065..65d438b 100644
--- a/drivers/net/wireless/ath/ath5k/attach.c
+++ b/drivers/net/wireless/ath/ath5k/attach.c
@@ -145,7 +145,7 @@  struct ath5k_hw *ath5k_hw_attach(struct ath5k_softc *sc, u8 mac_version)
 		goto err_free;
 
 	/* Bring device out of sleep and reset it's units */
-	ret = ath5k_hw_nic_wakeup(ah, CHANNEL_B, true);
+	ret = ath5k_hw_nic_wakeup(ah, 0, true);
 	if (ret)
 		goto err_free;
 
@@ -261,7 +261,7 @@  struct ath5k_hw *ath5k_hw_attach(struct ath5k_softc *sc, u8 mac_version)
 
 	/* Enable pci core retry fix on Hainan (5213A) and later chips */
 	if (srev >= AR5K_SREV_AR5213A)
-		ath5k_hw_reg_write(ah, AR5K_PCICFG_RETRY_FIX, AR5K_PCICFG);
+		AR5K_REG_ENABLE_BITS(ah, AR5K_PCICFG, AR5K_PCICFG_RETRY_FIX);
 
 	/*
 	 * Get card capabilities, calibration values etc
diff --git a/drivers/net/wireless/ath/ath5k/base.c b/drivers/net/wireless/ath/ath5k/base.c
index 7db32ce..b64731b 100644
--- a/drivers/net/wireless/ath/ath5k/base.c
+++ b/drivers/net/wireless/ath/ath5k/base.c
@@ -2448,27 +2448,29 @@  ath5k_stop_hw(struct ath5k_softc *sc)
 	ret = ath5k_stop_locked(sc);
 	if (ret == 0 && !test_bit(ATH_STAT_INVALID, sc->status)) {
 		/*
-		 * Set the chip in full sleep mode.  Note that we are
-		 * careful to do this only when bringing the interface
-		 * completely to a stop.  When the chip is in this state
-		 * it must be carefully woken up or references to
-		 * registers in the PCI clock domain may freeze the bus
-		 * (and system).  This varies by chip and is mostly an
-		 * issue with newer parts that go to sleep more quickly.
-		 */
-		if (sc->ah->ah_mac_srev >= 0x78) {
-			/*
-			 * XXX
-			 * don't put newer MAC revisions > 7.8 to sleep because
-			 * of the above mentioned problems
-			 */
-			ATH5K_DBG(sc, ATH5K_DEBUG_RESET, "mac version > 7.8, "
-				"not putting device to sleep\n");
-		} else {
-			ATH5K_DBG(sc, ATH5K_DEBUG_RESET,
-				"putting device to full sleep\n");
-			ath5k_hw_set_power(sc->ah, AR5K_PM_FULL_SLEEP, true, 0);
-		}
+		 * Don't set the card in full sleep mode!
+		 *
+		 * a) When the device is in this state it must be carefully
+		 * woken up or references to registers in the PCI clock
+		 * domain may freeze the bus (and system).  This varies
+		 * by chip and is mostly an issue with newer parts
+		 * (madwifi sources mentioned srev >= 0x78) that go to
+		 * sleep more quickly.
+		 *
+		 * b) On older chips full sleep results a weird behaviour
+		 * during wakeup. I tested various cards with srev < 0x78
+		 * and they don't wake up after module reload, a second
+		 * module reload is needed to bring the card up again.
+		 *
+		 * Until we figure out what's going on don't enable
+		 * full chip reset on any chip (this is what Legacy HAL
+		 * and Sam's HAL do anyway). Instead Perform a full reset
+		 * on the device (same as initial state after attach) and
+		 * leave it idle (keep MAC/BB on warm reset) */
+		ret = ath5k_hw_on_hold(sc->ah);
+
+		ATH5K_DBG(sc, ATH5K_DEBUG_RESET,
+				"putting device to sleep\n");
 	}
 	ath5k_txbuf_free(sc, sc->bbuf);
 
diff --git a/drivers/net/wireless/ath/ath5k/reset.c b/drivers/net/wireless/ath/ath5k/reset.c
index 86733fd..34e13c7 100644
--- a/drivers/net/wireless/ath/ath5k/reset.c
+++ b/drivers/net/wireless/ath/ath5k/reset.c
@@ -258,29 +258,35 @@  int ath5k_hw_set_power(struct ath5k_hw *ah, enum ath5k_power_mode mode,
 		if (!set_chip)
 			goto commit;
 
-		/* Preserve sleep duration */
 		data = ath5k_hw_reg_read(ah, AR5K_SLEEP_CTL);
+
+		/* If card is down we 'll get 0xffff... so we
+		 * need to clean this up before we write the register
+		 */
 		if (data & 0xffc00000)
 			data = 0;
 		else
-			data = data & 0xfffcffff;
+			/* Preserve sleep duration etc */
+			data = data & ~AR5K_SLEEP_CTL_SLE;
 
-		ath5k_hw_reg_write(ah, data, AR5K_SLEEP_CTL);
+		ath5k_hw_reg_write(ah, data | AR5K_SLEEP_CTL_SLE_WAKE,
+							AR5K_SLEEP_CTL);
 		udelay(15);
 
-		for (i = 50; i > 0; i--) {
+		for (i = 200; i > 0; i--) {
 			/* Check if the chip did wake up */
 			if ((ath5k_hw_reg_read(ah, AR5K_PCICFG) &
 					AR5K_PCICFG_SPWR_DN) == 0)
 				break;
 
 			/* Wait a bit and retry */
-			udelay(200);
-			ath5k_hw_reg_write(ah, data, AR5K_SLEEP_CTL);
+			udelay(50);
+			ath5k_hw_reg_write(ah, data | AR5K_SLEEP_CTL_SLE_WAKE,
+							AR5K_SLEEP_CTL);
 		}
 
 		/* Fail if the chip didn't wake up */
-		if (i <= 0)
+		if (i == 0)
 			return -EIO;
 
 		break;
@@ -296,6 +302,64 @@  commit:
 }
 
 /*
+ * Put device on hold
+ *
+ * Put MAC and Baseband on warm reset and
+ * keep that state (don't clean sleep control
+ * register). After this MAC and Baseband are
+ * disabled and a full reset is needed to come
+ * back. This way we save as much power as possible
+ * without puting the card on full sleep.
+ */
+int ath5k_hw_on_hold(struct ath5k_hw *ah)
+{
+	struct pci_dev *pdev = ah->ah_sc->pdev;
+	u32 bus_flags;
+	int ret;
+
+	/* Make sure device is awake */
+	ret = ath5k_hw_set_power(ah, AR5K_PM_AWAKE, true, 0);
+	if (ret) {
+		ATH5K_ERR(ah->ah_sc, "failed to wakeup the MAC Chip\n");
+		return ret;
+	}
+
+	/*
+	 * Put chipset on warm reset...
+	 *
+	 * Note: puting PCI core on warm reset on PCI-E cards
+	 * results card to hang and always return 0xffff... so
+	 * we ingore that flag for PCI-E cards. On PCI cards
+	 * this flag gets cleared after 64 PCI clocks.
+	 */
+	bus_flags = (pdev->is_pcie) ? 0 : AR5K_RESET_CTL_PCI;
+
+	if (ah->ah_version == AR5K_AR5210) {
+		ret = ath5k_hw_nic_reset(ah, AR5K_RESET_CTL_PCU |
+			AR5K_RESET_CTL_MAC | AR5K_RESET_CTL_DMA |
+			AR5K_RESET_CTL_PHY | AR5K_RESET_CTL_PCI);
+			mdelay(2);
+	} else {
+		ret = ath5k_hw_nic_reset(ah, AR5K_RESET_CTL_PCU |
+			AR5K_RESET_CTL_BASEBAND | bus_flags);
+	}
+
+	if (ret) {
+		ATH5K_ERR(ah->ah_sc, "failed to put device on warm reset\n");
+		return -EIO;
+	}
+
+	/* ...wakeup again!*/
+	ret = ath5k_hw_set_power(ah, AR5K_PM_AWAKE, true, 0);
+	if (ret) {
+		ATH5K_ERR(ah->ah_sc, "failed to put device on hold\n");
+		return ret;
+	}
+
+	return ret;
+}
+
+/*
  * Bring up MAC + PHY Chips and program PLL
  * TODO: Half/Quarter rate support
  */
@@ -318,6 +382,50 @@  int ath5k_hw_nic_wakeup(struct ath5k_hw *ah, int flags, bool initial)
 		return ret;
 	}
 
+	/*
+	 * Put chipset on warm reset...
+	 *
+	 * Note: puting PCI core on warm reset on PCI-E cards
+	 * results card to hang and always return 0xffff... so
+	 * we ingore that flag for PCI-E cards. On PCI cards
+	 * this flag gets cleared after 64 PCI clocks.
+	 */
+	bus_flags = (pdev->is_pcie) ? 0 : AR5K_RESET_CTL_PCI;
+
+	if (ah->ah_version == AR5K_AR5210) {
+		ret = ath5k_hw_nic_reset(ah, AR5K_RESET_CTL_PCU |
+			AR5K_RESET_CTL_MAC | AR5K_RESET_CTL_DMA |
+			AR5K_RESET_CTL_PHY | AR5K_RESET_CTL_PCI);
+			mdelay(2);
+	} else {
+		ret = ath5k_hw_nic_reset(ah, AR5K_RESET_CTL_PCU |
+			AR5K_RESET_CTL_BASEBAND | bus_flags);
+	}
+
+	if (ret) {
+		ATH5K_ERR(ah->ah_sc, "failed to reset the MAC Chip\n");
+		return -EIO;
+	}
+
+	/* ...wakeup again!...*/
+	ret = ath5k_hw_set_power(ah, AR5K_PM_AWAKE, true, 0);
+	if (ret) {
+		ATH5K_ERR(ah->ah_sc, "failed to resume the MAC Chip\n");
+		return ret;
+	}
+
+	/* ...clear reset control register and pull device out of
+	 * warm reset */
+	if (ath5k_hw_nic_reset(ah, 0)) {
+		ATH5K_ERR(ah->ah_sc, "failed to warm reset the MAC Chip\n");
+		return -EIO;
+	}
+
+	/* On initialization skip PLL programming since we don't have
+	 * a channel / mode set yet */
+	if (initial)
+		return 0;
+
 	if (ah->ah_version != AR5K_AR5210) {
 		/*
 		 * Get channel mode flags
@@ -383,39 +491,6 @@  int ath5k_hw_nic_wakeup(struct ath5k_hw *ah, int flags, bool initial)
 					AR5K_PHY_TURBO);
 	}
 
-	/* reseting PCI on PCI-E cards results card to hang
-	 * and always return 0xffff... so we ingore that flag
-	 * for PCI-E cards */
-	bus_flags = (pdev->is_pcie) ? 0 : AR5K_RESET_CTL_PCI;
-
-	/* Reset chipset */
-	if (ah->ah_version == AR5K_AR5210) {
-		ret = ath5k_hw_nic_reset(ah, AR5K_RESET_CTL_PCU |
-			AR5K_RESET_CTL_MAC | AR5K_RESET_CTL_DMA |
-			AR5K_RESET_CTL_PHY | AR5K_RESET_CTL_PCI);
-			mdelay(2);
-	} else {
-		ret = ath5k_hw_nic_reset(ah, AR5K_RESET_CTL_PCU |
-			AR5K_RESET_CTL_BASEBAND | bus_flags);
-	}
-	if (ret) {
-		ATH5K_ERR(ah->ah_sc, "failed to reset the MAC Chip\n");
-		return -EIO;
-	}
-
-	/* ...wakeup again!*/
-	ret = ath5k_hw_set_power(ah, AR5K_PM_AWAKE, true, 0);
-	if (ret) {
-		ATH5K_ERR(ah->ah_sc, "failed to resume the MAC Chip\n");
-		return ret;
-	}
-
-	/* ...final warm reset */
-	if (ath5k_hw_nic_reset(ah, 0)) {
-		ATH5K_ERR(ah->ah_sc, "failed to warm reset the MAC Chip\n");
-		return -EIO;
-	}
-
 	if (ah->ah_version != AR5K_AR5210) {
 
 		/* ...update PLL if needed */