Commit e63efbca authored by Hector Martin's avatar Hector Martin Committed by Kalle Valo

wifi: brcmfmac: pcie: Read Apple OTP information

On Apple platforms, the One Time Programmable ROM in the Broadcom chips
contains information about the specific board design (module, vendor,
version) that is required to select the correct NVRAM file. Parse this
OTP ROM and extract the required strings.

Note that the user OTP offset/size is per-chip. This patch does not add
any chips yet.
Reviewed-by: default avatarArend van Spriel <arend.vanspriel@broadcom.com>
Signed-off-by: default avatarHector Martin <marcan@marcan.st>
Signed-off-by: default avatarRussell King (Oracle) <rmk+kernel@armlinux.org.uk>
Signed-off-by: default avatarKalle Valo <kvalo@kernel.org>
Link: https://lore.kernel.org/r/E1oZDni-0077aM-I6@rmk-PC.armlinux.org.uk
parent 7cb46e72
...@@ -256,6 +256,15 @@ struct brcmf_pcie_core_info { ...@@ -256,6 +256,15 @@ struct brcmf_pcie_core_info {
u32 wrapbase; u32 wrapbase;
}; };
#define BRCMF_OTP_MAX_PARAM_LEN 16
struct brcmf_otp_params {
char module[BRCMF_OTP_MAX_PARAM_LEN];
char vendor[BRCMF_OTP_MAX_PARAM_LEN];
char version[BRCMF_OTP_MAX_PARAM_LEN];
bool valid;
};
struct brcmf_pciedev_info { struct brcmf_pciedev_info {
enum brcmf_pcie_state state; enum brcmf_pcie_state state;
bool in_irq; bool in_irq;
...@@ -283,6 +292,7 @@ struct brcmf_pciedev_info { ...@@ -283,6 +292,7 @@ struct brcmf_pciedev_info {
void (*write_ptr)(struct brcmf_pciedev_info *devinfo, u32 mem_offset, void (*write_ptr)(struct brcmf_pciedev_info *devinfo, u32 mem_offset,
u16 value); u16 value);
struct brcmf_mp_device *settings; struct brcmf_mp_device *settings;
struct brcmf_otp_params otp;
}; };
struct brcmf_pcie_ringbuf { struct brcmf_pcie_ringbuf {
...@@ -354,6 +364,14 @@ static void brcmf_pcie_setup(struct device *dev, int ret, ...@@ -354,6 +364,14 @@ static void brcmf_pcie_setup(struct device *dev, int ret,
static struct brcmf_fw_request * static struct brcmf_fw_request *
brcmf_pcie_prepare_fw_request(struct brcmf_pciedev_info *devinfo); brcmf_pcie_prepare_fw_request(struct brcmf_pciedev_info *devinfo);
static u16
brcmf_pcie_read_reg16(struct brcmf_pciedev_info *devinfo, u32 reg_offset)
{
void __iomem *address = devinfo->regs + reg_offset;
return ioread16(address);
}
static u32 static u32
brcmf_pcie_read_reg32(struct brcmf_pciedev_info *devinfo, u32 reg_offset) brcmf_pcie_read_reg32(struct brcmf_pciedev_info *devinfo, u32 reg_offset)
{ {
...@@ -499,6 +517,8 @@ brcmf_pcie_copy_dev_tomem(struct brcmf_pciedev_info *devinfo, u32 mem_offset, ...@@ -499,6 +517,8 @@ brcmf_pcie_copy_dev_tomem(struct brcmf_pciedev_info *devinfo, u32 mem_offset,
} }
#define READCC32(devinfo, reg) brcmf_pcie_read_reg32(devinfo, \
CHIPCREGOFFS(reg))
#define WRITECC32(devinfo, reg, value) brcmf_pcie_write_reg32(devinfo, \ #define WRITECC32(devinfo, reg, value) brcmf_pcie_write_reg32(devinfo, \
CHIPCREGOFFS(reg), value) CHIPCREGOFFS(reg), value)
...@@ -1734,6 +1754,198 @@ static const struct brcmf_buscore_ops brcmf_pcie_buscore_ops = { ...@@ -1734,6 +1754,198 @@ static const struct brcmf_buscore_ops brcmf_pcie_buscore_ops = {
.write32 = brcmf_pcie_buscore_write32, .write32 = brcmf_pcie_buscore_write32,
}; };
#define BRCMF_OTP_SYS_VENDOR 0x15
#define BRCMF_OTP_BRCM_CIS 0x80
#define BRCMF_OTP_VENDOR_HDR 0x00000008
static int
brcmf_pcie_parse_otp_sys_vendor(struct brcmf_pciedev_info *devinfo,
u8 *data, size_t size)
{
int idx = 4;
const char *chip_params;
const char *board_params;
const char *p;
/* 4-byte header and two empty strings */
if (size < 6)
return -EINVAL;
if (get_unaligned_le32(data) != BRCMF_OTP_VENDOR_HDR)
return -EINVAL;
chip_params = &data[idx];
/* Skip first string, including terminator */
idx += strnlen(chip_params, size - idx) + 1;
if (idx >= size)
return -EINVAL;
board_params = &data[idx];
/* Skip to terminator of second string */
idx += strnlen(board_params, size - idx);
if (idx >= size)
return -EINVAL;
/* At this point both strings are guaranteed NUL-terminated */
brcmf_dbg(PCIE, "OTP: chip_params='%s' board_params='%s'\n",
chip_params, board_params);
p = skip_spaces(board_params);
while (*p) {
char tag = *p++;
const char *end;
size_t len;
if (*p++ != '=') /* implicit NUL check */
return -EINVAL;
/* *p might be NUL here, if so end == p and len == 0 */
end = strchrnul(p, ' ');
len = end - p;
/* leave 1 byte for NUL in destination string */
if (len > (BRCMF_OTP_MAX_PARAM_LEN - 1))
return -EINVAL;
/* Copy len characters plus a NUL terminator */
switch (tag) {
case 'M':
strscpy(devinfo->otp.module, p, len + 1);
break;
case 'V':
strscpy(devinfo->otp.vendor, p, len + 1);
break;
case 'm':
strscpy(devinfo->otp.version, p, len + 1);
break;
}
/* Skip to next arg, if any */
p = skip_spaces(end);
}
brcmf_dbg(PCIE, "OTP: module=%s vendor=%s version=%s\n",
devinfo->otp.module, devinfo->otp.vendor,
devinfo->otp.version);
if (!devinfo->otp.module[0] ||
!devinfo->otp.vendor[0] ||
!devinfo->otp.version[0])
return -EINVAL;
devinfo->otp.valid = true;
return 0;
}
static int
brcmf_pcie_parse_otp(struct brcmf_pciedev_info *devinfo, u8 *otp, size_t size)
{
int p = 0;
int ret = -EINVAL;
brcmf_dbg(PCIE, "parse_otp size=%zd\n", size);
while (p < (size - 1)) {
u8 type = otp[p];
u8 length = otp[p + 1];
if (type == 0)
break;
if ((p + 2 + length) > size)
break;
switch (type) {
case BRCMF_OTP_SYS_VENDOR:
brcmf_dbg(PCIE, "OTP @ 0x%x (%d): SYS_VENDOR\n",
p, length);
ret = brcmf_pcie_parse_otp_sys_vendor(devinfo,
&otp[p + 2],
length);
break;
case BRCMF_OTP_BRCM_CIS:
brcmf_dbg(PCIE, "OTP @ 0x%x (%d): BRCM_CIS\n",
p, length);
break;
default:
brcmf_dbg(PCIE, "OTP @ 0x%x (%d): Unknown type 0x%x\n",
p, length, type);
break;
}
p += 2 + length;
}
return ret;
}
static int brcmf_pcie_read_otp(struct brcmf_pciedev_info *devinfo)
{
const struct pci_dev *pdev = devinfo->pdev;
struct brcmf_bus *bus = dev_get_drvdata(&pdev->dev);
u32 coreid, base, words, idx, sromctl;
u16 *otp;
struct brcmf_core *core;
int ret;
switch (devinfo->ci->chip) {
default:
/* OTP not supported on this chip */
return 0;
}
core = brcmf_chip_get_core(devinfo->ci, coreid);
if (!core) {
brcmf_err(bus, "No OTP core\n");
return -ENODEV;
}
if (coreid == BCMA_CORE_CHIPCOMMON) {
/* Chips with OTP accessed via ChipCommon need additional
* handling to access the OTP
*/
brcmf_pcie_select_core(devinfo, coreid);
sromctl = READCC32(devinfo, sromcontrol);
if (!(sromctl & BCMA_CC_SROM_CONTROL_OTP_PRESENT)) {
/* Chip lacks OTP, try without it... */
brcmf_err(bus,
"OTP unavailable, using default firmware\n");
return 0;
}
/* Map OTP to shadow area */
WRITECC32(devinfo, sromcontrol,
sromctl | BCMA_CC_SROM_CONTROL_OTPSEL);
}
otp = kcalloc(words, sizeof(u16), GFP_KERNEL);
if (!otp)
return -ENOMEM;
/* Map bus window to SROM/OTP shadow area in core */
base = brcmf_pcie_buscore_prep_addr(devinfo->pdev, base + core->base);
brcmf_dbg(PCIE, "OTP data:\n");
for (idx = 0; idx < words; idx++) {
otp[idx] = brcmf_pcie_read_reg16(devinfo, base + 2 * idx);
brcmf_dbg(PCIE, "[%8x] 0x%04x\n", base + 2 * idx, otp[idx]);
}
if (coreid == BCMA_CORE_CHIPCOMMON) {
brcmf_pcie_select_core(devinfo, coreid);
WRITECC32(devinfo, sromcontrol, sromctl);
}
ret = brcmf_pcie_parse_otp(devinfo, (u8 *)otp, 2 * words);
kfree(otp);
return ret;
}
#define BRCMF_PCIE_FW_CODE 0 #define BRCMF_PCIE_FW_CODE 0
#define BRCMF_PCIE_FW_NVRAM 1 #define BRCMF_PCIE_FW_NVRAM 1
#define BRCMF_PCIE_FW_CLM 2 #define BRCMF_PCIE_FW_CLM 2
...@@ -1930,6 +2142,12 @@ brcmf_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id) ...@@ -1930,6 +2142,12 @@ brcmf_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id)
if (ret) if (ret)
goto fail_bus; goto fail_bus;
ret = brcmf_pcie_read_otp(devinfo);
if (ret) {
brcmf_err(bus, "failed to parse OTP\n");
goto fail_brcmf;
}
fwreq = brcmf_pcie_prepare_fw_request(devinfo); fwreq = brcmf_pcie_prepare_fw_request(devinfo);
if (!fwreq) { if (!fwreq) {
ret = -ENOMEM; ret = -ENOMEM;
......
...@@ -271,6 +271,7 @@ ...@@ -271,6 +271,7 @@
#define BCMA_CC_SROM_CONTROL_OP_WRDIS 0x40000000 #define BCMA_CC_SROM_CONTROL_OP_WRDIS 0x40000000
#define BCMA_CC_SROM_CONTROL_OP_WREN 0x60000000 #define BCMA_CC_SROM_CONTROL_OP_WREN 0x60000000
#define BCMA_CC_SROM_CONTROL_OTPSEL 0x00000010 #define BCMA_CC_SROM_CONTROL_OTPSEL 0x00000010
#define BCMA_CC_SROM_CONTROL_OTP_PRESENT 0x00000020
#define BCMA_CC_SROM_CONTROL_LOCK 0x00000008 #define BCMA_CC_SROM_CONTROL_LOCK 0x00000008
#define BCMA_CC_SROM_CONTROL_SIZE_MASK 0x00000006 #define BCMA_CC_SROM_CONTROL_SIZE_MASK 0x00000006
#define BCMA_CC_SROM_CONTROL_SIZE_1K 0x00000000 #define BCMA_CC_SROM_CONTROL_SIZE_1K 0x00000000
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment