/* * Copyright (c) 2011 Peter Korsgaard * * This file is licensed under the terms of the GNU General Public * License version 2. This program is licensed "as is" without any * warranty of any kind, whether express or implied. */ #include #include #include #include #include #include #include #include #include #include #include #include #define TRNG_CR 0x00 #define TRNG_MR 0x04 #define TRNG_ISR 0x1c #define TRNG_ISR_DATRDY BIT(0) #define TRNG_ODATA 0x50 #define TRNG_KEY 0x524e4700 /* RNG */ #define TRNG_HALFR BIT(0) /* generate RN every 168 cycles */ struct atmel_trng_data { bool has_half_rate; }; struct atmel_trng { struct clk *clk; void __iomem *base; struct hwrng rng; bool has_half_rate; }; static bool atmel_trng_wait_ready(struct atmel_trng *trng, bool wait) { int ready; ready = readl(trng->base + TRNG_ISR) & TRNG_ISR_DATRDY; if (!ready && wait) readl_poll_timeout(trng->base + TRNG_ISR, ready, ready & TRNG_ISR_DATRDY, 1000, 20000); return !!ready; } static int atmel_trng_read(struct hwrng *rng, void *buf, size_t max, bool wait) { struct atmel_trng *trng = container_of(rng, struct atmel_trng, rng); u32 *data = buf; int ret; ret = pm_runtime_get_sync((struct device *)trng->rng.priv); if (ret < 0) { pm_runtime_put_sync((struct device *)trng->rng.priv); return ret; } ret = atmel_trng_wait_ready(trng, wait); if (!ret) goto out; *data = readl(trng->base + TRNG_ODATA); /* * ensure data ready is only set again AFTER the next data word is ready * in case it got set between checking ISR and reading ODATA, so we * don't risk re-reading the same word */ readl(trng->base + TRNG_ISR); ret = 4; out: pm_runtime_mark_last_busy((struct device *)trng->rng.priv); pm_runtime_put_sync_autosuspend((struct device *)trng->rng.priv); return ret; } static int atmel_trng_init(struct atmel_trng *trng) { unsigned long rate; int ret; ret = clk_prepare_enable(trng->clk); if (ret) return ret; if (trng->has_half_rate) { rate = clk_get_rate(trng->clk); /* if peripheral clk is above 100MHz, set HALFR */ if (rate > 100000000) writel(TRNG_HALFR, trng->base + TRNG_MR); } writel(TRNG_KEY | 1, trng->base + TRNG_CR); return 0; } static void atmel_trng_cleanup(struct atmel_trng *trng) { writel(TRNG_KEY, trng->base + TRNG_CR); clk_disable_unprepare(trng->clk); } static int atmel_trng_probe(struct platform_device *pdev) { struct atmel_trng *trng; const struct atmel_trng_data *data; int ret; trng = devm_kzalloc(&pdev->dev, sizeof(*trng), GFP_KERNEL); if (!trng) return -ENOMEM; trng->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(trng->base)) return PTR_ERR(trng->base); trng->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(trng->clk)) return PTR_ERR(trng->clk); data = of_device_get_match_data(&pdev->dev); if (!data) return -ENODEV; trng->has_half_rate = data->has_half_rate; trng->rng.name = pdev->name; trng->rng.read = atmel_trng_read; trng->rng.priv = (unsigned long)&pdev->dev; platform_set_drvdata(pdev, trng); #ifndef CONFIG_PM ret = atmel_trng_init(trng); if (ret) return ret; #endif pm_runtime_set_autosuspend_delay(&pdev->dev, 100); pm_runtime_use_autosuspend(&pdev->dev); pm_runtime_enable(&pdev->dev); ret = devm_hwrng_register(&pdev->dev, &trng->rng); if (ret) { pm_runtime_disable(&pdev->dev); pm_runtime_set_suspended(&pdev->dev); #ifndef CONFIG_PM atmel_trng_cleanup(trng); #endif } return ret; } static int atmel_trng_remove(struct platform_device *pdev) { struct atmel_trng *trng = platform_get_drvdata(pdev); atmel_trng_cleanup(trng); pm_runtime_disable(&pdev->dev); pm_runtime_set_suspended(&pdev->dev); return 0; } static int __maybe_unused atmel_trng_runtime_suspend(struct device *dev) { struct atmel_trng *trng = dev_get_drvdata(dev); atmel_trng_cleanup(trng); return 0; } static int __maybe_unused atmel_trng_runtime_resume(struct device *dev) { struct atmel_trng *trng = dev_get_drvdata(dev); return atmel_trng_init(trng); } static const struct dev_pm_ops __maybe_unused atmel_trng_pm_ops = { SET_RUNTIME_PM_OPS(atmel_trng_runtime_suspend, atmel_trng_runtime_resume, NULL) SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) }; static const struct atmel_trng_data at91sam9g45_config = { .has_half_rate = false, }; static const struct atmel_trng_data sam9x60_config = { .has_half_rate = true, }; static const struct of_device_id atmel_trng_dt_ids[] = { { .compatible = "atmel,at91sam9g45-trng", .data = &at91sam9g45_config, }, { .compatible = "microchip,sam9x60-trng", .data = &sam9x60_config, }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, atmel_trng_dt_ids); static struct platform_driver atmel_trng_driver = { .probe = atmel_trng_probe, .remove = atmel_trng_remove, .driver = { .name = "atmel-trng", .pm = pm_ptr(&atmel_trng_pm_ops), .of_match_table = atmel_trng_dt_ids, }, }; module_platform_driver(atmel_trng_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Peter Korsgaard "); MODULE_DESCRIPTION("Atmel true random number generator driver");