|  | /* | 
|  | * Broadcom BCM63xx Random Number Generator support | 
|  | * | 
|  | * Copyright (C) 2011, Florian Fainelli <florian@openwrt.org> | 
|  | * Copyright (C) 2009, Broadcom Corporation | 
|  | * | 
|  | */ | 
|  | #include <linux/module.h> | 
|  | #include <linux/slab.h> | 
|  | #include <linux/io.h> | 
|  | #include <linux/err.h> | 
|  | #include <linux/clk.h> | 
|  | #include <linux/platform_device.h> | 
|  | #include <linux/hw_random.h> | 
|  |  | 
|  | #include <bcm63xx_io.h> | 
|  | #include <bcm63xx_regs.h> | 
|  |  | 
|  | struct bcm63xx_rng_priv { | 
|  | struct clk *clk; | 
|  | void __iomem *regs; | 
|  | }; | 
|  |  | 
|  | #define to_rng_priv(rng)	((struct bcm63xx_rng_priv *)rng->priv) | 
|  |  | 
|  | static int bcm63xx_rng_init(struct hwrng *rng) | 
|  | { | 
|  | struct bcm63xx_rng_priv *priv = to_rng_priv(rng); | 
|  | u32 val; | 
|  |  | 
|  | val = bcm_readl(priv->regs + RNG_CTRL); | 
|  | val |= RNG_EN; | 
|  | bcm_writel(val, priv->regs + RNG_CTRL); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void bcm63xx_rng_cleanup(struct hwrng *rng) | 
|  | { | 
|  | struct bcm63xx_rng_priv *priv = to_rng_priv(rng); | 
|  | u32 val; | 
|  |  | 
|  | val = bcm_readl(priv->regs + RNG_CTRL); | 
|  | val &= ~RNG_EN; | 
|  | bcm_writel(val, priv->regs + RNG_CTRL); | 
|  | } | 
|  |  | 
|  | static int bcm63xx_rng_data_present(struct hwrng *rng, int wait) | 
|  | { | 
|  | struct bcm63xx_rng_priv *priv = to_rng_priv(rng); | 
|  |  | 
|  | return bcm_readl(priv->regs + RNG_STAT) & RNG_AVAIL_MASK; | 
|  | } | 
|  |  | 
|  | static int bcm63xx_rng_data_read(struct hwrng *rng, u32 *data) | 
|  | { | 
|  | struct bcm63xx_rng_priv *priv = to_rng_priv(rng); | 
|  |  | 
|  | *data = bcm_readl(priv->regs + RNG_DATA); | 
|  |  | 
|  | return 4; | 
|  | } | 
|  |  | 
|  | static int __devinit bcm63xx_rng_probe(struct platform_device *pdev) | 
|  | { | 
|  | struct resource *r; | 
|  | struct clk *clk; | 
|  | int ret; | 
|  | struct bcm63xx_rng_priv *priv; | 
|  | struct hwrng *rng; | 
|  |  | 
|  | r = platform_get_resource(pdev, IORESOURCE_MEM, 0); | 
|  | if (!r) { | 
|  | dev_err(&pdev->dev, "no iomem resource\n"); | 
|  | ret = -ENXIO; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | priv = kzalloc(sizeof(*priv), GFP_KERNEL); | 
|  | if (!priv) { | 
|  | dev_err(&pdev->dev, "no memory for private structure\n"); | 
|  | ret = -ENOMEM; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | rng = kzalloc(sizeof(*rng), GFP_KERNEL); | 
|  | if (!rng) { | 
|  | dev_err(&pdev->dev, "no memory for rng structure\n"); | 
|  | ret = -ENOMEM; | 
|  | goto out_free_priv; | 
|  | } | 
|  |  | 
|  | platform_set_drvdata(pdev, rng); | 
|  | rng->priv = (unsigned long)priv; | 
|  | rng->name = pdev->name; | 
|  | rng->init = bcm63xx_rng_init; | 
|  | rng->cleanup = bcm63xx_rng_cleanup; | 
|  | rng->data_present = bcm63xx_rng_data_present; | 
|  | rng->data_read = bcm63xx_rng_data_read; | 
|  |  | 
|  | clk = clk_get(&pdev->dev, "ipsec"); | 
|  | if (IS_ERR(clk)) { | 
|  | dev_err(&pdev->dev, "no clock for device\n"); | 
|  | ret = PTR_ERR(clk); | 
|  | goto out_free_rng; | 
|  | } | 
|  |  | 
|  | priv->clk = clk; | 
|  |  | 
|  | if (!devm_request_mem_region(&pdev->dev, r->start, | 
|  | resource_size(r), pdev->name)) { | 
|  | dev_err(&pdev->dev, "request mem failed"); | 
|  | ret = -ENOMEM; | 
|  | goto out_free_rng; | 
|  | } | 
|  |  | 
|  | priv->regs = devm_ioremap_nocache(&pdev->dev, r->start, | 
|  | resource_size(r)); | 
|  | if (!priv->regs) { | 
|  | dev_err(&pdev->dev, "ioremap failed"); | 
|  | ret = -ENOMEM; | 
|  | goto out_free_rng; | 
|  | } | 
|  |  | 
|  | clk_enable(clk); | 
|  |  | 
|  | ret = hwrng_register(rng); | 
|  | if (ret) { | 
|  | dev_err(&pdev->dev, "failed to register rng device\n"); | 
|  | goto out_clk_disable; | 
|  | } | 
|  |  | 
|  | dev_info(&pdev->dev, "registered RNG driver\n"); | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | out_clk_disable: | 
|  | clk_disable(clk); | 
|  | out_free_rng: | 
|  | platform_set_drvdata(pdev, NULL); | 
|  | kfree(rng); | 
|  | out_free_priv: | 
|  | kfree(priv); | 
|  | out: | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int __devexit bcm63xx_rng_remove(struct platform_device *pdev) | 
|  | { | 
|  | struct hwrng *rng = platform_get_drvdata(pdev); | 
|  | struct bcm63xx_rng_priv *priv = to_rng_priv(rng); | 
|  |  | 
|  | hwrng_unregister(rng); | 
|  | clk_disable(priv->clk); | 
|  | kfree(priv); | 
|  | kfree(rng); | 
|  | platform_set_drvdata(pdev, NULL); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static struct platform_driver bcm63xx_rng_driver = { | 
|  | .probe		= bcm63xx_rng_probe, | 
|  | .remove		= __devexit_p(bcm63xx_rng_remove), | 
|  | .driver		= { | 
|  | .name	= "bcm63xx-rng", | 
|  | .owner	= THIS_MODULE, | 
|  | }, | 
|  | }; | 
|  |  | 
|  | module_platform_driver(bcm63xx_rng_driver); | 
|  |  | 
|  | MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>"); | 
|  | MODULE_DESCRIPTION("Broadcom BCM63xx RNG driver"); | 
|  | MODULE_LICENSE("GPL"); |