/* * i.MX6 OCOTP fusebox driver * * Copyright (c) 2015 Pengutronix, Philipp Zabel * * Based on the barebox ocotp driver, * Copyright (c) 2010 Baruch Siach , * Orex Computed Radiography * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 * as published by the Free Software Foundation. * * http://www.opensource.org/licenses/gpl-license.html * http://www.gnu.org/copyleft/gpl.html */ #include #include #include #include #include #include #include #include #include #include enum ocotp_devtype { IMX8QM, IMX8QXP, }; struct ocotp_devtype_data { int devtype; int nregs; }; struct ocotp_priv { struct device *dev; struct ocotp_devtype_data *data; sc_ipc_t nvmem_ipc; }; static struct ocotp_devtype_data imx8qm_data = { .devtype = IMX8QM, .nregs = 800, }; static struct ocotp_devtype_data imx8qxp_data = { .devtype = IMX8QXP, .nregs = 800, }; static int imx_scu_ocotp_read(void *context, unsigned int offset, void *val, size_t bytes) { struct ocotp_priv *priv = context; sc_err_t sciErr = SC_ERR_NONE; unsigned int count; u32 index; u32 num_bytes; int i; u8 *buf, *p; index = offset >> 2; num_bytes = round_up((offset % 4) + bytes, 4); count = num_bytes >> 2; if (count > (priv->data->nregs - index)) count = priv->data->nregs - index; p = kzalloc(num_bytes, GFP_KERNEL); if (!p) return -ENOMEM; buf = p; for (i = index; i < (index + count); i++) { if (priv->data->devtype == IMX8QXP) { if ((i > 271) && (i < 544)) { *(u32 *)buf = 0; buf += 4; continue; } } sciErr = sc_misc_otp_fuse_read(priv->nvmem_ipc, i, (u32 *)buf); if (sciErr != SC_ERR_NONE) { kfree(p); return -EIO; } buf += 4; } index = offset % 4; memcpy(val, &p[index], bytes); kfree(p); return 0; } static struct nvmem_config imx_scu_ocotp_nvmem_config = { .name = "imx-ocotp", .read_only = true, .word_size = 4, .stride = 1, .owner = THIS_MODULE, .reg_read = imx_scu_ocotp_read, }; static const struct of_device_id imx_scu_ocotp_dt_ids[] = { { .compatible = "fsl,imx8qm-ocotp", (void *)&imx8qm_data }, { .compatible = "fsl,imx8qxp-ocotp", (void *)&imx8qxp_data }, { }, }; MODULE_DEVICE_TABLE(of, imx_scu_ocotp_dt_ids); static int imx_scu_ocotp_probe(struct platform_device *pdev) { const struct of_device_id *of_id; struct device *dev = &pdev->dev; struct ocotp_priv *priv; struct nvmem_device *nvmem; uint32_t mu_id; sc_err_t sciErr = SC_ERR_NONE; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; sciErr = sc_ipc_getMuID(&mu_id); if (sciErr != SC_ERR_NONE) { pr_info("pinctrl: Cannot obtain MU ID\n"); return -EIO; } sciErr = sc_ipc_open(&priv->nvmem_ipc, mu_id); if (sciErr != SC_ERR_NONE) { pr_info("pinctrl: Cannot open MU channel to SCU\n"); return -EIO; }; of_id = of_match_device(imx_scu_ocotp_dt_ids, dev); priv->data = (struct ocotp_devtype_data *)of_id->data; priv->dev = dev; imx_scu_ocotp_nvmem_config.size = 4 * priv->data->nregs; imx_scu_ocotp_nvmem_config.dev = dev; imx_scu_ocotp_nvmem_config.priv = priv; nvmem = nvmem_register(&imx_scu_ocotp_nvmem_config); if (IS_ERR(nvmem)) return PTR_ERR(nvmem); platform_set_drvdata(pdev, nvmem); return 0; } static int imx_scu_ocotp_remove(struct platform_device *pdev) { struct nvmem_device *nvmem = platform_get_drvdata(pdev); return nvmem_unregister(nvmem); } static struct platform_driver imx_scu_ocotp_driver = { .probe = imx_scu_ocotp_probe, .remove = imx_scu_ocotp_remove, .driver = { .name = "imx_scu_ocotp", .of_match_table = imx_scu_ocotp_dt_ids, }, }; module_platform_driver(imx_scu_ocotp_driver); MODULE_AUTHOR("Peng Fan "); MODULE_DESCRIPTION("i.MX8QM OCOTP fuse box driver"); MODULE_LICENSE("GPL v2");