2019-04-13 13:32:55 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* STM32 Factory - programmed memory read access driver
*
* Copyright ( C ) 2017 , STMicroelectronics - All Rights Reserved
* Author : Fabrice Gasnier < fabrice . gasnier @ st . com > for STMicroelectronics .
*/
2019-04-13 13:32:56 +03:00
# include <linux/arm-smccc.h>
2019-04-13 13:32:55 +03:00
# include <linux/io.h>
# include <linux/module.h>
# include <linux/nvmem-provider.h>
# include <linux/of_device.h>
2019-04-13 13:32:56 +03:00
/* BSEC secure service access from non-secure */
# define STM32_SMC_BSEC 0x82001003
# define STM32_SMC_READ_SHADOW 0x01
# define STM32_SMC_PROG_OTP 0x02
# define STM32_SMC_WRITE_SHADOW 0x03
# define STM32_SMC_READ_OTP 0x04
/* shadow registers offest */
# define STM32MP15_BSEC_DATA0 0x200
/* 32 (x 32-bits) lower shadow registers */
# define STM32MP15_BSEC_NUM_LOWER 32
struct stm32_romem_cfg {
int size ;
} ;
2019-04-13 13:32:55 +03:00
struct stm32_romem_priv {
void __iomem * base ;
struct nvmem_config cfg ;
} ;
static int stm32_romem_read ( void * context , unsigned int offset , void * buf ,
size_t bytes )
{
struct stm32_romem_priv * priv = context ;
u8 * buf8 = buf ;
int i ;
for ( i = offset ; i < offset + bytes ; i + + )
* buf8 + + = readb_relaxed ( priv - > base + i ) ;
return 0 ;
}
2019-04-13 13:32:56 +03:00
static int stm32_bsec_smc ( u8 op , u32 otp , u32 data , u32 * result )
{
# if IS_ENABLED(CONFIG_HAVE_ARM_SMCCC)
struct arm_smccc_res res ;
arm_smccc_smc ( STM32_SMC_BSEC , op , otp , data , 0 , 0 , 0 , 0 , & res ) ;
if ( res . a0 )
return - EIO ;
if ( result )
* result = ( u32 ) res . a1 ;
return 0 ;
# else
return - ENXIO ;
# endif
}
static int stm32_bsec_read ( void * context , unsigned int offset , void * buf ,
size_t bytes )
{
struct stm32_romem_priv * priv = context ;
struct device * dev = priv - > cfg . dev ;
u32 roffset , rbytes , val ;
u8 * buf8 = buf , * val8 = ( u8 * ) & val ;
int i , j = 0 , ret , skip_bytes , size ;
/* Round unaligned access to 32-bits */
roffset = rounddown ( offset , 4 ) ;
skip_bytes = offset & 0x3 ;
rbytes = roundup ( bytes + skip_bytes , 4 ) ;
if ( roffset + rbytes > priv - > cfg . size )
return - EINVAL ;
for ( i = roffset ; ( i < roffset + rbytes ) ; i + = 4 ) {
u32 otp = i > > 2 ;
if ( otp < STM32MP15_BSEC_NUM_LOWER ) {
/* read lower data from shadow registers */
val = readl_relaxed (
priv - > base + STM32MP15_BSEC_DATA0 + i ) ;
} else {
ret = stm32_bsec_smc ( STM32_SMC_READ_SHADOW , otp , 0 ,
& val ) ;
if ( ret ) {
dev_err ( dev , " Can't read data%d (%d) \n " , otp ,
ret ) ;
return ret ;
}
}
/* skip first bytes in case of unaligned read */
if ( skip_bytes )
size = min ( bytes , ( size_t ) ( 4 - skip_bytes ) ) ;
else
size = min ( bytes , ( size_t ) 4 ) ;
memcpy ( & buf8 [ j ] , & val8 [ skip_bytes ] , size ) ;
bytes - = size ;
j + = size ;
skip_bytes = 0 ;
}
return 0 ;
}
static int stm32_bsec_write ( void * context , unsigned int offset , void * buf ,
size_t bytes )
{
struct stm32_romem_priv * priv = context ;
struct device * dev = priv - > cfg . dev ;
u32 * buf32 = buf ;
int ret , i ;
/* Allow only writing complete 32-bits aligned words */
if ( ( bytes % 4 ) | | ( offset % 4 ) )
return - EINVAL ;
for ( i = offset ; i < offset + bytes ; i + = 4 ) {
ret = stm32_bsec_smc ( STM32_SMC_PROG_OTP , i > > 2 , * buf32 + + ,
NULL ) ;
if ( ret ) {
dev_err ( dev , " Can't write data%d (%d) \n " , i > > 2 , ret ) ;
return ret ;
}
}
return 0 ;
}
2019-04-13 13:32:55 +03:00
static int stm32_romem_probe ( struct platform_device * pdev )
{
2019-04-13 13:32:56 +03:00
const struct stm32_romem_cfg * cfg ;
2019-04-13 13:32:55 +03:00
struct device * dev = & pdev - > dev ;
struct stm32_romem_priv * priv ;
struct resource * res ;
priv = devm_kzalloc ( dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
priv - > base = devm_ioremap_resource ( dev , res ) ;
if ( IS_ERR ( priv - > base ) )
return PTR_ERR ( priv - > base ) ;
priv - > cfg . name = " stm32-romem " ;
priv - > cfg . word_size = 1 ;
priv - > cfg . stride = 1 ;
priv - > cfg . dev = dev ;
priv - > cfg . priv = priv ;
priv - > cfg . owner = THIS_MODULE ;
2019-04-13 13:32:56 +03:00
cfg = ( const struct stm32_romem_cfg * )
of_match_device ( dev - > driver - > of_match_table , dev ) - > data ;
if ( ! cfg ) {
priv - > cfg . read_only = true ;
priv - > cfg . size = resource_size ( res ) ;
priv - > cfg . reg_read = stm32_romem_read ;
} else {
priv - > cfg . size = cfg - > size ;
priv - > cfg . reg_read = stm32_bsec_read ;
priv - > cfg . reg_write = stm32_bsec_write ;
}
2019-04-13 13:32:55 +03:00
return PTR_ERR_OR_ZERO ( devm_nvmem_register ( dev , & priv - > cfg ) ) ;
}
2019-04-13 13:32:56 +03:00
static const struct stm32_romem_cfg stm32mp15_bsec_cfg = {
. size = 384 , /* 96 x 32-bits data words */
} ;
2019-04-13 13:32:55 +03:00
static const struct of_device_id stm32_romem_of_match [ ] = {
2019-04-13 13:32:56 +03:00
{ . compatible = " st,stm32f4-otp " , } , {
. compatible = " st,stm32mp15-bsec " ,
. data = ( void * ) & stm32mp15_bsec_cfg ,
} , {
} ,
2019-04-13 13:32:55 +03:00
} ;
MODULE_DEVICE_TABLE ( of , stm32_romem_of_match ) ;
static struct platform_driver stm32_romem_driver = {
. probe = stm32_romem_probe ,
. driver = {
. name = " stm32-romem " ,
. of_match_table = of_match_ptr ( stm32_romem_of_match ) ,
} ,
} ;
module_platform_driver ( stm32_romem_driver ) ;
MODULE_AUTHOR ( " Fabrice Gasnier <fabrice.gasnier@st.com> " ) ;
MODULE_DESCRIPTION ( " STMicroelectronics STM32 RO-MEM " ) ;
MODULE_ALIAS ( " platform:nvmem-stm32-romem " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;