linux/drivers/nvmem/mxs-ocotp.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * Freescale MXS On-Chip OTP driver
   4 *
   5 * Copyright (C) 2015 Stefan Wahren <stefan.wahren@i2se.com>
   6 *
   7 * Based on the driver from Huang Shijie and Christoph G. Baumann
   8 */
   9#include <linux/clk.h>
  10#include <linux/delay.h>
  11#include <linux/device.h>
  12#include <linux/err.h>
  13#include <linux/io.h>
  14#include <linux/module.h>
  15#include <linux/nvmem-provider.h>
  16#include <linux/of_device.h>
  17#include <linux/platform_device.h>
  18#include <linux/slab.h>
  19#include <linux/stmp_device.h>
  20
  21/* OCOTP registers and bits */
  22
  23#define BM_OCOTP_CTRL_RD_BANK_OPEN      BIT(12)
  24#define BM_OCOTP_CTRL_ERROR             BIT(9)
  25#define BM_OCOTP_CTRL_BUSY              BIT(8)
  26
  27#define OCOTP_TIMEOUT           10000
  28#define OCOTP_DATA_OFFSET       0x20
  29
  30struct mxs_ocotp {
  31        struct clk *clk;
  32        void __iomem *base;
  33        struct nvmem_device *nvmem;
  34};
  35
  36static int mxs_ocotp_wait(struct mxs_ocotp *otp)
  37{
  38        int timeout = OCOTP_TIMEOUT;
  39        unsigned int status = 0;
  40
  41        while (timeout--) {
  42                status = readl(otp->base);
  43
  44                if (!(status & (BM_OCOTP_CTRL_BUSY | BM_OCOTP_CTRL_ERROR)))
  45                        break;
  46
  47                cpu_relax();
  48        }
  49
  50        if (status & BM_OCOTP_CTRL_BUSY)
  51                return -EBUSY;
  52        else if (status & BM_OCOTP_CTRL_ERROR)
  53                return -EIO;
  54
  55        return 0;
  56}
  57
  58static int mxs_ocotp_read(void *context, unsigned int offset,
  59                          void *val, size_t bytes)
  60{
  61        struct mxs_ocotp *otp = context;
  62        u32 *buf = val;
  63        int ret;
  64
  65        ret = clk_enable(otp->clk);
  66        if (ret)
  67                return ret;
  68
  69        writel(BM_OCOTP_CTRL_ERROR, otp->base + STMP_OFFSET_REG_CLR);
  70
  71        ret = mxs_ocotp_wait(otp);
  72        if (ret)
  73                goto disable_clk;
  74
  75        /* open OCOTP banks for read */
  76        writel(BM_OCOTP_CTRL_RD_BANK_OPEN, otp->base + STMP_OFFSET_REG_SET);
  77
  78        /* approximately wait 33 hclk cycles */
  79        udelay(1);
  80
  81        ret = mxs_ocotp_wait(otp);
  82        if (ret)
  83                goto close_banks;
  84
  85        while (bytes) {
  86                if ((offset < OCOTP_DATA_OFFSET) || (offset % 16)) {
  87                        /* fill up non-data register */
  88                        *buf++ = 0;
  89                } else {
  90                        *buf++ = readl(otp->base + offset);
  91                }
  92
  93                bytes -= 4;
  94                offset += 4;
  95        }
  96
  97close_banks:
  98        /* close banks for power saving */
  99        writel(BM_OCOTP_CTRL_RD_BANK_OPEN, otp->base + STMP_OFFSET_REG_CLR);
 100
 101disable_clk:
 102        clk_disable(otp->clk);
 103
 104        return ret;
 105}
 106
 107static struct nvmem_config ocotp_config = {
 108        .name = "mxs-ocotp",
 109        .stride = 16,
 110        .word_size = 4,
 111        .reg_read = mxs_ocotp_read,
 112};
 113
 114struct mxs_data {
 115        int size;
 116};
 117
 118static const struct mxs_data imx23_data = {
 119        .size = 0x220,
 120};
 121
 122static const struct mxs_data imx28_data = {
 123        .size = 0x2a0,
 124};
 125
 126static const struct of_device_id mxs_ocotp_match[] = {
 127        { .compatible = "fsl,imx23-ocotp", .data = &imx23_data },
 128        { .compatible = "fsl,imx28-ocotp", .data = &imx28_data },
 129        { /* sentinel */},
 130};
 131MODULE_DEVICE_TABLE(of, mxs_ocotp_match);
 132
 133static int mxs_ocotp_probe(struct platform_device *pdev)
 134{
 135        struct device *dev = &pdev->dev;
 136        const struct mxs_data *data;
 137        struct mxs_ocotp *otp;
 138        const struct of_device_id *match;
 139        int ret;
 140
 141        match = of_match_device(dev->driver->of_match_table, dev);
 142        if (!match || !match->data)
 143                return -EINVAL;
 144
 145        otp = devm_kzalloc(dev, sizeof(*otp), GFP_KERNEL);
 146        if (!otp)
 147                return -ENOMEM;
 148
 149        otp->base = devm_platform_ioremap_resource(pdev, 0);
 150        if (IS_ERR(otp->base))
 151                return PTR_ERR(otp->base);
 152
 153        otp->clk = devm_clk_get(&pdev->dev, NULL);
 154        if (IS_ERR(otp->clk))
 155                return PTR_ERR(otp->clk);
 156
 157        ret = clk_prepare(otp->clk);
 158        if (ret < 0) {
 159                dev_err(dev, "failed to prepare clk: %d\n", ret);
 160                return ret;
 161        }
 162
 163        data = match->data;
 164
 165        ocotp_config.size = data->size;
 166        ocotp_config.priv = otp;
 167        ocotp_config.dev = dev;
 168        otp->nvmem = devm_nvmem_register(dev, &ocotp_config);
 169        if (IS_ERR(otp->nvmem)) {
 170                ret = PTR_ERR(otp->nvmem);
 171                goto err_clk;
 172        }
 173
 174        platform_set_drvdata(pdev, otp);
 175
 176        return 0;
 177
 178err_clk:
 179        clk_unprepare(otp->clk);
 180
 181        return ret;
 182}
 183
 184static int mxs_ocotp_remove(struct platform_device *pdev)
 185{
 186        struct mxs_ocotp *otp = platform_get_drvdata(pdev);
 187
 188        clk_unprepare(otp->clk);
 189
 190        return 0;
 191}
 192
 193static struct platform_driver mxs_ocotp_driver = {
 194        .probe = mxs_ocotp_probe,
 195        .remove = mxs_ocotp_remove,
 196        .driver = {
 197                .name = "mxs-ocotp",
 198                .of_match_table = mxs_ocotp_match,
 199        },
 200};
 201
 202module_platform_driver(mxs_ocotp_driver);
 203MODULE_AUTHOR("Stefan Wahren <stefan.wahren@i2se.com>");
 204MODULE_DESCRIPTION("driver for OCOTP in i.MX23/i.MX28");
 205MODULE_LICENSE("GPL v2");
 206