2018-07-16 11:20:24 +05:30
// 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 11:20:27 +05:30
# include <linux/acpi.h>
2018-07-16 11:20:24 +05:30
# include <linux/clk.h>
# include <linux/crypto.h>
2023-10-03 09:10:21 +02:00
# include <linux/hw_random.h>
2020-08-19 21:58:20 +10:00
# include <linux/io.h>
2022-03-10 18:24:59 -05:00
# include <linux/iopoll.h>
2022-07-29 17:35:31 +08:00
# include <linux/kernel.h>
2018-07-16 11:20:24 +05:30
# 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
2023-10-03 09:10:21 +02:00
# define QCOM_TRNG_QUALITY 1024
2018-07-16 11:20:24 +05:30
struct qcom_rng {
struct mutex lock ;
void __iomem * base ;
struct clk * clk ;
2023-10-03 09:10:21 +02:00
struct hwrng hwrng ;
struct qcom_rng_of_data * of_data ;
2018-07-16 11:20:24 +05:30
} ;
struct qcom_rng_ctx {
struct qcom_rng * rng ;
} ;
2023-10-03 09:10:21 +02:00
struct qcom_rng_of_data {
bool skip_init ;
bool hwrng_support ;
} ;
2018-07-16 11:20:24 +05:30
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-10 18:24:59 -05:00
int ret ;
2018-07-16 11:20:24 +05:30
/* read random data from hardware */
do {
2022-03-10 18:24:59 -05:00
ret = readl_poll_timeout ( rng - > base + PRNG_STATUS , val ,
val & PRNG_STATUS_DATA_AVAIL ,
200 , 10000 ) ;
if ( ret )
return ret ;
2018-07-16 11:20:24 +05:30
val = readl_relaxed ( rng - > base + PRNG_DATA_OUT ) ;
if ( ! val )
2022-03-10 18:24:59 -05:00
return - EINVAL ;
2018-07-16 11:20:24 +05:30
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 ) ;
2023-10-03 09:10:21 +02:00
currsize = max ;
2018-07-16 11:20:24 +05:30
}
} while ( currsize < max ) ;
2023-10-03 09:10:21 +02:00
return currsize ;
2018-07-16 11:20:24 +05:30
}
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 ) ;
2023-10-03 09:10:21 +02:00
if ( ret > = 0 )
ret = 0 ;
2022-03-10 18:24:59 -05:00
return ret ;
2018-07-16 11:20:24 +05:30
}
static int qcom_rng_seed ( struct crypto_rng * tfm , const u8 * seed ,
unsigned int slen )
{
return 0 ;
}
2023-10-03 09:10:21 +02:00
static int qcom_hwrng_read ( struct hwrng * hwrng , void * data , size_t max , bool wait )
{
struct qcom_rng * qrng = container_of ( hwrng , struct qcom_rng , hwrng ) ;
return qcom_rng_read ( qrng , data , max ) ;
}
2018-07-16 11:20:24 +05:30
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 ;
2023-10-03 09:10:21 +02:00
if ( ! ctx - > rng - > of_data - > skip_init )
2018-07-16 11:20:26 +05:30
return qcom_rng_enable ( ctx - > rng ) ;
return 0 ;
2018-07-16 11:20:24 +05:30
}
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 21:28:09 +08:00
rng - > base = devm_platform_ioremap_resource ( pdev , 0 ) ;
2018-07-16 11:20:24 +05:30
if ( IS_ERR ( rng - > base ) )
return PTR_ERR ( rng - > base ) ;
2023-08-11 22:50:57 +02:00
rng - > clk = devm_clk_get_optional ( & pdev - > dev , " core " ) ;
if ( IS_ERR ( rng - > clk ) )
return PTR_ERR ( rng - > clk ) ;
2018-07-16 11:20:24 +05:30
2023-10-03 09:10:21 +02:00
rng - > of_data = ( struct qcom_rng_of_data * ) of_device_get_match_data ( & pdev - > dev ) ;
2018-07-16 11:20:26 +05:30
2018-07-16 11:20:24 +05:30
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 ;
2023-10-03 09:10:21 +02:00
return ret ;
}
if ( rng - > of_data - > hwrng_support ) {
rng - > hwrng . name = " qcom_hwrng " ;
rng - > hwrng . read = qcom_hwrng_read ;
rng - > hwrng . quality = QCOM_TRNG_QUALITY ;
ret = devm_hwrng_register ( & pdev - > dev , & rng - > hwrng ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Register hwrng failed: %d \n " , ret ) ;
qcom_rng_dev = NULL ;
goto fail ;
}
2018-07-16 11:20:24 +05:30
}
2023-10-03 09:10:21 +02:00
return ret ;
fail :
crypto_unregister_rng ( & qcom_rng_alg ) ;
2018-07-16 11:20:24 +05:30
return ret ;
}
2023-10-20 09:55:55 +02:00
static void qcom_rng_remove ( struct platform_device * pdev )
2018-07-16 11:20:24 +05:30
{
crypto_unregister_rng ( & qcom_rng_alg ) ;
qcom_rng_dev = NULL ;
}
2023-10-03 09:10:21 +02:00
static struct qcom_rng_of_data qcom_prng_of_data = {
. skip_init = false ,
. hwrng_support = false ,
} ;
static struct qcom_rng_of_data qcom_prng_ee_of_data = {
. skip_init = true ,
. hwrng_support = false ,
} ;
static struct qcom_rng_of_data qcom_trng_of_data = {
. skip_init = true ,
. hwrng_support = true ,
} ;
2022-07-29 17:35:31 +08:00
static const struct acpi_device_id __maybe_unused qcom_rng_acpi_match [ ] = {
2018-07-16 11:20:27 +05:30
{ . id = " QCOM8160 " , . driver_data = 1 } ,
{ }
} ;
MODULE_DEVICE_TABLE ( acpi , qcom_rng_acpi_match ) ;
2022-07-29 17:35:31 +08:00
static const struct of_device_id __maybe_unused qcom_rng_of_match [ ] = {
2023-10-03 09:10:21 +02:00
{ . compatible = " qcom,prng " , . data = & qcom_prng_of_data } ,
{ . compatible = " qcom,prng-ee " , . data = & qcom_prng_ee_of_data } ,
{ . compatible = " qcom,trng " , . data = & qcom_trng_of_data } ,
2018-07-16 11:20:24 +05:30
{ }
} ;
MODULE_DEVICE_TABLE ( of , qcom_rng_of_match ) ;
static struct platform_driver qcom_rng_driver = {
. probe = qcom_rng_probe ,
2023-10-20 09:55:55 +02:00
. remove_new = qcom_rng_remove ,
2018-07-16 11:20:24 +05:30
. driver = {
. name = KBUILD_MODNAME ,
. of_match_table = of_match_ptr ( qcom_rng_of_match ) ,
2018-07-16 11:20:27 +05:30
. acpi_match_table = ACPI_PTR ( qcom_rng_acpi_match ) ,
2018-07-16 11:20:24 +05:30
}
} ;
module_platform_driver ( qcom_rng_driver ) ;
MODULE_ALIAS ( " platform: " KBUILD_MODNAME ) ;
MODULE_DESCRIPTION ( " Qualcomm random number generator driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;