hwrng: omap - Add OMAP4 TRNG support
[cascardo/linux.git] / drivers / char / hw_random / omap-rng.c
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;
 }