blob: 8d8b5cacee20d28997aab82157215a90f8365916 [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-rng.h"
#include "asr_aes_clk.h"
static inline u32 rng_read(struct asr_rng *hwrng, u32 offset)
{
u32 value = readl_relaxed(hwrng->io_base + offset);
return value;
}
static inline void rng_write(struct asr_rng *hwrng, u32 offset, u32 val)
{
writel_relaxed(val, hwrng->io_base + offset);
}
static int _rng_read(struct asr_rng *hwrng, bool wait)
{
uint32_t val, random;
uint32_t cnt = 0;
/* generate software seed */
rng_write(hwrng, RNG_SEED_VAL, jiffies & 0xFFFFFFFF);
val = rng_read(hwrng, RNG_CTRL);
val |= CTRL_RNG_SEED_EN;
rng_write(hwrng, RNG_CTRL, val);
do {
val = rng_read(hwrng, RNG_CTRL);
if (cnt >= 100*1000) {
dev_err(hwrng->dev, "fail to generate rng seed, time out !");
return 0;
}
udelay(1);
cnt++;
} while(!(val & CTRL_RNG_SEED_VALID));
/* clr squ fifo */
rng_write(hwrng, RNG_SQU_CTRL, SQU_CTRL_FIFO_CLR);
/* generate random value */
val = rng_read(hwrng, RNG_CTRL);
val |= CTRL_RNG_EN;
rng_write(hwrng, RNG_CTRL, val);
cnt = 0;
do {
val = rng_read(hwrng, RNG_CTRL);
if (cnt >= 100*1000) {
dev_err(hwrng->dev, "fail to generate rng, time out !");
return 0;
}
udelay(1);
cnt++;
} while(!(val & CTRL_RNG_VALID));
cnt = 0;
/* get random value */
do {
random = rng_read(hwrng, RNG_DATA);
if (wait) {
if (cnt >= 100*1000) {
dev_err(hwrng->dev, "fail to generate rng, time out !");
return 0;
}
udelay(1);
cnt++;
} else {
break;
}
} while(random == 0 || random == hwrng->rn_saved);
hwrng->rn_saved = random;
return random;
}
static int asr_rng_disable(struct asr_rng *hwrng)
{
u32 val;
val = rng_read(hwrng, RNG_CTRL);
val &= ~CTRL_RNG_SEED_EN;
rng_write(hwrng, RNG_CTRL, val);
val = rng_read(hwrng, RNG_CTRL);
val &= ~CTRL_RNG_EN;
rng_write(hwrng, RNG_CTRL, val);
return 0;
}
static int asr_rng_read(struct hwrng *rng, void *buf, size_t max, bool wait)
{
unsigned int val;
u32 *data = (u32 *)buf;
size_t read = 0;
struct asr_rng *hwrng = (struct asr_rng *)rng->priv;
struct asr_rng_ops *rng_ops = hwrng->rng_ops;
rng_ops->dev_get(hwrng);
while (read < max) {
val = _rng_read(hwrng, wait);
if (!val) {
rng_ops->dev_put(hwrng);
return read;
}
*data = val;
data++;
read += 4;
}
asr_rng_disable(hwrng);
rng_ops->dev_put(hwrng);
return read;
}
static struct hwrng asr_rng = {
.name = "asr",
.read = asr_rng_read,
.quality = 1000,
};
#if defined(CONFIG_OF)
static const struct of_device_id asr_rng_dt_ids[] = {
{ .compatible = "asr,asr-hwrng" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, asr_rng_dt_ids);
#endif
static int asr_rng_clk_sync(struct asr_rng *rng)
{
struct clk *rng_clk;
if (rng->clk_synced)
return 0;
rng_clk = rng->rng_clk;
/* BCM clk will be disable by CP core, but the enable count is still 1.
* Need to sync the clk enable state here and re-enable the clk.
*/
if (__clk_is_enabled(rng_clk) == false &&
__clk_get_enable_count(rng_clk))
{
asr_aes_clk_put(rng_clk);
asr_aes_clk_get(rng_clk);
rng->clk_synced = 1;
dev_dbg(rng->dev, "sync rng clk done\n");
return 1;
}
return 0;
}
static int asr_rng_dev_get(struct asr_rng *rng)
{
mutex_lock(&rng->rng_lock);
asr_rng_clk_sync(rng);
asr_aes_clk_get(rng->rng_clk);
return 0;
}
static int asr_rng_dev_put(struct asr_rng *rng)
{
asr_aes_clk_put(rng->rng_clk);
mutex_unlock(&rng->rng_lock);
return 0;
}
static struct asr_rng_ops rng_ops = {
.dev_get = asr_rng_dev_get,
.dev_put = asr_rng_dev_put,
};
#ifdef CONFIG_PM
static int asr_rng_suspend(struct device *dev)
{
struct asr_rng *prng = dev_get_drvdata(dev);
asr_aes_clk_put(prng->rng_clk);
return 0;
}
static int asr_rng_resume(struct device *dev)
{
struct asr_rng *prng = dev_get_drvdata(dev);
return asr_aes_clk_get(prng->rng_clk);
}
static const struct dev_pm_ops asr_rng_pm_ops = {
.suspend = asr_rng_suspend,
.resume = asr_rng_resume,
};
#endif /* CONFIG_PM */
static int asr_rng_probe(struct platform_device *pdev)
{
struct asr_rng *prng;
struct device *dev = &pdev->dev;
struct resource *rng_res;
int err = 0;
prng = devm_kzalloc(&pdev->dev, sizeof(*prng), GFP_KERNEL);
if (prng == NULL)
return -ENOMEM;
prng->dev = dev;
prng->rng_ops = &rng_ops;
platform_set_drvdata(pdev, prng);
mutex_init(&prng->rng_lock);
/* Get the base address */
rng_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!rng_res) {
dev_err(dev, "no MEM resource info\n");
err = -ENODEV;
goto res_err;
}
prng->phys_base = rng_res->start;
/* Initializing the clock */
prng->rng_clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(prng->rng_clk)) {
dev_err(dev, "clock initialization failed.\n");
err = PTR_ERR(prng->rng_clk);
goto res_err;
}
prng->clk_synced = 0;
prng->io_base = devm_ioremap_resource(&pdev->dev, rng_res);
if (IS_ERR(prng->io_base)) {
dev_err(dev, "can't ioremap\n");
err = PTR_ERR(prng->io_base);
goto res_err;
}
err = clk_prepare(prng->rng_clk);
if (err)
goto res_err;
err = asr_aes_clk_get(prng->rng_clk);
if (err)
goto rng_clk_unprepare;
refcount_set(&prng->refcount, 1);
prng->rn_saved = 0xdeadbeef;
prng->hwrng = &asr_rng;
asr_rng.priv = (unsigned long)prng;
err = hwrng_register(&asr_rng);
if (err) {
dev_err(dev, "failed to register asr_rng!\n");
goto rng_asr_aes_clk_put;
}
dev_info(dev, "H/W RNG is initialized\n");
return 0;
rng_asr_aes_clk_put:
asr_aes_clk_put(prng->rng_clk);
rng_clk_unprepare:
clk_unprepare(prng->rng_clk);
res_err:
devm_kfree(dev, prng);
dev_err(dev, "initialization failed.\n");
return err;
}
static int asr_rng_remove(struct platform_device *pdev)
{
struct asr_rng *prng;
prng = platform_get_drvdata(pdev);
if (!prng)
return -ENODEV;
clk_unprepare(prng->rng_clk);
asr_aes_clk_put(prng->rng_clk);
hwrng_unregister(prng->hwrng);
devm_kfree(prng->dev, prng);
return 0;
}
static struct platform_driver asr_rng_driver = {
.probe = asr_rng_probe,
.remove = asr_rng_remove,
.driver = {
.name = "asr_rng",
#ifdef CONFIG_PM
.pm = &asr_rng_pm_ops,
#endif
.of_match_table = of_match_ptr(asr_rng_dt_ids),
},
};
static int __init asr_random_init(void)
{
int ret;
ret = platform_driver_register(&asr_rng_driver);
return ret;
}
device_initcall_sync(asr_random_init);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Yu Zhang <yuzhang@asrmicro.com>");
MODULE_AUTHOR("Wang Yonggan <yongganwnag@asrmicro.com>");
MODULE_DESCRIPTION("ASR H/W RNG driver");