1 | // SPDX-License-Identifier: GPL-2.0-or-later OR MIT |
2 | /* |
3 | * Copyright (c) 2023 David Yang |
4 | */ |
5 | |
6 | #include <linux/err.h> |
7 | #include <linux/hw_random.h> |
8 | #include <linux/io.h> |
9 | #include <linux/iopoll.h> |
10 | #include <linux/kernel.h> |
11 | #include <linux/mod_devicetable.h> |
12 | #include <linux/module.h> |
13 | #include <linux/platform_device.h> |
14 | |
15 | #define RNG_CTRL 0x0 |
16 | #define RNG_SOURCE GENMASK(1, 0) |
17 | #define DROP_ENABLE BIT(5) |
18 | #define POST_PROCESS_ENABLE BIT(7) |
19 | #define POST_PROCESS_DEPTH GENMASK(15, 8) |
20 | #define RNG_NUMBER 0x4 |
21 | #define RNG_STAT 0x8 |
22 | #define DATA_COUNT GENMASK(2, 0) /* max 4 */ |
23 | |
24 | struct histb_rng_priv { |
25 | struct hwrng rng; |
26 | void __iomem *base; |
27 | }; |
28 | |
29 | /* |
30 | * Observed: |
31 | * depth = 1 -> ~1ms |
32 | * depth = 255 -> ~16ms |
33 | */ |
34 | static int histb_rng_wait(void __iomem *base) |
35 | { |
36 | u32 val; |
37 | |
38 | return readl_relaxed_poll_timeout(base + RNG_STAT, val, |
39 | val & DATA_COUNT, 1000, 30 * 1000); |
40 | } |
41 | |
42 | static void histb_rng_init(void __iomem *base, unsigned int depth) |
43 | { |
44 | u32 val; |
45 | |
46 | val = readl_relaxed(base + RNG_CTRL); |
47 | |
48 | val &= ~RNG_SOURCE; |
49 | val |= 2; |
50 | |
51 | val &= ~POST_PROCESS_DEPTH; |
52 | val |= min(depth, 0xffu) << 8; |
53 | |
54 | val |= POST_PROCESS_ENABLE; |
55 | val |= DROP_ENABLE; |
56 | |
57 | writel_relaxed(val, base + RNG_CTRL); |
58 | } |
59 | |
60 | static int histb_rng_read(struct hwrng *rng, void *data, size_t max, bool wait) |
61 | { |
62 | struct histb_rng_priv *priv = container_of(rng, typeof(*priv), rng); |
63 | void __iomem *base = priv->base; |
64 | |
65 | for (int i = 0; i < max; i += sizeof(u32)) { |
66 | if (!(readl_relaxed(base + RNG_STAT) & DATA_COUNT)) { |
67 | if (!wait) |
68 | return i; |
69 | if (histb_rng_wait(base)) { |
70 | pr_err("failed to generate random number, generated %d\n" , |
71 | i); |
72 | return i ? i : -ETIMEDOUT; |
73 | } |
74 | } |
75 | *(u32 *) (data + i) = readl_relaxed(base + RNG_NUMBER); |
76 | } |
77 | |
78 | return max; |
79 | } |
80 | |
81 | static unsigned int histb_rng_get_depth(void __iomem *base) |
82 | { |
83 | return (readl_relaxed(base + RNG_CTRL) & POST_PROCESS_DEPTH) >> 8; |
84 | } |
85 | |
86 | static ssize_t |
87 | depth_show(struct device *dev, struct device_attribute *attr, char *buf) |
88 | { |
89 | struct histb_rng_priv *priv = dev_get_drvdata(dev); |
90 | void __iomem *base = priv->base; |
91 | |
92 | return sprintf(buf, fmt: "%d\n" , histb_rng_get_depth(base)); |
93 | } |
94 | |
95 | static ssize_t |
96 | depth_store(struct device *dev, struct device_attribute *attr, |
97 | const char *buf, size_t count) |
98 | { |
99 | struct histb_rng_priv *priv = dev_get_drvdata(dev); |
100 | void __iomem *base = priv->base; |
101 | unsigned int depth; |
102 | |
103 | if (kstrtouint(s: buf, base: 0, res: &depth)) |
104 | return -ERANGE; |
105 | |
106 | histb_rng_init(base, depth); |
107 | return count; |
108 | } |
109 | |
110 | static DEVICE_ATTR_RW(depth); |
111 | |
112 | static struct attribute *histb_rng_attrs[] = { |
113 | &dev_attr_depth.attr, |
114 | NULL, |
115 | }; |
116 | |
117 | ATTRIBUTE_GROUPS(histb_rng); |
118 | |
119 | static int histb_rng_probe(struct platform_device *pdev) |
120 | { |
121 | struct device *dev = &pdev->dev; |
122 | struct histb_rng_priv *priv; |
123 | void __iomem *base; |
124 | int ret; |
125 | |
126 | priv = devm_kzalloc(dev, size: sizeof(*priv), GFP_KERNEL); |
127 | if (!priv) |
128 | return -ENOMEM; |
129 | |
130 | base = devm_platform_ioremap_resource(pdev, index: 0); |
131 | if (IS_ERR(ptr: base)) |
132 | return PTR_ERR(ptr: base); |
133 | |
134 | histb_rng_init(base, depth: 144); |
135 | if (histb_rng_wait(base)) { |
136 | dev_err(dev, "cannot bring up device\n" ); |
137 | return -ENODEV; |
138 | } |
139 | |
140 | priv->base = base; |
141 | priv->rng.name = pdev->name; |
142 | priv->rng.read = histb_rng_read; |
143 | ret = devm_hwrng_register(dev, rng: &priv->rng); |
144 | if (ret) { |
145 | dev_err(dev, "failed to register hwrng: %d\n" , ret); |
146 | return ret; |
147 | } |
148 | |
149 | platform_set_drvdata(pdev, data: priv); |
150 | dev_set_drvdata(dev, data: priv); |
151 | return 0; |
152 | } |
153 | |
154 | static const struct of_device_id histb_rng_of_match[] = { |
155 | { .compatible = "hisilicon,histb-rng" , }, |
156 | { } |
157 | }; |
158 | MODULE_DEVICE_TABLE(of, histb_rng_of_match); |
159 | |
160 | static struct platform_driver histb_rng_driver = { |
161 | .probe = histb_rng_probe, |
162 | .driver = { |
163 | .name = "histb-rng" , |
164 | .of_match_table = histb_rng_of_match, |
165 | .dev_groups = histb_rng_groups, |
166 | }, |
167 | }; |
168 | |
169 | module_platform_driver(histb_rng_driver); |
170 | |
171 | MODULE_DESCRIPTION("Hisilicon STB random number generator driver" ); |
172 | MODULE_LICENSE("Dual MIT/GPL" ); |
173 | MODULE_AUTHOR("David Yang <mmyangfl@gmail.com>" ); |
174 | |