248 lines
		
	
	
	
		
			5.7 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
		
		
			
		
	
	
			248 lines
		
	
	
	
		
			5.7 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
|   | /*
 | ||
|  |  * RNG driver for Freescale RNGA | ||
|  |  * | ||
|  |  * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved. | ||
|  |  * Author: Alan Carvalho de Assis <acassis@gmail.com> | ||
|  |  */ | ||
|  | 
 | ||
|  | /*
 | ||
|  |  * The code contained herein is licensed under the GNU General Public | ||
|  |  * License. You may obtain a copy of the GNU General Public License | ||
|  |  * Version 2 or later at the following locations: | ||
|  |  * | ||
|  |  * http://www.opensource.org/licenses/gpl-license.html
 | ||
|  |  * http://www.gnu.org/copyleft/gpl.html
 | ||
|  |  * | ||
|  |  * This driver is based on other RNG drivers. | ||
|  |  */ | ||
|  | 
 | ||
|  | #include <linux/module.h>
 | ||
|  | #include <linux/init.h>
 | ||
|  | #include <linux/kernel.h>
 | ||
|  | #include <linux/clk.h>
 | ||
|  | #include <linux/err.h>
 | ||
|  | #include <linux/ioport.h>
 | ||
|  | #include <linux/platform_device.h>
 | ||
|  | #include <linux/hw_random.h>
 | ||
|  | #include <linux/io.h>
 | ||
|  | 
 | ||
|  | /* RNGA Registers */ | ||
|  | #define RNGA_CONTROL			0x00
 | ||
|  | #define RNGA_STATUS			0x04
 | ||
|  | #define RNGA_ENTROPY			0x08
 | ||
|  | #define RNGA_OUTPUT_FIFO		0x0c
 | ||
|  | #define RNGA_MODE			0x10
 | ||
|  | #define RNGA_VERIFICATION_CONTROL	0x14
 | ||
|  | #define RNGA_OSC_CONTROL_COUNTER	0x18
 | ||
|  | #define RNGA_OSC1_COUNTER		0x1c
 | ||
|  | #define RNGA_OSC2_COUNTER		0x20
 | ||
|  | #define RNGA_OSC_COUNTER_STATUS		0x24
 | ||
|  | 
 | ||
|  | /* RNGA Registers Range */ | ||
|  | #define RNG_ADDR_RANGE			0x28
 | ||
|  | 
 | ||
|  | /* RNGA Control Register */ | ||
|  | #define RNGA_CONTROL_SLEEP		0x00000010
 | ||
|  | #define RNGA_CONTROL_CLEAR_INT		0x00000008
 | ||
|  | #define RNGA_CONTROL_MASK_INTS		0x00000004
 | ||
|  | #define RNGA_CONTROL_HIGH_ASSURANCE	0x00000002
 | ||
|  | #define RNGA_CONTROL_GO			0x00000001
 | ||
|  | 
 | ||
|  | #define RNGA_STATUS_LEVEL_MASK		0x0000ff00
 | ||
|  | 
 | ||
|  | /* RNGA Status Register */ | ||
|  | #define RNGA_STATUS_OSC_DEAD		0x80000000
 | ||
|  | #define RNGA_STATUS_SLEEP		0x00000010
 | ||
|  | #define RNGA_STATUS_ERROR_INT		0x00000008
 | ||
|  | #define RNGA_STATUS_FIFO_UNDERFLOW	0x00000004
 | ||
|  | #define RNGA_STATUS_LAST_READ_STATUS	0x00000002
 | ||
|  | #define RNGA_STATUS_SECURITY_VIOLATION	0x00000001
 | ||
|  | 
 | ||
|  | static struct platform_device *rng_dev; | ||
|  | 
 | ||
|  | static int mxc_rnga_data_present(struct hwrng *rng) | ||
|  | { | ||
|  | 	int level; | ||
|  | 	void __iomem *rng_base = (void __iomem *)rng->priv; | ||
|  | 
 | ||
|  | 	/* how many random numbers is in FIFO? [0-16] */ | ||
|  | 	level = ((__raw_readl(rng_base + RNGA_STATUS) & | ||
|  | 			RNGA_STATUS_LEVEL_MASK) >> 8); | ||
|  | 
 | ||
|  | 	return level > 0 ? 1 : 0; | ||
|  | } | ||
|  | 
 | ||
|  | static int mxc_rnga_data_read(struct hwrng *rng, u32 * data) | ||
|  | { | ||
|  | 	int err; | ||
|  | 	u32 ctrl; | ||
|  | 	void __iomem *rng_base = (void __iomem *)rng->priv; | ||
|  | 
 | ||
|  | 	/* retrieve a random number from FIFO */ | ||
|  | 	*data = __raw_readl(rng_base + RNGA_OUTPUT_FIFO); | ||
|  | 
 | ||
|  | 	/* some error while reading this random number? */ | ||
|  | 	err = __raw_readl(rng_base + RNGA_STATUS) & RNGA_STATUS_ERROR_INT; | ||
|  | 
 | ||
|  | 	/* if error: clear error interrupt, but doesn't return random number */ | ||
|  | 	if (err) { | ||
|  | 		dev_dbg(&rng_dev->dev, "Error while reading random number!\n"); | ||
|  | 		ctrl = __raw_readl(rng_base + RNGA_CONTROL); | ||
|  | 		__raw_writel(ctrl | RNGA_CONTROL_CLEAR_INT, | ||
|  | 					rng_base + RNGA_CONTROL); | ||
|  | 		return 0; | ||
|  | 	} else | ||
|  | 		return 4; | ||
|  | } | ||
|  | 
 | ||
|  | static int mxc_rnga_init(struct hwrng *rng) | ||
|  | { | ||
|  | 	u32 ctrl, osc; | ||
|  | 	void __iomem *rng_base = (void __iomem *)rng->priv; | ||
|  | 
 | ||
|  | 	/* wake up */ | ||
|  | 	ctrl = __raw_readl(rng_base + RNGA_CONTROL); | ||
|  | 	__raw_writel(ctrl & ~RNGA_CONTROL_SLEEP, rng_base + RNGA_CONTROL); | ||
|  | 
 | ||
|  | 	/* verify if oscillator is working */ | ||
|  | 	osc = __raw_readl(rng_base + RNGA_STATUS); | ||
|  | 	if (osc & RNGA_STATUS_OSC_DEAD) { | ||
|  | 		dev_err(&rng_dev->dev, "RNGA Oscillator is dead!\n"); | ||
|  | 		return -ENODEV; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/* go running */ | ||
|  | 	ctrl = __raw_readl(rng_base + RNGA_CONTROL); | ||
|  | 	__raw_writel(ctrl | RNGA_CONTROL_GO, rng_base + RNGA_CONTROL); | ||
|  | 
 | ||
|  | 	return 0; | ||
|  | } | ||
|  | 
 | ||
|  | static void mxc_rnga_cleanup(struct hwrng *rng) | ||
|  | { | ||
|  | 	u32 ctrl; | ||
|  | 	void __iomem *rng_base = (void __iomem *)rng->priv; | ||
|  | 
 | ||
|  | 	ctrl = __raw_readl(rng_base + RNGA_CONTROL); | ||
|  | 
 | ||
|  | 	/* stop rnga */ | ||
|  | 	__raw_writel(ctrl & ~RNGA_CONTROL_GO, rng_base + RNGA_CONTROL); | ||
|  | } | ||
|  | 
 | ||
|  | static struct hwrng mxc_rnga = { | ||
|  | 	.name = "mxc-rnga", | ||
|  | 	.init = mxc_rnga_init, | ||
|  | 	.cleanup = mxc_rnga_cleanup, | ||
|  | 	.data_present = mxc_rnga_data_present, | ||
|  | 	.data_read = mxc_rnga_data_read | ||
|  | }; | ||
|  | 
 | ||
|  | static int __init mxc_rnga_probe(struct platform_device *pdev) | ||
|  | { | ||
|  | 	int err = -ENODEV; | ||
|  | 	struct clk *clk; | ||
|  | 	struct resource *res, *mem; | ||
|  | 	void __iomem *rng_base = NULL; | ||
|  | 
 | ||
|  | 	if (rng_dev) | ||
|  | 		return -EBUSY; | ||
|  | 
 | ||
|  | 	clk = clk_get(&pdev->dev, "rng"); | ||
|  | 	if (IS_ERR(clk)) { | ||
|  | 		dev_err(&pdev->dev, "Could not get rng_clk!\n"); | ||
|  | 		err = PTR_ERR(clk); | ||
|  | 		goto out; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	clk_enable(clk); | ||
|  | 
 | ||
|  | 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
|  | 	if (!res) { | ||
|  | 		err = -ENOENT; | ||
|  | 		goto err_region; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	mem = request_mem_region(res->start, resource_size(res), pdev->name); | ||
|  | 	if (mem == NULL) { | ||
|  | 		err = -EBUSY; | ||
|  | 		goto err_region; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	rng_base = ioremap(res->start, resource_size(res)); | ||
|  | 	if (!rng_base) { | ||
|  | 		err = -ENOMEM; | ||
|  | 		goto err_ioremap; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	mxc_rnga.priv = (unsigned long)rng_base; | ||
|  | 
 | ||
|  | 	err = hwrng_register(&mxc_rnga); | ||
|  | 	if (err) { | ||
|  | 		dev_err(&pdev->dev, "MXC RNGA registering failed (%d)\n", err); | ||
|  | 		goto err_register; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	rng_dev = pdev; | ||
|  | 
 | ||
|  | 	dev_info(&pdev->dev, "MXC RNGA Registered.\n"); | ||
|  | 
 | ||
|  | 	return 0; | ||
|  | 
 | ||
|  | err_register: | ||
|  | 	iounmap(rng_base); | ||
|  | 	rng_base = NULL; | ||
|  | 
 | ||
|  | err_ioremap: | ||
|  | 	release_mem_region(res->start, resource_size(res)); | ||
|  | 
 | ||
|  | err_region: | ||
|  | 	clk_disable(clk); | ||
|  | 	clk_put(clk); | ||
|  | 
 | ||
|  | out: | ||
|  | 	return err; | ||
|  | } | ||
|  | 
 | ||
|  | static int __exit mxc_rnga_remove(struct platform_device *pdev) | ||
|  | { | ||
|  | 	struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
|  | 	void __iomem *rng_base = (void __iomem *)mxc_rnga.priv; | ||
|  | 	struct clk *clk = clk_get(&pdev->dev, "rng"); | ||
|  | 
 | ||
|  | 	hwrng_unregister(&mxc_rnga); | ||
|  | 
 | ||
|  | 	iounmap(rng_base); | ||
|  | 
 | ||
|  | 	release_mem_region(res->start, resource_size(res)); | ||
|  | 
 | ||
|  | 	clk_disable(clk); | ||
|  | 	clk_put(clk); | ||
|  | 
 | ||
|  | 	return 0; | ||
|  | } | ||
|  | 
 | ||
|  | static struct platform_driver mxc_rnga_driver = { | ||
|  | 	.driver = { | ||
|  | 		   .name = "mxc_rnga", | ||
|  | 		   .owner = THIS_MODULE, | ||
|  | 		   }, | ||
|  | 	.remove = __exit_p(mxc_rnga_remove), | ||
|  | }; | ||
|  | 
 | ||
|  | static int __init mod_init(void) | ||
|  | { | ||
|  | 	return platform_driver_probe(&mxc_rnga_driver, mxc_rnga_probe); | ||
|  | } | ||
|  | 
 | ||
|  | static void __exit mod_exit(void) | ||
|  | { | ||
|  | 	platform_driver_unregister(&mxc_rnga_driver); | ||
|  | } | ||
|  | 
 | ||
|  | module_init(mod_init); | ||
|  | module_exit(mod_exit); | ||
|  | 
 | ||
|  | MODULE_AUTHOR("Freescale Semiconductor, Inc."); | ||
|  | MODULE_DESCRIPTION("H/W RNGA driver for i.MX"); | ||
|  | MODULE_LICENSE("GPL"); |