linux/drivers/nvmem/sunxi_sid.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * Allwinner sunXi SoCs Security ID support.
   4 *
   5 * Copyright (c) 2013 Oliver Schinagl <oliver@schinagl.nl>
   6 * Copyright (C) 2014 Maxime Ripard <maxime.ripard@free-electrons.com>
   7 */
   8
   9#include <linux/device.h>
  10#include <linux/io.h>
  11#include <linux/iopoll.h>
  12#include <linux/module.h>
  13#include <linux/nvmem-provider.h>
  14#include <linux/of.h>
  15#include <linux/of_device.h>
  16#include <linux/platform_device.h>
  17#include <linux/slab.h>
  18#include <linux/random.h>
  19
  20/* Registers and special values for doing register-based SID readout on H3 */
  21#define SUN8I_SID_PRCTL         0x40
  22#define SUN8I_SID_RDKEY         0x60
  23
  24#define SUN8I_SID_OFFSET_MASK   0x1FF
  25#define SUN8I_SID_OFFSET_SHIFT  16
  26#define SUN8I_SID_OP_LOCK       (0xAC << 8)
  27#define SUN8I_SID_READ          BIT(1)
  28
  29struct sunxi_sid_cfg {
  30        u32     value_offset;
  31        u32     size;
  32        bool    need_register_readout;
  33};
  34
  35struct sunxi_sid {
  36        void __iomem            *base;
  37        u32                     value_offset;
  38};
  39
  40static int sunxi_sid_read(void *context, unsigned int offset,
  41                          void *val, size_t bytes)
  42{
  43        struct sunxi_sid *sid = context;
  44
  45        memcpy_fromio(val, sid->base + sid->value_offset + offset, bytes);
  46
  47        return 0;
  48}
  49
  50static int sun8i_sid_register_readout(const struct sunxi_sid *sid,
  51                                      const unsigned int offset,
  52                                      u32 *out)
  53{
  54        u32 reg_val;
  55        int ret;
  56
  57        /* Set word, lock access, and set read command */
  58        reg_val = (offset & SUN8I_SID_OFFSET_MASK)
  59                  << SUN8I_SID_OFFSET_SHIFT;
  60        reg_val |= SUN8I_SID_OP_LOCK | SUN8I_SID_READ;
  61        writel(reg_val, sid->base + SUN8I_SID_PRCTL);
  62
  63        ret = readl_poll_timeout(sid->base + SUN8I_SID_PRCTL, reg_val,
  64                                 !(reg_val & SUN8I_SID_READ), 100, 250000);
  65        if (ret)
  66                return ret;
  67
  68        if (out)
  69                *out = readl(sid->base + SUN8I_SID_RDKEY);
  70
  71        writel(0, sid->base + SUN8I_SID_PRCTL);
  72
  73        return 0;
  74}
  75
  76/*
  77 * On Allwinner H3, the value on the 0x200 offset of the SID controller seems
  78 * to be not reliable at all.
  79 * Read by the registers instead.
  80 */
  81static int sun8i_sid_read_by_reg(void *context, unsigned int offset,
  82                                 void *val, size_t bytes)
  83{
  84        struct sunxi_sid *sid = context;
  85        u32 word;
  86        int ret;
  87
  88        /* .stride = 4 so offset is guaranteed to be aligned */
  89        while (bytes >= 4) {
  90                ret = sun8i_sid_register_readout(sid, offset, val);
  91                if (ret)
  92                        return ret;
  93
  94                val += 4;
  95                offset += 4;
  96                bytes -= 4;
  97        }
  98
  99        if (!bytes)
 100                return 0;
 101
 102        /* Handle any trailing bytes */
 103        ret = sun8i_sid_register_readout(sid, offset, &word);
 104        if (ret)
 105                return ret;
 106
 107        memcpy(val, &word, bytes);
 108
 109        return 0;
 110}
 111
 112static int sunxi_sid_probe(struct platform_device *pdev)
 113{
 114        struct device *dev = &pdev->dev;
 115        struct resource *res;
 116        struct nvmem_config *nvmem_cfg;
 117        struct nvmem_device *nvmem;
 118        struct sunxi_sid *sid;
 119        int size;
 120        char *randomness;
 121        const struct sunxi_sid_cfg *cfg;
 122
 123        sid = devm_kzalloc(dev, sizeof(*sid), GFP_KERNEL);
 124        if (!sid)
 125                return -ENOMEM;
 126
 127        cfg = of_device_get_match_data(dev);
 128        if (!cfg)
 129                return -EINVAL;
 130        sid->value_offset = cfg->value_offset;
 131
 132        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 133        sid->base = devm_ioremap_resource(dev, res);
 134        if (IS_ERR(sid->base))
 135                return PTR_ERR(sid->base);
 136
 137        size = cfg->size;
 138
 139        nvmem_cfg = devm_kzalloc(dev, sizeof(*nvmem_cfg), GFP_KERNEL);
 140        if (!nvmem_cfg)
 141                return -ENOMEM;
 142
 143        nvmem_cfg->dev = dev;
 144        nvmem_cfg->name = "sunxi-sid";
 145        nvmem_cfg->read_only = true;
 146        nvmem_cfg->size = cfg->size;
 147        nvmem_cfg->word_size = 1;
 148        nvmem_cfg->stride = 4;
 149        nvmem_cfg->priv = sid;
 150        if (cfg->need_register_readout)
 151                nvmem_cfg->reg_read = sun8i_sid_read_by_reg;
 152        else
 153                nvmem_cfg->reg_read = sunxi_sid_read;
 154
 155        nvmem = devm_nvmem_register(dev, nvmem_cfg);
 156        if (IS_ERR(nvmem))
 157                return PTR_ERR(nvmem);
 158
 159        randomness = kzalloc(size, GFP_KERNEL);
 160        if (!randomness)
 161                return -ENOMEM;
 162
 163        nvmem_cfg->reg_read(sid, 0, randomness, size);
 164        add_device_randomness(randomness, size);
 165        kfree(randomness);
 166
 167        platform_set_drvdata(pdev, nvmem);
 168
 169        return 0;
 170}
 171
 172static const struct sunxi_sid_cfg sun4i_a10_cfg = {
 173        .size = 0x10,
 174};
 175
 176static const struct sunxi_sid_cfg sun7i_a20_cfg = {
 177        .size = 0x200,
 178};
 179
 180static const struct sunxi_sid_cfg sun8i_h3_cfg = {
 181        .value_offset = 0x200,
 182        .size = 0x100,
 183        .need_register_readout = true,
 184};
 185
 186static const struct sunxi_sid_cfg sun50i_a64_cfg = {
 187        .value_offset = 0x200,
 188        .size = 0x100,
 189        .need_register_readout = true,
 190};
 191
 192static const struct sunxi_sid_cfg sun50i_h6_cfg = {
 193        .value_offset = 0x200,
 194        .size = 0x200,
 195};
 196
 197static const struct of_device_id sunxi_sid_of_match[] = {
 198        { .compatible = "allwinner,sun4i-a10-sid", .data = &sun4i_a10_cfg },
 199        { .compatible = "allwinner,sun7i-a20-sid", .data = &sun7i_a20_cfg },
 200        { .compatible = "allwinner,sun8i-a83t-sid", .data = &sun50i_a64_cfg },
 201        { .compatible = "allwinner,sun8i-h3-sid", .data = &sun8i_h3_cfg },
 202        { .compatible = "allwinner,sun50i-a64-sid", .data = &sun50i_a64_cfg },
 203        { .compatible = "allwinner,sun50i-h5-sid", .data = &sun50i_a64_cfg },
 204        { .compatible = "allwinner,sun50i-h6-sid", .data = &sun50i_h6_cfg },
 205        {/* sentinel */},
 206};
 207MODULE_DEVICE_TABLE(of, sunxi_sid_of_match);
 208
 209static struct platform_driver sunxi_sid_driver = {
 210        .probe = sunxi_sid_probe,
 211        .driver = {
 212                .name = "eeprom-sunxi-sid",
 213                .of_match_table = sunxi_sid_of_match,
 214        },
 215};
 216module_platform_driver(sunxi_sid_driver);
 217
 218MODULE_AUTHOR("Oliver Schinagl <oliver@schinagl.nl>");
 219MODULE_DESCRIPTION("Allwinner sunxi security id driver");
 220MODULE_LICENSE("GPL");
 221