// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2015, Daniel Thompson */ #include #include #include #include #include #include #include #include #include #include #include #include #define RNG_CR 0x00 #define RNG_CR_RNGEN BIT(2) #define RNG_CR_CED BIT(5) #define RNG_SR 0x04 #define RNG_SR_SEIS BIT(6) #define RNG_SR_CEIS BIT(5) #define RNG_SR_DRDY BIT(0) #define RNG_DR 0x08 struct stm32_rng_private { struct hwrng rng; void __iomem *base; struct clk *clk; struct reset_control *rst; bool ced; }; static int stm32_rng_read(struct hwrng *rng, void *data, size_t max, bool wait) { struct stm32_rng_private *priv = container_of(rng, struct stm32_rng_private, rng); u32 sr; int retval = 0; pm_runtime_get_sync((struct device *) priv->rng.priv); while (max > sizeof(u32)) { sr = readl_relaxed(priv->base + RNG_SR); /* Manage timeout which is based on timer and take */ /* care of initial delay time when enabling rng */ if (!sr && wait) { retval = readl_relaxed_poll_timeout_atomic(priv->base + RNG_SR, sr, sr, 10, 50000); if (retval) dev_err((struct device *)priv->rng.priv, "%s: timeout %x!\n", __func__, sr); } /* If error detected or data not ready... */ if (sr != RNG_SR_DRDY) { if (WARN_ONCE(sr & (RNG_SR_SEIS | RNG_SR_CEIS), "bad RNG status - %x\n", sr)) writel_relaxed(0, priv->base + RNG_SR); break; } *(u32 *)data = readl_relaxed(priv->base + RNG_DR); retval += sizeof(u32); data += sizeof(u32); max -= sizeof(u32); } pm_runtime_mark_last_busy((struct device *) priv->rng.priv); pm_runtime_put_sync_autosuspend((struct device *) priv->rng.priv); return retval || !wait ? retval : -EIO; } static int stm32_rng_init(struct hwrng *rng) { struct stm32_rng_private *priv = container_of(rng, struct stm32_rng_private, rng); int err; err = clk_prepare_enable(priv->clk); if (err) return err; if (priv->ced) writel_relaxed(RNG_CR_RNGEN, priv->base + RNG_CR); else writel_relaxed(RNG_CR_RNGEN | RNG_CR_CED, priv->base + RNG_CR); /* clear error indicators */ writel_relaxed(0, priv->base + RNG_SR); return 0; } static void stm32_rng_cleanup(struct hwrng *rng) { struct stm32_rng_private *priv = container_of(rng, struct stm32_rng_private, rng); writel_relaxed(0, priv->base + RNG_CR); clk_disable_unprepare(priv->clk); } static int stm32_rng_probe(struct platform_device *ofdev) { struct device *dev = &ofdev->dev; struct device_node *np = ofdev->dev.of_node; struct stm32_rng_private *priv; struct resource res; int err; priv = devm_kzalloc(dev, sizeof(struct stm32_rng_private), GFP_KERNEL); if (!priv) return -ENOMEM; err = of_address_to_resource(np, 0, &res); if (err) return err; priv->base = devm_ioremap_resource(dev, &res); if (IS_ERR(priv->base)) return PTR_ERR(priv->base); priv->clk = devm_clk_get(&ofdev->dev, NULL); if (IS_ERR(priv->clk)) return PTR_ERR(priv->clk); priv->rst = devm_reset_control_get(&ofdev->dev, NULL); if (IS_ERR(priv->rst)) { if (PTR_ERR(priv->rst) != -ENOENT) return PTR_ERR(priv->rst); } else { reset_control_assert(priv->rst); udelay(2); reset_control_deassert(priv->rst); } priv->ced = of_property_read_bool(np, "clock-error-detect"); dev_set_drvdata(dev, priv); priv->rng.name = dev_driver_string(dev), #ifndef CONFIG_PM priv->rng.init = stm32_rng_init, priv->rng.cleanup = stm32_rng_cleanup, #endif priv->rng.read = stm32_rng_read, priv->rng.priv = (unsigned long) dev; priv->rng.quality = 900; pm_runtime_set_autosuspend_delay(dev, 100); pm_runtime_use_autosuspend(dev); pm_runtime_enable(dev); return devm_hwrng_register(dev, &priv->rng); } static int stm32_rng_remove(struct platform_device *ofdev) { pm_runtime_disable(&ofdev->dev); return 0; } #ifdef CONFIG_PM static int stm32_rng_runtime_suspend(struct device *dev) { struct stm32_rng_private *priv = dev_get_drvdata(dev); stm32_rng_cleanup(&priv->rng); return 0; } static int stm32_rng_runtime_resume(struct device *dev) { struct stm32_rng_private *priv = dev_get_drvdata(dev); return stm32_rng_init(&priv->rng); } #endif static const struct dev_pm_ops stm32_rng_pm_ops = { SET_RUNTIME_PM_OPS(stm32_rng_runtime_suspend, stm32_rng_runtime_resume, NULL) SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) }; static const struct of_device_id stm32_rng_match[] = { { .compatible = "st,stm32-rng", }, {}, }; MODULE_DEVICE_TABLE(of, stm32_rng_match); static struct platform_driver stm32_rng_driver = { .driver = { .name = "stm32-rng", .pm = &stm32_rng_pm_ops, .of_match_table = stm32_rng_match, }, .probe = stm32_rng_probe, .remove = stm32_rng_remove, }; module_platform_driver(stm32_rng_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Daniel Thompson "); MODULE_DESCRIPTION("STMicroelectronics STM32 RNG device driver");