// SPDX-License-Identifier: GPL-2.0+ /* * Copyright 2021 NXP */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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");