/* * Copyright (c) 2010 Broadcom Corporation * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include <linux/kernel.h> #include <linux/string.h> #include <linux/etherdevice.h> #include <bcmdefs.h> #include <linux/module.h> #include <linux/pci.h> #include <stdarg.h> #include <bcmutils.h> #include <hndsoc.h> #include <sbchipc.h> #include <bcmdevs.h> #include <pcicfg.h> #include <siutils.h> #include <bcmsrom.h> #include <bcmsrom_tbl.h> #ifdef BCMSDIO #include <bcmsdh.h> #include <sdio.h> #endif #include <bcmnvram.h> #include <bcmotp.h> #if defined(BCMSDIO) #include <sbsdio.h> #include <sbhnddma.h> #include <sbsdpcmdev.h> #endif #include <linux/if_ether.h> #define BS_ERROR(args) #define SROM_OFFSET(sih) ((sih->ccrev > 31) ? \ (((sih->cccaps & CC_CAP_SROM) == 0) ? NULL : \ ((u8 *)curmap + PCI_16KB0_CCREGS_OFFSET + CC_SROM_OTP)) : \ ((u8 *)curmap + PCI_BAR0_SPROM_OFFSET)) #if defined(BCMDBG) #define WRITE_ENABLE_DELAY 500 /* 500 ms after write enable/disable toggle */ #define WRITE_WORD_DELAY 20 /* 20 ms between each word write */ #endif typedef struct varbuf { char *base; /* pointer to buffer base */ char *buf; /* pointer to current position */ unsigned int size; /* current (residual) size in bytes */ } varbuf_t; extern char *_vars; extern uint _varsz; #define SROM_CIS_SINGLE 1 static int initvars_srom_si(si_t *sih, void *curmap, char **vars, uint *count); static void _initvars_srom_pci(u8 sromrev, u16 *srom, uint off, varbuf_t *b); static int initvars_srom_pci(si_t *sih, void *curmap, char **vars, uint *count); static int initvars_flash_si(si_t *sih, char **vars, uint *count); #ifdef BCMSDIO static int initvars_cis_sdio(char **vars, uint *count); static int sprom_cmd_sdio(u8 cmd); static int sprom_read_sdio(u16 addr, u16 *data); #endif /* BCMSDIO */ static int sprom_read_pci(si_t *sih, u16 *sprom, uint wordoff, u16 *buf, uint nwords, bool check_crc); #if defined(BCMNVRAMR) static int otp_read_pci(si_t *sih, u16 *buf, uint bufsz); #endif static u16 srom_cc_cmd(si_t *sih, void *ccregs, u32 cmd, uint wordoff, u16 data); static int initvars_table(char *start, char *end, char **vars, uint *count); static int initvars_flash(si_t *sih, char **vp, uint len); /* Initialization of varbuf structure */ static void varbuf_init(varbuf_t *b, char *buf, uint size) { b->size = size; b->base = b->buf = buf; } /* append a null terminated var=value string */ static int varbuf_append(varbuf_t *b, const char *fmt, ...) { va_list ap; int r; size_t len; char *s; if (b->size < 2) return 0; va_start(ap, fmt); r = vsnprintf(b->buf, b->size, fmt, ap); va_end(ap); /* C99 snprintf behavior returns r >= size on overflow, * others return -1 on overflow. * All return -1 on format error. * We need to leave room for 2 null terminations, one for the current var * string, and one for final null of the var table. So check that the * strlen written, r, leaves room for 2 chars. */ if ((r == -1) || (r > (int)(b->size - 2))) { b->size = 0; return 0; } /* Remove any earlier occurrence of the same variable */ s = strchr(b->buf, '='); if (s != NULL) { len = (size_t) (s - b->buf); for (s = b->base; s < b->buf;) { if ((memcmp(s, b->buf, len) == 0) && s[len] == '=') { len = strlen(s) + 1; memmove(s, (s + len), ((b->buf + r + 1) - (s + len))); b->buf -= len; b->size += (unsigned int)len; break; } while (*s++) ; } } /* skip over this string's null termination */ r++; b->size -= r; b->buf += r; return r; } /* * Initialize local vars from the right source for this platform. * Return 0 on success, nonzero on error. */ int srom_var_init(si_t *sih, uint bustype, void *curmap, char **vars, uint *count) { uint len; len = 0; ASSERT(bustype == bustype); if (vars == NULL || count == NULL) return 0; *vars = NULL; *count = 0; switch (bustype) { case SI_BUS: case JTAG_BUS: return initvars_srom_si(sih, curmap, vars, count); case PCI_BUS: ASSERT(curmap != NULL); if (curmap == NULL) return -1; return initvars_srom_pci(sih, curmap, vars, count); #ifdef BCMSDIO case SDIO_BUS: return initvars_cis_sdio(vars, count); #endif /* BCMSDIO */ default: ASSERT(0); } return -1; } /* support only 16-bit word read from srom */ int srom_read(si_t *sih, uint bustype, void *curmap, uint byteoff, uint nbytes, u16 *buf, bool check_crc) { uint off, nw; #ifdef BCMSDIO uint i; #endif /* BCMSDIO */ ASSERT(bustype == bustype); /* check input - 16-bit access only */ if (byteoff & 1 || nbytes & 1 || (byteoff + nbytes) > SROM_MAX) return 1; off = byteoff / 2; nw = nbytes / 2; if (bustype == PCI_BUS) { if (!curmap) return 1; if (si_is_sprom_available(sih)) { u16 *srom; srom = (u16 *) SROM_OFFSET(sih); if (srom == NULL) return 1; if (sprom_read_pci (sih, srom, off, buf, nw, check_crc)) return 1; } #if defined(BCMNVRAMR) else { if (otp_read_pci(sih, buf, SROM_MAX)) return 1; } #endif #ifdef BCMSDIO } else if (bustype == SDIO_BUS) { off = byteoff / 2; nw = nbytes / 2; for (i = 0; i < nw; i++) { if (sprom_read_sdio ((u16) (off + i), (u16 *) (buf + i))) return 1; } #endif /* BCMSDIO */ } else if (bustype == SI_BUS) { return 1; } else { return 1; } return 0; } static const char vstr_manf[] = "manf=%s"; static const char vstr_productname[] = "productname=%s"; static const char vstr_manfid[] = "manfid=0x%x"; static const char vstr_prodid[] = "prodid=0x%x"; #ifdef BCMSDIO static const char vstr_sdmaxspeed[] = "sdmaxspeed=%d"; static const char vstr_sdmaxblk[][13] = { "sdmaxblk0=%d", "sdmaxblk1=%d", "sdmaxblk2=%d"}; #endif static const char vstr_regwindowsz[] = "regwindowsz=%d"; static const char vstr_sromrev[] = "sromrev=%d"; static const char vstr_chiprev[] = "chiprev=%d"; static const char vstr_subvendid[] = "subvendid=0x%x"; static const char vstr_subdevid[] = "subdevid=0x%x"; static const char vstr_boardrev[] = "boardrev=0x%x"; static const char vstr_aa2g[] = "aa2g=0x%x"; static const char vstr_aa5g[] = "aa5g=0x%x"; static const char vstr_ag[] = "ag%d=0x%x"; static const char vstr_cc[] = "cc=%d"; static const char vstr_opo[] = "opo=%d"; static const char vstr_pa0b[][9] = { "pa0b0=%d", "pa0b1=%d", "pa0b2=%d"}; static const char vstr_pa0itssit[] = "pa0itssit=%d"; static const char vstr_pa0maxpwr[] = "pa0maxpwr=%d"; static const char vstr_pa1b[][9] = { "pa1b0=%d", "pa1b1=%d", "pa1b2=%d"}; static const char vstr_pa1lob[][11] = { "pa1lob0=%d", "pa1lob1=%d", "pa1lob2=%d"}; static const char vstr_pa1hib[][11] = { "pa1hib0=%d", "pa1hib1=%d", "pa1hib2=%d"}; static const char vstr_pa1itssit[] = "pa1itssit=%d"; static const char vstr_pa1maxpwr[] = "pa1maxpwr=%d"; static const char vstr_pa1lomaxpwr[] = "pa1lomaxpwr=%d"; static const char vstr_pa1himaxpwr[] = "pa1himaxpwr=%d"; static const char vstr_oem[] = "oem=%02x%02x%02x%02x%02x%02x%02x%02x"; static const char vstr_boardflags[] = "boardflags=0x%x"; static const char vstr_boardflags2[] = "boardflags2=0x%x"; static const char vstr_ledbh[] = "ledbh%d=0x%x"; static const char vstr_noccode[] = "ccode=0x0"; static const char vstr_ccode[] = "ccode=%c%c"; static const char vstr_cctl[] = "cctl=0x%x"; static const char vstr_cckpo[] = "cckpo=0x%x"; static const char vstr_ofdmpo[] = "ofdmpo=0x%x"; static const char vstr_rdlid[] = "rdlid=0x%x"; static const char vstr_rdlrndis[] = "rdlrndis=%d"; static const char vstr_rdlrwu[] = "rdlrwu=%d"; static const char vstr_usbfs[] = "usbfs=%d"; static const char vstr_wpsgpio[] = "wpsgpio=%d"; static const char vstr_wpsled[] = "wpsled=%d"; static const char vstr_rdlsn[] = "rdlsn=%d"; static const char vstr_rssismf2g[] = "rssismf2g=%d"; static const char vstr_rssismc2g[] = "rssismc2g=%d"; static const char vstr_rssisav2g[] = "rssisav2g=%d"; static const char vstr_bxa2g[] = "bxa2g=%d"; static const char vstr_rssismf5g[] = "rssismf5g=%d"; static const char vstr_rssismc5g[] = "rssismc5g=%d"; static const char vstr_rssisav5g[] = "rssisav5g=%d"; static const char vstr_bxa5g[] = "bxa5g=%d"; static const char vstr_tri2g[] = "tri2g=%d"; static const char vstr_tri5gl[] = "tri5gl=%d"; static const char vstr_tri5g[] = "tri5g=%d"; static const char vstr_tri5gh[] = "tri5gh=%d"; static const char vstr_rxpo2g[] = "rxpo2g=%d"; static const char vstr_rxpo5g[] = "rxpo5g=%d"; static const char vstr_boardtype[] = "boardtype=0x%x"; static const char vstr_leddc[] = "leddc=0x%04x"; static const char vstr_vendid[] = "vendid=0x%x"; static const char vstr_devid[] = "devid=0x%x"; static const char vstr_xtalfreq[] = "xtalfreq=%d"; static const char vstr_txchain[] = "txchain=0x%x"; static const char vstr_rxchain[] = "rxchain=0x%x"; static const char vstr_antswitch[] = "antswitch=0x%x"; static const char vstr_regrev[] = "regrev=0x%x"; static const char vstr_antswctl2g[] = "antswctl2g=0x%x"; static const char vstr_triso2g[] = "triso2g=0x%x"; static const char vstr_pdetrange2g[] = "pdetrange2g=0x%x"; static const char vstr_extpagain2g[] = "extpagain2g=0x%x"; static const char vstr_tssipos2g[] = "tssipos2g=0x%x"; static const char vstr_antswctl5g[] = "antswctl5g=0x%x"; static const char vstr_triso5g[] = "triso5g=0x%x"; static const char vstr_pdetrange5g[] = "pdetrange5g=0x%x"; static const char vstr_extpagain5g[] = "extpagain5g=0x%x"; static const char vstr_tssipos5g[] = "tssipos5g=0x%x"; static const char vstr_maxp2ga0[] = "maxp2ga0=0x%x"; static const char vstr_itt2ga0[] = "itt2ga0=0x%x"; static const char vstr_pa[] = "pa%dgw%da%d=0x%x"; static const char vstr_pahl[] = "pa%dg%cw%da%d=0x%x"; static const char vstr_maxp5ga0[] = "maxp5ga0=0x%x"; static const char vstr_itt5ga0[] = "itt5ga0=0x%x"; static const char vstr_maxp5gha0[] = "maxp5gha0=0x%x"; static const char vstr_maxp5gla0[] = "maxp5gla0=0x%x"; static const char vstr_maxp2ga1[] = "maxp2ga1=0x%x"; static const char vstr_itt2ga1[] = "itt2ga1=0x%x"; static const char vstr_maxp5ga1[] = "maxp5ga1=0x%x"; static const char vstr_itt5ga1[] = "itt5ga1=0x%x"; static const char vstr_maxp5gha1[] = "maxp5gha1=0x%x"; static const char vstr_maxp5gla1[] = "maxp5gla1=0x%x"; static const char vstr_cck2gpo[] = "cck2gpo=0x%x"; static const char vstr_ofdm2gpo[] = "ofdm2gpo=0x%x"; static const char vstr_ofdm5gpo[] = "ofdm5gpo=0x%x"; static const char vstr_ofdm5glpo[] = "ofdm5glpo=0x%x"; static const char vstr_ofdm5ghpo[] = "ofdm5ghpo=0x%x"; static const char vstr_cddpo[] = "cddpo=0x%x"; static const char vstr_stbcpo[] = "stbcpo=0x%x"; static const char vstr_bw40po[] = "bw40po=0x%x"; static const char vstr_bwduppo[] = "bwduppo=0x%x"; static const char vstr_mcspo[] = "mcs%dgpo%d=0x%x"; static const char vstr_mcspohl[] = "mcs%dg%cpo%d=0x%x"; static const char vstr_custom[] = "customvar%d=0x%x"; static const char vstr_cckdigfilttype[] = "cckdigfilttype=%d"; static const char vstr_boardnum[] = "boardnum=%d"; static const char vstr_macaddr[] = "macaddr=%s"; static const char vstr_usbepnum[] = "usbepnum=0x%x"; static const char vstr_end[] = "END\0"; u8 patch_pair; /* For dongle HW, accept partial calibration parameters */ #define BCMDONGLECASE(n) int srom_parsecis(u8 *pcis[], uint ciscnt, char **vars, uint *count) { char eabuf[32]; char *base; varbuf_t b; u8 *cis, tup, tlen, sromrev = 1; int i, j; bool ag_init = false; u32 w32; uint funcid; uint cisnum; s32 boardnum; int err; bool standard_cis; ASSERT(vars != NULL); ASSERT(count != NULL); boardnum = -1; base = kmalloc(MAXSZ_NVRAM_VARS, GFP_ATOMIC); ASSERT(base != NULL); if (!base) return -2; varbuf_init(&b, base, MAXSZ_NVRAM_VARS); memset(base, 0, MAXSZ_NVRAM_VARS); eabuf[0] = '\0'; for (cisnum = 0; cisnum < ciscnt; cisnum++) { cis = *pcis++; i = 0; funcid = 0; standard_cis = true; do { if (standard_cis) { tup = cis[i++]; if (tup == CISTPL_NULL || tup == CISTPL_END) tlen = 0; else tlen = cis[i++]; } else { if (cis[i] == CISTPL_NULL || cis[i] == CISTPL_END) { tlen = 0; tup = cis[i]; } else { tlen = cis[i]; tup = CISTPL_BRCM_HNBU; } ++i; } if ((i + tlen) >= CIS_SIZE) break; switch (tup) { case CISTPL_VERS_1: /* assume the strings are good if the version field checks out */ if (((cis[i + 1] << 8) + cis[i]) >= 0x0008) { varbuf_append(&b, vstr_manf, &cis[i + 2]); varbuf_append(&b, vstr_productname, &cis[i + 3 + strlen((char *) &cis[i + 2])]); break; } case CISTPL_MANFID: varbuf_append(&b, vstr_manfid, (cis[i + 1] << 8) + cis[i]); varbuf_append(&b, vstr_prodid, (cis[i + 3] << 8) + cis[i + 2]); break; case CISTPL_FUNCID: funcid = cis[i]; break; case CISTPL_FUNCE: switch (funcid) { case CISTPL_FID_SDIO: #ifdef BCMSDIO if (cis[i] == 0) { u8 spd = cis[i + 3]; static int base[] = { -1, 10, 12, 13, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 70, 80 }; static int mult[] = { 10, 100, 1000, 10000, -1, -1, -1, -1 }; ASSERT((mult[spd & 0x7] != -1) && (base [(spd >> 3) & 0x0f])); varbuf_append(&b, vstr_sdmaxblk[0], (cis[i + 2] << 8) + cis[i + 1]); varbuf_append(&b, vstr_sdmaxspeed, (mult[spd & 0x7] * base[(spd >> 3) & 0x0f])); } else if (cis[i] == 1) { varbuf_append(&b, vstr_sdmaxblk [cisnum], (cis[i + 13] << 8) | cis[i + 12]); } #endif /* BCMSDIO */ funcid = 0; break; default: /* set macaddr if HNBU_MACADDR not seen yet */ if (eabuf[0] == '\0' && cis[i] == LAN_NID && !is_zero_ether_addr(&cis[i + 2]) && !is_multicast_ether_addr(&cis[i + 2])) { ASSERT(cis[i + 1] == ETH_ALEN); snprintf(eabuf, sizeof(eabuf), "%pM", &cis[i + 2]); /* set boardnum if HNBU_BOARDNUM not seen yet */ if (boardnum == -1) boardnum = (cis[i + 6] << 8) + cis[i + 7]; } break; } break; case CISTPL_CFTABLE: varbuf_append(&b, vstr_regwindowsz, (cis[i + 7] << 8) | cis[i + 6]); break; case CISTPL_BRCM_HNBU: switch (cis[i]) { case HNBU_SROMREV: sromrev = cis[i + 1]; varbuf_append(&b, vstr_sromrev, sromrev); break; case HNBU_XTALFREQ: varbuf_append(&b, vstr_xtalfreq, (cis[i + 4] << 24) | (cis[i + 3] << 16) | (cis[i + 2] << 8) | cis[i + 1]); break; case HNBU_CHIPID: varbuf_append(&b, vstr_vendid, (cis[i + 2] << 8) + cis[i + 1]); varbuf_append(&b, vstr_devid, (cis[i + 4] << 8) + cis[i + 3]); if (tlen >= 7) { varbuf_append(&b, vstr_chiprev, (cis[i + 6] << 8) + cis[i + 5]); } if (tlen >= 9) { varbuf_append(&b, vstr_subvendid, (cis[i + 8] << 8) + cis[i + 7]); } if (tlen >= 11) { varbuf_append(&b, vstr_subdevid, (cis[i + 10] << 8) + cis[i + 9]); /* subdevid doubles for boardtype */ varbuf_append(&b, vstr_boardtype, (cis[i + 10] << 8) + cis[i + 9]); } break; case HNBU_BOARDNUM: boardnum = (cis[i + 2] << 8) + cis[i + 1]; break; case HNBU_PATCH: { char vstr_paddr[16]; char vstr_pdata[16]; /* retrieve the patch pairs * from tlen/6; where 6 is * sizeof(patch addr(2)) + * sizeof(patch data(4)). */ patch_pair = tlen / 6; for (j = 0; j < patch_pair; j++) { snprintf(vstr_paddr, sizeof (vstr_paddr), "pa%d=0x%%x", j); snprintf(vstr_pdata, sizeof (vstr_pdata), "pd%d=0x%%x", j); varbuf_append(&b, vstr_paddr, (cis [i + (j * 6) + 2] << 8) | cis[i + (j * 6) + 1]); varbuf_append(&b, vstr_pdata, (cis [i + (j * 6) + 6] << 24) | (cis [i + (j * 6) + 5] << 16) | (cis [i + (j * 6) + 4] << 8) | cis[i + (j * 6) + 3]); } } break; case HNBU_BOARDREV: if (tlen == 2) varbuf_append(&b, vstr_boardrev, cis[i + 1]); else varbuf_append(&b, vstr_boardrev, (cis[i + 2] << 8) + cis[i + 1]); break; case HNBU_BOARDFLAGS: w32 = (cis[i + 2] << 8) + cis[i + 1]; if (tlen >= 5) w32 |= ((cis[i + 4] << 24) + (cis[i + 3] << 16)); varbuf_append(&b, vstr_boardflags, w32); if (tlen >= 7) { w32 = (cis[i + 6] << 8) + cis[i + 5]; if (tlen >= 9) w32 |= ((cis[i + 8] << 24) + (cis[i + 7] << 16)); varbuf_append(&b, vstr_boardflags2, w32); } break; case HNBU_USBFS: varbuf_append(&b, vstr_usbfs, cis[i + 1]); break; case HNBU_BOARDTYPE: varbuf_append(&b, vstr_boardtype, (cis[i + 2] << 8) + cis[i + 1]); break; case HNBU_HNBUCIS: /* * what follows is a nonstandard HNBU CIS * that lacks CISTPL_BRCM_HNBU tags * * skip 0xff (end of standard CIS) * after this tuple */ tlen++; standard_cis = false; break; case HNBU_USBEPNUM: varbuf_append(&b, vstr_usbepnum, (cis[i + 2] << 8) | cis[i + 1]); break; case HNBU_AA: varbuf_append(&b, vstr_aa2g, cis[i + 1]); if (tlen >= 3) varbuf_append(&b, vstr_aa5g, cis[i + 2]); break; case HNBU_AG: varbuf_append(&b, vstr_ag, 0, cis[i + 1]); if (tlen >= 3) varbuf_append(&b, vstr_ag, 1, cis[i + 2]); if (tlen >= 4) varbuf_append(&b, vstr_ag, 2, cis[i + 3]); if (tlen >= 5) varbuf_append(&b, vstr_ag, 3, cis[i + 4]); ag_init = true; break; case HNBU_ANT5G: varbuf_append(&b, vstr_aa5g, cis[i + 1]); varbuf_append(&b, vstr_ag, 1, cis[i + 2]); break; case HNBU_CC: ASSERT(sromrev == 1); varbuf_append(&b, vstr_cc, cis[i + 1]); break; case HNBU_PAPARMS: switch (tlen) { case 2: ASSERT(sromrev == 1); varbuf_append(&b, vstr_pa0maxpwr, cis[i + 1]); break; case 10: ASSERT(sromrev >= 2); varbuf_append(&b, vstr_opo, cis[i + 9]); /* FALLTHROUGH */ case 9: varbuf_append(&b, vstr_pa0maxpwr, cis[i + 8]); /* FALLTHROUGH */ BCMDONGLECASE(8) varbuf_append(&b, vstr_pa0itssit, cis[i + 7]); /* FALLTHROUGH */ BCMDONGLECASE(7) for (j = 0; j < 3; j++) { varbuf_append(&b, vstr_pa0b [j], (cis [i + (j * 2) + 2] << 8) + cis[i + (j * 2) + 1]); } break; default: ASSERT((tlen == 2) || (tlen == 9) || (tlen == 10)); break; } break; case HNBU_PAPARMS5G: ASSERT((sromrev == 2) || (sromrev == 3)); switch (tlen) { case 23: varbuf_append(&b, vstr_pa1himaxpwr, cis[i + 22]); varbuf_append(&b, vstr_pa1lomaxpwr, cis[i + 21]); varbuf_append(&b, vstr_pa1maxpwr, cis[i + 20]); /* FALLTHROUGH */ case 20: varbuf_append(&b, vstr_pa1itssit, cis[i + 19]); /* FALLTHROUGH */ case 19: for (j = 0; j < 3; j++) { varbuf_append(&b, vstr_pa1b [j], (cis [i + (j * 2) + 2] << 8) + cis[i + (j * 2) + 1]); } for (j = 3; j < 6; j++) { varbuf_append(&b, vstr_pa1lob [j - 3], (cis [i + (j * 2) + 2] << 8) + cis[i + (j * 2) + 1]); } for (j = 6; j < 9; j++) { varbuf_append(&b, vstr_pa1hib [j - 6], (cis [i + (j * 2) + 2] << 8) + cis[i + (j * 2) + 1]); } break; default: ASSERT((tlen == 19) || (tlen == 20) || (tlen == 23)); break; } break; case HNBU_OEM: ASSERT(sromrev == 1); varbuf_append(&b, vstr_oem, cis[i + 1], cis[i + 2], cis[i + 3], cis[i + 4], cis[i + 5], cis[i + 6], cis[i + 7], cis[i + 8]); break; case HNBU_LEDS: for (j = 1; j <= 4; j++) { if (cis[i + j] != 0xff) { varbuf_append(&b, vstr_ledbh, j - 1, cis[i + j]); } } break; case HNBU_CCODE: ASSERT(sromrev > 1); if ((cis[i + 1] == 0) || (cis[i + 2] == 0)) varbuf_append(&b, vstr_noccode); else varbuf_append(&b, vstr_ccode, cis[i + 1], cis[i + 2]); varbuf_append(&b, vstr_cctl, cis[i + 3]); break; case HNBU_CCKPO: ASSERT(sromrev > 2); varbuf_append(&b, vstr_cckpo, (cis[i + 2] << 8) | cis[i + 1]); break; case HNBU_OFDMPO: ASSERT(sromrev > 2); varbuf_append(&b, vstr_ofdmpo, (cis[i + 4] << 24) | (cis[i + 3] << 16) | (cis[i + 2] << 8) | cis[i + 1]); break; case HNBU_WPS: varbuf_append(&b, vstr_wpsgpio, cis[i + 1]); if (tlen >= 3) varbuf_append(&b, vstr_wpsled, cis[i + 2]); break; case HNBU_RSSISMBXA2G: ASSERT(sromrev == 3); varbuf_append(&b, vstr_rssismf2g, cis[i + 1] & 0xf); varbuf_append(&b, vstr_rssismc2g, (cis[i + 1] >> 4) & 0xf); varbuf_append(&b, vstr_rssisav2g, cis[i + 2] & 0x7); varbuf_append(&b, vstr_bxa2g, (cis[i + 2] >> 3) & 0x3); break; case HNBU_RSSISMBXA5G: ASSERT(sromrev == 3); varbuf_append(&b, vstr_rssismf5g, cis[i + 1] & 0xf); varbuf_append(&b, vstr_rssismc5g, (cis[i + 1] >> 4) & 0xf); varbuf_append(&b, vstr_rssisav5g, cis[i + 2] & 0x7); varbuf_append(&b, vstr_bxa5g, (cis[i + 2] >> 3) & 0x3); break; case HNBU_TRI2G: ASSERT(sromrev == 3); varbuf_append(&b, vstr_tri2g, cis[i + 1]); break; case HNBU_TRI5G: ASSERT(sromrev == 3); varbuf_append(&b, vstr_tri5gl, cis[i + 1]); varbuf_append(&b, vstr_tri5g, cis[i + 2]); varbuf_append(&b, vstr_tri5gh, cis[i + 3]); break; case HNBU_RXPO2G: ASSERT(sromrev == 3); varbuf_append(&b, vstr_rxpo2g, cis[i + 1]); break; case HNBU_RXPO5G: ASSERT(sromrev == 3); varbuf_append(&b, vstr_rxpo5g, cis[i + 1]); break; case HNBU_MACADDR: if (!is_zero_ether_addr(&cis[i + 1]) && !is_multicast_ether_addr(&cis[i + 1])) { snprintf(eabuf, sizeof(eabuf), "%pM", &cis[i + 1]); /* set boardnum if HNBU_BOARDNUM not seen yet */ if (boardnum == -1) boardnum = (cis[i + 5] << 8) + cis[i + 6]; } break; case HNBU_LEDDC: /* CIS leddc only has 16bits, convert it to 32bits */ w32 = ((cis[i + 2] << 24) | /* oncount */ (cis[i + 1] << 8)); /* offcount */ varbuf_append(&b, vstr_leddc, w32); break; case HNBU_CHAINSWITCH: varbuf_append(&b, vstr_txchain, cis[i + 1]); varbuf_append(&b, vstr_rxchain, cis[i + 2]); varbuf_append(&b, vstr_antswitch, (cis[i + 4] << 8) + cis[i + 3]); break; case HNBU_REGREV: varbuf_append(&b, vstr_regrev, cis[i + 1]); break; case HNBU_FEM:{ u16 fem = (cis[i + 2] << 8) + cis[i + 1]; varbuf_append(&b, vstr_antswctl2g, (fem & SROM8_FEM_ANTSWLUT_MASK) >> SROM8_FEM_ANTSWLUT_SHIFT); varbuf_append(&b, vstr_triso2g, (fem & SROM8_FEM_TR_ISO_MASK) >> SROM8_FEM_TR_ISO_SHIFT); varbuf_append(&b, vstr_pdetrange2g, (fem & SROM8_FEM_PDET_RANGE_MASK) >> SROM8_FEM_PDET_RANGE_SHIFT); varbuf_append(&b, vstr_extpagain2g, (fem & SROM8_FEM_EXTPA_GAIN_MASK) >> SROM8_FEM_EXTPA_GAIN_SHIFT); varbuf_append(&b, vstr_tssipos2g, (fem & SROM8_FEM_TSSIPOS_MASK) >> SROM8_FEM_TSSIPOS_SHIFT); if (tlen < 5) break; fem = (cis[i + 4] << 8) + cis[i + 3]; varbuf_append(&b, vstr_antswctl5g, (fem & SROM8_FEM_ANTSWLUT_MASK) >> SROM8_FEM_ANTSWLUT_SHIFT); varbuf_append(&b, vstr_triso5g, (fem & SROM8_FEM_TR_ISO_MASK) >> SROM8_FEM_TR_ISO_SHIFT); varbuf_append(&b, vstr_pdetrange5g, (fem & SROM8_FEM_PDET_RANGE_MASK) >> SROM8_FEM_PDET_RANGE_SHIFT); varbuf_append(&b, vstr_extpagain5g, (fem & SROM8_FEM_EXTPA_GAIN_MASK) >> SROM8_FEM_EXTPA_GAIN_SHIFT); varbuf_append(&b, vstr_tssipos5g, (fem & SROM8_FEM_TSSIPOS_MASK) >> SROM8_FEM_TSSIPOS_SHIFT); break; } case HNBU_PAPARMS_C0: varbuf_append(&b, vstr_maxp2ga0, cis[i + 1]); varbuf_append(&b, vstr_itt2ga0, cis[i + 2]); varbuf_append(&b, vstr_pa, 2, 0, 0, (cis[i + 4] << 8) + cis[i + 3]); varbuf_append(&b, vstr_pa, 2, 1, 0, (cis[i + 6] << 8) + cis[i + 5]); varbuf_append(&b, vstr_pa, 2, 2, 0, (cis[i + 8] << 8) + cis[i + 7]); if (tlen < 31) break; varbuf_append(&b, vstr_maxp5ga0, cis[i + 9]); varbuf_append(&b, vstr_itt5ga0, cis[i + 10]); varbuf_append(&b, vstr_maxp5gha0, cis[i + 11]); varbuf_append(&b, vstr_maxp5gla0, cis[i + 12]); varbuf_append(&b, vstr_pa, 5, 0, 0, (cis[i + 14] << 8) + cis[i + 13]); varbuf_append(&b, vstr_pa, 5, 1, 0, (cis[i + 16] << 8) + cis[i + 15]); varbuf_append(&b, vstr_pa, 5, 2, 0, (cis[i + 18] << 8) + cis[i + 17]); varbuf_append(&b, vstr_pahl, 5, 'l', 0, 0, (cis[i + 20] << 8) + cis[i + 19]); varbuf_append(&b, vstr_pahl, 5, 'l', 1, 0, (cis[i + 22] << 8) + cis[i + 21]); varbuf_append(&b, vstr_pahl, 5, 'l', 2, 0, (cis[i + 24] << 8) + cis[i + 23]); varbuf_append(&b, vstr_pahl, 5, 'h', 0, 0, (cis[i + 26] << 8) + cis[i + 25]); varbuf_append(&b, vstr_pahl, 5, 'h', 1, 0, (cis[i + 28] << 8) + cis[i + 27]); varbuf_append(&b, vstr_pahl, 5, 'h', 2, 0, (cis[i + 30] << 8) + cis[i + 29]); break; case HNBU_PAPARMS_C1: varbuf_append(&b, vstr_maxp2ga1, cis[i + 1]); varbuf_append(&b, vstr_itt2ga1, cis[i + 2]); varbuf_append(&b, vstr_pa, 2, 0, 1, (cis[i + 4] << 8) + cis[i + 3]); varbuf_append(&b, vstr_pa, 2, 1, 1, (cis[i + 6] << 8) + cis[i + 5]); varbuf_append(&b, vstr_pa, 2, 2, 1, (cis[i + 8] << 8) + cis[i + 7]); if (tlen < 31) break; varbuf_append(&b, vstr_maxp5ga1, cis[i + 9]); varbuf_append(&b, vstr_itt5ga1, cis[i + 10]); varbuf_append(&b, vstr_maxp5gha1, cis[i + 11]); varbuf_append(&b, vstr_maxp5gla1, cis[i + 12]); varbuf_append(&b, vstr_pa, 5, 0, 1, (cis[i + 14] << 8) + cis[i + 13]); varbuf_append(&b, vstr_pa, 5, 1, 1, (cis[i + 16] << 8) + cis[i + 15]); varbuf_append(&b, vstr_pa, 5, 2, 1, (cis[i + 18] << 8) + cis[i + 17]); varbuf_append(&b, vstr_pahl, 5, 'l', 0, 1, (cis[i + 20] << 8) + cis[i + 19]); varbuf_append(&b, vstr_pahl, 5, 'l', 1, 1, (cis[i + 22] << 8) + cis[i + 21]); varbuf_append(&b, vstr_pahl, 5, 'l', 2, 1, (cis[i + 24] << 8) + cis[i + 23]); varbuf_append(&b, vstr_pahl, 5, 'h', 0, 1, (cis[i + 26] << 8) + cis[i + 25]); varbuf_append(&b, vstr_pahl, 5, 'h', 1, 1, (cis[i + 28] << 8) + cis[i + 27]); varbuf_append(&b, vstr_pahl, 5, 'h', 2, 1, (cis[i + 30] << 8) + cis[i + 29]); break; case HNBU_PO_CCKOFDM: varbuf_append(&b, vstr_cck2gpo, (cis[i + 2] << 8) + cis[i + 1]); varbuf_append(&b, vstr_ofdm2gpo, (cis[i + 6] << 24) + (cis[i + 5] << 16) + (cis[i + 4] << 8) + cis[i + 3]); if (tlen < 19) break; varbuf_append(&b, vstr_ofdm5gpo, (cis[i + 10] << 24) + (cis[i + 9] << 16) + (cis[i + 8] << 8) + cis[i + 7]); varbuf_append(&b, vstr_ofdm5glpo, (cis[i + 14] << 24) + (cis[i + 13] << 16) + (cis[i + 12] << 8) + cis[i + 11]); varbuf_append(&b, vstr_ofdm5ghpo, (cis[i + 18] << 24) + (cis[i + 17] << 16) + (cis[i + 16] << 8) + cis[i + 15]); break; case HNBU_PO_MCS2G: for (j = 0; j <= (tlen / 2); j++) { varbuf_append(&b, vstr_mcspo, 2, j, (cis [i + 2 + 2 * j] << 8) + cis[i + 1 + 2 * j]); } break; case HNBU_PO_MCS5GM: for (j = 0; j <= (tlen / 2); j++) { varbuf_append(&b, vstr_mcspo, 5, j, (cis [i + 2 + 2 * j] << 8) + cis[i + 1 + 2 * j]); } break; case HNBU_PO_MCS5GLH: for (j = 0; j <= (tlen / 4); j++) { varbuf_append(&b, vstr_mcspohl, 5, 'l', j, (cis [i + 2 + 2 * j] << 8) + cis[i + 1 + 2 * j]); } for (j = 0; j <= (tlen / 4); j++) { varbuf_append(&b, vstr_mcspohl, 5, 'h', j, (cis [i + ((tlen / 2) + 2) + 2 * j] << 8) + cis[i + ((tlen / 2) + 1) + 2 * j]); } break; case HNBU_PO_CDD: varbuf_append(&b, vstr_cddpo, (cis[i + 2] << 8) + cis[i + 1]); break; case HNBU_PO_STBC: varbuf_append(&b, vstr_stbcpo, (cis[i + 2] << 8) + cis[i + 1]); break; case HNBU_PO_40M: varbuf_append(&b, vstr_bw40po, (cis[i + 2] << 8) + cis[i + 1]); break; case HNBU_PO_40MDUP: varbuf_append(&b, vstr_bwduppo, (cis[i + 2] << 8) + cis[i + 1]); break; case HNBU_OFDMPO5G: varbuf_append(&b, vstr_ofdm5gpo, (cis[i + 4] << 24) + (cis[i + 3] << 16) + (cis[i + 2] << 8) + cis[i + 1]); varbuf_append(&b, vstr_ofdm5glpo, (cis[i + 8] << 24) + (cis[i + 7] << 16) + (cis[i + 6] << 8) + cis[i + 5]); varbuf_append(&b, vstr_ofdm5ghpo, (cis[i + 12] << 24) + (cis[i + 11] << 16) + (cis[i + 10] << 8) + cis[i + 9]); break; case HNBU_CUSTOM1: varbuf_append(&b, vstr_custom, 1, ((cis[i + 4] << 24) + (cis[i + 3] << 16) + (cis[i + 2] << 8) + cis[i + 1])); break; #if defined(BCMSDIO) case HNBU_SROM3SWRGN: if (tlen >= 73) { u16 srom[35]; u8 srev = cis[i + 1 + 70]; ASSERT(srev == 3); /* make tuple value 16-bit aligned and parse it */ memcpy(srom, &cis[i + 1], sizeof(srom)); _initvars_srom_pci(srev, srom, SROM3_SWRGN_OFF, &b); /* 2.4G antenna gain is included in SROM */ ag_init = true; /* Ethernet MAC address is included in SROM */ eabuf[0] = 0; boardnum = -1; } /* create extra variables */ if (tlen >= 75) varbuf_append(&b, vstr_vendid, (cis[i + 1 + 73] << 8) + cis[i + 1 + 72]); if (tlen >= 77) varbuf_append(&b, vstr_devid, (cis[i + 1 + 75] << 8) + cis[i + 1 + 74]); if (tlen >= 79) varbuf_append(&b, vstr_xtalfreq, (cis[i + 1 + 77] << 8) + cis[i + 1 + 76]); break; #endif /* defined(BCMSDIO) */ case HNBU_CCKFILTTYPE: varbuf_append(&b, vstr_cckdigfilttype, (cis[i + 1])); break; } break; } i += tlen; } while (tup != CISTPL_END); } if (boardnum != -1) { varbuf_append(&b, vstr_boardnum, boardnum); } if (eabuf[0]) { varbuf_append(&b, vstr_macaddr, eabuf); } /* if there is no antenna gain field, set default */ if (getvar(NULL, "ag0") == NULL && ag_init == false) { varbuf_append(&b, vstr_ag, 0, 0xff); } /* final nullbyte terminator */ ASSERT(b.size >= 1); *b.buf++ = '\0'; ASSERT(b.buf - base <= MAXSZ_NVRAM_VARS); err = initvars_table(base, b.buf, vars, count); kfree(base); return err; } /* In chips with chipcommon rev 32 and later, the srom is in chipcommon, * not in the bus cores. */ static u16 srom_cc_cmd(si_t *sih, void *ccregs, u32 cmd, uint wordoff, u16 data) { chipcregs_t *cc = (chipcregs_t *) ccregs; uint wait_cnt = 1000; if ((cmd == SRC_OP_READ) || (cmd == SRC_OP_WRITE)) { W_REG(&cc->sromaddress, wordoff * 2); if (cmd == SRC_OP_WRITE) W_REG(&cc->sromdata, data); } W_REG(&cc->sromcontrol, SRC_START | cmd); while (wait_cnt--) { if ((R_REG(&cc->sromcontrol) & SRC_BUSY) == 0) break; } if (!wait_cnt) { BS_ERROR(("%s: Command 0x%x timed out\n", __func__, cmd)); return 0xffff; } if (cmd == SRC_OP_READ) return (u16) R_REG(&cc->sromdata); else return 0xffff; } static inline void ltoh16_buf(u16 *buf, unsigned int size) { for (size /= 2; size; size--) *(buf + size) = le16_to_cpu(*(buf + size)); } static inline void htol16_buf(u16 *buf, unsigned int size) { for (size /= 2; size; size--) *(buf + size) = cpu_to_le16(*(buf + size)); } /* * Read in and validate sprom. * Return 0 on success, nonzero on error. */ static int sprom_read_pci(si_t *sih, u16 *sprom, uint wordoff, u16 *buf, uint nwords, bool check_crc) { int err = 0; uint i; void *ccregs = NULL; /* read the sprom */ for (i = 0; i < nwords; i++) { if (sih->ccrev > 31 && ISSIM_ENAB(sih)) { /* use indirect since direct is too slow on QT */ if ((sih->cccaps & CC_CAP_SROM) == 0) return 1; ccregs = (void *)((u8 *) sprom - CC_SROM_OTP); buf[i] = srom_cc_cmd(sih, ccregs, SRC_OP_READ, wordoff + i, 0); } else { if (ISSIM_ENAB(sih)) buf[i] = R_REG(&sprom[wordoff + i]); buf[i] = R_REG(&sprom[wordoff + i]); } } /* bypass crc checking for simulation to allow srom hack */ if (ISSIM_ENAB(sih)) return err; if (check_crc) { if (buf[0] == 0xffff) { /* The hardware thinks that an srom that starts with 0xffff * is blank, regardless of the rest of the content, so declare * it bad. */ BS_ERROR(("%s: buf[0] = 0x%x, returning bad-crc\n", __func__, buf[0])); return 1; } /* fixup the endianness so crc8 will pass */ htol16_buf(buf, nwords * 2); if (hndcrc8((u8 *) buf, nwords * 2, CRC8_INIT_VALUE) != CRC8_GOOD_VALUE) { /* DBG only pci always read srom4 first, then srom8/9 */ /* BS_ERROR(("%s: bad crc\n", __func__)); */ err = 1; } /* now correct the endianness of the byte array */ ltoh16_buf(buf, nwords * 2); } return err; } #if defined(BCMNVRAMR) static int otp_read_pci(si_t *sih, u16 *buf, uint bufsz) { u8 *otp; uint sz = OTP_SZ_MAX / 2; /* size in words */ int err = 0; ASSERT(bufsz <= OTP_SZ_MAX); otp = kzalloc(OTP_SZ_MAX, GFP_ATOMIC); if (otp == NULL) { return BCME_ERROR; } err = otp_read_region(sih, OTP_HW_RGN, (u16 *) otp, &sz); memcpy(buf, otp, bufsz); kfree(otp); /* Check CRC */ if (buf[0] == 0xffff) { /* The hardware thinks that an srom that starts with 0xffff * is blank, regardless of the rest of the content, so declare * it bad. */ BS_ERROR(("%s: buf[0] = 0x%x, returning bad-crc\n", __func__, buf[0])); return 1; } /* fixup the endianness so crc8 will pass */ htol16_buf(buf, bufsz); if (hndcrc8((u8 *) buf, SROM4_WORDS * 2, CRC8_INIT_VALUE) != CRC8_GOOD_VALUE) { BS_ERROR(("%s: bad crc\n", __func__)); err = 1; } /* now correct the endianness of the byte array */ ltoh16_buf(buf, bufsz); return err; } #endif /* defined(BCMNVRAMR) */ /* * Create variable table from memory. * Return 0 on success, nonzero on error. */ static int initvars_table(char *start, char *end, char **vars, uint *count) { int c = (int)(end - start); /* do it only when there is more than just the null string */ if (c > 1) { char *vp = kmalloc(c, GFP_ATOMIC); ASSERT(vp != NULL); if (!vp) return BCME_NOMEM; memcpy(vp, start, c); *vars = vp; *count = c; } else { *vars = NULL; *count = 0; } return 0; } /* * Find variables with <devpath> from flash. 'base' points to the beginning * of the table upon enter and to the end of the table upon exit when success. * Return 0 on success, nonzero on error. */ static int initvars_flash(si_t *sih, char **base, uint len) { char *vp = *base; char *flash; int err; char *s; uint l, dl, copy_len; char devpath[SI_DEVPATH_BUFSZ]; /* allocate memory and read in flash */ flash = kmalloc(NVRAM_SPACE, GFP_ATOMIC); if (!flash) return BCME_NOMEM; err = nvram_getall(flash, NVRAM_SPACE); if (err) goto exit; si_devpath(sih, devpath, sizeof(devpath)); /* grab vars with the <devpath> prefix in name */ dl = strlen(devpath); for (s = flash; s && *s; s += l + 1) { l = strlen(s); /* skip non-matching variable */ if (strncmp(s, devpath, dl)) continue; /* is there enough room to copy? */ copy_len = l - dl + 1; if (len < copy_len) { err = BCME_BUFTOOSHORT; goto exit; } /* no prefix, just the name=value */ strncpy(vp, &s[dl], copy_len); vp += copy_len; len -= copy_len; } /* add null string as terminator */ if (len < 1) { err = BCME_BUFTOOSHORT; goto exit; } *vp++ = '\0'; *base = vp; exit: kfree(flash); return err; } /* * Initialize nonvolatile variable table from flash. * Return 0 on success, nonzero on error. */ static int initvars_flash_si(si_t *sih, char **vars, uint *count) { char *vp, *base; int err; ASSERT(vars != NULL); ASSERT(count != NULL); base = vp = kmalloc(MAXSZ_NVRAM_VARS, GFP_ATOMIC); ASSERT(vp != NULL); if (!vp) return BCME_NOMEM; err = initvars_flash(sih, &vp, MAXSZ_NVRAM_VARS); if (err == 0) err = initvars_table(base, vp, vars, count); kfree(base); return err; } /* Parse SROM and create name=value pairs. 'srom' points to * the SROM word array. 'off' specifies the offset of the * first word 'srom' points to, which should be either 0 or * SROM3_SWRG_OFF (full SROM or software region). */ static uint mask_shift(u16 mask) { uint i; for (i = 0; i < (sizeof(mask) << 3); i++) { if (mask & (1 << i)) return i; } ASSERT(mask); return 0; } static uint mask_width(u16 mask) { int i; for (i = (sizeof(mask) << 3) - 1; i >= 0; i--) { if (mask & (1 << i)) return (uint) (i - mask_shift(mask) + 1); } ASSERT(mask); return 0; } #if defined(BCMDBG) static bool mask_valid(u16 mask) { uint shift = mask_shift(mask); uint width = mask_width(mask); return mask == ((~0 << shift) & ~(~0 << (shift + width))); } #endif /* BCMDBG */ static void _initvars_srom_pci(u8 sromrev, u16 *srom, uint off, varbuf_t *b) { u16 w; u32 val; const sromvar_t *srv; uint width; uint flags; u32 sr = (1 << sromrev); varbuf_append(b, "sromrev=%d", sromrev); for (srv = pci_sromvars; srv->name != NULL; srv++) { const char *name; if ((srv->revmask & sr) == 0) continue; if (srv->off < off) continue; flags = srv->flags; name = srv->name; /* This entry is for mfgc only. Don't generate param for it, */ if (flags & SRFL_NOVAR) continue; if (flags & SRFL_ETHADDR) { u8 ea[ETH_ALEN]; ea[0] = (srom[srv->off - off] >> 8) & 0xff; ea[1] = srom[srv->off - off] & 0xff; ea[2] = (srom[srv->off + 1 - off] >> 8) & 0xff; ea[3] = srom[srv->off + 1 - off] & 0xff; ea[4] = (srom[srv->off + 2 - off] >> 8) & 0xff; ea[5] = srom[srv->off + 2 - off] & 0xff; varbuf_append(b, "%s=%pM", name, ea); } else { ASSERT(mask_valid(srv->mask)); ASSERT(mask_width(srv->mask)); w = srom[srv->off - off]; val = (w & srv->mask) >> mask_shift(srv->mask); width = mask_width(srv->mask); while (srv->flags & SRFL_MORE) { srv++; ASSERT(srv->name != NULL); if (srv->off == 0 || srv->off < off) continue; ASSERT(mask_valid(srv->mask)); ASSERT(mask_width(srv->mask)); w = srom[srv->off - off]; val += ((w & srv->mask) >> mask_shift(srv-> mask)) << width; width += mask_width(srv->mask); } if ((flags & SRFL_NOFFS) && ((int)val == (1 << width) - 1)) continue; if (flags & SRFL_CCODE) { if (val == 0) varbuf_append(b, "ccode="); else varbuf_append(b, "ccode=%c%c", (val >> 8), (val & 0xff)); } /* LED Powersave duty cycle has to be scaled: *(oncount >> 24) (offcount >> 8) */ else if (flags & SRFL_LEDDC) { u32 w32 = (((val >> 8) & 0xff) << 24) | /* oncount */ (((val & 0xff)) << 8); /* offcount */ varbuf_append(b, "leddc=%d", w32); } else if (flags & SRFL_PRHEX) varbuf_append(b, "%s=0x%x", name, val); else if ((flags & SRFL_PRSIGN) && (val & (1 << (width - 1)))) varbuf_append(b, "%s=%d", name, (int)(val | (~0 << width))); else varbuf_append(b, "%s=%u", name, val); } } if (sromrev >= 4) { /* Do per-path variables */ uint p, pb, psz; if (sromrev >= 8) { pb = SROM8_PATH0; psz = SROM8_PATH1 - SROM8_PATH0; } else { pb = SROM4_PATH0; psz = SROM4_PATH1 - SROM4_PATH0; } for (p = 0; p < MAX_PATH_SROM; p++) { for (srv = perpath_pci_sromvars; srv->name != NULL; srv++) { if ((srv->revmask & sr) == 0) continue; if (pb + srv->off < off) continue; /* This entry is for mfgc only. Don't generate param for it, */ if (srv->flags & SRFL_NOVAR) continue; w = srom[pb + srv->off - off]; ASSERT(mask_valid(srv->mask)); val = (w & srv->mask) >> mask_shift(srv->mask); width = mask_width(srv->mask); /* Cheating: no per-path var is more than 1 word */ if ((srv->flags & SRFL_NOFFS) && ((int)val == (1 << width) - 1)) continue; if (srv->flags & SRFL_PRHEX) varbuf_append(b, "%s%d=0x%x", srv->name, p, val); else varbuf_append(b, "%s%d=%d", srv->name, p, val); } pb += psz; } } } /* * Initialize nonvolatile variable table from sprom. * Return 0 on success, nonzero on error. */ static int initvars_srom_pci(si_t *sih, void *curmap, char **vars, uint *count) { u16 *srom, *sromwindow; u8 sromrev = 0; u32 sr; varbuf_t b; char *vp, *base = NULL; bool flash = false; int err = 0; /* * Apply CRC over SROM content regardless SROM is present or not, * and use variable <devpath>sromrev's existence in flash to decide * if we should return an error when CRC fails or read SROM variables * from flash. */ srom = kmalloc(SROM_MAX, GFP_ATOMIC); ASSERT(srom != NULL); if (!srom) return -2; sromwindow = (u16 *) SROM_OFFSET(sih); if (si_is_sprom_available(sih)) { err = sprom_read_pci(sih, sromwindow, 0, srom, SROM_WORDS, true); if ((srom[SROM4_SIGN] == SROM4_SIGNATURE) || (((sih->buscoretype == PCIE_CORE_ID) && (sih->buscorerev >= 6)) || ((sih->buscoretype == PCI_CORE_ID) && (sih->buscorerev >= 0xe)))) { /* sromrev >= 4, read more */ err = sprom_read_pci(sih, sromwindow, 0, srom, SROM4_WORDS, true); sromrev = srom[SROM4_CRCREV] & 0xff; if (err) BS_ERROR(("%s: srom %d, bad crc\n", __func__, sromrev)); } else if (err == 0) { /* srom is good and is rev < 4 */ /* top word of sprom contains version and crc8 */ sromrev = srom[SROM_CRCREV] & 0xff; /* bcm4401 sroms misprogrammed */ if (sromrev == 0x10) sromrev = 1; } } #if defined(BCMNVRAMR) /* Use OTP if SPROM not available */ else { err = otp_read_pci(sih, srom, SROM_MAX); if (err == 0) /* OTP only contain SROM rev8/rev9 for now */ sromrev = srom[SROM4_CRCREV] & 0xff; else err = 1; } #else else err = 1; #endif /* * We want internal/wltest driver to come up with default * sromvars so we can program a blank SPROM/OTP. */ if (err) { char *value; u32 val; val = 0; BS_ERROR(("Neither SPROM nor OTP has valid image\n")); value = si_getdevpathvar(sih, "sromrev"); if (value) { sromrev = (u8) simple_strtoul(value, NULL, 0); flash = true; goto varscont; } BS_ERROR(("%s, SROM CRC Error\n", __func__)); value = si_getnvramflvar(sih, "sromrev"); if (value) { err = 0; goto errout; } { err = -1; goto errout; } } varscont: /* Bitmask for the sromrev */ sr = 1 << sromrev; /* srom version check: Current valid versions: 1, 2, 3, 4, 5, 8, 9 */ if ((sr & 0x33e) == 0) { err = -2; goto errout; } ASSERT(vars != NULL); ASSERT(count != NULL); base = vp = kmalloc(MAXSZ_NVRAM_VARS, GFP_ATOMIC); ASSERT(vp != NULL); if (!vp) { err = -2; goto errout; } /* read variables from flash */ if (flash) { err = initvars_flash(sih, &vp, MAXSZ_NVRAM_VARS); if (err) goto errout; goto varsdone; } varbuf_init(&b, base, MAXSZ_NVRAM_VARS); /* parse SROM into name=value pairs. */ _initvars_srom_pci(sromrev, srom, 0, &b); /* final nullbyte terminator */ ASSERT(b.size >= 1); vp = b.buf; *vp++ = '\0'; ASSERT((vp - base) <= MAXSZ_NVRAM_VARS); varsdone: err = initvars_table(base, vp, vars, count); errout: if (base) kfree(base); kfree(srom); return err; } #ifdef BCMSDIO /* * Read the SDIO cis and call parsecis to initialize the vars. * Return 0 on success, nonzero on error. */ static int initvars_cis_sdio(char **vars, uint *count) { u8 *cis[SBSDIO_NUM_FUNCTION + 1]; uint fn, numfn; int rc = 0; numfn = bcmsdh_query_iofnum(NULL); ASSERT(numfn <= SDIOD_MAX_IOFUNCS); for (fn = 0; fn <= numfn; fn++) { cis[fn] = kzalloc(SBSDIO_CIS_SIZE_LIMIT, GFP_ATOMIC); if (cis[fn] == NULL) { rc = -1; break; } if (bcmsdh_cis_read(NULL, fn, cis[fn], SBSDIO_CIS_SIZE_LIMIT) != 0) { kfree(cis[fn]); rc = -2; break; } } if (!rc) rc = srom_parsecis(cis, fn, vars, count); while (fn-- > 0) kfree(cis[fn]); return rc; } /* set SDIO sprom command register */ static int sprom_cmd_sdio(u8 cmd) { u8 status = 0; uint wait_cnt = 1000; /* write sprom command register */ bcmsdh_cfg_write(NULL, SDIO_FUNC_1, SBSDIO_SPROM_CS, cmd, NULL); /* wait status */ while (wait_cnt--) { status = bcmsdh_cfg_read(NULL, SDIO_FUNC_1, SBSDIO_SPROM_CS, NULL); if (status & SBSDIO_SPROM_DONE) return 0; } return 1; } /* read a word from the SDIO srom */ static int sprom_read_sdio(u16 addr, u16 *data) { u8 addr_l, addr_h, data_l, data_h; addr_l = (u8) ((addr * 2) & 0xff); addr_h = (u8) (((addr * 2) >> 8) & 0xff); /* set address */ bcmsdh_cfg_write(NULL, SDIO_FUNC_1, SBSDIO_SPROM_ADDR_HIGH, addr_h, NULL); bcmsdh_cfg_write(NULL, SDIO_FUNC_1, SBSDIO_SPROM_ADDR_LOW, addr_l, NULL); /* do read */ if (sprom_cmd_sdio(SBSDIO_SPROM_READ)) return 1; /* read data */ data_h = bcmsdh_cfg_read(NULL, SDIO_FUNC_1, SBSDIO_SPROM_DATA_HIGH, NULL); data_l = bcmsdh_cfg_read(NULL, SDIO_FUNC_1, SBSDIO_SPROM_DATA_LOW, NULL); *data = (data_h << 8) | data_l; return 0; } #endif /* BCMSDIO */ static int initvars_srom_si(si_t *sih, void *curmap, char **vars, uint *varsz) { /* Search flash nvram section for srom variables */ return initvars_flash_si(sih, vars, varsz); }