1 // SPDX-License-Identifier: GPL-2.0
2 // Copyright (c) 2017-18 Linaro Limited
3 //
4 // Based on msm-rng.c and downstream driver
5 
6 #include <crypto/internal/rng.h>
7 #include <linux/acpi.h>
8 #include <linux/clk.h>
9 #include <linux/crypto.h>
10 #include <linux/io.h>
11 #include <linux/module.h>
12 #include <linux/of.h>
13 #include <linux/platform_device.h>
14 
15 /* Device specific register offsets */
16 #define PRNG_DATA_OUT		0x0000
17 #define PRNG_STATUS		0x0004
18 #define PRNG_LFSR_CFG		0x0100
19 #define PRNG_CONFIG		0x0104
20 
21 /* Device specific register masks and config values */
22 #define PRNG_LFSR_CFG_MASK	0x0000ffff
23 #define PRNG_LFSR_CFG_CLOCKS	0x0000dddd
24 #define PRNG_CONFIG_HW_ENABLE	BIT(1)
25 #define PRNG_STATUS_DATA_AVAIL	BIT(0)
26 
27 #define WORD_SZ			4
28 
29 struct qcom_rng {
30 	struct mutex lock;
31 	void __iomem *base;
32 	struct clk *clk;
33 	unsigned int skip_init;
34 };
35 
36 struct qcom_rng_ctx {
37 	struct qcom_rng *rng;
38 };
39 
40 static struct qcom_rng *qcom_rng_dev;
41 
qcom_rng_read(struct qcom_rng * rng,u8 * data,unsigned int max)42 static int qcom_rng_read(struct qcom_rng *rng, u8 *data, unsigned int max)
43 {
44 	unsigned int currsize = 0;
45 	u32 val;
46 
47 	/* read random data from hardware */
48 	do {
49 		val = readl_relaxed(rng->base + PRNG_STATUS);
50 		if (!(val & PRNG_STATUS_DATA_AVAIL))
51 			break;
52 
53 		val = readl_relaxed(rng->base + PRNG_DATA_OUT);
54 		if (!val)
55 			break;
56 
57 		if ((max - currsize) >= WORD_SZ) {
58 			memcpy(data, &val, WORD_SZ);
59 			data += WORD_SZ;
60 			currsize += WORD_SZ;
61 		} else {
62 			/* copy only remaining bytes */
63 			memcpy(data, &val, max - currsize);
64 			break;
65 		}
66 	} while (currsize < max);
67 
68 	return currsize;
69 }
70 
qcom_rng_generate(struct crypto_rng * tfm,const u8 * src,unsigned int slen,u8 * dstn,unsigned int dlen)71 static int qcom_rng_generate(struct crypto_rng *tfm,
72 			     const u8 *src, unsigned int slen,
73 			     u8 *dstn, unsigned int dlen)
74 {
75 	struct qcom_rng_ctx *ctx = crypto_rng_ctx(tfm);
76 	struct qcom_rng *rng = ctx->rng;
77 	int ret;
78 
79 	ret = clk_prepare_enable(rng->clk);
80 	if (ret)
81 		return ret;
82 
83 	mutex_lock(&rng->lock);
84 
85 	ret = qcom_rng_read(rng, dstn, dlen);
86 
87 	mutex_unlock(&rng->lock);
88 	clk_disable_unprepare(rng->clk);
89 
90 	return 0;
91 }
92 
qcom_rng_seed(struct crypto_rng * tfm,const u8 * seed,unsigned int slen)93 static int qcom_rng_seed(struct crypto_rng *tfm, const u8 *seed,
94 			 unsigned int slen)
95 {
96 	return 0;
97 }
98 
qcom_rng_enable(struct qcom_rng * rng)99 static int qcom_rng_enable(struct qcom_rng *rng)
100 {
101 	u32 val;
102 	int ret;
103 
104 	ret = clk_prepare_enable(rng->clk);
105 	if (ret)
106 		return ret;
107 
108 	/* Enable PRNG only if it is not already enabled */
109 	val = readl_relaxed(rng->base + PRNG_CONFIG);
110 	if (val & PRNG_CONFIG_HW_ENABLE)
111 		goto already_enabled;
112 
113 	val = readl_relaxed(rng->base + PRNG_LFSR_CFG);
114 	val &= ~PRNG_LFSR_CFG_MASK;
115 	val |= PRNG_LFSR_CFG_CLOCKS;
116 	writel(val, rng->base + PRNG_LFSR_CFG);
117 
118 	val = readl_relaxed(rng->base + PRNG_CONFIG);
119 	val |= PRNG_CONFIG_HW_ENABLE;
120 	writel(val, rng->base + PRNG_CONFIG);
121 
122 already_enabled:
123 	clk_disable_unprepare(rng->clk);
124 
125 	return 0;
126 }
127 
qcom_rng_init(struct crypto_tfm * tfm)128 static int qcom_rng_init(struct crypto_tfm *tfm)
129 {
130 	struct qcom_rng_ctx *ctx = crypto_tfm_ctx(tfm);
131 
132 	ctx->rng = qcom_rng_dev;
133 
134 	if (!ctx->rng->skip_init)
135 		return qcom_rng_enable(ctx->rng);
136 
137 	return 0;
138 }
139 
140 static struct rng_alg qcom_rng_alg = {
141 	.generate	= qcom_rng_generate,
142 	.seed		= qcom_rng_seed,
143 	.seedsize	= 0,
144 	.base		= {
145 		.cra_name		= "stdrng",
146 		.cra_driver_name	= "qcom-rng",
147 		.cra_flags		= CRYPTO_ALG_TYPE_RNG,
148 		.cra_priority		= 300,
149 		.cra_ctxsize		= sizeof(struct qcom_rng_ctx),
150 		.cra_module		= THIS_MODULE,
151 		.cra_init		= qcom_rng_init,
152 	}
153 };
154 
qcom_rng_probe(struct platform_device * pdev)155 static int qcom_rng_probe(struct platform_device *pdev)
156 {
157 	struct qcom_rng *rng;
158 	int ret;
159 
160 	rng = devm_kzalloc(&pdev->dev, sizeof(*rng), GFP_KERNEL);
161 	if (!rng)
162 		return -ENOMEM;
163 
164 	platform_set_drvdata(pdev, rng);
165 	mutex_init(&rng->lock);
166 
167 	rng->base = devm_platform_ioremap_resource(pdev, 0);
168 	if (IS_ERR(rng->base))
169 		return PTR_ERR(rng->base);
170 
171 	/* ACPI systems have clk already on, so skip clk_get */
172 	if (!has_acpi_companion(&pdev->dev)) {
173 		rng->clk = devm_clk_get(&pdev->dev, "core");
174 		if (IS_ERR(rng->clk))
175 			return PTR_ERR(rng->clk);
176 	}
177 
178 
179 	rng->skip_init = (unsigned long)device_get_match_data(&pdev->dev);
180 
181 	qcom_rng_dev = rng;
182 	ret = crypto_register_rng(&qcom_rng_alg);
183 	if (ret) {
184 		dev_err(&pdev->dev, "Register crypto rng failed: %d\n", ret);
185 		qcom_rng_dev = NULL;
186 	}
187 
188 	return ret;
189 }
190 
qcom_rng_remove(struct platform_device * pdev)191 static int qcom_rng_remove(struct platform_device *pdev)
192 {
193 	crypto_unregister_rng(&qcom_rng_alg);
194 
195 	qcom_rng_dev = NULL;
196 
197 	return 0;
198 }
199 
200 #if IS_ENABLED(CONFIG_ACPI)
201 static const struct acpi_device_id qcom_rng_acpi_match[] = {
202 	{ .id = "QCOM8160", .driver_data = 1 },
203 	{}
204 };
205 MODULE_DEVICE_TABLE(acpi, qcom_rng_acpi_match);
206 #endif
207 
208 static const struct of_device_id qcom_rng_of_match[] = {
209 	{ .compatible = "qcom,prng", .data = (void *)0},
210 	{ .compatible = "qcom,prng-ee", .data = (void *)1},
211 	{}
212 };
213 MODULE_DEVICE_TABLE(of, qcom_rng_of_match);
214 
215 static struct platform_driver qcom_rng_driver = {
216 	.probe = qcom_rng_probe,
217 	.remove =  qcom_rng_remove,
218 	.driver = {
219 		.name = KBUILD_MODNAME,
220 		.of_match_table = of_match_ptr(qcom_rng_of_match),
221 		.acpi_match_table = ACPI_PTR(qcom_rng_acpi_match),
222 	}
223 };
224 module_platform_driver(qcom_rng_driver);
225 
226 MODULE_ALIAS("platform:" KBUILD_MODNAME);
227 MODULE_DESCRIPTION("Qualcomm random number generator driver");
228 MODULE_LICENSE("GPL v2");
229