1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * PRNG driver for Qualcomm IPQ40xx
4  *
5  * Copyright (c) 2020 Sartura Ltd.
6  *
7  * Author: Robert Marko <robert.marko@sartura.hr>
8  *
9  * Based on Linux driver
10  */
11 
12 #include <asm/io.h>
13 #include <clk.h>
14 #include <common.h>
15 #include <dm.h>
16 #include <linux/bitops.h>
17 #include <rng.h>
18 
19 /* Device specific register offsets */
20 #define PRNG_DATA_OUT		0x0000
21 #define PRNG_STATUS			0x0004
22 #define PRNG_LFSR_CFG		0x0100
23 #define PRNG_CONFIG			0x0104
24 
25 /* Device specific register masks and config values */
26 #define PRNG_LFSR_CFG_MASK		0x0000ffff
27 #define PRNG_LFSR_CFG_CLOCKS	0x0000dddd
28 #define PRNG_CONFIG_HW_ENABLE	BIT(1)
29 #define PRNG_STATUS_DATA_AVAIL	BIT(0)
30 
31 #define MAX_HW_FIFO_DEPTH		16
32 #define MAX_HW_FIFO_SIZE		(MAX_HW_FIFO_DEPTH * 4)
33 #define WORD_SZ					4
34 
35 struct msm_rng_priv {
36 	phys_addr_t base;
37 	struct clk clk;
38 };
39 
msm_rng_read(struct udevice * dev,void * data,size_t len)40 static int msm_rng_read(struct udevice *dev, void *data, size_t len)
41 {
42 	struct msm_rng_priv *priv = dev_get_priv(dev);
43 	size_t currsize = 0;
44 	u32 *retdata = data;
45 	size_t maxsize;
46 	u32 val;
47 
48 	/* calculate max size bytes to transfer back to caller */
49 	maxsize = min_t(size_t, MAX_HW_FIFO_SIZE, len);
50 
51 	/* read random data from hardware */
52 	do {
53 		val = readl_relaxed(priv->base + PRNG_STATUS);
54 		if (!(val & PRNG_STATUS_DATA_AVAIL))
55 			break;
56 
57 		val = readl_relaxed(priv->base + PRNG_DATA_OUT);
58 		if (!val)
59 			break;
60 
61 		*retdata++ = val;
62 		currsize += WORD_SZ;
63 
64 		/* make sure we stay on 32bit boundary */
65 		if ((maxsize - currsize) < WORD_SZ)
66 			break;
67 	} while (currsize < maxsize);
68 
69 	return 0;
70 }
71 
msm_rng_enable(struct msm_rng_priv * priv,int enable)72 static int msm_rng_enable(struct msm_rng_priv *priv, int enable)
73 {
74 	u32 val;
75 
76 	if (enable) {
77 		/* Enable PRNG only if it is not already enabled */
78 		val = readl_relaxed(priv->base + PRNG_CONFIG);
79 		if (val & PRNG_CONFIG_HW_ENABLE) {
80 			val = readl_relaxed(priv->base + PRNG_LFSR_CFG);
81 			val &= ~PRNG_LFSR_CFG_MASK;
82 			val |= PRNG_LFSR_CFG_CLOCKS;
83 			writel(val, priv->base + PRNG_LFSR_CFG);
84 
85 			val = readl_relaxed(priv->base + PRNG_CONFIG);
86 			val |= PRNG_CONFIG_HW_ENABLE;
87 			writel(val, priv->base + PRNG_CONFIG);
88 		}
89 	} else {
90 		val = readl_relaxed(priv->base + PRNG_CONFIG);
91 		val &= ~PRNG_CONFIG_HW_ENABLE;
92 		writel(val, priv->base + PRNG_CONFIG);
93 	}
94 
95 	return 0;
96 }
97 
msm_rng_probe(struct udevice * dev)98 static int msm_rng_probe(struct udevice *dev)
99 {
100 	struct msm_rng_priv *priv = dev_get_priv(dev);
101 
102 	int ret;
103 
104 	priv->base = dev_read_addr(dev);
105 	if (priv->base == FDT_ADDR_T_NONE)
106 		return -EINVAL;
107 
108 	ret = clk_get_by_index(dev, 0, &priv->clk);
109 	if (ret)
110 		return ret;
111 
112 	ret = clk_enable(&priv->clk);
113 	if (ret < 0)
114 		return ret;
115 
116 	return msm_rng_enable(priv, 1);
117 }
118 
msm_rng_remove(struct udevice * dev)119 static int msm_rng_remove(struct udevice *dev)
120 {
121 	struct msm_rng_priv *priv = dev_get_priv(dev);
122 
123 	return msm_rng_enable(priv, 0);
124 }
125 
126 static const struct dm_rng_ops msm_rng_ops = {
127 	.read = msm_rng_read,
128 };
129 
130 static const struct udevice_id msm_rng_match[] = {
131 	{ .compatible = "qcom,prng", },
132 	{},
133 };
134 
135 U_BOOT_DRIVER(msm_rng) = {
136 	.name = "msm-rng",
137 	.id = UCLASS_RNG,
138 	.of_match = msm_rng_match,
139 	.ops = &msm_rng_ops,
140 	.probe = msm_rng_probe,
141 	.remove = msm_rng_remove,
142 	.priv_auto	= sizeof(struct msm_rng_priv),
143 };
144