/* * drivers/char/hw_random/timeriomem-rng.c * * Copyright (C) 2009 Alexander Clouter <alex@digriz.org.uk> * * Derived from drivers/char/hw_random/omap-rng.c * Copyright 2005 (c) MontaVista Software, Inc. * Author: Deepak Saxena <dsaxena@plexity.net> * * 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. * * Overview: * This driver is useful for platforms that have an IO range that provides * periodic random data from a single IO memory address. All the platform * has to do is provide the address and 'wait time' that new data becomes * available. * * TODO: add support for reading sizes other than 32bits and masking */ #include <linux/module.h> #include <linux/kernel.h> #include <linux/platform_device.h> #include <linux/hw_random.h> #include <linux/io.h> #include <linux/timeriomem-rng.h> #include <linux/jiffies.h> #include <linux/sched.h> #include <linux/timer.h> #include <linux/completion.h> static struct timeriomem_rng_data *timeriomem_rng_data; static void timeriomem_rng_trigger(unsigned long); static DEFINE_TIMER(timeriomem_rng_timer, timeriomem_rng_trigger, 0, 0); /* * have data return 1, however return 0 if we have nothing */ static int timeriomem_rng_data_present(struct hwrng *rng, int wait) { if (rng->priv == 0) return 1; if (!wait || timeriomem_rng_data->present) return timeriomem_rng_data->present; wait_for_completion(&timeriomem_rng_data->completion); return 1; } static int timeriomem_rng_data_read(struct hwrng *rng, u32 *data) { unsigned long cur; s32 delay; *data = readl(timeriomem_rng_data->address); if (rng->priv != 0) { cur = jiffies; delay = cur - timeriomem_rng_timer.expires; delay = rng->priv - (delay % rng->priv); timeriomem_rng_timer.expires = cur + delay; timeriomem_rng_data->present = 0; init_completion(&timeriomem_rng_data->completion); add_timer(&timeriomem_rng_timer); } return 4; } static void timeriomem_rng_trigger(unsigned long dummy) { timeriomem_rng_data->present = 1; complete(&timeriomem_rng_data->completion); } static struct hwrng timeriomem_rng_ops = { .name = "timeriomem", .data_present = timeriomem_rng_data_present, .data_read = timeriomem_rng_data_read, .priv = 0, }; static int __devinit timeriomem_rng_probe(struct platform_device *pdev) { struct resource *res; int ret; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) return -ENOENT; timeriomem_rng_data = pdev->dev.platform_data; timeriomem_rng_data->address = ioremap(res->start, res->end - res->start + 1); if (!timeriomem_rng_data->address) return -EIO; if (timeriomem_rng_data->period != 0 && usecs_to_jiffies(timeriomem_rng_data->period) > 0) { timeriomem_rng_timer.expires = jiffies; timeriomem_rng_ops.priv = usecs_to_jiffies( timeriomem_rng_data->period); } timeriomem_rng_data->present = 1; ret = hwrng_register(&timeriomem_rng_ops); if (ret) goto failed; dev_info(&pdev->dev, "32bits from 0x%p @ %dus\n", timeriomem_rng_data->address, timeriomem_rng_data->period); return 0; failed: dev_err(&pdev->dev, "problem registering\n"); iounmap(timeriomem_rng_data->address); return ret; } static int __devexit timeriomem_rng_remove(struct platform_device *pdev) { del_timer_sync(&timeriomem_rng_timer); hwrng_unregister(&timeriomem_rng_ops); iounmap(timeriomem_rng_data->address); return 0; } static struct platform_driver timeriomem_rng_driver = { .driver = { .name = "timeriomem_rng", .owner = THIS_MODULE, }, .probe = timeriomem_rng_probe, .remove = __devexit_p(timeriomem_rng_remove), }; static int __init timeriomem_rng_init(void) { return platform_driver_register(&timeriomem_rng_driver); } static void __exit timeriomem_rng_exit(void) { platform_driver_unregister(&timeriomem_rng_driver); } module_init(timeriomem_rng_init); module_exit(timeriomem_rng_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Alexander Clouter <alex@digriz.org.uk>"); MODULE_DESCRIPTION("Timer IOMEM H/W RNG driver");