]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
hwrng: airoha - add support for Airoha EN7581 TRNG
authorChristian Marangi <ansuelsmth@gmail.com>
Thu, 17 Oct 2024 12:44:38 +0000 (14:44 +0200)
committerHerbert Xu <herbert@gondor.apana.org.au>
Mon, 28 Oct 2024 10:33:10 +0000 (18:33 +0800)
Add support for Airoha TRNG. The Airoha SoC provide a True RNG module
that can output 4 bytes of raw data at times.

The module makes use of various noise source to provide True Random
Number Generation.

On probe the module is reset to operate Health Test and verify correct
execution of it.

The module can also provide DRBG function but the execution mode is
mutually exclusive, running as TRNG doesn't permit to also run it as
DRBG.

Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
Reviewed-by: Martin Kaiser <martin@kaiser.cx>
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
drivers/char/hw_random/Kconfig
drivers/char/hw_random/Makefile
drivers/char/hw_random/airoha-trng.c [new file with mode: 0644]

index 5912c2dd63983c208587fbbb8f8109b4c808d1fd..bda283f290bcb7944e2c39a9e99cfda62d4023f0 100644 (file)
@@ -62,6 +62,19 @@ config HW_RANDOM_AMD
 
          If unsure, say Y.
 
+config HW_RANDOM_AIROHA
+       tristate "Airoha True HW Random Number Generator support"
+       depends on ARCH_AIROHA || COMPILE_TEST
+       default HW_RANDOM
+       help
+         This driver provides kernel-side support for the True Random Number
+         Generator hardware found on Airoha SoC.
+
+         To compile this driver as a module, choose M here: the
+         module will be called airoha-rng.
+
+         If unsure, say Y.
+
 config HW_RANDOM_ATMEL
        tristate "Atmel Random Number Generator support"
        depends on (ARCH_AT91 || COMPILE_TEST)
index 01f012eab44008a1a79294e7514f5261d6066ffd..dfb717b12f0b570f905b90c4e695275c56a75510 100644 (file)
@@ -8,6 +8,7 @@ rng-core-y := core.o
 obj-$(CONFIG_HW_RANDOM_TIMERIOMEM) += timeriomem-rng.o
 obj-$(CONFIG_HW_RANDOM_INTEL) += intel-rng.o
 obj-$(CONFIG_HW_RANDOM_AMD) += amd-rng.o
+obj-$(CONFIG_HW_RANDOM_AIROHA) += airoha-trng.o
 obj-$(CONFIG_HW_RANDOM_ATMEL) += atmel-rng.o
 obj-$(CONFIG_HW_RANDOM_BA431) += ba431-rng.o
 obj-$(CONFIG_HW_RANDOM_GEODE) += geode-rng.o
diff --git a/drivers/char/hw_random/airoha-trng.c b/drivers/char/hw_random/airoha-trng.c
new file mode 100644 (file)
index 0000000..1dbfa95
--- /dev/null
@@ -0,0 +1,243 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2024 Christian Marangi */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/hw_random.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/platform_device.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) /* referenced as ring_en */
+#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   CONTINUOUS_HEALTH_INITR_EN   BIT(2)
+#define   SW_STARTUP_INITR_EN          BIT(1)
+#define   RST_STARTUP_INITR_EN         BIT(0)
+/* Notice that Health Test are done only out of Reset and with RNG_EN */
+#define TRNG_HEALTH_TEST_STATUS                0x824
+#define   CONTINUOUS_HEALTH_AP_TEST_FAIL BIT(23)
+#define   CONTINUOUS_HEALTH_RC_TEST_FAIL BIT(22)
+#define   SW_STARTUP_TEST_DONE         BIT(21)
+#define   SW_STARTUP_AP_TEST_FAIL      BIT(20)
+#define   SW_STARTUP_RC_TEST_FAIL      BIT(19)
+#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 BUSY_LOOP_SLEEP                        10
+#define BUSY_LOOP_TIMEOUT              (BUSY_LOOP_SLEEP * 10000)
+
+struct airoha_trng {
+       void __iomem *base;
+       struct hwrng rng;
+       struct device *dev;
+
+       struct completion rng_op_done;
+};
+
+static int airoha_trng_irq_mask(struct airoha_trng *trng)
+{
+       u32 val;
+
+       val = readl(trng->base + TRNG_INTR_EN);
+       val |= INTR_MASK;
+       writel(val, trng->base + TRNG_INTR_EN);
+
+       return 0;
+}
+
+static int airoha_trng_irq_unmask(struct airoha_trng *trng)
+{
+       u32 val;
+
+       val = readl(trng->base + TRNG_INTR_EN);
+       val &= ~INTR_MASK;
+       writel(val, trng->base + TRNG_INTR_EN);
+
+       return 0;
+}
+
+static int airoha_trng_init(struct hwrng *rng)
+{
+       struct airoha_trng *trng = container_of(rng, struct airoha_trng, rng);
+       int ret;
+       u32 val;
+
+       val = readl(trng->base + TRNG_NS_SEK_AND_DAT_EN);
+       val |= RNG_EN;
+       writel(val, trng->base + TRNG_NS_SEK_AND_DAT_EN);
+
+       /* Set out of SW Reset */
+       airoha_trng_irq_unmask(trng);
+       writel(0, trng->base + TRNG_HEALTH_TEST_SW_RST);
+
+       ret = wait_for_completion_timeout(&trng->rng_op_done, BUSY_LOOP_TIMEOUT);
+       if (ret <= 0) {
+               dev_err(trng->dev, "Timeout waiting for Health Check\n");
+               airoha_trng_irq_mask(trng);
+               return -ENODEV;
+       }
+
+       /* Check if Health Test Failed */
+       val = readl(trng->base + TRNG_HEALTH_TEST_STATUS);
+       if (val & (RST_STARTUP_AP_TEST_FAIL | RST_STARTUP_RC_TEST_FAIL)) {
+               dev_err(trng->dev, "Health Check fail: %s test fail\n",
+                       val & RST_STARTUP_AP_TEST_FAIL ? "AP" : "RC");
+               return -ENODEV;
+       }
+
+       /* Check if IP is ready */
+       ret = readl_poll_timeout(trng->base + TRNG_IP_RDY, val,
+                                val & SAMPLE_RDY, 10, 1000);
+       if (ret < 0) {
+               dev_err(trng->dev, "Timeout waiting for IP ready");
+               return -ENODEV;
+       }
+
+       /* CNT_TRANS must be 0x80 for IP to be considered ready */
+       ret = readl_poll_timeout(trng->base + TRNG_IP_RDY, val,
+                                FIELD_GET(CNT_TRANS, val) == TRNG_CNT_TRANS_VALID,
+                                10, 1000);
+       if (ret < 0) {
+               dev_err(trng->dev, "Timeout waiting for IP ready");
+               return -ENODEV;
+       }
+
+       return 0;
+}
+
+static void airoha_trng_cleanup(struct hwrng *rng)
+{
+       struct airoha_trng *trng = container_of(rng, struct airoha_trng, rng);
+       u32 val;
+
+       val = readl(trng->base + TRNG_NS_SEK_AND_DAT_EN);
+       val &= ~RNG_EN;
+       writel(val, trng->base + TRNG_NS_SEK_AND_DAT_EN);
+
+       /* Put it in SW Reset */
+       writel(SW_RST, trng->base + TRNG_HEALTH_TEST_SW_RST);
+}
+
+static int airoha_trng_read(struct hwrng *rng, void *buf, size_t max, bool wait)
+{
+       struct airoha_trng *trng = container_of(rng, struct airoha_trng, rng);
+       u32 *data = buf;
+       u32 status;
+       int ret;
+
+       ret = readl_poll_timeout(trng->base + TRNG_HEALTH_TEST_STATUS, status,
+                                status & RAW_DATA_VALID, 10, 1000);
+       if (ret < 0) {
+               dev_err(trng->dev, "Timeout waiting for TRNG RAW Data valid\n");
+               return ret;
+       }
+
+       *data = readl(trng->base + TRNG_RAW_DATA_OUT);
+
+       return 4;
+}
+
+static irqreturn_t airoha_trng_irq(int irq, void *priv)
+{
+       struct airoha_trng *trng = (struct airoha_trng *)priv;
+
+       airoha_trng_irq_mask(trng);
+       /* Just complete the task, we will read the value later */
+       complete(&trng->rng_op_done);
+
+       return IRQ_HANDLED;
+}
+
+static int airoha_trng_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct airoha_trng *trng;
+       int irq, ret;
+       u32 val;
+
+       trng = devm_kzalloc(dev, sizeof(*trng), GFP_KERNEL);
+       if (!trng)
+               return -ENOMEM;
+
+       trng->base = devm_platform_ioremap_resource(pdev, 0);
+       if (IS_ERR(trng->base))
+               return PTR_ERR(trng->base);
+
+       irq = platform_get_irq(pdev, 0);
+       if (irq < 0)
+               return irq;
+
+       airoha_trng_irq_mask(trng);
+       ret = devm_request_irq(&pdev->dev, irq, airoha_trng_irq, 0,
+                              pdev->name, (void *)trng);
+       if (ret) {
+               dev_err(dev, "Can't get interrupt working.\n");
+               return ret;
+       }
+
+       init_completion(&trng->rng_op_done);
+
+       /* Enable interrupt for SW reset Health Check */
+       val = readl(trng->base + TRNG_INTR_EN);
+       val |= RST_STARTUP_INITR_EN;
+       writel(val, trng->base + TRNG_INTR_EN);
+
+       /* Set output to raw data */
+       val = readl(trng->base + TRNG_NS_SEK_AND_DAT_EN);
+       val |= RAW_DATA_EN;
+       writel(val, trng->base + TRNG_NS_SEK_AND_DAT_EN);
+
+       /* Put it in SW Reset */
+       writel(SW_RST, trng->base + TRNG_HEALTH_TEST_SW_RST);
+
+       trng->dev = dev;
+       trng->rng.name = pdev->name;
+       trng->rng.init = airoha_trng_init;
+       trng->rng.cleanup = airoha_trng_cleanup;
+       trng->rng.read = airoha_trng_read;
+
+       ret = devm_hwrng_register(dev, &trng->rng);
+       if (ret) {
+               dev_err(dev, "failed to register rng device: %d\n", ret);
+               return ret;
+       }
+
+       return 0;
+}
+
+static const struct of_device_id airoha_trng_of_match[] = {
+       { .compatible = "airoha,en7581-trng", },
+       {},
+};
+MODULE_DEVICE_TABLE(of, airoha_trng_of_match);
+
+static struct platform_driver airoha_trng_driver = {
+       .driver = {
+               .name = "airoha-trng",
+               .of_match_table = airoha_trng_of_match,
+       },
+       .probe = airoha_trng_probe,
+};
+
+module_platform_driver(airoha_trng_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>");
+MODULE_DESCRIPTION("Airoha True Random Number Generator driver");