hwrng: mx-rngc - add a driver for Freescale RNGC
authorMartin Kaiser <martin@kaiser.cx>
Sun, 23 Jul 2017 17:49:06 +0000 (19:49 +0200)
committerHerbert Xu <herbert@gondor.apana.org.au>
Thu, 3 Aug 2017 06:00:21 +0000 (14:00 +0800)
The driver is ported from Freescale's Linux git and can be
found in the

vendor/freescale/imx_2.6.35_maintain

branch.

The driver supports both RNG version C that's part of some Freescale
i.MX3 SoCs and version B that is available on i.MX2x chipsets.

Signed-off-by: Steffen Trumtrar <s.trumtrar@pengutronix.de>
Signed-off-by: Martin Kaiser <martin@kaiser.cx>
Reviewed-by: PrasannaKumar Muralidharan <prasannatsmkumar@gmail.com>
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
drivers/char/hw_random/Kconfig
drivers/char/hw_random/Makefile
drivers/char/hw_random/imx-rngc.c [new file with mode: 0644]

index 2f45e1532670eab91dd4438c8a8365b26f6b3702..95a031e9eced07ce3566387054c00610a9825ffe 100644 (file)
@@ -253,6 +253,20 @@ config HW_RANDOM_MXC_RNGA
 
          If unsure, say Y.
 
+config HW_RANDOM_IMX_RNGC
+       tristate "Freescale i.MX RNGC Random Number Generator"
+       depends on ARCH_MXC
+       default HW_RANDOM
+       ---help---
+         This driver provides kernel-side support for the Random Number
+         Generator Version C hardware found on some Freescale i.MX
+         processors. Version B is also supported by this driver.
+
+         To compile this driver as a module, choose M here: the
+         module will be called imx-rngc.
+
+         If unsure, say Y.
+
 config HW_RANDOM_NOMADIK
        tristate "ST-Ericsson Nomadik Random Number Generator support"
        depends on ARCH_NOMADIK
index b085975ec1d2ff6600b375d14b6d28281a0d47b1..39a67defac67cba157fb5587c34cce315614a6b6 100644 (file)
@@ -20,6 +20,7 @@ obj-$(CONFIG_HW_RANDOM_PASEMI) += pasemi-rng.o
 obj-$(CONFIG_HW_RANDOM_VIRTIO) += virtio-rng.o
 obj-$(CONFIG_HW_RANDOM_TX4939) += tx4939-rng.o
 obj-$(CONFIG_HW_RANDOM_MXC_RNGA) += mxc-rnga.o
+obj-$(CONFIG_HW_RANDOM_IMX_RNGC) += imx-rngc.o
 obj-$(CONFIG_HW_RANDOM_OCTEON) += octeon-rng.o
 obj-$(CONFIG_HW_RANDOM_NOMADIK) += nomadik-rng.o
 obj-$(CONFIG_HW_RANDOM_PSERIES) += pseries-rng.o
diff --git a/drivers/char/hw_random/imx-rngc.c b/drivers/char/hw_random/imx-rngc.c
new file mode 100644 (file)
index 0000000..88db42d
--- /dev/null
@@ -0,0 +1,331 @@
+/*
+ * RNG driver for Freescale RNGC
+ *
+ * Copyright (C) 2008-2012 Freescale Semiconductor, Inc.
+ * Copyright (C) 2017 Martin Kaiser <martin@kaiser.cx>
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/hw_random.h>
+#include <linux/completion.h>
+#include <linux/io.h>
+
+#define RNGC_COMMAND                   0x0004
+#define RNGC_CONTROL                   0x0008
+#define RNGC_STATUS                    0x000C
+#define RNGC_ERROR                     0x0010
+#define RNGC_FIFO                      0x0014
+
+#define RNGC_CMD_CLR_ERR               0x00000020
+#define RNGC_CMD_CLR_INT               0x00000010
+#define RNGC_CMD_SEED                  0x00000002
+#define RNGC_CMD_SELF_TEST             0x00000001
+
+#define RNGC_CTRL_MASK_ERROR           0x00000040
+#define RNGC_CTRL_MASK_DONE            0x00000020
+
+#define RNGC_STATUS_ERROR              0x00010000
+#define RNGC_STATUS_FIFO_LEVEL_MASK    0x00000f00
+#define RNGC_STATUS_FIFO_LEVEL_SHIFT   8
+#define RNGC_STATUS_SEED_DONE          0x00000020
+#define RNGC_STATUS_ST_DONE            0x00000010
+
+#define RNGC_ERROR_STATUS_STAT_ERR     0x00000008
+
+#define RNGC_TIMEOUT  3000 /* 3 sec */
+
+
+static bool self_test = true;
+module_param(self_test, bool, 0);
+
+struct imx_rngc {
+       struct device           *dev;
+       struct clk              *clk;
+       void __iomem            *base;
+       struct hwrng            rng;
+       struct completion       rng_op_done;
+       /*
+        * err_reg is written only by the irq handler and read only
+        * when interrupts are masked, we need no spinlock
+        */
+       u32                     err_reg;
+};
+
+
+static inline void imx_rngc_irq_mask_clear(struct imx_rngc *rngc)
+{
+       u32 ctrl, cmd;
+
+       /* mask interrupts */
+       ctrl = readl(rngc->base + RNGC_CONTROL);
+       ctrl |= RNGC_CTRL_MASK_DONE | RNGC_CTRL_MASK_ERROR;
+       writel(ctrl, rngc->base + RNGC_CONTROL);
+
+       /*
+        * CLR_INT clears the interrupt only if there's no error
+        * CLR_ERR clear the interrupt and the error register if there
+        * is an error
+        */
+       cmd = readl(rngc->base + RNGC_COMMAND);
+       cmd |= RNGC_CMD_CLR_INT | RNGC_CMD_CLR_ERR;
+       writel(cmd, rngc->base + RNGC_COMMAND);
+}
+
+static inline void imx_rngc_irq_unmask(struct imx_rngc *rngc)
+{
+       u32 ctrl;
+
+       ctrl = readl(rngc->base + RNGC_CONTROL);
+       ctrl &= ~(RNGC_CTRL_MASK_DONE | RNGC_CTRL_MASK_ERROR);
+       writel(ctrl, rngc->base + RNGC_CONTROL);
+}
+
+static int imx_rngc_self_test(struct imx_rngc *rngc)
+{
+       u32 cmd;
+       int ret;
+
+       imx_rngc_irq_unmask(rngc);
+
+       /* run self test */
+       cmd = readl(rngc->base + RNGC_COMMAND);
+       writel(cmd | RNGC_CMD_SELF_TEST, rngc->base + RNGC_COMMAND);
+
+       ret = wait_for_completion_timeout(&rngc->rng_op_done, RNGC_TIMEOUT);
+       if (!ret) {
+               imx_rngc_irq_mask_clear(rngc);
+               return -ETIMEDOUT;
+       }
+
+       if (rngc->err_reg != 0)
+               return -EIO;
+
+       return 0;
+}
+
+static int imx_rngc_read(struct hwrng *rng, void *data, size_t max, bool wait)
+{
+       struct imx_rngc *rngc = container_of(rng, struct imx_rngc, rng);
+       unsigned int status;
+       unsigned int level;
+       int retval = 0;
+
+       while (max >= sizeof(u32)) {
+               status = readl(rngc->base + RNGC_STATUS);
+
+               /* is there some error while reading this random number? */
+               if (status & RNGC_STATUS_ERROR)
+                       break;
+
+               /* how many random numbers are in FIFO? [0-16] */
+               level = (status & RNGC_STATUS_FIFO_LEVEL_MASK) >>
+                       RNGC_STATUS_FIFO_LEVEL_SHIFT;
+
+               if (level) {
+                       /* retrieve a random number from FIFO */
+                       *(u32 *)data = readl(rngc->base + RNGC_FIFO);
+
+                       retval += sizeof(u32);
+                       data += sizeof(u32);
+                       max -= sizeof(u32);
+               }
+       }
+
+       return retval ? retval : -EIO;
+}
+
+static irqreturn_t imx_rngc_irq(int irq, void *priv)
+{
+       struct imx_rngc *rngc = (struct imx_rngc *)priv;
+       u32 status;
+
+       /*
+        * clearing the interrupt will also clear the error register
+        * read error and status before clearing
+        */
+       status = readl(rngc->base + RNGC_STATUS);
+       rngc->err_reg = readl(rngc->base + RNGC_ERROR);
+
+       imx_rngc_irq_mask_clear(rngc);
+
+       if (status & (RNGC_STATUS_SEED_DONE | RNGC_STATUS_ST_DONE))
+               complete(&rngc->rng_op_done);
+
+       return IRQ_HANDLED;
+}
+
+static int imx_rngc_init(struct hwrng *rng)
+{
+       struct imx_rngc *rngc = container_of(rng, struct imx_rngc, rng);
+       u32 cmd;
+       int ret;
+
+       /* clear error */
+       cmd = readl(rngc->base + RNGC_COMMAND);
+       writel(cmd | RNGC_CMD_CLR_ERR, rngc->base + RNGC_COMMAND);
+
+       /* create seed, repeat while there is some statistical error */
+       do {
+               imx_rngc_irq_unmask(rngc);
+
+               /* seed creation */
+               cmd = readl(rngc->base + RNGC_COMMAND);
+               writel(cmd | RNGC_CMD_SEED, rngc->base + RNGC_COMMAND);
+
+               ret = wait_for_completion_timeout(&rngc->rng_op_done,
+                               RNGC_TIMEOUT);
+
+               if (!ret) {
+                       imx_rngc_irq_mask_clear(rngc);
+                       return -ETIMEDOUT;
+               }
+
+       } while (rngc->err_reg == RNGC_ERROR_STATUS_STAT_ERR);
+
+       return rngc->err_reg ? -EIO : 0;
+}
+
+static int imx_rngc_probe(struct platform_device *pdev)
+{
+       struct imx_rngc *rngc;
+       struct resource *res;
+       int ret;
+       int irq;
+
+       rngc = devm_kzalloc(&pdev->dev, sizeof(*rngc), GFP_KERNEL);
+       if (!rngc)
+               return -ENOMEM;
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       rngc->base = devm_ioremap_resource(&pdev->dev, res);
+       if (IS_ERR(rngc->base))
+               return PTR_ERR(rngc->base);
+
+       rngc->clk = devm_clk_get(&pdev->dev, NULL);
+       if (IS_ERR(rngc->clk)) {
+               dev_err(&pdev->dev, "Can not get rng_clk\n");
+               return PTR_ERR(rngc->clk);
+       }
+
+       irq = platform_get_irq(pdev, 0);
+       if (irq <= 0) {
+               dev_err(&pdev->dev, "Couldn't get irq %d\n", irq);
+               return irq;
+       }
+
+       ret = clk_prepare_enable(rngc->clk);
+       if (ret)
+               return ret;
+
+       ret = devm_request_irq(&pdev->dev,
+                       irq, imx_rngc_irq, 0, pdev->name, (void *)rngc);
+       if (ret) {
+               dev_err(rngc->dev, "Can't get interrupt working.\n");
+               goto err;
+       }
+
+       init_completion(&rngc->rng_op_done);
+
+       rngc->rng.name = pdev->name;
+       rngc->rng.init = imx_rngc_init;
+       rngc->rng.read = imx_rngc_read;
+
+       rngc->dev = &pdev->dev;
+       platform_set_drvdata(pdev, rngc);
+
+       imx_rngc_irq_mask_clear(rngc);
+
+       if (self_test) {
+               ret = imx_rngc_self_test(rngc);
+               if (ret) {
+                       dev_err(rngc->dev, "FSL RNGC self test failed.\n");
+                       goto err;
+               }
+       }
+
+       ret = hwrng_register(&rngc->rng);
+       if (ret) {
+               dev_err(&pdev->dev, "FSL RNGC registering failed (%d)\n", ret);
+               goto err;
+       }
+
+       dev_info(&pdev->dev, "Freescale RNGC registered.\n");
+       return 0;
+
+err:
+       clk_disable_unprepare(rngc->clk);
+
+       return ret;
+}
+
+static int __exit imx_rngc_remove(struct platform_device *pdev)
+{
+       struct imx_rngc *rngc = platform_get_drvdata(pdev);
+
+       hwrng_unregister(&rngc->rng);
+
+       clk_disable_unprepare(rngc->clk);
+
+       return 0;
+}
+
+#ifdef CONFIG_PM
+static int imx_rngc_suspend(struct device *dev)
+{
+       struct imx_rngc *rngc = dev_get_drvdata(dev);
+
+       clk_disable_unprepare(rngc->clk);
+
+       return 0;
+}
+
+static int imx_rngc_resume(struct device *dev)
+{
+       struct imx_rngc *rngc = dev_get_drvdata(dev);
+
+       clk_prepare_enable(rngc->clk);
+
+       return 0;
+}
+
+static const struct dev_pm_ops imx_rngc_pm_ops = {
+       .suspend        = imx_rngc_suspend,
+       .resume         = imx_rngc_resume,
+};
+#endif
+
+static const struct of_device_id imx_rngc_dt_ids[] = {
+       { .compatible = "fsl,imx25-rngb", .data = NULL, },
+       { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_rngc_dt_ids);
+
+static struct platform_driver imx_rngc_driver = {
+       .driver = {
+               .name = "imx_rngc",
+#ifdef CONFIG_PM
+               .pm = &imx_rngc_pm_ops,
+#endif
+               .of_match_table = imx_rngc_dt_ids,
+       },
+       .remove = __exit_p(imx_rngc_remove),
+};
+
+module_platform_driver_probe(imx_rngc_driver, imx_rngc_probe);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("H/W RNGC driver for i.MX");
+MODULE_LICENSE("GPL");