diff options
author | wbx <wbx@3c298f89-4303-0410-b956-a3cf2f4a3e73> | 2005-03-16 13:50:00 +0000 |
---|---|---|
committer | wbx <wbx@3c298f89-4303-0410-b956-a3cf2f4a3e73> | 2005-03-16 13:50:00 +0000 |
commit | 2cea1e6b9aa20af4040e87e88f9d4d2017cc2233 (patch) | |
tree | 5cd5a5f93b8b203d2343d5ade6e35ebd6b99f55d /openwrt/package/linux/kernel-source/drivers/net/hnd/sbutils.c | |
parent | cfb6561e9bd4c5db6a6323525f0b5383e0649c5f (diff) |
add all source code from linksys/broadcom which is free, to cvs for better maintainence inside
openwrt. this gives us the ability to better support different hardware models, without changing
any external tar-balls. only et.o and wl.o is missing and is fetched from my webserver.
git-svn-id: svn://svn.openwrt.org/openwrt/trunk@379 3c298f89-4303-0410-b956-a3cf2f4a3e73
Diffstat (limited to 'openwrt/package/linux/kernel-source/drivers/net/hnd/sbutils.c')
-rw-r--r-- | openwrt/package/linux/kernel-source/drivers/net/hnd/sbutils.c | 2164 |
1 files changed, 2164 insertions, 0 deletions
diff --git a/openwrt/package/linux/kernel-source/drivers/net/hnd/sbutils.c b/openwrt/package/linux/kernel-source/drivers/net/hnd/sbutils.c new file mode 100644 index 000000000..50ec33986 --- /dev/null +++ b/openwrt/package/linux/kernel-source/drivers/net/hnd/sbutils.c @@ -0,0 +1,2164 @@ +/* + * Misc utility routines for accessing chip-specific features + * of the SiliconBackplane-based Broadcom chips. + * + * Copyright 2004, Broadcom Corporation + * All Rights Reserved. + * + * THIS SOFTWARE IS OFFERED "AS IS", AND BROADCOM GRANTS NO WARRANTIES OF ANY + * KIND, EXPRESS OR IMPLIED, BY STATUTE, COMMUNICATION OR OTHERWISE. BROADCOM + * SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A SPECIFIC PURPOSE OR NONINFRINGEMENT CONCERNING THIS SOFTWARE. + * $Id$ + */ + +#include <typedefs.h> +#include <osl.h> +#include <bcmutils.h> +#include <bcmdevs.h> +#include <sbconfig.h> +#include <sbchipc.h> +#include <sbpci.h> +#include <pcicfg.h> +#include <sbpcmcia.h> +#include <sbextif.h> +#include <sbutils.h> +#include <bcmsrom.h> + +/* debug/trace */ +#define SB_ERROR(args) + +typedef uint32 (*sb_intrsoff_t)(void *intr_arg); +typedef void (*sb_intrsrestore_t)(void *intr_arg, uint32 arg); +typedef bool (*sb_intrsenabled_t)(void *intr_arg); + +/* misc sb info needed by some of the routines */ +typedef struct sb_info { + uint chip; /* chip number */ + uint chiprev; /* chip revision */ + uint chippkg; /* chip package option */ + uint boardtype; /* board type */ + uint boardvendor; /* board vendor id */ + uint bus; /* what bus type we are going through */ + + void *osh; /* osl os handle */ + void *sdh; /* bcmsdh handle */ + + void *curmap; /* current regs va */ + void *regs[SB_MAXCORES]; /* other regs va */ + + uint curidx; /* current core index */ + uint dev_coreid; /* the core provides driver functions */ + uint pciidx; /* pci core index */ + uint pcirev; /* pci core rev */ + + uint pcmciaidx; /* pcmcia core index */ + uint pcmciarev; /* pcmcia core rev */ + bool memseg; /* flag to toggle MEM_SEG register */ + + uint ccrev; /* chipc core rev */ + + uint gpioidx; /* gpio control core index */ + uint gpioid; /* gpio control coretype */ + + uint numcores; /* # discovered cores */ + uint coreid[SB_MAXCORES]; /* id of each core */ + + void *intr_arg; /* interrupt callback function arg */ + sb_intrsoff_t intrsoff_fn; /* function turns chip interrupts off */ + sb_intrsrestore_t intrsrestore_fn; /* function restore chip interrupts */ + sb_intrsenabled_t intrsenabled_fn; /* function to check if chip interrupts are enabled */ +} sb_info_t; + +/* local prototypes */ +static void* sb_doattach(sb_info_t *si, uint devid, void *osh, void *regs, uint bustype, void *sdh, char **vars, int *varsz); +static void sb_scan(sb_info_t *si); +static uint sb_corereg(void *sbh, uint coreidx, uint regoff, uint mask, uint val); +static uint _sb_coreidx(void *sbh); +static uint sb_findcoreidx(void *sbh, uint coreid, uint coreunit); +static uint sb_pcidev2chip(uint pcidev); +static uint sb_chip2numcores(uint chip); + +#define SB_INFO(sbh) (sb_info_t*)sbh +#define SET_SBREG(sbh, r, mask, val) W_SBREG((sbh), (r), ((R_SBREG((sbh), (r)) & ~(mask)) | (val))) +#define GOODCOREADDR(x) (((x) >= SB_ENUM_BASE) && ((x) <= SB_ENUM_LIM) && ISALIGNED((x), SB_CORE_SIZE)) +#define GOODREGS(regs) (regs && ISALIGNED(regs, SB_CORE_SIZE)) +#define REGS2SB(va) (sbconfig_t*) ((uint)(va) + SBCONFIGOFF) +#define GOODIDX(idx) (((uint)idx) < SB_MAXCORES) +#define BADIDX (SB_MAXCORES+1) + +#define R_SBREG(sbh, sbr) sb_read_sbreg((sbh), (sbr)) +#define W_SBREG(sbh, sbr, v) sb_write_sbreg((sbh), (sbr), (v)) +#define AND_SBREG(sbh, sbr, v) W_SBREG((sbh), (sbr), (R_SBREG((sbh), (sbr)) & (v))) +#define OR_SBREG(sbh, sbr, v) W_SBREG((sbh), (sbr), (R_SBREG((sbh), (sbr)) | (v))) + +/* + * Macros to disable/restore function core(D11, ENET, ILINE20, etc) interrupts before/ + * after core switching to avoid invalid register accesss inside ISR. + */ +#define INTR_OFF(si, intr_val) \ + if ((si)->intrsoff_fn && (si)->coreid[(si)->curidx] == (si)->dev_coreid) { \ + intr_val = (*(si)->intrsoff_fn)((si)->intr_arg); } +#define INTR_RESTORE(si, intr_val) \ + if ((si)->intrsrestore_fn && (si)->coreid[(si)->curidx] == (si)->dev_coreid) { \ + (*(si)->intrsrestore_fn)((si)->intr_arg, intr_val); } + +/* power control defines */ +#define LPOMINFREQ 25000 /* low power oscillator min */ +#define LPOMAXFREQ 43000 /* low power oscillator max */ +#define XTALMINFREQ 19800000 /* 20mhz - 1% */ +#define XTALMAXFREQ 20200000 /* 20mhz + 1% */ +#define PCIMINFREQ 25000000 /* 25mhz */ +#define PCIMAXFREQ 34000000 /* 33mhz + fudge */ +#define SCC_DEF_DIV 0 /* default slow clock divider */ + +#define XTAL_ON_DELAY 1000 /* Xtal power on delay in us */ + +#define SCC_LOW2FAST_LIMIT 5000 /* turn on fast clock time, in unit of ms */ + + +static uint32 +sb_read_sbreg(void *sbh, volatile uint32 *sbr) +{ + sb_info_t *si; + uint8 tmp; + uint32 val, intr_val = 0; + + si = SB_INFO(sbh); + + /* + * compact flash only has 11 bits address, while we needs 12 bits address. + * MEM_SEG will be OR'd with other 11 bits address in hardware, + * so we program MEM_SEG with 12th bit when necessary(access sb regsiters). + * For normal PCMCIA bus(CFTable_regwinsz > 2k), do nothing special + */ + if(si->memseg) { + INTR_OFF(si, intr_val); + tmp = 1; + OSL_PCMCIA_WRITE_ATTR(si->osh, MEM_SEG, &tmp, 1); + (uint32)sbr &= ~(1 << 11); /* mask out bit 11*/ + } + + val = R_REG(sbr); + + if(si->memseg) { + tmp = 0; + OSL_PCMCIA_WRITE_ATTR(si->osh, MEM_SEG, &tmp, 1); + INTR_RESTORE(si, intr_val); + } + + return (val); +} + +static void +sb_write_sbreg(void *sbh, volatile uint32 *sbr, uint32 v) +{ + sb_info_t *si; + uint8 tmp; + volatile uint32 dummy; + uint32 intr_val = 0; + + si = SB_INFO(sbh); + + /* + * compact flash only has 11 bits address, while we needs 12 bits address. + * MEM_SEG will be OR'd with other 11 bits address in hardware, + * so we program MEM_SEG with 12th bit when necessary(access sb regsiters). + * For normal PCMCIA bus(CFTable_regwinsz > 2k), do nothing special + */ + if(si->memseg) { + INTR_OFF(si, intr_val); + tmp = 1; + OSL_PCMCIA_WRITE_ATTR(si->osh, MEM_SEG, &tmp, 1); + (uint32)sbr &= ~(1 << 11); /* mask out bit 11 */ + } + + if (si->bus == PCMCIA_BUS) { +#ifdef IL_BIGENDIAN + dummy = R_REG(sbr); + W_REG((volatile uint16 *)((uint32)sbr + 2), (uint16)((v >> 16) & 0xffff)); + dummy = R_REG(sbr); + W_REG((volatile uint16 *)sbr, (uint16)(v & 0xffff)); +#else + dummy = R_REG(sbr); + W_REG((volatile uint16 *)sbr, (uint16)(v & 0xffff)); + dummy = R_REG(sbr); + W_REG((volatile uint16 *)((uint32)sbr + 2), (uint16)((v >> 16) & 0xffff)); +#endif + } else + W_REG(sbr, v); + + if(si->memseg) { + tmp = 0; + OSL_PCMCIA_WRITE_ATTR(si->osh, MEM_SEG, &tmp, 1); + INTR_RESTORE(si, intr_val); + } +} + +/* + * Allocate a sb handle. + * devid - pci device id (used to determine chip#) + * osh - opaque OS handle + * regs - virtual address of initial core registers + * bustype - pci/pcmcia/sb/sdio/etc + * vars - pointer to a pointer area for "environment" variables + * varsz - pointer to int to return the size of the vars + */ +void* +sb_attach(uint devid, void *osh, void *regs, uint bustype, void *sdh, char **vars, int *varsz) +{ + sb_info_t *si; + + /* alloc sb_info_t */ + if ((si = MALLOC(sizeof (sb_info_t))) == NULL) { + SB_ERROR(("sb_attach: malloc failed!\n")); + return (NULL); + } + + return (sb_doattach(si, devid, osh, regs, bustype, sdh, vars, varsz)); +} + +/* global kernel resource */ +static sb_info_t ksi; + +/* generic kernel variant of sb_attach() */ +void* +sb_kattach() +{ + uint32 *regs; + char *unused; + int varsz; + + if (ksi.curmap == NULL) { + uint32 cid; + + regs = (uint32 *)REG_MAP(SB_ENUM_BASE, SB_CORE_SIZE); + cid = R_REG((uint32 *)regs); + if (((cid & CID_ID_MASK) == 0x4712) && + ((cid & CID_REV_MASK) <= 0x00020000)) { + uint32 *scc, val; + + scc = (uint32 *)((uint32)regs + OFFSETOF(chipcregs_t, slow_clk_ctl)); + val = R_REG(scc); + SB_ERROR((" initial scc = 0x%x\n", val)); + val |= SCC_SS_XTAL; + W_REG(scc, val); + } + + sb_doattach(&ksi, BCM4710_DEVICE_ID, NULL, (void*)regs, + SB_BUS, NULL, &unused, &varsz); + } + + return &ksi; +} + +static void* +sb_doattach(sb_info_t *si, uint devid, void *osh, void *regs, uint bustype, void *sdh, char **vars, int *varsz) +{ + uint origidx; + chipcregs_t *cc; + uint32 w; + + ASSERT(GOODREGS(regs)); + + bzero((uchar*)si, sizeof (sb_info_t)); + + si->pciidx = si->gpioidx = BADIDX; + + si->osh = osh; + si->curmap = regs; + si->sdh = sdh; + + /* check to see if we are a sb core mimic'ing a pci core */ + if (bustype == PCI_BUS) { + if (OSL_PCI_READ_CONFIG(osh, PCI_SPROM_CONTROL, sizeof (uint32)) == 0xffffffff) + bustype = SB_BUS; + else + bustype = PCI_BUS; + } + + si->bus = bustype; + + if (si->bus == PCMCIA_BUS) + /* need to set memseg flag for CF card first before any sb registers access, + * such as the access inside sb_scan. the card type is detected and memseg + * flag is reassigned later after srom_var_init. there should be no effect + * for PCMCIA cards even though the memseg flag is set + */ + si->memseg = TRUE; + + /* kludge to enable the clock on the 4306 which lacks a slowclock */ + if (si->bus == PCI_BUS) + sb_pwrctl_xtal((void*)si, XTAL|PLL, ON); + + /* initialize current core index value */ + si->curidx = _sb_coreidx((void*)si); + if (si->curidx == BADIDX) + goto bad; + + /* keep and reuse the initial register mapping */ + origidx = si->curidx; + if (si->bus == SB_BUS) + si->regs[origidx] = regs; + + /* is core-0 a chipcommon core? */ + si->numcores = 1; + cc = (chipcregs_t*) sb_setcoreidx((void*)si, 0); + if (sb_coreid((void*)si) != SB_CC) + cc = NULL; + + /* determine chip id and rev */ + if (cc) { + /* chip common core found! */ + si->chip = R_REG(&cc->chipid) & CID_ID_MASK; + si->chiprev = (R_REG(&cc->chipid) & CID_REV_MASK) >> CID_REV_SHIFT; + si->chippkg = (R_REG(&cc->chipid) & CID_PKG_MASK) >> CID_PKG_SHIFT; + } else { + /* The only pcmcia chip without a chipcommon core is a 4301 */ + if (si->bus == PCMCIA_BUS) + devid = BCM4301_DEVICE_ID; + + /* no chip common core -- must convert device id to chip id */ + if ((si->chip = sb_pcidev2chip(devid)) == 0) { + SB_ERROR(("sb_attach: unrecognized device id 0x%04x\n", devid)); + goto bad; + } + } + + /* get chipcommon rev */ + si->ccrev = cc? sb_corerev((void*)si) : 0; + + /* determine numcores */ + if ((si->ccrev == 4) || (si->ccrev >= 6)) + si->numcores = (R_REG(&cc->chipid) & CID_CC_MASK) >> CID_CC_SHIFT; + else + si->numcores = sb_chip2numcores(si->chip); + + /* return to original core */ + sb_setcoreidx((void*)si, origidx); + + /* sanity checks */ + ASSERT(si->chip); + + /* scan for cores */ + sb_scan(si); + + /* initialize the vars after sb_scan so that the core rev. information + * collected by sb_scan is available for the srom_var_init. + */ + if (srom_var_init(si, si->bus, si->curmap, osh, vars, varsz)) { + SB_ERROR(("sb_attach: srom_var_init failed\n")); + goto bad; + } + + if (cc == NULL) { + /* + * The chip revision number is hardwired into all + * of the pci function config rev fields and is + * independent from the individual core revision numbers. + * For example, the "A0" silicon of each chip is chip rev 0. + * For PCMCIA we get it from the CIS instead. + */ + if (si->bus == PCMCIA_BUS) { + ASSERT(vars); + si->chiprev = getintvar(*vars, "chiprev"); + } else if (si->bus == PCI_BUS) { + w = OSL_PCI_READ_CONFIG(osh, PCI_CFG_REV, sizeof (uint32)); + si->chiprev = w & 0xff; + } else + si->chiprev = 0; + } + + if (si->bus == PCMCIA_BUS) { + w = getintvar(*vars, "regwindowsz"); + si->memseg = (w <= CFTABLE_REGWIN_2K) ? TRUE : FALSE; + } + + /* pci core is required */ + if (!GOODIDX(si->pciidx)) { + SB_ERROR(("sb_attach: pci core not found\n")); + goto bad; + } + + /* gpio control core is required */ + if (!GOODIDX(si->gpioidx)) { + SB_ERROR(("sb_attach: gpio control core not found\n")); + goto bad; + } + + /* get boardtype and boardrev */ + switch (si->bus) { + case PCI_BUS: + /* do a pci config read to get subsystem id and subvendor id */ + w = OSL_PCI_READ_CONFIG(osh, PCI_CFG_SVID, sizeof (uint32)); + si->boardvendor = w & 0xffff; + si->boardtype = (w >> 16) & 0xffff; + break; + + case PCMCIA_BUS: + case SDIO_BUS: + si->boardvendor = getintvar(*vars, "manfid"); + si->boardtype = getintvar(*vars, "prodid"); + break; + + case SB_BUS: + si->boardvendor = VENDOR_BROADCOM; + si->boardtype = 0xffff; + break; + } + + if (si->boardtype == 0) { + SB_ERROR(("sb_attach: unknown board type\n")); + ASSERT(si->boardtype); + } + + /* clear any previous epidiag-induced target abort */ + sb_taclear((void*)si); + + return ((void*)si); + +bad: + MFREE(si, sizeof (sb_info_t)); + return (NULL); +} + +uint +sb_coreid(void *sbh) +{ + sb_info_t *si; + sbconfig_t *sb; + + si = SB_INFO(sbh); + sb = REGS2SB(si->curmap); + + return ((R_SBREG(sbh, &(sb)->sbidhigh) & SBIDH_CC_MASK) >> SBIDH_CC_SHIFT); +} + +uint +sb_coreidx(void *sbh) +{ + sb_info_t *si; + + si = SB_INFO(sbh); + return (si->curidx); +} + +/* return current index of core */ +static uint +_sb_coreidx(void *sbh) +{ + sb_info_t *si; + sbconfig_t *sb; + uint32 sbaddr = 0; + + si = SB_INFO(sbh); + ASSERT(si); + + switch (si->bus) { + case SB_BUS: + sb = REGS2SB(si->curmap); + sbaddr = sb_base(R_SBREG(sbh, &sb->sbadmatch0)); + break; + + case PCI_BUS: + sbaddr = OSL_PCI_READ_CONFIG(si->osh, PCI_BAR0_WIN, sizeof (uint32)); + break; + + case PCMCIA_BUS: { + uint8 tmp; + + OSL_PCMCIA_READ_ATTR(si->osh, PCMCIA_ADDR0, &tmp, 1); + sbaddr = (uint)tmp << 12; + OSL_PCMCIA_READ_ATTR(si->osh, PCMCIA_ADDR1, &tmp, 1); + sbaddr |= (uint)tmp << 16; + OSL_PCMCIA_READ_ATTR(si->osh, PCMCIA_ADDR2, &tmp, 1); + sbaddr |= (uint)tmp << 24; + break; + } + default: + ASSERT(0); + } + + if (!GOODCOREADDR(sbaddr)) + return BADIDX; + + return ((sbaddr - SB_ENUM_BASE) / SB_CORE_SIZE); +} + +uint +sb_corevendor(void *sbh) +{ + sb_info_t *si; + sbconfig_t *sb; + + si = SB_INFO(sbh); + sb = REGS2SB(si->curmap); + + return ((R_SBREG(sbh, &(sb)->sbidhigh) & SBIDH_VC_MASK) >> SBIDH_VC_SHIFT); +} + +uint +sb_corerev(void *sbh) +{ + sb_info_t *si; + sbconfig_t *sb; + + si = SB_INFO(sbh); + sb = REGS2SB(si->curmap); + + return (R_SBREG(sbh, &(sb)->sbidhigh) & SBIDH_RC_MASK); +} + +#define SBTML_ALLOW (SBTML_PE | SBTML_FGC | SBTML_FL_MASK) + +/* set/clear sbtmstatelow core-specific flags */ +uint32 +sb_coreflags(void *sbh, uint32 mask, uint32 val) +{ + sb_info_t *si; + sbconfig_t *sb; + uint32 w; + + si = SB_INFO(sbh); + sb = REGS2SB(si->curmap); + + ASSERT((val & ~mask) == 0); + ASSERT((mask & ~SBTML_ALLOW) == 0); + + /* mask and set */ + if (mask || val) { + w = (R_SBREG(sbh, &sb->sbtmstatelow) & ~mask) | val; + W_SBREG(sbh, &sb->sbtmstatelow, w); + } + + /* return the new value */ + return (R_SBREG(sbh, &sb->sbtmstatelow) & SBTML_ALLOW); +} + +/* set/clear sbtmstatehigh core-specific flags */ +uint32 +sb_coreflagshi(void *sbh, uint32 mask, uint32 val) +{ + sb_info_t *si; + sbconfig_t *sb; + uint32 w; + + si = SB_INFO(sbh); + sb = REGS2SB(si->curmap); + + ASSERT((val & ~mask) == 0); + ASSERT((mask & ~SBTMH_FL_MASK) == 0); + + /* mask and set */ + if (mask || val) { + w = (R_SBREG(sbh, &sb->sbtmstatehigh) & ~mask) | val; + W_SBREG(sbh, &sb->sbtmstatehigh, w); + } + + /* return the new value */ + return (R_SBREG(sbh, &sb->sbtmstatehigh) & SBTMH_FL_MASK); +} + +bool +sb_iscoreup(void *sbh) +{ + sb_info_t *si; + sbconfig_t *sb; + + si = SB_INFO(sbh); + sb = REGS2SB(si->curmap); + + return ((R_SBREG(sbh, &(sb)->sbtmstatelow) & (SBTML_RESET | SBTML_REJ | SBTML_CLK)) == SBTML_CLK); +} + +/* + * Switch to 'coreidx', issue a single arbitrary 32bit register mask&set operation, + * switch back to the original core, and return the new value. + */ +static uint +sb_corereg(void *sbh, uint coreidx, uint regoff, uint mask, uint val) +{ + sb_info_t *si; + uint origidx; + uint32 *r; + uint w; + uint intr_val = 0; + + ASSERT(GOODIDX(coreidx)); + ASSERT(regoff < SB_CORE_SIZE); + ASSERT((val & ~mask) == 0); + + si = SB_INFO(sbh); + + INTR_OFF(si, intr_val); + + /* save current core index */ + origidx = sb_coreidx(sbh); + + /* switch core */ + r = (uint32*) ((uint) sb_setcoreidx(sbh, coreidx) + regoff); + + /* mask and set */ + if (mask || val) { + if (regoff >= SBCONFIGOFF) { + w = (R_SBREG(sbh, r) & ~mask) | val; + W_SBREG(sbh, r, w); + } else { + w = (R_REG(r) & ~mask) | val; + W_REG(r, w); + } + } + + /* readback */ + if (regoff >= SBCONFIGOFF) + w = R_SBREG(sbh, r); + else + w = R_REG(r); + + /* restore core index */ + if (origidx != coreidx) + sb_setcoreidx(sbh, origidx); + + INTR_RESTORE(si, intr_val); + return (w); +} + +/* scan the sb enumerated space to identify all cores */ +static void +sb_scan(sb_info_t *si) +{ + void *sbh; + uint origidx; + uint i; + + sbh = (void*) si; + + /* numcores should already be set */ + ASSERT((si->numcores > 0) && (si->numcores <= SB_MAXCORES)); + + /* save current core index */ + origidx = sb_coreidx(sbh); + + si->pciidx = si->gpioidx = BADIDX; + + for (i = 0; i < si->numcores; i++) { + sb_setcoreidx(sbh, i); + si->coreid[i] = sb_coreid(sbh); + + if (si->coreid[i] == SB_PCI) { + si->pciidx = i; + si->pcirev = sb_corerev(sbh); + + } else if (si->coreid[i] == SB_PCMCIA) { + si->pcmciaidx = i; + si->pcmciarev = sb_corerev(sbh); + } + } + + /* + * Find the gpio "controlling core" type and index. + * Precedence: + * - if there's a chip common core - use that + * - else if there's a pci core (rev >= 2) - use that + * - else there had better be an extif core (4710 only) + */ + if (GOODIDX(sb_findcoreidx(sbh, SB_CC, 0))) { + si->gpioidx = sb_findcoreidx(sbh, SB_CC, 0); + si->gpioid = SB_CC; + } else if (GOODIDX(si->pciidx) && (si->pcirev >= 2)) { + si->gpioidx = si->pciidx; + si->gpioid = SB_PCI; + } else if (sb_findcoreidx(sbh, SB_EXTIF, 0)) { + si->gpioidx = sb_findcoreidx(sbh, SB_EXTIF, 0); + si->gpioid = SB_EXTIF; + } + + /* return to original core index */ + sb_setcoreidx(sbh, origidx); +} + +/* may be called with core in reset */ +void +sb_detach(void *sbh) +{ + sb_info_t *si; + uint idx; + + si = SB_INFO(sbh); + + if (si == NULL) + return; + + if (si->bus == SB_BUS) + for (idx = 0; idx < SB_MAXCORES; idx++) + if (si->regs[idx]) { + REG_UNMAP(si->regs[idx]); + si->regs[idx] = NULL; + } + + MFREE(si, sizeof (sb_info_t)); +} + +/* use pci dev id to determine chip id for chips not having a chipcommon core */ +static uint +sb_pcidev2chip(uint pcidev) +{ + if ((pcidev >= BCM4710_DEVICE_ID) && (pcidev <= BCM47XX_USB_ID)) + return (BCM4710_DEVICE_ID); + if ((pcidev >= BCM4610_DEVICE_ID) && (pcidev <= BCM4610_USB_ID)) + return (BCM4610_DEVICE_ID); + if ((pcidev >= BCM4402_DEVICE_ID) && (pcidev <= BCM4402_V90_ID)) + return (BCM4402_DEVICE_ID); + if ((pcidev >= BCM4307_V90_ID) && (pcidev <= BCM4307_D11B_ID)) + return (BCM4307_DEVICE_ID); + if (pcidev == BCM4301_DEVICE_ID) + return (BCM4301_DEVICE_ID); + + return (0); +} + +/* convert chip number to number of i/o cores */ +static uint +sb_chip2numcores(uint chip) +{ + if (chip == 0x4710) + return (9); + if (chip == 0x4610) + return (9); + if (chip == 0x4402) + return (3); + if ((chip == 0x4307) || (chip == 0x4301)) + return (5); + if (chip == 0x4310) + return (8); + if (chip == 0x4306) /* < 4306c0 */ + return (6); + if (chip == 0x4704) + return (9); + if (chip == 0x5365) + return (7); + + SB_ERROR(("sb_chip2numcores: unsupported chip 0x%x\n", chip)); + ASSERT(0); + return (1); +} + +/* return index of coreid or BADIDX if not found */ +static uint +sb_findcoreidx(void *sbh, uint coreid, uint coreunit) +{ + sb_info_t *si; + uint found; + uint i; + + si = SB_INFO(sbh); + found = 0; + + for (i = 0; i < si->numcores; i++) + if (si->coreid[i] == coreid) { + if (found == coreunit) + return (i); + found++; + } + + return (BADIDX); +} + +/* + * this function changes logical "focus" to the indiciated core, + * must be called with interrupt off. + * Moreover, callers should keep interrupts off during switching out of and back to d11 core + */ +void* +sb_setcoreidx(void *sbh, uint coreidx) +{ + sb_info_t *si; + uint32 sbaddr; + uint8 tmp; + + si = SB_INFO(sbh); + + if (coreidx >= si->numcores) + return (NULL); + + /* + * If the user has provided an interrupt mask enabled function, + * then assert interrupts are disabled before switching the core. + */ + ASSERT((si->intrsenabled_fn == NULL) || !(*(si)->intrsenabled_fn)((si)->intr_arg)); + + sbaddr = SB_ENUM_BASE + (coreidx * SB_CORE_SIZE); + + switch (si->bus) { + case SB_BUS: + /* map new one */ + if (!si->regs[coreidx]) { + si->regs[coreidx] = (void*)REG_MAP(sbaddr, SB_CORE_SIZE); + ASSERT(GOODREGS(si->regs[coreidx])); + } + si->curmap = si->regs[coreidx]; + break; + + case PCI_BUS: + /* point bar0 window */ + OSL_PCI_WRITE_CONFIG(si->osh, PCI_BAR0_WIN, 4, sbaddr); + break; + + case PCMCIA_BUS: + tmp = (sbaddr >> 12) & 0x0f; + OSL_PCMCIA_WRITE_ATTR(si->osh, PCMCIA_ADDR0, &tmp, 1); + tmp = (sbaddr >> 16) & 0xff; + OSL_PCMCIA_WRITE_ATTR(si->osh, PCMCIA_ADDR1, &tmp, 1); + tmp = (sbaddr >> 24) & 0xff; + OSL_PCMCIA_WRITE_ATTR(si->osh, PCMCIA_ADDR2, &tmp, 1); + break; + } + + si->curidx = coreidx; + + return (si->curmap); +} + +/* + * this function changes logical "focus" to the indiciated core, + * must be called with interrupt off. + * Moreover, callers should keep interrupts off during switching out of and back to d11 core + */ +void* +sb_setcore(void *sbh, uint coreid, uint coreunit) +{ + sb_info_t *si; + uint idx; + + si = SB_INFO(sbh); + + idx = sb_findcoreidx(sbh, coreid, coreunit); + if (!GOODIDX(idx)) + return (NULL); + + return (sb_setcoreidx(sbh, idx)); +} + +/* return chip number */ +uint +sb_chip(void *sbh) +{ + sb_info_t *si; + + si = SB_INFO(sbh); + return (si->chip); +} + +/* return chip revision number */ +uint +sb_chiprev(void *sbh) +{ + sb_info_t *si; + + si = SB_INFO(sbh); + return (si->chiprev); +} + +/* return chip common revision number */ +uint +sb_chipcrev(void *sbh) +{ + sb_info_t *si; + + si = SB_INFO(sbh); + return (si->ccrev); +} + +/* return chip package option */ +uint +sb_chippkg(void *sbh) +{ + sb_info_t *si; + + si = SB_INFO(sbh); + return (si->chippkg); +} + +/* return PCI core rev. */ +uint +sb_pcirev(void *sbh) +{ + sb_info_t *si; + + si = SB_INFO(sbh); + return (si->pcirev); +} + +/* return PCMCIA core rev. */ +uint +sb_pcmciarev(void *sbh) +{ + sb_info_t *si; + + si = SB_INFO(sbh); + return (si->pcmciarev); +} + +/* return board vendor id */ +uint +sb_boardvendor(void *sbh) +{ + sb_info_t *si; + + si = SB_INFO(sbh); + return (si->boardvendor); +} + +/* return boardtype */ +uint +sb_boardtype(void *sbh) +{ + sb_info_t *si; + char *var; + + si = SB_INFO(sbh); + + if (si->bus == SB_BUS && si->boardtype == 0xffff) { + /* boardtype format is a hex string */ + si->boardtype = getintvar(NULL, "boardtype"); + + /* backward compatibility for older boardtype string format */ + if ((si->boardtype == 0) && (var = getvar(NULL, "boardtype"))) { + if (!strcmp(var, "bcm94710dev")) + si->boardtype = BCM94710D_BOARD; + else if (!strcmp(var, "bcm94710ap")) + si->boardtype = BCM94710AP_BOARD; + else if (!strcmp(var, "bcm94310u")) + si->boardtype = BCM94310U_BOARD; + else if (!strcmp(var, "bu4711")) + si->boardtype = BU4711_BOARD; + else if (!strcmp(var, "bu4710")) + si->boardtype = BU4710_BOARD; + else if (!strcmp(var, "bcm94702mn")) + si->boardtype = BCM94702MN_BOARD; + else if (!strcmp(var, "bcm94710r1")) + si->boardtype = BCM94710R1_BOARD; + else if (!strcmp(var, "bcm94710r4")) + si->boardtype = BCM94710R4_BOARD; + else if (!strcmp(var, "bcm94702cpci")) + si->boardtype = BCM94702CPCI_BOARD; + else if (!strcmp(var, "bcm95380_rr")) + si->boardtype = BCM95380RR_BOARD; + } + } + + return (si->boardtype); +} + +/* return board bus style */ +uint +sb_boardstyle(void *sbh) +{ + sb_info_t *si; + uint16 w; + + si = SB_INFO(sbh); + + if (si->bus == PCMCIA_BUS) + return (BOARDSTYLE_PCMCIA); + + if (si->bus == SB_BUS) + return (BOARDSTYLE_SOC); + + /* bus is PCI */ + + if (OSL_PCI_READ_CONFIG(si->osh, PCI_CFG_CIS, sizeof (uint32)) != 0) + return (BOARDSTYLE_CARDBUS); + + if ((srom_read(si->bus, si->curmap, si->osh, (SPROM_SIZE - 1) * 2, 2, &w) == 0) && + (w == 0x0313)) + return (BOARDSTYLE_CARDBUS); + + return (BOARDSTYLE_PCI); +} + +/* return boolean if sbh device is in pci hostmode or client mode */ +uint +sb_bus(void *sbh) +{ + sb_info_t *si; + + si = SB_INFO(sbh); + return (si->bus); +} + +/* return list of found cores */ +uint +sb_corelist(void *sbh, uint coreid[]) +{ + sb_info_t *si; + + si = SB_INFO(sbh); + + bcopy((uchar*)si->coreid, (uchar*)coreid, (si->numcores * sizeof (uint))); + return (si->numcores); +} + +/* return current register mapping */ +void * +sb_coreregs(void *sbh) +{ + sb_info_t *si; + + si = SB_INFO(sbh); + ASSERT(GOODREGS(si->curmap)); + + return (si->curmap); +} + +/* traverse all cores to find and clear source of serror */ +static void +sb_serr_clear(void *sbh) +{ + sb_info_t *si; + sbconfig_t *sb; + uint origidx; + uint i, intr_val = 0; + void * corereg = NULL; + + si = SB_INFO(sbh); + + INTR_OFF(si, intr_val); + origidx = sb_coreidx(sbh); + + for (i = 0; i < si->numcores; i++) { + corereg = sb_setcoreidx(sbh, i); + if (NULL != corereg) { + sb = REGS2SB(corereg); + if ((si->chip == BCM4317_DEVICE_ID) && (si->chiprev == 0)) { + W_SBREG(sbh, &sb->sbtmstatehigh, 0); + } else { + if ((R_SBREG(sbh, &sb->sbtmstatehigh)) & SBTMH_SERR) { + AND_SBREG(sbh, &sb->sbtmstatehigh, ~SBTMH_SERR); + SB_ERROR(("sb_serr_clear: SError at core 0x%x\n", sb_coreid(sbh))); + } + } + } + } + + sb_setcoreidx(sbh, origidx); + INTR_RESTORE(si, intr_val); +} + +/* check if any inband, outband or timeout errors has happened and clear them */ +/* !! must be called with chip clk on */ +bool +sb_taclear(void *sbh) +{ + sb_info_t *si; + sbconfig_t *sb; + uint origidx; + uint intr_val = 0; + bool rc = FALSE; + uint32 inband = 0, serror = 0, timeout = 0; + void *corereg = NULL; + volatile uint32 imstate, tmstate; + + si = SB_INFO(sbh); + + if (si->bus == PCI_BUS) { + volatile uint32 stcmd; + + /* inband error is Target abort for PCI */ + stcmd = OSL_PCI_READ_CONFIG(si->osh, PCI_CFG_CMD, sizeof(uint32)); + inband = stcmd & PCI_CFG_CMD_STAT_TA; + if (inband) + OSL_PCI_WRITE_CONFIG(si->osh, PCI_CFG_CMD, sizeof(uint32), stcmd); + + /* serror */ + stcmd = OSL_PCI_READ_CONFIG(si->osh, PCI_INT_STATUS, sizeof(uint32)); + serror = stcmd & PCI_SBIM_STATUS_SERR; + if (serror) { + sb_serr_clear(sbh); + OSL_PCI_WRITE_CONFIG(si->osh, PCI_INT_STATUS, sizeof(uint32), stcmd); + } + + /* timeout */ + imstate = sb_corereg(sbh, si->pciidx, SBCONFIGOFF + OFFSETOF(sbconfig_t, sbimstate), 0, 0); + if ((imstate != 0xffffffff) && (imstate & (SBIM_IBE | SBIM_TO))) { + sb_corereg(sbh, si->pciidx, SBCONFIGOFF + OFFSETOF(sbconfig_t, sbimstate), ~0, + (imstate & ~(SBIM_IBE | SBIM_TO))); + /* inband = imstate & SBIM_IBE; same as TA above */ + timeout = imstate & SBIM_TO; + } + + } else if (si->bus == PCMCIA_BUS) { + + INTR_OFF(si, intr_val); + origidx = sb_coreidx(sbh); + + corereg = sb_setcore(sbh, SB_PCMCIA, 0); + if (NULL != corereg) { + sb = REGS2SB(corereg); + + imstate = R_SBREG(sbh, &sb->sbimstate); + /* handle surprise removal */ + if ((imstate != 0xffffffff) && (imstate & (SBIM_IBE | SBIM_TO))) { + AND_SBREG(sbh, &sb->sbimstate, ~(SBIM_IBE | SBIM_TO)); + inband = imstate & SBIM_IBE; + timeout = imstate & SBIM_TO; + } + tmstate = R_SBREG(sbh, &sb->sbtmstatehigh); + if ((tmstate != 0xffffffff) && (tmstate & SBTMH_INT_STATUS)) { + if (!inband) { + serror = 1; + sb_serr_clear(sbh); + } + OR_SBREG(sbh, &sb->sbtmstatelow, SBTML_INT_ACK); + AND_SBREG(sbh, &sb->sbtmstatelow, ~SBTML_INT_ACK); + } + } + sb_setcoreidx(sbh, origidx); + INTR_RESTORE(si, intr_val); + + } else if (si->bus == SDIO_BUS) { + + INTR_OFF(si, intr_val); + origidx = sb_coreidx(sbh); + + corereg = sb_setcore(sbh, SB_PCMCIA, 0); + if (NULL != corereg) { + sb = REGS2SB(corereg); + + imstate = R_SBREG(sbh, &sb->sbimstate); + if ((imstate != 0xffffffff) && (imstate & (SBIM_IBE | SBIM_TO))) { + AND_SBREG(sbh, &sb->sbimstate, ~(SBIM_IBE | SBIM_TO)); + /* inband = imstate & SBIM_IBE; cmd error */ + timeout = imstate & SBIM_TO; + } + tmstate = R_SBREG(sbh, &sb->sbtmstatehigh); + if ((tmstate != 0xffffffff) && (tmstate & SBTMH_INT_STATUS)) { + sb_serr_clear(sbh); + serror = 1; + OR_SBREG(sbh, &sb->sbtmstatelow, SBTML_INT_ACK); + AND_SBREG(sbh, &sb->sbtmstatelow, ~SBTML_INT_ACK); + } + } + + sb_setcoreidx(sbh, origidx); + INTR_RESTORE(si, intr_val); + } + + if ((inband | timeout | serror) != 0) { + rc = TRUE; + SB_ERROR(("sb_taclear: inband 0x%x, serror 0x%x, timeout 0x%x!\n", inband, serror, timeout)); + } + + return (rc); +} + +/* do buffered registers update */ +void +sb_commit(void *sbh) +{ + sb_info_t *si; + sbpciregs_t *pciregs; + uint origidx; + uint intr_val = 0; + + si = SB_INFO(sbh); + + origidx = si->curidx; + ASSERT(GOODIDX(origidx)); + + INTR_OFF(si, intr_val); + /* switch over to pci core */ + pciregs = (sbpciregs_t*) sb_setcore(sbh, SB_PCI, 0); + + /* do the buffer registers update */ + W_REG(&pciregs->bcastaddr, SB_COMMIT); + W_REG(&pciregs->bcastdata, 0x0); + + /* restore core index */ + sb_setcoreidx(sbh, origidx); + INTR_RESTORE(si, intr_val); +} + +/* reset and re-enable a core */ +void +sb_core_reset(void *sbh, uint32 bits) +{ + sb_info_t *si; + sbconfig_t *sb; + volatile uint32 dummy; + + si = SB_INFO(sbh); + ASSERT(GOODREGS(si->curmap)); + sb = REGS2SB(si->curmap); + + /* + * Must do the disable sequence first to work for arbitrary current core state. + */ + sb_core_disable(sbh, bits); + + /* + * Now do the initialization sequence. + */ + + /* set reset while enabling the clock and forcing them on throughout the core */ + W_SBREG(sbh, &sb->sbtmstatelow, (SBTML_FGC | SBTML_CLK | SBTML_RESET | bits)); + dummy = R_SBREG(sbh, &sb->sbtmstatelow); + + if (sb_coreid(sbh) == SB_ILINE100) { + bcm_mdelay(50); + } else { + OSL_DELAY(1); + } + + if (R_SBREG(sbh, &sb->sbtmstatehigh) & SBTMH_SERR) { + W_SBREG(sbh, &sb->sbtmstatehigh, 0); + } + if ((dummy = R_SBREG(sbh, &sb->sbimstate)) & (SBIM_IBE | SBIM_TO)) { + AND_SBREG(sbh, &sb->sbimstate, ~(SBIM_IBE | SBIM_TO)); + } + + /* clear reset and allow it to propagate throughout the core */ + W_SBREG(sbh, &sb->sbtmstatelow, (SBTML_FGC | SBTML_CLK | bits)); + dummy = R_SBREG(sbh, &sb->sbtmstatelow); + OSL_DELAY(1); + + /* leave clock enabled */ + W_SBREG(sbh, &sb->sbtmstatelow, (SBTML_CLK | bits)); + dummy = R_SBREG(sbh, &sb->sbtmstatelow); + OSL_DELAY(1); +} + +void +sb_core_tofixup(void *sbh) +{ + sb_info_t *si; + sbconfig_t *sb; + + si = SB_INFO(sbh); + + if (si->pcirev >= 5) + return; + + ASSERT(GOODREGS(si->curmap)); + sb = REGS2SB(si->curmap); + + if (si->bus == SB_BUS) { + SET_SBREG(sbh, &sb->sbimconfiglow, + SBIMCL_RTO_MASK | SBIMCL_STO_MASK, + (0x5 << SBIMCL_RTO_SHIFT) | 0x3); + } else { + if (sb_coreid(sbh) == SB_PCI) { + SET_SBREG(sbh, &sb->sbimconfiglow, + SBIMCL_RTO_MASK | SBIMCL_STO_MASK, + (0x3 << SBIMCL_RTO_SHIFT) | 0x2); + } else { + SET_SBREG(sbh, &sb->sbimconfiglow, (SBIMCL_RTO_MASK | SBIMCL_STO_MASK), 0); + } + } + + sb_commit(sbh); +} + +void +sb_core_disable(void *sbh, uint32 bits) +{ + sb_info_t *si; + volatile uint32 dummy; + sbconfig_t *sb; + + si = SB_INFO(sbh); + + ASSERT(GOODREGS(si->curmap)); + sb = REGS2SB(si->curmap); + + /* must return if core is already in reset */ + if (R_SBREG(sbh, &sb->sbtmstatelow) & SBTML_RESET) + return; + + /* put into reset and return if clocks are not enabled */ + if ((R_SBREG(sbh, &sb->sbtmstatelow) & SBTML_CLK) == 0) + goto disable; + + /* set the reject bit */ + W_SBREG(sbh, &sb->sbtmstatelow, (SBTML_CLK | SBTML_REJ)); + + /* spin until reject is set */ + while ((R_SBREG(sbh, &sb->sbtmstatelow) & SBTML_REJ) == 0) + OSL_DELAY(1); + + /* spin until sbtmstatehigh.busy is clear */ + while (R_SBREG(sbh, &sb->sbtmstatehigh) & SBTMH_BUSY) + OSL_DELAY(1); + + /* set reset and reject while enabling the clocks */ + W_SBREG(sbh, &sb->sbtmstatelow, (bits | SBTML_FGC | SBTML_CLK | SBTML_REJ | SBTML_RESET)); + dummy = R_SBREG(sbh, &sb->sbtmstatelow); + OSL_DELAY(10); + + disable: + /* leave reset and reject asserted */ + W_SBREG(sbh, &sb->sbtmstatelow, (bits | SBTML_REJ | SBTML_RESET)); + OSL_DELAY(1); +} + +void +sb_watchdog(void *sbh, uint ticks) +{ + sb_info_t *si = SB_INFO(sbh); + + /* instant NMI */ + switch (si->gpioid) { + case SB_CC: + sb_corereg(sbh, si->gpioidx, OFFSETOF(chipcregs_t, watchdog), ~0, ticks); + break; + case SB_EXTIF: + sb_corereg(sbh, si->gpioidx, OFFSETOF(extifregs_t, watchdog), ~0, ticks); + break; + } +} + +/* initialize the pcmcia core */ +void +sb_pcmcia_init(void *sbh) +{ + sb_info_t *si; + uint8 cor; + + si = SB_INFO(sbh); + + /* enable d11 mac interrupts */ + if (si->chip == BCM4301_DEVICE_ID) { + /* Have to use FCR2 in 4301 */ + OSL_PCMCIA_READ_ATTR(si->osh, PCMCIA_FCR2 + PCMCIA_COR, &cor, 1); + cor |= COR_IRQEN | COR_FUNEN; + OSL_PCMCIA_WRITE_ATTR(si->osh, PCMCIA_FCR2 + PCMCIA_COR, &cor, 1); + } else { + OSL_PCMCIA_READ_ATTR(si->osh, PCMCIA_FCR0 + PCMCIA_COR, &cor, 1); + cor |= COR_IRQEN | COR_FUNEN; + OSL_PCMCIA_WRITE_ATTR(si->osh, PCMCIA_FCR0 + PCMCIA_COR, &cor, 1); + } + +} + + +/* + * Configure the pci core for pci client (NIC) action + * and get appropriate dma offset value. + * coremask is the bitvec of cores by index to be enabled. + */ +void +sb_pci_setup(void *sbh, uint32 *dmaoffset, uint coremask) +{ + sb_info_t *si; + sbconfig_t *sb; + sbpciregs_t *pciregs; + uint32 sbflag; + uint32 w; + uint idx; + + si = SB_INFO(sbh); + + if (dmaoffset) + *dmaoffset = 0; + + /* if not pci bus, we're done */ + if (si->bus != PCI_BUS) + return; + + ASSERT(si->pciidx); + + /* get current core index */ + idx = si->curidx; + + /* we interrupt on this backplane flag number */ + ASSERT(GOODREGS(si->curmap)); + sb = REGS2SB(si->curmap); + sbflag = R_SBREG(sbh, &sb->sbtpsflag) & SBTPS_NUM0_MASK; + + /* switch over to pci core */ + pciregs = (sbpciregs_t*) sb_setcoreidx(sbh, si->pciidx); + sb = REGS2SB(pciregs); + + /* + * Enable sb->pci interrupts. Assume + * PCI rev 2.3 support was added in pci core rev 6 and things changed.. + */ + if (si->pcirev < 6) { + /* set sbintvec bit for our flag number */ + OR_SBREG(sbh, &sb->sbintvec, (1 << sbflag)); + } else { + /* pci config write to set this core bit in PCIIntMask */ + w = OSL_PCI_READ_CONFIG(si->osh, PCI_INT_MASK, sizeof(uint32)); + w |= (coremask << PCI_SBIM_SHIFT); + OSL_PCI_WRITE_CONFIG(si->osh, PCI_INT_MASK, sizeof(uint32), w); + } + + /* enable prefetch and bursts for sonics-to-pci translation 2 */ + OR_REG(&pciregs->sbtopci2, (SBTOPCI_PREF|SBTOPCI_BURST)); + + if (si->pcirev < 5) { + SET_SBREG(sbh, &sb->sbimconfiglow, SBIMCL_RTO_MASK | SBIMCL_STO_MASK, + (0x3 << SBIMCL_RTO_SHIFT) | 0x2); + sb_commit(sbh); + } + + /* switch back to previous core */ + sb_setcoreidx(sbh, idx); + + /* use large sb pci dma window */ + if (dmaoffset) + *dmaoffset = SB_PCI_DMA; +} + +uint32 +sb_base(uint32 admatch) +{ + uint32 base; + uint type; + + type = admatch & SBAM_TYPE_MASK; + ASSERT(type < 3); + + base = 0; + + if (type == 0) { + base = admatch & SBAM_BASE0_MASK; + } else if (type == 1) { + ASSERT(!(admatch & SBAM_ADNEG)); /* neg not supported */ + base = admatch & SBAM_BASE1_MASK; + } else if (type == 2) { + ASSERT(!(admatch & SBAM_ADNEG)); /* neg not supported */ + base = admatch & SBAM_BASE2_MASK; + } + + return (base); +} + +uint32 +sb_size(uint32 admatch) +{ + uint32 size; + uint type; + + type = admatch & SBAM_TYPE_MASK; + ASSERT(type < 3); + + size = 0; + + if (type == 0) { + size = 1 << (((admatch & SBAM_ADINT0_MASK) >> SBAM_ADINT0_SHIFT) + 1); + } else if (type == 1) { + ASSERT(!(admatch & SBAM_ADNEG)); /* neg not supported */ + size = 1 << (((admatch & SBAM_ADINT1_MASK) >> SBAM_ADINT1_SHIFT) + 1); + } else if (type == 2) { + ASSERT(!(admatch & SBAM_ADNEG)); /* neg not supported */ + size = 1 << (((admatch & SBAM_ADINT2_MASK) >> SBAM_ADINT2_SHIFT) + 1); + } + + return (size); +} + +/* return the core-type instantiation # of the current core */ +uint +sb_coreunit(void *sbh) +{ + sb_info_t *si; + uint idx; + uint coreid; + uint coreunit; + uint i; + + si = SB_INFO(sbh); + coreunit = 0; + + idx = si->curidx; + + ASSERT(GOODREGS(si->curmap)); + coreid = sb_coreid(sbh); + + /* count the cores of our type */ + for (i = 0; i < idx; i++) + if (si->coreid[i] == coreid) + coreunit++; + + return (coreunit); +} + +static INLINE uint32 +factor6(uint32 x) +{ + switch (x) { + case CC_F6_2: return 2; + case CC_F6_3: return 3; + case CC_F6_4: return 4; + case CC_F6_5: return 5; + case CC_F6_6: return 6; + case CC_F6_7: return 7; + default: return 0; + } +} + +/* calculate the speed the SB would run at given a set of clockcontrol values */ +uint32 +sb_clock_rate(uint32 pll_type, uint32 n, uint32 m) +{ + uint32 n1, n2, clock, m1, m2, m3, mc; + + n1 = n & CN_N1_MASK; + n2 = (n & CN_N2_MASK) >> CN_N2_SHIFT; + + if ((pll_type == PLL_TYPE1) || (pll_type == PLL_TYPE4)) { + n1 = factor6(n1); + n2 += CC_F5_BIAS; + } else if (pll_type == PLL_TYPE2) { + n1 += CC_T2_BIAS; + n2 += CC_T2_BIAS; + ASSERT((n1 >= 2) && (n1 <= 7)); + ASSERT((n2 >= 5) && (n2 <= 23)); + } else if (pll_type == PLL_TYPE3) { + return (100000000); + } else + ASSERT((pll_type >= PLL_TYPE1) && (pll_type <= PLL_TYPE4)); + + clock = CC_CLOCK_BASE * n1 * n2; + + if (clock == 0) + return 0; + + m1 = m & CC_M1_MASK; + m2 = (m & CC_M2_MASK) >> CC_M2_SHIFT; + m3 = (m & CC_M3_MASK) >> CC_M3_SHIFT; + mc = (m & CC_MC_MASK) >> CC_MC_SHIFT; + + if ((pll_type == PLL_TYPE1) || (pll_type == PLL_TYPE4)) { + m1 = factor6(m1); + if (pll_type == PLL_TYPE1) + m2 += CC_F5_BIAS; + else + m2 = factor6(m2); + m3 = factor6(m3); + + switch (mc) { + case CC_MC_BYPASS: return (clock); + case CC_MC_M1: return (clock / m1); + case CC_MC_M1M2: return (clock / (m1 * m2)); + case CC_MC_M1M2M3: return (clock / (m1 * m2 * m3)); + case CC_MC_M1M3: return (clock / (m1 * m3)); + default: return (0); + } + } else { + ASSERT(pll_type == PLL_TYPE2); + + m1 += CC_T2_BIAS; + m2 += CC_T2M2_BIAS; + m3 += CC_T2_BIAS; + ASSERT((m1 >= 2) && (m1 <= 7)); + ASSERT((m2 >= 3) && (m2 <= 10)); + ASSERT((m3 >= 2) && (m3 <= 7)); + + if ((mc & CC_T2MC_M1BYP) == 0) + clock /= m1; + if ((mc & CC_T2MC_M2BYP) == 0) + clock /= m2; + if ((mc & CC_T2MC_M3BYP) == 0) + clock /= m3; + + return(clock); + } +} + +/* returns the current speed the SB is running at */ +uint32 +sb_clock(void *sbh) +{ + sb_info_t *si; + extifregs_t *eir; + chipcregs_t *cc; + uint32 n, m; + uint idx; + uint32 pll_type, rate; + uint intr_val = 0; + + si = SB_INFO(sbh); + idx = si->curidx; + pll_type = PLL_TYPE1; + + INTR_OFF(si, intr_val); + + /* switch to extif or chipc core */ + if ((eir = (extifregs_t *) sb_setcore(sbh, SB_EXTIF, 0))) { + n = R_REG(&eir->clockcontrol_n); + m = R_REG(&eir->clockcontrol_sb); + } else if ((cc = (chipcregs_t *) sb_setcore(sbh, SB_CC, 0))) { + pll_type = R_REG(&cc->capabilities) & CAP_PLL_MASK; + n = R_REG(&cc->clockcontrol_n); + m = R_REG(&cc->clockcontrol_sb); + } else { + INTR_RESTORE(si, intr_val); + return 0; + } + + /* calculate rate */ + rate = sb_clock_rate(pll_type, n, m); + + /* switch back to previous core */ + sb_setcoreidx(sbh, idx); + + INTR_RESTORE(si, intr_val); + + return rate; +} + +/* change logical "focus" to the gpio core for optimized access */ +void* +sb_gpiosetcore(void *sbh) +{ + sb_info_t *si; + + si = SB_INFO(sbh); + + return (sb_setcoreidx(sbh, si->gpioidx)); +} + +/* mask&set gpiocontrol bits */ +uint32 +sb_gpiocontrol(void *sbh, uint32 mask, uint32 val) +{ + sb_info_t *si; + uint regoff; + + si = SB_INFO(sbh); + regoff = 0; + + switch (si->gpioid) { + case SB_CC: + regoff = OFFSETOF(chipcregs_t, gpiocontrol); + break; + + case SB_PCI: + regoff = OFFSETOF(sbpciregs_t, gpiocontrol); + break; + + case SB_EXTIF: + return (0); + } + + return (sb_corereg(sbh, si->gpioidx, regoff, mask, val)); +} + +/* mask&set gpio output enable bits */ +uint32 +sb_gpioouten(void *sbh, uint32 mask, uint32 val) +{ + sb_info_t *si; + uint regoff; + + si = SB_INFO(sbh); + regoff = 0; + + switch (si->gpioid) { + case SB_CC: + regoff = OFFSETOF(chipcregs_t, gpioouten); + break; + + case SB_PCI: + regoff = OFFSETOF(sbpciregs_t, gpioouten); + break; + + case SB_EXTIF: + regoff = OFFSETOF(extifregs_t, gpio[0].outen); + break; + } + + return (sb_corereg(sbh, si->gpioidx, regoff, mask, val)); +} + +/* mask&set gpio output bits */ +uint32 +sb_gpioout(void *sbh, uint32 mask, uint32 val) +{ + sb_info_t *si; + uint regoff; + + si = SB_INFO(sbh); + regoff = 0; + + switch (si->gpioid) { + case SB_CC: + regoff = OFFSETOF(chipcregs_t, gpioout); + break; + + case SB_PCI: + regoff = OFFSETOF(sbpciregs_t, gpioout); + break; + + case SB_EXTIF: + regoff = OFFSETOF(extifregs_t, gpio[0].out); + break; + } + + return (sb_corereg(sbh, si->gpioidx, regoff, mask, val)); +} + +/* return the current gpioin register value */ +uint32 +sb_gpioin(void *sbh) +{ + sb_info_t *si; + uint regoff; + + si = SB_INFO(sbh); + regoff = 0; + + switch (si->gpioid) { + case SB_CC: + regoff = OFFSETOF(chipcregs_t, gpioin); + break; + + case SB_PCI: + regoff = OFFSETOF(sbpciregs_t, gpioin); + break; + + case SB_EXTIF: + regoff = OFFSETOF(extifregs_t, gpioin); + break; + } + + return (sb_corereg(sbh, si->gpioidx, regoff, 0, 0)); +} + +/* mask&set gpio interrupt polarity bits */ +uint32 +sb_gpiointpolarity(void *sbh, uint32 mask, uint32 val) +{ + sb_info_t *si; + uint regoff; + + si = SB_INFO(sbh); + regoff = 0; + + switch (si->gpioid) { + case SB_CC: + regoff = OFFSETOF(chipcregs_t, gpiointpolarity); + break; + + case SB_PCI: + /* pci gpio implementation does not support interrupt polarity */ + ASSERT(0); + break; + + case SB_EXTIF: + regoff = OFFSETOF(extifregs_t, gpiointpolarity); + break; + } + + return (sb_corereg(sbh, si->gpioidx, regoff, mask, val)); +} + +/* mask&set gpio interrupt mask bits */ +uint32 +sb_gpiointmask(void *sbh, uint32 mask, uint32 val) +{ + sb_info_t *si; + uint regoff; + + si = SB_INFO(sbh); + regoff = 0; + + switch (si->gpioid) { + case SB_CC: + regoff = OFFSETOF(chipcregs_t, gpiointmask); + break; + + case SB_PCI: + /* pci gpio implementation does not support interrupt mask */ + ASSERT(0); + break; + + case SB_EXTIF: + regoff = OFFSETOF(extifregs_t, gpiointmask); + break; + } + + return (sb_corereg(sbh, si->gpioidx, regoff, mask, val)); +} + + +/* + * Return the slow clock source. + * Three sources of SLOW CLOCK: LPO, Xtal, PCI + */ +static uint +sb_slowclk_src(void *sbh) +{ + sb_info_t *si; + chipcregs_t *cc; + uint32 v; + + si = SB_INFO(sbh); + + ASSERT(sb_coreid(sbh) == SB_CC); + + if (si->ccrev < 6) { + switch (si->bus) { + case PCMCIA_BUS: return (SCC_SS_XTAL); + case PCI_BUS: + v = OSL_PCI_READ_CONFIG(si->osh, PCI_GPIO_OUT, sizeof (uint32)); + if (v & PCI_CFG_GPIO_SCS) + return (SCC_SS_PCI); + else + return (SCC_SS_XTAL); + default: return (SCC_SS_XTAL); + } + } else if (si->ccrev < 10) { + cc = (chipcregs_t*) sb_setcoreidx(sbh, si->curidx); + v = R_REG(&cc->slow_clk_ctl) & SCC_SS_MASK; + return (v); + } else { + return (SCC_SS_XTAL); + } +} + +/* + * Return the slowclock min or max frequency. + * Three sources of SLOW CLOCK: + * 1. On Chip LPO - 32khz or 160khz + * 2. On Chip Xtal OSC - 20mhz/4*(divider+1) + * 3. External PCI clock - 66mhz/4*(divider+1) + */ +static uint +sb_slowclk_freq(void *sbh, bool max) +{ + sb_info_t *si; + chipcregs_t *cc; + uint32 slowclk; + uint div; + + si = SB_INFO(sbh); + + ASSERT(sb_coreid(sbh) == SB_CC); + + cc = (chipcregs_t*) sb_setcoreidx(sbh, si->curidx); + + /* shouldn't be here unless we've established the chip has dynamic power control */ + ASSERT(R_REG(&cc->capabilities) & CAP_PWR_CTL); + + slowclk = sb_slowclk_src(sbh); + if (si->ccrev < 6) { + if (slowclk == SCC_SS_PCI) + return (max? (PCIMAXFREQ/64) : (PCIMINFREQ/64)); + else + return (max? (XTALMAXFREQ/32) : (XTALMINFREQ/32)); + } else if (si->ccrev < 10) { + div = 4 * (((R_REG(&cc->slow_clk_ctl) & SCC_CD_MASK) >> SCC_CD_SHF) + 1); + if (slowclk == SCC_SS_LPO) + return (max? LPOMAXFREQ : LPOMINFREQ); + else if (slowclk == SCC_SS_XTAL) + return (max? (XTALMAXFREQ/div) : (XTALMINFREQ/div)); + else if (slowclk == SCC_SS_PCI) + return (max? (PCIMAXFREQ/div) : (PCIMINFREQ/div)); + else + ASSERT(0); + } else { + /* Chipc rev 10 is InstaClock */ + div = R_REG(&cc->system_clk_ctl) >> SYCC_CD_SHF; + div = 4 * (div + 1); + return (max ? XTALMAXFREQ : (XTALMINFREQ/div)); + } + return (0); +} + +static void +sb_pwrctl_setdelay(void *sbh, void *chipcregs) +{ + chipcregs_t * cc; + uint slowmaxfreq, pll_delay, slowclk; + uint pll_on_delay, fref_sel_delay; + + pll_delay = PLL_DELAY; + + /* If the slow clock is not sourced by the xtal then add the xtal_on_delay + * since the xtal will also be powered down by dynamic power control logic. + */ + slowclk = sb_slowclk_src(sbh); + if (slowclk != SCC_SS_XTAL) + pll_delay += XTAL_ON_DELAY; + + slowmaxfreq = sb_slowclk_freq(sbh, TRUE); + + pll_on_delay = ((slowmaxfreq * pll_delay) + 999999) / 1000000; + fref_sel_delay = ((slowmaxfreq * FREF_DELAY) + 999999) / 1000000; + + cc = (chipcregs_t *)chipcregs; + W_REG(&cc->pll_on_delay, pll_on_delay); + W_REG(&cc->fref_sel_delay, fref_sel_delay); +} + +/* set or get slow clock divider */ +int +sb_pwrctl_slowclk(void *sbh, bool set, uint *div) +{ + sb_info_t *si; + uint origidx; + chipcregs_t *cc; + uint intr_val = 0; + uint err = 0; + + si = SB_INFO(sbh); + + /* chipcommon cores prior to rev6 don't support slowclkcontrol */ + if (si->ccrev < 6) + return 1; + + /* chipcommon cores rev10 are a whole new ball game */ + if (si->ccrev >= 10) + return 1; + + if (set && ((*div % 4) || (*div < 4))) + return 2; + + INTR_OFF(si, intr_val); + origidx = si->curidx; + cc = (chipcregs_t*) sb_setcore(sbh, SB_CC, 0); + ASSERT(cc != NULL); + + if (!(R_REG(&cc->capabilities) & CAP_PWR_CTL)) { + err = 3; + goto done; + } + + if (set) { + SET_REG(&cc->slow_clk_ctl, SCC_CD_MASK, ((*div / 4 - 1) << SCC_CD_SHF)); + sb_pwrctl_setdelay(sbh, (void *)cc); + } else + *div = 4 * (((R_REG(&cc->slow_clk_ctl) & SCC_CD_MASK) >> SCC_CD_SHF) + 1); + +done: + sb_setcoreidx(sbh, origidx); + INTR_RESTORE(si, intr_val); + return err; +} + +/* initialize power control delay registers */ +void +sb_pwrctl_init(void *sbh) +{ + sb_info_t *si; + uint origidx; + chipcregs_t *cc; + + si = SB_INFO(sbh); + + if (si->bus == SB_BUS) + return; + + origidx = si->curidx; + + if ((cc = (chipcregs_t*) sb_setcore(sbh, SB_CC, 0)) == NULL) + return; + + if (!(R_REG(&cc->capabilities) & CAP_PWR_CTL)) + goto done; + + /* 4317pc does not work with SlowClock less than 5Mhz */ + if (si->bus == PCMCIA_BUS) { + if ((si->ccrev >= 6) && (si->ccrev < 10)) + SET_REG(&cc->slow_clk_ctl, SCC_CD_MASK, (SCC_DEF_DIV << SCC_CD_SHF)); + } + + sb_pwrctl_setdelay(sbh, (void *)cc); + +done: + sb_setcoreidx(sbh, origidx); +} + +/* return the value suitable for writing to the dot11 core FAST_PWRUP_DELAY register */ +uint16 +sb_pwrctl_fast_pwrup_delay(void *sbh) +{ + sb_info_t *si; + uint origidx; + chipcregs_t *cc; + uint slowminfreq; + uint16 fpdelay; + uint intr_val = 0; + + si = SB_INFO(sbh); + fpdelay = 0; + origidx = si->curidx; + + if (si->bus == SB_BUS) + goto done; + + INTR_OFF(si, intr_val); + + if ((cc = (chipcregs_t*) sb_setcore(sbh, SB_CC, 0)) == NULL) + goto done; + + if (!(R_REG(&cc->capabilities) & CAP_PWR_CTL)) + goto done; + + slowminfreq = sb_slowclk_freq(sbh, FALSE); + fpdelay = (((R_REG(&cc->pll_on_delay) + 2) * 1000000) + (slowminfreq - 1)) / slowminfreq; + +done: + sb_setcoreidx(sbh, origidx); + INTR_RESTORE(si, intr_val); + return (fpdelay); +} + +/* turn primary xtal and/or pll off/on */ +int +sb_pwrctl_xtal(void *sbh, uint what, bool on) +{ + sb_info_t *si; + uint32 in, out, outen; + + si = SB_INFO(sbh); + + switch (si->bus) { + + + case PCMCIA_BUS: + return (0); + + + case PCI_BUS: + + in = OSL_PCI_READ_CONFIG(si->osh, PCI_GPIO_IN, sizeof (uint32)); + out = OSL_PCI_READ_CONFIG(si->osh, PCI_GPIO_OUT, sizeof (uint32)); + outen = OSL_PCI_READ_CONFIG(si->osh, PCI_GPIO_OUTEN, sizeof (uint32)); + + /* + * We can't actually read the state of the PLLPD so we infer it + * by the value of XTAL_PU which *is* readable via gpioin. + */ + if (on && (in & PCI_CFG_GPIO_XTAL)) + return (0); + + if (what & XTAL) + outen |= PCI_CFG_GPIO_XTAL; + if (what & PLL) + outen |= PCI_CFG_GPIO_PLL; + + if (on) { + /* turn primary xtal on */ + if (what & XTAL) { + out |= PCI_CFG_GPIO_XTAL; + if (what & PLL) + out |= PCI_CFG_GPIO_PLL; + OSL_PCI_WRITE_CONFIG(si->osh, PCI_GPIO_OUT, sizeof (uint32), out); + OSL_PCI_WRITE_CONFIG(si->osh, PCI_GPIO_OUTEN, sizeof (uint32), outen); + OSL_DELAY(XTAL_ON_DELAY); + } + + /* turn pll on */ + if (what & PLL) { + out &= ~PCI_CFG_GPIO_PLL; + OSL_PCI_WRITE_CONFIG(si->osh, PCI_GPIO_OUT, sizeof (uint32), out); + OSL_DELAY(2000); + } + } else { + if (what & XTAL) + out &= ~PCI_CFG_GPIO_XTAL; + if (what & PLL) + out |= PCI_CFG_GPIO_PLL; + OSL_PCI_WRITE_CONFIG(si->osh, PCI_GPIO_OUT, sizeof (uint32), out); + OSL_PCI_WRITE_CONFIG(si->osh, PCI_GPIO_OUTEN, sizeof (uint32), outen); + } + + default: + return (-1); + } + + return (0); +} + +/* set dynamic power control mode (forceslow, forcefast, dynamic) */ +/* returns true if ignore pll off is set and false if it is not */ +bool +sb_pwrctl_clk(void *sbh, uint mode) +{ + sb_info_t *si; + uint origidx; + chipcregs_t *cc; + uint32 scc; + bool forcefastclk=FALSE; + uint intr_val = 0; + + si = SB_INFO(sbh); + + /* chipcommon cores prior to rev6 don't support slowclkcontrol */ + if (si->ccrev < 6) + return (FALSE); + + /* chipcommon cores rev10 are a whole new ball game */ + if (si->ccrev >= 10) + return (FALSE); + + INTR_OFF(si, intr_val); + + origidx = si->curidx; + + cc = (chipcregs_t*) sb_setcore(sbh, SB_CC, 0); + ASSERT(cc != NULL); + + if (!(R_REG(&cc->capabilities) & CAP_PWR_CTL)) + goto done; + + switch (mode) { + case CLK_FAST: /* force fast (pll) clock */ + /* don't forget to force xtal back on before we clear SCC_DYN_XTAL.. */ + sb_pwrctl_xtal(sbh, XTAL, ON); + + SET_REG(&cc->slow_clk_ctl, (SCC_XC | SCC_FS | SCC_IP), SCC_IP); + break; + + case CLK_SLOW: /* force slow clock */ + if ((si->bus == SDIO_BUS) || (si->bus == PCMCIA_BUS)) + return (-1); + + if (si->ccrev >= 6) + OR_REG(&cc->slow_clk_ctl, SCC_FS); + break; + + case CLK_DYNAMIC: /* enable dynamic power control */ + scc = R_REG(&cc->slow_clk_ctl); + scc &= ~(SCC_FS | SCC_IP | SCC_XC); + if ((scc & SCC_SS_MASK) != SCC_SS_XTAL) + scc |= SCC_XC; + W_REG(&cc->slow_clk_ctl, scc); + + /* for dynamic control, we have to release our xtal_pu "force on" */ + if (scc & SCC_XC) + sb_pwrctl_xtal(sbh, XTAL, OFF); + break; + } + + /* Is the h/w forcing the use of the fast clk */ + forcefastclk = (bool)((R_REG(&cc->slow_clk_ctl) & SCC_IP) == SCC_IP); + +done: + sb_setcoreidx(sbh, origidx); + INTR_RESTORE(si, intr_val); + return (forcefastclk); +} + +/* register driver interrupt disabling and restoring callback functions */ +void +sb_register_intr_callback(void *sbh, void *intrsoff_fn, void *intrsrestore_fn, void *intrsenabled_fn, void *intr_arg) +{ + sb_info_t *si; + + si = SB_INFO(sbh); + si->intr_arg = intr_arg; + si->intrsoff_fn = (sb_intrsoff_t)intrsoff_fn; + si->intrsrestore_fn = (sb_intrsrestore_t)intrsrestore_fn; + si->intrsenabled_fn = (sb_intrsenabled_t)intrsenabled_fn; + /* save current core id. when this function called, the current core + * must be the core which provides driver functions(il, et, wl, etc.) + */ + si->dev_coreid = si->coreid[si->curidx]; +} + + |