411 lines
9.8 KiB
C
411 lines
9.8 KiB
C
|
/*
|
||
|
* 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 {
|
||
|
struct bcma_device *core; /* chipc core */
|
||
|
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, uint wn)
|
||
|
{
|
||
|
return bcma_read16(oi->core,
|
||
|
CHIPCREGOFFS(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 (ai_get_chip_id(sih)) {
|
||
|
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)
|
||
|
{
|
||
|
uint k;
|
||
|
u32 otpp, st;
|
||
|
int ccrev = ai_get_ccrev(oi->sih);
|
||
|
|
||
|
|
||
|
/*
|
||
|
* record word offset of General Use Region
|
||
|
* for various chipcommon revs
|
||
|
*/
|
||
|
if (ccrev == 21 || ccrev == 24
|
||
|
|| ccrev == 27) {
|
||
|
oi->otpgu_base = REVA4_OTPGU_BASE;
|
||
|
} else if (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 (ccrev == 23 || 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);
|
||
|
|
||
|
bcma_write32(oi->core, CHIPCREGOFFS(otpprog), otpp);
|
||
|
st = bcma_read32(oi->core, CHIPCREGOFFS(otpprog));
|
||
|
for (k = 0; (st & OTPP_START_BUSY) && (k < OTPP_TRIES); k++)
|
||
|
st = bcma_read32(oi->core, CHIPCREGOFFS(otpprog));
|
||
|
if (k >= OTPP_TRIES)
|
||
|
return;
|
||
|
|
||
|
/* Read OTP lock bits and subregion programmed indication bits */
|
||
|
oi->status = bcma_read32(oi->core, CHIPCREGOFFS(otpstatus));
|
||
|
|
||
|
if ((ai_get_chip_id(oi->sih) == BCM43224_CHIP_ID)
|
||
|
|| (ai_get_chip_id(oi->sih) == BCM43225_CHIP_ID)) {
|
||
|
u32 p_bits;
|
||
|
p_bits = (ipxotp_otpr(oi, 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, 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, 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)
|
||
|
{
|
||
|
/* Make sure we're running IPX OTP */
|
||
|
if (!OTPTYPE_IPX(ai_get_ccrev(sih)))
|
||
|
return -EBADE;
|
||
|
|
||
|
/* Make sure OTP is not disabled */
|
||
|
if (ai_is_otp_disabled(sih))
|
||
|
return -EBADE;
|
||
|
|
||
|
/* Check for otp size */
|
||
|
switch ((ai_get_cccaps(sih) & 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 */
|
||
|
_ipxotp_init(oi);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
ipxotp_read_region(struct otpinfo *oi, int region, u16 *data, uint *wlen)
|
||
|
{
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
/* Read the data */
|
||
|
for (i = 0; i < sz; i++)
|
||
|
data[i] = ipxotp_otpr(oi, base + i);
|
||
|
|
||
|
*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->core = ai_findcore(sih, BCMA_CORE_CHIPCOMMON, 0);
|
||
|
|
||
|
if (OTPTYPE_IPX(ai_get_ccrev(sih)))
|
||
|
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;
|
||
|
}
|