| #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"); |