1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * i.MX8 OCOTP fusebox driver 4 * 5 * Copyright 2019 NXP 6 * 7 * Peng Fan <peng.fan@nxp.com> 8 */ 9 10 #include <linux/firmware/imx/sci.h> 11 #include <linux/module.h> 12 #include <linux/nvmem-provider.h> 13 #include <linux/of_device.h> 14 #include <linux/platform_device.h> 15 #include <linux/slab.h> 16 17 enum ocotp_devtype { 18 IMX8QXP, 19 IMX8QM, 20 }; 21 22 struct ocotp_devtype_data { 23 int devtype; 24 int nregs; 25 }; 26 27 struct ocotp_priv { 28 struct device *dev; 29 const struct ocotp_devtype_data *data; 30 struct imx_sc_ipc *nvmem_ipc; 31 }; 32 33 struct imx_sc_msg_misc_fuse_read { 34 struct imx_sc_rpc_msg hdr; 35 u32 word; 36 } __packed; 37 38 static struct ocotp_devtype_data imx8qxp_data = { 39 .devtype = IMX8QXP, 40 .nregs = 800, 41 }; 42 43 static struct ocotp_devtype_data imx8qm_data = { 44 .devtype = IMX8QM, 45 .nregs = 800, 46 }; 47 48 static int imx_sc_misc_otp_fuse_read(struct imx_sc_ipc *ipc, u32 word, 49 u32 *val) 50 { 51 struct imx_sc_msg_misc_fuse_read msg; 52 struct imx_sc_rpc_msg *hdr = &msg.hdr; 53 int ret; 54 55 hdr->ver = IMX_SC_RPC_VERSION; 56 hdr->svc = IMX_SC_RPC_SVC_MISC; 57 hdr->func = IMX_SC_MISC_FUNC_OTP_FUSE_READ; 58 hdr->size = 2; 59 60 msg.word = word; 61 62 ret = imx_scu_call_rpc(ipc, &msg, true); 63 if (ret) 64 return ret; 65 66 *val = msg.word; 67 68 return 0; 69 } 70 71 static int imx_scu_ocotp_read(void *context, unsigned int offset, 72 void *val, size_t bytes) 73 { 74 struct ocotp_priv *priv = context; 75 u32 count, index, num_bytes; 76 u32 *buf; 77 void *p; 78 int i, ret; 79 80 index = offset >> 2; 81 num_bytes = round_up((offset % 4) + bytes, 4); 82 count = num_bytes >> 2; 83 84 if (count > (priv->data->nregs - index)) 85 count = priv->data->nregs - index; 86 87 p = kzalloc(num_bytes, GFP_KERNEL); 88 if (!p) 89 return -ENOMEM; 90 91 buf = p; 92 93 for (i = index; i < (index + count); i++) { 94 if (priv->data->devtype == IMX8QXP) { 95 if ((i > 271) && (i < 544)) { 96 *buf++ = 0; 97 continue; 98 } 99 } 100 101 ret = imx_sc_misc_otp_fuse_read(priv->nvmem_ipc, i, buf); 102 if (ret) { 103 kfree(p); 104 return ret; 105 } 106 buf++; 107 } 108 109 memcpy(val, (u8 *)p + offset % 4, bytes); 110 111 kfree(p); 112 113 return 0; 114 } 115 116 static struct nvmem_config imx_scu_ocotp_nvmem_config = { 117 .name = "imx-scu-ocotp", 118 .read_only = true, 119 .word_size = 4, 120 .stride = 1, 121 .owner = THIS_MODULE, 122 .reg_read = imx_scu_ocotp_read, 123 }; 124 125 static const struct of_device_id imx_scu_ocotp_dt_ids[] = { 126 { .compatible = "fsl,imx8qxp-scu-ocotp", (void *)&imx8qxp_data }, 127 { .compatible = "fsl,imx8qm-scu-ocotp", (void *)&imx8qm_data }, 128 { }, 129 }; 130 MODULE_DEVICE_TABLE(of, imx_scu_ocotp_dt_ids); 131 132 static int imx_scu_ocotp_probe(struct platform_device *pdev) 133 { 134 struct device *dev = &pdev->dev; 135 struct ocotp_priv *priv; 136 struct nvmem_device *nvmem; 137 int ret; 138 139 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 140 if (!priv) 141 return -ENOMEM; 142 143 ret = imx_scu_get_handle(&priv->nvmem_ipc); 144 if (ret) 145 return ret; 146 147 priv->data = of_device_get_match_data(dev); 148 priv->dev = dev; 149 imx_scu_ocotp_nvmem_config.size = 4 * priv->data->nregs; 150 imx_scu_ocotp_nvmem_config.dev = dev; 151 imx_scu_ocotp_nvmem_config.priv = priv; 152 nvmem = devm_nvmem_register(dev, &imx_scu_ocotp_nvmem_config); 153 154 return PTR_ERR_OR_ZERO(nvmem); 155 } 156 157 static struct platform_driver imx_scu_ocotp_driver = { 158 .probe = imx_scu_ocotp_probe, 159 .driver = { 160 .name = "imx_scu_ocotp", 161 .of_match_table = imx_scu_ocotp_dt_ids, 162 }, 163 }; 164 module_platform_driver(imx_scu_ocotp_driver); 165 166 MODULE_AUTHOR("Peng Fan <peng.fan@nxp.com>"); 167 MODULE_DESCRIPTION("i.MX8 SCU OCOTP fuse box driver"); 168 MODULE_LICENSE("GPL v2"); 169