1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // Copyright (c) 2019 Nuvoton Technology corporation. |
3 | |
4 | #include <linux/kernel.h> |
5 | #include <linux/module.h> |
6 | #include <linux/io.h> |
7 | #include <linux/iopoll.h> |
8 | #include <linux/init.h> |
9 | #include <linux/random.h> |
10 | #include <linux/err.h> |
11 | #include <linux/of.h> |
12 | #include <linux/platform_device.h> |
13 | #include <linux/hw_random.h> |
14 | #include <linux/delay.h> |
15 | #include <linux/pm_runtime.h> |
16 | |
17 | #define NPCM_RNGCS_REG 0x00 /* Control and status register */ |
18 | #define NPCM_RNGD_REG 0x04 /* Data register */ |
19 | #define NPCM_RNGMODE_REG 0x08 /* Mode register */ |
20 | |
21 | #define NPCM_RNG_CLK_SET_62_5MHZ BIT(2) /* 60-80 MHz */ |
22 | #define NPCM_RNG_CLK_SET_25MHZ GENMASK(4, 3) /* 20-25 MHz */ |
23 | #define NPCM_RNG_DATA_VALID BIT(1) |
24 | #define NPCM_RNG_ENABLE BIT(0) |
25 | #define NPCM_RNG_M1ROSEL BIT(1) |
26 | |
27 | #define NPCM_RNG_TIMEOUT_USEC 20000 |
28 | #define NPCM_RNG_POLL_USEC 1000 |
29 | |
30 | #define to_npcm_rng(p) container_of(p, struct npcm_rng, rng) |
31 | |
32 | struct npcm_rng { |
33 | void __iomem *base; |
34 | struct hwrng rng; |
35 | u32 clkp; |
36 | }; |
37 | |
38 | static int npcm_rng_init(struct hwrng *rng) |
39 | { |
40 | struct npcm_rng *priv = to_npcm_rng(rng); |
41 | |
42 | writel(val: priv->clkp | NPCM_RNG_ENABLE, addr: priv->base + NPCM_RNGCS_REG); |
43 | |
44 | return 0; |
45 | } |
46 | |
47 | static void npcm_rng_cleanup(struct hwrng *rng) |
48 | { |
49 | struct npcm_rng *priv = to_npcm_rng(rng); |
50 | |
51 | writel(val: priv->clkp, addr: priv->base + NPCM_RNGCS_REG); |
52 | } |
53 | |
54 | static int npcm_rng_read(struct hwrng *rng, void *buf, size_t max, bool wait) |
55 | { |
56 | struct npcm_rng *priv = to_npcm_rng(rng); |
57 | int retval = 0; |
58 | int ready; |
59 | |
60 | pm_runtime_get_sync(dev: (struct device *)priv->rng.priv); |
61 | |
62 | while (max) { |
63 | if (wait) { |
64 | if (readb_poll_timeout(priv->base + NPCM_RNGCS_REG, |
65 | ready, |
66 | ready & NPCM_RNG_DATA_VALID, |
67 | NPCM_RNG_POLL_USEC, |
68 | NPCM_RNG_TIMEOUT_USEC)) |
69 | break; |
70 | } else { |
71 | if ((readb(addr: priv->base + NPCM_RNGCS_REG) & |
72 | NPCM_RNG_DATA_VALID) == 0) |
73 | break; |
74 | } |
75 | |
76 | *(u8 *)buf = readb(addr: priv->base + NPCM_RNGD_REG); |
77 | retval++; |
78 | buf++; |
79 | max--; |
80 | } |
81 | |
82 | pm_runtime_mark_last_busy(dev: (struct device *)priv->rng.priv); |
83 | pm_runtime_put_sync_autosuspend(dev: (struct device *)priv->rng.priv); |
84 | |
85 | return retval || !wait ? retval : -EIO; |
86 | } |
87 | |
88 | static int npcm_rng_probe(struct platform_device *pdev) |
89 | { |
90 | struct npcm_rng *priv; |
91 | int ret; |
92 | |
93 | priv = devm_kzalloc(dev: &pdev->dev, size: sizeof(*priv), GFP_KERNEL); |
94 | if (!priv) |
95 | return -ENOMEM; |
96 | |
97 | priv->base = devm_platform_ioremap_resource(pdev, index: 0); |
98 | if (IS_ERR(ptr: priv->base)) |
99 | return PTR_ERR(ptr: priv->base); |
100 | |
101 | dev_set_drvdata(dev: &pdev->dev, data: priv); |
102 | pm_runtime_set_autosuspend_delay(dev: &pdev->dev, delay: 100); |
103 | pm_runtime_use_autosuspend(dev: &pdev->dev); |
104 | pm_runtime_enable(dev: &pdev->dev); |
105 | |
106 | #ifndef CONFIG_PM |
107 | priv->rng.init = npcm_rng_init; |
108 | priv->rng.cleanup = npcm_rng_cleanup; |
109 | #endif |
110 | priv->rng.name = pdev->name; |
111 | priv->rng.read = npcm_rng_read; |
112 | priv->rng.priv = (unsigned long)&pdev->dev; |
113 | priv->clkp = (u32)(uintptr_t)of_device_get_match_data(dev: &pdev->dev); |
114 | |
115 | writel(NPCM_RNG_M1ROSEL, addr: priv->base + NPCM_RNGMODE_REG); |
116 | |
117 | ret = devm_hwrng_register(dev: &pdev->dev, rng: &priv->rng); |
118 | if (ret) { |
119 | dev_err(&pdev->dev, "Failed to register rng device: %d\n" , |
120 | ret); |
121 | pm_runtime_disable(dev: &pdev->dev); |
122 | pm_runtime_set_suspended(dev: &pdev->dev); |
123 | return ret; |
124 | } |
125 | |
126 | return 0; |
127 | } |
128 | |
129 | static int npcm_rng_remove(struct platform_device *pdev) |
130 | { |
131 | struct npcm_rng *priv = platform_get_drvdata(pdev); |
132 | |
133 | devm_hwrng_unregister(dve: &pdev->dev, rng: &priv->rng); |
134 | pm_runtime_disable(dev: &pdev->dev); |
135 | pm_runtime_set_suspended(dev: &pdev->dev); |
136 | |
137 | return 0; |
138 | } |
139 | |
140 | #ifdef CONFIG_PM |
141 | static int npcm_rng_runtime_suspend(struct device *dev) |
142 | { |
143 | struct npcm_rng *priv = dev_get_drvdata(dev); |
144 | |
145 | npcm_rng_cleanup(rng: &priv->rng); |
146 | |
147 | return 0; |
148 | } |
149 | |
150 | static int npcm_rng_runtime_resume(struct device *dev) |
151 | { |
152 | struct npcm_rng *priv = dev_get_drvdata(dev); |
153 | |
154 | return npcm_rng_init(rng: &priv->rng); |
155 | } |
156 | #endif |
157 | |
158 | static const struct dev_pm_ops npcm_rng_pm_ops = { |
159 | SET_RUNTIME_PM_OPS(npcm_rng_runtime_suspend, |
160 | npcm_rng_runtime_resume, NULL) |
161 | SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, |
162 | pm_runtime_force_resume) |
163 | }; |
164 | |
165 | static const struct of_device_id rng_dt_id[] __maybe_unused = { |
166 | { .compatible = "nuvoton,npcm750-rng" , |
167 | .data = (void *)NPCM_RNG_CLK_SET_25MHZ }, |
168 | { .compatible = "nuvoton,npcm845-rng" , |
169 | .data = (void *)NPCM_RNG_CLK_SET_62_5MHZ }, |
170 | {}, |
171 | }; |
172 | MODULE_DEVICE_TABLE(of, rng_dt_id); |
173 | |
174 | static struct platform_driver npcm_rng_driver = { |
175 | .driver = { |
176 | .name = "npcm-rng" , |
177 | .pm = &npcm_rng_pm_ops, |
178 | .of_match_table = of_match_ptr(rng_dt_id), |
179 | }, |
180 | .probe = npcm_rng_probe, |
181 | .remove = npcm_rng_remove, |
182 | }; |
183 | |
184 | module_platform_driver(npcm_rng_driver); |
185 | |
186 | MODULE_DESCRIPTION("Nuvoton NPCM Random Number Generator Driver" ); |
187 | MODULE_AUTHOR("Tomer Maimon <tomer.maimon@nuvoton.com>" ); |
188 | MODULE_LICENSE("GPL v2" ); |
189 | |