/*

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