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