linux/drivers/char/hw_random/exynos-trng.c
Uwe Kleine-König 9daec3cba0 hwrng: exynos - Convert to platform remove callback returning void
The .remove() callback for a platform driver returns an int which makes
many driver authors wrongly assume it's possible to do error handling by
returning an error code. However the value returned is ignored (apart
from emitting a warning) and this typically results in resource leaks.

To improve here there is a quest to make the remove callback return
void. In the first step of this quest all drivers are converted to
.remove_new(), which already returns void. Eventually after all drivers
are converted, .remove_new() will be renamed to .remove().

Trivially convert this driver from always returning zero in the remove
callback to the void returning variant.

Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
Acked-by: Lukasz Stelmach <l.stelmach@samsung.com>
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
2023-12-15 17:52:54 +08:00

231 lines
5.3 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* RNG driver for Exynos TRNGs
*
* Author: Łukasz Stelmach <l.stelmach@samsung.com>
*
* Copyright 2017 (c) Samsung Electronics Software, Inc.
*
* Based on the Exynos PRNG driver drivers/crypto/exynos-rng by
* Krzysztof Kozłowski <krzk@kernel.org>
*/
#include <linux/clk.h>
#include <linux/crypto.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/hw_random.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#define EXYNOS_TRNG_CLKDIV (0x0)
#define EXYNOS_TRNG_CTRL (0x20)
#define EXYNOS_TRNG_CTRL_RNGEN BIT(31)
#define EXYNOS_TRNG_POST_CTRL (0x30)
#define EXYNOS_TRNG_ONLINE_CTRL (0x40)
#define EXYNOS_TRNG_ONLINE_STAT (0x44)
#define EXYNOS_TRNG_ONLINE_MAXCHI2 (0x48)
#define EXYNOS_TRNG_FIFO_CTRL (0x50)
#define EXYNOS_TRNG_FIFO_0 (0x80)
#define EXYNOS_TRNG_FIFO_1 (0x84)
#define EXYNOS_TRNG_FIFO_2 (0x88)
#define EXYNOS_TRNG_FIFO_3 (0x8c)
#define EXYNOS_TRNG_FIFO_4 (0x90)
#define EXYNOS_TRNG_FIFO_5 (0x94)
#define EXYNOS_TRNG_FIFO_6 (0x98)
#define EXYNOS_TRNG_FIFO_7 (0x9c)
#define EXYNOS_TRNG_FIFO_LEN (8)
#define EXYNOS_TRNG_CLOCK_RATE (500000)
struct exynos_trng_dev {
struct device *dev;
void __iomem *mem;
struct clk *clk;
struct hwrng rng;
};
static int exynos_trng_do_read(struct hwrng *rng, void *data, size_t max,
bool wait)
{
struct exynos_trng_dev *trng;
int val;
max = min_t(size_t, max, (EXYNOS_TRNG_FIFO_LEN * 4));
trng = (struct exynos_trng_dev *)rng->priv;
writel_relaxed(max * 8, trng->mem + EXYNOS_TRNG_FIFO_CTRL);
val = readl_poll_timeout(trng->mem + EXYNOS_TRNG_FIFO_CTRL, val,
val == 0, 200, 1000000);
if (val < 0)
return val;
memcpy_fromio(data, trng->mem + EXYNOS_TRNG_FIFO_0, max);
return max;
}
static int exynos_trng_init(struct hwrng *rng)
{
struct exynos_trng_dev *trng = (struct exynos_trng_dev *)rng->priv;
unsigned long sss_rate;
u32 val;
sss_rate = clk_get_rate(trng->clk);
/*
* For most TRNG circuits the clock frequency of under 500 kHz
* is safe.
*/
val = sss_rate / (EXYNOS_TRNG_CLOCK_RATE * 2);
if (val > 0x7fff) {
dev_err(trng->dev, "clock divider too large: %d", val);
return -ERANGE;
}
val = val << 1;
writel_relaxed(val, trng->mem + EXYNOS_TRNG_CLKDIV);
/* Enable the generator. */
val = EXYNOS_TRNG_CTRL_RNGEN;
writel_relaxed(val, trng->mem + EXYNOS_TRNG_CTRL);
/*
* Disable post-processing. /dev/hwrng is supposed to deliver
* unprocessed data.
*/
writel_relaxed(0, trng->mem + EXYNOS_TRNG_POST_CTRL);
return 0;
}
static int exynos_trng_probe(struct platform_device *pdev)
{
struct exynos_trng_dev *trng;
int ret = -ENOMEM;
trng = devm_kzalloc(&pdev->dev, sizeof(*trng), GFP_KERNEL);
if (!trng)
return ret;
trng->rng.name = devm_kstrdup(&pdev->dev, dev_name(&pdev->dev),
GFP_KERNEL);
if (!trng->rng.name)
return ret;
trng->rng.init = exynos_trng_init;
trng->rng.read = exynos_trng_do_read;
trng->rng.priv = (unsigned long) trng;
platform_set_drvdata(pdev, trng);
trng->dev = &pdev->dev;
trng->mem = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(trng->mem))
return PTR_ERR(trng->mem);
pm_runtime_enable(&pdev->dev);
ret = pm_runtime_resume_and_get(&pdev->dev);
if (ret < 0) {
dev_err(&pdev->dev, "Could not get runtime PM.\n");
goto err_pm_get;
}
trng->clk = devm_clk_get(&pdev->dev, "secss");
if (IS_ERR(trng->clk)) {
ret = PTR_ERR(trng->clk);
dev_err(&pdev->dev, "Could not get clock.\n");
goto err_clock;
}
ret = clk_prepare_enable(trng->clk);
if (ret) {
dev_err(&pdev->dev, "Could not enable the clk.\n");
goto err_clock;
}
ret = devm_hwrng_register(&pdev->dev, &trng->rng);
if (ret) {
dev_err(&pdev->dev, "Could not register hwrng device.\n");
goto err_register;
}
dev_info(&pdev->dev, "Exynos True Random Number Generator.\n");
return 0;
err_register:
clk_disable_unprepare(trng->clk);
err_clock:
pm_runtime_put_noidle(&pdev->dev);
err_pm_get:
pm_runtime_disable(&pdev->dev);
return ret;
}
static void exynos_trng_remove(struct platform_device *pdev)
{
struct exynos_trng_dev *trng = platform_get_drvdata(pdev);
clk_disable_unprepare(trng->clk);
pm_runtime_put_sync(&pdev->dev);
pm_runtime_disable(&pdev->dev);
}
static int exynos_trng_suspend(struct device *dev)
{
pm_runtime_put_sync(dev);
return 0;
}
static int exynos_trng_resume(struct device *dev)
{
int ret;
ret = pm_runtime_resume_and_get(dev);
if (ret < 0) {
dev_err(dev, "Could not get runtime PM.\n");
return ret;
}
return 0;
}
static DEFINE_SIMPLE_DEV_PM_OPS(exynos_trng_pm_ops, exynos_trng_suspend,
exynos_trng_resume);
static const struct of_device_id exynos_trng_dt_match[] = {
{
.compatible = "samsung,exynos5250-trng",
},
{ },
};
MODULE_DEVICE_TABLE(of, exynos_trng_dt_match);
static struct platform_driver exynos_trng_driver = {
.driver = {
.name = "exynos-trng",
.pm = pm_sleep_ptr(&exynos_trng_pm_ops),
.of_match_table = exynos_trng_dt_match,
},
.probe = exynos_trng_probe,
.remove_new = exynos_trng_remove,
};
module_platform_driver(exynos_trng_driver);
MODULE_AUTHOR("Łukasz Stelmach");
MODULE_DESCRIPTION("H/W TRNG driver for Exynos chips");
MODULE_LICENSE("GPL v2");