18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci// Copyright (c) 2017-18 Linaro Limited 38c2ecf20Sopenharmony_ci// 48c2ecf20Sopenharmony_ci// Based on msm-rng.c and downstream driver 58c2ecf20Sopenharmony_ci 68c2ecf20Sopenharmony_ci#include <crypto/internal/rng.h> 78c2ecf20Sopenharmony_ci#include <linux/acpi.h> 88c2ecf20Sopenharmony_ci#include <linux/clk.h> 98c2ecf20Sopenharmony_ci#include <linux/crypto.h> 108c2ecf20Sopenharmony_ci#include <linux/io.h> 118c2ecf20Sopenharmony_ci#include <linux/iopoll.h> 128c2ecf20Sopenharmony_ci#include <linux/module.h> 138c2ecf20Sopenharmony_ci#include <linux/of.h> 148c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci/* Device specific register offsets */ 178c2ecf20Sopenharmony_ci#define PRNG_DATA_OUT 0x0000 188c2ecf20Sopenharmony_ci#define PRNG_STATUS 0x0004 198c2ecf20Sopenharmony_ci#define PRNG_LFSR_CFG 0x0100 208c2ecf20Sopenharmony_ci#define PRNG_CONFIG 0x0104 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci/* Device specific register masks and config values */ 238c2ecf20Sopenharmony_ci#define PRNG_LFSR_CFG_MASK 0x0000ffff 248c2ecf20Sopenharmony_ci#define PRNG_LFSR_CFG_CLOCKS 0x0000dddd 258c2ecf20Sopenharmony_ci#define PRNG_CONFIG_HW_ENABLE BIT(1) 268c2ecf20Sopenharmony_ci#define PRNG_STATUS_DATA_AVAIL BIT(0) 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci#define WORD_SZ 4 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_cistruct qcom_rng { 318c2ecf20Sopenharmony_ci struct mutex lock; 328c2ecf20Sopenharmony_ci void __iomem *base; 338c2ecf20Sopenharmony_ci struct clk *clk; 348c2ecf20Sopenharmony_ci unsigned int skip_init; 358c2ecf20Sopenharmony_ci}; 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_cistruct qcom_rng_ctx { 388c2ecf20Sopenharmony_ci struct qcom_rng *rng; 398c2ecf20Sopenharmony_ci}; 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_cistatic struct qcom_rng *qcom_rng_dev; 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_cistatic int qcom_rng_read(struct qcom_rng *rng, u8 *data, unsigned int max) 448c2ecf20Sopenharmony_ci{ 458c2ecf20Sopenharmony_ci unsigned int currsize = 0; 468c2ecf20Sopenharmony_ci u32 val; 478c2ecf20Sopenharmony_ci int ret; 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci /* read random data from hardware */ 508c2ecf20Sopenharmony_ci do { 518c2ecf20Sopenharmony_ci ret = readl_poll_timeout(rng->base + PRNG_STATUS, val, 528c2ecf20Sopenharmony_ci val & PRNG_STATUS_DATA_AVAIL, 538c2ecf20Sopenharmony_ci 200, 10000); 548c2ecf20Sopenharmony_ci if (ret) 558c2ecf20Sopenharmony_ci return ret; 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci val = readl_relaxed(rng->base + PRNG_DATA_OUT); 588c2ecf20Sopenharmony_ci if (!val) 598c2ecf20Sopenharmony_ci return -EINVAL; 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci if ((max - currsize) >= WORD_SZ) { 628c2ecf20Sopenharmony_ci memcpy(data, &val, WORD_SZ); 638c2ecf20Sopenharmony_ci data += WORD_SZ; 648c2ecf20Sopenharmony_ci currsize += WORD_SZ; 658c2ecf20Sopenharmony_ci } else { 668c2ecf20Sopenharmony_ci /* copy only remaining bytes */ 678c2ecf20Sopenharmony_ci memcpy(data, &val, max - currsize); 688c2ecf20Sopenharmony_ci break; 698c2ecf20Sopenharmony_ci } 708c2ecf20Sopenharmony_ci } while (currsize < max); 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci return 0; 738c2ecf20Sopenharmony_ci} 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_cistatic int qcom_rng_generate(struct crypto_rng *tfm, 768c2ecf20Sopenharmony_ci const u8 *src, unsigned int slen, 778c2ecf20Sopenharmony_ci u8 *dstn, unsigned int dlen) 788c2ecf20Sopenharmony_ci{ 798c2ecf20Sopenharmony_ci struct qcom_rng_ctx *ctx = crypto_rng_ctx(tfm); 808c2ecf20Sopenharmony_ci struct qcom_rng *rng = ctx->rng; 818c2ecf20Sopenharmony_ci int ret; 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci ret = clk_prepare_enable(rng->clk); 848c2ecf20Sopenharmony_ci if (ret) 858c2ecf20Sopenharmony_ci return ret; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci mutex_lock(&rng->lock); 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci ret = qcom_rng_read(rng, dstn, dlen); 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci mutex_unlock(&rng->lock); 928c2ecf20Sopenharmony_ci clk_disable_unprepare(rng->clk); 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci return ret; 958c2ecf20Sopenharmony_ci} 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_cistatic int qcom_rng_seed(struct crypto_rng *tfm, const u8 *seed, 988c2ecf20Sopenharmony_ci unsigned int slen) 998c2ecf20Sopenharmony_ci{ 1008c2ecf20Sopenharmony_ci return 0; 1018c2ecf20Sopenharmony_ci} 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_cistatic int qcom_rng_enable(struct qcom_rng *rng) 1048c2ecf20Sopenharmony_ci{ 1058c2ecf20Sopenharmony_ci u32 val; 1068c2ecf20Sopenharmony_ci int ret; 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci ret = clk_prepare_enable(rng->clk); 1098c2ecf20Sopenharmony_ci if (ret) 1108c2ecf20Sopenharmony_ci return ret; 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci /* Enable PRNG only if it is not already enabled */ 1138c2ecf20Sopenharmony_ci val = readl_relaxed(rng->base + PRNG_CONFIG); 1148c2ecf20Sopenharmony_ci if (val & PRNG_CONFIG_HW_ENABLE) 1158c2ecf20Sopenharmony_ci goto already_enabled; 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci val = readl_relaxed(rng->base + PRNG_LFSR_CFG); 1188c2ecf20Sopenharmony_ci val &= ~PRNG_LFSR_CFG_MASK; 1198c2ecf20Sopenharmony_ci val |= PRNG_LFSR_CFG_CLOCKS; 1208c2ecf20Sopenharmony_ci writel(val, rng->base + PRNG_LFSR_CFG); 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci val = readl_relaxed(rng->base + PRNG_CONFIG); 1238c2ecf20Sopenharmony_ci val |= PRNG_CONFIG_HW_ENABLE; 1248c2ecf20Sopenharmony_ci writel(val, rng->base + PRNG_CONFIG); 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_cialready_enabled: 1278c2ecf20Sopenharmony_ci clk_disable_unprepare(rng->clk); 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci return 0; 1308c2ecf20Sopenharmony_ci} 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_cistatic int qcom_rng_init(struct crypto_tfm *tfm) 1338c2ecf20Sopenharmony_ci{ 1348c2ecf20Sopenharmony_ci struct qcom_rng_ctx *ctx = crypto_tfm_ctx(tfm); 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci ctx->rng = qcom_rng_dev; 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci if (!ctx->rng->skip_init) 1398c2ecf20Sopenharmony_ci return qcom_rng_enable(ctx->rng); 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci return 0; 1428c2ecf20Sopenharmony_ci} 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_cistatic struct rng_alg qcom_rng_alg = { 1458c2ecf20Sopenharmony_ci .generate = qcom_rng_generate, 1468c2ecf20Sopenharmony_ci .seed = qcom_rng_seed, 1478c2ecf20Sopenharmony_ci .seedsize = 0, 1488c2ecf20Sopenharmony_ci .base = { 1498c2ecf20Sopenharmony_ci .cra_name = "stdrng", 1508c2ecf20Sopenharmony_ci .cra_driver_name = "qcom-rng", 1518c2ecf20Sopenharmony_ci .cra_flags = CRYPTO_ALG_TYPE_RNG, 1528c2ecf20Sopenharmony_ci .cra_priority = 300, 1538c2ecf20Sopenharmony_ci .cra_ctxsize = sizeof(struct qcom_rng_ctx), 1548c2ecf20Sopenharmony_ci .cra_module = THIS_MODULE, 1558c2ecf20Sopenharmony_ci .cra_init = qcom_rng_init, 1568c2ecf20Sopenharmony_ci } 1578c2ecf20Sopenharmony_ci}; 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_cistatic int qcom_rng_probe(struct platform_device *pdev) 1608c2ecf20Sopenharmony_ci{ 1618c2ecf20Sopenharmony_ci struct qcom_rng *rng; 1628c2ecf20Sopenharmony_ci int ret; 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci rng = devm_kzalloc(&pdev->dev, sizeof(*rng), GFP_KERNEL); 1658c2ecf20Sopenharmony_ci if (!rng) 1668c2ecf20Sopenharmony_ci return -ENOMEM; 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, rng); 1698c2ecf20Sopenharmony_ci mutex_init(&rng->lock); 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci rng->base = devm_platform_ioremap_resource(pdev, 0); 1728c2ecf20Sopenharmony_ci if (IS_ERR(rng->base)) 1738c2ecf20Sopenharmony_ci return PTR_ERR(rng->base); 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci /* ACPI systems have clk already on, so skip clk_get */ 1768c2ecf20Sopenharmony_ci if (!has_acpi_companion(&pdev->dev)) { 1778c2ecf20Sopenharmony_ci rng->clk = devm_clk_get(&pdev->dev, "core"); 1788c2ecf20Sopenharmony_ci if (IS_ERR(rng->clk)) 1798c2ecf20Sopenharmony_ci return PTR_ERR(rng->clk); 1808c2ecf20Sopenharmony_ci } 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci rng->skip_init = (unsigned long)device_get_match_data(&pdev->dev); 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci qcom_rng_dev = rng; 1868c2ecf20Sopenharmony_ci ret = crypto_register_rng(&qcom_rng_alg); 1878c2ecf20Sopenharmony_ci if (ret) { 1888c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Register crypto rng failed: %d\n", ret); 1898c2ecf20Sopenharmony_ci qcom_rng_dev = NULL; 1908c2ecf20Sopenharmony_ci } 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci return ret; 1938c2ecf20Sopenharmony_ci} 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_cistatic int qcom_rng_remove(struct platform_device *pdev) 1968c2ecf20Sopenharmony_ci{ 1978c2ecf20Sopenharmony_ci crypto_unregister_rng(&qcom_rng_alg); 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_ci qcom_rng_dev = NULL; 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci return 0; 2028c2ecf20Sopenharmony_ci} 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci#if IS_ENABLED(CONFIG_ACPI) 2058c2ecf20Sopenharmony_cistatic const struct acpi_device_id qcom_rng_acpi_match[] = { 2068c2ecf20Sopenharmony_ci { .id = "QCOM8160", .driver_data = 1 }, 2078c2ecf20Sopenharmony_ci {} 2088c2ecf20Sopenharmony_ci}; 2098c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, qcom_rng_acpi_match); 2108c2ecf20Sopenharmony_ci#endif 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_cistatic const struct of_device_id qcom_rng_of_match[] = { 2138c2ecf20Sopenharmony_ci { .compatible = "qcom,prng", .data = (void *)0}, 2148c2ecf20Sopenharmony_ci { .compatible = "qcom,prng-ee", .data = (void *)1}, 2158c2ecf20Sopenharmony_ci {} 2168c2ecf20Sopenharmony_ci}; 2178c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, qcom_rng_of_match); 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_cistatic struct platform_driver qcom_rng_driver = { 2208c2ecf20Sopenharmony_ci .probe = qcom_rng_probe, 2218c2ecf20Sopenharmony_ci .remove = qcom_rng_remove, 2228c2ecf20Sopenharmony_ci .driver = { 2238c2ecf20Sopenharmony_ci .name = KBUILD_MODNAME, 2248c2ecf20Sopenharmony_ci .of_match_table = of_match_ptr(qcom_rng_of_match), 2258c2ecf20Sopenharmony_ci .acpi_match_table = ACPI_PTR(qcom_rng_acpi_match), 2268c2ecf20Sopenharmony_ci } 2278c2ecf20Sopenharmony_ci}; 2288c2ecf20Sopenharmony_cimodule_platform_driver(qcom_rng_driver); 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:" KBUILD_MODNAME); 2318c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Qualcomm random number generator driver"); 2328c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 233