linux-imx/drivers/nvmem/imx-ocotp-fsb-s400.c
Pankaj Gupta be13b6c87d drivers: nvmem: imx: ports ocotp driver to updated se kernel driver.
ports ocotp driver to updated se kernel driver.

Signed-off-by: Pankaj Gupta <pankaj.gupta@nxp.com>
Acked-by: Jason Liu <jason.hui.liu@nxp.com>
2025-05-28 10:40:30 +08:00

487 lines
12 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2021 NXP
*/
#include <linux/of_address.h>
#include <linux/dev_printk.h>
#include <linux/errno.h>
#include <linux/firmware/imx/se_api.h>
#include <linux/io.h>
#include <linux/etherdevice.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/nvmem-consumer.h>
#include <linux/nvmem-provider.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#define LOCK_CFG 0x01
#define ECID 0x02
#define UNIQ_ID 0x07
#define OTFAD_CFG 0x17
#define MAPPING_SIZE 0x20
#define FUSE_ACC_DIS 0x28
enum soc_type {
IMX8ULP,
IMX93,
IMX95,
};
struct bank_2_reg {
unsigned int bank;
unsigned int reg;
bool flag;
};
struct imx_fsb_s400_hw {
enum soc_type soc;
unsigned int fsb_otp_shadow;
const u16 se_soc;
const u8 se_if_idx;
const struct bank_2_reg fsb_bank_reg[MAPPING_SIZE];
bool oscca_fuse_read;
bool reverse_mac_address;
bool increase_mac_address;
const u8 *pf_mac_offset_list;
};
struct imx_fsb_s400_fuse {
void __iomem *regs;
struct nvmem_config config;
struct mutex lock;
struct device *se_dev;
const struct imx_fsb_s400_hw *hw;
bool fsb_read_dis;
u8 pfn;
};
static int read_words_via_s400_api(u32 *buf, unsigned int fuse_base,
unsigned int num, struct device *se_dev)
{
unsigned int i;
int err = 0;
for (i = 0; i < num; i++)
err = imx_se_read_fuse(se_dev, fuse_base + i, buf + i);
return err;
}
static int read_words_via_fsb(void *priv, unsigned int bank, u32 *buf)
{
struct imx_fsb_s400_fuse *fuse = priv;
void __iomem *regs = fuse->regs + fuse->hw->fsb_otp_shadow;
unsigned int i;
unsigned int reg_id = UINT_MAX;
unsigned int size = ARRAY_SIZE(fuse->hw->fsb_bank_reg);
for (i = 0; i < size; i++) {
if (fuse->hw->fsb_bank_reg[i].bank == bank) {
reg_id = fuse->hw->fsb_bank_reg[i].reg;
break;
}
}
if (reg_id != UINT_MAX) {
size = fuse->hw->fsb_bank_reg[i].flag ? 4 : 8;
for (i = 0; i < size; i++) {
*buf = readl_relaxed(regs + (reg_id + i) * 4);
buf = buf + 1;
}
}
return 0;
}
static int read_nwords_via_fsb(void __iomem *regs, u32 *buf, u32 fuse_base, u32 num)
{
unsigned int i;
for (i = 0; i < num; i++) {
*buf = readl_relaxed(regs + (fuse_base + i) * 4);
buf = buf + 1;
}
return 0;
}
static int fsb_s400_fuse_read(void *priv, unsigned int offset, void *val,
size_t bytes)
{
struct imx_fsb_s400_fuse *fuse = priv;
void __iomem *regs = fuse->regs + fuse->hw->fsb_otp_shadow;
unsigned int num_bytes, bank;
u32 *buf;
int err, i;
num_bytes = round_up(fuse->config.size, 4);
buf = kzalloc(num_bytes, GFP_KERNEL);
if (!buf)
return -ENOMEM;
err = -EINVAL;
mutex_lock(&fuse->lock);
if (fuse->hw->soc == IMX8ULP) {
for (bank = 0; bank < 63; bank++) {
switch (bank) {
case 0:
break;
case LOCK_CFG:
err = read_words_via_s400_api(&buf[8], 8, 8,
fuse->se_dev);
if (err)
goto ret;
break;
case ECID:
err = read_words_via_s400_api(&buf[16], 16, 8,
fuse->se_dev);
if (err)
goto ret;
break;
case UNIQ_ID:
err = imx_se_read_fuse(fuse->se_dev,
OTP_UNIQ_ID,
&buf[56]);
if (err)
goto ret;
break;
case OTFAD_CFG:
err = imx_se_read_fuse(fuse->se_dev,
OTFAD_CONFIG, &buf[184]);
if (err)
goto ret;
break;
case 25:
case 26:
case 27:
err = read_words_via_s400_api(&buf[200], 200, 24,
fuse->se_dev);
if (err)
goto ret;
break;
case 32:
case 33:
case 34:
case 35:
case 36:
err = read_words_via_s400_api(&buf[256], 256, 40,
fuse->se_dev);
if (err)
goto ret;
break;
case 49:
case 50:
case 51:
err = read_words_via_s400_api(&buf[392], 392, 24,
fuse->se_dev);
if (err)
goto ret;
break;
default:
err = read_words_via_fsb(priv, bank, &buf[bank * 8]);
break;
}
}
} else if (fuse->hw->soc == IMX93) {
for (bank = 0; bank < 6; bank++) {
if (fuse->fsb_read_dis)
read_words_via_s400_api(&buf[bank * 8], bank * 8,
8, fuse->se_dev);
else
read_nwords_via_fsb(regs, &buf[bank * 8], bank * 8, 8);
}
if (fuse->fsb_read_dis)
read_words_via_s400_api(&buf[48], 48, 4, fuse->se_dev);
else
read_nwords_via_fsb(regs, &buf[48], 48, 4); /* OTP_UNIQ_ID */
err = read_words_via_s400_api(&buf[63], 63, 1, fuse->se_dev);
if (err)
goto ret;
err = read_words_via_s400_api(&buf[128], 128, 16, fuse->se_dev);
if (err)
goto ret;
err = read_words_via_s400_api(&buf[182], 182, 1, fuse->se_dev);
if (err)
goto ret;
err = read_words_via_s400_api(&buf[188], 188, 1, fuse->se_dev);
if (err)
goto ret;
for (bank = 39; bank < 64; bank++) {
if (fuse->fsb_read_dis)
read_words_via_s400_api(&buf[bank * 8], bank * 8,
8, fuse->se_dev);
else
read_nwords_via_fsb(regs, &buf[bank * 8], bank * 8, 8);
}
} else if (fuse->hw->soc == IMX95) {
buf[0] = readl_relaxed(regs + 0 * 4) & 0xffff;
buf[7] = readl_relaxed(regs + 7 * 4) & 0xffff;
buf[9] = readl_relaxed(regs + 9 * 4) & 0xffff;
buf[10] = readl_relaxed(regs + 10 * 4) & 0xffff;
buf[11] = readl_relaxed(regs + 11 * 4) & 0xffff;
for (i = 12; i < 36; i++)
buf[i] = readl_relaxed(regs + i * 4);
buf[36] = readl_relaxed(regs + 36 * 4) & 0xffff;
buf[37] = readl_relaxed(regs + 37 * 4) & 0xffff;
for (i = 38; i < 52; i++)
buf[i] = readl_relaxed(regs + i * 4);
buf[317] = readl_relaxed(regs + 317 * 4) & 0xffff;
buf[318] = readl_relaxed(regs + 318 * 4) & 0xffff;
for (i = 320; i < 327; i++)
buf[i] = readl_relaxed(regs + i * 4);
for (i = 328; i < 392; i++)
buf[i] = readl_relaxed(regs + i * 4);
for (i = 448; i < 591; i++)
buf[i] = readl_relaxed(regs + i * 4);
buf[591] = readl_relaxed(regs + 591 * 4) & 0xffff;
for (i = 592; i < 608; i++)
buf[i] = readl_relaxed(regs + i * 4);
read_words_via_s400_api(&buf[128], 128, 16, fuse->se_dev);
read_words_via_s400_api(&buf[188], 188, 1, fuse->se_dev);
err = 0;
fuse->pfn = offset >> 12 & 0xf;
offset = offset & 0xfff;
}
memcpy(val, (u8 *)(buf) + offset, bytes);
ret:
kfree(buf);
mutex_unlock(&fuse->lock);
return err;
}
static int fsb_s400_fuse_post_process(void *priv, const char *id, int index,
unsigned int offset, void *data,
size_t bytes)
{
struct imx_fsb_s400_fuse *fuse = priv;
u8 *buf = data;
int i;
if (!fuse)
return -EINVAL;
/* Deal with some post processing of nvmem cell data */
if (id && !strcmp(id, "mac-address")) {
if (fuse->hw->reverse_mac_address) {
for (i = 0; i < bytes / 2; i++)
swap(buf[i], buf[bytes - i - 1]);
}
if (fuse->hw->increase_mac_address &&
fuse->hw->pf_mac_offset_list) {
if (fuse->pfn >= sizeof(fuse->hw->pf_mac_offset_list))
return -EINVAL;
eth_addr_add(buf,
fuse->hw->pf_mac_offset_list[fuse->pfn]);
}
}
return 0;
}
static int fsb_s400_fuse_write(void *priv, unsigned int offset, void *val, size_t bytes)
{
struct imx_fsb_s400_fuse *fuse = priv;
u32 *buf = val;
u32 index;
int ret;
/* allow only writing one complete OTP word at a time */
if (bytes != 4)
return -EINVAL;
/* divide the offset by the word size to get the word count */
index = offset / 4;
mutex_lock(&fuse->lock);
ret = imx_se_write_fuse(fuse->se_dev, index, *buf, false);
mutex_unlock(&fuse->lock);
return ret;
}
struct imx_fsb_s400_fuse *gfuse;
static void imx_fsb_s400_fuse_fixup_cell_info(struct nvmem_device *nvmem,
struct nvmem_layout *layout,
struct nvmem_cell_info *cell)
{
cell->priv = gfuse;
cell->read_post_process = fsb_s400_fuse_post_process;
}
static struct nvmem_layout imx_fsb_s400_fuse_layout = {
.fixup_cell_info = imx_fsb_s400_fuse_fixup_cell_info,
};
static int imx_fsb_s400_fuse_probe(struct platform_device *pdev)
{
struct imx_fsb_s400_fuse *fuse;
struct nvmem_device *nvmem;
struct device_node *np;
void __iomem *reg;
u32 v;
fuse = devm_kzalloc(&pdev->dev, sizeof(*fuse), GFP_KERNEL);
if (!fuse)
return -ENOMEM;
gfuse = fuse;
fuse->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(fuse->regs))
return PTR_ERR(fuse->regs);
fuse->config.dev = &pdev->dev;
fuse->config.name = "fsb_s400_fuse";
fuse->config.id = NVMEM_DEVID_AUTO;
fuse->config.owner = THIS_MODULE;
fuse->config.size = 2048; /* 64 Banks */
fuse->config.add_legacy_fixed_of_cells = true;
fuse->config.reg_read = fsb_s400_fuse_read;
if ((of_device_is_compatible(pdev->dev.of_node, "fsl,imx93-ocotp")) ||
(of_device_is_compatible(pdev->dev.of_node, "fsl,imx95-ocotp")))
fuse->config.reg_write = fsb_s400_fuse_write;
fuse->config.priv = fuse;
mutex_init(&fuse->lock);
fuse->hw = of_device_get_match_data(&pdev->dev);
if (of_device_is_compatible(pdev->dev.of_node, "fsl,imx95-ocotp"))
fuse->config.size = 2440; /* 610 words */
if (fuse->hw->reverse_mac_address || fuse->hw->increase_mac_address)
fuse->config.layout = &imx_fsb_s400_fuse_layout;
if (fuse->hw->oscca_fuse_read) {
np = of_find_compatible_node(NULL, NULL, "fsl,imx93-aonmix-ns-syscfg");
if (!np)
return -ENODEV;
reg = of_iomap(np, 0);
if (!reg)
return -ENOMEM;
v = readl_relaxed(reg + FUSE_ACC_DIS);
if (v & BIT(0))
fuse->fsb_read_dis = true;
else
fuse->fsb_read_dis = false;
} else {
fuse->fsb_read_dis = false;
}
nvmem = devm_nvmem_register(&pdev->dev, &fuse->config);
if (IS_ERR(nvmem)) {
dev_err(&pdev->dev, "failed to register fuse nvmem device\n");
return PTR_ERR(nvmem);
}
fuse->se_dev = imx_get_se_data_info(fuse->hw->se_soc, fuse->hw->se_if_idx);
if (IS_ERR_OR_NULL(fuse->se_dev)) {
dev_err(&pdev->dev, "failed to get the se-fw2 device\n");
return -EPROBE_DEFER;
}
dev_dbg(&pdev->dev, "fuse nvmem device registered successfully\n");
return 0;
}
static const u8 imx95_pf_mac_offset_list[] = { 0, 3, 6 };
static const struct imx_fsb_s400_hw imx8ulp_fsb_s400_hw = {
.soc = IMX8ULP,
.fsb_otp_shadow = 0x800,
.fsb_bank_reg = {
[0] = { 3, 0 },
[1] = { 4, 8 },
[2] = { 5, 64 },
[3] = { 6, 72 },
[4] = { 8, 80, true },
[5] = { 24, 84, true },
[6] = { 26, 88, true },
[7] = { 27, 92, true },
[8] = { 28, 96 },
[9] = { 29, 104 },
[10] = { 30, 112 },
[11] = { 31, 120 },
[12] = { 37, 128 },
[13] = { 38, 136 },
[14] = { 39, 144 },
[15] = { 40, 152 },
[16] = { 41, 160 },
[17] = { 42, 168 },
[18] = { 43, 176 },
[19] = { 44, 184 },
[20] = { 45, 192 },
[21] = { 46, 200 },
},
.oscca_fuse_read = false,
.reverse_mac_address = false,
.increase_mac_address = false,
.pf_mac_offset_list = NULL,
.se_soc = SOC_ID_OF_IMX8ULP,
.se_if_idx = 0,
};
static const struct imx_fsb_s400_hw imx93_fsb_s400_hw = {
.soc = IMX93,
.fsb_otp_shadow = 0x8000,
.oscca_fuse_read = true,
.reverse_mac_address = true,
.increase_mac_address = false,
.pf_mac_offset_list = NULL,
.se_soc = SOC_ID_OF_IMX93,
.se_if_idx = 0,
};
static const struct imx_fsb_s400_hw imx95_fsb_s400_hw = {
.soc = IMX95,
.fsb_otp_shadow = 0x8000,
.oscca_fuse_read = false,
.reverse_mac_address = false,
.increase_mac_address = true,
.pf_mac_offset_list = imx95_pf_mac_offset_list,
.se_soc = SOC_ID_OF_IMX95,
.se_if_idx = 0,
};
static const struct of_device_id imx_fsb_s400_fuse_match[] = {
{ .compatible = "fsl,imx8ulp-ocotp", .data = &imx8ulp_fsb_s400_hw, },
{ .compatible = "fsl,imx93-ocotp", .data = &imx93_fsb_s400_hw, },
{ .compatible = "fsl,imx95-ocotp", .data = &imx95_fsb_s400_hw, },
{},
};
static struct platform_driver imx_fsb_s400_fuse_driver = {
.driver = {
.name = "fsl-ocotp-fsb-s400",
.of_match_table = imx_fsb_s400_fuse_match,
},
.probe = imx_fsb_s400_fuse_probe,
};
MODULE_DEVICE_TABLE(of, imx_fsb_s400_fuse_match);
module_platform_driver(imx_fsb_s400_fuse_driver);
MODULE_AUTHOR("NXP");
MODULE_DESCRIPTION("i.MX FSB/S400-API ocotp fuse box driver");
MODULE_LICENSE("GPL v2");