/* -*- linux-c -*- */ /*- * Copyright (c) 2007 Nick Kossifidis * Copyright (c) 2007 Joerg Albert * * 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 . */ /* So here is how it works: * * First compile... * * gcc ath_info.c -o ath_info * * then find card's physical address * * lspci -v * * 02:02.0 Ethernet controller: Atheros Communications, Inc. AR5212 802.11abg NIC (rev 01) * Subsystem: Fujitsu Limited. Unknown device 1234 * Flags: bus master, medium devsel, latency 168, IRQ 23 * Memory at c2000000 (32-bit, non-prefetchable) [size=64K] * Capabilities: [44] Power Management version 2 * * address here is 0xc2000000 * * load madwifi-ng or madwifi-old if not already loaded (be sure the * interface is down!) * * modprobe ath_pci * * OR * * call: * setpci -s 02:02.0 command=0x41f cache_line_size=0x10 * * to enable access to the PCI device. * * and we run the thing... * * ./ath_info 0xc2000000 * * In order to change the regdomain to 0, call: * * ./ath_info -w 0xc2000000 regdomain 0 * * to change any PCI ID value, say: * * ./ath_info -w 0xc2000000 X * * with ::= pci_dev_id | pci_vendor_id | pci_class | * pci_subsys_dev_id | pci_subsys_vendor_id * * With newer chipsets (>= AR5004x, i.e. MAC >= AR5213), Atheros introduced * write protection on the EEPROM. On a GIGABYTE GN-WI01HT you can set GPIO 4 * to low to be able to write the EEPROM. This depends highly on the PCB layout, * so there may be different GPIO used. * This program currently sets GPIO 4 to low for a MAC >= AR5213, but you can * override this with the -g option: * * ./ath_info -g 5:0 -w 0xc2000000 regdomain X * * would set GPIO 5 to low (and wouldn't touch GPIO 4). -g can be given several times. * * The write function is currently not tested with 5210 devices. * * Use at your own risk, entering a false device address will have really * nasty results! * * Writing wrong values to the PCI id fields may prevent the driver from * detecting the card! * * Transmitting on illegal frequencies may violate state laws. Stick to the local * regulations! * * DISCLAIMER: * The authors are in no case responsible for damaged hardware or violation of * local laws by operating modified hardware. * */ #include #include #include #include #include #include #include #include #include #define dbg(fmt, __args__...) \ do { \ if (verbose) \ printf("#DBG %s: " fmt "\n", __FUNCTION__, ##__args__); \ } while (0) #define err(fmt, __args__...) \ fprintf(stderr, "#ERR %s: " fmt "\n", __FUNCTION__, ##__args__) #define AR5K_PCI_MEM_SIZE 0x10000 #define AR5K_ELEMENTS(_array) (sizeof(_array) / sizeof(_array[0])) #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 /* * Common silicon revision/version values */ enum ath5k_srev_type { AR5K_VERSION_VER, AR5K_VERSION_REV, AR5K_VERSION_RAD, }; struct ath5k_srev_name { const char *sr_name; enum ath5k_srev_type sr_type; u_int sr_val; }; #define AR5K_SREV_UNKNOWN 0xffff /* Known MAC revision numbers */ #define AR5K_SREV_VER_AR5210 0x00 #define AR5K_SREV_VER_AR5311 0x10 #define AR5K_SREV_VER_AR5311A 0x20 #define AR5K_SREV_VER_AR5311B 0x30 #define AR5K_SREV_VER_AR5211 0x40 #define AR5K_SREV_VER_AR5212 0x50 #define AR5K_SREV_VER_AR5213 0x55 #define AR5K_SREV_VER_AR5213A 0x59 #define AR5K_SREV_VER_AR2424 0xa0 #define AR5K_SREV_VER_AR5424 0xa3 #define AR5K_SREV_VER_AR5413 0xa4 #define AR5K_SREV_VER_AR5414 0xa5 #define AR5K_SREV_VER_AR5416 0xc0 #define AR5K_SREV_VER_AR5418 0xca #define AR5K_SREV_VER_AR2425 0xe0 /* Known PHY revision nymbers */ #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_2112 0x40 #define AR5K_SREV_RAD_2112A 0x45 #define AR5K_SREV_RAD_SC1 0x63 /* Found on 5413/5414 */ #define AR5K_SREV_RAD_SC2 0xa2 /* Found on 2424/5424 */ #define AR5K_SREV_RAD_5133 0xc0 /* MIMO found on 5418 */ static const struct ath5k_srev_name ath5k_srev_names[] = { {"5210", AR5K_VERSION_VER, AR5K_SREV_VER_AR5210}, {"5311", AR5K_VERSION_VER, AR5K_SREV_VER_AR5311}, {"5311A", AR5K_VERSION_VER, AR5K_SREV_VER_AR5311A}, {"5311B", AR5K_VERSION_VER, AR5K_SREV_VER_AR5311B}, {"5211", AR5K_VERSION_VER, AR5K_SREV_VER_AR5211}, {"5212", AR5K_VERSION_VER, AR5K_SREV_VER_AR5212}, {"5213", AR5K_VERSION_VER, AR5K_SREV_VER_AR5213}, {"5213A", AR5K_VERSION_VER, AR5K_SREV_VER_AR5213A}, {"2424", AR5K_VERSION_VER, AR5K_SREV_VER_AR2424}, {"5424", AR5K_VERSION_VER, AR5K_SREV_VER_AR5424}, {"5413", AR5K_VERSION_VER, AR5K_SREV_VER_AR5413}, {"5414", AR5K_VERSION_VER, AR5K_SREV_VER_AR5414}, {"5416", AR5K_VERSION_VER, AR5K_SREV_VER_AR5416}, {"5418", AR5K_VERSION_VER, AR5K_SREV_VER_AR5418}, {"2425", AR5K_VERSION_VER, AR5K_SREV_VER_AR2425}, {"xxxxx", AR5K_VERSION_VER, AR5K_SREV_UNKNOWN}, {"5110", AR5K_VERSION_RAD, AR5K_SREV_RAD_5110}, {"5111", AR5K_VERSION_RAD, AR5K_SREV_RAD_5111}, {"2111", AR5K_VERSION_RAD, AR5K_SREV_RAD_2111}, {"5112", AR5K_VERSION_RAD, AR5K_SREV_RAD_5112}, {"5112a", AR5K_VERSION_RAD, AR5K_SREV_RAD_5112A}, {"2112", AR5K_VERSION_RAD, AR5K_SREV_RAD_2112}, {"2112a", AR5K_VERSION_RAD, AR5K_SREV_RAD_2112A}, {"SChip", AR5K_VERSION_RAD, AR5K_SREV_RAD_SC1}, {"SChip", AR5K_VERSION_RAD, AR5K_SREV_RAD_SC2}, {"5133", AR5K_VERSION_RAD, AR5K_SREV_RAD_5133}, {"xxxxx", AR5K_VERSION_RAD, AR5K_SREV_UNKNOWN}, }; /* * Silicon revision register */ #define AR5K_SREV 0x4020 /* Register Address */ #define AR5K_SREV_REV 0x0000000f /* Mask for revision */ #define AR5K_SREV_REV_S 0 #define AR5K_SREV_VER 0x000000ff /* Mask for version */ #define AR5K_SREV_VER_S 4 /* * 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 #define AR5K_EEPROM_MAGIC 0x003d /* Offset for EEPROM Magic number */ #define AR5K_EEPROM_MAGIC_VALUE 0x5aa5 /* Default - found on EEPROM */ #define AR5K_EEPROM_MAGIC_5212 0x0000145c /* 5212 */ #define AR5K_EEPROM_MAGIC_5211 0x0000145b /* 5211 */ #define AR5K_EEPROM_MAGIC_5210 0x0000145a /* 5210 */ /* * EEPROM data register */ #define AR5K_EEPROM_DATA_5211 0x6004 #define AR5K_EEPROM_DATA_5210 0x6800 #define AR5K_EEPROM_DATA (mac_version == AR5K_SREV_VER_AR5210 ? \ AR5K_EEPROM_DATA_5210 : AR5K_EEPROM_DATA_5211) /* * EEPROM command register */ #define AR5K_EEPROM_CMD 0x6008 /* Register Addres */ #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 (mac_version == AR5K_SREV_VER_AR5210 ? \ 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 */ #define AR5K_EEPROM_REG_DOMAIN 0x00bf /* Offset for EEPROM regulatory domain */ #define AR5K_EEPROM_INFO_BASE 0x00c0 /* Offset for EEPROM header */ #define AR5K_EEPROM_INFO_MAX (0x400 - AR5K_EEPROM_INFO_BASE) #define AR5K_EEPROM_INFO_CKSUM 0xffff #define AR5K_EEPROM_INFO(_n) (AR5K_EEPROM_INFO_BASE + (_n)) #define AR5K_EEPROM_MODE_11A 0 #define AR5K_EEPROM_MODE_11B 1 #define AR5K_EEPROM_MODE_11G 2 #define AR5K_EEPROM_VERSION AR5K_EEPROM_INFO(1) #define AR5K_EEPROM_HDR AR5K_EEPROM_INFO(2) /* Header that contains the device caps */ #define AR5K_EEPROM_HDR_11A(_v) (((_v) >> AR5K_EEPROM_MODE_11A) & 0x1) /* Device has a support */ #define AR5K_EEPROM_HDR_11B(_v) (((_v) >> AR5K_EEPROM_MODE_11B) & 0x1) /* Device has b support */ #define AR5K_EEPROM_HDR_11G(_v) (((_v) >> AR5K_EEPROM_MODE_11G) & 0x1) /* Device has g support */ #define AR5K_EEPROM_HDR_T_2GHZ_DIS(_v) (((_v) >> 3) & 0x1) /* Disable turbo for 2Ghz (?) */ #define AR5K_EEPROM_HDR_T_5GHZ_DBM(_v) (((_v) >> 4) & 0x7f) /* Max turbo power for a/XR mode (eeprom_init) */ #define AR5K_EEPROM_HDR_DEVICE(_v) (((_v) >> 11) & 0x7) #define AR5K_EEPROM_HDR_T_5GHZ_DIS(_v) (((_v) >> 15) & 0x1) /* Disable turbo for 5Ghz (?) */ #define AR5K_EEPROM_HDR_RFKILL(_v) (((_v) >> 14) & 0x1) /* Device has RFKill support */ /* Misc values available since EEPROM 4.0 */ #define AR5K_EEPROM_MISC0 0x00c4 #define AR5K_EEPROM_EARSTART(_v) ((_v) & 0xfff) #define AR5K_EEPROM_EEMAP(_v) (((_v) >> 14) & 0x3) #define AR5K_EEPROM_MISC1 0x00c5 #define AR5K_EEPROM_TARGET_PWRSTART(_v) ((_v) & 0xfff) #define AR5K_EEPROM_HAS32KHZCRYSTAL(_v) (((_v) >> 14) & 0x1) /* * 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 /* 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}, }; static const int eeprom_addr_len = sizeof(eeprom_addr) / sizeof(eeprom_addr[0]); static int force_write = 0; static int verbose = 0; /* 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_int16_t mac_version, void *mem, 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_version == AR5K_SREV_VER_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(void *mem, u_int32_t offset, u_int16_t data, u_int8_t mac_version) { u_int32_t status, timeout; /* * Initialize EEPROM access */ if (mac_version == AR5K_SREV_VER_AR5210) { 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(void *mem, u_int32_t offset, u_int16_t *data, u_int8_t mac_version) { u_int32_t status, timeout; /* * Initialize EEPROM access */ if (mac_version == AR5K_SREV_VER_AR5210) { 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; } static const char *ath5k_hw_get_part_name(enum ath5k_srev_type type, u_int32_t val) { const char *name = "xxxxx"; int i; for (i = 0; i < AR5K_ELEMENTS(ath5k_srev_names); i++) { if (ath5k_srev_names[i].sr_type != type || ath5k_srev_names[i].sr_val == AR5K_SREV_UNKNOWN) continue; if ((val & 0xff) < ath5k_srev_names[i + 1].sr_val) { name = ath5k_srev_names[i].sr_name; break; } } return (name); } /* returns -1 on unknown name */ static int eeprom_name2addr(const char *name) { int i; if (!name || !name[0]) return -1; for (i = 0; i < eeprom_addr_len; i++) if (!strcmp(name, eeprom_addr[i].name)) return eeprom_addr[i].addr; return -1; } /* eeprom_name2addr */ /* returns "" on unknown address */ static const char *eeprom_addr2name(int addr) { int i; for (i = 0; i < eeprom_addr_len; i++) if (eeprom_addr[i].addr == addr) return eeprom_addr[i].name; return ""; } /* eeprom_addr2name */ static int do_write_pairs(int anr, int argc, char **argv, unsigned char *mem, int mac_version) { #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++; } /* while (anr < (argc-1)) */ 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 (mem, wr_ops[i].addr, &oldval, mac_version)) { 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 (mem, wr_ops[i].addr, wr_ops[i].val, mac_version)) { 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 (mem, wr_ops[i].addr, &u, mac_version)) { 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; } /* do_write_pairs */ static void usage(const char *n) { int i; fprintf(stderr, "%s [-w [-g N:M]] [-v] [-f] [-d] " "[ [ ...]]\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" " device base address (see lspci output)\n\n"); fprintf(stderr, "- read info:\n" " %s \n\n" "- set regdomain to N:\n" " %s -w regdomain N\n\n" "- set a PCI id field to value N:\n" " %s -w N\n" " where is on of:\n ", n, n, n); for (i = 0; i < eeprom_addr_len; 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 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"); } int main(int argc, char *argv[]) { u_int32_t dev_addr; u_int16_t eeprom_header, srev, phy_rev_5ghz, phy_rev_2ghz; u_int16_t eeprom_version, mac_version, regdomain, has_crystal, ee_magic; u_int8_t error, has_a, has_b, has_g, has_rfkill, eeprom_size; int byte_size = 0; void *mem; int fd; int i, anr = 1; int do_write = 0; /* default: read only */ int do_dump = 0; struct { int valid; int value; } gpio_set[AR5K_NUM_GPIO]; int nr_gpio_set = 0; for (i = 0; i < sizeof(gpio_set) / sizeof(gpio_set[0]); 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 '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 'h': usage(argv[0]); return 0; break; default: err("unknown option %s", argv[anr]); return 2; } /* switch (argv[anr][1]) */ anr++; } /* while (anr < argc && ...) */ if (anr >= argc) { err("missing device address"); usage(argv[0]); return 3; } dev_addr = strtoul(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%08X 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); srev = AR5K_REG_READ(AR5K_SREV); mac_version = AR5K_REG_MS(srev, AR5K_SREV_VER) << 4; /* Verify eeprom magic value first */ error = ath5k_hw_eeprom_read(mem, AR5K_EEPROM_MAGIC, &ee_magic, mac_version); 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"); } error = ath5k_hw_eeprom_read(mem, AR5K_EEPROM_HDR, &eeprom_header, mac_version); if (error) { printf("Unable to read EEPROM Header!\n"); return -1; } error = ath5k_hw_eeprom_read(mem, AR5K_EEPROM_VERSION, &eeprom_version, mac_version); if (error) { printf("Unable to read EEPROM version!\n"); return -1; } error = ath5k_hw_eeprom_read(mem, AR5K_EEPROM_REG_DOMAIN, ®domain, mac_version); if (error) { printf("Unable to read Regdomain!\n"); return -1; } if (eeprom_version >= 0x4000) { error = ath5k_hw_eeprom_read(mem, AR5K_EEPROM_MISC0, &has_crystal, mac_version); if (error) { printf("Unable to read EEPROM Misc data!\n"); return -1; } has_crystal = AR5K_EEPROM_HAS32KHZCRYSTAL(has_crystal); } else { has_crystal = 2; } eeprom_size = AR5K_REG_MS(AR5K_REG_READ(AR5K_PCICFG), AR5K_PCICFG_EESIZE); has_a = AR5K_EEPROM_HDR_11A(eeprom_header); has_b = AR5K_EEPROM_HDR_11B(eeprom_header); has_g = AR5K_EEPROM_HDR_11G(eeprom_header); has_rfkill = AR5K_EEPROM_HDR_RFKILL(eeprom_header); if (has_a) phy_rev_5ghz = ath5k_hw_radio_revision(mac_version, mem, 1); else phy_rev_5ghz = 0; if (has_b) phy_rev_2ghz = ath5k_hw_radio_revision(mac_version, mem, 0); else phy_rev_2ghz = 0; printf(" -==Device Information==-\n"); printf("MAC Version: %-5s (0x%02x)\n", ath5k_hw_get_part_name(AR5K_VERSION_VER, mac_version), mac_version); printf("MAC Revision: %-5s (0x%02x)\n", ath5k_hw_get_part_name(AR5K_VERSION_VER, srev), srev); /* Single-chip PHY with a/b/g support */ if (has_b && !phy_rev_2ghz) { printf("PHY Revision: %-5s (0x%02x)\n", ath5k_hw_get_part_name(AR5K_VERSION_RAD, phy_rev_5ghz), phy_rev_5ghz); phy_rev_5ghz = 0; } /* Single-chip PHY with b/g support */ if (!has_a) { printf("PHY Revision: %-5s (0x%02x)\n", ath5k_hw_get_part_name(AR5K_VERSION_RAD, phy_rev_2ghz), phy_rev_2ghz); phy_rev_2ghz = 0; } /* Different chip for 5Ghz and 2Ghz */ if (phy_rev_5ghz) { printf("5Ghz PHY Revision: %-5s (0x%2x)\n", ath5k_hw_get_part_name(AR5K_VERSION_RAD, phy_rev_5ghz), phy_rev_5ghz); } if (phy_rev_2ghz) { printf("2Ghz PHY Revision: %-5s (0x%2x)\n", ath5k_hw_get_part_name(AR5K_VERSION_RAD, phy_rev_2ghz), phy_rev_2ghz); } printf(" -==EEPROM Information==-\n"); printf("EEPROM Version: %x.%x\n", (eeprom_version & 0xF000) >> 12, eeprom_version & 0xFFF); printf("EEPROM Size: "); if (eeprom_size == 0) { printf(" 4K\n"); byte_size = 4096; } else if (eeprom_size == 1) { printf(" 8K\n"); byte_size = 8192; } else if (eeprom_size == 2) { printf(" 16K\n"); byte_size = 16384; } else printf(" ??\n"); printf("Regulatory Domain: 0x%X\n", regdomain); printf(" -==== Capabilities ====-\n"); printf("| 802.11a Support: "); if (has_a) printf("yes |\n"); else printf("no |\n"); printf("| 802.11b Support: "); if (has_b) printf("yes |\n"); else printf("no |\n"); printf("| 802.11g Support: "); if (has_g) printf("yes |\n"); else printf("no |\n"); printf("| RFKill Support: "); if (has_rfkill) printf("yes |\n"); else printf("no |\n"); if (has_crystal != 2) { printf("| 32KHz Crystal: "); if (has_crystal) printf("yes |\n"); else printf("no |\n"); } printf(" ========================\n"); /* print current GPIO settings */ printf("GPIO registers: CR %08x DO %08x DI %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 byte)\n", byte_size); printf("=============================================="); for (i = 1; i <= (byte_size / 2); i++) { error = ath5k_hw_eeprom_read(mem, i, &data, mac_version); if (error) { printf("\nUnable to read at %04x\n", i); continue; } if (!((i - 1) % 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_version >= AR5K_SREV_VER_AR5213 && !nr_gpio_set) { dbg("new MAC %x (>= AR5213) set gpio4 to low", mac_version); 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 < sizeof(gpio_set) / sizeof(gpio_set[0]); 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, mem, mac_version); /* 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; } return 0; }