2013-10-15 18:11:18 +04:00
/*
* Copyright ( c ) 2011 - 2013 , The Linux Foundation . All rights reserved .
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
*/
# include <linux/clk.h>
# include <linux/err.h>
# include <linux/hw_random.h>
# include <linux/io.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/platform_device.h>
/* Device specific register offsets */
# define PRNG_DATA_OUT 0x0000
# define PRNG_STATUS 0x0004
# define PRNG_LFSR_CFG 0x0100
# define PRNG_CONFIG 0x0104
/* Device specific register masks and config values */
# define PRNG_LFSR_CFG_MASK 0x0000ffff
# define PRNG_LFSR_CFG_CLOCKS 0x0000dddd
# define PRNG_CONFIG_HW_ENABLE BIT(1)
# define PRNG_STATUS_DATA_AVAIL BIT(0)
# define MAX_HW_FIFO_DEPTH 16
# define MAX_HW_FIFO_SIZE (MAX_HW_FIFO_DEPTH * 4)
# define WORD_SZ 4
struct msm_rng {
void __iomem * base ;
struct clk * clk ;
struct hwrng hwrng ;
} ;
# define to_msm_rng(p) container_of(p, struct msm_rng, hwrng)
static int msm_rng_enable ( struct hwrng * hwrng , int enable )
{
struct msm_rng * rng = to_msm_rng ( hwrng ) ;
u32 val ;
int ret ;
ret = clk_prepare_enable ( rng - > clk ) ;
if ( ret )
return ret ;
if ( enable ) {
/* Enable PRNG only if it is not already enabled */
val = readl_relaxed ( rng - > base + PRNG_CONFIG ) ;
if ( val & PRNG_CONFIG_HW_ENABLE )
goto already_enabled ;
val = readl_relaxed ( rng - > base + PRNG_LFSR_CFG ) ;
val & = ~ PRNG_LFSR_CFG_MASK ;
val | = PRNG_LFSR_CFG_CLOCKS ;
writel ( val , rng - > base + PRNG_LFSR_CFG ) ;
val = readl_relaxed ( rng - > base + PRNG_CONFIG ) ;
val | = PRNG_CONFIG_HW_ENABLE ;
writel ( val , rng - > base + PRNG_CONFIG ) ;
} else {
val = readl_relaxed ( rng - > base + PRNG_CONFIG ) ;
val & = ~ PRNG_CONFIG_HW_ENABLE ;
writel ( val , rng - > base + PRNG_CONFIG ) ;
}
already_enabled :
clk_disable_unprepare ( rng - > clk ) ;
return 0 ;
}
static int msm_rng_read ( struct hwrng * hwrng , void * data , size_t max , bool wait )
{
struct msm_rng * rng = to_msm_rng ( hwrng ) ;
size_t currsize = 0 ;
u32 * retdata = data ;
size_t maxsize ;
int ret ;
u32 val ;
/* calculate max size bytes to transfer back to caller */
maxsize = min_t ( size_t , MAX_HW_FIFO_SIZE , max ) ;
ret = clk_prepare_enable ( rng - > clk ) ;
if ( ret )
return ret ;
/* read random data from hardware */
do {
val = readl_relaxed ( rng - > base + PRNG_STATUS ) ;
if ( ! ( val & PRNG_STATUS_DATA_AVAIL ) )
break ;
val = readl_relaxed ( rng - > base + PRNG_DATA_OUT ) ;
if ( ! val )
break ;
* retdata + + = val ;
currsize + = WORD_SZ ;
/* make sure we stay on 32bit boundary */
if ( ( maxsize - currsize ) < WORD_SZ )
break ;
} while ( currsize < maxsize ) ;
clk_disable_unprepare ( rng - > clk ) ;
return currsize ;
}
static int msm_rng_init ( struct hwrng * hwrng )
{
return msm_rng_enable ( hwrng , 1 ) ;
}
static void msm_rng_cleanup ( struct hwrng * hwrng )
{
msm_rng_enable ( hwrng , 0 ) ;
}
static int msm_rng_probe ( struct platform_device * pdev )
{
struct resource * res ;
struct msm_rng * rng ;
int ret ;
rng = devm_kzalloc ( & pdev - > dev , sizeof ( * rng ) , GFP_KERNEL ) ;
if ( ! rng )
return - ENOMEM ;
platform_set_drvdata ( pdev , rng ) ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
rng - > base = devm_ioremap_resource ( & pdev - > dev , res ) ;
if ( IS_ERR ( rng - > base ) )
return PTR_ERR ( rng - > base ) ;
rng - > clk = devm_clk_get ( & pdev - > dev , " core " ) ;
if ( IS_ERR ( rng - > clk ) )
return PTR_ERR ( rng - > clk ) ;
rng - > hwrng . name = KBUILD_MODNAME ,
rng - > hwrng . init = msm_rng_init ,
rng - > hwrng . cleanup = msm_rng_cleanup ,
rng - > hwrng . read = msm_rng_read ,
2015-03-13 00:00:05 +03:00
ret = devm_hwrng_register ( & pdev - > dev , & rng - > hwrng ) ;
2013-10-15 18:11:18 +04:00
if ( ret ) {
dev_err ( & pdev - > dev , " failed to register hwrng \n " ) ;
return ret ;
}
return 0 ;
}
static const struct of_device_id msm_rng_of_match [ ] = {
{ . compatible = " qcom,prng " , } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , msm_rng_of_match ) ;
static struct platform_driver msm_rng_driver = {
. probe = msm_rng_probe ,
. driver = {
. name = KBUILD_MODNAME ,
. of_match_table = of_match_ptr ( msm_rng_of_match ) ,
}
} ;
module_platform_driver ( msm_rng_driver ) ;
MODULE_ALIAS ( " platform: " KBUILD_MODNAME ) ;
MODULE_AUTHOR ( " The Linux Foundation " ) ;
MODULE_DESCRIPTION ( " Qualcomm MSM random number generator driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;