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