162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * i.MX9 OCOTP fusebox driver 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright 2023 NXP 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/device.h> 962306a36Sopenharmony_ci#include <linux/io.h> 1062306a36Sopenharmony_ci#include <linux/module.h> 1162306a36Sopenharmony_ci#include <linux/nvmem-provider.h> 1262306a36Sopenharmony_ci#include <linux/of.h> 1362306a36Sopenharmony_ci#include <linux/platform_device.h> 1462306a36Sopenharmony_ci#include <linux/slab.h> 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_cienum fuse_type { 1762306a36Sopenharmony_ci FUSE_FSB = 1, 1862306a36Sopenharmony_ci FUSE_ELE = 2, 1962306a36Sopenharmony_ci FUSE_INVALID = -1 2062306a36Sopenharmony_ci}; 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_cistruct ocotp_map_entry { 2362306a36Sopenharmony_ci u32 start; /* start word */ 2462306a36Sopenharmony_ci u32 num; /* num words */ 2562306a36Sopenharmony_ci enum fuse_type type; 2662306a36Sopenharmony_ci}; 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_cistruct ocotp_devtype_data { 2962306a36Sopenharmony_ci u32 reg_off; 3062306a36Sopenharmony_ci char *name; 3162306a36Sopenharmony_ci u32 size; 3262306a36Sopenharmony_ci u32 num_entry; 3362306a36Sopenharmony_ci u32 flag; 3462306a36Sopenharmony_ci nvmem_reg_read_t reg_read; 3562306a36Sopenharmony_ci struct ocotp_map_entry entry[]; 3662306a36Sopenharmony_ci}; 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_cistruct imx_ocotp_priv { 3962306a36Sopenharmony_ci struct device *dev; 4062306a36Sopenharmony_ci void __iomem *base; 4162306a36Sopenharmony_ci struct nvmem_config config; 4262306a36Sopenharmony_ci struct mutex lock; 4362306a36Sopenharmony_ci const struct ocotp_devtype_data *data; 4462306a36Sopenharmony_ci}; 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_cistatic enum fuse_type imx_ocotp_fuse_type(void *context, u32 index) 4762306a36Sopenharmony_ci{ 4862306a36Sopenharmony_ci struct imx_ocotp_priv *priv = context; 4962306a36Sopenharmony_ci const struct ocotp_devtype_data *data = priv->data; 5062306a36Sopenharmony_ci u32 start, end; 5162306a36Sopenharmony_ci int i; 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci for (i = 0; i < data->num_entry; i++) { 5462306a36Sopenharmony_ci start = data->entry[i].start; 5562306a36Sopenharmony_ci end = data->entry[i].start + data->entry[i].num; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci if (index >= start && index < end) 5862306a36Sopenharmony_ci return data->entry[i].type; 5962306a36Sopenharmony_ci } 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci return FUSE_INVALID; 6262306a36Sopenharmony_ci} 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_cistatic int imx_ocotp_reg_read(void *context, unsigned int offset, void *val, size_t bytes) 6562306a36Sopenharmony_ci{ 6662306a36Sopenharmony_ci struct imx_ocotp_priv *priv = context; 6762306a36Sopenharmony_ci void __iomem *reg = priv->base + priv->data->reg_off; 6862306a36Sopenharmony_ci u32 count, index, num_bytes; 6962306a36Sopenharmony_ci enum fuse_type type; 7062306a36Sopenharmony_ci u32 *buf; 7162306a36Sopenharmony_ci void *p; 7262306a36Sopenharmony_ci int i; 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci index = offset; 7562306a36Sopenharmony_ci num_bytes = round_up(bytes, 4); 7662306a36Sopenharmony_ci count = num_bytes >> 2; 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci if (count > ((priv->data->size >> 2) - index)) 7962306a36Sopenharmony_ci count = (priv->data->size >> 2) - index; 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci p = kzalloc(num_bytes, GFP_KERNEL); 8262306a36Sopenharmony_ci if (!p) 8362306a36Sopenharmony_ci return -ENOMEM; 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci mutex_lock(&priv->lock); 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci buf = p; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci for (i = index; i < (index + count); i++) { 9062306a36Sopenharmony_ci type = imx_ocotp_fuse_type(context, i); 9162306a36Sopenharmony_ci if (type == FUSE_INVALID || type == FUSE_ELE) { 9262306a36Sopenharmony_ci *buf++ = 0; 9362306a36Sopenharmony_ci continue; 9462306a36Sopenharmony_ci } 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci *buf++ = readl_relaxed(reg + (i << 2)); 9762306a36Sopenharmony_ci } 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci memcpy(val, (u8 *)p, bytes); 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci mutex_unlock(&priv->lock); 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci kfree(p); 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci return 0; 10662306a36Sopenharmony_ci}; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_cistatic int imx_ele_ocotp_probe(struct platform_device *pdev) 10962306a36Sopenharmony_ci{ 11062306a36Sopenharmony_ci struct device *dev = &pdev->dev; 11162306a36Sopenharmony_ci struct imx_ocotp_priv *priv; 11262306a36Sopenharmony_ci struct nvmem_device *nvmem; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 11562306a36Sopenharmony_ci if (!priv) 11662306a36Sopenharmony_ci return -ENOMEM; 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci priv->data = of_device_get_match_data(dev); 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci priv->base = devm_platform_ioremap_resource(pdev, 0); 12162306a36Sopenharmony_ci if (IS_ERR(priv->base)) 12262306a36Sopenharmony_ci return PTR_ERR(priv->base); 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci priv->config.dev = dev; 12562306a36Sopenharmony_ci priv->config.name = "ELE-OCOTP"; 12662306a36Sopenharmony_ci priv->config.id = NVMEM_DEVID_AUTO; 12762306a36Sopenharmony_ci priv->config.owner = THIS_MODULE; 12862306a36Sopenharmony_ci priv->config.size = priv->data->size; 12962306a36Sopenharmony_ci priv->config.reg_read = priv->data->reg_read; 13062306a36Sopenharmony_ci priv->config.word_size = 4; 13162306a36Sopenharmony_ci priv->config.stride = 1; 13262306a36Sopenharmony_ci priv->config.priv = priv; 13362306a36Sopenharmony_ci priv->config.read_only = true; 13462306a36Sopenharmony_ci mutex_init(&priv->lock); 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci nvmem = devm_nvmem_register(dev, &priv->config); 13762306a36Sopenharmony_ci if (IS_ERR(nvmem)) 13862306a36Sopenharmony_ci return PTR_ERR(nvmem); 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci return 0; 14162306a36Sopenharmony_ci} 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_cistatic const struct ocotp_devtype_data imx93_ocotp_data = { 14462306a36Sopenharmony_ci .reg_off = 0x8000, 14562306a36Sopenharmony_ci .reg_read = imx_ocotp_reg_read, 14662306a36Sopenharmony_ci .size = 2048, 14762306a36Sopenharmony_ci .num_entry = 6, 14862306a36Sopenharmony_ci .entry = { 14962306a36Sopenharmony_ci { 0, 52, FUSE_FSB }, 15062306a36Sopenharmony_ci { 63, 1, FUSE_ELE}, 15162306a36Sopenharmony_ci { 128, 16, FUSE_ELE }, 15262306a36Sopenharmony_ci { 182, 1, FUSE_ELE }, 15362306a36Sopenharmony_ci { 188, 1, FUSE_ELE }, 15462306a36Sopenharmony_ci { 312, 200, FUSE_FSB } 15562306a36Sopenharmony_ci }, 15662306a36Sopenharmony_ci}; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_cistatic const struct of_device_id imx_ele_ocotp_dt_ids[] = { 15962306a36Sopenharmony_ci { .compatible = "fsl,imx93-ocotp", .data = &imx93_ocotp_data, }, 16062306a36Sopenharmony_ci {}, 16162306a36Sopenharmony_ci}; 16262306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, imx_ele_ocotp_dt_ids); 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_cistatic struct platform_driver imx_ele_ocotp_driver = { 16562306a36Sopenharmony_ci .driver = { 16662306a36Sopenharmony_ci .name = "imx_ele_ocotp", 16762306a36Sopenharmony_ci .of_match_table = imx_ele_ocotp_dt_ids, 16862306a36Sopenharmony_ci }, 16962306a36Sopenharmony_ci .probe = imx_ele_ocotp_probe, 17062306a36Sopenharmony_ci}; 17162306a36Sopenharmony_cimodule_platform_driver(imx_ele_ocotp_driver); 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ciMODULE_DESCRIPTION("i.MX OCOTP/ELE driver"); 17462306a36Sopenharmony_ciMODULE_AUTHOR("Peng Fan <peng.fan@nxp.com>"); 17562306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 176