linux/drivers/char/hw_random/msm-rng.c
<<
>>
Prefs
   1/*
   2 * Copyright (c) 2011-2013, The Linux Foundation. All rights reserved.
   3 *
   4 * This program is free software; you can redistribute it and/or modify
   5 * it under the terms of the GNU General Public License version 2 and
   6 * only version 2 as published by the Free Software Foundation.
   7 *
   8 * This program is distributed in the hope that it will be useful,
   9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  11 * GNU General Public License for more details.
  12 *
  13 */
  14#include <linux/clk.h>
  15#include <linux/err.h>
  16#include <linux/hw_random.h>
  17#include <linux/io.h>
  18#include <linux/module.h>
  19#include <linux/of.h>
  20#include <linux/platform_device.h>
  21
  22/* Device specific register offsets */
  23#define PRNG_DATA_OUT           0x0000
  24#define PRNG_STATUS             0x0004
  25#define PRNG_LFSR_CFG           0x0100
  26#define PRNG_CONFIG             0x0104
  27
  28/* Device specific register masks and config values */
  29#define PRNG_LFSR_CFG_MASK      0x0000ffff
  30#define PRNG_LFSR_CFG_CLOCKS    0x0000dddd
  31#define PRNG_CONFIG_HW_ENABLE   BIT(1)
  32#define PRNG_STATUS_DATA_AVAIL  BIT(0)
  33
  34#define MAX_HW_FIFO_DEPTH       16
  35#define MAX_HW_FIFO_SIZE        (MAX_HW_FIFO_DEPTH * 4)
  36#define WORD_SZ                 4
  37
  38struct msm_rng {
  39        void __iomem *base;
  40        struct clk *clk;
  41        struct hwrng hwrng;
  42};
  43
  44#define to_msm_rng(p)   container_of(p, struct msm_rng, hwrng)
  45
  46static int msm_rng_enable(struct hwrng *hwrng, int enable)
  47{
  48        struct msm_rng *rng = to_msm_rng(hwrng);
  49        u32 val;
  50        int ret;
  51
  52        ret = clk_prepare_enable(rng->clk);
  53        if (ret)
  54                return ret;
  55
  56        if (enable) {
  57                /* Enable PRNG only if it is not already enabled */
  58                val = readl_relaxed(rng->base + PRNG_CONFIG);
  59                if (val & PRNG_CONFIG_HW_ENABLE)
  60                        goto already_enabled;
  61
  62                val = readl_relaxed(rng->base + PRNG_LFSR_CFG);
  63                val &= ~PRNG_LFSR_CFG_MASK;
  64                val |= PRNG_LFSR_CFG_CLOCKS;
  65                writel(val, rng->base + PRNG_LFSR_CFG);
  66
  67                val = readl_relaxed(rng->base + PRNG_CONFIG);
  68                val |= PRNG_CONFIG_HW_ENABLE;
  69                writel(val, rng->base + PRNG_CONFIG);
  70        } else {
  71                val = readl_relaxed(rng->base + PRNG_CONFIG);
  72                val &= ~PRNG_CONFIG_HW_ENABLE;
  73                writel(val, rng->base + PRNG_CONFIG);
  74        }
  75
  76already_enabled:
  77        clk_disable_unprepare(rng->clk);
  78        return 0;
  79}
  80
  81static int msm_rng_read(struct hwrng *hwrng, void *data, size_t max, bool wait)
  82{
  83        struct msm_rng *rng = to_msm_rng(hwrng);
  84        size_t currsize = 0;
  85        u32 *retdata = data;
  86        size_t maxsize;
  87        int ret;
  88        u32 val;
  89
  90        /* calculate max size bytes to transfer back to caller */
  91        maxsize = min_t(size_t, MAX_HW_FIFO_SIZE, max);
  92
  93        /* no room for word data */
  94        if (maxsize < WORD_SZ)
  95                return 0;
  96
  97        ret = clk_prepare_enable(rng->clk);
  98        if (ret)
  99                return ret;
 100
 101        /* read random data from hardware */
 102        do {
 103                val = readl_relaxed(rng->base + PRNG_STATUS);
 104                if (!(val & PRNG_STATUS_DATA_AVAIL))
 105                        break;
 106
 107                val = readl_relaxed(rng->base + PRNG_DATA_OUT);
 108                if (!val)
 109                        break;
 110
 111                *retdata++ = val;
 112                currsize += WORD_SZ;
 113
 114                /* make sure we stay on 32bit boundary */
 115                if ((maxsize - currsize) < WORD_SZ)
 116                        break;
 117        } while (currsize < maxsize);
 118
 119        clk_disable_unprepare(rng->clk);
 120
 121        return currsize;
 122}
 123
 124static int msm_rng_init(struct hwrng *hwrng)
 125{
 126        return msm_rng_enable(hwrng, 1);
 127}
 128
 129static void msm_rng_cleanup(struct hwrng *hwrng)
 130{
 131        msm_rng_enable(hwrng, 0);
 132}
 133
 134static int msm_rng_probe(struct platform_device *pdev)
 135{
 136        struct resource *res;
 137        struct msm_rng *rng;
 138        int ret;
 139
 140        rng = devm_kzalloc(&pdev->dev, sizeof(*rng), GFP_KERNEL);
 141        if (!rng)
 142                return -ENOMEM;
 143
 144        platform_set_drvdata(pdev, rng);
 145
 146        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 147        rng->base = devm_ioremap_resource(&pdev->dev, res);
 148        if (IS_ERR(rng->base))
 149                return PTR_ERR(rng->base);
 150
 151        rng->clk = devm_clk_get(&pdev->dev, "core");
 152        if (IS_ERR(rng->clk))
 153                return PTR_ERR(rng->clk);
 154
 155        rng->hwrng.name = KBUILD_MODNAME,
 156        rng->hwrng.init = msm_rng_init,
 157        rng->hwrng.cleanup = msm_rng_cleanup,
 158        rng->hwrng.read = msm_rng_read,
 159
 160        ret = hwrng_register(&rng->hwrng);
 161        if (ret) {
 162                dev_err(&pdev->dev, "failed to register hwrng\n");
 163                return ret;
 164        }
 165
 166        return 0;
 167}
 168
 169static int msm_rng_remove(struct platform_device *pdev)
 170{
 171        struct msm_rng *rng = platform_get_drvdata(pdev);
 172
 173        hwrng_unregister(&rng->hwrng);
 174        return 0;
 175}
 176
 177static const struct of_device_id msm_rng_of_match[] = {
 178        { .compatible = "qcom,prng", },
 179        {}
 180};
 181MODULE_DEVICE_TABLE(of, msm_rng_of_match);
 182
 183static struct platform_driver msm_rng_driver = {
 184        .probe = msm_rng_probe,
 185        .remove = msm_rng_remove,
 186        .driver = {
 187                .name = KBUILD_MODNAME,
 188                .of_match_table = of_match_ptr(msm_rng_of_match),
 189        }
 190};
 191module_platform_driver(msm_rng_driver);
 192
 193MODULE_ALIAS("platform:" KBUILD_MODNAME);
 194MODULE_AUTHOR("The Linux Foundation");
 195MODULE_DESCRIPTION("Qualcomm MSM random number generator driver");
 196MODULE_LICENSE("GPL v2");
 197