summaryrefslogtreecommitdiffstats
path: root/drivers/char/hw_random/histb-rng.c
blob: f652e1135e4b24b7807d2a00c300a0e8ceb15e1c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
/*
 * Copyright (c) 2023 David Yang
 */

#include <linux/err.h>
#include <linux/hw_random.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/kernel.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/platform_device.h>

#define RNG_CTRL		0x0
#define  RNG_SOURCE			GENMASK(1, 0)
#define  DROP_ENABLE			BIT(5)
#define  POST_PROCESS_ENABLE		BIT(7)
#define  POST_PROCESS_DEPTH		GENMASK(15, 8)
#define RNG_NUMBER		0x4
#define RNG_STAT		0x8
#define  DATA_COUNT			GENMASK(2, 0)	/* max 4 */

struct histb_rng_priv {
	struct hwrng rng;
	void __iomem *base;
};

/*
 * Observed:
 * depth = 1 -> ~1ms
 * depth = 255 -> ~16ms
 */
static int histb_rng_wait(void __iomem *base)
{
	u32 val;

	return readl_relaxed_poll_timeout(base + RNG_STAT, val,
					  val & DATA_COUNT, 1000, 30 * 1000);
}

static void histb_rng_init(void __iomem *base, unsigned int depth)
{
	u32 val;

	val = readl_relaxed(base + RNG_CTRL);

	val &= ~RNG_SOURCE;
	val |= 2;

	val &= ~POST_PROCESS_DEPTH;
	val |= min(depth, 0xffu) << 8;

	val |= POST_PROCESS_ENABLE;
	val |= DROP_ENABLE;

	writel_relaxed(val, base + RNG_CTRL);
}

static int histb_rng_read(struct hwrng *rng, void *data, size_t max, bool wait)
{
	struct histb_rng_priv *priv = container_of(rng, typeof(*priv), rng);
	void __iomem *base = priv->base;

	for (int i = 0; i < max; i += sizeof(u32)) {
		if (!(readl_relaxed(base + RNG_STAT) & DATA_COUNT)) {
			if (!wait)
				return i;
			if (histb_rng_wait(base)) {
				pr_err("failed to generate random number, generated %d\n",
				       i);
				return i ? i : -ETIMEDOUT;
			}
		}
		*(u32 *) (data + i) = readl_relaxed(base + RNG_NUMBER);
	}

	return max;
}

static unsigned int histb_rng_get_depth(void __iomem *base)
{
	return (readl_relaxed(base + RNG_CTRL) & POST_PROCESS_DEPTH) >> 8;
}

static ssize_t
depth_show(struct device *dev, struct device_attribute *attr, char *buf)
{
	struct histb_rng_priv *priv = dev_get_drvdata(dev);
	void __iomem *base = priv->base;

	return sprintf(buf, "%d\n", histb_rng_get_depth(base));
}

static ssize_t
depth_store(struct device *dev, struct device_attribute *attr,
	    const char *buf, size_t count)
{
	struct histb_rng_priv *priv = dev_get_drvdata(dev);
	void __iomem *base = priv->base;
	unsigned int depth;

	if (kstrtouint(buf, 0, &depth))
		return -ERANGE;

	histb_rng_init(base, depth);
	return count;
}

static DEVICE_ATTR_RW(depth);

static struct attribute *histb_rng_attrs[] = {
	&dev_attr_depth.attr,
	NULL,
};

ATTRIBUTE_GROUPS(histb_rng);

static int histb_rng_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct histb_rng_priv *priv;
	void __iomem *base;
	int ret;

	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

	base = devm_platform_ioremap_resource(pdev, 0);
	if (IS_ERR(base))
		return PTR_ERR(base);

	histb_rng_init(base, 144);
	if (histb_rng_wait(base)) {
		dev_err(dev, "cannot bring up device\n");
		return -ENODEV;
	}

	priv->base = base;
	priv->rng.name = pdev->name;
	priv->rng.read = histb_rng_read;
	ret = devm_hwrng_register(dev, &priv->rng);
	if (ret) {
		dev_err(dev, "failed to register hwrng: %d\n", ret);
		return ret;
	}

	platform_set_drvdata(pdev, priv);
	dev_set_drvdata(dev, priv);
	return 0;
}

static const struct of_device_id histb_rng_of_match[] = {
	{ .compatible = "hisilicon,histb-rng", },
	{ }
};
MODULE_DEVICE_TABLE(of, histb_rng_of_match);

static struct platform_driver histb_rng_driver = {
	.probe = histb_rng_probe,
	.driver = {
		.name = "histb-rng",
		.of_match_table = histb_rng_of_match,
		.dev_groups = histb_rng_groups,
	},
};

module_platform_driver(histb_rng_driver);

MODULE_DESCRIPTION("Hisilicon STB random number generator driver");
MODULE_LICENSE("Dual MIT/GPL");
MODULE_AUTHOR("David Yang <mmyangfl@gmail.com>");