Port the Linux driver for the True Random Number Generator found on
Airoha EN7581/AN7581 SoCs to the U-Boot driver model.

The Linux driver waits for an interrupt to learn that the startup
health test finished. U-Boot has no interrupts, so the TRNG interrupt
is kept masked and RST_STARTUP_TEST_DONE is polled instead.

U-Boot can then provide /chosen/kaslr-seed, which KASLR requires.

Signed-off-by: Vitaliy Sochnev <[email protected]>
---
 drivers/rng/Kconfig      |   7 ++
 drivers/rng/Makefile     |   1 +
 drivers/rng/airoha-rng.c | 136 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 144 insertions(+)
 create mode 100644 drivers/rng/airoha-rng.c

diff --git a/drivers/rng/Kconfig b/drivers/rng/Kconfig
index 19b2b70..bd61697 100644
--- a/drivers/rng/Kconfig
+++ b/drivers/rng/Kconfig
@@ -133,4 +133,11 @@ config RNG_EXYNOS
          case the LDFW (Loadable Firmware) has to be loaded first, as it
          actually implements TRNG SMC calls.
 
+config RNG_AIROHA
+       bool "Airoha True Random Number Generator support"
+       depends on DM_RNG && ARCH_AIROHA
+       help
+         Enable support for the True Random Number Generator (TRNG) found
+         on Airoha EN7581/AN7581 SoCs.
+
 endif
diff --git a/drivers/rng/Makefile b/drivers/rng/Makefile
index 30c5827..f4cf72e 100644
--- a/drivers/rng/Makefile
+++ b/drivers/rng/Makefile
@@ -19,3 +19,4 @@ obj-$(CONFIG_TPM_RNG) += tpm_rng.o
 obj-$(CONFIG_RNG_JH7110) += jh7110_rng.o
 obj-$(CONFIG_RNG_TURRIS_RWTM) += turris_rwtm_rng.o
 obj-$(CONFIG_RNG_EXYNOS) += exynos-trng.o
+obj-$(CONFIG_RNG_AIROHA) += airoha-rng.o
diff --git a/drivers/rng/airoha-rng.c b/drivers/rng/airoha-rng.c
new file mode 100644
index 0000000..a94161a
--- /dev/null
+++ b/drivers/rng/airoha-rng.c
@@ -0,0 +1,136 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Airoha True Random Number Generator driver
+ *
+ * Ported from the Linux driver drivers/char/hw_random/airoha-trng.c
+ *
+ * Copyright (C) 2024 Christian Marangi <[email protected]>
+ */
+
+#include <dm.h>
+#include <rng.h>
+#include <dm/device_compat.h>
+#include <asm/io.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/iopoll.h>
+#include <linux/string.h>
+
+#define TRNG_IP_RDY                    0x800
+#define   CNT_TRANS                    GENMASK(15, 8)
+#define   SAMPLE_RDY                   BIT(0)
+#define TRNG_NS_SEK_AND_DAT_EN         0x804
+#define   RNG_EN                       BIT(31)
+#define   RAW_DATA_EN                  BIT(16)
+#define TRNG_HEALTH_TEST_SW_RST                0x808
+#define   SW_RST                       BIT(0) /* Active High */
+#define TRNG_INTR_EN                   0x818
+#define   INTR_MASK                    BIT(16)
+#define   RST_STARTUP_INITR_EN         BIT(0)
+#define TRNG_HEALTH_TEST_STATUS                0x824
+#define   RST_STARTUP_TEST_DONE                BIT(18)
+#define   RST_STARTUP_AP_TEST_FAIL     BIT(17)
+#define   RST_STARTUP_RC_TEST_FAIL     BIT(16)
+#define   RAW_DATA_VALID               BIT(7)
+#define TRNG_RAW_DATA_OUT              0x828
+
+#define TRNG_CNT_TRANS_VALID           0x80
+#define TRNG_TIMEOUT_US                        100000
+
+struct airoha_trng {
+       void __iomem *base;
+};
+
+static int airoha_trng_read(struct udevice *dev, void *data, size_t len)
+{
+       struct airoha_trng *trng = dev_get_priv(dev);
+       u8 *buf = data;
+
+       while (len) {
+               size_t step = min(len, sizeof(u32));
+               u32 status, val;
+               int ret;
+
+               ret = readl_poll_timeout(trng->base + TRNG_HEALTH_TEST_STATUS,
+                                        status, status & RAW_DATA_VALID,
+                                        TRNG_TIMEOUT_US);
+               if (ret) {
+                       dev_err(dev, "Timeout waiting for TRNG RAW Data 
valid\n");
+                       return ret;
+               }
+
+               val = readl(trng->base + TRNG_RAW_DATA_OUT);
+               memcpy(buf, &val, step);
+
+               buf += step;
+               len -= step;
+       }
+
+       return 0;
+}
+
+static int airoha_trng_probe(struct udevice *dev)
+{
+       struct airoha_trng *trng = dev_get_priv(dev);
+       u32 val;
+       int ret;
+
+       trng->base = dev_read_addr_ptr(dev);
+       if (!trng->base)
+               return -EINVAL;
+
+       /* No interrupts in U-Boot: keep the TRNG one masked and poll instead */
+       val = readl(trng->base + TRNG_INTR_EN);
+       val |= INTR_MASK | RST_STARTUP_INITR_EN;
+       writel(val, trng->base + TRNG_INTR_EN);
+
+       val = readl(trng->base + TRNG_NS_SEK_AND_DAT_EN);
+       val |= RAW_DATA_EN | RNG_EN;
+       writel(val, trng->base + TRNG_NS_SEK_AND_DAT_EN);
+
+       /* Health Test runs only on the transition out of SW Reset */
+       writel(SW_RST, trng->base + TRNG_HEALTH_TEST_SW_RST);
+       writel(0, trng->base + TRNG_HEALTH_TEST_SW_RST);
+
+       ret = readl_poll_timeout(trng->base + TRNG_HEALTH_TEST_STATUS, val,
+                                val & RST_STARTUP_TEST_DONE, TRNG_TIMEOUT_US);
+       if (ret) {
+               dev_err(dev, "Timeout waiting for Health Check\n");
+               return ret;
+       }
+
+       if (val & (RST_STARTUP_AP_TEST_FAIL | RST_STARTUP_RC_TEST_FAIL)) {
+               dev_err(dev, "Health Check fail: %s test fail\n",
+                       val & RST_STARTUP_AP_TEST_FAIL ? "AP" : "RC");
+               return -EIO;
+       }
+
+       ret = readl_poll_timeout(trng->base + TRNG_IP_RDY, val,
+                                val & SAMPLE_RDY &&
+                                FIELD_GET(CNT_TRANS, val) == 
TRNG_CNT_TRANS_VALID,
+                                TRNG_TIMEOUT_US);
+       if (ret) {
+               dev_err(dev, "Timeout waiting for IP ready\n");
+               return ret;
+       }
+
+       return 0;
+}
+
+static const struct dm_rng_ops airoha_trng_ops = {
+       .read = airoha_trng_read,
+};
+
+static const struct udevice_id airoha_trng_match[] = {
+       { .compatible = "airoha,en7581-trng" },
+       { }
+};
+
+U_BOOT_DRIVER(airoha_trng) = {
+       .name = "airoha-trng",
+       .id = UCLASS_RNG,
+       .of_match = airoha_trng_match,
+       .ops = &airoha_trng_ops,
+       .probe = airoha_trng_probe,
+       .priv_auto = sizeof(struct airoha_trng),
+};

Reply via email to