xref: /linux/drivers/char/hw_random/meson-rng.c (revision e7d759f31ca295d589f7420719c311870bb3166f)
1 // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
2 /*
3  * Copyright (c) 2016 BayLibre, SAS.
4  * Author: Neil Armstrong <narmstrong@baylibre.com>
5  * Copyright (C) 2014 Amlogic, Inc.
6  */
7 #include <linux/err.h>
8 #include <linux/module.h>
9 #include <linux/io.h>
10 #include <linux/platform_device.h>
11 #include <linux/hw_random.h>
12 #include <linux/slab.h>
13 #include <linux/types.h>
14 #include <linux/of.h>
15 #include <linux/clk.h>
16 #include <linux/iopoll.h>
17 
18 #define RNG_DATA	0x00
19 #define RNG_S4_DATA	0x08
20 #define RNG_S4_CFG	0x00
21 
22 #define RUN_BIT		BIT(0)
23 #define SEED_READY_STS_BIT	BIT(31)
24 
25 struct meson_rng_priv {
26 	int (*read)(struct hwrng *rng, void *buf, size_t max, bool wait);
27 };
28 
29 struct meson_rng_data {
30 	void __iomem *base;
31 	struct hwrng rng;
32 	struct device *dev;
33 };
34 
35 static int meson_rng_read(struct hwrng *rng, void *buf, size_t max, bool wait)
36 {
37 	struct meson_rng_data *data =
38 			container_of(rng, struct meson_rng_data, rng);
39 
40 	*(u32 *)buf = readl_relaxed(data->base + RNG_DATA);
41 
42 	return sizeof(u32);
43 }
44 
45 static int meson_rng_wait_status(void __iomem *cfg_addr, int bit)
46 {
47 	u32 status = 0;
48 	int ret;
49 
50 	ret = readl_relaxed_poll_timeout_atomic(cfg_addr,
51 						status, !(status & bit),
52 						10, 10000);
53 	if (ret)
54 		return -EBUSY;
55 
56 	return 0;
57 }
58 
59 static int meson_s4_rng_read(struct hwrng *rng, void *buf, size_t max, bool wait)
60 {
61 	struct meson_rng_data *data =
62 			container_of(rng, struct meson_rng_data, rng);
63 
64 	void __iomem *cfg_addr = data->base + RNG_S4_CFG;
65 	int err;
66 
67 	writel_relaxed(readl_relaxed(cfg_addr) | SEED_READY_STS_BIT, cfg_addr);
68 
69 	err = meson_rng_wait_status(cfg_addr, SEED_READY_STS_BIT);
70 	if (err) {
71 		dev_err(data->dev, "Seed isn't ready, try again\n");
72 		return err;
73 	}
74 
75 	err = meson_rng_wait_status(cfg_addr, RUN_BIT);
76 	if (err) {
77 		dev_err(data->dev, "Can't get random number, try again\n");
78 		return err;
79 	}
80 
81 	*(u32 *)buf = readl_relaxed(data->base + RNG_S4_DATA);
82 
83 	return sizeof(u32);
84 }
85 
86 static int meson_rng_probe(struct platform_device *pdev)
87 {
88 	struct device *dev = &pdev->dev;
89 	struct meson_rng_data *data;
90 	struct clk *core_clk;
91 	const struct meson_rng_priv *priv;
92 
93 	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
94 	if (!data)
95 		return -ENOMEM;
96 
97 	priv = device_get_match_data(&pdev->dev);
98 	if (!priv)
99 		return -ENODEV;
100 
101 	data->base = devm_platform_ioremap_resource(pdev, 0);
102 	if (IS_ERR(data->base))
103 		return PTR_ERR(data->base);
104 
105 	core_clk = devm_clk_get_optional_enabled(dev, "core");
106 	if (IS_ERR(core_clk))
107 		return dev_err_probe(dev, PTR_ERR(core_clk),
108 				     "Failed to get core clock\n");
109 
110 	data->rng.name = pdev->name;
111 	data->rng.read = priv->read;
112 
113 	data->dev = &pdev->dev;
114 
115 	return devm_hwrng_register(dev, &data->rng);
116 }
117 
118 static const struct meson_rng_priv meson_rng_priv = {
119 	.read = meson_rng_read,
120 };
121 
122 static const struct meson_rng_priv meson_rng_priv_s4 = {
123 	.read = meson_s4_rng_read,
124 };
125 
126 static const struct of_device_id meson_rng_of_match[] = {
127 	{
128 		.compatible = "amlogic,meson-rng",
129 		.data = (void *)&meson_rng_priv,
130 	},
131 	{
132 		.compatible = "amlogic,meson-s4-rng",
133 		.data = (void *)&meson_rng_priv_s4,
134 	},
135 	{},
136 };
137 MODULE_DEVICE_TABLE(of, meson_rng_of_match);
138 
139 static struct platform_driver meson_rng_driver = {
140 	.probe	= meson_rng_probe,
141 	.driver	= {
142 		.name = "meson-rng",
143 		.of_match_table = meson_rng_of_match,
144 	},
145 };
146 
147 module_platform_driver(meson_rng_driver);
148 
149 MODULE_DESCRIPTION("Meson H/W Random Number Generator driver");
150 MODULE_AUTHOR("Lawrence Mok <lawrence.mok@amlogic.com>");
151 MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
152 MODULE_LICENSE("Dual BSD/GPL");
153