blob: bc4e192b4b408a4a537246a17b6362cf6b659ff0 [file] [log] [blame]
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/clk-provider.h>
#include <linux/clk.h>
#include <linux/cputype.h>
#include <linux/hw_random.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include "asr-geu.h"
#define SAMPLE_UDELAY (6)
static inline u32 rng_readl(struct asr_geu_rng *hwrng, u32 offset)
{
struct asr_geu_dev *geu_dd = dev_get_drvdata(hwrng->dev);
return readl(geu_dd->io_base + offset);
}
static inline void rng_writel(struct asr_geu_rng *hwrng, u32 val,
u32 offset)
{
struct asr_geu_dev *geu_dd = dev_get_drvdata(hwrng->dev);
writel(val, geu_dd->io_base + offset);
}
#if defined(CONFIG_CPU_ASR1901)
static void asr_rng_clr_fifo(struct asr_geu_rng *hwrng)
{
u32 val;
val = rng_readl(hwrng, GEU_SQU_RNG_CTRL);
val |= RNG_FIFO_CLR;
rng_writel(hwrng, val, GEU_SQU_RNG_CTRL);
}
#endif
static int asr_rng_init(struct hwrng *rng)
{
u32 val;
struct asr_geu_rng *hwrng = (struct asr_geu_rng *)rng->priv;
val = rng_readl(hwrng, GEU_RNG_CTRL);
if ((val & GEU_RNG_EN) == GEU_RNG_EN)
return 0;
#if !defined(CONFIG_CPU_ASR1901)
#if defined (CONFIG_CPU_ASR1903)
if (!cpu_is_asr1903_z1()) {
writel(hwrng->rn_saved, hwrng->seed_base + 0x0);
writel(jiffies & 0xFFFFFFFF, hwrng->seed_base + 0x4);
}
#else
rng_writel(hwrng, jiffies & 0xFFFFFFFF, GEU_RNG_SEED_HI);
rng_writel(hwrng, hwrng->rn_saved, GEU_RNG_SEED_LO);
#endif
#endif
val |= GEU_RNG_EN; /* enable hardware random generator */
rng_writel(hwrng, val, GEU_RNG_CTRL);
usleep_range(70, 80);
return 0;
}
static int asr_rng_disable(struct asr_geu_rng *hwrng)
{
u32 val;
val = rng_readl(hwrng, GEU_RNG_CTRL);
val &= ~GEU_RNG_EN;
rng_writel(hwrng, val, GEU_RNG_CTRL);
return 0;
}
static int asr_rng_read(struct hwrng *rng, void *buf, size_t max, bool wait)
{
int timeout = max / 4 + 1;
unsigned int val, random = 0;
u32 *data = (u32 *)buf;
size_t read = 0;
struct asr_geu_rng *hwrng = (struct asr_geu_rng *)rng->priv;
struct asr_geu_dev *geu_dd = dev_get_drvdata(hwrng->dev);
struct asr_geu_ops *geu_ops = geu_dd->geu_ops;
geu_ops->dev_get(geu_dd);
asr_rng_init(rng);
random = hwrng->rn_saved;
while (read < max) {
val = rng_readl(hwrng, GEU_RNG_GEN);
#if defined(CONFIG_CPU_ASR1901)
asr_rng_clr_fifo(hwrng);
#endif
if (val == random || val == 0) {
if (wait) {
udelay(SAMPLE_UDELAY);
if (timeout-- == 0) {
geu_ops->dev_put(geu_dd);
return read;
}
} else {
geu_ops->dev_put(geu_dd);
return 0;
}
} else {
*data = random = val;
data++;
read += 4;
}
}
asr_rng_disable(hwrng);
hwrng->rn_saved = random;
geu_ops->dev_put(geu_dd);
return read;
}
static struct hwrng asr_rng = {
.name = "asr",
.init = asr_rng_init,
.read = asr_rng_read,
.quality = 1000,
};
int asr_geu_rng_register(struct asr_geu_dev *geu_dd)
{
int err = 0;
struct asr_geu_rng *prng;
prng = &geu_dd->asr_rng;
prng->rn_saved = 0xdeadbeef;
prng->hwrng = &asr_rng;
prng->io_base = geu_dd->io_base;
prng->dev = geu_dd->dev;
#ifdef CONFIG_CPU_ASR1903
prng->seed_base = ioremap_nocache(ASR1903_RNG_SEED, 0x10);
#endif
asr_rng.priv = (unsigned long)prng;
err = hwrng_register(&asr_rng);
if (err) {
dev_err(prng->dev, "failed to register asr_rng!\n");
return err;
}
return 0;
}
EXPORT_SYMBOL_GPL(asr_geu_rng_register);
int asr_geu_rng_unregister(struct asr_geu_dev *geu_dd)
{
struct asr_geu_rng *prng = &geu_dd->asr_rng;
#ifdef CONFIG_CPU_ASR1903
iounmap(prng->seed_base);
#endif
hwrng_unregister(prng->hwrng);
return 0;
}
EXPORT_SYMBOL_GPL(asr_geu_rng_unregister);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Yu Zhang <yuzhang@asrmicro.com>");
MODULE_DESCRIPTION("ASR H/W RNG driver");