diff options
author | Artem Bityutskiy <Artem.Bityutskiy@nokia.com> | 2011-03-25 17:41:20 +0200 |
---|---|---|
committer | Artem Bityutskiy <Artem.Bityutskiy@nokia.com> | 2011-03-25 17:41:20 +0200 |
commit | 7bf7e370d5919112c223a269462cd0b546903829 (patch) | |
tree | 03ccc715239df14ae168277dbccc9d9cf4d8a2c8 /drivers/char/hw_random/picoxcell-rng.c | |
parent | 68b1a1e786f29c900fa1c516a402e24f0ece622a (diff) | |
parent | d39dd11c3e6a7af5c20bfac40594db36cf270f42 (diff) |
Merge branch 'master' of git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6 into for-linus-1
* 'master' of git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6: (9356 commits)
[media] rc: update for bitop name changes
fs: simplify iget & friends
fs: pull inode->i_lock up out of writeback_single_inode
fs: rename inode_lock to inode_hash_lock
fs: move i_wb_list out from under inode_lock
fs: move i_sb_list out from under inode_lock
fs: remove inode_lock from iput_final and prune_icache
fs: Lock the inode LRU list separately
fs: factor inode disposal
fs: protect inode->i_state with inode->i_lock
lib, arch: add filter argument to show_mem and fix private implementations
SLUB: Write to per cpu data when allocating it
slub: Fix debugobjects with lockless fastpath
autofs4: Do not potentially dereference NULL pointer returned by fget() in autofs_dev_ioctl_setpipefd()
autofs4 - remove autofs4_lock
autofs4 - fix d_manage() return on rcu-walk
autofs4 - fix autofs4_expire_indirect() traversal
autofs4 - fix dentry leak in autofs4_expire_direct()
autofs4 - reinstate last used update on access
vfs - check non-mountpoint dentry might block in __follow_mount_rcu()
...
NOTE!
This merge commit was created to fix compilation error. The block
tree was merged upstream and removed the 'elv_queue_empty()'
function which the new 'mtdswap' driver is using. So a simple
merge of the mtd tree with upstream does not compile. And the
mtd tree has already be published, so re-basing it is not an option.
To fix this unfortunate situation, I had to merge upstream into the
mtd-2.6.git tree without committing, put the fixup patch on top of
this, and then commit this. The result is that we do not have commits
which do not compile.
In other words, this merge commit "merges" 3 things: the MTD tree, the
upstream tree, and the fixup patch.
Diffstat (limited to 'drivers/char/hw_random/picoxcell-rng.c')
-rw-r--r-- | drivers/char/hw_random/picoxcell-rng.c | 208 |
1 files changed, 208 insertions, 0 deletions
diff --git a/drivers/char/hw_random/picoxcell-rng.c b/drivers/char/hw_random/picoxcell-rng.c new file mode 100644 index 00000000000..990d55a5e3e --- /dev/null +++ b/drivers/char/hw_random/picoxcell-rng.c @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2010-2011 Picochip Ltd., Jamie Iles + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * All enquiries to support@picochip.com + */ +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/hw_random.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#define DATA_REG_OFFSET 0x0200 +#define CSR_REG_OFFSET 0x0278 +#define CSR_OUT_EMPTY_MASK (1 << 24) +#define CSR_FAULT_MASK (1 << 1) +#define TRNG_BLOCK_RESET_MASK (1 << 0) +#define TAI_REG_OFFSET 0x0380 + +/* + * The maximum amount of time in microseconds to spend waiting for data if the + * core wants us to wait. The TRNG should generate 32 bits every 320ns so a + * timeout of 20us seems reasonable. The TRNG does builtin tests of the data + * for randomness so we can't always assume there is data present. + */ +#define PICO_TRNG_TIMEOUT 20 + +static void __iomem *rng_base; +static struct clk *rng_clk; +struct device *rng_dev; + +static inline u32 picoxcell_trng_read_csr(void) +{ + return __raw_readl(rng_base + CSR_REG_OFFSET); +} + +static inline bool picoxcell_trng_is_empty(void) +{ + return picoxcell_trng_read_csr() & CSR_OUT_EMPTY_MASK; +} + +/* + * Take the random number generator out of reset and make sure the interrupts + * are masked. We shouldn't need to get large amounts of random bytes so just + * poll the status register. The hardware generates 32 bits every 320ns so we + * shouldn't have to wait long enough to warrant waiting for an IRQ. + */ +static void picoxcell_trng_start(void) +{ + __raw_writel(0, rng_base + TAI_REG_OFFSET); + __raw_writel(0, rng_base + CSR_REG_OFFSET); +} + +static void picoxcell_trng_reset(void) +{ + __raw_writel(TRNG_BLOCK_RESET_MASK, rng_base + CSR_REG_OFFSET); + __raw_writel(TRNG_BLOCK_RESET_MASK, rng_base + TAI_REG_OFFSET); + picoxcell_trng_start(); +} + +/* + * Get some random data from the random number generator. The hw_random core + * layer provides us with locking. + */ +static int picoxcell_trng_read(struct hwrng *rng, void *buf, size_t max, + bool wait) +{ + int i; + + /* Wait for some data to become available. */ + for (i = 0; i < PICO_TRNG_TIMEOUT && picoxcell_trng_is_empty(); ++i) { + if (!wait) + return 0; + + udelay(1); + } + + if (picoxcell_trng_read_csr() & CSR_FAULT_MASK) { + dev_err(rng_dev, "fault detected, resetting TRNG\n"); + picoxcell_trng_reset(); + return -EIO; + } + + if (i == PICO_TRNG_TIMEOUT) + return 0; + + *(u32 *)buf = __raw_readl(rng_base + DATA_REG_OFFSET); + return sizeof(u32); +} + +static struct hwrng picoxcell_trng = { + .name = "picoxcell", + .read = picoxcell_trng_read, +}; + +static int picoxcell_trng_probe(struct platform_device *pdev) +{ + int ret; + struct resource *mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + if (!mem) { + dev_warn(&pdev->dev, "no memory resource\n"); + return -ENOMEM; + } + + if (!devm_request_mem_region(&pdev->dev, mem->start, resource_size(mem), + "picoxcell_trng")) { + dev_warn(&pdev->dev, "unable to request io mem\n"); + return -EBUSY; + } + + rng_base = devm_ioremap(&pdev->dev, mem->start, resource_size(mem)); + if (!rng_base) { + dev_warn(&pdev->dev, "unable to remap io mem\n"); + return -ENOMEM; + } + + rng_clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(rng_clk)) { + dev_warn(&pdev->dev, "no clk\n"); + return PTR_ERR(rng_clk); + } + + ret = clk_enable(rng_clk); + if (ret) { + dev_warn(&pdev->dev, "unable to enable clk\n"); + goto err_enable; + } + + picoxcell_trng_start(); + ret = hwrng_register(&picoxcell_trng); + if (ret) + goto err_register; + + rng_dev = &pdev->dev; + dev_info(&pdev->dev, "pixoxcell random number generator active\n"); + + return 0; + +err_register: + clk_disable(rng_clk); +err_enable: + clk_put(rng_clk); + + return ret; +} + +static int __devexit picoxcell_trng_remove(struct platform_device *pdev) +{ + hwrng_unregister(&picoxcell_trng); + clk_disable(rng_clk); + clk_put(rng_clk); + + return 0; +} + +#ifdef CONFIG_PM +static int picoxcell_trng_suspend(struct device *dev) +{ + clk_disable(rng_clk); + + return 0; +} + +static int picoxcell_trng_resume(struct device *dev) +{ + return clk_enable(rng_clk); +} + +static const struct dev_pm_ops picoxcell_trng_pm_ops = { + .suspend = picoxcell_trng_suspend, + .resume = picoxcell_trng_resume, +}; +#endif /* CONFIG_PM */ + +static struct platform_driver picoxcell_trng_driver = { + .probe = picoxcell_trng_probe, + .remove = __devexit_p(picoxcell_trng_remove), + .driver = { + .name = "picoxcell-trng", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &picoxcell_trng_pm_ops, +#endif /* CONFIG_PM */ + }, +}; + +static int __init picoxcell_trng_init(void) +{ + return platform_driver_register(&picoxcell_trng_driver); +} +module_init(picoxcell_trng_init); + +static void __exit picoxcell_trng_exit(void) +{ + platform_driver_unregister(&picoxcell_trng_driver); +} +module_exit(picoxcell_trng_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jamie Iles"); +MODULE_DESCRIPTION("Picochip picoXcell TRNG driver"); |