root/drivers/char/hw_random/hisi-rng.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (C) 2016 HiSilicon Co., Ltd.
 */

#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/hw_random.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/random.h>

#define RNG_SEED        0x0
#define RNG_CTRL        0x4
  #define RNG_SEED_SEL  BIT(2)
  #define RNG_RING_EN   BIT(1)
  #define RNG_EN        BIT(0)
#define RNG_RAN_NUM     0x10
#define RNG_PHY_SEED    0x14

#define to_hisi_rng(p)  container_of(p, struct hisi_rng, rng)

static int seed_sel;
module_param(seed_sel, int, S_IRUGO);
MODULE_PARM_DESC(seed_sel, "Auto reload seed. 0, use LFSR(default); 1, use ring oscillator.");

struct hisi_rng {
        void __iomem *base;
        struct hwrng rng;
};

static int hisi_rng_init(struct hwrng *rng)
{
        struct hisi_rng *hrng = to_hisi_rng(rng);
        int val = RNG_EN;
        u32 seed;

        /* get a random number as initial seed */
        get_random_bytes(&seed, sizeof(seed));

        writel_relaxed(seed, hrng->base + RNG_SEED);

        /**
         * The seed is reload periodically, there are two choice
         * of seeds, default seed using the value from LFSR, or
         * will use seed generated by ring oscillator.
         */
        if (seed_sel == 1)
                val |= RNG_RING_EN | RNG_SEED_SEL;

        writel_relaxed(val, hrng->base + RNG_CTRL);
        return 0;
}

static void hisi_rng_cleanup(struct hwrng *rng)
{
        struct hisi_rng *hrng = to_hisi_rng(rng);

        writel_relaxed(0, hrng->base + RNG_CTRL);
}

static int hisi_rng_read(struct hwrng *rng, void *buf, size_t max, bool wait)
{
        struct hisi_rng *hrng = to_hisi_rng(rng);
        u32 *data = buf;

        *data = readl_relaxed(hrng->base + RNG_RAN_NUM);
        return 4;
}

static int hisi_rng_probe(struct platform_device *pdev)
{
        struct hisi_rng *rng;
        int ret;

        rng = devm_kzalloc(&pdev->dev, sizeof(*rng), GFP_KERNEL);
        if (!rng)
                return -ENOMEM;

        rng->base = devm_platform_ioremap_resource(pdev, 0);
        if (IS_ERR(rng->base))
                return PTR_ERR(rng->base);

        rng->rng.name = pdev->name;
        rng->rng.init = hisi_rng_init;
        rng->rng.cleanup = hisi_rng_cleanup;
        rng->rng.read = hisi_rng_read;

        ret = devm_hwrng_register(&pdev->dev, &rng->rng);
        if (ret)
                return dev_err_probe(&pdev->dev, ret, "failed to register hwrng\n");

        return 0;
}

static const struct of_device_id hisi_rng_dt_ids[] __maybe_unused = {
        { .compatible = "hisilicon,hip04-rng" },
        { .compatible = "hisilicon,hip05-rng" },
        { }
};
MODULE_DEVICE_TABLE(of, hisi_rng_dt_ids);

static struct platform_driver hisi_rng_driver = {
        .probe          = hisi_rng_probe,
        .driver         = {
                .name   = "hisi-rng",
                .of_match_table = of_match_ptr(hisi_rng_dt_ids),
        },
};

module_platform_driver(hisi_rng_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Kefeng Wang <wangkefeng.wang@huawei>");
MODULE_DESCRIPTION("Hisilicon random number generator driver");