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        ret = clk_prepare_enable(rng->clk);
  94        if (ret)
  95                return ret;
  96
  97        /* read random data from hardware */
  98        do {
  99                val = readl_relaxed(rng->base + PRNG_STATUS);
 100                if (!(val & PRNG_STATUS_DATA_AVAIL))
 101                        break;
 102
 103                val = readl_relaxed(rng->base + PRNG_DATA_OUT);
 104                if (!val)
 105                        break;
 106
 107                *retdata++ = val;
 108                currsize += WORD_SZ;
 109
 110                /* make sure we stay on 32bit boundary */
 111                if ((maxsize - currsize) < WORD_SZ)
 112                        break;
 113        } while (currsize < maxsize);
 114
 115        clk_disable_unprepare(rng->clk);
 116
 117        return currsize;
 118}
 119
 120static int msm_rng_init(struct hwrng *hwrng)
 121{
 122        return msm_rng_enable(hwrng, 1);
 123}
 124
 125static void msm_rng_cleanup(struct hwrng *hwrng)
 126{
 127        msm_rng_enable(hwrng, 0);
 128}
 129
 130static int msm_rng_probe(struct platform_device *pdev)
 131{
 132        struct resource *res;
 133        struct msm_rng *rng;
 134        int ret;
 135
 136        rng = devm_kzalloc(&pdev->dev, sizeof(*rng), GFP_KERNEL);
 137        if (!rng)
 138                return -ENOMEM;
 139
 140        platform_set_drvdata(pdev, rng);
 141
 142        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 143        rng->base = devm_ioremap_resource(&pdev->dev, res);
 144        if (IS_ERR(rng->base))
 145                return PTR_ERR(rng->base);
 146
 147        rng->clk = devm_clk_get(&pdev->dev, "core");
 148        if (IS_ERR(rng->clk))
 149                return PTR_ERR(rng->clk);
 150
 151        rng->hwrng.name = KBUILD_MODNAME,
 152        rng->hwrng.init = msm_rng_init,
 153        rng->hwrng.cleanup = msm_rng_cleanup,
 154        rng->hwrng.read = msm_rng_read,
 155
 156        ret = devm_hwrng_register(&pdev->dev, &rng->hwrng);
 157        if (ret) {
 158                dev_err(&pdev->dev, "failed to register hwrng\n");
 159                return ret;
 160        }
 161
 162        return 0;
 163}
 164
 165static const struct of_device_id msm_rng_of_match[] = {
 166        { .compatible = "qcom,prng", },
 167        {}
 168};
 169MODULE_DEVICE_TABLE(of, msm_rng_of_match);
 170
 171static struct platform_driver msm_rng_driver = {
 172        .probe = msm_rng_probe,
 173        .driver = {
 174                .name = KBUILD_MODNAME,
 175                .of_match_table = of_match_ptr(msm_rng_of_match),
 176        }
 177};
 178module_platform_driver(msm_rng_driver);
 179
 180MODULE_ALIAS("platform:" KBUILD_MODNAME);
 181MODULE_AUTHOR("The Linux Foundation");
 182MODULE_DESCRIPTION("Qualcomm MSM random number generator driver");
 183MODULE_LICENSE("GPL v2");
 184