| /* |
| |
| 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; |
| |
| old_core = bcm->current_core; |
| err = bcm43xx_switch_core(bcm, &bcm->core_chipcommon); |
| if (err == -ENODEV) |
| return 0; |
| if (err) |
| goto out; |
| |
| if (bcm->chip_id == 0x4321) { |
| if (bcm->chip_rev == 0) |
| bcm43xx_write32(bcm, BCM43xx_CHIPCOMMON_CTL, 0x03A4); |
| if (bcm->chip_rev == 1) |
| bcm43xx_write32(bcm, BCM43xx_CHIPCOMMON_CTL, 0x00A4); |
| } |
| |
| if (bcm->chipcommon_capabilities & BCM43xx_CAPABILITIES_PCTL) { |
| if (bcm->current_core->rev >= 10) { |
| /* Set Idle Power clock rate to 1Mhz */ |
| bcm43xx_write32(bcm, BCM43xx_CHIPCOMMON_SYSCLKCTL, |
| (bcm43xx_read32(bcm, BCM43xx_CHIPCOMMON_SYSCLKCTL) |
| & 0x0000FFFF) | 0x40000); |
| } else { |
| 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); |
| } |
| } |
| } |