brintos

brintos / linux-shallow public Read only

0
0
Text · 5.3 KiB · e915725 Raw
232 lines · c
1/*2 * Copyright (c) 2011 Peter Korsgaard <jacmet@sunsite.dk>3 *4 * This file is licensed under  the terms of the GNU General Public5 * License version 2. This program is licensed "as is" without any6 * warranty of any kind, whether express or implied.7 */8 9#include <linux/kernel.h>10#include <linux/module.h>11#include <linux/mod_devicetable.h>12#include <linux/slab.h>13#include <linux/err.h>14#include <linux/clk.h>15#include <linux/io.h>16#include <linux/iopoll.h>17#include <linux/hw_random.h>18#include <linux/of.h>19#include <linux/platform_device.h>20#include <linux/pm_runtime.h>21 22#define TRNG_CR		0x0023#define TRNG_MR		0x0424#define TRNG_ISR	0x1c25#define TRNG_ISR_DATRDY	BIT(0)26#define TRNG_ODATA	0x5027 28#define TRNG_KEY	0x524e4700 /* RNG */29 30#define TRNG_HALFR	BIT(0) /* generate RN every 168 cycles */31 32struct atmel_trng_data {33	bool has_half_rate;34};35 36struct atmel_trng {37	struct clk *clk;38	void __iomem *base;39	struct hwrng rng;40	bool has_half_rate;41};42 43static bool atmel_trng_wait_ready(struct atmel_trng *trng, bool wait)44{45	int ready;46 47	ready = readl(trng->base + TRNG_ISR) & TRNG_ISR_DATRDY;48	if (!ready && wait)49		readl_poll_timeout(trng->base + TRNG_ISR, ready,50				   ready & TRNG_ISR_DATRDY, 1000, 20000);51 52	return !!ready;53}54 55static int atmel_trng_read(struct hwrng *rng, void *buf, size_t max,56			   bool wait)57{58	struct atmel_trng *trng = container_of(rng, struct atmel_trng, rng);59	u32 *data = buf;60	int ret;61 62	ret = pm_runtime_get_sync((struct device *)trng->rng.priv);63	if (ret < 0) {64		pm_runtime_put_sync((struct device *)trng->rng.priv);65		return ret;66	}67 68	ret = atmel_trng_wait_ready(trng, wait);69	if (!ret)70		goto out;71 72	*data = readl(trng->base + TRNG_ODATA);73	/*74	 * ensure data ready is only set again AFTER the next data word is ready75	 * in case it got set between checking ISR and reading ODATA, so we76	 * don't risk re-reading the same word77	 */78	readl(trng->base + TRNG_ISR);79	ret = 4;80 81out:82	pm_runtime_mark_last_busy((struct device *)trng->rng.priv);83	pm_runtime_put_sync_autosuspend((struct device *)trng->rng.priv);84	return ret;85}86 87static int atmel_trng_init(struct atmel_trng *trng)88{89	unsigned long rate;90	int ret;91 92	ret = clk_prepare_enable(trng->clk);93	if (ret)94		return ret;95 96	if (trng->has_half_rate) {97		rate = clk_get_rate(trng->clk);98 99		/* if peripheral clk is above 100MHz, set HALFR */100		if (rate > 100000000)101			writel(TRNG_HALFR, trng->base + TRNG_MR);102	}103 104	writel(TRNG_KEY | 1, trng->base + TRNG_CR);105 106	return 0;107}108 109static void atmel_trng_cleanup(struct atmel_trng *trng)110{111	writel(TRNG_KEY, trng->base + TRNG_CR);112	clk_disable_unprepare(trng->clk);113}114 115static int atmel_trng_probe(struct platform_device *pdev)116{117	struct atmel_trng *trng;118	const struct atmel_trng_data *data;119	int ret;120 121	trng = devm_kzalloc(&pdev->dev, sizeof(*trng), GFP_KERNEL);122	if (!trng)123		return -ENOMEM;124 125	trng->base = devm_platform_ioremap_resource(pdev, 0);126	if (IS_ERR(trng->base))127		return PTR_ERR(trng->base);128 129	trng->clk = devm_clk_get(&pdev->dev, NULL);130	if (IS_ERR(trng->clk))131		return PTR_ERR(trng->clk);132	data = of_device_get_match_data(&pdev->dev);133	if (!data)134		return -ENODEV;135 136	trng->has_half_rate = data->has_half_rate;137	trng->rng.name = pdev->name;138	trng->rng.read = atmel_trng_read;139	trng->rng.priv = (unsigned long)&pdev->dev;140	platform_set_drvdata(pdev, trng);141 142#ifndef CONFIG_PM143	ret = atmel_trng_init(trng);144	if (ret)145		return ret;146#endif147 148	pm_runtime_set_autosuspend_delay(&pdev->dev, 100);149	pm_runtime_use_autosuspend(&pdev->dev);150	pm_runtime_enable(&pdev->dev);151 152	ret = devm_hwrng_register(&pdev->dev, &trng->rng);153	if (ret) {154		pm_runtime_disable(&pdev->dev);155		pm_runtime_set_suspended(&pdev->dev);156#ifndef CONFIG_PM157		atmel_trng_cleanup(trng);158#endif159	}160 161	return ret;162}163 164static void atmel_trng_remove(struct platform_device *pdev)165{166	struct atmel_trng *trng = platform_get_drvdata(pdev);167 168	atmel_trng_cleanup(trng);169	pm_runtime_disable(&pdev->dev);170	pm_runtime_set_suspended(&pdev->dev);171}172 173static int __maybe_unused atmel_trng_runtime_suspend(struct device *dev)174{175	struct atmel_trng *trng = dev_get_drvdata(dev);176 177	atmel_trng_cleanup(trng);178 179	return 0;180}181 182static int __maybe_unused atmel_trng_runtime_resume(struct device *dev)183{184	struct atmel_trng *trng = dev_get_drvdata(dev);185 186	return atmel_trng_init(trng);187}188 189static const struct dev_pm_ops __maybe_unused atmel_trng_pm_ops = {190	SET_RUNTIME_PM_OPS(atmel_trng_runtime_suspend,191			   atmel_trng_runtime_resume, NULL)192	SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,193				pm_runtime_force_resume)194};195 196static const struct atmel_trng_data at91sam9g45_config = {197	.has_half_rate = false,198};199 200static const struct atmel_trng_data sam9x60_config = {201	.has_half_rate = true,202};203 204static const struct of_device_id atmel_trng_dt_ids[] = {205	{206		.compatible = "atmel,at91sam9g45-trng",207		.data = &at91sam9g45_config,208	}, {209		.compatible = "microchip,sam9x60-trng",210		.data = &sam9x60_config,211	}, {212		/* sentinel */213	}214};215MODULE_DEVICE_TABLE(of, atmel_trng_dt_ids);216 217static struct platform_driver atmel_trng_driver = {218	.probe		= atmel_trng_probe,219	.remove_new	= atmel_trng_remove,220	.driver		= {221		.name	= "atmel-trng",222		.pm	= pm_ptr(&atmel_trng_pm_ops),223		.of_match_table = atmel_trng_dt_ids,224	},225};226 227module_platform_driver(atmel_trng_driver);228 229MODULE_LICENSE("GPL");230MODULE_AUTHOR("Peter Korsgaard <jacmet@sunsite.dk>");231MODULE_DESCRIPTION("Atmel true random number generator driver");232