/* -*- linux-c -*- */
/*-
 * Copyright (c) 2007 Nick Kossifidis <mickflemm@gmail.com>
 * Copyright (c) 2007 Joerg Albert    <jal2 *at* gmx.de>
 *
 * This program is free software you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * 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.  If not, see <http://www.gnu.org/licenses/>.
 */

/* Try accepting 64-bit device address even with 32-bit userspace */
#define _FILE_OFFSET_BITS 64

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <endian.h>
#include <byteswap.h>
#include "eeprom.h"

#undef ARRAY_SIZE
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))

#define dbg(...) \
do { \
	if (verbose) \
		printf("#DBG %s: ", __func__); \
		printf(__VA_ARGS__); \
		printf("\n"); \
 } while (0)

#define err(...) \
do { \
	printf("#ERR %s: ", __func__); \
	printf(__VA_ARGS__); \
	printf("\n"); \
 } while (0)

#define AR5K_PCI_MEM_SIZE 0x10000

#define AR5K_NUM_GPIO	6

#define AR5K_GPIOCR		0x4014	/* Register Address */
#define AR5K_GPIOCR_OUT(n)	(3 << ((n) * 2))	/* Mode 3 for pin n */
#define AR5K_GPIOCR_INT_SEL(n)	((n) << 12)	/* Interrupt for GPIO pin n */

/*
 * GPIO (General Purpose Input/Output) data output register
 */
#define AR5K_GPIODO	0x4018

/*
 * GPIO (General Purpose Input/Output) data input register
 */
#define AR5K_GPIODI	0x401c

struct ath5k_srev_name {
	const char *sr_name;
	u_int8_t sr_val;
};

#define AR5K_SREV_UNKNOWN	0xff

#define AR5K_SREV_AR5210	0x00 /* Crete */
#define AR5K_SREV_AR5311	0x10 /* Maui 1 */
#define AR5K_SREV_AR5311A	0x20 /* Maui 2 */
#define AR5K_SREV_AR5311B	0x30 /* Spirit */
#define AR5K_SREV_AR5211	0x40 /* Oahu */
#define AR5K_SREV_AR5212	0x50 /* Venice */
#define AR5K_SREV_AR5213	0x55 /* ??? */
#define AR5K_SREV_AR5213A	0x59 /* Hainan */
#define AR5K_SREV_AR2413	0x78 /* Griffin lite */
#define AR5K_SREV_AR2414	0x70 /* Griffin */
#define AR5K_SREV_AR5424	0x90 /* Condor */
#define AR5K_SREV_AR5413	0xa4 /* Eagle lite */
#define AR5K_SREV_AR5414	0xa0 /* Eagle */
#define AR5K_SREV_AR2415	0xb0 /* Cobra */
#define AR5K_SREV_AR5416	0xc0 /* PCI-E */
#define AR5K_SREV_AR5418	0xca /* PCI-E */
#define AR5K_SREV_AR2425	0xe0 /* Swan */
#define AR5K_SREV_AR2417	0xf0 /* Nala */

#define AR5K_SREV_RAD_5110	0x00
#define AR5K_SREV_RAD_5111	0x10
#define AR5K_SREV_RAD_5111A	0x15
#define AR5K_SREV_RAD_2111	0x20
#define AR5K_SREV_RAD_5112	0x30
#define AR5K_SREV_RAD_5112A	0x35
#define	AR5K_SREV_RAD_5112B	0x36
#define AR5K_SREV_RAD_2112	0x40
#define AR5K_SREV_RAD_2112A	0x45
#define	AR5K_SREV_RAD_2112B	0x46
#define AR5K_SREV_RAD_2413	0x50
#define AR5K_SREV_RAD_5413	0x60
#define AR5K_SREV_RAD_2316	0x70
#define AR5K_SREV_RAD_2317	0x80
#define AR5K_SREV_RAD_5424	0xa0 /* Mostly same as 5413 */
#define AR5K_SREV_RAD_2425	0xa2
#define AR5K_SREV_RAD_5133	0xc0

#define AR5K_SREV_PHY_5211	0x30
#define AR5K_SREV_PHY_5212	0x41
#define AR5K_SREV_PHY_2112B	0x43
#define AR5K_SREV_PHY_2413	0x45
#define AR5K_SREV_PHY_5413	0x61
#define AR5K_SREV_PHY_2425	0x70

static const struct ath5k_srev_name ath5k_mac_names[] = {
	{ "5210",	AR5K_SREV_AR5210 },
	{ "5311",	AR5K_SREV_AR5311 },
	{ "5311A",	AR5K_SREV_AR5311A },
	{ "5311B",	AR5K_SREV_AR5311B },
	{ "5211",	AR5K_SREV_AR5211 },
	{ "5212",	AR5K_SREV_AR5212 },
	{ "5213",	AR5K_SREV_AR5213 },
	{ "5213A",	AR5K_SREV_AR5213A },
	{ "2413",	AR5K_SREV_AR2413 },
	{ "2414",	AR5K_SREV_AR2414 },
	{ "5424",	AR5K_SREV_AR5424 },
	{ "5413",	AR5K_SREV_AR5413 },
	{ "5414",	AR5K_SREV_AR5414 },
	{ "2415",	AR5K_SREV_AR2415 },
	{ "5416",	AR5K_SREV_AR5416 },
	{ "5418",	AR5K_SREV_AR5418 },
	{ "2425",	AR5K_SREV_AR2425 },
	{ "2417",	AR5K_SREV_AR2417 },
	{ "xxxxx",	AR5K_SREV_UNKNOWN },
};

static const struct ath5k_srev_name ath5k_phy_names[] = {
	{ "5110",	AR5K_SREV_RAD_5110 },
	{ "5111",	AR5K_SREV_RAD_5111 },
	{ "5111A",	AR5K_SREV_RAD_5111A },
	{ "2111",	AR5K_SREV_RAD_2111 },
	{ "5112",	AR5K_SREV_RAD_5112 },
	{ "5112A",	AR5K_SREV_RAD_5112A },
	{ "5112B",	AR5K_SREV_RAD_5112B },
	{ "2112",	AR5K_SREV_RAD_2112 },
	{ "2112A",	AR5K_SREV_RAD_2112A },
	{ "2112B",	AR5K_SREV_RAD_2112B },
	{ "2413",	AR5K_SREV_RAD_2413 },
	{ "5413",	AR5K_SREV_RAD_5413 },
	{ "2316",	AR5K_SREV_RAD_2316 },
	{ "2317",	AR5K_SREV_RAD_2317 },
	{ "5424",	AR5K_SREV_RAD_5424 },
	{ "5133",	AR5K_SREV_RAD_5133 },
	{ "xxxxx",	AR5K_SREV_UNKNOWN },
};

/*
 * Silicon revision register
 */
#define AR5K_SREV		0x4020	/* Register Address */
#define AR5K_SREV_VER		0x000000f0	/* Mask for version */
#define AR5K_SREV_REV		0x000000ff	/* Mask for revision */

/*
 * PHY chip revision register
 */
#define AR5K_PHY_CHIP_ID		0x9818

/*
 * PHY register
 */
#define AR5K_PHY_BASE			0x9800
#define AR5K_PHY(_n)			(AR5K_PHY_BASE + ((_n) << 2))
#define AR5K_PHY_SHIFT_2GHZ		0x00004007
#define AR5K_PHY_SHIFT_5GHZ		0x00000007

#define AR5K_RESET_CTL		0x4000	/* Register Address */
#define AR5K_RESET_CTL_PCU	0x00000001	/* Protocol Control Unit reset */
#define AR5K_RESET_CTL_DMA	0x00000002	/* DMA (Rx/Tx) reset -5210 only */
#define AR5K_RESET_CTL_BASEBAND	0x00000002	/* Baseband reset (5211/5212) */
#define AR5K_RESET_CTL_MAC	0x00000004	/* MAC reset (PCU+Baseband?) -5210 only */
#define AR5K_RESET_CTL_PHY	0x00000008	/* PHY reset -5210 only */
#define AR5K_RESET_CTL_PCI	0x00000010	/* PCI Core reset (interrupts etc) */
#define AR5K_RESET_CTL_CHIP	(AR5K_RESET_CTL_PCU | AR5K_RESET_CTL_DMA |	\
				AR5K_RESET_CTL_MAC | AR5K_RESET_CTL_PHY)

/*
 * Sleep control register
 */
#define AR5K_SLEEP_CTL			0x4004	/* Register Address */
#define AR5K_SLEEP_CTL_SLDUR		0x0000ffff	/* Sleep duration mask */
#define AR5K_SLEEP_CTL_SLDUR_S		0
#define AR5K_SLEEP_CTL_SLE		0x00030000	/* Sleep enable mask */
#define AR5K_SLEEP_CTL_SLE_S		16
#define AR5K_SLEEP_CTL_SLE_WAKE		0x00000000	/* Force chip awake */
#define AR5K_SLEEP_CTL_SLE_SLP		0x00010000	/* Force chip sleep */
#define AR5K_SLEEP_CTL_SLE_ALLOW	0x00020000
#define AR5K_SLEEP_CTL_SLE_UNITS	0x00000008	/* not on 5210 */

#define AR5K_PCICFG			0x4010	/* Register Address */
#define AR5K_PCICFG_EEAE		0x00000001	/* EEPROM access enable [5210] */
#define AR5K_PCICFG_CLKRUNEN		0x00000004	/* CLKRUN enable [5211+] */
#define AR5K_PCICFG_EESIZE		0x00000018	/* Mask for EEPROM size [5211+] */
#define AR5K_PCICFG_EESIZE_S		3
#define AR5K_PCICFG_EESIZE_4K		0	/* 4K */
#define AR5K_PCICFG_EESIZE_8K		1	/* 8K */
#define AR5K_PCICFG_EESIZE_16K		2	/* 16K */
#define AR5K_PCICFG_EESIZE_FAIL		3	/* Failed to get size (?) [5211+] */

#define AR5K_PCICFG_SPWR_DN		0x00010000	/* Mask for power status (5210) */

#define AR5K_EEPROM_BASE	0x6000

/*
 * EEPROM data register
 */
#define AR5K_EEPROM_DATA_5211	0x6004
#define AR5K_EEPROM_DATA_5210	0x6800
#define AR5K_EEPROM_DATA	(eeprom_access == AR5K_EEPROM_ACCESS_5210 ? \
				AR5K_EEPROM_DATA_5210 : AR5K_EEPROM_DATA_5211)

/*
 * EEPROM command register
 */
#define AR5K_EEPROM_CMD		0x6008			/* Register Address */
#define AR5K_EEPROM_CMD_READ	0x00000001	/* EEPROM read */
#define AR5K_EEPROM_CMD_WRITE	0x00000002	/* EEPROM write */
#define AR5K_EEPROM_CMD_RESET	0x00000004	/* EEPROM reset */

/*
 * EEPROM status register
 */
#define AR5K_EEPROM_STAT_5210	0x6c00			/* Register Address [5210] */
#define AR5K_EEPROM_STAT_5211	0x600c			/* Register Address [5211+] */
#define AR5K_EEPROM_STATUS	(eeprom_access == AR5K_EEPROM_ACCESS_5210 ? \
				AR5K_EEPROM_STAT_5210 : AR5K_EEPROM_STAT_5211)
#define AR5K_EEPROM_STAT_RDERR	0x00000001	/* EEPROM read failed */
#define AR5K_EEPROM_STAT_RDDONE	0x00000002	/* EEPROM read successful */
#define AR5K_EEPROM_STAT_WRERR	0x00000004	/* EEPROM write failed */
#define AR5K_EEPROM_STAT_WRDONE	0x00000008	/* EEPROM write successful */

/*
 * EEPROM config register (?)
 */
#define AR5K_EEPROM_CFG	0x6010

/*
 * Read data by masking
 */
#define AR5K_REG_MS(_val, _flags)	\
	(((_val) & (_flags)) >> _flags##_S)

/*
 * Access device registers
 */
#if __BYTE_ORDER == __BIG_ENDIAN
#define AR5K_REG_READ(_reg)		\
	__bswap_32(*((volatile u_int32_t *)(mem + (_reg))))
#define AR5K_REG_WRITE(_reg, _val)	\
	(*((volatile u_int32_t *)(mem + (_reg))) = __bswap_32(_val))
#else
#define AR5K_REG_READ(_reg)		\
	(*((volatile u_int32_t *)(mem + (_reg))))
#define AR5K_REG_WRITE(_reg, _val)	\
	(*((volatile u_int32_t *)(mem + (_reg))) = (_val))
#endif

#define AR5K_REG_ENABLE_BITS(_reg, _flags)	\
	AR5K_REG_WRITE(_reg, AR5K_REG_READ(_reg) | (_flags))

#define AR5K_REG_DISABLE_BITS(_reg, _flags)	\
	AR5K_REG_WRITE(_reg, AR5K_REG_READ(_reg) & ~(_flags))

#define AR5K_TUNE_REGISTER_TIMEOUT		20000

#define AR5K_EEPROM_READ(_o, _v) do {					\
	if ((ret = ath5k_hw_eeprom_read((_o), &(_v))) != 0)	\
		return (ret);						\
} while (0)

/* Names for EEPROM fields */
struct eeprom_entry {
	const char *name;
	int addr;
};

static const struct eeprom_entry eeprom_addr[] = {
	{"pci_dev_id", 0},
	{"pci_vendor_id", 1},
	{"pci_class", 2},
	{"pci_rev_id", 3},
	{"pci_subsys_dev_id", 7},
	{"pci_subsys_vendor_id", 8},
	{"regdomain", AR5K_EEPROM_REG_DOMAIN},
};

/* Command line settings */
static int force_write = 0;
static int verbose = 0;

/* Global device characteristics */
static enum {
	AR5K_EEPROM_ACCESS_5210,
	AR5K_EEPROM_ACCESS_5211,
	AR5K_EEPROM_ACCESS_5416
} eeprom_access;
static unsigned int eeprom_size;
static int mac_revision;
static void *mem;

/* forward decl. */
static void usage(const char *n);

static u_int32_t ath5k_hw_bitswap(u_int32_t val, u_int bits)
{
	u_int32_t retval = 0, bit, i;

	for (i = 0; i < bits; i++) {
		bit = (val >> i) & 1;
		retval = (retval << 1) | bit;
	}

	return (retval);
}

/*
 * Get the PHY Chip revision
 */
static u_int16_t ath5k_hw_radio_revision(u_int8_t chip)
{
	int i;
	u_int32_t srev;
	u_int16_t ret;

	/*
	 * Set the radio chip access register
	 */
	switch (chip) {
	case 0:
		AR5K_REG_WRITE(AR5K_PHY(0), AR5K_PHY_SHIFT_2GHZ);
		break;
	case 1:
		AR5K_REG_WRITE(AR5K_PHY(0), AR5K_PHY_SHIFT_5GHZ);
		break;
	default:
		return (0);
	}

	usleep(2000);

	/* ...wait until PHY is ready and read the selected radio revision */
	AR5K_REG_WRITE(AR5K_PHY(0x34), 0x00001c16);

	for (i = 0; i < 8; i++)
		AR5K_REG_WRITE(AR5K_PHY(0x20), 0x00010000);

	if (mac_revision == AR5K_SREV_AR5210) {
		srev = AR5K_REG_READ(AR5K_PHY(256) >> 28) & 0xf;

		ret = (u_int16_t)ath5k_hw_bitswap(srev, 4) + 1;
	} else {
		srev = (AR5K_REG_READ(AR5K_PHY(0x100)) >> 24) & 0xff;

		ret = (u_int16_t)ath5k_hw_bitswap(((srev & 0xf0) >> 4) |
						  ((srev & 0x0f) << 4), 8);
	}

	/* Reset to the 5GHz mode */
	AR5K_REG_WRITE(AR5K_PHY(0), AR5K_PHY_SHIFT_5GHZ);

	return (ret);
}

/*
 * Write to EEPROM
 */
static int ath5k_hw_eeprom_write(u_int32_t offset, u_int16_t data)
{
	u_int32_t status, timeout;

	/*
	 * Initialize EEPROM access
	 */

	if (eeprom_access == AR5K_EEPROM_ACCESS_5210) {

		AR5K_REG_ENABLE_BITS(AR5K_PCICFG, AR5K_PCICFG_EEAE);

		/* data to write */
		(void)AR5K_REG_WRITE(AR5K_EEPROM_BASE + (4 * offset), data);

	} else {
		/* not 5210 */
		/* reset EEPROM access */
		AR5K_REG_WRITE(AR5K_EEPROM_CMD, AR5K_EEPROM_CMD_RESET);
		usleep(5);

		AR5K_REG_WRITE(AR5K_EEPROM_DATA, data);

		/* set offset in EEPROM to write to */
		AR5K_REG_WRITE(AR5K_EEPROM_BASE, offset);
		usleep(5);

		/* issue write command */
		AR5K_REG_WRITE(AR5K_EEPROM_CMD, AR5K_EEPROM_CMD_WRITE);
	}

	for (timeout = AR5K_TUNE_REGISTER_TIMEOUT; timeout > 0; timeout--) {
		status = AR5K_REG_READ(AR5K_EEPROM_STATUS);
		if (status & AR5K_EEPROM_STAT_WRDONE) {
			if (status & AR5K_EEPROM_STAT_WRERR) {
				err("EEPROM write access to 0x%04x failed",
				    offset);
				return 1;
			}
			return 0;
		}
		usleep(15);
	}

	return 1;
}

/*
 * Read from EEPROM
 */
static int ath5k_hw_eeprom_read(u_int32_t offset, u_int16_t *data)
{
	u_int32_t status, timeout;

	/*
	 * Initialize EEPROM access
	 */
	if (eeprom_access == AR5K_EEPROM_ACCESS_5210) {
		AR5K_REG_ENABLE_BITS(AR5K_PCICFG, AR5K_PCICFG_EEAE);
		(void)AR5K_REG_READ(AR5K_EEPROM_BASE + (4 * offset));
	} else {
		AR5K_REG_WRITE(AR5K_EEPROM_BASE, offset);
		AR5K_REG_ENABLE_BITS(AR5K_EEPROM_CMD, AR5K_EEPROM_CMD_READ);
	}

	for (timeout = AR5K_TUNE_REGISTER_TIMEOUT; timeout > 0; timeout--) {
		status = AR5K_REG_READ(AR5K_EEPROM_STATUS);
		if (status & AR5K_EEPROM_STAT_RDDONE) {
			if (status & AR5K_EEPROM_STAT_RDERR)
				return 1;
			*data = (u_int16_t)
			    (AR5K_REG_READ(AR5K_EEPROM_DATA) & 0xffff);
			return (0);
		}
		usleep(15);
	}

	return 1;
}

/*
 * Translate binary channel representation in EEPROM to frequency
 */
static u_int16_t ath5k_eeprom_bin2freq(struct ath5k_eeprom_info *ee,
				       u_int16_t bin, unsigned int mode)
{
	u_int16_t val;

	if (bin == AR5K_EEPROM_CHANNEL_DIS)
		return bin;

	if (mode == AR5K_EEPROM_MODE_11A) {
		if (ee->ee_version > AR5K_EEPROM_VERSION_3_2)
			val = (5 * bin) + 4800;
		else
			val = bin > 62 ? (10 * 62) + (5 * (bin - 62)) + 5100 :
			    (bin * 10) + 5100;
	} else {
		if (ee->ee_version > AR5K_EEPROM_VERSION_3_2)
			val = bin + 2300;
		else
			val = bin + 2400;
	}

	return val;
}

/*
 * Read antenna info from EEPROM
 */
static int ath5k_eeprom_read_ants(struct ath5k_eeprom_info *ee,
				  u_int32_t *offset, unsigned int mode)
{
	u_int32_t o = *offset;
	u_int16_t val;
	int ret, i = 0;

	AR5K_EEPROM_READ(o++, val);
	ee->ee_switch_settling[mode]	= (val >> 8) & 0x7f;
	ee->ee_atn_tx_rx[mode]		= (val >> 2) & 0x3f;
	ee->ee_ant_control[mode][i]	= (val << 4) & 0x3f;

	AR5K_EEPROM_READ(o++, val);
	ee->ee_ant_control[mode][i++]	|= (val >> 12) & 0xf;
	ee->ee_ant_control[mode][i++]	= (val >> 6) & 0x3f;
	ee->ee_ant_control[mode][i++]	= val & 0x3f;

	AR5K_EEPROM_READ(o++, val);
	ee->ee_ant_control[mode][i++]	= (val >> 10) & 0x3f;
	ee->ee_ant_control[mode][i++]	= (val >> 4) & 0x3f;
	ee->ee_ant_control[mode][i]	= (val << 2) & 0x3f;

	AR5K_EEPROM_READ(o++, val);
	ee->ee_ant_control[mode][i++]	|= (val >> 14) & 0x3;
	ee->ee_ant_control[mode][i++]	= (val >> 8) & 0x3f;
	ee->ee_ant_control[mode][i++]	= (val >> 2) & 0x3f;
	ee->ee_ant_control[mode][i]	= (val << 4) & 0x3f;

	AR5K_EEPROM_READ(o++, val);
	ee->ee_ant_control[mode][i++]	|= (val >> 12) & 0xf;
	ee->ee_ant_control[mode][i++]	= (val >> 6) & 0x3f;
	ee->ee_ant_control[mode][i++]	= val & 0x3f;

	/* Get antenna modes */
	ee->ee_antenna[mode][0] =
	    (ee->ee_ant_control[mode][0] << 4) | 0x1;
	ee->ee_antenna[mode][AR5K_ANT_FIXED_A] =
	     ee->ee_ant_control[mode][1]	|
	    (ee->ee_ant_control[mode][2] << 6)	|
	    (ee->ee_ant_control[mode][3] << 12) |
	    (ee->ee_ant_control[mode][4] << 18) |
	    (ee->ee_ant_control[mode][5] << 24);
	ee->ee_antenna[mode][AR5K_ANT_FIXED_B] =
	     ee->ee_ant_control[mode][6]	|
	    (ee->ee_ant_control[mode][7] << 6)	|
	    (ee->ee_ant_control[mode][8] << 12) |
	    (ee->ee_ant_control[mode][9] << 18) |
	    (ee->ee_ant_control[mode][10] << 24);

	/* return new offset */
	*offset = o;

	return 0;
}

/*
 * Read supported modes from EEPROM
 */
static int ath5k_eeprom_read_modes(struct ath5k_eeprom_info *ee,
				   u_int32_t *offset, unsigned int mode)
{
	u_int32_t o = *offset;
	u_int16_t val;
	int ret;

	switch (mode){
	case AR5K_EEPROM_MODE_11A:
		AR5K_EEPROM_READ(o++, val);
		ee->ee_adc_desired_size[mode]	= (int8_t)((val >> 8) & 0xff);
		ee->ee_ob[mode][3]		= (val >> 5) & 0x7;
		ee->ee_db[mode][3]		= (val >> 2) & 0x7;
		ee->ee_ob[mode][2]		= (val << 1) & 0x7;

		AR5K_EEPROM_READ(o++, val);
		ee->ee_ob[mode][2]		|= (val >> 15) & 0x1;
		ee->ee_db[mode][2]		= (val >> 12) & 0x7;
		ee->ee_ob[mode][1]		= (val >> 9) & 0x7;
		ee->ee_db[mode][1]		= (val >> 6) & 0x7;
		ee->ee_ob[mode][0]		= (val >> 3) & 0x7;
		ee->ee_db[mode][0]		= val & 0x7;
		break;
	case AR5K_EEPROM_MODE_11B:
		AR5K_EEPROM_READ(o++, val);
		ee->ee_adc_desired_size[mode]	= (int8_t)((val >> 8) & 0xff);
		ee->ee_ob[mode][1]		= (val >> 4) & 0x7;
		ee->ee_db[mode][1]		= val & 0x7;
		break;
	case AR5K_EEPROM_MODE_11G:
		AR5K_EEPROM_READ(o++, val);
		ee->ee_adc_desired_size[mode]	= (signed short int)((val >> 8) & 0xff);
		ee->ee_ob[mode][1]		= (val >> 4) & 0x7;
		ee->ee_db[mode][1]		= val & 0x7;
		break;
	}

	AR5K_EEPROM_READ(o++, val);
	ee->ee_tx_end2xlna_enable[mode]	= (val >> 8) & 0xff;
	ee->ee_thr_62[mode]		= val & 0xff;

	if (ee->ee_version <= AR5K_EEPROM_VERSION_3_2)
		ee->ee_thr_62[mode] = mode == AR5K_EEPROM_MODE_11A ? 15 : 28;

	AR5K_EEPROM_READ(o++, val);
	ee->ee_tx_end2xpa_disable[mode]	= (val >> 8) & 0xff;
	ee->ee_tx_frm2xpa_enable[mode]	= val & 0xff;

	AR5K_EEPROM_READ(o++, val);
	ee->ee_pga_desired_size[mode]	= (val >> 8) & 0xff;

	if ((val & 0xff) & 0x80)
		ee->ee_noise_floor_thr[mode] = -((((val & 0xff) ^ 0xff)) + 1);
	else
		ee->ee_noise_floor_thr[mode] = val & 0xff;

	if (ee->ee_version <= AR5K_EEPROM_VERSION_3_2)
		ee->ee_noise_floor_thr[mode] =
		    mode == AR5K_EEPROM_MODE_11A ? -54 : -1;

	AR5K_EEPROM_READ(o++, val);
	ee->ee_xlna_gain[mode]		= (val >> 5) & 0xff;
	ee->ee_x_gain[mode]		= (val >> 1) & 0xf;
	ee->ee_xpd[mode]		= val & 0x1;

	if (ee->ee_version >= AR5K_EEPROM_VERSION_4_0)
		ee->ee_fixed_bias[mode] = (val >> 13) & 0x1;

	if (ee->ee_version >= AR5K_EEPROM_VERSION_3_3) {
		AR5K_EEPROM_READ(o++, val);
		ee->ee_false_detect[mode] = (val >> 6) & 0x7f;

		if (mode == AR5K_EEPROM_MODE_11A)
			ee->ee_xr_power[mode] = val & 0x3f;
		else {
			ee->ee_ob[mode][0] = val & 0x7;
			ee->ee_db[mode][0] = (val >> 3) & 0x7;
		}
	}

	if (ee->ee_version < AR5K_EEPROM_VERSION_3_4) {
		ee->ee_i_gain[mode] = AR5K_EEPROM_I_GAIN;
		ee->ee_cck_ofdm_power_delta = AR5K_EEPROM_CCK_OFDM_DELTA;
	} else {
		ee->ee_i_gain[mode] = (val >> 13) & 0x7;

		AR5K_EEPROM_READ(o++, val);
		ee->ee_i_gain[mode] |= (val << 3) & 0x38;

		if (mode == AR5K_EEPROM_MODE_11G) {
			ee->ee_cck_ofdm_power_delta = (val >> 3) & 0xff;

			if (ee->ee_version >= AR5K_EEPROM_VERSION_4_6)
				ee->ee_scaled_cck_delta = (val >> 11) & 0x1f;
		}
	}

	if (ee->ee_version >= AR5K_EEPROM_VERSION_4_0 &&
	    mode == AR5K_EEPROM_MODE_11A) {
		ee->ee_i_cal[mode] = (val >> 8) & 0x3f;
		ee->ee_q_cal[mode] = (val >> 3) & 0x1f;
	}

	if (ee->ee_version >= AR5K_EEPROM_VERSION_4_0) {
		switch (mode) {
		case AR5K_EEPROM_MODE_11B:
			AR5K_EEPROM_READ(o++, val);

			ee->ee_cal_piers_b = 0;

			ee->ee_pwr_cal_b[0].freq =
				ath5k_eeprom_bin2freq(ee, val & 0xff, mode);
			if (ee->ee_pwr_cal_b[0].freq != AR5K_EEPROM_CHANNEL_DIS)
				ee->ee_cal_piers_b++;

			ee->ee_pwr_cal_b[1].freq =
				ath5k_eeprom_bin2freq(ee, (val >> 8) & 0xff, mode);
			if (ee->ee_pwr_cal_b[1].freq != AR5K_EEPROM_CHANNEL_DIS)
				ee->ee_cal_piers_b++;

			AR5K_EEPROM_READ(o++, val);
			ee->ee_pwr_cal_b[2].freq =
				ath5k_eeprom_bin2freq(ee, val & 0xff, mode);
			if (ee->ee_pwr_cal_b[2].freq != AR5K_EEPROM_CHANNEL_DIS)
				ee->ee_cal_piers_b++;
			break;
		case AR5K_EEPROM_MODE_11G:
			AR5K_EEPROM_READ(o++, val);

			ee->ee_cal_piers_g = 0;

			ee->ee_pwr_cal_g[0].freq =
				ath5k_eeprom_bin2freq(ee, val & 0xff, mode);
			if (ee->ee_pwr_cal_g[0].freq != AR5K_EEPROM_CHANNEL_DIS)
				ee->ee_cal_piers_g++;

			ee->ee_pwr_cal_g[1].freq =
				ath5k_eeprom_bin2freq(ee, (val >> 8) & 0xff, mode);
			if (ee->ee_pwr_cal_g[1].freq != AR5K_EEPROM_CHANNEL_DIS)
				ee->ee_cal_piers_g++;

			AR5K_EEPROM_READ(o++, val);
			ee->ee_turbo_max_power[mode] = val & 0x7f;
			ee->ee_xr_power[mode] = (val >> 7) & 0x3f;

			AR5K_EEPROM_READ(o++, val);
			ee->ee_pwr_cal_g[2].freq =
				ath5k_eeprom_bin2freq(ee, val & 0xff, mode);
			if (ee->ee_pwr_cal_g[2].freq != AR5K_EEPROM_CHANNEL_DIS)
				ee->ee_cal_piers_g++;
			break;
		}
	}

	if (ee->ee_version >= AR5K_EEPROM_VERSION_4_1) {
		switch (mode) {
		case AR5K_EEPROM_MODE_11A:
			AR5K_EEPROM_READ(o++, val);
			ee->ee_margin_tx_rx[mode] = val & 0x3f;
			break;
		case AR5K_EEPROM_MODE_11B:
		case AR5K_EEPROM_MODE_11G:
			ee->ee_margin_tx_rx[mode] = (val >> 8) & 0x3f;
			break;
		}
	}

	if (ee->ee_version >= AR5K_EEPROM_VERSION_4_0 &&
	    mode == AR5K_EEPROM_MODE_11G) {
		AR5K_EEPROM_READ(o++, val);
		ee->ee_i_cal[mode] = (val >> 8) & 0x3f;
		ee->ee_q_cal[mode] = (val >> 3) & 0x1f;

		if (ee->ee_version >= AR5K_EEPROM_VERSION_4_2) {
			AR5K_EEPROM_READ(o++, val);
			ee->ee_cck_ofdm_gain_delta = val & 0xff;
		}
	}

	/* return new offset */
	*offset = o;

	return 0;
}

/*
 * Read turbo mode information on newer EEPROM versions
 */
static int ath5k_eeprom_read_turbo_modes(struct ath5k_eeprom_info *ee,
				   u_int32_t *offset, unsigned int mode)
{
	u_int32_t o = *offset;
	u_int16_t val;
	int ret;

	if (ee->ee_version < AR5K_EEPROM_VERSION_5_0)
		return 0;

	switch (mode){
	case AR5K_EEPROM_MODE_11A:
		ee->ee_switch_settling_turbo[mode] = (val >> 6) & 0x7f;

		ee->ee_atn_tx_rx_turbo[mode] = (val >> 13) & 0x7;
		AR5K_EEPROM_READ(o++, val);
		ee->ee_atn_tx_rx_turbo[mode] |= (val & 0x7) << 3;
		ee->ee_margin_tx_rx_turbo[mode] = (val >> 3) & 0x3f;

		ee->ee_adc_desired_size_turbo[mode] = (val >> 9) & 0x7f;
		AR5K_EEPROM_READ(o++, val);
		ee->ee_adc_desired_size_turbo[mode] |= (val & 0x1) << 7;
		ee->ee_pga_desired_size_turbo[mode] = (val >> 1) & 0xff;

		if (AR5K_EEPROM_EEMAP(ee->ee_misc0) >=2)
			ee->ee_pd_gain_overlap = (val >> 9) & 0xf;
		break;
	case AR5K_EEPROM_MODE_11G:
		ee->ee_switch_settling_turbo[mode] = (val >> 8) & 0x7f;

		ee->ee_atn_tx_rx_turbo[mode] = (val >> 15) & 0x7;
		AR5K_EEPROM_READ(o++, val);
		ee->ee_atn_tx_rx_turbo[mode] |= (val & 0x1f) << 1;
		ee->ee_margin_tx_rx_turbo[mode] = (val >> 5) & 0x3f;

		ee->ee_adc_desired_size_turbo[mode] = (val >> 11) & 0x7f;
		AR5K_EEPROM_READ(o++, val);
		ee->ee_adc_desired_size_turbo[mode] |= (val & 0x7) << 5;
		ee->ee_pga_desired_size_turbo[mode] = (val >> 3) & 0xff;
		break;
	}

	/* return new offset */
	*offset = o;

	return 0;
}

/*
 * Read per channel calibration info from EEPROM
 *
 * This info is used to calibrate the baseband power table. Imagine
 * that for each channel there is a power curve that's hw specific
 * (depends on amplifier) and we try to "correct" this curve using offests
 * we pass on to phy chip (baseband -> before amplifier) so that it can
 * use accurate power values when setting tx power (takes amplifier's
 * performance on each channel into account).
 *
 * EEPROM provides us with the offsets for some pre-calibrated channels
 * and we have to scale (to create the full table for these channels) and
 * interpolate (in order to create the table for any channel).
 */

static int ath5k_eeprom_read_rf5111_pcal_info(struct ath5k_eeprom_info *ee,
							unsigned int mode)
{
	u_int32_t offset;
	unsigned int i, c;
	int ret;
	u_int16_t val;
	struct	ath5k_chan_pcal_info *gen_chan_info;
	struct ath5k_chan_pcal_info_rf5111 *chan_pcal_info;
	u_int16_t cal_piers;
	/* Fixed percentage intercepts */
	static const u_int8_t intercepts_3[] =
		{ 0, 5, 10, 20, 30, 50, 70, 85, 90, 95, 100 };
	static const u_int8_t intercepts_3_2[] =
		{ 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };
	const u_int8_t *intercepts =
		ee->ee_version < AR5K_EEPROM_VERSION_3_2 ?
			intercepts_3 : intercepts_3_2;

	switch (mode) {
	case AR5K_EEPROM_MODE_11A:
		/*
		 * Read 5GHz EEPROM channels
		 */
		offset = AR5K_EEPROM_GROUPS_START(ee->ee_version);
		gen_chan_info = ee->ee_pwr_cal_a;
		ee->ee_cal_piers_a = 0;
		/* Different frequency mask for < 3.2 */
		if (ee->ee_version <= AR5K_EEPROM_VERSION_3_2) {
			AR5K_EEPROM_READ(offset++, val);
			gen_chan_info[0].freq =
				ath5k_eeprom_bin2freq(ee, val >> 9 & 0x7f,
						AR5K_EEPROM_MODE_11A);
			gen_chan_info[1].freq =
				ath5k_eeprom_bin2freq(ee, val >> 2 & 0x7f,
						AR5K_EEPROM_MODE_11A);

			gen_chan_info[2].freq = val << 5 & 0x7f;
			AR5K_EEPROM_READ(offset++, val);
			gen_chan_info[2].freq |= val >> 11 & 0x1f;
			gen_chan_info[2].freq =
			ath5k_eeprom_bin2freq(ee, gen_chan_info[2].freq,
						AR5K_EEPROM_MODE_11A);

			gen_chan_info[3].freq =
				ath5k_eeprom_bin2freq(ee, val >> 4 & 0x7f,
						AR5K_EEPROM_MODE_11A);

			gen_chan_info[4].freq = val << 3 & 0x7f;
			AR5K_EEPROM_READ(offset++, val);
			gen_chan_info[4].freq |= val >> 13 & 0x7;
			gen_chan_info[4].freq =
				ath5k_eeprom_bin2freq(ee, gen_chan_info[4].freq,
						AR5K_EEPROM_MODE_11A);

			gen_chan_info[5].freq =
				ath5k_eeprom_bin2freq(ee, val >> 6 & 0x7f,
						AR5K_EEPROM_MODE_11A);

			gen_chan_info[6].freq = val << 1 & 0x7f;
			AR5K_EEPROM_READ(offset++, val);
			gen_chan_info[6].freq |= val >> 15 & 0x1;
			gen_chan_info[6].freq =
				ath5k_eeprom_bin2freq(ee, gen_chan_info[6].freq,
						AR5K_EEPROM_MODE_11A);

			gen_chan_info[7].freq =
				ath5k_eeprom_bin2freq(ee, val >> 8 & 0x7f,
						AR5K_EEPROM_MODE_11A);

			gen_chan_info[8].freq =
				ath5k_eeprom_bin2freq(ee, val >> 1 & 0x7f,
						AR5K_EEPROM_MODE_11A);

			gen_chan_info[9].freq = val << 6 & 0x7f;
			AR5K_EEPROM_READ(offset++, val);
			gen_chan_info[9].freq |= val >> 10 & 0x3f;
			gen_chan_info[9].freq =
				ath5k_eeprom_bin2freq(ee, gen_chan_info[9].freq,
						AR5K_EEPROM_MODE_11A);

			ee->ee_cal_piers_a = 10;
		} else {
			for (i = 0; i < AR5K_EEPROM_N_5GHZ_CHAN; i++) {
				AR5K_EEPROM_READ(offset++, val);

				if ((val & 0xff) == 0)
					break;

				ee->ee_pwr_cal_a[i].freq =
					ath5k_eeprom_bin2freq(ee, val & 0xff,
							AR5K_EEPROM_MODE_11A);
				ee->ee_cal_piers_a++;

				if (((val >> 8) & 0xff) == 0)
					break;

				ee->ee_pwr_cal_a[++i].freq =
					ath5k_eeprom_bin2freq(ee, (val >> 8) & 0xff,
								AR5K_EEPROM_MODE_11A);
				ee->ee_cal_piers_a++;

			}
		}
		offset = AR5K_EEPROM_GROUPS_START(ee->ee_version) +
						AR5K_EEPROM_GROUP2_OFFSET;
		cal_piers = ee->ee_cal_piers_a;
		break;
	case AR5K_EEPROM_MODE_11B:
		offset = AR5K_EEPROM_GROUPS_START(ee->ee_version) +
						AR5K_EEPROM_GROUP3_OFFSET;
		gen_chan_info = ee->ee_pwr_cal_b;
		/* Fixed cal piers */
		gen_chan_info[0].freq = 2412;
		gen_chan_info[1].freq = 2447;
		gen_chan_info[2].freq = 2484;
		ee->ee_cal_piers_b = 3;
		cal_piers = ee->ee_cal_piers_b;
		break;
	case AR5K_EEPROM_MODE_11G:
		offset = AR5K_EEPROM_GROUPS_START(ee->ee_version) +
						AR5K_EEPROM_GROUP4_OFFSET;
		gen_chan_info = ee->ee_pwr_cal_g;
		/* Fixed cal piers */
		gen_chan_info[0].freq = 2312;
		gen_chan_info[1].freq = 2412;
		gen_chan_info[2].freq = 2484;
		ee->ee_cal_piers_b = 3;
		cal_piers = ee->ee_cal_piers_g;
		break;
	default:
		return -EINVAL;
	}

	for (i = 0; i < cal_piers; i++) {

		gen_chan_info[i].rf5111_info =
				malloc(sizeof(struct ath5k_chan_pcal_info_rf5111));

		chan_pcal_info = gen_chan_info[i].rf5111_info;

		AR5K_EEPROM_READ(offset++, val);
		chan_pcal_info->pcdac_max     = (u_int16_t)((val >> 10) & 0x3f);
		chan_pcal_info->pcdac_min    = (u_int16_t)((val >> 4) & 0x3f);
		chan_pcal_info->pwr[0] = (u_int16_t)((val << 2) & 0x3f);

		AR5K_EEPROM_READ(offset++, val);
		chan_pcal_info->pwr[0] |= (u_int16_t)((val >> 14) & 0x3);
		chan_pcal_info->pwr[1] = (u_int16_t)((val >> 8) & 0x3f);
		chan_pcal_info->pwr[2] = (u_int16_t)((val >> 2) & 0x3f);
		chan_pcal_info->pwr[3] = (u_int16_t)((val << 4) & 0x3f);

		AR5K_EEPROM_READ(offset++, val);
		chan_pcal_info->pwr[3] |= (u_int16_t)((val >> 12) & 0xf);
		chan_pcal_info->pwr[4] = (u_int16_t)((val >> 6) & 0x3f);
		chan_pcal_info->pwr[5] = (u_int16_t)(val  & 0x3f);

		AR5K_EEPROM_READ(offset++, val);
		chan_pcal_info->pwr[6] = (u_int16_t)((val >> 10) & 0x3f);
		chan_pcal_info->pwr[7] = (u_int16_t)((val >> 4) & 0x3f);
		chan_pcal_info->pwr[8] = (u_int16_t)((val << 2) & 0x3f);

		AR5K_EEPROM_READ(offset++, val);
		chan_pcal_info->pwr[8] |= (u_int16_t)((val >> 14) & 0x3);
		chan_pcal_info->pwr[9] = (u_int16_t)((val >> 8) & 0x3f);
		chan_pcal_info->pwr[10] = (u_int16_t)((val >> 2) & 0x3f);

		/* Recreate pcdac offsets table for this channel
		 * using intercepts table and PCDAC min/max */
		for (c = 0; c < AR5K_EEPROM_N_PWR_POINTS_5111; c++ )
			chan_pcal_info->pcdac[c] =
				(intercepts[c] * chan_pcal_info->pcdac_max + 
				(100 - intercepts[c]) * chan_pcal_info->pcdac_min) / 100;
	}

	return 0;

}

static int ath5k_eeprom_read_rf5112_pcal_info(struct ath5k_eeprom_info *ee,
							unsigned int mode)
{
	u_int32_t offset;
	unsigned int i, c;
	int ret;
	u_int16_t val;
	struct	ath5k_chan_pcal_info *gen_chan_info;
	struct ath5k_chan_pcal_info_rf5112 *chan_pcal_info;
	u_int16_t cal_piers;

	switch (mode) {
	case AR5K_EEPROM_MODE_11A:
		/*
		 * Read 5GHz EEPROM channels
		 */
		offset = AR5K_EEPROM_GROUPS_START(ee->ee_version);
		ee->ee_cal_piers_a = 0;
		for (i = 0; i < AR5K_EEPROM_N_5GHZ_CHAN; i++) {
			AR5K_EEPROM_READ(offset++, val);

			if ((val & 0xff) == 0)
				break;

			ee->ee_pwr_cal_a[i].freq =
				ath5k_eeprom_bin2freq(ee, val & 0xff,
						AR5K_EEPROM_MODE_11A);
			ee->ee_cal_piers_a++;

			if (((val >> 8) & 0xff) == 0)
				break;

			ee->ee_pwr_cal_a[++i].freq =
				ath5k_eeprom_bin2freq(ee, (val >> 8) & 0xff,
							AR5K_EEPROM_MODE_11A);
			ee->ee_cal_piers_a++;

		}
		offset = AR5K_EEPROM_GROUPS_START(ee->ee_version) +
						AR5K_EEPROM_GROUP2_OFFSET;
		gen_chan_info = ee->ee_pwr_cal_a;
		cal_piers = ee->ee_cal_piers_a;
		break;
	case AR5K_EEPROM_MODE_11B:

		if (AR5K_EEPROM_HDR_11A(ee->ee_header))		
			offset = AR5K_EEPROM_GROUPS_START(ee->ee_version) +
							AR5K_EEPROM_GROUP3_OFFSET;
		else
			offset = AR5K_EEPROM_GROUPS_START(ee->ee_version);

		gen_chan_info = ee->ee_pwr_cal_b;
		cal_piers = ee->ee_cal_piers_b;
		break;
	case AR5K_EEPROM_MODE_11G:

		if (AR5K_EEPROM_HDR_11A(ee->ee_header))	{	
			offset = AR5K_EEPROM_GROUPS_START(ee->ee_version) +
						AR5K_EEPROM_GROUP4_OFFSET;
		} else if (AR5K_EEPROM_HDR_11B(ee->ee_header)) {
			offset = AR5K_EEPROM_GROUPS_START(ee->ee_version) +
						AR5K_EEPROM_GROUP2_OFFSET;
		} else
			offset = AR5K_EEPROM_GROUPS_START(ee->ee_version);
			
		gen_chan_info = ee->ee_pwr_cal_g;
		cal_piers = ee->ee_cal_piers_g;
		break;
	default:
		return -EINVAL;
	}

	for (i = 0; i < cal_piers; i++) {

		gen_chan_info[i].rf5112_info =
				malloc(sizeof(struct ath5k_chan_pcal_info_rf5112));

		chan_pcal_info = gen_chan_info[i].rf5112_info;

		/* Power values in dBm * 4 
		 * for the lower xpd gain curve
		 * (0 dBm -> higher output power) */
		for (c = 0; c < AR5K_EEPROM_N_XPD0_POINTS; c++) {
			AR5K_EEPROM_READ(offset++, val);
			chan_pcal_info->pwr_x0[c] = (val & 0xff);
			chan_pcal_info->pwr_x0[++c] = ((val >> 8) & 0xff);
		}

		/* PCDAC steps
		 * corresponding to the above power
		 * measurements */
		AR5K_EEPROM_READ(offset++, val);
		chan_pcal_info->pcdac_x0[1] = (val & 0x1f);
		chan_pcal_info->pcdac_x0[2] = ((val >> 5) & 0x1f);
		chan_pcal_info->pcdac_x0[3] = ((val >> 10) & 0x1f);

		/* Power values in dBm * 4
		 * for the higher xpd gain curve
		 * (18 dBm -> lower output power) */
		AR5K_EEPROM_READ(offset++, val);
		chan_pcal_info->pwr_x3[0] = (val & 0xff);
		chan_pcal_info->pwr_x3[1] = ((val >> 8) & 0xff);

		AR5K_EEPROM_READ(offset++, val);
		chan_pcal_info->pwr_x3[2] = (val & 0xff);

		/* PCDAC steps
		 * corresponding to the above power
		 * measurements (static) */
		chan_pcal_info->pcdac_x3[0] = 20;
		chan_pcal_info->pcdac_x3[1] = 35;
		chan_pcal_info->pcdac_x3[2] = 63;

		if (ee->ee_version >= AR5K_EEPROM_VERSION_4_3) {
			chan_pcal_info->pcdac_x0[0] = ((val >> 8) & 0xff);

			/* Last xpd0 power level is also channel maximum */
			gen_chan_info[i].max_pwr = chan_pcal_info->pwr_x0[3];
		} else {
			chan_pcal_info->pcdac_x0[0] = 1;
			gen_chan_info[i].max_pwr = ((val >> 8) & 0xff);
		}

		/* Recreate pcdac_x0 table for this channel using pcdac steps */
		chan_pcal_info->pcdac_x0[1] += chan_pcal_info->pcdac_x0[0];
		chan_pcal_info->pcdac_x0[2] += chan_pcal_info->pcdac_x0[1];
		chan_pcal_info->pcdac_x0[3] += chan_pcal_info->pcdac_x0[2];
	}

	return 0;
}

static int ath5k_eeprom_read_rf2413_pcal_info(struct ath5k_eeprom_info *ee,
							unsigned int mode)
{
	u_int32_t offset, start_offset;
	unsigned int i, c;
	int ret;
	u_int16_t val;
	struct	ath5k_chan_pcal_info *gen_chan_info;
	struct ath5k_chan_pcal_info_rf2413 *chan_pcal_info;
	u_int16_t cal_piers;
	u_int8_t pd_gains = 0;

	if (ee->ee_x_gain[mode] & 0x1) pd_gains++;
	if ((ee->ee_x_gain[mode] >> 1) & 0x1) pd_gains++;
	if ((ee->ee_x_gain[mode] >> 2) & 0x1) pd_gains++;
	if ((ee->ee_x_gain[mode] >> 3) & 0x1) pd_gains++;

	switch (mode) {
	case AR5K_EEPROM_MODE_11A:
		start_offset = AR5K_EEPROM_CAL_DATA_START(ee->ee_misc4);
		offset = start_offset;
		ee->ee_cal_piers_a = 0;

		if (!AR5K_EEPROM_HDR_11A(ee->ee_header))
			return 0;

		for (i = 0; i < AR5K_EEPROM_N_5GHZ_CHAN; i++) {
			AR5K_EEPROM_READ(offset++, val);

			if ((val & 0xff) == 0)
				break;

			ee->ee_pwr_cal_a[i].freq =
				ath5k_eeprom_bin2freq(ee, val & 0xff,
						AR5K_EEPROM_MODE_11A);
			ee->ee_cal_piers_a++;

			if (((val >> 8) & 0xff) == 0)
				break;

			ee->ee_pwr_cal_a[++i].freq =
				ath5k_eeprom_bin2freq(ee, (val >> 8) & 0xff,
							AR5K_EEPROM_MODE_11A);
			ee->ee_cal_piers_a++;

		}
		offset = start_offset + (AR5K_EEPROM_N_5GHZ_CHAN / 2);
		gen_chan_info = ee->ee_pwr_cal_a;
		cal_piers = ee->ee_cal_piers_a;
		break;
	case AR5K_EEPROM_MODE_11B:
		start_offset = AR5K_EEPROM_CAL_DATA_START(ee->ee_misc4);

		if (AR5K_EEPROM_HDR_11A(ee->ee_header))
			start_offset +=	(ee->ee_cal_piers_a * (3 * ee->ee_pwr_cal_a[0].rf2413_info->pd_gains) +
						(ee->ee_pwr_cal_a[0].rf2413_info->pd_gains == 1 ? 1 : 0)) + 5;

		offset = start_offset;
		ee->ee_cal_piers_b = 0;

		if (!AR5K_EEPROM_HDR_11B(ee->ee_header))
			return 0;

		for (i = 0; i < AR5K_EEPROM_N_2GHZ_CHAN_2413; i++) {
			AR5K_EEPROM_READ(offset++, val);

			if ((val & 0xff) == 0)
				break;

			ee->ee_pwr_cal_b[i].freq =
				ath5k_eeprom_bin2freq(ee, val & 0xff,
						AR5K_EEPROM_MODE_11B);
			ee->ee_cal_piers_b++;

			if (((val >> 8) & 0xff) == 0)
				break;

			ee->ee_pwr_cal_b[++i].freq =
				ath5k_eeprom_bin2freq(ee, (val >> 8) & 0xff,
							AR5K_EEPROM_MODE_11B);
			ee->ee_cal_piers_b++;

		}
		offset = start_offset + (AR5K_EEPROM_N_2GHZ_CHAN_2413 / 2);
		gen_chan_info = ee->ee_pwr_cal_b;
		cal_piers = ee->ee_cal_piers_b;
		break;
	case AR5K_EEPROM_MODE_11G:
		start_offset = AR5K_EEPROM_CAL_DATA_START(ee->ee_misc4);

		if (AR5K_EEPROM_HDR_11A(ee->ee_header))
			start_offset +=	(ee->ee_cal_piers_a * (3 * ee->ee_pwr_cal_a[0].rf2413_info->pd_gains) +
							(ee->ee_pwr_cal_a[0].rf2413_info->pd_gains == 1 ? 1 : 0)) + 5;

		if (AR5K_EEPROM_HDR_11B(ee->ee_header))
			start_offset +=	(ee->ee_cal_piers_b * (3 * ee->ee_pwr_cal_b[0].rf2413_info->pd_gains) +
							(ee->ee_pwr_cal_b[0].rf2413_info->pd_gains == 1 ? 1 : 0)) + 2;

		offset = start_offset;
		ee->ee_cal_piers_g = 0;

		if (!AR5K_EEPROM_HDR_11G(ee->ee_header))
			return 0;

		for (i = 0; i < AR5K_EEPROM_N_2GHZ_CHAN_2413; i++) {
			AR5K_EEPROM_READ(offset++, val);

			if ((val & 0xff) == 0)
				break;

			ee->ee_pwr_cal_g[i].freq =
				ath5k_eeprom_bin2freq(ee, val & 0xff,
						AR5K_EEPROM_MODE_11G);
			ee->ee_cal_piers_g++;

			if (((val >> 8) & 0xff) == 0)
				break;

			ee->ee_pwr_cal_g[++i].freq =
				ath5k_eeprom_bin2freq(ee, (val >> 8) & 0xff,
							AR5K_EEPROM_MODE_11G);
			ee->ee_cal_piers_g++;

		}
		offset = start_offset + (AR5K_EEPROM_N_2GHZ_CHAN_2413 / 2);
		gen_chan_info = ee->ee_pwr_cal_g;
		cal_piers = ee->ee_cal_piers_g;
		break;
	default:
		return -EINVAL;
	}

	for (i = 0; i < cal_piers; i++) {

		gen_chan_info[i].rf2413_info =
				malloc(sizeof(struct ath5k_chan_pcal_info_rf2413));

		chan_pcal_info = gen_chan_info[i].rf2413_info;

		chan_pcal_info->pd_gains = pd_gains;

		if (chan_pcal_info->pd_gains > 0) {
			/*
			 * Read pwr_i, pddac_i and the first
			 * 2 pd points (pwr, pddac)
			 */
			AR5K_EEPROM_READ(offset++, val);
			chan_pcal_info->pwr_i[0] = val & 0x1f;
			chan_pcal_info->pddac_i[0] = (val >> 5) & 0x7f;
			chan_pcal_info->pwr[0][0] =
						(val >> 12) & 0xf;

			AR5K_EEPROM_READ(offset++, val);
			chan_pcal_info->pddac[0][0] = val & 0x3f;
			chan_pcal_info->pwr[0][1] = (val >> 6) & 0xf;
			chan_pcal_info->pddac[0][1] =
						(val >> 10) & 0x3f;
			
			AR5K_EEPROM_READ(offset++, val);
			chan_pcal_info->pwr[0][2] = val & 0xf;
			chan_pcal_info->pddac[0][2] =
						(val >> 4) & 0x3f;

			chan_pcal_info->pwr[0][3] = 0;
			chan_pcal_info->pddac[0][3] = 0;
		}
		
		if (chan_pcal_info->pd_gains > 1) {
			/*
			 * Pd gain 0 is not the last pd gain
			 * so it only has 2 pd points.
			 * Continue wih pd gain 1.
			 */
			chan_pcal_info->pwr_i[1] = (val >> 10) & 0x1f;

			chan_pcal_info->pddac_i[1] = (val >> 15) & 0x1;
			AR5K_EEPROM_READ(offset++, val);
			chan_pcal_info->pddac_i[1] |= (val & 0x3F) << 1;

			chan_pcal_info->pwr[1][0] = (val >> 6) & 0xf;
			chan_pcal_info->pddac[1][0] =
						(val >> 10) & 0x3f;
			
			AR5K_EEPROM_READ(offset++, val);
			chan_pcal_info->pwr[1][1] = val & 0xf;
			chan_pcal_info->pddac[1][1] =
						(val >> 4) & 0x3f;
			chan_pcal_info->pwr[1][2] =
						(val >> 10) & 0xf;

			chan_pcal_info->pddac[1][2] =
						(val >> 14) & 0x3;
			AR5K_EEPROM_READ(offset++, val);
			chan_pcal_info->pddac[1][2] |=
						(val & 0xF) << 2;

			chan_pcal_info->pwr[1][3] = 0;
			chan_pcal_info->pddac[1][3] = 0;
		} else if (chan_pcal_info->pd_gains == 1) {
			/*
			 * Pd gain 0 is the last one so
			 * read the extra point.
			 */
			chan_pcal_info->pwr[0][3] =
						(val >> 10) & 0xf;

			chan_pcal_info->pddac[0][3] =
						(val >> 14) & 0x3;
			AR5K_EEPROM_READ(offset++, val);
			chan_pcal_info->pddac[0][3] |=
						(val & 0xF) << 2;
		}

		/*
		 * Proceed with the other pd_gains
		 * as above.
		 */
		if (chan_pcal_info->pd_gains > 2) {
			chan_pcal_info->pwr_i[2] = (val >> 4) & 0x1f;
			chan_pcal_info->pddac_i[2] = (val >> 9) & 0x7f;
			
			AR5K_EEPROM_READ(offset++, val);
			chan_pcal_info->pwr[2][0] =
						(val >> 0) & 0xf;
			chan_pcal_info->pddac[2][0] =
						(val >> 4) & 0x3f;
			chan_pcal_info->pwr[2][1] =
						(val >> 10) & 0xf;

			chan_pcal_info->pddac[2][1] =
						(val >> 14) & 0x3;
			AR5K_EEPROM_READ(offset++, val);
			chan_pcal_info->pddac[2][1] |=
						(val & 0xF) << 2;

			chan_pcal_info->pwr[2][2] =
						(val >> 4) & 0xf;
			chan_pcal_info->pddac[2][2] =
						(val >> 8) & 0x3f;

			chan_pcal_info->pwr[2][3] = 0;
			chan_pcal_info->pddac[2][3] = 0;
		} else if (chan_pcal_info->pd_gains == 2) {
			chan_pcal_info->pwr[1][3] =
						(val >> 4) & 0xf;
			chan_pcal_info->pddac[1][3] =
						(val >> 8) & 0x3f;
		}

		if (chan_pcal_info->pd_gains > 3) {
			chan_pcal_info->pwr_i[3] = (val >> 14) & 0x3;
			AR5K_EEPROM_READ(offset++, val);
			chan_pcal_info->pwr_i[3] |= ((val >> 0) & 0x7) << 2;

			chan_pcal_info->pddac_i[3] = (val >> 3) & 0x7f;
			chan_pcal_info->pwr[3][0] =
						(val >> 10) & 0xf;
			chan_pcal_info->pddac[3][0] =
						(val >> 14) & 0x3;
			
			AR5K_EEPROM_READ(offset++, val);
			chan_pcal_info->pddac[3][0] |=
						(val & 0xF) << 2;
			chan_pcal_info->pwr[3][1] =
						(val >> 4) & 0xf;
			chan_pcal_info->pddac[3][1] =
						(val >> 8) & 0x3f;

			chan_pcal_info->pwr[3][2] =
						(val >> 14) & 0x3;
			AR5K_EEPROM_READ(offset++, val);
			chan_pcal_info->pwr[3][2] |=
						((val >> 0) & 0x3) << 2;

			chan_pcal_info->pddac[3][2] =
						(val >> 2) & 0x3f;
			chan_pcal_info->pwr[3][3] =
						(val >> 8) & 0xf;

			chan_pcal_info->pddac[3][3] =
						(val >> 12) & 0xF;
			AR5K_EEPROM_READ(offset++, val);
			chan_pcal_info->pddac[3][3] |=
						((val >> 0) & 0x3) << 4;
		} else if (chan_pcal_info->pd_gains == 3) {
			chan_pcal_info->pwr[2][3] =
						(val >> 14) & 0x3;
			AR5K_EEPROM_READ(offset++, val);
			chan_pcal_info->pwr[2][3] |=
						((val >> 0) & 0x3) << 2;

			chan_pcal_info->pddac[2][3] =
						(val >> 2) & 0x3f;
		}

		for (c = 0; c < pd_gains; c++) {
			/* Recreate pwr table for this channel using pwr steps */
			chan_pcal_info->pwr[c][0] += chan_pcal_info->pwr_i[c] * 2;
			chan_pcal_info->pwr[c][1] += chan_pcal_info->pwr[c][0];
			chan_pcal_info->pwr[c][2] += chan_pcal_info->pwr[c][1];
			chan_pcal_info->pwr[c][3] += chan_pcal_info->pwr[c][2];
			if (chan_pcal_info->pwr[c][3] == chan_pcal_info->pwr[c][2])
				chan_pcal_info->pwr[c][3] = 0;

			/* Recreate pddac table for this channel using pddac steps */
			chan_pcal_info->pddac[c][0] += chan_pcal_info->pddac_i[c];
			chan_pcal_info->pddac[c][1] += chan_pcal_info->pddac[c][0];
			chan_pcal_info->pddac[c][2] += chan_pcal_info->pddac[c][1];
			chan_pcal_info->pddac[c][3] += chan_pcal_info->pddac[c][2];
			if (chan_pcal_info->pddac[c][3] == chan_pcal_info->pddac[c][2])
				chan_pcal_info->pddac[c][3] = 0;
		}
	}

	return 0;
}

static int ath5k_eeprom_read_pcal_info(struct ath5k_eeprom_info *ee, unsigned int mode)
{

	if (ee->ee_version >= AR5K_EEPROM_VERSION_4_0 && AR5K_EEPROM_EEMAP(ee->ee_misc0) == 1)
		return ath5k_eeprom_read_rf5112_pcal_info(ee, mode);
	else if (ee->ee_version >= AR5K_EEPROM_VERSION_5_0 && AR5K_EEPROM_EEMAP(ee->ee_misc0) == 2)
		return ath5k_eeprom_read_rf2413_pcal_info(ee, mode);
	/* Not sure if EEMAP existed in early eeproms */
	else if (ee->ee_version >= AR5K_EEPROM_VERSION_3_0 || AR5K_EEPROM_EEMAP(ee->ee_misc0) == 0)
		return ath5k_eeprom_read_rf5111_pcal_info(ee, mode);

	return 0;

}

/*
 * Read per rate target power (this is the maximum tx power
 * supported by the card). This info is used when setting
 * tx power, no matter the channel.
 *
 * This also works for v5 EEPROMs.
 */
static int ath5k_eeprom_read_target_rate_pwr_info(struct ath5k_eeprom_info *ee, unsigned int mode)
{
	u_int32_t offset;
	u_int16_t val;
	struct ath5k_rate_pcal_info *rate_pcal_info;
	u_int16_t *rate_target_pwr_num;
	int ret, i;

	switch (mode) {
	case AR5K_EEPROM_MODE_11A:
		offset = AR5K_EEPROM_TARGET_PWRSTART(ee->ee_misc1) +
				AR5K_EEPROM_TARGET_PWR_OFF_11A(ee->ee_version);
		rate_pcal_info = ee->ee_rate_tpwr_a;
		ee->ee_rate_target_pwr_num_a = AR5K_EEPROM_N_5GHZ_CHAN;
		rate_target_pwr_num = &ee->ee_rate_target_pwr_num_a;
		break;
	case AR5K_EEPROM_MODE_11B:
		offset = AR5K_EEPROM_TARGET_PWRSTART(ee->ee_misc1) +
				AR5K_EEPROM_TARGET_PWR_OFF_11B(ee->ee_version);
		rate_pcal_info = ee->ee_rate_tpwr_b;
		ee->ee_rate_target_pwr_num_b = 2; /* 3rd is g mode's 1st */
		rate_target_pwr_num = &ee->ee_rate_target_pwr_num_b;
		break;
	case AR5K_EEPROM_MODE_11G:
		offset = AR5K_EEPROM_TARGET_PWRSTART(ee->ee_misc1) +
				AR5K_EEPROM_TARGET_PWR_OFF_11G(ee->ee_version);
		rate_pcal_info = ee->ee_rate_tpwr_g;
		ee->ee_rate_target_pwr_num_g = AR5K_EEPROM_N_2GHZ_CHAN;
		rate_target_pwr_num = &ee->ee_rate_target_pwr_num_g;
		break;
	default:
		return -EINVAL;
	}

	/* Different freq mask for older eeproms (<= v3.2) */
	if(ee->ee_version <= AR5K_EEPROM_VERSION_3_2){
		for (i = 0; i < (*rate_target_pwr_num); i++) {
			AR5K_EEPROM_READ(offset++, val);
			rate_pcal_info[i].freq =
			    ath5k_eeprom_bin2freq(ee, (val >> 9) & 0x7f, mode);
	
			rate_pcal_info[i].target_power_6to24 = ((val >> 3) & 0x3f);
			rate_pcal_info[i].target_power_36 = (val << 3) & 0x3f;
	
			AR5K_EEPROM_READ(offset++, val);
	
			if (rate_pcal_info[i].freq == AR5K_EEPROM_CHANNEL_DIS ||
			    val == 0) {
				(*rate_target_pwr_num) = i;
				break;
			}

			rate_pcal_info[i].target_power_36 |= ((val >> 13) & 0x7);
			rate_pcal_info[i].target_power_48 = ((val >> 7) & 0x3f);
			rate_pcal_info[i].target_power_54 = ((val >> 1) & 0x3f);
		}
	} else {
		for (i = 0; i < (*rate_target_pwr_num); i++) {
			AR5K_EEPROM_READ(offset++, val);
			rate_pcal_info[i].freq =
			    ath5k_eeprom_bin2freq(ee, (val >> 8) & 0xff, mode);
	
			rate_pcal_info[i].target_power_6to24 = ((val >> 2) & 0x3f);
			rate_pcal_info[i].target_power_36 = (val << 4) & 0x3f;
	
			AR5K_EEPROM_READ(offset++, val);
	
			if (rate_pcal_info[i].freq == AR5K_EEPROM_CHANNEL_DIS ||
			    val == 0) {
				(*rate_target_pwr_num) = i;
				break;
			}

			rate_pcal_info[i].target_power_36 |= (val >> 12) & 0xf;
			rate_pcal_info[i].target_power_48 = ((val >> 6) & 0x3f);
			rate_pcal_info[i].target_power_54 = (val & 0x3f);
		}
	}

	return 0;
}

/*
 * Initialize EEPROM & capabilities data
 */
static int ath5k_eeprom_init(struct ath5k_eeprom_info *ee)
{
	unsigned int mode, i;
	int ret;
	u_int32_t offset;
	u_int16_t val;

	/* Initial TX thermal adjustment values */
	ee->ee_tx_clip = 4;
	ee->ee_pwd_84 = ee->ee_pwd_90 = 1;
	ee->ee_gain_select = 1;

	/*
	 * Read values from EEPROM and store them in the capability structure
	 */
	AR5K_EEPROM_READ(AR5K_EEPROM_MAGIC, ee->ee_magic);
	AR5K_EEPROM_READ(AR5K_EEPROM_PROTECT, ee->ee_protect);
	AR5K_EEPROM_READ(AR5K_EEPROM_REG_DOMAIN, ee->ee_regdomain);
	AR5K_EEPROM_READ(AR5K_EEPROM_VERSION, ee->ee_version);
	AR5K_EEPROM_READ(AR5K_EEPROM_HDR, ee->ee_header);

	/* Return if we have an old EEPROM */
	if (ee->ee_version < AR5K_EEPROM_VERSION_3_0)
		return 0;

#ifdef notyet
	/*
	 * Validate the checksum of the EEPROM date. There are some
	 * devices with invalid EEPROMs.
	 */
	for (cksum = 0, offset = 0; offset < AR5K_EEPROM_INFO_MAX; offset++) {
		AR5K_EEPROM_READ(AR5K_EEPROM_INFO(offset), val);
		cksum ^= val;
	}
	if (cksum != AR5K_EEPROM_INFO_CKSUM) {
		AR5K_PRINTF("Invalid EEPROM checksum 0x%04x\n", cksum);
		return -EIO;
	}
#endif

	AR5K_EEPROM_READ(AR5K_EEPROM_ANT_GAIN(ee->ee_version), ee->ee_ant_gain);

	if (ee->ee_version >= AR5K_EEPROM_VERSION_4_0) {
		AR5K_EEPROM_READ(AR5K_EEPROM_MISC0, ee->ee_misc0);
		AR5K_EEPROM_READ(AR5K_EEPROM_MISC1, ee->ee_misc1);

		AR5K_EEPROM_READ(AR5K_EEPROM_MISC2, ee->ee_misc2);
		AR5K_EEPROM_READ(AR5K_EEPROM_MISC3, ee->ee_misc3);

		if (ee->ee_version >= AR5K_EEPROM_VERSION_5_0){
			AR5K_EEPROM_READ(AR5K_EEPROM_MISC4, ee->ee_misc4);
			AR5K_EEPROM_READ(AR5K_EEPROM_MISC5, ee->ee_misc5);
			AR5K_EEPROM_READ(AR5K_EEPROM_MISC6, ee->ee_misc6);
		}
	}

	if (ee->ee_version < AR5K_EEPROM_VERSION_3_3) {
		AR5K_EEPROM_READ(AR5K_EEPROM_OBDB0_2GHZ, val);
		ee->ee_ob[AR5K_EEPROM_MODE_11B][0] = val & 0x7;
		ee->ee_db[AR5K_EEPROM_MODE_11B][0] = (val >> 3) & 0x7;

		AR5K_EEPROM_READ(AR5K_EEPROM_OBDB1_2GHZ, val);
		ee->ee_ob[AR5K_EEPROM_MODE_11G][0] = val & 0x7;
		ee->ee_db[AR5K_EEPROM_MODE_11G][0] = (val >> 3) & 0x7;
	}

	/*
	 * Get values for 802.11a (5GHz)
	 */
	mode = AR5K_EEPROM_MODE_11A;

	ee->ee_turbo_max_power[mode] =
	    AR5K_EEPROM_HDR_T_5GHZ_DBM(ee->ee_header);

	offset = AR5K_EEPROM_MODES_11A(ee->ee_version);

	ret = ath5k_eeprom_read_ants(ee, &offset, mode);
	if (ret)
		return ret;

	ret = ath5k_eeprom_read_modes(ee, &offset, mode);
	if (ret)
		return ret;

	ret = ath5k_eeprom_read_turbo_modes(ee, &offset, mode);
	if (ret)
		return ret;

	/*
	 * Get values for 802.11b (2.4GHz)
	 */
	mode = AR5K_EEPROM_MODE_11B;
	offset = AR5K_EEPROM_MODES_11B(ee->ee_version);

	ret = ath5k_eeprom_read_ants(ee, &offset, mode);
	if (ret)
		return ret;

	ret = ath5k_eeprom_read_modes(ee, &offset, mode);
	if (ret)
		return ret;

	/*
	 * Get values for 802.11g (2.4GHz)
	 */
	mode = AR5K_EEPROM_MODE_11G;
	offset = AR5K_EEPROM_MODES_11G(ee->ee_version);

	ret = ath5k_eeprom_read_ants(ee, &offset, mode);
	if (ret)
		return ret;

	ret = ath5k_eeprom_read_modes(ee, &offset, mode);
	if (ret)
		return ret;

	ret = ath5k_eeprom_read_turbo_modes(ee, &offset, mode);
	if (ret)
		return ret;

	/*
	 * Get conformance test limit values
	 */
	offset = AR5K_EEPROM_CTL(ee->ee_version);
	ee->ee_ctls = 0;

	for (i = 0; i < AR5K_EEPROM_N_CTLS(ee->ee_version); i++) {
		AR5K_EEPROM_READ(offset++, val);

		if (((val >> 8) & 0xff) == 0)
			break;

		ee->ee_ctl[i] = (val >> 8) & 0xff;
		ee->ee_ctls++;

		if ((val & 0xff) == 0)
			break;

		ee->ee_ctl[i + 1] = val & 0xff;
		ee->ee_ctls++;
	}

	/*
	 * Read power calibration info
	 */
	mode = AR5K_EEPROM_MODE_11A;
	ret = ath5k_eeprom_read_pcal_info(ee, mode);
	if (ret)
		return ret;

	mode = AR5K_EEPROM_MODE_11B;
	ret = ath5k_eeprom_read_pcal_info(ee, mode);
	if (ret)
		return ret;

	mode = AR5K_EEPROM_MODE_11G;
	ret = ath5k_eeprom_read_pcal_info(ee, mode);
	if (ret)
		return ret;

	/*
	 * Read per rate target power info
	 */
	mode = AR5K_EEPROM_MODE_11A;
	ret = ath5k_eeprom_read_target_rate_pwr_info(ee, mode);
	if (ret)
		return ret;

	mode = AR5K_EEPROM_MODE_11B;
	ret = ath5k_eeprom_read_target_rate_pwr_info(ee,  mode);
	if (ret)
		return ret;

	mode = AR5K_EEPROM_MODE_11G;
	ret = ath5k_eeprom_read_target_rate_pwr_info(ee, mode);
	if (ret)
		return ret;

	return 0;
}

static const char *ath5k_hw_get_mac_name(u_int8_t val)
{
	const char *name = "?????";
	unsigned int i;

	for (i = 0; i < ARRAY_SIZE(ath5k_mac_names); i++) {

		if ((val & 0xf0) == ath5k_mac_names[i].sr_val)
			name = ath5k_mac_names[i].sr_name;

		if ((val & 0xff) == ath5k_mac_names[i].sr_val) {
			name = ath5k_mac_names[i].sr_name;
			break;
		}
	}

	return name;
}

static const char *ath5k_hw_get_phy_name(u_int8_t val)
{
	const char *name = "?????";
	unsigned int i;

	for (i = 0; i < ARRAY_SIZE(ath5k_phy_names); i++) {

		if ((val & 0xf0) == ath5k_phy_names[i].sr_val)
			name = ath5k_phy_names[i].sr_name;

		if ((val & 0xff) == ath5k_phy_names[i].sr_val) {
			name = ath5k_phy_names[i].sr_name;
			break;
		}

	}

	return name;
}

/* returns -1 on unknown name */
static int eeprom_name2addr(const char *name)
{
	unsigned int i;

	if (!name || !name[0])
		return -1;
	for (i = 0; i < ARRAY_SIZE(eeprom_addr); i++)
		if (!strcmp(name, eeprom_addr[i].name))
			return eeprom_addr[i].addr;
	return -1;
}

/* returns "<unknown>" on unknown address */
static const char *eeprom_addr2name(int addr)
{
	unsigned int i;

	for (i = 0; i < ARRAY_SIZE(eeprom_addr); i++)
		if (eeprom_addr[i].addr == addr)
			return eeprom_addr[i].name;
	return "<unknown>";
}

static int do_write_pairs(int anr, int argc, char **argv)
{
#define MAX_NR_WRITES 16
	struct {
		int addr;
		unsigned int val;
	} wr_ops[MAX_NR_WRITES];
	int wr_ops_len = 0;
	int i;
	char *end;
	int errors = 0;		/* count errors during write/verify */

	if (anr >= argc) {
		err("missing values to write.");
		usage(argv[0]);
		return 1;
	}

	if ((argc - anr) % 2) {
		err("write spec. needs an even number of arguments.");
		usage(argv[0]);
		return 2;
	}

	if ((argc - anr) / 2 > MAX_NR_WRITES) {
		err("too many values to write (max. %d)", MAX_NR_WRITES);
		return 3;
	}

	/* get the (addr,val) pairs we have to write */
	i = 0;
	while (anr < (argc - 1)) {
		wr_ops[i].addr = strtoul(argv[anr], &end, 16);
		if (end == argv[anr]) {
			/* maybe a symbolic name for the address? */
			if ((wr_ops[i].addr =
			     eeprom_name2addr(argv[anr])) == -1) {
				err("pair %d: bad address %s", i, argv[anr]);
				return 4;
			}
		}

		if (wr_ops[i].addr >= AR5K_EEPROM_INFO_BASE) {
			err("offset 0x%04x in CRC protected area is "
			    "not supported", wr_ops[i].addr);
			return 5;
		}

		anr++;
		wr_ops[i].val = strtoul(argv[anr], &end, 16);
		if (end == argv[anr]) {
			err("pair %d: bad val %s", i, argv[anr]);
			return 5;
		}

		if (wr_ops[i].val > 0xffff) {
			err("pair %d: value %u too large", i, wr_ops[i].val);
			return 6;
		}
		anr++;
		i++;
	}

	if (!(wr_ops_len = i)) {
		err("no (addr,val) pairs given");
		return 7;
	}

	if (verbose || !force_write) {
		for (i = 0; i < wr_ops_len; i++)
			printf("%20s (0x%04x) := 0x%04x\n",
			       eeprom_addr2name(wr_ops[i].addr), wr_ops[i].addr,
			       wr_ops[i].val);
	}

	if (!force_write) {
		int c;
		printf
		    ("WARNING: The write function may easy brick your device or\n"
		     "violate state regulation on frequency usage.\n"
		     "Proceed on your own risk!\n"
		     "Shall I write the above value(s)? (y/n)\n");
		c = getchar();
		if (c != 'y' && c != 'Y') {
			printf("user abort\n");
			return 0;
		}
	}

	for (i = 0; i < wr_ops_len; i++) {
		u_int16_t oldval, u;

		if (ath5k_hw_eeprom_read
		    (wr_ops[i].addr, &oldval)) {
			err("failed to read old value from offset 0x%04x ",
			    wr_ops[i].addr);
			errors++;
		}

		if (oldval == wr_ops[i].val) {
			dbg("pair %d: skipped, value already there", i);
			continue;
		}

		dbg("writing *0x%04x := 0x%04x", wr_ops[i].addr, wr_ops[i].val);
		if (ath5k_hw_eeprom_write(wr_ops[i].addr, wr_ops[i].val)) {
			err("failed to write 0x%04x to offset 0x%04x",
			    wr_ops[i].val, wr_ops[i].addr);
			errors++;
		} else {
			if (ath5k_hw_eeprom_read(wr_ops[i].addr, &u)) {
				err("failed to read offset 0x%04x for "
				    "verification", wr_ops[i].addr);
				errors++;
			} else {
				if (u != wr_ops[i].val) {
					err("offset 0x%04x: wrote 0x%04x but "
					    "read 0x%04x", wr_ops[i].addr,
					    wr_ops[i].val, u);
					errors++;
				}
			}
		}
	}

	return errors ? 11 : 0;
}

static void usage(const char *n)
{
	unsigned int i;

	fprintf(stderr, "%s [-w [-g N:M]] [-v] [-f] [-d] [-R addr] [-W addr val] <base_address> "
		"[<name1> <val1> [<name2> <val2> ...]]\n\n", n);
	fprintf(stderr,
		"-w      write values into EEPROM\n"
		"-g N:M  set GPIO N to level M (only used with -w)\n"
		"-v      verbose output\n"
		"-f      force; suppress question before writing\n"
		"-d      dump EEPROM (file 'ath-eeprom-dump.bin' and screen)\n"
		"-R <addr>       read register at <addr> (hex)\n"
		"-W <addr> <val> write <val> (hex) into register at <addr> (hex)\n"
		"<base_address>  device base address (see lspci output)\n\n");

	fprintf(stderr,
		"- read info:\n"
		"  %s <base_address>\n\n"
		"- set regdomain to N:\n"
		"  %s -w <base_address> regdomain N\n\n"
		"- set a PCI id field to value N:\n"
		"  %s -w <base_address> <field> N\n"
		"  where <field> is one of:\n    ", n, n, n);
	for (i = 0; i < ARRAY_SIZE(eeprom_addr); i++)
		fprintf(stderr, " %s", eeprom_addr[i].name);
	fprintf(stderr, "\n\n");
	fprintf(stderr,
		"You may need to set a GPIO to a certain value in order to enable\n"
		"writing to the EEPROM with newer chipsets, e.g. set GPIO 4 to low:\n"
		"  %s -g 4:0 -w <base_address> regdomain N\n", n);
	fprintf(stderr,
		"\nDISCLAIMER: The authors are not responsible for any damages caused by\n"
		"this program. Writing improper values may damage the card or cause\n"
		"unlawful radio transmissions!\n\n");
}

static void dump_capabilities(struct ath5k_eeprom_info *ee)
{
	u_int8_t has_a, has_b, has_g, has_rfkill, turbog_dis, turboa_dis;
	u_int8_t xr2_dis, xr5_dis, has_crystal;

	has_a = AR5K_EEPROM_HDR_11A(ee->ee_header);
	has_b = AR5K_EEPROM_HDR_11B(ee->ee_header);
	has_g = AR5K_EEPROM_HDR_11G(ee->ee_header);
	has_rfkill = AR5K_EEPROM_HDR_RFKILL(ee->ee_header);
	has_crystal = AR5K_EEPROM_HAS32KHZCRYSTAL(ee->ee_misc1);
	turbog_dis = AR5K_EEPROM_HDR_T_2GHZ_DIS(ee->ee_header);
	turboa_dis = AR5K_EEPROM_HDR_T_5GHZ_DIS(ee->ee_header);
	xr2_dis = AR5K_EEPROM_HDR_XR2_DIS(ee->ee_misc0);
	xr5_dis = AR5K_EEPROM_HDR_XR5_DIS(ee->ee_misc0);

	printf("|================= Capabilities ================|\n");

	printf("| 802.11a Support: ");
	if (has_a)
		printf(" yes |");
	else
		printf(" no  |");

	printf(" Turbo-A disabled:");
	if (turboa_dis)
		printf(" yes |\n");
	else
		printf(" no  |\n");

	printf("| 802.11b Support: ");
	if (has_b)
		printf(" yes |");
	else
		printf(" no  |");

	printf(" Turbo-G disabled:");
	if (turbog_dis)
		printf(" yes |\n");
	else
		printf(" no  |\n");

	printf("| 802.11g Support: ");
	if (has_g)
		printf(" yes |");
	else
		printf(" no  |");

	printf(" 2GHz XR disabled:");
	if (xr2_dis)
		printf(" yes |\n");
	else
		printf(" no  |\n");

	printf("| RFKill  Support: ");
	if (has_rfkill)
		printf(" yes |");
	else
		printf(" no  |");

	printf(" 5GHz XR disabled:");
	if (xr5_dis)
		printf(" yes |\n");
	else
		printf(" no  |\n");

	if (has_crystal != 2) {
		printf("| 32kHz   Crystal: ");
		if (has_crystal)
			printf(" yes |");
		else
			printf(" no  |");

		printf("                       |\n");
	}

	printf("\\===============================================/\n");
}

static void dump_calinfo_for_mode(int mode, struct ath5k_eeprom_info *ee)
{
	int i;

	printf("|=========================================================|\n");
	printf("| I power:              0x%02x |", ee->ee_i_cal[mode]);
	printf(" Q power:              0x%02x |\n", ee->ee_q_cal[mode]);
	printf("| Use fixed bias:       0x%02x |", ee->ee_fixed_bias[mode]);
	printf(" Max turbo power:      0x%02x |\n", ee->ee_turbo_max_power[mode]);
	printf("| Max XR power:         0x%02x |", ee->ee_xr_power[mode]);
	printf(" Switch Settling Time: 0x%02x |\n", ee->ee_switch_settling[mode]);
	printf("| Tx/Rx attenuation:    0x%02x |", ee->ee_atn_tx_rx[mode]);
	printf(" TX end to XLNA On:    0x%02x |\n", ee->ee_tx_end2xlna_enable[mode]);
	printf("| TX end to XPA Off:    0x%02x |", ee->ee_tx_end2xpa_disable[mode]);
	printf(" TX end to XPA On:     0x%02x |\n", ee->ee_tx_frm2xpa_enable[mode]);
	printf("| 62db Threshold:       0x%02x |", ee->ee_thr_62[mode]);
	printf(" XLNA gain:            0x%02x |\n", ee->ee_xlna_gain[mode]);
	printf("| XPD:                  0x%02x |", ee->ee_xpd[mode]);
	printf(" XPD gain:             0x%02x |\n", ee->ee_x_gain[mode]);
	printf("| I gain:               0x%02x |", ee->ee_i_gain[mode]);
	printf(" Tx/Rx margin:         0x%02x |\n", ee->ee_margin_tx_rx[mode]);
	printf("| False detect backoff: 0x%02x |", ee->ee_false_detect[mode]);
	printf(" Noise Floor Threshold: %3d |\n", ee->ee_noise_floor_thr[mode]);
	printf("| ADC desired size:      %3d |", ee->ee_adc_desired_size[mode]);
	printf(" PGA desired size:      %3d |\n", ee->ee_pga_desired_size[mode]);
	printf("|=========================================================|\n");
	for (i = 0; i < AR5K_EEPROM_N_PCDAC; i++) {
		printf("| Antenna control  %2i:  0x%02x |", i, ee->ee_ant_control[mode][i]);
		i++;
		printf(" Antenna control  %2i:  0x%02x |\n", i, ee->ee_ant_control[mode][i]);
	}
	printf("|=========================================================|\n");
	for (i = 0; i < AR5K_EEPROM_N_OBDB; i++) {
		printf("| Octave Band %i:          %2i |", i, ee->ee_ob[mode][i]);
		printf(" db %i:                   %2i |\n", i, ee->ee_db[mode][i]);
	}
	printf("\\=========================================================/\n");

	if (ee->ee_version >= AR5K_EEPROM_VERSION_5_0 && (mode == AR5K_EEPROM_MODE_11A
	|| mode == AR5K_EEPROM_MODE_11G)) {
	printf("/==================== Turbo mode infos ===================\\\n");
	printf("| Switch Settling time: 0x%02x |", ee->ee_switch_settling_turbo[mode]);
	printf(" Tx/Rx margin:         0x%02x |\n", ee->ee_margin_tx_rx_turbo[mode]);
	printf("| Tx/Rx attenuation:    0x%02x |", ee->ee_atn_tx_rx_turbo[mode]);
	printf(" ADC desired size:      %3d |\n", ee->ee_adc_desired_size_turbo[mode]);
	printf("| PGA desired size:      %3d ", ee->ee_pga_desired_size_turbo[mode]);
	printf("|                            |\n");
	printf("\\=========================================================/\n");
	}
}

static void dump_rf5111_power_calinfo_for_mode(int mode, struct ath5k_eeprom_info *ee)
{
	struct	ath5k_chan_pcal_info *gen_chan_info;
	struct ath5k_chan_pcal_info_rf5111 *chan_pcal_info;
	u_int16_t cal_piers;
	int i, c;

	switch (mode) {
	case AR5K_EEPROM_MODE_11A:
		gen_chan_info = ee->ee_pwr_cal_a;
		cal_piers = ee->ee_cal_piers_a;
		break;
	case AR5K_EEPROM_MODE_11B:
		gen_chan_info = ee->ee_pwr_cal_b;
		cal_piers = ee->ee_cal_piers_b;
		break;
	case AR5K_EEPROM_MODE_11G:
		gen_chan_info = ee->ee_pwr_cal_g;
		cal_piers = ee->ee_cal_piers_g;
		break;
	default:
		return;
	}

	printf("/=============================== Per channel power calibration ================================\\\n");
	printf("| Freq | pwr_0 | pwr_1 | pwr_2 | pwr_3 | pwr_4 | pwr_5 | pwr_6 | pwr_7 | pwr_8 | pwr_9 | pwr10 |\n");
	printf("|      | pcdac | pcdac | pcdac | pcdac | pcdac | pcdac | pcdac | pcdac | pcdac | pcdac | pcdac |\n");

	for (i = 0; i < cal_piers; i++) {
		char buf[16];
		chan_pcal_info = gen_chan_info[i].rf5111_info;

		printf("|======|=======|=======|=======|=======|=======|=======|=======|=======|=======|=======|=======|\n");
		printf("| %4i |", gen_chan_info[i].freq);
		if (ee->ee_version <= AR5K_EEPROM_VERSION_3_2) {
			for (c = 0; c < AR5K_EEPROM_N_PWR_POINTS_5111; c++) {
				printf(" %2i.%02i |", chan_pcal_info->pwr[c] / 2,
				       chan_pcal_info->pwr[c] % 2 * 50);
			}

			printf("\n|      |");
		} else {
			for (c = 0; c < AR5K_EEPROM_N_PWR_POINTS_5111; c++) {
				printf(" %2i.%02i |", chan_pcal_info->pwr[c] / 4,
				       chan_pcal_info->pwr[c] % 4 * 25);
			}

			printf("\n|      |");
		}

		for (c = 0; c < AR5K_EEPROM_N_PWR_POINTS_5111; c++) {
			snprintf(buf, sizeof(buf), "[%i]",
				 chan_pcal_info->pcdac[c]);
			printf("%6s |", buf);
		}

		printf("\n");

	}
	printf("\\==============================================================================================/\n");
}

static void dump_rf5112_power_calinfo_for_mode(int mode, struct ath5k_eeprom_info *ee)
{
	struct	ath5k_chan_pcal_info *gen_chan_info;
	struct ath5k_chan_pcal_info_rf5112 *chan_pcal_info;
	u_int16_t cal_piers;
	int i, c;

	switch (mode) {
	case AR5K_EEPROM_MODE_11A:
		gen_chan_info = ee->ee_pwr_cal_a;
		cal_piers = ee->ee_cal_piers_a;
		break;
	case AR5K_EEPROM_MODE_11B:
		gen_chan_info = ee->ee_pwr_cal_b;
		cal_piers = ee->ee_cal_piers_b;
		break;
	case AR5K_EEPROM_MODE_11G:
		gen_chan_info = ee->ee_pwr_cal_g;
		cal_piers = ee->ee_cal_piers_g;
		break;
	default:
		return;
	}

	printf("/=================== Per channel power calibration ====================\\\n");
	printf("| Freq | pwr_0 | pwr_1 | pwr_2 | pwr_3 |pwrx3_0|pwrx3_1|pwrx3_2|max_pwr|\n");
	printf("|      | pcdac | pcdac | pcdac | pcdac | pcdac | pcdac | pcdac |       |\n");

	for (i = 0; i < cal_piers; i++) {
		char buf[16];

		chan_pcal_info = gen_chan_info[i].rf5112_info;

		printf("|======|=======|=======|=======|=======|=======|=======|=======|=======|\n");
		printf("| %4i |", gen_chan_info[i].freq);
		for (c = 0; c < AR5K_EEPROM_N_XPD0_POINTS; c++) {
			printf(" %2i.%02i |", chan_pcal_info->pwr_x0[c] / 4,
			       chan_pcal_info->pwr_x0[c] % 4 * 25);
		}
		for (c = 0; c < AR5K_EEPROM_N_XPD3_POINTS; c++) {
			printf(" %2i.%02i |", chan_pcal_info->pwr_x3[c] / 4,
			       chan_pcal_info->pwr_x3[c] % 4 * 25);
		}
		printf(" %2i.%02i |\n", gen_chan_info[i].max_pwr / 4,
		       gen_chan_info[i].max_pwr % 4 * 25);

		printf("|      |");
		for (c = 0; c < AR5K_EEPROM_N_XPD0_POINTS; c++) {
			snprintf(buf, sizeof(buf), "[%i]",
				 chan_pcal_info->pcdac_x0[c]);
			printf("%6s |", buf);
		}
		for (c = 0; c < AR5K_EEPROM_N_XPD3_POINTS; c++) {
			snprintf(buf, sizeof(buf), "[%i]",
				 chan_pcal_info->pcdac_x3[c]);
			printf("%6s |", buf);
		}
		printf("       |\n");

	}
	printf("\\======================================================================/\n");
}

static void dump_rf2413_power_calinfo_for_mode(int mode, struct ath5k_eeprom_info *ee)
{
	struct	ath5k_chan_pcal_info *gen_chan_info;
	struct ath5k_chan_pcal_info_rf2413 *chan_pcal_info;
	u_int16_t cal_piers;
	int i, c;

	switch (mode) {
	case AR5K_EEPROM_MODE_11A:
		gen_chan_info = ee->ee_pwr_cal_a;
		cal_piers = ee->ee_cal_piers_a;
		break;
	case AR5K_EEPROM_MODE_11B:
		gen_chan_info = ee->ee_pwr_cal_b;
		cal_piers = ee->ee_cal_piers_b;
		break;
	case AR5K_EEPROM_MODE_11G:
		gen_chan_info = ee->ee_pwr_cal_g;
		cal_piers = ee->ee_cal_piers_g;
		break;
	default:
		return;
	}

	printf("/====================== Per channel power calibration ===================\\\n");
	printf("| Freq |  pwr_i  |    pwr_0    |    pwr_1    |    pwr_2    |    pwr_3    |\n");
	printf("|      | pddac_i |   pddac_0   |   pddac_1   |   pddac_2   |   pddac_3   |\n");

	for (i = 0; i < cal_piers; i++) {

		chan_pcal_info = gen_chan_info[i].rf2413_info;

		printf("|======|=========|=============|=============|=============|=============|\n");
		printf("| %4i |         |             |             |             |             |\n", gen_chan_info[i].freq);
		printf("|------|---------|-------------|-------------|-------------|-------------|\n");
		for (c = 0; c < chan_pcal_info->pd_gains; c++){
			printf("|      |    %2i   |    %2i.%02i    |    %2i.%02i    |    %2i.%02i    |    %2i.%02i    |\n",
							chan_pcal_info->pwr_i[c],
							chan_pcal_info->pwr[c][0] / 2,
							chan_pcal_info->pwr[c][0] % 2 * 50,
							chan_pcal_info->pwr[c][1] / 2,
							chan_pcal_info->pwr[c][1] % 2 * 50,
							chan_pcal_info->pwr[c][2] / 2,
							chan_pcal_info->pwr[c][2] % 2 * 50,
							chan_pcal_info->pwr[c][3] / 2,
							chan_pcal_info->pwr[c][3] % 2 * 50);

			printf("|      |   %3i   |      %3i    |      %3i    |      %3i    |      %3i    |\n",
							chan_pcal_info->pddac_i[c],
							chan_pcal_info->pddac[c][0],
							chan_pcal_info->pddac[c][1],
							chan_pcal_info->pddac[c][2],
							chan_pcal_info->pddac[c][3]);
			if ( c < chan_pcal_info->pd_gains - 1)		
			printf("|------|---------|-------------|-------------|-------------|-------------|\n");
		}
	}
	printf("\\========================================================================/\n");
}

static void dump_rate_calinfo_for_mode(int mode, struct ath5k_eeprom_info *ee)
{
	int i;
	struct ath5k_rate_pcal_info *rate_pcal_info;
	u_int16_t rate_target_pwr_num;

	switch (mode) {
	case AR5K_EEPROM_MODE_11A:
		rate_pcal_info = ee->ee_rate_tpwr_a;
		rate_target_pwr_num = ee->ee_rate_target_pwr_num_a;
		break;
	case AR5K_EEPROM_MODE_11B:
		rate_pcal_info = ee->ee_rate_tpwr_b;
		rate_target_pwr_num = ee->ee_rate_target_pwr_num_b;
		break;
	case AR5K_EEPROM_MODE_11G:
		rate_pcal_info = ee->ee_rate_tpwr_g;
		rate_target_pwr_num = ee->ee_rate_target_pwr_num_g;
		break;
	default:
		return;
	}

	printf("/============== Per rate power calibration ===========\\\n");
	if (mode == AR5K_EEPROM_MODE_11B)
		printf("| Freq |   1Mbit/s  | 2Mbit/s  | 5.5Mbit/s | 11Mbit/s |\n");
	else
		printf("| Freq | 6-24Mbit/s | 36Mbit/s |  48Mbit/s | 54Mbit/s |\n");

	for (i = 0; i < rate_target_pwr_num; i++) {

		printf("|======|============|==========|===========|==========|\n");
		printf("| %4i |", rate_pcal_info[i].freq);
		printf("    %2i.%02i   |",rate_pcal_info[i].target_power_6to24 /2,
					rate_pcal_info[i].target_power_6to24 % 2);
		printf("  %2i.%02i   |",rate_pcal_info[i].target_power_36 /2,
					rate_pcal_info[i].target_power_36 % 2);
		printf("   %2i.%02i   |",rate_pcal_info[i].target_power_48 /2,
					rate_pcal_info[i].target_power_48 % 2);
		printf("  %2i.%02i   |\n",rate_pcal_info[i].target_power_54 /2,
					rate_pcal_info[i].target_power_54 % 2);
	}
	printf("\\=====================================================/\n");
}

static u_int32_t extend_tu(u_int32_t base_tu, u_int32_t val, u_int32_t mask)
{
	u_int32_t result;

	result = (base_tu & ~mask) | (val & mask);
	if ((base_tu & mask) > (val & mask))
		result += mask + 1;
	return result;
}

static void dump_timers_register(void)
{
#define AR5K_TIMER0_5210		0x802c	/* next TBTT */
#define AR5K_TIMER0_5211		0x8028
#define AR5K_TIMER0			(mac_revision == AR5K_SREV_AR5210 ? \
					AR5K_TIMER0_5210 : AR5K_TIMER0_5211)

#define AR5K_TIMER1_5210		0x8030	/* next DMA beacon */
#define AR5K_TIMER1_5211		0x802c
#define AR5K_TIMER1			(mac_revision == AR5K_SREV_AR5210 ? \
					AR5K_TIMER1_5210 : AR5K_TIMER1_5211)

#define AR5K_TIMER2_5210		0x8034	/* next SWBA interrupt */
#define AR5K_TIMER2_5211		0x8030
#define AR5K_TIMER2			(mac_revision == AR5K_SREV_AR5210 ? \
					AR5K_TIMER2_5210 : AR5K_TIMER2_5211)

#define AR5K_TIMER3_5210		0x8038	/* next ATIM window */
#define AR5K_TIMER3_5211		0x8034
#define AR5K_TIMER3			(mac_revision == AR5K_SREV_AR5210 ? \
					AR5K_TIMER3_5210 : AR5K_TIMER3_5211)

#define AR5K_TSF_L32_5210		0x806c	/* TSF (lower 32 bits) */
#define AR5K_TSF_L32_5211		0x804c
#define AR5K_TSF_L32			(mac_revision == AR5K_SREV_AR5210 ? \
					AR5K_TSF_L32_5210 : AR5K_TSF_L32_5211)

#define AR5K_TSF_U32_5210		0x8070
#define AR5K_TSF_U32_5211		0x8050
#define AR5K_TSF_U32			(mac_revision == AR5K_SREV_AR5210 ? \
					AR5K_TSF_U32_5210 : AR5K_TSF_U32_5211)

#define AR5K_BEACON_5210		0x8024
#define AR5K_BEACON_5211		0x8020
#define AR5K_BEACON			(mac_revision == AR5K_SREV_AR5210 ? \
					AR5K_BEACON_5210 : AR5K_BEACON_5211)

#define AR5K_LAST_TSTP			0x8080

	const int timer_mask = 0xffff;

	u_int32_t timer0, timer1, timer2, timer3, now_tu;
	u_int32_t timer0_tu, timer1_tu, timer2_tu, timer3_tu;
	u_int64_t now_tsf;

	timer0 = AR5K_REG_READ(AR5K_TIMER0);		/* 0x0000ffff */
	timer1 = AR5K_REG_READ(AR5K_TIMER1_5211);	/* 0x0007ffff */
	timer2 = AR5K_REG_READ(AR5K_TIMER2_5211);	/* 0x?1ffffff */
	timer3 = AR5K_REG_READ(AR5K_TIMER3_5211);	/* 0x0000ffff */

	now_tsf = ((u_int64_t)AR5K_REG_READ(AR5K_TSF_U32_5211) << 32)
		| (u_int64_t)AR5K_REG_READ(AR5K_TSF_L32_5211);

	now_tu = now_tsf >> 10;

	timer0_tu = extend_tu(now_tu, timer0, 0xffff);
	printf("TIMER0: 0x%08x, TBTT: %5u, TU: 0x%08x\n", timer0,
	       timer0 & timer_mask, timer0_tu);
	timer1_tu = extend_tu(now_tu, timer1 >> 3, 0x7ffff >> 3);
	printf("TIMER1: 0x%08x, DMAb: %5u, TU: 0x%08x (%+d)\n", timer1,
	       (timer1 >> 3) & timer_mask, timer1_tu, timer1_tu - timer0_tu);
	timer2_tu = extend_tu(now_tu, timer2 >> 3, 0x1ffffff >> 3);
	printf("TIMER2: 0x%08x, SWBA: %5u, TU: 0x%08x (%+d)\n", timer2,
	       (timer2 >> 3) & timer_mask, timer2_tu, timer2_tu - timer0_tu);
	timer3_tu = extend_tu(now_tu, timer3, 0xffff);
	printf("TIMER3: 0x%08x, ATIM: %5u, TU: 0x%08x (%+d)\n", timer3,
	       timer3 & timer_mask, timer3_tu, timer3_tu - timer0_tu);
	printf("TSF: 0x%016llx, TSFTU: %5u, TU: 0x%08x\n",
	       (unsigned long long)now_tsf, now_tu & timer_mask, now_tu);

	printf("BEACON: 0x%08x\n", AR5K_REG_READ(AR5K_BEACON));
	printf("LAST_TSTP: 0x%08x\n", AR5K_REG_READ(AR5K_LAST_TSTP));
}

#define AR5K_KEYTABLE_0_5210		0x9000
#define AR5K_KEYTABLE_0_5211		0x8800
#define AR5K_KEYTABLE_0			(mac_revision == AR5K_SREV_AR5210 ? \
					AR5K_KEYTABLE_0_5210 : \
					AR5K_KEYTABLE_0_5211)

#define AR5K_KEYTABLE(_n)		(AR5K_KEYTABLE_0_5211 + ((_n) << 5))
#define AR5K_KEYTABLE_OFF(_n, x)	(AR5K_KEYTABLE(_n) + ((x) << 2))
#define AR5K_KEYTABLE_VALID		0x00008000

#define AR5K_KEYTABLE_SIZE_5210		64
#define AR5K_KEYTABLE_SIZE_5211		128
#define AR5K_KEYTABLE_SIZE		(mac_revision == AR5K_SREV_AR5210 ? \
					AR5K_KEYTABLE_SIZE_5210 : \
					AR5K_KEYTABLE_SIZE_5211)

static void keycache_dump(void)
{
	int i, keylen;
	u_int32_t val0, val1, val2, val3, val4, keytype, ant, mac0, mac1;

	/* dump all 128 entries */
	printf("Dumping keycache entries...\n");
	for (i = 0; i < AR5K_KEYTABLE_SIZE; i++) {
		mac1 = AR5K_REG_READ(AR5K_KEYTABLE_OFF(i, 7));
		if (mac1 & AR5K_KEYTABLE_VALID) {
			val0    = AR5K_REG_READ(AR5K_KEYTABLE_OFF(i, 0));
			val1    = AR5K_REG_READ(AR5K_KEYTABLE_OFF(i, 1));
			val2    = AR5K_REG_READ(AR5K_KEYTABLE_OFF(i, 2));
			val3    = AR5K_REG_READ(AR5K_KEYTABLE_OFF(i, 3));
			val4    = AR5K_REG_READ(AR5K_KEYTABLE_OFF(i, 4));
			keytype = AR5K_REG_READ(AR5K_KEYTABLE_OFF(i, 5));
			ant = keytype & 8;
			keytype &= ~8;
			switch (keytype) {
			case 0: /* WEP40  */ keylen =  40 / 8; break;
			case 1: /* WEP104 */ keylen = 104 / 8; break;
			case 3: /* WEP128 */ keylen = 128 / 8; break;
			case 4: /* TKIP   */ keylen = 128 / 8; break;
			case 5: /* AES    */ keylen = 128 / 8; break;
			case 6: /* CCM    */ keylen = 128 / 8; break;
			default:             keylen = 0;       break;
			}
			mac0 = AR5K_REG_READ(AR5K_KEYTABLE_OFF(i, 6));

			printf("[%3u] keytype %d [%s%s%s%s%s%s%s%s] mac %02x:%02x:%02x:%02x:%02x:%02x key:%08x-%08x-%08x-%08x-%08x\n",
			       i,
			       keytype,
			       keytype == 0 ? "WEP40 " : "",
			       keytype == 1 ? "WEP104" : "",
			       keytype == 3 ? "WEP128" : "",
			       keytype == 4 ? "TKIP  " : "",
			       keytype == 5 ? "AES   " : "",
			       keytype == 6 ? "CCM   " : "",
			       keytype == 7 ? "NULL  " : "",
			       ant     == 8 ? "+ANT"   : "",
			       ((mac0 <<  1) & 0xff),
			       ((mac0 >>  7) & 0xff),
			       ((mac0 >> 15) & 0xff),
			       ((mac0 >> 23) & 0xff),
			       ((mac1 <<  1) & 0xff) | (mac0 >> 31),
			       ((mac1 >>  7) & 0xff),
			       val0, val1, val2, val3, val4);
		}
	}
}

/* copy key index (0) to key index (idx) */

static void keycache_copy(int idx)
{
	u_int32_t val0, val1, val2, val3, val4, keytype, mac0, mac1;

	printf("Copying keycache entry 0 to %d\n", idx);
	if (idx < 0 || idx >= AR5K_KEYTABLE_SIZE) {
		printf("invalid keycache index\n");
		return;
	}

	val0    = AR5K_REG_READ(AR5K_KEYTABLE_OFF(0, 0));
	val1    = AR5K_REG_READ(AR5K_KEYTABLE_OFF(0, 1));
	val2    = AR5K_REG_READ(AR5K_KEYTABLE_OFF(0, 2));
	val3    = AR5K_REG_READ(AR5K_KEYTABLE_OFF(0, 3));
	val4    = AR5K_REG_READ(AR5K_KEYTABLE_OFF(0, 4));
	keytype = AR5K_REG_READ(AR5K_KEYTABLE_OFF(0, 5));
	mac0    = AR5K_REG_READ(AR5K_KEYTABLE_OFF(0, 6));
	mac1    = AR5K_REG_READ(AR5K_KEYTABLE_OFF(0, 7));

	AR5K_REG_WRITE(AR5K_KEYTABLE_OFF(idx, 0), val0);
	AR5K_REG_WRITE(AR5K_KEYTABLE_OFF(idx, 1), val1);
	AR5K_REG_WRITE(AR5K_KEYTABLE_OFF(idx, 2), val2);
	AR5K_REG_WRITE(AR5K_KEYTABLE_OFF(idx, 3), val3);
	AR5K_REG_WRITE(AR5K_KEYTABLE_OFF(idx, 4), val4);
	AR5K_REG_WRITE(AR5K_KEYTABLE_OFF(idx, 5), keytype);
	AR5K_REG_WRITE(AR5K_KEYTABLE_OFF(idx, 6), mac0);
	AR5K_REG_WRITE(AR5K_KEYTABLE_OFF(idx, 7), mac1);
}

static void sta_id0_id1_dump(void)
{
#define AR5K_STA_ID0			0x8000
#define AR5K_STA_ID1			0x8004
#define AR5K_STA_ID1_AP                 0x00010000
#define AR5K_STA_ID1_ADHOC              0x00020000
#define AR5K_STA_ID1_NO_KEYSRCH		0x00080000

	u_int32_t sta_id0, sta_id1;

	sta_id0 = AR5K_REG_READ(AR5K_STA_ID0);
	sta_id1 = AR5K_REG_READ(AR5K_STA_ID1);
	printf("STA_ID0: %02x:%02x:%02x:%02x:%02x:%02x\n",
	       (sta_id0 >>  0) & 0xff,
	       (sta_id0 >>  8) & 0xff,
	       (sta_id0 >> 16) & 0xff,
	       (sta_id0 >> 24) & 0xff,
	       (sta_id1 >>  0) & 0xff,
	       (sta_id1 >>  8) & 0xff);
	printf("STA_ID1: 0x%08x, AP: %d, IBSS: %d, KeyCache Disable: %d\n",
	       sta_id1,
	       sta_id1 & AR5K_STA_ID1_AP ? 1 : 0,
	       sta_id1 & AR5K_STA_ID1_ADHOC ? 1 : 0,
	       sta_id1 & AR5K_STA_ID1_NO_KEYSRCH ? 1 : 0);
}

void show_eeprom_info(struct ath5k_eeprom_info *ee)
{
	u_int8_t eemap;

	eemap = AR5K_EEPROM_EEMAP(ee->ee_misc0);

	printf("/============== EEPROM Information =============\\\n");
	printf("| EEPROM Version:   %1x.%1x |",
	       (ee->ee_version & 0xF000) >> 12, ee->ee_version & 0xFFF);

	printf(" EEPROM Size: %3d kbit |\n", eeprom_size * 8 / 1024);

	printf("| EEMAP:              %i |", eemap);

	printf(" Reg. Domain:     0x%02X |\n", ee->ee_regdomain);

	dump_capabilities(ee);
	printf("\n");
}

int main(int argc, char *argv[])
{
	unsigned long long dev_addr;
	u_int16_t srev, phy_rev_5ghz, phy_rev_2ghz, ee_magic;
	u_int8_t error, dev_type;
	u_int16_t eesize;
	struct ath5k_eeprom_info *ee;
	int fd;
	unsigned int i;
	int anr = 1;
	int do_write = 0;	/* default: read only */
	int do_dump = 0;
	int reg_read = 0;
	int reg_write = 0;
	unsigned int reg_write_val = 0;
	unsigned int timer_count = 1;
	int do_keycache_dump = 0;
	int keycache_copy_idx = 0;

	struct {
		int valid;
		int value;
	} gpio_set[AR5K_NUM_GPIO];
	int nr_gpio_set = 0;

	for (i = 0; i < ARRAY_SIZE(gpio_set); i++)
		gpio_set[i].valid = 0;

	if (argc < 2) {
		usage(argv[0]);
		return -1;
	}

	while (anr < argc && argv[anr][0] == '-') {
		switch (argv[anr][1]) {
		case 't':
			if (++anr < argc) {
				timer_count = atoi(argv[anr]);
				printf("timer_count:%d\n", timer_count);
			} else {
				usage(argv[0]);
				return 0;
			}
			break;
		case 'w':
			do_write = 1;
			break;
		case 'g':
			anr++;
			if (strlen(argv[anr]) != 3 || argv[anr][1] != ':' ||
			    argv[anr][0] < '0' || argv[anr][0] > '5' ||
			    (argv[anr][2] != '0' && argv[anr][2] != '1')) {
				err("invalid GPIO spec. %s", argv[anr]);
				return 2;
			}
			gpio_set[argv[anr][0] - '0'].valid = 1;
			gpio_set[argv[anr][0] - '0'].value = argv[anr][2] - '0';
			nr_gpio_set++;
			break;

		case 'f':
			force_write = 1;
			break;

		case 'v':
			verbose = 1;
			break;

		case 'd':
			do_dump = 1;
			break;

		case 'R':
			anr++;
			reg_read = strtoul(argv[anr], NULL, 16);
			break;

		case 'W':
			anr++;
			reg_write = strtoul(argv[anr++], NULL, 16);
			reg_write_val = strtoul(argv[anr], NULL, 16);
			break;

		case 'k':
			do_keycache_dump = 1;
			break;

		case 'K':
			keycache_copy_idx = atoi(argv[++anr]);
			break;

		case 'h':
			usage(argv[0]);
			return 0;
			break;

		default:
			err("unknown option %s", argv[anr]);
			return 2;
		}

		anr++;
	}

	if (anr >= argc) {
		err("missing device address");
		usage(argv[0]);
		return 3;
	}

	dev_addr = strtoull(argv[anr], NULL, 16);

	fd = open("/dev/mem", O_RDWR);
	if (fd < 0) {
		printf("Opening /dev/mem failed!\n");
		return -2;
	}

	mem = mmap(NULL, AR5K_PCI_MEM_SIZE, PROT_READ | PROT_WRITE,
		   MAP_SHARED | MAP_FILE, fd, dev_addr);

	if (mem == MAP_FAILED) {
		printf("mmap of device at 0x%08llX for 0x%X bytes failed - "
		       "%s\n", dev_addr, AR5K_PCI_MEM_SIZE, strerror(errno));
		return -3;
	}

	/* wake from power-down and remove reset (in case the driver isn't running) */
	{
		u_int32_t
		    sleep_ctl = AR5K_REG_READ(AR5K_SLEEP_CTL),
		    reset_ctl = AR5K_REG_READ(AR5K_RESET_CTL);

		dbg("sleep_ctl reg %08x   reset_ctl reg %08x",
		    sleep_ctl, reset_ctl);
		if (sleep_ctl & AR5K_SLEEP_CTL_SLE_SLP) {
			dbg("waking up the chip");
			AR5K_REG_WRITE(AR5K_SLEEP_CTL,
				       (sleep_ctl & ~AR5K_SLEEP_CTL_SLE_SLP));
		}

		if (reset_ctl) {
			dbg("removing resets");
			AR5K_REG_WRITE(AR5K_RESET_CTL, 0);
		}
	}

	AR5K_REG_DISABLE_BITS(AR5K_PCICFG, AR5K_PCICFG_SPWR_DN);
	usleep(500);

	if (reg_read) {
		printf("READ %04x = %08x\n", reg_read, AR5K_REG_READ(reg_read));
		return 0;
	}

	if (reg_write) {
		printf("WRITE %04x = %08x\n", reg_write, reg_write_val);
		AR5K_REG_WRITE(reg_write, reg_write_val);
		return 0;
	}

	srev = AR5K_REG_READ(AR5K_SREV);
	if (srev >= 0x0100) {
		printf("MAC revision 0x%04x is not supported!\n", srev);
		return -1;
	}
	mac_revision = srev & AR5K_SREV_REV;
	if (mac_revision < AR5K_SREV_AR5311)
		eeprom_access = AR5K_EEPROM_ACCESS_5210;
	else
		eeprom_access = AR5K_EEPROM_ACCESS_5211;

	printf(" -==Device Information==-\n");

	printf("MAC Revision: %-5s (0x%02x)\n",
	       ath5k_hw_get_mac_name(mac_revision), mac_revision);

	/* Verify EEPROM magic value first */
	error = ath5k_hw_eeprom_read(AR5K_EEPROM_MAGIC, &ee_magic);

	if (error) {
		printf("Unable to read EEPROM Magic value!\n");
		return -1;
	}

	if (ee_magic != AR5K_EEPROM_MAGIC_VALUE) {
		printf("Warning: Invalid EEPROM Magic number!\n");
	}

	ee = calloc(sizeof(struct ath5k_eeprom_info), 1);
	if (!ee) {
		printf("Cannot allocate memory for EEPROM information\n");
		return -1;
	}

	if (ath5k_eeprom_init(ee)) {
		printf("EEPROM init failed\n");
		return -1;
	}

	/* 1 = ?? 2 = ?? 3 = card 4 = wmac */
	dev_type = AR5K_EEPROM_HDR_DEVICE(ee->ee_header);
	printf("Device type:  %1i\n", dev_type);

	if (AR5K_EEPROM_HDR_11A(ee->ee_header))
		phy_rev_5ghz = ath5k_hw_radio_revision(1);
	else
		phy_rev_5ghz = 0;

	if (AR5K_EEPROM_HDR_11B(ee->ee_header))
		phy_rev_2ghz = ath5k_hw_radio_revision(0);
	else
		phy_rev_2ghz = 0;

	if (phy_rev_5ghz) {
		printf("5GHz PHY Revision: %-5s (0x%02x)\n",
		       ath5k_hw_get_phy_name(phy_rev_5ghz), phy_rev_5ghz);
	}
	if (phy_rev_2ghz) {
		printf("2GHz PHY Revision: %-5s (0x%02x)\n",
		       ath5k_hw_get_phy_name(phy_rev_2ghz), phy_rev_2ghz);
	}

	printf("\n");

	eesize = AR5K_REG_MS(AR5K_REG_READ(AR5K_PCICFG), AR5K_PCICFG_EESIZE);

	if (eesize == 0)
		eeprom_size = 4096 / 8;
	else if (eesize == 1)
		eeprom_size = 8192 / 8;
	else if (eesize == 2)
		eeprom_size = 16384 / 8;
	else
		eeprom_size = 0;

	show_eeprom_info(ee);

	printf("/=========================================================\\\n");
	printf("|          Calibration data common for all modes          |\n");
	printf("|=========================================================|\n");
	printf("|          CCK/OFDM gain delta:            %2i             |\n", ee->ee_cck_ofdm_gain_delta);
	printf("|          CCK/OFDM power delta:           %2i             |\n", ee->ee_cck_ofdm_power_delta);
	printf("|          Scaled CCK delta:               %2i             |\n", ee->ee_scaled_cck_delta);
	printf("|          2GHz Antenna gain:              %2i             |\n", AR5K_EEPROM_ANT_GAIN_2GHZ(ee->ee_ant_gain));
	printf("|          5GHz Antenna gain:              %2i             |\n", AR5K_EEPROM_ANT_GAIN_5GHZ(ee->ee_ant_gain));
	printf("|          Turbo 2W maximum dBm:           %2i             |\n", AR5K_EEPROM_HDR_T_5GHZ_DBM(ee->ee_header));
	printf("|          Target power start:          0x%03x             |\n", AR5K_EEPROM_TARGET_PWRSTART(ee->ee_misc1));
	printf("|          EAR Start:                   0x%03x             |\n", AR5K_EEPROM_EARSTART(ee->ee_misc0));
	printf("\\=========================================================/\n");

	printf("\n");
	if (AR5K_EEPROM_HDR_11A(ee->ee_header)) {
		printf("/=========================================================\\\n");
		printf("|          Calibration data for 802.11a operation         |\n");
		dump_calinfo_for_mode(AR5K_EEPROM_MODE_11A, ee);
		dump_rate_calinfo_for_mode(AR5K_EEPROM_MODE_11A, ee);
		if (ee->ee_version >= AR5K_EEPROM_VERSION_4_0 && AR5K_EEPROM_EEMAP(ee->ee_misc0) == 1)
				dump_rf5112_power_calinfo_for_mode(AR5K_EEPROM_MODE_11A, ee);
		else if (ee->ee_version >= AR5K_EEPROM_VERSION_5_0 && AR5K_EEPROM_EEMAP(ee->ee_misc0) == 2)
				dump_rf2413_power_calinfo_for_mode(AR5K_EEPROM_MODE_11A, ee);
		else if (ee->ee_version >= AR5K_EEPROM_VERSION_3_0 || AR5K_EEPROM_EEMAP(ee->ee_misc0) == 0)
				dump_rf5111_power_calinfo_for_mode(AR5K_EEPROM_MODE_11A, ee);
		printf("\n");
	}

	if (AR5K_EEPROM_HDR_11B(ee->ee_header)) {
		printf("/=========================================================\\\n");
		printf("|          Calibration data for 802.11b operation         |\n");
		dump_calinfo_for_mode(AR5K_EEPROM_MODE_11B, ee);
		dump_rate_calinfo_for_mode(AR5K_EEPROM_MODE_11B, ee);
		if (ee->ee_version >= AR5K_EEPROM_VERSION_4_0 && AR5K_EEPROM_EEMAP(ee->ee_misc0) == 1)
				dump_rf5112_power_calinfo_for_mode(AR5K_EEPROM_MODE_11B, ee);
		else if (ee->ee_version >= AR5K_EEPROM_VERSION_5_0 && AR5K_EEPROM_EEMAP(ee->ee_misc0) == 2)
				dump_rf2413_power_calinfo_for_mode(AR5K_EEPROM_MODE_11B, ee);
		else if (ee->ee_version >= AR5K_EEPROM_VERSION_3_0 || AR5K_EEPROM_EEMAP(ee->ee_misc0) == 0)
				dump_rf5111_power_calinfo_for_mode(AR5K_EEPROM_MODE_11B, ee);
		printf("\n");
	}

	if (AR5K_EEPROM_HDR_11G(ee->ee_header)) {
		printf("/=========================================================\\\n");
		printf("|          Calibration data for 802.11g operation         |\n");
		dump_calinfo_for_mode(AR5K_EEPROM_MODE_11G, ee);
		dump_rate_calinfo_for_mode(AR5K_EEPROM_MODE_11G, ee);
		if (ee->ee_version >= AR5K_EEPROM_VERSION_4_0 && AR5K_EEPROM_EEMAP(ee->ee_misc0) == 1)
				dump_rf5112_power_calinfo_for_mode(AR5K_EEPROM_MODE_11G, ee);
		else if (ee->ee_version >= AR5K_EEPROM_VERSION_5_0 && AR5K_EEPROM_EEMAP(ee->ee_misc0) == 2)
				dump_rf2413_power_calinfo_for_mode(AR5K_EEPROM_MODE_11G, ee);
		else if (ee->ee_version >= AR5K_EEPROM_VERSION_3_0 || AR5K_EEPROM_EEMAP(ee->ee_misc0) == 0)
				dump_rf5111_power_calinfo_for_mode(AR5K_EEPROM_MODE_11G, ee);
		printf("\n");
	}

	/* print current GPIO settings */
	printf("GPIO registers: CR 0x%08x, DO 0x%08x, DI 0x%08x\n",
	       AR5K_REG_READ(AR5K_GPIOCR), AR5K_REG_READ(AR5K_GPIODO),
	       AR5K_REG_READ(AR5K_GPIODI));

	if (do_dump) {
		u_int16_t data;
		FILE *dumpfile = fopen("ath-eeprom-dump.bin", "w");

		printf("\nEEPROM dump (%d bytes)\n", eeprom_size);
		printf("==============================================");
		for (i = 0; i < eeprom_size / 2; i++) {
			error =
			    ath5k_hw_eeprom_read(i, &data);
			if (error) {
				printf("\nUnable to read at %04x\n", i);
				continue;
			}
			if (!(i % 8))
				printf("\n%04x: ", i);
			printf(" %04x", data);
			fwrite(&data, 2, 1, dumpfile);
		}
		printf("\n==============================================\n");
		fclose(dumpfile);
	}

	if (do_write) {
		u_int32_t rcr = AR5K_REG_READ(AR5K_GPIOCR),
		    rdo = AR5K_REG_READ(AR5K_GPIODO);
		u_int32_t old_cr = rcr, old_do = rdo;
		int rc;

		if (mac_revision >= AR5K_SREV_AR5213 && !nr_gpio_set) {
			dbg("new MAC %x (>= AR5213) set GPIO4 to low",
			    mac_revision);
			gpio_set[4].valid = 1;
			gpio_set[4].value = 0;
		}

		/* set GPIOs */
		dbg("old GPIO CR %08x DO %08x DI %08x",
		    rcr, rdo, AR5K_REG_READ(AR5K_GPIODI));

		for (i = 0; i < ARRAY_SIZE(gpio_set); i++) {
			if (gpio_set[i].valid) {
				rcr |= AR5K_GPIOCR_OUT(i);	/* we use mode 3 */
				rcr &= ~AR5K_GPIOCR_INT_SEL(i);
				rdo &= ~(1 << i);
				rdo |= (gpio_set[i].value << i);
			}
		}

		if (rcr != old_cr) {
			dbg("GPIO CR %x -> %x", old_cr, rcr);
			AR5K_REG_WRITE(AR5K_GPIOCR, rcr);
		}
		usleep(5);

		if (rdo != old_do) {
			dbg("GPIO CR %x -> %x", old_do, rdo);
			AR5K_REG_WRITE(AR5K_GPIODO, rdo);
		}

		/* dump current values again if we have written anything */
		if (rcr != old_cr || rdo != old_do)
			dbg("new GPIO CR %08x DO %08x DI %08x",
			    AR5K_REG_READ(AR5K_GPIOCR),
			    AR5K_REG_READ(AR5K_GPIODO),
			    AR5K_REG_READ(AR5K_GPIODI));

		/* let argv[anr] be the first write parameter */
		anr++;

		rc = do_write_pairs(anr, argc, argv);

		/* restore old GPIO settings */
		if (rcr != old_cr) {
			dbg("restoring GPIO CR %x -> %x", rcr, old_cr);
			AR5K_REG_WRITE(AR5K_GPIOCR, old_cr);
		}
		usleep(5);

		if (rdo != old_do) {
			dbg("restoring GPIO CR %x -> %x", rdo, old_do);
			AR5K_REG_WRITE(AR5K_GPIODO, old_do);
		}

		return rc;
	}

	sta_id0_id1_dump();

	for (i = 0; i < timer_count; i++)
		dump_timers_register();

	if (do_keycache_dump)
		keycache_dump();

	if (keycache_copy_idx > 0)
		keycache_copy(keycache_copy_idx);

	return 0;
}
