// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright 2020, Heinrich Schuchardt * * Driver for Amlogic hardware random number generator */ #include #include #include #include #include #include #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 { u32 (*read)(fdt_addr_t base); }; struct meson_rng_plat { fdt_addr_t base; struct clk clk; struct meson_rng_priv *priv; }; /** * meson_rng_read() - fill buffer with random bytes * * @buffer: buffer to receive data * @size: size of buffer * * Return: 0 */ static int meson_rng_read(struct udevice *dev, void *data, size_t len) { struct meson_rng_plat *pdata = dev_get_plat(dev); struct meson_rng_priv *priv = pdata->priv; char *buffer = (char *)data; while (len) { u32 rand = priv->read(pdata->base); size_t step; if (len >= 4) step = 4; else step = len; memcpy(buffer, &rand, step); buffer += step; len -= step; } return 0; } static int meson_rng_wait_status(void __iomem *cfg_addr, int bit) { u32 status = 0; int ret; ret = readl_relaxed_poll_timeout(cfg_addr, status, !(status & bit), 10000); if (ret) return -EBUSY; return 0; } static u32 meson_common_rng_read(fdt_addr_t base) { return readl(base); } static u32 meson_s4_rng_read(fdt_addr_t base) { void __iomem *cfg_addr = (void *)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) { pr_err("Seed isn't ready, try again\n"); return err; } err = meson_rng_wait_status(cfg_addr, RUN_BIT); if (err) { pr_err("Can't get random number, try again\n"); return err; } return readl_relaxed(base + RNG_S4_DATA); } /** * meson_rng_probe() - probe rng device * * @dev: device * Return: 0 if ok */ static int meson_rng_probe(struct udevice *dev) { struct meson_rng_plat *pdata = dev_get_plat(dev); int err; err = clk_enable(&pdata->clk); if (err) return err; pdata->priv = (struct meson_rng_priv *)dev_get_driver_data(dev); return 0; } /** * meson_rng_remove() - deinitialize rng device * * @dev: device * Return: 0 if ok */ static int meson_rng_remove(struct udevice *dev) { struct meson_rng_plat *pdata = dev_get_plat(dev); return clk_disable(&pdata->clk); } /** * meson_rng_of_to_plat() - transfer device tree data to plaform data * * @dev: device * Return: 0 if ok */ static int meson_rng_of_to_plat(struct udevice *dev) { struct meson_rng_plat *pdata = dev_get_plat(dev); int err; pdata->base = dev_read_addr(dev); if (!pdata->base) return -ENODEV; /* Get optional "core" clock */ err = clk_get_by_name_optional(dev, "core", &pdata->clk); if (err) return err; return 0; } static const struct dm_rng_ops meson_rng_ops = { .read = meson_rng_read, }; static const struct meson_rng_priv meson_rng_priv = { .read = meson_common_rng_read, }; static const struct meson_rng_priv meson_rng_priv_s4 = { .read = meson_s4_rng_read, }; static const struct udevice_id meson_rng_match[] = { { .compatible = "amlogic,meson-rng", .data = (ulong)&meson_rng_priv, }, { .compatible = "amlogic,meson-s4-rng", .data = (ulong)&meson_rng_priv_s4, }, {}, }; U_BOOT_DRIVER(meson_rng) = { .name = "meson-rng", .id = UCLASS_RNG, .of_match = meson_rng_match, .ops = &meson_rng_ops, .probe = meson_rng_probe, .remove = meson_rng_remove, .plat_auto = sizeof(struct meson_rng_plat), .of_to_plat = meson_rng_of_to_plat, };