2017-12-22 17:48:35 +01:00
// 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/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 ;
2018-01-10 12:36:58 +03:00
int val ;
2017-12-22 17:48:35 +01:00
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 ;
struct resource * res ;
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 ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
trng - > mem = devm_ioremap_resource ( & pdev - > dev , res ) ;
if ( IS_ERR ( trng - > mem ) ) {
dev_err ( & pdev - > dev , " Could not map IO resources. \n " ) ;
return PTR_ERR ( trng - > mem ) ;
}
pm_runtime_enable ( & pdev - > dev ) ;
ret = pm_runtime_get_sync ( & 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 = hwrng_register ( & 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_sync ( & pdev - > dev ) ;
err_pm_get :
pm_runtime_disable ( & pdev - > dev ) ;
return ret ;
}
static int exynos_trng_remove ( struct platform_device * pdev )
{
struct exynos_trng_dev * trng = platform_get_drvdata ( pdev ) ;
hwrng_unregister ( & trng - > rng ) ;
clk_disable_unprepare ( trng - > clk ) ;
pm_runtime_put_sync ( & pdev - > dev ) ;
pm_runtime_disable ( & pdev - > dev ) ;
return 0 ;
}
static int __maybe_unused exynos_trng_suspend ( struct device * dev )
{
pm_runtime_put_sync ( dev ) ;
return 0 ;
}
static int __maybe_unused exynos_trng_resume ( struct device * dev )
{
int ret ;
ret = pm_runtime_get_sync ( dev ) ;
if ( ret < 0 ) {
dev_err ( dev , " Could not get runtime PM. \n " ) ;
pm_runtime_put_noidle ( dev ) ;
return ret ;
}
return 0 ;
}
static 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 = & exynos_trng_pm_ops ,
. of_match_table = exynos_trng_dt_match ,
} ,
. probe = exynos_trng_probe ,
. remove = 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 " ) ;