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