diff options
Diffstat (limited to 'drivers/char/hw_random/msm-rng.c')
| -rw-r--r-- | drivers/char/hw_random/msm-rng.c | 197 | 
1 files changed, 197 insertions, 0 deletions
diff --git a/drivers/char/hw_random/msm-rng.c b/drivers/char/hw_random/msm-rng.c new file mode 100644 index 00000000000..148521e51dc --- /dev/null +++ b/drivers/char/hw_random/msm-rng.c @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2011-2013, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + */ +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/hw_random.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> + +/* Device specific register offsets */ +#define PRNG_DATA_OUT		0x0000 +#define PRNG_STATUS		0x0004 +#define PRNG_LFSR_CFG		0x0100 +#define PRNG_CONFIG		0x0104 + +/* Device specific register masks and config values */ +#define PRNG_LFSR_CFG_MASK	0x0000ffff +#define PRNG_LFSR_CFG_CLOCKS	0x0000dddd +#define PRNG_CONFIG_HW_ENABLE	BIT(1) +#define PRNG_STATUS_DATA_AVAIL	BIT(0) + +#define MAX_HW_FIFO_DEPTH	16 +#define MAX_HW_FIFO_SIZE	(MAX_HW_FIFO_DEPTH * 4) +#define WORD_SZ			4 + +struct msm_rng { +	void __iomem *base; +	struct clk *clk; +	struct hwrng hwrng; +}; + +#define to_msm_rng(p)	container_of(p, struct msm_rng, hwrng) + +static int msm_rng_enable(struct hwrng *hwrng, int enable) +{ +	struct msm_rng *rng = to_msm_rng(hwrng); +	u32 val; +	int ret; + +	ret = clk_prepare_enable(rng->clk); +	if (ret) +		return ret; + +	if (enable) { +		/* Enable PRNG only if it is not already enabled */ +		val = readl_relaxed(rng->base + PRNG_CONFIG); +		if (val & PRNG_CONFIG_HW_ENABLE) +			goto already_enabled; + +		val = readl_relaxed(rng->base + PRNG_LFSR_CFG); +		val &= ~PRNG_LFSR_CFG_MASK; +		val |= PRNG_LFSR_CFG_CLOCKS; +		writel(val, rng->base + PRNG_LFSR_CFG); + +		val = readl_relaxed(rng->base + PRNG_CONFIG); +		val |= PRNG_CONFIG_HW_ENABLE; +		writel(val, rng->base + PRNG_CONFIG); +	} else { +		val = readl_relaxed(rng->base + PRNG_CONFIG); +		val &= ~PRNG_CONFIG_HW_ENABLE; +		writel(val, rng->base + PRNG_CONFIG); +	} + +already_enabled: +	clk_disable_unprepare(rng->clk); +	return 0; +} + +static int msm_rng_read(struct hwrng *hwrng, void *data, size_t max, bool wait) +{ +	struct msm_rng *rng = to_msm_rng(hwrng); +	size_t currsize = 0; +	u32 *retdata = data; +	size_t maxsize; +	int ret; +	u32 val; + +	/* calculate max size bytes to transfer back to caller */ +	maxsize = min_t(size_t, MAX_HW_FIFO_SIZE, max); + +	/* no room for word data */ +	if (maxsize < WORD_SZ) +		return 0; + +	ret = clk_prepare_enable(rng->clk); +	if (ret) +		return ret; + +	/* read random data from hardware */ +	do { +		val = readl_relaxed(rng->base + PRNG_STATUS); +		if (!(val & PRNG_STATUS_DATA_AVAIL)) +			break; + +		val = readl_relaxed(rng->base + PRNG_DATA_OUT); +		if (!val) +			break; + +		*retdata++ = val; +		currsize += WORD_SZ; + +		/* make sure we stay on 32bit boundary */ +		if ((maxsize - currsize) < WORD_SZ) +			break; +	} while (currsize < maxsize); + +	clk_disable_unprepare(rng->clk); + +	return currsize; +} + +static int msm_rng_init(struct hwrng *hwrng) +{ +	return msm_rng_enable(hwrng, 1); +} + +static void msm_rng_cleanup(struct hwrng *hwrng) +{ +	msm_rng_enable(hwrng, 0); +} + +static int msm_rng_probe(struct platform_device *pdev) +{ +	struct resource *res; +	struct msm_rng *rng; +	int ret; + +	rng = devm_kzalloc(&pdev->dev, sizeof(*rng), GFP_KERNEL); +	if (!rng) +		return -ENOMEM; + +	platform_set_drvdata(pdev, rng); + +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	rng->base = devm_ioremap_resource(&pdev->dev, res); +	if (IS_ERR(rng->base)) +		return PTR_ERR(rng->base); + +	rng->clk = devm_clk_get(&pdev->dev, "core"); +	if (IS_ERR(rng->clk)) +		return PTR_ERR(rng->clk); + +	rng->hwrng.name = KBUILD_MODNAME, +	rng->hwrng.init = msm_rng_init, +	rng->hwrng.cleanup = msm_rng_cleanup, +	rng->hwrng.read = msm_rng_read, + +	ret = hwrng_register(&rng->hwrng); +	if (ret) { +		dev_err(&pdev->dev, "failed to register hwrng\n"); +		return ret; +	} + +	return 0; +} + +static int msm_rng_remove(struct platform_device *pdev) +{ +	struct msm_rng *rng = platform_get_drvdata(pdev); + +	hwrng_unregister(&rng->hwrng); +	return 0; +} + +static const struct of_device_id msm_rng_of_match[] = { +	{ .compatible = "qcom,prng", }, +	{} +}; +MODULE_DEVICE_TABLE(of, msm_rng_of_match); + +static struct platform_driver msm_rng_driver = { +	.probe = msm_rng_probe, +	.remove = msm_rng_remove, +	.driver = { +		.name = KBUILD_MODNAME, +		.owner = THIS_MODULE, +		.of_match_table = of_match_ptr(msm_rng_of_match), +	} +}; +module_platform_driver(msm_rng_driver); + +MODULE_ALIAS("platform:" KBUILD_MODNAME); +MODULE_AUTHOR("The Linux Foundation"); +MODULE_DESCRIPTION("Qualcomm MSM random number generator driver"); +MODULE_LICENSE("GPL v2");  | 
