18c2ecf20Sopenharmony_ci/*
28c2ecf20Sopenharmony_ci * Copyright (c) 2011 Peter Korsgaard <jacmet@sunsite.dk>
38c2ecf20Sopenharmony_ci *
48c2ecf20Sopenharmony_ci * This file is licensed under  the terms of the GNU General Public
58c2ecf20Sopenharmony_ci * License version 2. This program is licensed "as is" without any
68c2ecf20Sopenharmony_ci * warranty of any kind, whether express or implied.
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include <linux/kernel.h>
108c2ecf20Sopenharmony_ci#include <linux/module.h>
118c2ecf20Sopenharmony_ci#include <linux/mod_devicetable.h>
128c2ecf20Sopenharmony_ci#include <linux/slab.h>
138c2ecf20Sopenharmony_ci#include <linux/err.h>
148c2ecf20Sopenharmony_ci#include <linux/clk.h>
158c2ecf20Sopenharmony_ci#include <linux/io.h>
168c2ecf20Sopenharmony_ci#include <linux/hw_random.h>
178c2ecf20Sopenharmony_ci#include <linux/of_device.h>
188c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci#define TRNG_CR		0x00
218c2ecf20Sopenharmony_ci#define TRNG_MR		0x04
228c2ecf20Sopenharmony_ci#define TRNG_ISR	0x1c
238c2ecf20Sopenharmony_ci#define TRNG_ODATA	0x50
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci#define TRNG_KEY	0x524e4700 /* RNG */
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_ci#define TRNG_HALFR	BIT(0) /* generate RN every 168 cycles */
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_cistruct atmel_trng_data {
308c2ecf20Sopenharmony_ci	bool has_half_rate;
318c2ecf20Sopenharmony_ci};
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_cistruct atmel_trng {
348c2ecf20Sopenharmony_ci	struct clk *clk;
358c2ecf20Sopenharmony_ci	void __iomem *base;
368c2ecf20Sopenharmony_ci	struct hwrng rng;
378c2ecf20Sopenharmony_ci};
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_cistatic int atmel_trng_read(struct hwrng *rng, void *buf, size_t max,
408c2ecf20Sopenharmony_ci			   bool wait)
418c2ecf20Sopenharmony_ci{
428c2ecf20Sopenharmony_ci	struct atmel_trng *trng = container_of(rng, struct atmel_trng, rng);
438c2ecf20Sopenharmony_ci	u32 *data = buf;
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ci	/* data ready? */
468c2ecf20Sopenharmony_ci	if (readl(trng->base + TRNG_ISR) & 1) {
478c2ecf20Sopenharmony_ci		*data = readl(trng->base + TRNG_ODATA);
488c2ecf20Sopenharmony_ci		/*
498c2ecf20Sopenharmony_ci		  ensure data ready is only set again AFTER the next data
508c2ecf20Sopenharmony_ci		  word is ready in case it got set between checking ISR
518c2ecf20Sopenharmony_ci		  and reading ODATA, so we don't risk re-reading the
528c2ecf20Sopenharmony_ci		  same word
538c2ecf20Sopenharmony_ci		*/
548c2ecf20Sopenharmony_ci		readl(trng->base + TRNG_ISR);
558c2ecf20Sopenharmony_ci		return 4;
568c2ecf20Sopenharmony_ci	} else
578c2ecf20Sopenharmony_ci		return 0;
588c2ecf20Sopenharmony_ci}
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_cistatic void atmel_trng_enable(struct atmel_trng *trng)
618c2ecf20Sopenharmony_ci{
628c2ecf20Sopenharmony_ci	writel(TRNG_KEY | 1, trng->base + TRNG_CR);
638c2ecf20Sopenharmony_ci}
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_cistatic void atmel_trng_disable(struct atmel_trng *trng)
668c2ecf20Sopenharmony_ci{
678c2ecf20Sopenharmony_ci	writel(TRNG_KEY, trng->base + TRNG_CR);
688c2ecf20Sopenharmony_ci}
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_cistatic int atmel_trng_probe(struct platform_device *pdev)
718c2ecf20Sopenharmony_ci{
728c2ecf20Sopenharmony_ci	struct atmel_trng *trng;
738c2ecf20Sopenharmony_ci	const struct atmel_trng_data *data;
748c2ecf20Sopenharmony_ci	int ret;
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci	trng = devm_kzalloc(&pdev->dev, sizeof(*trng), GFP_KERNEL);
778c2ecf20Sopenharmony_ci	if (!trng)
788c2ecf20Sopenharmony_ci		return -ENOMEM;
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_ci	trng->base = devm_platform_ioremap_resource(pdev, 0);
818c2ecf20Sopenharmony_ci	if (IS_ERR(trng->base))
828c2ecf20Sopenharmony_ci		return PTR_ERR(trng->base);
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci	trng->clk = devm_clk_get(&pdev->dev, NULL);
858c2ecf20Sopenharmony_ci	if (IS_ERR(trng->clk))
868c2ecf20Sopenharmony_ci		return PTR_ERR(trng->clk);
878c2ecf20Sopenharmony_ci	data = of_device_get_match_data(&pdev->dev);
888c2ecf20Sopenharmony_ci	if (!data)
898c2ecf20Sopenharmony_ci		return -ENODEV;
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci	if (data->has_half_rate) {
928c2ecf20Sopenharmony_ci		unsigned long rate = clk_get_rate(trng->clk);
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci		/* if peripheral clk is above 100MHz, set HALFR */
958c2ecf20Sopenharmony_ci		if (rate > 100000000)
968c2ecf20Sopenharmony_ci			writel(TRNG_HALFR, trng->base + TRNG_MR);
978c2ecf20Sopenharmony_ci	}
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	ret = clk_prepare_enable(trng->clk);
1008c2ecf20Sopenharmony_ci	if (ret)
1018c2ecf20Sopenharmony_ci		return ret;
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci	atmel_trng_enable(trng);
1048c2ecf20Sopenharmony_ci	trng->rng.name = pdev->name;
1058c2ecf20Sopenharmony_ci	trng->rng.read = atmel_trng_read;
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	ret = devm_hwrng_register(&pdev->dev, &trng->rng);
1088c2ecf20Sopenharmony_ci	if (ret)
1098c2ecf20Sopenharmony_ci		goto err_register;
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, trng);
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_ci	return 0;
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_cierr_register:
1168c2ecf20Sopenharmony_ci	clk_disable_unprepare(trng->clk);
1178c2ecf20Sopenharmony_ci	atmel_trng_disable(trng);
1188c2ecf20Sopenharmony_ci	return ret;
1198c2ecf20Sopenharmony_ci}
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_cistatic int atmel_trng_remove(struct platform_device *pdev)
1228c2ecf20Sopenharmony_ci{
1238c2ecf20Sopenharmony_ci	struct atmel_trng *trng = platform_get_drvdata(pdev);
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ci	atmel_trng_disable(trng);
1278c2ecf20Sopenharmony_ci	clk_disable_unprepare(trng->clk);
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_ci	return 0;
1308c2ecf20Sopenharmony_ci}
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci#ifdef CONFIG_PM
1338c2ecf20Sopenharmony_cistatic int atmel_trng_suspend(struct device *dev)
1348c2ecf20Sopenharmony_ci{
1358c2ecf20Sopenharmony_ci	struct atmel_trng *trng = dev_get_drvdata(dev);
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	atmel_trng_disable(trng);
1388c2ecf20Sopenharmony_ci	clk_disable_unprepare(trng->clk);
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci	return 0;
1418c2ecf20Sopenharmony_ci}
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_cistatic int atmel_trng_resume(struct device *dev)
1448c2ecf20Sopenharmony_ci{
1458c2ecf20Sopenharmony_ci	struct atmel_trng *trng = dev_get_drvdata(dev);
1468c2ecf20Sopenharmony_ci	int ret;
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci	ret = clk_prepare_enable(trng->clk);
1498c2ecf20Sopenharmony_ci	if (ret)
1508c2ecf20Sopenharmony_ci		return ret;
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_ci	atmel_trng_enable(trng);
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ci	return 0;
1558c2ecf20Sopenharmony_ci}
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_cistatic const struct dev_pm_ops atmel_trng_pm_ops = {
1588c2ecf20Sopenharmony_ci	.suspend	= atmel_trng_suspend,
1598c2ecf20Sopenharmony_ci	.resume		= atmel_trng_resume,
1608c2ecf20Sopenharmony_ci};
1618c2ecf20Sopenharmony_ci#endif /* CONFIG_PM */
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_cistatic const struct atmel_trng_data at91sam9g45_config = {
1648c2ecf20Sopenharmony_ci	.has_half_rate = false,
1658c2ecf20Sopenharmony_ci};
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_cistatic const struct atmel_trng_data sam9x60_config = {
1688c2ecf20Sopenharmony_ci	.has_half_rate = true,
1698c2ecf20Sopenharmony_ci};
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_cistatic const struct of_device_id atmel_trng_dt_ids[] = {
1728c2ecf20Sopenharmony_ci	{
1738c2ecf20Sopenharmony_ci		.compatible = "atmel,at91sam9g45-trng",
1748c2ecf20Sopenharmony_ci		.data = &at91sam9g45_config,
1758c2ecf20Sopenharmony_ci	}, {
1768c2ecf20Sopenharmony_ci		.compatible = "microchip,sam9x60-trng",
1778c2ecf20Sopenharmony_ci		.data = &sam9x60_config,
1788c2ecf20Sopenharmony_ci	}, {
1798c2ecf20Sopenharmony_ci		/* sentinel */
1808c2ecf20Sopenharmony_ci	}
1818c2ecf20Sopenharmony_ci};
1828c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, atmel_trng_dt_ids);
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_cistatic struct platform_driver atmel_trng_driver = {
1858c2ecf20Sopenharmony_ci	.probe		= atmel_trng_probe,
1868c2ecf20Sopenharmony_ci	.remove		= atmel_trng_remove,
1878c2ecf20Sopenharmony_ci	.driver		= {
1888c2ecf20Sopenharmony_ci		.name	= "atmel-trng",
1898c2ecf20Sopenharmony_ci#ifdef CONFIG_PM
1908c2ecf20Sopenharmony_ci		.pm	= &atmel_trng_pm_ops,
1918c2ecf20Sopenharmony_ci#endif /* CONFIG_PM */
1928c2ecf20Sopenharmony_ci		.of_match_table = atmel_trng_dt_ids,
1938c2ecf20Sopenharmony_ci	},
1948c2ecf20Sopenharmony_ci};
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_cimodule_platform_driver(atmel_trng_driver);
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
1998c2ecf20Sopenharmony_ciMODULE_AUTHOR("Peter Korsgaard <jacmet@sunsite.dk>");
2008c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Atmel true random number generator driver");
201