1// SPDX-License-Identifier: GPL-2.0
2/* Copyright (C) 2020 Xiphera Ltd. */
3
4#include <linux/kernel.h>
5#include <linux/module.h>
6#include <linux/mod_devicetable.h>
7#include <linux/err.h>
8#include <linux/io.h>
9#include <linux/hw_random.h>
10#include <linux/platform_device.h>
11#include <linux/delay.h>
12
13#define CONTROL_REG			0x00000000
14#define STATUS_REG			0x00000004
15#define RAND_REG			0x00000000
16
17#define HOST_TO_TRNG_RESET		0x00000001
18#define HOST_TO_TRNG_RELEASE_RESET	0x00000002
19#define HOST_TO_TRNG_ENABLE		0x80000000
20#define HOST_TO_TRNG_ZEROIZE		0x80000004
21#define HOST_TO_TRNG_ACK_ZEROIZE	0x80000008
22#define HOST_TO_TRNG_READ		0x8000000F
23
24/* trng statuses */
25#define TRNG_ACK_RESET			0x000000AC
26#define TRNG_SUCCESSFUL_STARTUP		0x00000057
27#define TRNG_FAILED_STARTUP		0x000000FA
28#define TRNG_NEW_RAND_AVAILABLE		0x000000ED
29
30struct xiphera_trng {
31	void __iomem *mem;
32	struct hwrng rng;
33};
34
35static int xiphera_trng_read(struct hwrng *rng, void *buf, size_t max, bool wait)
36{
37	struct xiphera_trng *trng = container_of(rng, struct xiphera_trng, rng);
38	int ret = 0;
39
40	while (max >= sizeof(u32)) {
41		/* check for data */
42		if (readl(trng->mem + STATUS_REG) == TRNG_NEW_RAND_AVAILABLE) {
43			*(u32 *)buf = readl(trng->mem + RAND_REG);
44			/*
45			 * Inform the trng of the read
46			 * and re-enable it to produce a new random number
47			 */
48			writel(HOST_TO_TRNG_READ, trng->mem + CONTROL_REG);
49			writel(HOST_TO_TRNG_ENABLE, trng->mem + CONTROL_REG);
50			ret += sizeof(u32);
51			buf += sizeof(u32);
52			max -= sizeof(u32);
53		} else {
54			break;
55		}
56	}
57	return ret;
58}
59
60static int xiphera_trng_probe(struct platform_device *pdev)
61{
62	int ret;
63	struct xiphera_trng *trng;
64	struct device *dev = &pdev->dev;
65
66	trng = devm_kzalloc(dev, sizeof(*trng), GFP_KERNEL);
67	if (!trng)
68		return -ENOMEM;
69
70	trng->mem = devm_platform_ioremap_resource(pdev, 0);
71	if (IS_ERR(trng->mem))
72		return PTR_ERR(trng->mem);
73
74	/*
75	 * the trng needs to be reset first which might not happen in time,
76	 * hence we incorporate a small delay to ensure proper behaviour
77	 */
78	writel(HOST_TO_TRNG_RESET, trng->mem + CONTROL_REG);
79	usleep_range(100, 200);
80
81	if (readl(trng->mem + STATUS_REG) != TRNG_ACK_RESET) {
82		/*
83		 * there is a small chance the trng is just not ready yet,
84		 * so we try one more time. If the second time fails, we give up
85		 */
86		usleep_range(100, 200);
87		if (readl(trng->mem + STATUS_REG) != TRNG_ACK_RESET) {
88			dev_err(dev, "failed to reset the trng ip\n");
89			return -ENODEV;
90		}
91	}
92
93	/*
94	 * once again, to ensure proper behaviour we sleep
95	 * for a while after zeroizing the trng
96	 */
97	writel(HOST_TO_TRNG_RELEASE_RESET, trng->mem + CONTROL_REG);
98	writel(HOST_TO_TRNG_ENABLE, trng->mem + CONTROL_REG);
99	writel(HOST_TO_TRNG_ZEROIZE, trng->mem + CONTROL_REG);
100	msleep(20);
101
102	if (readl(trng->mem + STATUS_REG) != TRNG_SUCCESSFUL_STARTUP) {
103		/* diagnose the reason for the failure */
104		if (readl(trng->mem + STATUS_REG) == TRNG_FAILED_STARTUP) {
105			dev_err(dev, "trng ip startup-tests failed\n");
106			return -ENODEV;
107		}
108		dev_err(dev, "startup-tests yielded no response\n");
109		return -ENODEV;
110	}
111
112	writel(HOST_TO_TRNG_ACK_ZEROIZE, trng->mem + CONTROL_REG);
113
114	trng->rng.name = pdev->name;
115	trng->rng.read = xiphera_trng_read;
116	trng->rng.quality = 900;
117
118	ret = devm_hwrng_register(dev, &trng->rng);
119	if (ret) {
120		dev_err(dev, "failed to register rng device: %d\n", ret);
121		return ret;
122	}
123
124	platform_set_drvdata(pdev, trng);
125
126	return 0;
127}
128
129static const struct of_device_id xiphera_trng_of_match[] = {
130	{ .compatible = "xiphera,xip8001b-trng", },
131	{},
132};
133MODULE_DEVICE_TABLE(of, xiphera_trng_of_match);
134
135static struct platform_driver xiphera_trng_driver = {
136	.driver = {
137		.name = "xiphera-trng",
138		.of_match_table	= xiphera_trng_of_match,
139	},
140	.probe = xiphera_trng_probe,
141};
142
143module_platform_driver(xiphera_trng_driver);
144
145MODULE_LICENSE("GPL");
146MODULE_AUTHOR("Atte Tommiska");
147MODULE_DESCRIPTION("Xiphera FPGA-based true random number generator driver");
148