summaryrefslogtreecommitdiffstats
path: root/drivers/staging/brcm80211/util/bcmotp.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/staging/brcm80211/util/bcmotp.c')
-rw-r--r--drivers/staging/brcm80211/util/bcmotp.c965
1 files changed, 965 insertions, 0 deletions
diff --git a/drivers/staging/brcm80211/util/bcmotp.c b/drivers/staging/brcm80211/util/bcmotp.c
new file mode 100644
index 0000000..c909832
--- /dev/null
+++ b/drivers/staging/brcm80211/util/bcmotp.c
@@ -0,0 +1,965 @@
+/*
+ * 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 <bcmdefs.h>
+#include <osl.h>
+#include <linuxver.h>
+#include <bcmdevs.h>
+#include <bcmutils.h>
+#include <siutils.h>
+#include <bcmendian.h>
+#include <hndsoc.h>
+#include <sbchipc.h>
+#include <bcmotp.h>
+#include "siutils_priv.h"
+
+/*
+ * There are two different OTP controllers so far:
+ * 1. new IPX OTP controller: chipc 21, >=23
+ * 2. older HND OTP controller: chipc 12, 17, 22
+ *
+ * Define BCMHNDOTP to include support for the HND OTP controller.
+ * Define BCMIPXOTP to include support for the IPX OTP controller.
+ *
+ * NOTE 1: More than one may be defined
+ * NOTE 2: If none are defined, the default is to include them all.
+ */
+
+#if !defined(BCMHNDOTP) && !defined(BCMIPXOTP)
+#define BCMHNDOTP 1
+#define BCMIPXOTP 1
+#endif
+
+#define OTPTYPE_HND(ccrev) ((ccrev) < 21 || (ccrev) == 22)
+#define OTPTYPE_IPX(ccrev) ((ccrev) == 21 || (ccrev) >= 23)
+
+#define OTPP_TRIES 10000000 /* # of tries for OTPP */
+
+#ifdef BCMIPXOTP
+#define MAXNUMRDES 9 /* Maximum OTP redundancy entries */
+#endif
+
+/* OTP common function type */
+typedef int (*otp_status_t) (void *oh);
+typedef int (*otp_size_t) (void *oh);
+typedef void *(*otp_init_t) (si_t *sih);
+typedef u16(*otp_read_bit_t) (void *oh, chipcregs_t *cc, uint off);
+typedef int (*otp_read_region_t) (si_t *sih, int region, u16 *data,
+ uint *wlen);
+typedef int (*otp_nvread_t) (void *oh, char *data, uint *len);
+
+/* OTP function struct */
+typedef struct otp_fn_s {
+ otp_size_t size;
+ otp_read_bit_t read_bit;
+ otp_init_t init;
+ otp_read_region_t read_region;
+ otp_nvread_t nvread;
+ otp_status_t status;
+} otp_fn_t;
+
+typedef struct {
+ uint ccrev; /* chipc revision */
+ otp_fn_t *fn; /* OTP functions */
+ si_t *sih; /* Saved sb handle */
+ osl_t *osh;
+
+#ifdef BCMIPXOTP
+ /* 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 */
+#endif /* BCMIPXOTP */
+
+#ifdef BCMHNDOTP
+ /* HND OTP section */
+ uint size; /* Size of otp in bytes */
+ uint hwprot; /* Hardware protection bits */
+ uint signvalid; /* Signature valid bits */
+ int boundary; /* hw/sw boundary */
+#endif /* BCMHNDOTP */
+} otpinfo_t;
+
+static otpinfo_t otpinfo;
+
+/*
+ * IPX OTP Code
+ *
+ * Exported functions:
+ * ipxotp_status()
+ * ipxotp_size()
+ * ipxotp_init()
+ * ipxotp_read_bit()
+ * ipxotp_read_region()
+ * ipxotp_nvread()
+ *
+ */
+
+#ifdef BCMIPXOTP
+
+#define HWSW_RGN(rgn) (((rgn) == OTP_HW_RGN) ? "h/w" : "s/w")
+
+/* 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 int ipxotp_status(void *oh)
+{
+ otpinfo_t *oi = (otpinfo_t *) oh;
+ return (int)(oi->status);
+}
+
+/* Return size in bytes */
+static int ipxotp_size(void *oh)
+{
+ otpinfo_t *oi = (otpinfo_t *) oh;
+ return (int)oi->wsize * 2;
+}
+
+static u16 ipxotp_otpr(void *oh, chipcregs_t *cc, uint wn)
+{
+ otpinfo_t *oi;
+
+ oi = (otpinfo_t *) oh;
+
+ ASSERT(wn < oi->wsize);
+ ASSERT(cc != NULL);
+
+ return R_REG(oi->osh, &cc->sromotp[wn]);
+}
+
+static u16 ipxotp_read_bit(void *oh, chipcregs_t *cc, uint off)
+{
+ otpinfo_t *oi = (otpinfo_t *) oh;
+ uint k, row, col;
+ u32 otpp, st;
+
+ row = off / oi->cols;
+ col = off % oi->cols;
+
+ otpp = OTPP_START_BUSY |
+ ((OTPPOC_READ << OTPP_OC_SHIFT) & OTPP_OC_MASK) |
+ ((row << OTPP_ROW_SHIFT) & OTPP_ROW_MASK) |
+ ((col << OTPP_COL_SHIFT) & OTPP_COL_MASK);
+ W_REG(oi->osh, &cc->otpprog, otpp);
+
+ for (k = 0;
+ ((st = R_REG(oi->osh, &cc->otpprog)) & OTPP_START_BUSY)
+ && (k < OTPP_TRIES); k++)
+ ;
+ if (k >= OTPP_TRIES) {
+ return 0xffff;
+ }
+ if (st & OTPP_READERR) {
+ return 0xffff;
+ }
+ st = (st & OTPP_VALUE_MASK) >> OTPP_VALUE_SHIFT;
+
+ return (int)st;
+}
+
+/* Calculate max HW/SW region byte size by substracting fuse region and checksum size,
+ * osizew is oi->wsize (OTP size - GU size) in words
+ */
+static int ipxotp_max_rgnsz(si_t *sih, int osizew)
+{
+ int ret = 0;
+
+ switch (CHIPID(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:
+ ASSERT(0); /* Don't konw about this chip */
+ }
+
+ return ret;
+}
+
+static void _ipxotp_init(otpinfo_t *oi, chipcregs_t *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(oi->osh, &cc->otpprog, otpp);
+ for (k = 0;
+ ((st = R_REG(oi->osh, &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(oi->osh, &cc->otpstatus);
+
+ if ((CHIPID(oi->sih->chip) == BCM43224_CHIP_ID)
+ || (CHIPID(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 void *ipxotp_init(si_t *sih)
+{
+ uint idx;
+ chipcregs_t *cc;
+ otpinfo_t *oi;
+
+ /* Make sure we're running IPX OTP */
+ ASSERT(OTPTYPE_IPX(sih->ccrev));
+ if (!OTPTYPE_IPX(sih->ccrev))
+ return NULL;
+
+ /* Make sure OTP is not disabled */
+ if (si_is_otp_disabled(sih)) {
+ return NULL;
+ }
+
+ /* Make sure OTP is powered up */
+ if (!si_is_otp_powered(sih)) {
+ return NULL;
+ }
+
+ oi = &otpinfo;
+
+ /* Check for otp size */
+ switch ((sih->cccaps & CC_CAP_OTPSIZE) >> CC_CAP_OTPSIZE_SHIFT) {
+ case 0:
+ /* Nothing there */
+ return NULL;
+ 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 NULL;
+ }
+
+ /* Retrieve OTP region info */
+ idx = si_coreidx(sih);
+ cc = si_setcoreidx(sih, SI_CC_IDX);
+ ASSERT(cc != NULL);
+
+ _ipxotp_init(oi, cc);
+
+ si_setcoreidx(sih, idx);
+
+ return (void *)oi;
+}
+
+static int ipxotp_read_region(void *oh, int region, u16 *data, uint *wlen)
+{
+ otpinfo_t *oi = (otpinfo_t *) oh;
+ uint idx;
+ chipcregs_t *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 BCME_NOTFOUND;
+ }
+ if (*wlen < sz) {
+ *wlen = sz;
+ return BCME_BUFTOOSHORT;
+ }
+ base = oi->hwbase;
+ break;
+ case OTP_SW_RGN:
+ sz = ((uint) oi->swlim - oi->swbase);
+ if (!(oi->status & OTPS_GUP_SW)) {
+ *wlen = sz;
+ return BCME_NOTFOUND;
+ }
+ if (*wlen < sz) {
+ *wlen = sz;
+ return BCME_BUFTOOSHORT;
+ }
+ base = oi->swbase;
+ break;
+ case OTP_CI_RGN:
+ sz = OTPGU_CI_SZ;
+ if (!(oi->status & OTPS_GUP_CI)) {
+ *wlen = sz;
+ return BCME_NOTFOUND;
+ }
+ if (*wlen < sz) {
+ *wlen = sz;
+ return BCME_BUFTOOSHORT;
+ }
+ 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 BCME_NOTFOUND;
+ }
+ if (*wlen < sz) {
+ *wlen = sz;
+ return BCME_BUFTOOSHORT;
+ }
+ 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 BCME_NOTFOUND;
+ }
+ if (*wlen < sz) {
+ *wlen = sz;
+ return BCME_BUFTOOSHORT;
+ }
+ base = oi->hwbase;
+ break;
+ default:
+ return BCME_BADARG;
+ }
+
+ idx = si_coreidx(oi->sih);
+ cc = si_setcoreidx(oi->sih, SI_CC_IDX);
+ ASSERT(cc != NULL);
+
+ /* Read the data */
+ for (i = 0; i < sz; i++)
+ data[i] = ipxotp_otpr(oh, cc, base + i);
+
+ si_setcoreidx(oi->sih, idx);
+ *wlen = sz;
+ return 0;
+}
+
+static int ipxotp_nvread(void *oh, char *data, uint *len)
+{
+ return BCME_UNSUPPORTED;
+}
+
+static otp_fn_t ipxotp_fn = {
+ (otp_size_t) ipxotp_size,
+ (otp_read_bit_t) ipxotp_read_bit,
+
+ (otp_init_t) ipxotp_init,
+ (otp_read_region_t) ipxotp_read_region,
+ (otp_nvread_t) ipxotp_nvread,
+
+ (otp_status_t) ipxotp_status
+};
+
+#endif /* BCMIPXOTP */
+
+/*
+ * HND OTP Code
+ *
+ * Exported functions:
+ * hndotp_status()
+ * hndotp_size()
+ * hndotp_init()
+ * hndotp_read_bit()
+ * hndotp_read_region()
+ * hndotp_nvread()
+ *
+ */
+
+#ifdef BCMHNDOTP
+
+/* Fields in otpstatus */
+#define OTPS_PROGFAIL 0x80000000
+#define OTPS_PROTECT 0x00000007
+#define OTPS_HW_PROTECT 0x00000001
+#define OTPS_SW_PROTECT 0x00000002
+#define OTPS_CID_PROTECT 0x00000004
+#define OTPS_RCEV_MSK 0x00003f00
+#define OTPS_RCEV_SHIFT 8
+
+/* Fields in the otpcontrol register */
+#define OTPC_RECWAIT 0xff000000
+#define OTPC_PROGWAIT 0x00ffff00
+#define OTPC_PRW_SHIFT 8
+#define OTPC_MAXFAIL 0x00000038
+#define OTPC_VSEL 0x00000006
+#define OTPC_SELVL 0x00000001
+
+/* OTP regions (Word offsets from otp size) */
+#define OTP_SWLIM_OFF (-4)
+#define OTP_CIDBASE_OFF 0
+#define OTP_CIDLIM_OFF 4
+
+/* Predefined OTP words (Word offset from otp size) */
+#define OTP_BOUNDARY_OFF (-4)
+#define OTP_HWSIGN_OFF (-3)
+#define OTP_SWSIGN_OFF (-2)
+#define OTP_CIDSIGN_OFF (-1)
+#define OTP_CID_OFF 0
+#define OTP_PKG_OFF 1
+#define OTP_FID_OFF 2
+#define OTP_RSV_OFF 3
+#define OTP_LIM_OFF 4
+#define OTP_RD_OFF 4 /* Redundancy row starts here */
+#define OTP_RC0_OFF 28 /* Redundancy control word 1 */
+#define OTP_RC1_OFF 32 /* Redundancy control word 2 */
+#define OTP_RC_LIM_OFF 36 /* Redundancy control word end */
+
+#define OTP_HW_REGION OTPS_HW_PROTECT
+#define OTP_SW_REGION OTPS_SW_PROTECT
+#define OTP_CID_REGION OTPS_CID_PROTECT
+
+#if OTP_HW_REGION != OTP_HW_RGN
+#error "incompatible OTP_HW_RGN"
+#endif
+#if OTP_SW_REGION != OTP_SW_RGN
+#error "incompatible OTP_SW_RGN"
+#endif
+#if OTP_CID_REGION != OTP_CI_RGN
+#error "incompatible OTP_CI_RGN"
+#endif
+
+/* Redundancy entry definitions */
+#define OTP_RCE_ROW_SZ 6
+#define OTP_RCE_SIGN_MASK 0x7fff
+#define OTP_RCE_ROW_MASK 0x3f
+#define OTP_RCE_BITS 21
+#define OTP_RCE_SIGN_SZ 15
+#define OTP_RCE_BIT0 1
+
+#define OTP_WPR 4
+#define OTP_SIGNATURE 0x578a
+#define OTP_MAGIC 0x4e56
+
+static int hndotp_status(void *oh)
+{
+ otpinfo_t *oi = (otpinfo_t *) oh;
+ return (int)(oi->hwprot | oi->signvalid);
+}
+
+static int hndotp_size(void *oh)
+{
+ otpinfo_t *oi = (otpinfo_t *) oh;
+ return (int)(oi->size);
+}
+
+static u16 hndotp_otpr(void *oh, chipcregs_t *cc, uint wn)
+{
+ otpinfo_t *oi = (otpinfo_t *) oh;
+ osl_t *osh;
+ volatile u16 *ptr;
+
+ ASSERT(wn < ((oi->size / 2) + OTP_RC_LIM_OFF));
+ ASSERT(cc != NULL);
+
+ osh = si_osh(oi->sih);
+
+ ptr = (volatile u16 *)((volatile char *)cc + CC_SROM_OTP);
+ return R_REG(osh, &ptr[wn]);
+}
+
+static u16 hndotp_otproff(void *oh, chipcregs_t *cc, int woff)
+{
+ otpinfo_t *oi = (otpinfo_t *) oh;
+ osl_t *osh;
+ volatile u16 *ptr;
+
+ ASSERT(woff >= (-((int)oi->size / 2)));
+ ASSERT(woff < OTP_LIM_OFF);
+ ASSERT(cc != NULL);
+
+ osh = si_osh(oi->sih);
+
+ ptr = (volatile u16 *)((volatile char *)cc + CC_SROM_OTP);
+
+ return R_REG(osh, &ptr[(oi->size / 2) + woff]);
+}
+
+static u16 hndotp_read_bit(void *oh, chipcregs_t *cc, uint idx)
+{
+ otpinfo_t *oi = (otpinfo_t *) oh;
+ uint k, row, col;
+ u32 otpp, st;
+ osl_t *osh;
+
+ osh = si_osh(oi->sih);
+ row = idx / 65;
+ col = idx % 65;
+
+ otpp = OTPP_START_BUSY | OTPP_READ |
+ ((row << OTPP_ROW_SHIFT) & OTPP_ROW_MASK) | (col & OTPP_COL_MASK);
+
+ W_REG(osh, &cc->otpprog, otpp);
+ st = R_REG(osh, &cc->otpprog);
+ for (k = 0;
+ ((st & OTPP_START_BUSY) == OTPP_START_BUSY) && (k < OTPP_TRIES);
+ k++)
+ st = R_REG(osh, &cc->otpprog);
+
+ if (k >= OTPP_TRIES) {
+ return 0xffff;
+ }
+ if (st & OTPP_READERR) {
+ return 0xffff;
+ }
+ st = (st & OTPP_VALUE_MASK) >> OTPP_VALUE_SHIFT;
+ return (u16) st;
+}
+
+static void *hndotp_init(si_t *sih)
+{
+ uint idx;
+ chipcregs_t *cc;
+ otpinfo_t *oi;
+ u32 cap = 0, clkdiv, otpdiv = 0;
+ void *ret = NULL;
+ osl_t *osh;
+
+ oi = &otpinfo;
+
+ idx = si_coreidx(sih);
+ osh = si_osh(oi->sih);
+
+ /* Check for otp */
+ cc = si_setcoreidx(sih, SI_CC_IDX);
+ if (cc != NULL) {
+ cap = R_REG(osh, &cc->capabilities);
+ if ((cap & CC_CAP_OTPSIZE) == 0) {
+ /* Nothing there */
+ goto out;
+ }
+
+ /* As of right now, support only 4320a2, 4311a1 and 4312 */
+ ASSERT((oi->ccrev == 12) || (oi->ccrev == 17)
+ || (oi->ccrev == 22));
+ if (!
+ ((oi->ccrev == 12) || (oi->ccrev == 17)
+ || (oi->ccrev == 22)))
+ return NULL;
+
+ /* Read the OTP byte size. chipcommon rev >= 18 has RCE so the size is
+ * 8 row (64 bytes) smaller
+ */
+ oi->size =
+ 1 << (((cap & CC_CAP_OTPSIZE) >> CC_CAP_OTPSIZE_SHIFT)
+ + CC_CAP_OTPSIZE_BASE);
+ if (oi->ccrev >= 18)
+ oi->size -= ((OTP_RC0_OFF - OTP_BOUNDARY_OFF) * 2);
+
+ oi->hwprot = (int)(R_REG(osh, &cc->otpstatus) & OTPS_PROTECT);
+ oi->boundary = -1;
+
+ /* Check the region signature */
+ if (hndotp_otproff(oi, cc, OTP_HWSIGN_OFF) == OTP_SIGNATURE) {
+ oi->signvalid |= OTP_HW_REGION;
+ oi->boundary = hndotp_otproff(oi, cc, OTP_BOUNDARY_OFF);
+ }
+
+ if (hndotp_otproff(oi, cc, OTP_SWSIGN_OFF) == OTP_SIGNATURE)
+ oi->signvalid |= OTP_SW_REGION;
+
+ if (hndotp_otproff(oi, cc, OTP_CIDSIGN_OFF) == OTP_SIGNATURE)
+ oi->signvalid |= OTP_CID_REGION;
+
+ /* Set OTP clkdiv for stability */
+ if (oi->ccrev == 22)
+ otpdiv = 12;
+
+ if (otpdiv) {
+ clkdiv = R_REG(osh, &cc->clkdiv);
+ clkdiv =
+ (clkdiv & ~CLKD_OTP) | (otpdiv << CLKD_OTP_SHIFT);
+ W_REG(osh, &cc->clkdiv, clkdiv);
+ }
+ udelay(10);
+
+ ret = (void *)oi;
+ }
+
+ out: /* All done */
+ si_setcoreidx(sih, idx);
+
+ return ret;
+}
+
+static int hndotp_read_region(void *oh, int region, u16 *data, uint *wlen)
+{
+ otpinfo_t *oi = (otpinfo_t *) oh;
+ u32 idx, st;
+ chipcregs_t *cc;
+ int i;
+
+ /* Only support HW region (no active chips use HND OTP SW region) */
+ ASSERT(region == OTP_HW_REGION);
+
+ /* Region empty? */
+ st = oi->hwprot | oi->signvalid;
+ if ((st & region) == 0)
+ return BCME_NOTFOUND;
+
+ *wlen =
+ ((int)*wlen < oi->boundary / 2) ? *wlen : (uint) oi->boundary / 2;
+
+ idx = si_coreidx(oi->sih);
+ cc = si_setcoreidx(oi->sih, SI_CC_IDX);
+ ASSERT(cc != NULL);
+
+ for (i = 0; i < (int)*wlen; i++)
+ data[i] = hndotp_otpr(oh, cc, i);
+
+ si_setcoreidx(oi->sih, idx);
+
+ return 0;
+}
+
+static int hndotp_nvread(void *oh, char *data, uint *len)
+{
+ int rc = 0;
+ otpinfo_t *oi = (otpinfo_t *) oh;
+ u32 base, bound, lim = 0, st;
+ int i, chunk, gchunks, tsz = 0;
+ u32 idx;
+ chipcregs_t *cc;
+ uint offset;
+ u16 *rawotp = NULL;
+
+ /* save the orig core */
+ idx = si_coreidx(oi->sih);
+ cc = si_setcoreidx(oi->sih, SI_CC_IDX);
+ ASSERT(cc != NULL);
+
+ st = hndotp_status(oh);
+ if (!(st & (OTP_HW_REGION | OTP_SW_REGION))) {
+ rc = -1;
+ goto out;
+ }
+
+ /* Read the whole otp so we can easily manipulate it */
+ lim = hndotp_size(oh);
+ rawotp = kmalloc(lim, GFP_ATOMIC);
+ if (rawotp == NULL) {
+ rc = -2;
+ goto out;
+ }
+ for (i = 0; i < (int)(lim / 2); i++)
+ rawotp[i] = hndotp_otpr(oh, cc, i);
+
+ if ((st & OTP_HW_REGION) == 0) {
+ /* This could be a programming failure in the first
+ * chunk followed by one or more good chunks
+ */
+ for (i = 0; i < (int)(lim / 2); i++)
+ if (rawotp[i] == OTP_MAGIC)
+ break;
+
+ if (i < (int)(lim / 2)) {
+ base = i;
+ bound = (i * 2) + rawotp[i + 1];
+ } else {
+ rc = -3;
+ goto out;
+ }
+ } else {
+ bound = rawotp[(lim / 2) + OTP_BOUNDARY_OFF];
+
+ /* There are two cases: 1) The whole otp is used as nvram
+ * and 2) There is a hardware header followed by nvram.
+ */
+ if (rawotp[0] == OTP_MAGIC) {
+ base = 0;
+ } else
+ base = bound;
+ }
+
+ /* Find and copy the data */
+
+ chunk = 0;
+ gchunks = 0;
+ i = base / 2;
+ offset = 0;
+ while ((i < (int)(lim / 2)) && (rawotp[i] == OTP_MAGIC)) {
+ int dsz, rsz = rawotp[i + 1];
+
+ if (((i * 2) + rsz) >= (int)lim) {
+ /* Bad length, try to find another chunk anyway */
+ rsz = 6;
+ }
+ if (hndcrc16((u8 *) &rawotp[i], rsz,
+ CRC16_INIT_VALUE) == CRC16_GOOD_VALUE) {
+ /* Good crc, copy the vars */
+ gchunks++;
+ dsz = rsz - 6;
+ tsz += dsz;
+ if (offset + dsz >= *len) {
+ goto out;
+ }
+ bcopy((char *)&rawotp[i + 2], &data[offset], dsz);
+ offset += dsz;
+ /* Remove extra null characters at the end */
+ while (offset > 1 &&
+ data[offset - 1] == 0 && data[offset - 2] == 0)
+ offset--;
+ i += rsz / 2;
+ } else {
+ /* bad length or crc didn't check, try to find the next set */
+ if (rawotp[i + (rsz / 2)] == OTP_MAGIC) {
+ /* Assume length is good */
+ i += rsz / 2;
+ } else {
+ while (++i < (int)(lim / 2))
+ if (rawotp[i] == OTP_MAGIC)
+ break;
+ }
+ }
+ chunk++;
+ }
+
+ *len = offset;
+
+ out:
+ if (rawotp)
+ kfree(rawotp);
+ si_setcoreidx(oi->sih, idx);
+
+ return rc;
+}
+
+static otp_fn_t hndotp_fn = {
+ (otp_size_t) hndotp_size,
+ (otp_read_bit_t) hndotp_read_bit,
+
+ (otp_init_t) hndotp_init,
+ (otp_read_region_t) hndotp_read_region,
+ (otp_nvread_t) hndotp_nvread,
+
+ (otp_status_t) hndotp_status
+};
+
+#endif /* BCMHNDOTP */
+
+/*
+ * Common Code: Compiled for IPX / HND / AUTO
+ * otp_status()
+ * otp_size()
+ * otp_read_bit()
+ * otp_init()
+ * otp_read_region()
+ * otp_nvread()
+ */
+
+int otp_status(void *oh)
+{
+ otpinfo_t *oi = (otpinfo_t *) oh;
+
+ return oi->fn->status(oh);
+}
+
+int otp_size(void *oh)
+{
+ otpinfo_t *oi = (otpinfo_t *) oh;
+
+ return oi->fn->size(oh);
+}
+
+u16 otp_read_bit(void *oh, uint offset)
+{
+ otpinfo_t *oi = (otpinfo_t *) oh;
+ uint idx = si_coreidx(oi->sih);
+ chipcregs_t *cc = si_setcoreidx(oi->sih, SI_CC_IDX);
+ u16 readBit = (u16) oi->fn->read_bit(oh, cc, offset);
+ si_setcoreidx(oi->sih, idx);
+ return readBit;
+}
+
+void *otp_init(si_t *sih)
+{
+ otpinfo_t *oi;
+ void *ret = NULL;
+
+ oi = &otpinfo;
+ bzero(oi, sizeof(otpinfo_t));
+
+ oi->ccrev = sih->ccrev;
+
+#ifdef BCMIPXOTP
+ if (OTPTYPE_IPX(oi->ccrev))
+ oi->fn = &ipxotp_fn;
+#endif
+
+#ifdef BCMHNDOTP
+ if (OTPTYPE_HND(oi->ccrev))
+ oi->fn = &hndotp_fn;
+#endif
+
+ if (oi->fn == NULL) {
+ return NULL;
+ }
+
+ oi->sih = sih;
+ oi->osh = si_osh(oi->sih);
+
+ ret = (oi->fn->init) (sih);
+
+ return ret;
+}
+
+int
+otp_read_region(si_t *sih, int region, u16 *data,
+ uint *wlen) {
+ bool wasup = false;
+ void *oh;
+ int err = 0;
+
+ wasup = si_is_otp_powered(sih);
+ if (!wasup)
+ si_otp_power(sih, true);
+
+ if (!si_is_otp_powered(sih) || si_is_otp_disabled(sih)) {
+ err = BCME_NOTREADY;
+ goto out;
+ }
+
+ oh = otp_init(sih);
+ if (oh == NULL) {
+ err = BCME_ERROR;
+ goto out;
+ }
+
+ err = (((otpinfo_t *) oh)->fn->read_region) (oh, region, data, wlen);
+
+ out:
+ if (!wasup)
+ si_otp_power(sih, false);
+
+ return err;
+}
+
+int otp_nvread(void *oh, char *data, uint *len)
+{
+ otpinfo_t *oi = (otpinfo_t *) oh;
+
+ return oi->fn->nvread(oh, data, len);
+}
OpenPOWER on IntegriCloud