2019-05-27 08:55:08 +02:00
// SPDX-License-Identifier: GPL-2.0-or-later
2017-07-23 19:49:06 +02:00
/*
* RNG driver for Freescale RNGC
*
* Copyright ( C ) 2008 - 2012 Freescale Semiconductor , Inc .
* Copyright ( C ) 2017 Martin Kaiser < martin @ kaiser . cx >
*/
# include <linux/module.h>
2018-06-19 22:47:28 -07:00
# include <linux/mod_devicetable.h>
2017-07-23 19:49:06 +02:00
# include <linux/init.h>
# include <linux/kernel.h>
# include <linux/clk.h>
# include <linux/err.h>
# include <linux/platform_device.h>
# include <linux/interrupt.h>
# include <linux/hw_random.h>
# include <linux/completion.h>
# include <linux/io.h>
2020-03-05 21:58:23 +01:00
# define RNGC_VER_ID 0x0000
2017-07-23 19:49:06 +02:00
# define RNGC_COMMAND 0x0004
# define RNGC_CONTROL 0x0008
# define RNGC_STATUS 0x000C
# define RNGC_ERROR 0x0010
# define RNGC_FIFO 0x0014
2020-03-05 21:58:23 +01:00
/* the fields in the ver id register */
# define RNGC_TYPE_SHIFT 28
# define RNGC_VER_MAJ_SHIFT 8
/* the rng_type field */
# define RNGC_TYPE_RNGB 0x1
# define RNGC_TYPE_RNGC 0x2
2017-07-23 19:49:06 +02:00
# define RNGC_CMD_CLR_ERR 0x00000020
# define RNGC_CMD_CLR_INT 0x00000010
# define RNGC_CMD_SEED 0x00000002
# define RNGC_CMD_SELF_TEST 0x00000001
# define RNGC_CTRL_MASK_ERROR 0x00000040
# define RNGC_CTRL_MASK_DONE 0x00000020
2020-03-05 21:58:21 +01:00
# define RNGC_CTRL_AUTO_SEED 0x00000010
2017-07-23 19:49:06 +02:00
# define RNGC_STATUS_ERROR 0x00010000
# define RNGC_STATUS_FIFO_LEVEL_MASK 0x00000f00
# define RNGC_STATUS_FIFO_LEVEL_SHIFT 8
# define RNGC_STATUS_SEED_DONE 0x00000020
# define RNGC_STATUS_ST_DONE 0x00000010
# define RNGC_ERROR_STATUS_STAT_ERR 0x00000008
# define RNGC_TIMEOUT 3000 /* 3 sec */
static bool self_test = true ;
module_param ( self_test , bool , 0 ) ;
struct imx_rngc {
struct device * dev ;
struct clk * clk ;
void __iomem * base ;
struct hwrng rng ;
struct completion rng_op_done ;
/*
* err_reg is written only by the irq handler and read only
* when interrupts are masked , we need no spinlock
*/
u32 err_reg ;
} ;
static inline void imx_rngc_irq_mask_clear ( struct imx_rngc * rngc )
{
u32 ctrl , cmd ;
/* mask interrupts */
ctrl = readl ( rngc - > base + RNGC_CONTROL ) ;
ctrl | = RNGC_CTRL_MASK_DONE | RNGC_CTRL_MASK_ERROR ;
writel ( ctrl , rngc - > base + RNGC_CONTROL ) ;
/*
* CLR_INT clears the interrupt only if there ' s no error
* CLR_ERR clear the interrupt and the error register if there
* is an error
*/
cmd = readl ( rngc - > base + RNGC_COMMAND ) ;
cmd | = RNGC_CMD_CLR_INT | RNGC_CMD_CLR_ERR ;
writel ( cmd , rngc - > base + RNGC_COMMAND ) ;
}
static inline void imx_rngc_irq_unmask ( struct imx_rngc * rngc )
{
u32 ctrl ;
ctrl = readl ( rngc - > base + RNGC_CONTROL ) ;
ctrl & = ~ ( RNGC_CTRL_MASK_DONE | RNGC_CTRL_MASK_ERROR ) ;
writel ( ctrl , rngc - > base + RNGC_CONTROL ) ;
}
static int imx_rngc_self_test ( struct imx_rngc * rngc )
{
u32 cmd ;
int ret ;
imx_rngc_irq_unmask ( rngc ) ;
/* run self test */
cmd = readl ( rngc - > base + RNGC_COMMAND ) ;
writel ( cmd | RNGC_CMD_SELF_TEST , rngc - > base + RNGC_COMMAND ) ;
ret = wait_for_completion_timeout ( & rngc - > rng_op_done , RNGC_TIMEOUT ) ;
2020-03-05 21:58:24 +01:00
imx_rngc_irq_mask_clear ( rngc ) ;
if ( ! ret )
2017-07-23 19:49:06 +02:00
return - ETIMEDOUT ;
2020-03-05 21:58:24 +01:00
return rngc - > err_reg ? - EIO : 0 ;
2017-07-23 19:49:06 +02:00
}
static int imx_rngc_read ( struct hwrng * rng , void * data , size_t max , bool wait )
{
struct imx_rngc * rngc = container_of ( rng , struct imx_rngc , rng ) ;
unsigned int status ;
unsigned int level ;
int retval = 0 ;
while ( max > = sizeof ( u32 ) ) {
status = readl ( rngc - > base + RNGC_STATUS ) ;
/* is there some error while reading this random number? */
if ( status & RNGC_STATUS_ERROR )
break ;
/* how many random numbers are in FIFO? [0-16] */
level = ( status & RNGC_STATUS_FIFO_LEVEL_MASK ) > >
RNGC_STATUS_FIFO_LEVEL_SHIFT ;
if ( level ) {
/* retrieve a random number from FIFO */
* ( u32 * ) data = readl ( rngc - > base + RNGC_FIFO ) ;
retval + = sizeof ( u32 ) ;
data + = sizeof ( u32 ) ;
max - = sizeof ( u32 ) ;
}
}
return retval ? retval : - EIO ;
}
static irqreturn_t imx_rngc_irq ( int irq , void * priv )
{
struct imx_rngc * rngc = ( struct imx_rngc * ) priv ;
u32 status ;
/*
* clearing the interrupt will also clear the error register
* read error and status before clearing
*/
status = readl ( rngc - > base + RNGC_STATUS ) ;
rngc - > err_reg = readl ( rngc - > base + RNGC_ERROR ) ;
imx_rngc_irq_mask_clear ( rngc ) ;
if ( status & ( RNGC_STATUS_SEED_DONE | RNGC_STATUS_ST_DONE ) )
complete ( & rngc - > rng_op_done ) ;
return IRQ_HANDLED ;
}
static int imx_rngc_init ( struct hwrng * rng )
{
struct imx_rngc * rngc = container_of ( rng , struct imx_rngc , rng ) ;
2020-03-05 21:58:21 +01:00
u32 cmd , ctrl ;
2017-07-23 19:49:06 +02:00
int ret ;
/* clear error */
cmd = readl ( rngc - > base + RNGC_COMMAND ) ;
writel ( cmd | RNGC_CMD_CLR_ERR , rngc - > base + RNGC_COMMAND ) ;
2020-03-05 21:58:24 +01:00
imx_rngc_irq_unmask ( rngc ) ;
2017-07-23 19:49:06 +02:00
/* create seed, repeat while there is some statistical error */
do {
/* seed creation */
cmd = readl ( rngc - > base + RNGC_COMMAND ) ;
writel ( cmd | RNGC_CMD_SEED , rngc - > base + RNGC_COMMAND ) ;
ret = wait_for_completion_timeout ( & rngc - > rng_op_done ,
RNGC_TIMEOUT ) ;
if ( ! ret ) {
2020-03-05 21:58:24 +01:00
ret = - ETIMEDOUT ;
goto err ;
2017-07-23 19:49:06 +02:00
}
} while ( rngc - > err_reg = = RNGC_ERROR_STATUS_STAT_ERR ) ;
2020-03-05 21:58:24 +01:00
if ( rngc - > err_reg ) {
ret = - EIO ;
goto err ;
}
2020-03-05 21:58:21 +01:00
/*
* enable automatic seeding , the rngc creates a new seed automatically
* after serving 2 ^ 20 random 160 - bit words
*/
ctrl = readl ( rngc - > base + RNGC_CONTROL ) ;
ctrl | = RNGC_CTRL_AUTO_SEED ;
writel ( ctrl , rngc - > base + RNGC_CONTROL ) ;
2020-03-05 21:58:24 +01:00
/*
* if initialisation was successful , we keep the interrupt
* unmasked until imx_rngc_cleanup is called
* we mask the interrupt ourselves if we return an error
*/
2020-03-05 21:58:21 +01:00
return 0 ;
2020-03-05 21:58:24 +01:00
err :
imx_rngc_irq_mask_clear ( rngc ) ;
return ret ;
}
static void imx_rngc_cleanup ( struct hwrng * rng )
{
struct imx_rngc * rngc = container_of ( rng , struct imx_rngc , rng ) ;
imx_rngc_irq_mask_clear ( rngc ) ;
2017-07-23 19:49:06 +02:00
}
static int imx_rngc_probe ( struct platform_device * pdev )
{
struct imx_rngc * rngc ;
int ret ;
int irq ;
2020-03-05 21:58:23 +01:00
u32 ver_id ;
u8 rng_type ;
2017-07-23 19:49:06 +02:00
rngc = devm_kzalloc ( & pdev - > dev , sizeof ( * rngc ) , GFP_KERNEL ) ;
if ( ! rngc )
return - ENOMEM ;
2019-07-17 17:04:37 +08:00
rngc - > base = devm_platform_ioremap_resource ( pdev , 0 ) ;
2017-07-23 19:49:06 +02:00
if ( IS_ERR ( rngc - > base ) )
return PTR_ERR ( rngc - > base ) ;
rngc - > clk = devm_clk_get ( & pdev - > dev , NULL ) ;
if ( IS_ERR ( rngc - > clk ) ) {
dev_err ( & pdev - > dev , " Can not get rng_clk \n " ) ;
return PTR_ERR ( rngc - > clk ) ;
}
irq = platform_get_irq ( pdev , 0 ) ;
if ( irq < = 0 ) {
dev_err ( & pdev - > dev , " Couldn't get irq %d \n " , irq ) ;
return irq ;
}
ret = clk_prepare_enable ( rngc - > clk ) ;
if ( ret )
return ret ;
2020-03-05 21:58:23 +01:00
ver_id = readl ( rngc - > base + RNGC_VER_ID ) ;
rng_type = ver_id > > RNGC_TYPE_SHIFT ;
/*
* This driver supports only RNGC and RNGB . ( There ' s a different
* driver for RNGA . )
*/
if ( rng_type ! = RNGC_TYPE_RNGC & & rng_type ! = RNGC_TYPE_RNGB ) {
ret = - ENODEV ;
goto err ;
}
2017-07-23 19:49:06 +02:00
ret = devm_request_irq ( & pdev - > dev ,
irq , imx_rngc_irq , 0 , pdev - > name , ( void * ) rngc ) ;
if ( ret ) {
dev_err ( rngc - > dev , " Can't get interrupt working. \n " ) ;
goto err ;
}
init_completion ( & rngc - > rng_op_done ) ;
rngc - > rng . name = pdev - > name ;
rngc - > rng . init = imx_rngc_init ;
rngc - > rng . read = imx_rngc_read ;
2020-03-05 21:58:24 +01:00
rngc - > rng . cleanup = imx_rngc_cleanup ;
2020-08-31 16:00:42 +02:00
rngc - > rng . quality = 19 ;
2017-07-23 19:49:06 +02:00
rngc - > dev = & pdev - > dev ;
platform_set_drvdata ( pdev , rngc ) ;
imx_rngc_irq_mask_clear ( rngc ) ;
if ( self_test ) {
ret = imx_rngc_self_test ( rngc ) ;
if ( ret ) {
2020-03-05 21:58:22 +01:00
dev_err ( rngc - > dev , " self test failed \n " ) ;
2017-07-23 19:49:06 +02:00
goto err ;
}
}
ret = hwrng_register ( & rngc - > rng ) ;
if ( ret ) {
2020-03-05 21:58:22 +01:00
dev_err ( & pdev - > dev , " hwrng registration failed \n " ) ;
2017-07-23 19:49:06 +02:00
goto err ;
}
2020-03-05 21:58:23 +01:00
dev_info ( & pdev - > dev ,
" Freescale RNG%c registered (HW revision %d.%02d) \n " ,
rng_type = = RNGC_TYPE_RNGB ? ' B ' : ' C ' ,
( ver_id > > RNGC_VER_MAJ_SHIFT ) & 0xff , ver_id & 0xff ) ;
2017-07-23 19:49:06 +02:00
return 0 ;
err :
clk_disable_unprepare ( rngc - > clk ) ;
return ret ;
}
static int __exit imx_rngc_remove ( struct platform_device * pdev )
{
struct imx_rngc * rngc = platform_get_drvdata ( pdev ) ;
hwrng_unregister ( & rngc - > rng ) ;
clk_disable_unprepare ( rngc - > clk ) ;
return 0 ;
}
2018-01-11 22:06:39 +01:00
static int __maybe_unused imx_rngc_suspend ( struct device * dev )
2017-07-23 19:49:06 +02:00
{
struct imx_rngc * rngc = dev_get_drvdata ( dev ) ;
clk_disable_unprepare ( rngc - > clk ) ;
return 0 ;
}
2018-01-11 22:06:39 +01:00
static int __maybe_unused imx_rngc_resume ( struct device * dev )
2017-07-23 19:49:06 +02:00
{
struct imx_rngc * rngc = dev_get_drvdata ( dev ) ;
clk_prepare_enable ( rngc - > clk ) ;
return 0 ;
}
2018-01-23 02:08:56 +00:00
static SIMPLE_DEV_PM_OPS ( imx_rngc_pm_ops , imx_rngc_suspend , imx_rngc_resume ) ;
2017-07-23 19:49:06 +02:00
static const struct of_device_id imx_rngc_dt_ids [ ] = {
{ . compatible = " fsl,imx25-rngb " , . data = NULL , } ,
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( of , imx_rngc_dt_ids ) ;
static struct platform_driver imx_rngc_driver = {
. driver = {
. name = " imx_rngc " ,
. pm = & imx_rngc_pm_ops ,
. of_match_table = imx_rngc_dt_ids ,
} ,
. remove = __exit_p ( imx_rngc_remove ) ,
} ;
module_platform_driver_probe ( imx_rngc_driver , imx_rngc_probe ) ;
MODULE_AUTHOR ( " Freescale Semiconductor, Inc. " ) ;
MODULE_DESCRIPTION ( " H/W RNGC driver for i.MX " ) ;
MODULE_LICENSE ( " GPL " ) ;