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