2018-07-16 08:50:24 +03:00
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2017-18 Linaro Limited
//
// Based on msm-rng.c and downstream driver
# include <crypto/internal/rng.h>
2018-07-16 08:50:27 +03:00
# include <linux/acpi.h>
2018-07-16 08:50:24 +03:00
# include <linux/clk.h>
# include <linux/crypto.h>
2020-08-19 14:58:20 +03:00
# include <linux/io.h>
2022-03-11 02:24:59 +03:00
# include <linux/iopoll.h>
2022-07-29 12:35:31 +03:00
# include <linux/kernel.h>
2018-07-16 08:50:24 +03:00
# 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 WORD_SZ 4
struct qcom_rng {
struct mutex lock ;
void __iomem * base ;
struct clk * clk ;
2018-07-16 08:50:26 +03:00
unsigned int skip_init ;
2018-07-16 08:50:24 +03:00
} ;
struct qcom_rng_ctx {
struct qcom_rng * rng ;
} ;
static struct qcom_rng * qcom_rng_dev ;
static int qcom_rng_read ( struct qcom_rng * rng , u8 * data , unsigned int max )
{
unsigned int currsize = 0 ;
u32 val ;
2022-03-11 02:24:59 +03:00
int ret ;
2018-07-16 08:50:24 +03:00
/* read random data from hardware */
do {
2022-03-11 02:24:59 +03:00
ret = readl_poll_timeout ( rng - > base + PRNG_STATUS , val ,
val & PRNG_STATUS_DATA_AVAIL ,
200 , 10000 ) ;
if ( ret )
return ret ;
2018-07-16 08:50:24 +03:00
val = readl_relaxed ( rng - > base + PRNG_DATA_OUT ) ;
if ( ! val )
2022-03-11 02:24:59 +03:00
return - EINVAL ;
2018-07-16 08:50:24 +03:00
if ( ( max - currsize ) > = WORD_SZ ) {
memcpy ( data , & val , WORD_SZ ) ;
data + = WORD_SZ ;
currsize + = WORD_SZ ;
} else {
/* copy only remaining bytes */
memcpy ( data , & val , max - currsize ) ;
2022-05-03 14:50:10 +03:00
break ;
2018-07-16 08:50:24 +03:00
}
} while ( currsize < max ) ;
2022-03-11 02:24:59 +03:00
return 0 ;
2018-07-16 08:50:24 +03:00
}
static int qcom_rng_generate ( struct crypto_rng * tfm ,
const u8 * src , unsigned int slen ,
u8 * dstn , unsigned int dlen )
{
struct qcom_rng_ctx * ctx = crypto_rng_ctx ( tfm ) ;
struct qcom_rng * rng = ctx - > rng ;
int ret ;
ret = clk_prepare_enable ( rng - > clk ) ;
if ( ret )
return ret ;
mutex_lock ( & rng - > lock ) ;
ret = qcom_rng_read ( rng , dstn , dlen ) ;
mutex_unlock ( & rng - > lock ) ;
clk_disable_unprepare ( rng - > clk ) ;
2022-03-11 02:24:59 +03:00
return ret ;
2018-07-16 08:50:24 +03:00
}
static int qcom_rng_seed ( struct crypto_rng * tfm , const u8 * seed ,
unsigned int slen )
{
return 0 ;
}
static int qcom_rng_enable ( struct qcom_rng * rng )
{
u32 val ;
int ret ;
ret = clk_prepare_enable ( rng - > clk ) ;
if ( ret )
return ret ;
/* 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 ) ;
already_enabled :
clk_disable_unprepare ( rng - > clk ) ;
return 0 ;
}
static int qcom_rng_init ( struct crypto_tfm * tfm )
{
struct qcom_rng_ctx * ctx = crypto_tfm_ctx ( tfm ) ;
ctx - > rng = qcom_rng_dev ;
2018-07-16 08:50:26 +03:00
if ( ! ctx - > rng - > skip_init )
return qcom_rng_enable ( ctx - > rng ) ;
return 0 ;
2018-07-16 08:50:24 +03:00
}
static struct rng_alg qcom_rng_alg = {
. generate = qcom_rng_generate ,
. seed = qcom_rng_seed ,
. seedsize = 0 ,
. base = {
. cra_name = " stdrng " ,
. cra_driver_name = " qcom-rng " ,
. cra_flags = CRYPTO_ALG_TYPE_RNG ,
. cra_priority = 300 ,
. cra_ctxsize = sizeof ( struct qcom_rng_ctx ) ,
. cra_module = THIS_MODULE ,
. cra_init = qcom_rng_init ,
}
} ;
static int qcom_rng_probe ( struct platform_device * pdev )
{
struct qcom_rng * rng ;
int ret ;
rng = devm_kzalloc ( & pdev - > dev , sizeof ( * rng ) , GFP_KERNEL ) ;
if ( ! rng )
return - ENOMEM ;
platform_set_drvdata ( pdev , rng ) ;
mutex_init ( & rng - > lock ) ;
2019-08-02 16:28:09 +03:00
rng - > base = devm_platform_ioremap_resource ( pdev , 0 ) ;
2018-07-16 08:50:24 +03:00
if ( IS_ERR ( rng - > base ) )
return PTR_ERR ( rng - > base ) ;
2018-07-16 08:50:27 +03:00
/* ACPI systems have clk already on, so skip clk_get */
if ( ! has_acpi_companion ( & pdev - > dev ) ) {
rng - > clk = devm_clk_get ( & pdev - > dev , " core " ) ;
if ( IS_ERR ( rng - > clk ) )
return PTR_ERR ( rng - > clk ) ;
}
2018-07-16 08:50:24 +03:00
2018-07-16 08:50:26 +03:00
rng - > skip_init = ( unsigned long ) device_get_match_data ( & pdev - > dev ) ;
2018-07-16 08:50:24 +03:00
qcom_rng_dev = rng ;
ret = crypto_register_rng ( & qcom_rng_alg ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Register crypto rng failed: %d \n " , ret ) ;
qcom_rng_dev = NULL ;
}
return ret ;
}
static int qcom_rng_remove ( struct platform_device * pdev )
{
crypto_unregister_rng ( & qcom_rng_alg ) ;
qcom_rng_dev = NULL ;
return 0 ;
}
2022-07-29 12:35:31 +03:00
static const struct acpi_device_id __maybe_unused qcom_rng_acpi_match [ ] = {
2018-07-16 08:50:27 +03:00
{ . id = " QCOM8160 " , . driver_data = 1 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( acpi , qcom_rng_acpi_match ) ;
2022-07-29 12:35:31 +03:00
static const struct of_device_id __maybe_unused qcom_rng_of_match [ ] = {
2018-07-16 08:50:26 +03:00
{ . compatible = " qcom,prng " , . data = ( void * ) 0 } ,
{ . compatible = " qcom,prng-ee " , . data = ( void * ) 1 } ,
2018-07-16 08:50:24 +03:00
{ }
} ;
MODULE_DEVICE_TABLE ( of , qcom_rng_of_match ) ;
static struct platform_driver qcom_rng_driver = {
. probe = qcom_rng_probe ,
. remove = qcom_rng_remove ,
. driver = {
. name = KBUILD_MODNAME ,
. of_match_table = of_match_ptr ( qcom_rng_of_match ) ,
2018-07-16 08:50:27 +03:00
. acpi_match_table = ACPI_PTR ( qcom_rng_acpi_match ) ,
2018-07-16 08:50:24 +03:00
}
} ;
module_platform_driver ( qcom_rng_driver ) ;
MODULE_ALIAS ( " platform: " KBUILD_MODNAME ) ;
MODULE_DESCRIPTION ( " Qualcomm random number generator driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;