/* * 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 #include #include #include #include #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; }