This commit was manufactured by cvs2svn to create branch 'vserver'.
[linux-2.6.git] / drivers / net / wireless / bcm43xx / bcm43xx_power.c
diff --git a/drivers/net/wireless/bcm43xx/bcm43xx_power.c b/drivers/net/wireless/bcm43xx/bcm43xx_power.c
new file mode 100644 (file)
index 0000000..6569da3
--- /dev/null
@@ -0,0 +1,379 @@
+/*
+
+  Broadcom BCM43xx wireless driver
+
+  Copyright (c) 2005 Martin Langer <martin-langer@gmx.de>,
+                     Stefano Brivio <st3@riseup.net>
+                     Michael Buesch <mbuesch@freenet.de>
+                     Danny van Dyk <kugelfang@gentoo.org>
+                     Andreas Jaggi <andreas.jaggi@waterwave.ch>
+
+  Some parts of the code in this file are derived from the ipw2200
+  driver  Copyright(c) 2003 - 2004 Intel Corporation.
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING.  If not, write to
+  the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor,
+  Boston, MA 02110-1301, USA.
+
+*/
+
+#include <linux/delay.h>
+
+#include "bcm43xx.h"
+#include "bcm43xx_power.h"
+#include "bcm43xx_main.h"
+
+
+/* Get the Slow Clock Source */
+static int bcm43xx_pctl_get_slowclksrc(struct bcm43xx_private *bcm)
+{
+       u32 tmp;
+       int err;
+
+       assert(bcm->current_core == &bcm->core_chipcommon);
+       if (bcm->current_core->rev < 6) {
+               if (bcm->bustype == BCM43xx_BUSTYPE_PCMCIA ||
+                   bcm->bustype == BCM43xx_BUSTYPE_SB)
+                       return BCM43xx_PCTL_CLKSRC_XTALOS;
+               if (bcm->bustype == BCM43xx_BUSTYPE_PCI) {
+                       err = bcm43xx_pci_read_config32(bcm, BCM43xx_PCTL_OUT, &tmp);
+                       assert(!err);
+                       if (tmp & 0x10)
+                               return BCM43xx_PCTL_CLKSRC_PCI;
+                       return BCM43xx_PCTL_CLKSRC_XTALOS;
+               }
+       }
+       if (bcm->current_core->rev < 10) {
+               tmp = bcm43xx_read32(bcm, BCM43xx_CHIPCOMMON_SLOWCLKCTL);
+               tmp &= 0x7;
+               if (tmp == 0)
+                       return BCM43xx_PCTL_CLKSRC_LOPWROS;
+               if (tmp == 1)
+                       return BCM43xx_PCTL_CLKSRC_XTALOS;
+               if (tmp == 2)
+                       return BCM43xx_PCTL_CLKSRC_PCI;
+       }
+
+       return BCM43xx_PCTL_CLKSRC_XTALOS;
+}
+
+/* Get max/min slowclock frequency
+ * as described in http://bcm-specs.sipsolutions.net/PowerControl
+ */
+static int bcm43xx_pctl_clockfreqlimit(struct bcm43xx_private *bcm,
+                                      int get_max)
+{
+       int limit;
+       int clocksrc;
+       int divisor;
+       u32 tmp;
+
+       assert(bcm->chipcommon_capabilities & BCM43xx_CAPABILITIES_PCTL);
+       assert(bcm->current_core == &bcm->core_chipcommon);
+
+       clocksrc = bcm43xx_pctl_get_slowclksrc(bcm);
+       if (bcm->current_core->rev < 6) {
+               switch (clocksrc) {
+               case BCM43xx_PCTL_CLKSRC_PCI:
+                       divisor = 64;
+                       break;
+               case BCM43xx_PCTL_CLKSRC_XTALOS:
+                       divisor = 32;
+                       break;
+               default:
+                       assert(0);
+                       divisor = 1;
+               }
+       } else if (bcm->current_core->rev < 10) {
+               switch (clocksrc) {
+               case BCM43xx_PCTL_CLKSRC_LOPWROS:
+                       divisor = 1;
+                       break;
+               case BCM43xx_PCTL_CLKSRC_XTALOS:
+               case BCM43xx_PCTL_CLKSRC_PCI:
+                       tmp = bcm43xx_read32(bcm, BCM43xx_CHIPCOMMON_SLOWCLKCTL);
+                       divisor = ((tmp & 0xFFFF0000) >> 16) + 1;
+                       divisor *= 4;
+                       break;
+               default:
+                       assert(0);
+                       divisor = 1;
+               }
+       } else {
+               tmp = bcm43xx_read32(bcm, BCM43xx_CHIPCOMMON_SYSCLKCTL);
+               divisor = ((tmp & 0xFFFF0000) >> 16) + 1;
+               divisor *= 4;
+       }
+
+       switch (clocksrc) {
+       case BCM43xx_PCTL_CLKSRC_LOPWROS:
+               if (get_max)
+                       limit = 43000;
+               else
+                       limit = 25000;
+               break;
+       case BCM43xx_PCTL_CLKSRC_XTALOS:
+               if (get_max)
+                       limit = 20200000;
+               else
+                       limit = 19800000;
+               break;
+       case BCM43xx_PCTL_CLKSRC_PCI:
+               if (get_max)
+                       limit = 34000000;
+               else
+                       limit = 25000000;
+               break;
+       default:
+               assert(0);
+               limit = 0;
+       }
+       limit /= divisor;
+
+       return limit;
+}
+
+
+/* init power control
+ * as described in http://bcm-specs.sipsolutions.net/PowerControl
+ */
+int bcm43xx_pctl_init(struct bcm43xx_private *bcm)
+{
+       int err, maxfreq;
+       struct bcm43xx_coreinfo *old_core;
+
+       if (!(bcm->chipcommon_capabilities & BCM43xx_CAPABILITIES_PCTL))
+               return 0;
+       old_core = bcm->current_core;
+       err = bcm43xx_switch_core(bcm, &bcm->core_chipcommon);
+       if (err == -ENODEV)
+               return 0;
+       if (err)
+               goto out;
+
+       maxfreq = bcm43xx_pctl_clockfreqlimit(bcm, 1);
+       bcm43xx_write32(bcm, BCM43xx_CHIPCOMMON_PLLONDELAY,
+                       (maxfreq * 150 + 999999) / 1000000);
+       bcm43xx_write32(bcm, BCM43xx_CHIPCOMMON_FREFSELDELAY,
+                       (maxfreq * 15 + 999999) / 1000000);
+
+       err = bcm43xx_switch_core(bcm, old_core);
+       assert(err == 0);
+
+out:
+       return err;
+}
+
+u16 bcm43xx_pctl_powerup_delay(struct bcm43xx_private *bcm)
+{
+       u16 delay = 0;
+       int err;
+       u32 pll_on_delay;
+       struct bcm43xx_coreinfo *old_core;
+       int minfreq;
+
+       if (bcm->bustype != BCM43xx_BUSTYPE_PCI)
+               goto out;
+       if (!(bcm->chipcommon_capabilities & BCM43xx_CAPABILITIES_PCTL))
+               goto out;
+       old_core = bcm->current_core;
+       err = bcm43xx_switch_core(bcm, &bcm->core_chipcommon);
+       if (err == -ENODEV)
+               goto out;
+
+       minfreq = bcm43xx_pctl_clockfreqlimit(bcm, 0);
+       pll_on_delay = bcm43xx_read32(bcm, BCM43xx_CHIPCOMMON_PLLONDELAY);
+       delay = (((pll_on_delay + 2) * 1000000) + (minfreq - 1)) / minfreq;
+
+       err = bcm43xx_switch_core(bcm, old_core);
+       assert(err == 0);
+
+out:
+       return delay;
+}
+
+/* set the powercontrol clock
+ * as described in http://bcm-specs.sipsolutions.net/PowerControl
+ */
+int bcm43xx_pctl_set_clock(struct bcm43xx_private *bcm, u16 mode)
+{
+       int err;
+       struct bcm43xx_coreinfo *old_core;
+       u32 tmp;
+
+       old_core = bcm->current_core;
+       err = bcm43xx_switch_core(bcm, &bcm->core_chipcommon);
+       if (err == -ENODEV)
+               return 0;
+       if (err)
+               goto out;
+       
+       if (bcm->core_chipcommon.rev < 6) {
+               if (mode == BCM43xx_PCTL_CLK_FAST) {
+                       err = bcm43xx_pctl_set_crystal(bcm, 1);
+                       if (err)
+                               goto out;
+               }
+       } else {
+               if ((bcm->chipcommon_capabilities & BCM43xx_CAPABILITIES_PCTL) &&
+                       (bcm->core_chipcommon.rev < 10)) {
+                       switch (mode) {
+                       case BCM43xx_PCTL_CLK_FAST:
+                               tmp = bcm43xx_read32(bcm, BCM43xx_CHIPCOMMON_SLOWCLKCTL);
+                               tmp = (tmp & ~BCM43xx_PCTL_FORCE_SLOW) | BCM43xx_PCTL_FORCE_PLL;
+                               bcm43xx_write32(bcm, BCM43xx_CHIPCOMMON_SLOWCLKCTL, tmp);
+                               break;
+                       case BCM43xx_PCTL_CLK_SLOW:
+                               tmp = bcm43xx_read32(bcm, BCM43xx_CHIPCOMMON_SLOWCLKCTL);
+                               tmp |= BCM43xx_PCTL_FORCE_SLOW;
+                               bcm43xx_write32(bcm, BCM43xx_CHIPCOMMON_SLOWCLKCTL, tmp);
+                               break;
+                       case BCM43xx_PCTL_CLK_DYNAMIC:
+                               tmp = bcm43xx_read32(bcm, BCM43xx_CHIPCOMMON_SLOWCLKCTL);
+                               tmp &= ~BCM43xx_PCTL_FORCE_SLOW;
+                               tmp |= BCM43xx_PCTL_FORCE_PLL;
+                               tmp &= ~BCM43xx_PCTL_DYN_XTAL;
+                               bcm43xx_write32(bcm, BCM43xx_CHIPCOMMON_SLOWCLKCTL, tmp);
+                       }
+               }
+       }
+       
+       err = bcm43xx_switch_core(bcm, old_core);
+       assert(err == 0);
+
+out:
+       return err;
+}
+
+int bcm43xx_pctl_set_crystal(struct bcm43xx_private *bcm, int on)
+{
+       int err;
+       u32 in, out, outenable;
+
+       err = bcm43xx_pci_read_config32(bcm, BCM43xx_PCTL_IN, &in);
+       if (err)
+               goto err_pci;
+       err = bcm43xx_pci_read_config32(bcm, BCM43xx_PCTL_OUT, &out);
+       if (err)
+               goto err_pci;
+       err = bcm43xx_pci_read_config32(bcm, BCM43xx_PCTL_OUTENABLE, &outenable);
+       if (err)
+               goto err_pci;
+
+       outenable |= (BCM43xx_PCTL_XTAL_POWERUP | BCM43xx_PCTL_PLL_POWERDOWN);
+
+       if (on) {
+               if (in & 0x40)
+                       return 0;
+
+               out |= (BCM43xx_PCTL_XTAL_POWERUP | BCM43xx_PCTL_PLL_POWERDOWN);
+
+               err = bcm43xx_pci_write_config32(bcm, BCM43xx_PCTL_OUT, out);
+               if (err)
+                       goto err_pci;
+               err = bcm43xx_pci_write_config32(bcm, BCM43xx_PCTL_OUTENABLE, outenable);
+               if (err)
+                       goto err_pci;
+               udelay(1000);
+
+               out &= ~BCM43xx_PCTL_PLL_POWERDOWN;
+               err = bcm43xx_pci_write_config32(bcm, BCM43xx_PCTL_OUT, out);
+               if (err)
+                       goto err_pci;
+               udelay(5000);
+       } else {
+               if (bcm->current_core->rev < 5)
+                       return 0;
+               if (bcm->sprom.boardflags & BCM43xx_BFL_XTAL_NOSLOW)
+                       return 0;
+
+/*             XXX: Why BCM43xx_MMIO_RADIO_HWENABLED_xx can't be read at this time?
+ *             err = bcm43xx_switch_core(bcm, bcm->active_80211_core);
+ *             if (err)
+ *                     return err;
+ *             if (((bcm->current_core->rev >= 3) &&
+ *                     (bcm43xx_read32(bcm, BCM43xx_MMIO_RADIO_HWENABLED_HI) & (1 << 16))) ||
+ *                   ((bcm->current_core->rev < 3) &&
+ *                     !(bcm43xx_read16(bcm, BCM43xx_MMIO_RADIO_HWENABLED_LO) & (1 << 4))))
+ *                     return 0;
+ *             err = bcm43xx_switch_core(bcm, &bcm->core_chipcommon);
+ *             if (err)
+ *                     return err;
+ */
+               
+               err = bcm43xx_pctl_set_clock(bcm, BCM43xx_PCTL_CLK_SLOW);
+               if (err)
+                       goto out;
+               out &= ~BCM43xx_PCTL_XTAL_POWERUP;
+               out |= BCM43xx_PCTL_PLL_POWERDOWN;
+               err = bcm43xx_pci_write_config32(bcm, BCM43xx_PCTL_OUT, out);
+               if (err)
+                       goto err_pci;
+               err = bcm43xx_pci_write_config32(bcm, BCM43xx_PCTL_OUTENABLE, outenable);
+               if (err)
+                       goto err_pci;
+       }
+
+out:
+       return err;
+
+err_pci:
+       printk(KERN_ERR PFX "Error: pctl_set_clock() could not access PCI config space!\n");
+       err = -EBUSY;
+       goto out;
+}
+
+/* Set the PowerSavingControlBits.
+ * Bitvalues:
+ *   0  => unset the bit
+ *   1  => set the bit
+ *   -1 => calculate the bit
+ */
+void bcm43xx_power_saving_ctl_bits(struct bcm43xx_private *bcm,
+                                  int bit25, int bit26)
+{
+       int i;
+       u32 status;
+
+//FIXME: Force 25 to off and 26 to on for now:
+bit25 = 0;
+bit26 = 1;
+
+       if (bit25 == -1) {
+               //TODO: If powersave is not off and FIXME is not set and we are not in adhoc
+               //      and thus is not an AP and we are associated, set bit 25
+       }
+       if (bit26 == -1) {
+               //TODO: If the device is awake or this is an AP, or we are scanning, or FIXME,
+               //      or we are associated, or FIXME, or the latest PS-Poll packet sent was
+               //      successful, set bit26
+       }
+       status = bcm43xx_read32(bcm, BCM43xx_MMIO_STATUS_BITFIELD);
+       if (bit25)
+               status |= BCM43xx_SBF_PS1;
+       else
+               status &= ~BCM43xx_SBF_PS1;
+       if (bit26)
+               status |= BCM43xx_SBF_PS2;
+       else
+               status &= ~BCM43xx_SBF_PS2;
+       bcm43xx_write32(bcm, BCM43xx_MMIO_STATUS_BITFIELD, status);
+       if (bit26 && bcm->current_core->rev >= 5) {
+               for (i = 0; i < 100; i++) {
+                       if (bcm43xx_shm_read32(bcm, BCM43xx_SHM_SHARED, 0x0040) != 4)
+                               break;
+                       udelay(10);
+               }
+       }
+}