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 void mxs_ocotp_action(void *data)
 134{
 135        clk_unprepare(data);
 136}
 137
 138static int mxs_ocotp_probe(struct platform_device *pdev)
 139{
 140        struct device *dev = &pdev->dev;
 141        const struct mxs_data *data;
 142        struct mxs_ocotp *otp;
 143        const struct of_device_id *match;
 144        int ret;
 145
 146        match = of_match_device(dev->driver->of_match_table, dev);
 147        if (!match || !match->data)
 148                return -EINVAL;
 149
 150        otp = devm_kzalloc(dev, sizeof(*otp), GFP_KERNEL);
 151        if (!otp)
 152                return -ENOMEM;
 153
 154        otp->base = devm_platform_ioremap_resource(pdev, 0);
 155        if (IS_ERR(otp->base))
 156                return PTR_ERR(otp->base);
 157
 158        otp->clk = devm_clk_get(&pdev->dev, NULL);
 159        if (IS_ERR(otp->clk))
 160                return PTR_ERR(otp->clk);
 161
 162        ret = clk_prepare(otp->clk);
 163        if (ret < 0) {
 164                dev_err(dev, "failed to prepare clk: %d\n", ret);
 165                return ret;
 166        }
 167
 168        ret = devm_add_action_or_reset(&pdev->dev, mxs_ocotp_action, otp->clk);
 169        if (ret)
 170                return ret;
 171
 172        data = match->data;
 173
 174        ocotp_config.size = data->size;
 175        ocotp_config.priv = otp;
 176        ocotp_config.dev = dev;
 177        otp->nvmem = devm_nvmem_register(dev, &ocotp_config);
 178        if (IS_ERR(otp->nvmem))
 179                return PTR_ERR(otp->nvmem);
 180
 181        platform_set_drvdata(pdev, otp);
 182
 183        return 0;
 184}
 185
 186static struct platform_driver mxs_ocotp_driver = {
 187        .probe = mxs_ocotp_probe,
 188        .driver = {
 189                .name = "mxs-ocotp",
 190                .of_match_table = mxs_ocotp_match,
 191        },
 192};
 193
 194module_platform_driver(mxs_ocotp_driver);
 195MODULE_AUTHOR("Stefan Wahren <wahrenst@gmx.net");
 196MODULE_DESCRIPTION("driver for OCOTP in i.MX23/i.MX28");
 197MODULE_LICENSE("GPL v2");
 198