linux/drivers/char/hw_random/omap3-rom-rng.c
<<
>>
Prefs
   1/*
   2 * omap3-rom-rng.c - RNG driver for TI OMAP3 CPU family
   3 *
   4 * Copyright (C) 2009 Nokia Corporation
   5 * Author: Juha Yrjola <juha.yrjola@solidboot.com>
   6 *
   7 * Copyright (C) 2013 Pali Rohár <pali@kernel.org>
   8 *
   9 * This file is licensed under  the terms of the GNU General Public
  10 * License version 2. This program is licensed "as is" without any
  11 * warranty of any kind, whether express or implied.
  12 */
  13
  14#include <linux/module.h>
  15#include <linux/init.h>
  16#include <linux/random.h>
  17#include <linux/hw_random.h>
  18#include <linux/workqueue.h>
  19#include <linux/clk.h>
  20#include <linux/err.h>
  21#include <linux/io.h>
  22#include <linux/of.h>
  23#include <linux/of_device.h>
  24#include <linux/platform_device.h>
  25#include <linux/pm_runtime.h>
  26
  27#define RNG_RESET                       0x01
  28#define RNG_GEN_PRNG_HW_INIT            0x02
  29#define RNG_GEN_HW                      0x08
  30
  31struct omap_rom_rng {
  32        struct clk *clk;
  33        struct device *dev;
  34        struct hwrng ops;
  35        u32 (*rom_rng_call)(u32 ptr, u32 count, u32 flag);
  36};
  37
  38static int omap3_rom_rng_read(struct hwrng *rng, void *data, size_t max, bool w)
  39{
  40        struct omap_rom_rng *ddata;
  41        u32 ptr;
  42        int r;
  43
  44        ddata = (struct omap_rom_rng *)rng->priv;
  45
  46        r = pm_runtime_get_sync(ddata->dev);
  47        if (r < 0) {
  48                pm_runtime_put_noidle(ddata->dev);
  49
  50                return r;
  51        }
  52
  53        ptr = virt_to_phys(data);
  54        r = ddata->rom_rng_call(ptr, 4, RNG_GEN_HW);
  55        if (r != 0)
  56                r = -EINVAL;
  57        else
  58                r = 4;
  59
  60        pm_runtime_mark_last_busy(ddata->dev);
  61        pm_runtime_put_autosuspend(ddata->dev);
  62
  63        return r;
  64}
  65
  66static int __maybe_unused omap_rom_rng_runtime_suspend(struct device *dev)
  67{
  68        struct omap_rom_rng *ddata;
  69        int r;
  70
  71        ddata = dev_get_drvdata(dev);
  72
  73        r = ddata->rom_rng_call(0, 0, RNG_RESET);
  74        if (r != 0)
  75                dev_err(dev, "reset failed: %d\n", r);
  76
  77        clk_disable_unprepare(ddata->clk);
  78
  79        return 0;
  80}
  81
  82static int __maybe_unused omap_rom_rng_runtime_resume(struct device *dev)
  83{
  84        struct omap_rom_rng *ddata;
  85        int r;
  86
  87        ddata = dev_get_drvdata(dev);
  88
  89        r = clk_prepare_enable(ddata->clk);
  90        if (r < 0)
  91                return r;
  92
  93        r = ddata->rom_rng_call(0, 0, RNG_GEN_PRNG_HW_INIT);
  94        if (r != 0) {
  95                clk_disable(ddata->clk);
  96                dev_err(dev, "HW init failed: %d\n", r);
  97
  98                return -EIO;
  99        }
 100
 101        return 0;
 102}
 103
 104static void omap_rom_rng_finish(void *data)
 105{
 106        struct omap_rom_rng *ddata = data;
 107
 108        pm_runtime_dont_use_autosuspend(ddata->dev);
 109        pm_runtime_disable(ddata->dev);
 110}
 111
 112static int omap3_rom_rng_probe(struct platform_device *pdev)
 113{
 114        struct omap_rom_rng *ddata;
 115        int ret = 0;
 116
 117        ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL);
 118        if (!ddata)
 119                return -ENOMEM;
 120
 121        ddata->dev = &pdev->dev;
 122        ddata->ops.priv = (unsigned long)ddata;
 123        ddata->ops.name = "omap3-rom";
 124        ddata->ops.read = of_device_get_match_data(&pdev->dev);
 125        ddata->ops.quality = 900;
 126        if (!ddata->ops.read) {
 127                dev_err(&pdev->dev, "missing rom code handler\n");
 128
 129                return -ENODEV;
 130        }
 131        dev_set_drvdata(ddata->dev, ddata);
 132
 133        ddata->rom_rng_call = pdev->dev.platform_data;
 134        if (!ddata->rom_rng_call) {
 135                dev_err(ddata->dev, "rom_rng_call is NULL\n");
 136                return -EINVAL;
 137        }
 138
 139        ddata->clk = devm_clk_get(ddata->dev, "ick");
 140        if (IS_ERR(ddata->clk)) {
 141                dev_err(ddata->dev, "unable to get RNG clock\n");
 142                return PTR_ERR(ddata->clk);
 143        }
 144
 145        pm_runtime_enable(&pdev->dev);
 146        pm_runtime_set_autosuspend_delay(&pdev->dev, 500);
 147        pm_runtime_use_autosuspend(&pdev->dev);
 148
 149        ret = devm_add_action_or_reset(ddata->dev, omap_rom_rng_finish,
 150                                       ddata);
 151        if (ret)
 152                return ret;
 153
 154        return devm_hwrng_register(ddata->dev, &ddata->ops);
 155}
 156
 157static const struct of_device_id omap_rom_rng_match[] = {
 158        { .compatible = "nokia,n900-rom-rng", .data = omap3_rom_rng_read, },
 159        { /* sentinel */ },
 160};
 161MODULE_DEVICE_TABLE(of, omap_rom_rng_match);
 162
 163static const struct dev_pm_ops omap_rom_rng_pm_ops = {
 164        SET_SYSTEM_SLEEP_PM_OPS(omap_rom_rng_runtime_suspend,
 165                                omap_rom_rng_runtime_resume)
 166};
 167
 168static struct platform_driver omap3_rom_rng_driver = {
 169        .driver = {
 170                .name           = "omap3-rom-rng",
 171                .of_match_table = omap_rom_rng_match,
 172                .pm = &omap_rom_rng_pm_ops,
 173        },
 174        .probe          = omap3_rom_rng_probe,
 175};
 176
 177module_platform_driver(omap3_rom_rng_driver);
 178
 179MODULE_ALIAS("platform:omap3-rom-rng");
 180MODULE_AUTHOR("Juha Yrjola");
 181MODULE_AUTHOR("Pali Rohár <pali@kernel.org>");
 182MODULE_LICENSE("GPL");
 183