aboutsummaryrefslogtreecommitdiff
path: root/drivers/char/hw_random/trng4xx.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/char/hw_random/trng4xx.c')
-rw-r--r--drivers/char/hw_random/trng4xx.c336
1 files changed, 336 insertions, 0 deletions
diff --git a/drivers/char/hw_random/trng4xx.c b/drivers/char/hw_random/trng4xx.c
new file mode 100644
index 00000000000..ece45e74aa1
--- /dev/null
+++ b/drivers/char/hw_random/trng4xx.c
@@ -0,0 +1,336 @@
+/**
+ *
+ * Copyright (c) 2008 Loc Ho <spulijala@amcc.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * Detail Description:
+ * This file AMCC TRNG offload Linux device driver
+ *
+ * @file trng4xx.c
+ *
+ *
+ */
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/of_platform.h>
+#include <linux/hw_random.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <crypto/trng4xx.h>
+
+#define TRNG4XX_VER_STR "0.1"
+#define TRNG4XX_HDR "TRNG4XX: "
+
+/* #define TRNG4XX_DEBUG */
+
+#if !defined(TRNG4XX_DEBUG)
+# define TRNG4XX_LOG(fmt, ...)
+#else
+# define TRNG4XX_LOG(fmt, ...) \
+ do { \
+ printk(KERN_INFO TRNG4XX_HDR fmt "\n", ##__VA_ARGS__); \
+} while(0);
+#endif
+
+struct trng4xx_dev {
+ struct resource res;
+ u32 irq;
+ volatile char __iomem *csr;
+ struct semaphore access_prot;
+ u32 datum;
+};
+
+struct hal_config {
+ struct of_device *ofdev;
+};
+
+static struct trng4xx_dev trng4xx_dev;
+
+static irqreturn_t trng4xx_irq_handler(int irq, void *id);
+static void trng4xx_chk_overflow(void);
+
+int trng4xx_config_set(struct hal_config *cfg)
+{
+ struct device_node *rng_np = cfg->ofdev->node;
+ int rc = 0;
+
+ rc = of_address_to_resource(rng_np, 0, &trng4xx_dev.res);
+ if (rc)
+ return -ENODEV;
+
+ trng4xx_dev.csr = ioremap(trng4xx_dev.res.start,
+ trng4xx_dev.res.end -
+ trng4xx_dev.res.start + 1);
+ if (trng4xx_dev.csr == NULL) {
+ printk(KERN_ERR "unable to ioremap 0x%02X_%08X size %d\n",
+ (u32) (trng4xx_dev.res.start >> 32),
+ (u32) trng4xx_dev.res.start,
+ (u32) (trng4xx_dev.res.end -
+ trng4xx_dev.res.start + 1));
+ return -ENOMEM;
+ }
+
+ TRNG4XX_LOG("TRNG1 0x%02X_%08X size %d\n",
+ (u32) (trng4xx_dev.res.start >> 32),
+ (u32) trng4xx_dev.res.start,
+ (u32) (trng4xx_dev.res.end -
+ trng4xx_dev.res.start + 1));
+
+ trng4xx_dev.irq = of_irq_to_resource(rng_np, 0, NULL);
+
+ if (trng4xx_dev.irq == NO_IRQ) {
+ /* Un-map CSR */
+ iounmap(trng4xx_dev.csr);
+ trng4xx_dev.csr = NULL;
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int trng4xx_pka_config_clear(void)
+{
+ iounmap(trng4xx_dev.csr);
+ return 0;
+}
+
+inline int trng4xx_hw_read32(u32 reg_addr, u32 *data_val)
+{
+ *data_val = in_be32((unsigned __iomem *)
+ (trng4xx_dev.csr + reg_addr));
+ return 0;
+}
+
+inline int trng4xx_hw_write32(u32 reg_addr, u32 data_val)
+{
+ out_be32((unsigned __iomem *) (trng4xx_dev.csr + reg_addr),
+ data_val);
+ return 0;
+}
+
+int trng4xx_hw_init(void)
+{
+ int rc;
+
+ rc = request_irq(trng4xx_dev.irq, trng4xx_irq_handler,
+ 0, "TRNG", NULL);
+ if (rc != 0) {
+ printk(KERN_ERR "failed to register interrupt IRQ %d\n",
+ trng4xx_dev.irq);
+ return rc;
+ }
+ return 0;
+}
+
+int trng4xx_hw_deinit(void)
+{
+ free_irq(trng4xx_dev.irq, NULL);
+ return 0;
+}
+
+static irqreturn_t trng4xx_irq_handler(int irq, void *id)
+{
+ /* TRNG Alarm Counter overflow */
+ trng4xx_chk_overflow();
+ return IRQ_HANDLED;
+}
+
+/**
+ * TRNG Functions
+ *
+ */
+static void trng4xx_chk_overflow(void)
+{
+ /* TRNG Alarm Counter overflow */
+ int rc;
+ u32 val;
+ struct trng4xx_cfg {
+ u32 ring1_delay_sel :3;
+ u32 ring2_delay_sel :3;
+ u32 reset_cnt :6;
+ } __attribute__((packed));
+
+
+ rc = trng4xx_hw_write32(TRNG4XX_ALARMCNT_ADDR, val);
+ if (rc != 0)
+ return;
+
+ if (val > 128) {
+ struct trng4xx_cfg *trng4xx_cfg;
+
+ /* Alarm count is half, reset it */
+ rc = trng4xx_hw_read32(TRNG4XX_CFG_ADDR, &val);
+ if (rc != 0)
+ return;
+ trng4xx_cfg = (struct trng4xx_cfg *) &val;
+ ++trng4xx_cfg->ring1_delay_sel;
+ trng4xx_cfg->ring2_delay_sel =
+ (~trng4xx_cfg->ring1_delay_sel) & 0x07;
+
+ rc = trng4xx_hw_write32(TRNG4XX_CFG_ADDR, val);
+ if (rc != 0)
+ return;
+ trng4xx_hw_write32(TRNG4XX_ALARMCNT_ADDR, 0x00000000);
+ if (rc != 0)
+ return;
+ }
+}
+
+#define MAX_TRY 3
+int trng4xx_random(u32 *rand_val)
+{
+ u32 val = 0;
+ int rc;
+ u16 try_cnt = 0;
+
+ down(&trng4xx_dev.access_prot);
+ do {
+ rc = trng4xx_hw_read32(TRNG4XX_STATUS_ADDR, &val);
+ if (rc != 0)
+ goto err;
+ } while ((val & TRNG4XX_STATUS_BUSY) && ++try_cnt <= MAX_TRY);
+ if (val & TRNG4XX_STATUS_BUSY) {
+ rc = -EINPROGRESS;
+ goto err;
+ }
+ rc = trng4xx_hw_read32(TRNG4XX_OUTPUT_ADDR, rand_val);
+
+err:
+ up(&trng4xx_dev.access_prot);
+ return rc;
+}
+EXPORT_SYMBOL_GPL(trng4xx_random);
+
+static int trng4xx_data_present(struct hwrng *rng, int wait)
+{
+ struct trng4xx_dev *dev = (struct trng4xx_dev *) rng->priv;
+ int i;
+ u32 val;
+
+ down(&trng4xx_dev.access_prot);
+ for (i = 0; i < 20; i++) {
+ udelay(10);
+ trng4xx_hw_read32(TRNG4XX_STATUS_ADDR, &val);
+ if (!(val & TRNG4XX_STATUS_BUSY)) {
+ trng4xx_hw_read32(TRNG4XX_OUTPUT_ADDR, &dev->datum);
+ break;
+ }
+ if (!wait)
+ break;
+ }
+ up(&trng4xx_dev.access_prot);
+ return (val & TRNG4XX_STATUS_BUSY) ? 0 : 1;
+}
+
+static int trng4xx_data_read(struct hwrng *rng, u32 *data)
+{
+ struct trng4xx_dev *dev = (struct trng4xx_dev *) rng->priv;
+
+ *data = dev->datum;
+ //printk("*data = 0x%08x\n", *data);
+ return 4;
+}
+
+static int trng4xx_init(struct hwrng *rng)
+{
+ return trng4xx_hw_init();
+}
+
+static void trng4xx_cleanup(struct hwrng *rng)
+{
+ trng4xx_hw_deinit();
+}
+
+static struct hwrng trng4xx_func = {
+ .name = "ppc4xx-trng",
+ .init = trng4xx_init,
+ .cleanup = trng4xx_cleanup,
+ .data_present = trng4xx_data_present,
+ .data_read = trng4xx_data_read,
+ .priv = (unsigned long) &trng4xx_dev,
+};
+
+/**
+ * Setup Driver with platform registration
+ *
+ */
+static int __devinit trng4xx_probe(struct of_device *ofdev,
+ const struct of_device_id *match)
+{
+ struct hal_config hw_cfg;
+ int rc;
+
+ hw_cfg.ofdev = ofdev;
+ rc = trng4xx_config_set(&hw_cfg);
+ if (rc != 0)
+ return rc;
+
+ TRNG4XX_LOG("AMCC 4xx TRNG v%s @0x%02X_%08X size %d IRQ %d\n",
+ TRNG4XX_VER_STR,
+ (u32) (trng4xx_dev.res.start >> 32),
+ (u32) trng4xx_dev.res.start,
+ (u32) (trng4xx_dev.res.end - trng4xx_dev.res.start + 1),
+ trng4xx_dev.irq);
+
+ init_MUTEX(&trng4xx_dev.access_prot);
+
+ rc = hwrng_register(&trng4xx_func);
+ if (rc) {
+ printk(KERN_ERR
+ "AMCC 4xx TRNG registering failed error %d\n", rc);
+ goto err;
+ }
+
+ return rc;
+
+err:
+ trng4xx_pka_config_clear();
+ return rc;
+}
+
+static int __devexit trng4xx_remove(struct of_device *dev)
+{
+ hwrng_unregister(&trng4xx_func);
+ trng4xx_pka_config_clear();
+ return 0;
+}
+
+static struct of_device_id trng4xx_match[] = {
+ { .compatible = "ppc4xx-trng", },
+ { .compatible = "amcc,ppc4xx-trng", },
+ { },
+};
+
+static struct of_platform_driver trng4xx_driver = {
+ .name = "ppc4xx-trng",
+ .match_table = trng4xx_match,
+ .probe = trng4xx_probe,
+ .remove = trng4xx_remove,
+};
+
+static int __init mod_init(void)
+{
+ return of_register_platform_driver(&trng4xx_driver);
+}
+
+static void __exit mod_exit(void)
+{
+ of_unregister_platform_driver(&trng4xx_driver);
+}
+
+module_init(mod_init);
+module_exit(mod_exit);
+
+MODULE_DESCRIPTION("AMCC 4xx True Random Number Generator");
+MODULE_LICENSE("GPL");