xref: /linux/drivers/platform/cznic/turris-omnia-mcu-trng.c (revision 41bb142a4028949a0c8c505f7f4963067b0a739f)
1*41bb142aSMarek Behún // SPDX-License-Identifier: GPL-2.0
2*41bb142aSMarek Behún /*
3*41bb142aSMarek Behún  * CZ.NIC's Turris Omnia MCU TRNG driver
4*41bb142aSMarek Behún  *
5*41bb142aSMarek Behún  * 2024 by Marek Behún <kabel@kernel.org>
6*41bb142aSMarek Behún  */
7*41bb142aSMarek Behún 
8*41bb142aSMarek Behún #include <linux/bitfield.h>
9*41bb142aSMarek Behún #include <linux/completion.h>
10*41bb142aSMarek Behún #include <linux/container_of.h>
11*41bb142aSMarek Behún #include <linux/errno.h>
12*41bb142aSMarek Behún #include <linux/gpio/consumer.h>
13*41bb142aSMarek Behún #include <linux/gpio/driver.h>
14*41bb142aSMarek Behún #include <linux/hw_random.h>
15*41bb142aSMarek Behún #include <linux/i2c.h>
16*41bb142aSMarek Behún #include <linux/interrupt.h>
17*41bb142aSMarek Behún #include <linux/minmax.h>
18*41bb142aSMarek Behún #include <linux/string.h>
19*41bb142aSMarek Behún #include <linux/types.h>
20*41bb142aSMarek Behún 
21*41bb142aSMarek Behún #include <linux/turris-omnia-mcu-interface.h>
22*41bb142aSMarek Behún #include "turris-omnia-mcu.h"
23*41bb142aSMarek Behún 
24*41bb142aSMarek Behún #define OMNIA_CMD_TRNG_MAX_ENTROPY_LEN	64
25*41bb142aSMarek Behún 
26*41bb142aSMarek Behún static irqreturn_t omnia_trng_irq_handler(int irq, void *dev_id)
27*41bb142aSMarek Behún {
28*41bb142aSMarek Behún 	struct omnia_mcu *mcu = dev_id;
29*41bb142aSMarek Behún 
30*41bb142aSMarek Behún 	complete(&mcu->trng_entropy_ready);
31*41bb142aSMarek Behún 
32*41bb142aSMarek Behún 	return IRQ_HANDLED;
33*41bb142aSMarek Behún }
34*41bb142aSMarek Behún 
35*41bb142aSMarek Behún static int omnia_trng_read(struct hwrng *rng, void *data, size_t max, bool wait)
36*41bb142aSMarek Behún {
37*41bb142aSMarek Behún 	struct omnia_mcu *mcu = container_of(rng, struct omnia_mcu, trng);
38*41bb142aSMarek Behún 	u8 reply[1 + OMNIA_CMD_TRNG_MAX_ENTROPY_LEN];
39*41bb142aSMarek Behún 	int err, bytes;
40*41bb142aSMarek Behún 
41*41bb142aSMarek Behún 	if (!wait && !completion_done(&mcu->trng_entropy_ready))
42*41bb142aSMarek Behún 		return 0;
43*41bb142aSMarek Behún 
44*41bb142aSMarek Behún 	do {
45*41bb142aSMarek Behún 		if (wait_for_completion_interruptible(&mcu->trng_entropy_ready))
46*41bb142aSMarek Behún 			return -ERESTARTSYS;
47*41bb142aSMarek Behún 
48*41bb142aSMarek Behún 		err = omnia_cmd_read(mcu->client,
49*41bb142aSMarek Behún 				     OMNIA_CMD_TRNG_COLLECT_ENTROPY,
50*41bb142aSMarek Behún 				     reply, sizeof(reply));
51*41bb142aSMarek Behún 		if (err)
52*41bb142aSMarek Behún 			return err;
53*41bb142aSMarek Behún 
54*41bb142aSMarek Behún 		bytes = min3(reply[0], max, OMNIA_CMD_TRNG_MAX_ENTROPY_LEN);
55*41bb142aSMarek Behún 	} while (wait && !bytes);
56*41bb142aSMarek Behún 
57*41bb142aSMarek Behún 	memcpy(data, &reply[1], bytes);
58*41bb142aSMarek Behún 
59*41bb142aSMarek Behún 	return bytes;
60*41bb142aSMarek Behún }
61*41bb142aSMarek Behún 
62*41bb142aSMarek Behún int omnia_mcu_register_trng(struct omnia_mcu *mcu)
63*41bb142aSMarek Behún {
64*41bb142aSMarek Behún 	struct device *dev = &mcu->client->dev;
65*41bb142aSMarek Behún 	u8 irq_idx, dummy;
66*41bb142aSMarek Behún 	int irq, err;
67*41bb142aSMarek Behún 
68*41bb142aSMarek Behún 	if (!(mcu->features & OMNIA_FEAT_TRNG))
69*41bb142aSMarek Behún 		return 0;
70*41bb142aSMarek Behún 
71*41bb142aSMarek Behún 	irq_idx = omnia_int_to_gpio_idx[__bf_shf(OMNIA_INT_TRNG)];
72*41bb142aSMarek Behún 	irq = gpiod_to_irq(gpio_device_get_desc(mcu->gc.gpiodev, irq_idx));
73*41bb142aSMarek Behún 	if (!irq)
74*41bb142aSMarek Behún 		return dev_err_probe(dev, -ENXIO, "Cannot get TRNG IRQ\n");
75*41bb142aSMarek Behún 
76*41bb142aSMarek Behún 	/*
77*41bb142aSMarek Behún 	 * If someone else cleared the TRNG interrupt but did not read the
78*41bb142aSMarek Behún 	 * entropy, a new interrupt won't be generated, and entropy collection
79*41bb142aSMarek Behún 	 * will be stuck. Ensure an interrupt will be generated by executing
80*41bb142aSMarek Behún 	 * the collect entropy command (and discarding the result).
81*41bb142aSMarek Behún 	 */
82*41bb142aSMarek Behún 	err = omnia_cmd_read(mcu->client, OMNIA_CMD_TRNG_COLLECT_ENTROPY,
83*41bb142aSMarek Behún 			     &dummy, 1);
84*41bb142aSMarek Behún 	if (err)
85*41bb142aSMarek Behún 		return err;
86*41bb142aSMarek Behún 
87*41bb142aSMarek Behún 	init_completion(&mcu->trng_entropy_ready);
88*41bb142aSMarek Behún 
89*41bb142aSMarek Behún 	err = devm_request_threaded_irq(dev, irq, NULL, omnia_trng_irq_handler,
90*41bb142aSMarek Behún 					IRQF_ONESHOT, "turris-omnia-mcu-trng",
91*41bb142aSMarek Behún 					mcu);
92*41bb142aSMarek Behún 	if (err)
93*41bb142aSMarek Behún 		return dev_err_probe(dev, err, "Cannot request TRNG IRQ\n");
94*41bb142aSMarek Behún 
95*41bb142aSMarek Behún 	mcu->trng.name = "turris-omnia-mcu-trng";
96*41bb142aSMarek Behún 	mcu->trng.read = omnia_trng_read;
97*41bb142aSMarek Behún 
98*41bb142aSMarek Behún 	err = devm_hwrng_register(dev, &mcu->trng);
99*41bb142aSMarek Behún 	if (err)
100*41bb142aSMarek Behún 		return dev_err_probe(dev, err, "Cannot register TRNG\n");
101*41bb142aSMarek Behún 
102*41bb142aSMarek Behún 	return 0;
103*41bb142aSMarek Behún }
104