/*
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);
}
}
}