hwrng: omap - Add OMAP4 TRNG support
authorLokesh Vutla <lokeshvutla@ti.com>
Mon, 5 Aug 2013 14:47:23 +0000 (20:17 +0530)
committerHerbert Xu <herbert@gondor.apana.org.au>
Fri, 9 Aug 2013 06:39:48 +0000 (16:39 +1000)
Add support for OMAP4 version of TRNG module
that is present on OMAP4, AM33xx and OMAP5 SoCs.

The modules have several differences including register
offsets, output size, triggering rng and how configuring
FROs. To handle these differences, a platform_data structure
is defined and contains routine pointers, register offsets. OMAP2
specific routines are prefixed with 'omap2_' and OMAP4
specific routines are prefixed with 'omap4_'.

Note: Few Hard coded values are from the TI AM33xx SDK.

Signed-off-by: Lokesh Vutla <lokeshvutla@ti.com>
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
drivers/char/hw_random/Kconfig
drivers/char/hw_random/omap-rng.c

index 2f9dbf7..5d7be1d 100644 (file)
@@ -153,12 +153,12 @@ config HW_RANDOM_IXP4XX
 
 config HW_RANDOM_OMAP
        tristate "OMAP Random Number Generator support"
-       depends on HW_RANDOM && (ARCH_OMAP16XX || ARCH_OMAP2)
+       depends on HW_RANDOM && (ARCH_OMAP16XX || ARCH_OMAP2PLUS)
        default HW_RANDOM
        ---help---
          This driver provides kernel-side support for the Random Number
-         Generator hardware found on OMAP16xx and OMAP24xx multimedia
-         processors.
+         Generator hardware found on OMAP16xx, OMAP2/3/4/5 and AM33xx/AM43xx
+         multimedia processors.
 
          To compile this driver as a module, choose M here: the
          module will be called omap-rng.
index 3076c9d..f3f7142 100644 (file)
 #include <linux/of.h>
 #include <linux/of_device.h>
 #include <linux/of_address.h>
+#include <linux/interrupt.h>
 
 #include <asm/io.h>
 
-#define RNG_OUT_REG            0x00            /* Output register */
-#define RNG_STAT_REG           0x04            /* Status register
-                                                       [0] = STAT_BUSY */
-#define RNG_ALARM_REG          0x24            /* Alarm register
-                                                       [7:0] = ALARM_COUNTER */
-#define RNG_CONFIG_REG         0x28            /* Configuration register
-                                                       [11:6] = RESET_COUNT
-                                                       [5:3]  = RING2_DELAY
-                                                       [2:0]  = RING1_DELAY */
-#define RNG_REV_REG            0x3c            /* Revision register
-                                                       [7:0] = REV_NB */
-#define RNG_MASK_REG           0x40            /* Mask and reset register
-                                                       [2] = IT_EN
-                                                       [1] = SOFTRESET
-                                                       [0] = AUTOIDLE */
-#define RNG_SYSSTATUS          0x44            /* System status
-                                                       [0] = RESETDONE */
+#define RNG_REG_STATUS_RDY                     (1 << 0)
+
+#define RNG_REG_INTACK_RDY_MASK                        (1 << 0)
+#define RNG_REG_INTACK_SHUTDOWN_OFLO_MASK      (1 << 1)
+#define RNG_SHUTDOWN_OFLO_MASK                 (1 << 1)
+
+#define RNG_CONTROL_STARTUP_CYCLES_SHIFT       16
+#define RNG_CONTROL_STARTUP_CYCLES_MASK                (0xffff << 16)
+#define RNG_CONTROL_ENABLE_TRNG_SHIFT          10
+#define RNG_CONTROL_ENABLE_TRNG_MASK           (1 << 10)
+
+#define RNG_CONFIG_MAX_REFIL_CYCLES_SHIFT      16
+#define RNG_CONFIG_MAX_REFIL_CYCLES_MASK       (0xffff << 16)
+#define RNG_CONFIG_MIN_REFIL_CYCLES_SHIFT      0
+#define RNG_CONFIG_MIN_REFIL_CYCLES_MASK       (0xff << 0)
+
+#define RNG_CONTROL_STARTUP_CYCLES             0xff
+#define RNG_CONFIG_MIN_REFIL_CYCLES            0x21
+#define RNG_CONFIG_MAX_REFIL_CYCLES            0x22
+
+#define RNG_ALARMCNT_ALARM_TH_SHIFT            0x0
+#define RNG_ALARMCNT_ALARM_TH_MASK             (0xff << 0)
+#define RNG_ALARMCNT_SHUTDOWN_TH_SHIFT         16
+#define RNG_ALARMCNT_SHUTDOWN_TH_MASK          (0x1f << 16)
+#define RNG_ALARM_THRESHOLD                    0xff
+#define RNG_SHUTDOWN_THRESHOLD                 0x4
+
+#define RNG_REG_FROENABLE_MASK                 0xffffff
+#define RNG_REG_FRODETUNE_MASK                 0xffffff
+
+#define OMAP2_RNG_OUTPUT_SIZE                  0x4
+#define OMAP4_RNG_OUTPUT_SIZE                  0x8
+
+enum {
+       RNG_OUTPUT_L_REG = 0,
+       RNG_OUTPUT_H_REG,
+       RNG_STATUS_REG,
+       RNG_INTMASK_REG,
+       RNG_INTACK_REG,
+       RNG_CONTROL_REG,
+       RNG_CONFIG_REG,
+       RNG_ALARMCNT_REG,
+       RNG_FROENABLE_REG,
+       RNG_FRODETUNE_REG,
+       RNG_ALARMMASK_REG,
+       RNG_ALARMSTOP_REG,
+       RNG_REV_REG,
+       RNG_SYSCONFIG_REG,
+};
+
+static const u16 reg_map_omap2[] = {
+       [RNG_OUTPUT_L_REG]      = 0x0,
+       [RNG_STATUS_REG]        = 0x4,
+       [RNG_CONFIG_REG]        = 0x28,
+       [RNG_REV_REG]           = 0x3c,
+       [RNG_SYSCONFIG_REG]     = 0x40,
+};
+
+static const u16 reg_map_omap4[] = {
+       [RNG_OUTPUT_L_REG]      = 0x0,
+       [RNG_OUTPUT_H_REG]      = 0x4,
+       [RNG_STATUS_REG]        = 0x8,
+       [RNG_INTMASK_REG]       = 0xc,
+       [RNG_INTACK_REG]        = 0x10,
+       [RNG_CONTROL_REG]       = 0x14,
+       [RNG_CONFIG_REG]        = 0x18,
+       [RNG_ALARMCNT_REG]      = 0x1c,
+       [RNG_FROENABLE_REG]     = 0x20,
+       [RNG_FRODETUNE_REG]     = 0x24,
+       [RNG_ALARMMASK_REG]     = 0x28,
+       [RNG_ALARMSTOP_REG]     = 0x2c,
+       [RNG_REV_REG]           = 0x1FE0,
+       [RNG_SYSCONFIG_REG]     = 0x1FE4,
+};
 
+struct omap_rng_dev;
 /**
- * struct omap_rng_private_data - RNG IP block-specific data
- * @base: virtual address of the beginning of the RNG IP block registers
- * @mem_res: struct resource * for the IP block registers physical memory
+ * struct omap_rng_pdata - RNG IP block-specific data
+ * @regs: Pointer to the register offsets structure.
+ * @data_size: No. of bytes in RNG output.
+ * @data_present: Callback to determine if data is available.
+ * @init: Callback for IP specific initialization sequence.
+ * @cleanup: Callback for IP specific cleanup sequence.
  */
-struct omap_rng_private_data {
-       void __iomem *base;
-       struct resource *mem_res;
+struct omap_rng_pdata {
+       u16     *regs;
+       u32     data_size;
+       u32     (*data_present)(struct omap_rng_dev *priv);
+       int     (*init)(struct omap_rng_dev *priv);
+       void    (*cleanup)(struct omap_rng_dev *priv);
 };
 
-static inline u32 omap_rng_read_reg(struct omap_rng_private_data *priv, int reg)
+struct omap_rng_dev {
+       void __iomem                    *base;
+       struct device                   *dev;
+       const struct omap_rng_pdata     *pdata;
+};
+
+static inline u32 omap_rng_read(struct omap_rng_dev *priv, u16 reg)
+{
+       return __raw_readl(priv->base + priv->pdata->regs[reg]);
+}
+
+static inline void omap_rng_write(struct omap_rng_dev *priv, u16 reg,
+                                     u32 val)
 {
-       return __raw_readl(priv->base + reg);
+       __raw_writel(val, priv->base + priv->pdata->regs[reg]);
 }
 
-static inline void omap_rng_write_reg(struct omap_rng_private_data *priv,
-                                     int reg, u32 val)
+static inline u32 omap2_rng_data_present(struct omap_rng_dev *priv)
 {
-       __raw_writel(val, priv->base + reg);
+       return omap_rng_read(priv, RNG_STATUS_REG) ? 0 : 1;
+}
+
+static inline u32 omap4_rng_data_present(struct omap_rng_dev *priv)
+{
+       return omap_rng_read(priv, RNG_STATUS_REG) & RNG_REG_STATUS_RDY;
 }
 
 static int omap_rng_data_present(struct hwrng *rng, int wait)
 {
-       struct omap_rng_private_data *priv;
+       struct omap_rng_dev *priv;
        int data, i;
 
-       priv = (struct omap_rng_private_data *)rng->priv;
+       priv = (struct omap_rng_dev *)rng->priv;
 
        for (i = 0; i < 20; i++) {
-               data = omap_rng_read_reg(priv, RNG_STAT_REG) ? 0 : 1;
+               data = priv->pdata->data_present(priv);
                if (data || !wait)
                        break;
                /* RNG produces data fast enough (2+ MBit/sec, even
@@ -92,36 +173,202 @@ static int omap_rng_data_present(struct hwrng *rng, int wait)
 
 static int omap_rng_data_read(struct hwrng *rng, u32 *data)
 {
-       struct omap_rng_private_data *priv;
+       struct omap_rng_dev *priv;
+       u32 data_size, i;
+
+       priv = (struct omap_rng_dev *)rng->priv;
+       data_size = priv->pdata->data_size;
+
+       for (i = 0; i < data_size / sizeof(u32); i++)
+               data[i] = omap_rng_read(priv, RNG_OUTPUT_L_REG + i);
+
+       if (priv->pdata->regs[RNG_INTACK_REG])
+               omap_rng_write(priv, RNG_INTACK_REG, RNG_REG_INTACK_RDY_MASK);
+       return data_size;
+}
+
+static int omap4_rng_init(struct omap_rng_dev *priv)
+{
+       u32 val;
+
+       /* Return if RNG is already running. */
+       if (omap_rng_read(priv, RNG_CONFIG_REG) & RNG_CONTROL_ENABLE_TRNG_MASK)
+               return 0;
+
+       val = RNG_CONFIG_MIN_REFIL_CYCLES << RNG_CONFIG_MIN_REFIL_CYCLES_SHIFT;
+       val |= RNG_CONFIG_MAX_REFIL_CYCLES << RNG_CONFIG_MAX_REFIL_CYCLES_SHIFT;
+       omap_rng_write(priv, RNG_CONFIG_REG, val);
+
+       omap_rng_write(priv, RNG_FRODETUNE_REG, 0x0);
+       omap_rng_write(priv, RNG_FROENABLE_REG, RNG_REG_FROENABLE_MASK);
+       val = RNG_ALARM_THRESHOLD << RNG_ALARMCNT_ALARM_TH_SHIFT;
+       val |= RNG_SHUTDOWN_THRESHOLD << RNG_ALARMCNT_SHUTDOWN_TH_SHIFT;
+       omap_rng_write(priv, RNG_ALARMCNT_REG, val);
+
+       val = RNG_CONTROL_STARTUP_CYCLES << RNG_CONTROL_STARTUP_CYCLES_SHIFT;
+       val |= RNG_CONTROL_ENABLE_TRNG_MASK;
+       omap_rng_write(priv, RNG_CONTROL_REG, val);
+
+       return 0;
+}
+
+static void omap4_rng_cleanup(struct omap_rng_dev *priv)
+{
+       int val;
+
+       val = omap_rng_read(priv, RNG_CONTROL_REG);
+       val &= ~RNG_CONTROL_ENABLE_TRNG_MASK;
+       omap_rng_write(priv, RNG_CONFIG_REG, val);
+}
+
+static int omap2_rng_init(struct omap_rng_dev *priv)
+{
+       omap_rng_write(priv, RNG_SYSCONFIG_REG, 0x1);
+       return 0;
+}
+
+static void omap2_rng_cleanup(struct omap_rng_dev *priv)
+{
+       omap_rng_write(priv, RNG_SYSCONFIG_REG, 0x0);
+}
+
+static int omap_rng_init(struct hwrng *rng)
+{
+       struct omap_rng_dev *priv;
+
+       priv = (struct omap_rng_dev *)rng->priv;
+       return priv->pdata->init(priv);
+}
+
+static void omap_rng_cleanup(struct hwrng *rng)
+{
+       struct omap_rng_dev *priv;
+
+       priv = (struct omap_rng_dev *)rng->priv;
+       priv->pdata->cleanup(priv);
+}
+
+static irqreturn_t omap4_rng_irq(int irq, void *dev_id)
+{
+       struct omap_rng_dev *priv = dev_id;
+       u32 fro_detune, fro_enable;
+
+       /*
+        * Interrupt raised by a fro shutdown threshold, do the following:
+        * 1. Clear the alarm events.
+        * 2. De tune the FROs which are shutdown.
+        * 3. Re enable the shutdown FROs.
+        */
+       omap_rng_write(priv, RNG_ALARMMASK_REG, 0x0);
+       omap_rng_write(priv, RNG_ALARMSTOP_REG, 0x0);
+
+       fro_enable = omap_rng_read(priv, RNG_FROENABLE_REG);
+       fro_detune = ~fro_enable & RNG_REG_FRODETUNE_MASK;
+       fro_detune = fro_detune | omap_rng_read(priv, RNG_FRODETUNE_REG);
+       fro_enable = RNG_REG_FROENABLE_MASK;
 
-       priv = (struct omap_rng_private_data *)rng->priv;
+       omap_rng_write(priv, RNG_FRODETUNE_REG, fro_detune);
+       omap_rng_write(priv, RNG_FROENABLE_REG, fro_enable);
 
-       *data = omap_rng_read_reg(priv, RNG_OUT_REG);
+       omap_rng_write(priv, RNG_INTACK_REG, RNG_REG_INTACK_SHUTDOWN_OFLO_MASK);
 
-       return sizeof(u32);
+       return IRQ_HANDLED;
 }
 
 static struct hwrng omap_rng_ops = {
        .name           = "omap",
        .data_present   = omap_rng_data_present,
        .data_read      = omap_rng_data_read,
+       .init           = omap_rng_init,
+       .cleanup        = omap_rng_cleanup,
+};
+
+static struct omap_rng_pdata omap2_rng_pdata = {
+       .regs           = (u16 *)reg_map_omap2,
+       .data_size      = OMAP2_RNG_OUTPUT_SIZE,
+       .data_present   = omap2_rng_data_present,
+       .init           = omap2_rng_init,
+       .cleanup        = omap2_rng_cleanup,
 };
 
 #if defined(CONFIG_OF)
+static struct omap_rng_pdata omap4_rng_pdata = {
+       .regs           = (u16 *)reg_map_omap4,
+       .data_size      = OMAP4_RNG_OUTPUT_SIZE,
+       .data_present   = omap4_rng_data_present,
+       .init           = omap4_rng_init,
+       .cleanup        = omap4_rng_cleanup,
+};
+
 static const struct of_device_id omap_rng_of_match[] = {
-               { .compatible = "ti,omap2-rng" },
+               {
+                       .compatible     = "ti,omap2-rng",
+                       .data           = &omap2_rng_pdata,
+               },
+               {
+                       .compatible     = "ti,omap4-rng",
+                       .data           = &omap4_rng_pdata,
+               },
                {},
 };
 MODULE_DEVICE_TABLE(of, omap_rng_of_match);
+
+static int of_get_omap_rng_device_details(struct omap_rng_dev *priv,
+                                         struct platform_device *pdev)
+{
+       const struct of_device_id *match;
+       struct device *dev = &pdev->dev;
+       int irq, err;
+
+       match = of_match_device(of_match_ptr(omap_rng_of_match), dev);
+       if (!match) {
+               dev_err(dev, "no compatible OF match\n");
+               return -EINVAL;
+       }
+       priv->pdata = match->data;
+
+       if (of_device_is_compatible(dev->of_node, "ti,omap4-rng")) {
+               irq = platform_get_irq(pdev, 0);
+               if (irq < 0) {
+                       dev_err(dev, "%s: error getting IRQ resource - %d\n",
+                               __func__, irq);
+                       return irq;
+               }
+
+               err = devm_request_irq(dev, irq, omap4_rng_irq,
+                                      IRQF_TRIGGER_NONE, dev_name(dev), priv);
+               if (err) {
+                       dev_err(dev, "unable to request irq %d, err = %d\n",
+                               irq, err);
+                       return err;
+               }
+               omap_rng_write(priv, RNG_INTMASK_REG, RNG_SHUTDOWN_OFLO_MASK);
+       }
+       return 0;
+}
+#else
+static int of_get_omap_rng_device_details(struct omap_rng_dev *omap_rng,
+                                         struct platform_device *pdev)
+{
+       return -EINVAL;
+}
 #endif
 
+static int get_omap_rng_device_details(struct omap_rng_dev *omap_rng)
+{
+       /* Only OMAP2/3 can be non-DT */
+       omap_rng->pdata = &omap2_rng_pdata;
+       return 0;
+}
+
 static int omap_rng_probe(struct platform_device *pdev)
 {
-       struct omap_rng_private_data *priv;
+       struct omap_rng_dev *priv;
+       struct resource *res;
+       struct device *dev = &pdev->dev;
        int ret;
 
-       priv = devm_kzalloc(&pdev->dev, sizeof(struct omap_rng_private_data),
-                           GFP_KERNEL);
+       priv = devm_kzalloc(dev, sizeof(struct omap_rng_dev), GFP_KERNEL);
        if (!priv) {
                dev_err(&pdev->dev, "could not allocate memory\n");
                return -ENOMEM;
@@ -129,9 +376,10 @@ static int omap_rng_probe(struct platform_device *pdev)
 
        omap_rng_ops.priv = (unsigned long)priv;
        platform_set_drvdata(pdev, priv);
+       priv->dev = dev;
 
-       priv->mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
-       priv->base = devm_ioremap_resource(&pdev->dev, priv->mem_res);
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       priv->base = devm_ioremap_resource(dev, res);
        if (IS_ERR(priv->base)) {
                ret = PTR_ERR(priv->base);
                goto err_ioremap;
@@ -140,14 +388,17 @@ static int omap_rng_probe(struct platform_device *pdev)
        pm_runtime_enable(&pdev->dev);
        pm_runtime_get_sync(&pdev->dev);
 
+       ret = (dev->of_node) ? of_get_omap_rng_device_details(priv, pdev) :
+                               get_omap_rng_device_details(priv);
+       if (ret)
+               goto err_ioremap;
+
        ret = hwrng_register(&omap_rng_ops);
        if (ret)
                goto err_register;
 
        dev_info(&pdev->dev, "OMAP Random Number Generator ver. %02x\n",
-                omap_rng_read_reg(priv, RNG_REV_REG));
-
-       omap_rng_write_reg(priv, RNG_MASK_REG, 0x1);
+                omap_rng_read(priv, RNG_REV_REG));
 
        return 0;
 
@@ -155,16 +406,17 @@ err_register:
        priv->base = NULL;
        pm_runtime_disable(&pdev->dev);
 err_ioremap:
+       dev_err(dev, "initialization failed.\n");
        return ret;
 }
 
 static int __exit omap_rng_remove(struct platform_device *pdev)
 {
-       struct omap_rng_private_data *priv = platform_get_drvdata(pdev);
+       struct omap_rng_dev *priv = platform_get_drvdata(pdev);
 
        hwrng_unregister(&omap_rng_ops);
 
-       omap_rng_write_reg(priv, RNG_MASK_REG, 0x0);
+       priv->pdata->cleanup(priv);
 
        pm_runtime_put_sync(&pdev->dev);
        pm_runtime_disable(&pdev->dev);
@@ -176,9 +428,9 @@ static int __exit omap_rng_remove(struct platform_device *pdev)
 
 static int omap_rng_suspend(struct device *dev)
 {
-       struct omap_rng_private_data *priv = dev_get_drvdata(dev);
+       struct omap_rng_dev *priv = dev_get_drvdata(dev);
 
-       omap_rng_write_reg(priv, RNG_MASK_REG, 0x0);
+       priv->pdata->cleanup(priv);
        pm_runtime_put_sync(dev);
 
        return 0;
@@ -186,10 +438,10 @@ static int omap_rng_suspend(struct device *dev)
 
 static int omap_rng_resume(struct device *dev)
 {
-       struct omap_rng_private_data *priv = dev_get_drvdata(dev);
+       struct omap_rng_dev *priv = dev_get_drvdata(dev);
 
        pm_runtime_get_sync(dev);
-       omap_rng_write_reg(priv, RNG_MASK_REG, 0x1);
+       priv->pdata->init(priv);
 
        return 0;
 }