diff options
Diffstat (limited to 'drivers/net/wireless/brcm80211/brcmsmac/otp.c')
-rw-r--r-- | drivers/net/wireless/brcm80211/brcmsmac/otp.c | 426 |
1 files changed, 426 insertions, 0 deletions
diff --git a/drivers/net/wireless/brcm80211/brcmsmac/otp.c b/drivers/net/wireless/brcm80211/brcmsmac/otp.c new file mode 100644 index 000000000000..edf551561fd8 --- /dev/null +++ b/drivers/net/wireless/brcm80211/brcmsmac/otp.c @@ -0,0 +1,426 @@ +/* + * 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/io.h> +#include <linux/errno.h> +#include <linux/string.h> + +#include <brcm_hw_ids.h> +#include <chipcommon.h> +#include "aiutils.h" +#include "otp.h" + +#define OTPS_GUP_MASK 0x00000f00 +#define OTPS_GUP_SHIFT 8 +/* h/w subregion is programmed */ +#define OTPS_GUP_HW 0x00000100 +/* s/w subregion is programmed */ +#define OTPS_GUP_SW 0x00000200 +/* chipid/pkgopt subregion is programmed */ +#define OTPS_GUP_CI 0x00000400 +/* fuse subregion is programmed */ +#define OTPS_GUP_FUSE 0x00000800 + +/* Fields in otpprog in rev >= 21 */ +#define OTPP_COL_MASK 0x000000ff +#define OTPP_COL_SHIFT 0 +#define OTPP_ROW_MASK 0x0000ff00 +#define OTPP_ROW_SHIFT 8 +#define OTPP_OC_MASK 0x0f000000 +#define OTPP_OC_SHIFT 24 +#define OTPP_READERR 0x10000000 +#define OTPP_VALUE_MASK 0x20000000 +#define OTPP_VALUE_SHIFT 29 +#define OTPP_START_BUSY 0x80000000 +#define OTPP_READ 0x40000000 + +/* Opcodes for OTPP_OC field */ +#define OTPPOC_READ 0 +#define OTPPOC_BIT_PROG 1 +#define OTPPOC_VERIFY 3 +#define OTPPOC_INIT 4 +#define OTPPOC_SET 5 +#define OTPPOC_RESET 6 +#define OTPPOC_OCST 7 +#define OTPPOC_ROW_LOCK 8 +#define OTPPOC_PRESCN_TEST 9 + +#define OTPTYPE_IPX(ccrev) ((ccrev) == 21 || (ccrev) >= 23) + +#define OTPP_TRIES 10000000 /* # of tries for OTPP */ + +#define MAXNUMRDES 9 /* Maximum OTP redundancy entries */ + +/* Fixed size subregions sizes in words */ +#define OTPGU_CI_SZ 2 + +struct otpinfo; + +/* OTP function struct */ +struct otp_fn_s { + int (*init)(struct si_pub *sih, struct otpinfo *oi); + int (*read_region)(struct otpinfo *oi, int region, u16 *data, + uint *wlen); +}; + +struct otpinfo { + uint ccrev; /* chipc revision */ + const struct otp_fn_s *fn; /* OTP functions */ + struct si_pub *sih; /* Saved sb handle */ + + /* IPX OTP section */ + u16 wsize; /* Size of otp in words */ + u16 rows; /* Geometry */ + u16 cols; /* Geometry */ + u32 status; /* Flag bits (lock/prog/rv). + * (Reflected only when OTP is power cycled) + */ + u16 hwbase; /* hardware subregion offset */ + u16 hwlim; /* hardware subregion boundary */ + u16 swbase; /* software subregion offset */ + u16 swlim; /* software subregion boundary */ + u16 fbase; /* fuse subregion offset */ + u16 flim; /* fuse subregion boundary */ + int otpgu_base; /* offset to General Use Region */ +}; + +/* OTP layout */ +/* CC revs 21, 24 and 27 OTP General Use Region word offset */ +#define REVA4_OTPGU_BASE 12 + +/* CC revs 23, 25, 26, 28 and above OTP General Use Region word offset */ +#define REVB8_OTPGU_BASE 20 + +/* CC rev 36 OTP General Use Region word offset */ +#define REV36_OTPGU_BASE 12 + +/* Subregion word offsets in General Use region */ +#define OTPGU_HSB_OFF 0 +#define OTPGU_SFB_OFF 1 +#define OTPGU_CI_OFF 2 +#define OTPGU_P_OFF 3 +#define OTPGU_SROM_OFF 4 + +/* Flag bit offsets in General Use region */ +#define OTPGU_HWP_OFF 60 +#define OTPGU_SWP_OFF 61 +#define OTPGU_CIP_OFF 62 +#define OTPGU_FUSEP_OFF 63 +#define OTPGU_CIP_MSK 0x4000 +#define OTPGU_P_MSK 0xf000 +#define OTPGU_P_SHIFT (OTPGU_HWP_OFF % 16) + +/* OTP Size */ +#define OTP_SZ_FU_324 ((roundup(324, 8))/8) /* 324 bits */ +#define OTP_SZ_FU_288 (288/8) /* 288 bits */ +#define OTP_SZ_FU_216 (216/8) /* 216 bits */ +#define OTP_SZ_FU_72 (72/8) /* 72 bits */ +#define OTP_SZ_CHECKSUM (16/8) /* 16 bits */ +#define OTP4315_SWREG_SZ 178 /* 178 bytes */ +#define OTP_SZ_FU_144 (144/8) /* 144 bits */ + +static u16 +ipxotp_otpr(struct otpinfo *oi, struct chipcregs __iomem *cc, uint wn) +{ + return R_REG(&cc->sromotp[wn]); +} + +/* + * Calculate max HW/SW region byte size by subtracting fuse region + * and checksum size, osizew is oi->wsize (OTP size - GU size) in words + */ +static int ipxotp_max_rgnsz(struct si_pub *sih, int osizew) +{ + int ret = 0; + + switch (sih->chip) { + case BCM43224_CHIP_ID: + case BCM43225_CHIP_ID: + ret = osizew * 2 - OTP_SZ_FU_72 - OTP_SZ_CHECKSUM; + break; + case BCM4313_CHIP_ID: + ret = osizew * 2 - OTP_SZ_FU_72 - OTP_SZ_CHECKSUM; + break; + default: + break; /* Don't know about this chip */ + } + + return ret; +} + +static void _ipxotp_init(struct otpinfo *oi, struct chipcregs __iomem *cc) +{ + uint k; + u32 otpp, st; + + /* + * record word offset of General Use Region + * for various chipcommon revs + */ + if (oi->sih->ccrev == 21 || oi->sih->ccrev == 24 + || oi->sih->ccrev == 27) { + oi->otpgu_base = REVA4_OTPGU_BASE; + } else if (oi->sih->ccrev == 36) { + /* + * OTP size greater than equal to 2KB (128 words), + * otpgu_base is similar to rev23 + */ + if (oi->wsize >= 128) + oi->otpgu_base = REVB8_OTPGU_BASE; + else + oi->otpgu_base = REV36_OTPGU_BASE; + } else if (oi->sih->ccrev == 23 || oi->sih->ccrev >= 25) { + oi->otpgu_base = REVB8_OTPGU_BASE; + } + + /* First issue an init command so the status is up to date */ + otpp = + OTPP_START_BUSY | ((OTPPOC_INIT << OTPP_OC_SHIFT) & OTPP_OC_MASK); + + W_REG(&cc->otpprog, otpp); + for (k = 0; + ((st = R_REG(&cc->otpprog)) & OTPP_START_BUSY) + && (k < OTPP_TRIES); k++) + ; + if (k >= OTPP_TRIES) + return; + + /* Read OTP lock bits and subregion programmed indication bits */ + oi->status = R_REG(&cc->otpstatus); + + if ((oi->sih->chip == BCM43224_CHIP_ID) + || (oi->sih->chip == BCM43225_CHIP_ID)) { + u32 p_bits; + p_bits = + (ipxotp_otpr(oi, cc, oi->otpgu_base + OTPGU_P_OFF) & + OTPGU_P_MSK) + >> OTPGU_P_SHIFT; + oi->status |= (p_bits << OTPS_GUP_SHIFT); + } + + /* + * h/w region base and fuse region limit are fixed to + * the top and the bottom of the general use region. + * Everything else can be flexible. + */ + oi->hwbase = oi->otpgu_base + OTPGU_SROM_OFF; + oi->hwlim = oi->wsize; + if (oi->status & OTPS_GUP_HW) { + oi->hwlim = + ipxotp_otpr(oi, cc, oi->otpgu_base + OTPGU_HSB_OFF) / 16; + oi->swbase = oi->hwlim; + } else + oi->swbase = oi->hwbase; + + /* subtract fuse and checksum from beginning */ + oi->swlim = ipxotp_max_rgnsz(oi->sih, oi->wsize) / 2; + + if (oi->status & OTPS_GUP_SW) { + oi->swlim = + ipxotp_otpr(oi, cc, oi->otpgu_base + OTPGU_SFB_OFF) / 16; + oi->fbase = oi->swlim; + } else + oi->fbase = oi->swbase; + + oi->flim = oi->wsize; +} + +static int ipxotp_init(struct si_pub *sih, struct otpinfo *oi) +{ + uint idx; + struct chipcregs __iomem *cc; + + /* Make sure we're running IPX OTP */ + if (!OTPTYPE_IPX(sih->ccrev)) + return -EBADE; + + /* Make sure OTP is not disabled */ + if (ai_is_otp_disabled(sih)) + return -EBADE; + + /* Check for otp size */ + switch ((sih->cccaps & CC_CAP_OTPSIZE) >> CC_CAP_OTPSIZE_SHIFT) { + case 0: + /* Nothing there */ + return -EBADE; + case 1: /* 32x64 */ + oi->rows = 32; + oi->cols = 64; + oi->wsize = 128; + break; + case 2: /* 64x64 */ + oi->rows = 64; + oi->cols = 64; + oi->wsize = 256; + break; + case 5: /* 96x64 */ + oi->rows = 96; + oi->cols = 64; + oi->wsize = 384; + break; + case 7: /* 16x64 *//* 1024 bits */ + oi->rows = 16; + oi->cols = 64; + oi->wsize = 64; + break; + default: + /* Don't know the geometry */ + return -EBADE; + } + + /* Retrieve OTP region info */ + idx = ai_coreidx(sih); + cc = ai_setcoreidx(sih, SI_CC_IDX); + + _ipxotp_init(oi, cc); + + ai_setcoreidx(sih, idx); + + return 0; +} + +static int +ipxotp_read_region(struct otpinfo *oi, int region, u16 *data, uint *wlen) +{ + uint idx; + struct chipcregs __iomem *cc; + uint base, i, sz; + + /* Validate region selection */ + switch (region) { + case OTP_HW_RGN: + sz = (uint) oi->hwlim - oi->hwbase; + if (!(oi->status & OTPS_GUP_HW)) { + *wlen = sz; + return -ENODATA; + } + if (*wlen < sz) { + *wlen = sz; + return -EOVERFLOW; + } + base = oi->hwbase; + break; + case OTP_SW_RGN: + sz = ((uint) oi->swlim - oi->swbase); + if (!(oi->status & OTPS_GUP_SW)) { + *wlen = sz; + return -ENODATA; + } + if (*wlen < sz) { + *wlen = sz; + return -EOVERFLOW; + } + base = oi->swbase; + break; + case OTP_CI_RGN: + sz = OTPGU_CI_SZ; + if (!(oi->status & OTPS_GUP_CI)) { + *wlen = sz; + return -ENODATA; + } + if (*wlen < sz) { + *wlen = sz; + return -EOVERFLOW; + } + base = oi->otpgu_base + OTPGU_CI_OFF; + break; + case OTP_FUSE_RGN: + sz = (uint) oi->flim - oi->fbase; + if (!(oi->status & OTPS_GUP_FUSE)) { + *wlen = sz; + return -ENODATA; + } + if (*wlen < sz) { + *wlen = sz; + return -EOVERFLOW; + } + base = oi->fbase; + break; + case OTP_ALL_RGN: + sz = ((uint) oi->flim - oi->hwbase); + if (!(oi->status & (OTPS_GUP_HW | OTPS_GUP_SW))) { + *wlen = sz; + return -ENODATA; + } + if (*wlen < sz) { + *wlen = sz; + return -EOVERFLOW; + } + base = oi->hwbase; + break; + default: + return -EINVAL; + } + + idx = ai_coreidx(oi->sih); + cc = ai_setcoreidx(oi->sih, SI_CC_IDX); + + /* Read the data */ + for (i = 0; i < sz; i++) + data[i] = ipxotp_otpr(oi, cc, base + i); + + ai_setcoreidx(oi->sih, idx); + *wlen = sz; + return 0; +} + +static const struct otp_fn_s ipxotp_fn = { + (int (*)(struct si_pub *, struct otpinfo *)) ipxotp_init, + (int (*)(struct otpinfo *, int, u16 *, uint *)) ipxotp_read_region, +}; + +static int otp_init(struct si_pub *sih, struct otpinfo *oi) +{ + + int ret; + + memset(oi, 0, sizeof(struct otpinfo)); + + oi->ccrev = sih->ccrev; + + if (OTPTYPE_IPX(oi->ccrev)) + oi->fn = &ipxotp_fn; + + if (oi->fn == NULL) + return -EBADE; + + oi->sih = sih; + + ret = (oi->fn->init) (sih, oi); + + return ret; +} + +int +otp_read_region(struct si_pub *sih, int region, u16 *data, uint *wlen) { + struct otpinfo otpinfo; + struct otpinfo *oi = &otpinfo; + int err = 0; + + if (ai_is_otp_disabled(sih)) { + err = -EPERM; + goto out; + } + + err = otp_init(sih, oi); + if (err) + goto out; + + err = ((oi)->fn->read_region)(oi, region, data, wlen); + + out: + return err; +} |