// SPDX-License-Identifier: GPL-2.0 /* * TRNG driver for the StarFive JH7110 SoC * * Copyright (C) 2022 StarFive Technology Co. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* trng register offset */ #define STARFIVE_CTRL 0x00 #define STARFIVE_STAT 0x04 #define STARFIVE_MODE 0x08 #define STARFIVE_SMODE 0x0C #define STARFIVE_IE 0x10 #define STARFIVE_ISTAT 0x14 #define STARFIVE_RAND0 0x20 #define STARFIVE_RAND1 0x24 #define STARFIVE_RAND2 0x28 #define STARFIVE_RAND3 0x2C #define STARFIVE_RAND4 0x30 #define STARFIVE_RAND5 0x34 #define STARFIVE_RAND6 0x38 #define STARFIVE_RAND7 0x3C #define STARFIVE_AUTO_RQSTS 0x60 #define STARFIVE_AUTO_AGE 0x64 /* CTRL CMD */ #define STARFIVE_CTRL_EXEC_NOP 0x0 #define STARFIVE_CTRL_GENE_RANDNUM 0x1 #define STARFIVE_CTRL_EXEC_RANDRESEED 0x2 /* STAT */ #define STARFIVE_STAT_NONCE_MODE BIT(2) #define STARFIVE_STAT_R256 BIT(3) #define STARFIVE_STAT_MISSION_MODE BIT(8) #define STARFIVE_STAT_SEEDED BIT(9) #define STARFIVE_STAT_LAST_RESEED(x) ((x) << 16) #define STARFIVE_STAT_SRVC_RQST BIT(27) #define STARFIVE_STAT_RAND_GENERATING BIT(30) #define STARFIVE_STAT_RAND_SEEDING BIT(31) /* MODE */ #define STARFIVE_MODE_R256 BIT(3) /* SMODE */ #define STARFIVE_SMODE_NONCE_MODE BIT(2) #define STARFIVE_SMODE_MISSION_MODE BIT(8) #define STARFIVE_SMODE_MAX_REJECTS(x) ((x) << 16) /* IE */ #define STARFIVE_IE_RAND_RDY_EN BIT(0) #define STARFIVE_IE_SEED_DONE_EN BIT(1) #define STARFIVE_IE_LFSR_LOCKUP_EN BIT(4) #define STARFIVE_IE_GLBL_EN BIT(31) #define STARFIVE_IE_ALL (STARFIVE_IE_GLBL_EN | \ STARFIVE_IE_RAND_RDY_EN | \ STARFIVE_IE_SEED_DONE_EN | \ STARFIVE_IE_LFSR_LOCKUP_EN) /* ISTAT */ #define STARFIVE_ISTAT_RAND_RDY BIT(0) #define STARFIVE_ISTAT_SEED_DONE BIT(1) #define STARFIVE_ISTAT_LFSR_LOCKUP BIT(4) #define STARFIVE_RAND_LEN sizeof(u32) #define to_trng(p) container_of(p, struct starfive_trng, rng) enum reseed { RANDOM_RESEED, NONCE_RESEED, }; enum mode { PRNG_128BIT, PRNG_256BIT, }; struct starfive_trng { struct device *dev; void __iomem *base; struct clk *hclk; struct clk *ahb; struct reset_control *rst; struct hwrng rng; struct completion random_done; struct completion reseed_done; u32 mode; u32 mission; u32 reseed; /* protects against concurrent write to ctrl register */ spinlock_t write_lock; }; static u16 autoreq; module_param(autoreq, ushort, 0); MODULE_PARM_DESC(autoreq, "Auto-reseeding after random number requests by host reaches specified counter:\n" " 0 - disable counter\n" " other - reload value for internal counter"); static u16 autoage; module_param(autoage, ushort, 0); MODULE_PARM_DESC(autoage, "Auto-reseeding after specified timer countdowns to 0:\n" " 0 - disable timer\n" " other - reload value for internal timer"); static inline int starfive_trng_wait_idle(struct starfive_trng *trng) { u32 stat; return readl_relaxed_poll_timeout(trng->base + STARFIVE_STAT, stat, !(stat & (STARFIVE_STAT_RAND_GENERATING | STARFIVE_STAT_RAND_SEEDING)), 10, 100000); } static inline void starfive_trng_irq_mask_clear(struct starfive_trng *trng) { /* clear register: ISTAT */ u32 data = readl(trng->base + STARFIVE_ISTAT); writel(data, trng->base + STARFIVE_ISTAT); } static int starfive_trng_cmd(struct starfive_trng *trng, u32 cmd, bool wait) { int wait_time = 1000; /* allow up to 40 us for wait == 0 */ if (!wait) wait_time = 40; switch (cmd) { case STARFIVE_CTRL_GENE_RANDNUM: reinit_completion(&trng->random_done); spin_lock_irq(&trng->write_lock); writel(cmd, trng->base + STARFIVE_CTRL); spin_unlock_irq(&trng->write_lock); if (!wait_for_completion_timeout(&trng->random_done, usecs_to_jiffies(wait_time))) return -ETIMEDOUT; break; case STARFIVE_CTRL_EXEC_RANDRESEED: reinit_completion(&trng->reseed_done); spin_lock_irq(&trng->write_lock); writel(cmd, trng->base + STARFIVE_CTRL); spin_unlock_irq(&trng->write_lock); if (!wait_for_completion_timeout(&trng->reseed_done, usecs_to_jiffies(wait_time))) return -ETIMEDOUT; break; default: return -EINVAL; } return 0; } static int starfive_trng_init(struct hwrng *rng) { struct starfive_trng *trng = to_trng(rng); u32 mode, intr = 0; /* setup Auto Request/Age register */ writel(autoage, trng->base + STARFIVE_AUTO_AGE); writel(autoreq, trng->base + STARFIVE_AUTO_RQSTS); /* clear register: ISTAT */ starfive_trng_irq_mask_clear(trng); intr |= STARFIVE_IE_ALL; writel(intr, trng->base + STARFIVE_IE); mode = readl(trng->base + STARFIVE_MODE); switch (trng->mode) { case PRNG_128BIT: mode &= ~STARFIVE_MODE_R256; break; case PRNG_256BIT: mode |= STARFIVE_MODE_R256; break; default: mode |= STARFIVE_MODE_R256; break; } writel(mode, trng->base + STARFIVE_MODE); return starfive_trng_cmd(trng, STARFIVE_CTRL_EXEC_RANDRESEED, 1); } static irqreturn_t starfive_trng_irq(int irq, void *priv) { u32 status; struct starfive_trng *trng = (struct starfive_trng *)priv; status = readl(trng->base + STARFIVE_ISTAT); if (status & STARFIVE_ISTAT_RAND_RDY) { writel(STARFIVE_ISTAT_RAND_RDY, trng->base + STARFIVE_ISTAT); complete(&trng->random_done); } if (status & STARFIVE_ISTAT_SEED_DONE) { writel(STARFIVE_ISTAT_SEED_DONE, trng->base + STARFIVE_ISTAT); complete(&trng->reseed_done); } if (status & STARFIVE_ISTAT_LFSR_LOCKUP) { writel(STARFIVE_ISTAT_LFSR_LOCKUP, trng->base + STARFIVE_ISTAT); /* SEU occurred, reseeding required*/ spin_lock(&trng->write_lock); writel(STARFIVE_CTRL_EXEC_RANDRESEED, trng->base + STARFIVE_CTRL); spin_unlock(&trng->write_lock); } return IRQ_HANDLED; } static void starfive_trng_cleanup(struct hwrng *rng) { struct starfive_trng *trng = to_trng(rng); writel(0, trng->base + STARFIVE_CTRL); reset_control_assert(trng->rst); clk_disable_unprepare(trng->hclk); clk_disable_unprepare(trng->ahb); } static int starfive_trng_read(struct hwrng *rng, void *buf, size_t max, bool wait) { struct starfive_trng *trng = to_trng(rng); int ret; pm_runtime_get_sync(trng->dev); if (trng->mode == PRNG_256BIT) max = min_t(size_t, max, (STARFIVE_RAND_LEN * 8)); else max = min_t(size_t, max, (STARFIVE_RAND_LEN * 4)); if (wait) { ret = starfive_trng_wait_idle(trng); if (ret) return -ETIMEDOUT; } ret = starfive_trng_cmd(trng, STARFIVE_CTRL_GENE_RANDNUM, wait); if (ret) return ret; memcpy_fromio(buf, trng->base + STARFIVE_RAND0, max); pm_runtime_put_sync_autosuspend(trng->dev); return max; } static int starfive_trng_probe(struct platform_device *pdev) { int ret; int irq; struct starfive_trng *trng; trng = devm_kzalloc(&pdev->dev, sizeof(*trng), GFP_KERNEL); if (!trng) return -ENOMEM; platform_set_drvdata(pdev, trng); trng->dev = &pdev->dev; trng->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(trng->base)) return dev_err_probe(&pdev->dev, PTR_ERR(trng->base), "Error remapping memory for platform device.\n"); irq = platform_get_irq(pdev, 0); if (irq < 0) return irq; init_completion(&trng->random_done); init_completion(&trng->reseed_done); spin_lock_init(&trng->write_lock); ret = devm_request_irq(&pdev->dev, irq, starfive_trng_irq, 0, pdev->name, (void *)trng); if (ret) return dev_err_probe(&pdev->dev, ret, "Failed to register interrupt handler\n"); trng->hclk = devm_clk_get(&pdev->dev, "hclk"); if (IS_ERR(trng->hclk)) return dev_err_probe(&pdev->dev, PTR_ERR(trng->hclk), "Error getting hardware reference clock\n"); trng->ahb = devm_clk_get(&pdev->dev, "ahb"); if (IS_ERR(trng->ahb)) return dev_err_probe(&pdev->dev, PTR_ERR(trng->ahb), "Error getting ahb reference clock\n"); trng->rst = devm_reset_control_get_shared(&pdev->dev, NULL); if (IS_ERR(trng->rst)) return dev_err_probe(&pdev->dev, PTR_ERR(trng->rst), "Error getting hardware reset line\n"); clk_prepare_enable(trng->hclk); clk_prepare_enable(trng->ahb); reset_control_deassert(trng->rst); trng->rng.name = dev_driver_string(&pdev->dev); trng->rng.init = starfive_trng_init; trng->rng.cleanup = starfive_trng_cleanup; trng->rng.read = starfive_trng_read; trng->mode = PRNG_256BIT; trng->mission = 1; trng->reseed = RANDOM_RESEED; pm_runtime_use_autosuspend(&pdev->dev); pm_runtime_set_autosuspend_delay(&pdev->dev, 100); pm_runtime_enable(&pdev->dev); ret = devm_hwrng_register(&pdev->dev, &trng->rng); if (ret) { pm_runtime_disable(&pdev->dev); reset_control_assert(trng->rst); clk_disable_unprepare(trng->ahb); clk_disable_unprepare(trng->hclk); return dev_err_probe(&pdev->dev, ret, "Failed to register hwrng\n"); } return 0; } static int __maybe_unused starfive_trng_suspend(struct device *dev) { struct starfive_trng *trng = dev_get_drvdata(dev); clk_disable_unprepare(trng->hclk); clk_disable_unprepare(trng->ahb); return 0; } static int __maybe_unused starfive_trng_resume(struct device *dev) { struct starfive_trng *trng = dev_get_drvdata(dev); clk_prepare_enable(trng->hclk); clk_prepare_enable(trng->ahb); return 0; } static const struct dev_pm_ops starfive_trng_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(starfive_trng_suspend, starfive_trng_resume) SET_RUNTIME_PM_OPS(starfive_trng_suspend, starfive_trng_resume, NULL) }; static const struct of_device_id trng_dt_ids[] __maybe_unused = { { .compatible = "starfive,jh7110-trng" }, { } }; MODULE_DEVICE_TABLE(of, trng_dt_ids); static struct platform_driver starfive_trng_driver = { .probe = starfive_trng_probe, .driver = { .name = "jh7110-trng", .pm = &starfive_trng_pm_ops, .of_match_table = of_match_ptr(trng_dt_ids), }, }; module_platform_driver(starfive_trng_driver); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("StarFive True Random Number Generator");