Commit | Line | Data |
---|---|---|
406346d2 ZY |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Ingenic True Random Number Generator driver | |
4 | * Copyright (c) 2019 漆鹏振 (Qi Pengzhen) <aric.pzqi@ingenic.com> | |
5 | * Copyright (c) 2020 周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com> | |
6 | */ | |
7 | ||
8 | #include <linux/clk.h> | |
9 | #include <linux/err.h> | |
10 | #include <linux/kernel.h> | |
11 | #include <linux/hw_random.h> | |
12 | #include <linux/io.h> | |
13 | #include <linux/iopoll.h> | |
14 | #include <linux/module.h> | |
15 | #include <linux/of_device.h> | |
16 | #include <linux/platform_device.h> | |
17 | #include <linux/slab.h> | |
18 | ||
19 | /* DTRNG register offsets */ | |
20 | #define TRNG_REG_CFG_OFFSET 0x00 | |
21 | #define TRNG_REG_RANDOMNUM_OFFSET 0x04 | |
22 | #define TRNG_REG_STATUS_OFFSET 0x08 | |
23 | ||
24 | /* bits within the CFG register */ | |
25 | #define CFG_RDY_CLR BIT(12) | |
26 | #define CFG_INT_MASK BIT(11) | |
27 | #define CFG_GEN_EN BIT(0) | |
28 | ||
29 | /* bits within the STATUS register */ | |
30 | #define STATUS_RANDOM_RDY BIT(0) | |
31 | ||
32 | struct ingenic_trng { | |
33 | void __iomem *base; | |
34 | struct clk *clk; | |
35 | struct hwrng rng; | |
36 | }; | |
37 | ||
38 | static int ingenic_trng_init(struct hwrng *rng) | |
39 | { | |
40 | struct ingenic_trng *trng = container_of(rng, struct ingenic_trng, rng); | |
41 | unsigned int ctrl; | |
42 | ||
43 | ctrl = readl(trng->base + TRNG_REG_CFG_OFFSET); | |
44 | ctrl |= CFG_GEN_EN; | |
45 | writel(ctrl, trng->base + TRNG_REG_CFG_OFFSET); | |
46 | ||
47 | return 0; | |
48 | } | |
49 | ||
50 | static void ingenic_trng_cleanup(struct hwrng *rng) | |
51 | { | |
52 | struct ingenic_trng *trng = container_of(rng, struct ingenic_trng, rng); | |
53 | unsigned int ctrl; | |
54 | ||
55 | ctrl = readl(trng->base + TRNG_REG_CFG_OFFSET); | |
56 | ctrl &= ~CFG_GEN_EN; | |
57 | writel(ctrl, trng->base + TRNG_REG_CFG_OFFSET); | |
58 | } | |
59 | ||
60 | static int ingenic_trng_read(struct hwrng *rng, void *buf, size_t max, bool wait) | |
61 | { | |
62 | struct ingenic_trng *trng = container_of(rng, struct ingenic_trng, rng); | |
63 | u32 *data = buf; | |
64 | u32 status; | |
65 | int ret; | |
66 | ||
67 | ret = readl_poll_timeout(trng->base + TRNG_REG_STATUS_OFFSET, status, | |
68 | status & STATUS_RANDOM_RDY, 10, 1000); | |
69 | if (ret == -ETIMEDOUT) { | |
70 | pr_err("%s: Wait for DTRNG data ready timeout\n", __func__); | |
71 | return ret; | |
72 | } | |
73 | ||
74 | *data = readl(trng->base + TRNG_REG_RANDOMNUM_OFFSET); | |
75 | ||
76 | return 4; | |
77 | } | |
78 | ||
79 | static int ingenic_trng_probe(struct platform_device *pdev) | |
80 | { | |
81 | struct ingenic_trng *trng; | |
82 | int ret; | |
83 | ||
84 | trng = devm_kzalloc(&pdev->dev, sizeof(*trng), GFP_KERNEL); | |
85 | if (!trng) | |
86 | return -ENOMEM; | |
87 | ||
88 | trng->base = devm_platform_ioremap_resource(pdev, 0); | |
89 | if (IS_ERR(trng->base)) { | |
90 | pr_err("%s: Failed to map DTRNG registers\n", __func__); | |
91 | ret = PTR_ERR(trng->base); | |
92 | return PTR_ERR(trng->base); | |
93 | } | |
94 | ||
95 | trng->clk = devm_clk_get(&pdev->dev, NULL); | |
96 | if (IS_ERR(trng->clk)) { | |
97 | ret = PTR_ERR(trng->clk); | |
98 | pr_crit("%s: Cannot get DTRNG clock\n", __func__); | |
99 | return PTR_ERR(trng->clk); | |
100 | } | |
101 | ||
102 | ret = clk_prepare_enable(trng->clk); | |
103 | if (ret) { | |
104 | pr_crit("%s: Unable to enable DTRNG clock\n", __func__); | |
105 | return ret; | |
106 | } | |
107 | ||
108 | trng->rng.name = pdev->name; | |
109 | trng->rng.init = ingenic_trng_init; | |
110 | trng->rng.cleanup = ingenic_trng_cleanup; | |
111 | trng->rng.read = ingenic_trng_read; | |
112 | ||
113 | ret = hwrng_register(&trng->rng); | |
114 | if (ret) { | |
115 | dev_err(&pdev->dev, "Failed to register hwrng\n"); | |
116 | return ret; | |
117 | } | |
118 | ||
119 | platform_set_drvdata(pdev, trng); | |
120 | ||
121 | dev_info(&pdev->dev, "Ingenic DTRNG driver registered\n"); | |
122 | return 0; | |
123 | } | |
124 | ||
125 | static int ingenic_trng_remove(struct platform_device *pdev) | |
126 | { | |
127 | struct ingenic_trng *trng = platform_get_drvdata(pdev); | |
128 | unsigned int ctrl; | |
129 | ||
130 | hwrng_unregister(&trng->rng); | |
131 | ||
132 | ctrl = readl(trng->base + TRNG_REG_CFG_OFFSET); | |
133 | ctrl &= ~CFG_GEN_EN; | |
134 | writel(ctrl, trng->base + TRNG_REG_CFG_OFFSET); | |
135 | ||
136 | clk_disable_unprepare(trng->clk); | |
137 | ||
138 | return 0; | |
139 | } | |
140 | ||
141 | static const struct of_device_id ingenic_trng_of_match[] = { | |
142 | { .compatible = "ingenic,x1830-dtrng" }, | |
143 | { /* sentinel */ } | |
144 | }; | |
145 | MODULE_DEVICE_TABLE(of, ingenic_trng_of_match); | |
146 | ||
147 | static struct platform_driver ingenic_trng_driver = { | |
148 | .probe = ingenic_trng_probe, | |
149 | .remove = ingenic_trng_remove, | |
150 | .driver = { | |
151 | .name = "ingenic-trng", | |
152 | .of_match_table = ingenic_trng_of_match, | |
153 | }, | |
154 | }; | |
155 | ||
156 | module_platform_driver(ingenic_trng_driver); | |
157 | ||
158 | MODULE_LICENSE("GPL"); | |
159 | MODULE_AUTHOR("漆鹏振 (Qi Pengzhen) <aric.pzqi@ingenic.com>"); | |
160 | MODULE_AUTHOR("周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>"); | |
161 | MODULE_DESCRIPTION("Ingenic True Random Number Generator driver"); |