diff options
Diffstat (limited to 'drivers/char/hw_random/meson-rng.c')
| -rw-r--r-- | drivers/char/hw_random/meson-rng.c | 99 |
1 files changed, 76 insertions, 23 deletions
diff --git a/drivers/char/hw_random/meson-rng.c b/drivers/char/hw_random/meson-rng.c index 8bb30282ca46..75225eb9fef6 100644 --- a/drivers/char/hw_random/meson-rng.c +++ b/drivers/char/hw_random/meson-rng.c @@ -13,14 +13,23 @@ #include <linux/types.h> #include <linux/of.h> #include <linux/clk.h> +#include <linux/iopoll.h> -#define RNG_DATA 0x00 +#define RNG_DATA 0x00 +#define RNG_S4_DATA 0x08 +#define RNG_S4_CFG 0x00 + +#define RUN_BIT BIT(0) +#define SEED_READY_STS_BIT BIT(31) + +struct meson_rng_priv { + int (*read)(struct hwrng *rng, void *buf, size_t max, bool wait); +}; struct meson_rng_data { void __iomem *base; - struct platform_device *pdev; struct hwrng rng; - struct clk *core_clk; + struct device *dev; }; static int meson_rng_read(struct hwrng *rng, void *buf, size_t max, bool wait) @@ -33,52 +42,96 @@ static int meson_rng_read(struct hwrng *rng, void *buf, size_t max, bool wait) return sizeof(u32); } -static void meson_rng_clk_disable(void *data) +static int meson_rng_wait_status(void __iomem *cfg_addr, int bit) { - clk_disable_unprepare(data); + u32 status = 0; + int ret; + + ret = readl_relaxed_poll_timeout_atomic(cfg_addr, + status, !(status & bit), + 10, 10000); + if (ret) + return -EBUSY; + + return 0; +} + +static int meson_s4_rng_read(struct hwrng *rng, void *buf, size_t max, bool wait) +{ + struct meson_rng_data *data = + container_of(rng, struct meson_rng_data, rng); + + void __iomem *cfg_addr = data->base + RNG_S4_CFG; + int err; + + writel_relaxed(readl_relaxed(cfg_addr) | SEED_READY_STS_BIT, cfg_addr); + + err = meson_rng_wait_status(cfg_addr, SEED_READY_STS_BIT); + if (err) { + dev_err(data->dev, "Seed isn't ready, try again\n"); + return err; + } + + err = meson_rng_wait_status(cfg_addr, RUN_BIT); + if (err) { + dev_err(data->dev, "Can't get random number, try again\n"); + return err; + } + + *(u32 *)buf = readl_relaxed(data->base + RNG_S4_DATA); + + return sizeof(u32); } static int meson_rng_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct meson_rng_data *data; - int ret; + struct clk *core_clk; + const struct meson_rng_priv *priv; data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; - data->pdev = pdev; + priv = device_get_match_data(&pdev->dev); + if (!priv) + return -ENODEV; data->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(data->base)) return PTR_ERR(data->base); - data->core_clk = devm_clk_get_optional(dev, "core"); - if (IS_ERR(data->core_clk)) - return dev_err_probe(dev, PTR_ERR(data->core_clk), + core_clk = devm_clk_get_optional_enabled(dev, "core"); + if (IS_ERR(core_clk)) + return dev_err_probe(dev, PTR_ERR(core_clk), "Failed to get core clock\n"); - if (data->core_clk) { - ret = clk_prepare_enable(data->core_clk); - if (ret) - return ret; - ret = devm_add_action_or_reset(dev, meson_rng_clk_disable, - data->core_clk); - if (ret) - return ret; - } - data->rng.name = pdev->name; - data->rng.read = meson_rng_read; + data->rng.read = priv->read; - platform_set_drvdata(pdev, data); + data->dev = &pdev->dev; return devm_hwrng_register(dev, &data->rng); } +static const struct meson_rng_priv meson_rng_priv = { + .read = meson_rng_read, +}; + +static const struct meson_rng_priv meson_rng_priv_s4 = { + .read = meson_s4_rng_read, +}; + static const struct of_device_id meson_rng_of_match[] = { - { .compatible = "amlogic,meson-rng", }, + { + .compatible = "amlogic,meson-rng", + .data = (void *)&meson_rng_priv, + }, + { + .compatible = "amlogic,meson-s4-rng", + .data = (void *)&meson_rng_priv_s4, + }, {}, }; MODULE_DEVICE_TABLE(of, meson_rng_of_match); |
