2019-06-14 17:32:19 +03:00
// SPDX-License-Identifier: GPL-2.0+
/*
* i . MX8 OCOTP fusebox driver
*
* Copyright 2019 NXP
*
* Peng Fan < peng . fan @ nxp . com >
*/
2019-10-29 14:42:34 +03:00
# include <linux/arm-smccc.h>
2019-06-14 17:32:19 +03:00
# include <linux/firmware/imx/sci.h>
# include <linux/module.h>
# include <linux/nvmem-provider.h>
# include <linux/of_device.h>
# include <linux/platform_device.h>
# include <linux/slab.h>
2020-01-09 13:40:14 +03:00
# define IMX_SIP_OTP_WRITE 0xc200000B
2019-10-29 14:42:34 +03:00
2019-06-14 17:32:19 +03:00
enum ocotp_devtype {
IMX8QXP ,
2019-08-18 12:33:45 +03:00
IMX8QM ,
2019-06-14 17:32:19 +03:00
} ;
2019-10-29 14:42:33 +03:00
# define ECC_REGION BIT(0)
# define HOLE_REGION BIT(1)
struct ocotp_region {
u32 start ;
u32 end ;
u32 flag ;
} ;
2019-06-14 17:32:19 +03:00
struct ocotp_devtype_data {
int devtype ;
int nregs ;
2019-10-29 14:42:33 +03:00
u32 num_region ;
struct ocotp_region region [ ] ;
2019-06-14 17:32:19 +03:00
} ;
struct ocotp_priv {
struct device * dev ;
const struct ocotp_devtype_data * data ;
struct imx_sc_ipc * nvmem_ipc ;
} ;
struct imx_sc_msg_misc_fuse_read {
struct imx_sc_rpc_msg hdr ;
u32 word ;
} __packed ;
2019-10-29 14:42:34 +03:00
static DEFINE_MUTEX ( scu_ocotp_mutex ) ;
2019-06-14 17:32:19 +03:00
static struct ocotp_devtype_data imx8qxp_data = {
. devtype = IMX8QXP ,
. nregs = 800 ,
2019-10-29 14:42:33 +03:00
. num_region = 3 ,
. region = {
{ 0x10 , 0x10f , ECC_REGION } ,
{ 0x110 , 0x21F , HOLE_REGION } ,
{ 0x220 , 0x31F , ECC_REGION } ,
} ,
2019-06-14 17:32:19 +03:00
} ;
2019-08-18 12:33:45 +03:00
static struct ocotp_devtype_data imx8qm_data = {
. devtype = IMX8QM ,
. nregs = 800 ,
2019-10-29 14:42:33 +03:00
. num_region = 2 ,
. region = {
{ 0x10 , 0x10f , ECC_REGION } ,
{ 0x1a0 , 0x1ff , ECC_REGION } ,
} ,
2019-08-18 12:33:45 +03:00
} ;
2019-10-29 14:42:33 +03:00
static bool in_hole ( void * context , u32 index )
{
struct ocotp_priv * priv = context ;
const struct ocotp_devtype_data * data = priv - > data ;
int i ;
for ( i = 0 ; i < data - > num_region ; i + + ) {
if ( data - > region [ i ] . flag & HOLE_REGION ) {
if ( ( index > = data - > region [ i ] . start ) & &
( index < = data - > region [ i ] . end ) )
return true ;
}
}
return false ;
}
2019-10-29 14:42:34 +03:00
static bool in_ecc ( void * context , u32 index )
{
struct ocotp_priv * priv = context ;
const struct ocotp_devtype_data * data = priv - > data ;
int i ;
for ( i = 0 ; i < data - > num_region ; i + + ) {
if ( data - > region [ i ] . flag & ECC_REGION ) {
if ( ( index > = data - > region [ i ] . start ) & &
( index < = data - > region [ i ] . end ) )
return true ;
}
}
return false ;
}
2019-06-14 17:32:19 +03:00
static int imx_sc_misc_otp_fuse_read ( struct imx_sc_ipc * ipc , u32 word ,
u32 * val )
{
struct imx_sc_msg_misc_fuse_read msg ;
struct imx_sc_rpc_msg * hdr = & msg . hdr ;
int ret ;
hdr - > ver = IMX_SC_RPC_VERSION ;
hdr - > svc = IMX_SC_RPC_SVC_MISC ;
hdr - > func = IMX_SC_MISC_FUNC_OTP_FUSE_READ ;
hdr - > size = 2 ;
msg . word = word ;
ret = imx_scu_call_rpc ( ipc , & msg , true ) ;
if ( ret )
return ret ;
* val = msg . word ;
return 0 ;
}
static int imx_scu_ocotp_read ( void * context , unsigned int offset ,
void * val , size_t bytes )
{
struct ocotp_priv * priv = context ;
u32 count , index , num_bytes ;
u32 * buf ;
void * p ;
int i , ret ;
2020-01-09 13:40:15 +03:00
index = offset ;
num_bytes = round_up ( bytes , 4 ) ;
2019-06-14 17:32:19 +03:00
count = num_bytes > > 2 ;
if ( count > ( priv - > data - > nregs - index ) )
count = priv - > data - > nregs - index ;
p = kzalloc ( num_bytes , GFP_KERNEL ) ;
if ( ! p )
return - ENOMEM ;
2019-10-29 14:42:34 +03:00
mutex_lock ( & scu_ocotp_mutex ) ;
2019-06-14 17:32:19 +03:00
buf = p ;
for ( i = index ; i < ( index + count ) ; i + + ) {
2019-10-29 14:42:33 +03:00
if ( in_hole ( context , i ) ) {
* buf + + = 0 ;
continue ;
2019-06-14 17:32:19 +03:00
}
ret = imx_sc_misc_otp_fuse_read ( priv - > nvmem_ipc , i , buf ) ;
if ( ret ) {
2019-10-29 14:42:34 +03:00
mutex_unlock ( & scu_ocotp_mutex ) ;
2019-06-14 17:32:19 +03:00
kfree ( p ) ;
return ret ;
}
buf + + ;
}
2020-01-09 13:40:15 +03:00
memcpy ( val , ( u8 * ) p , bytes ) ;
2019-06-14 17:32:19 +03:00
2019-10-29 14:42:34 +03:00
mutex_unlock ( & scu_ocotp_mutex ) ;
2019-06-14 17:32:19 +03:00
kfree ( p ) ;
return 0 ;
}
2019-10-29 14:42:34 +03:00
static int imx_scu_ocotp_write ( void * context , unsigned int offset ,
void * val , size_t bytes )
{
struct ocotp_priv * priv = context ;
struct arm_smccc_res res ;
u32 * buf = val ;
u32 tmp ;
u32 index ;
int ret ;
/* allow only writing one complete OTP word at a time */
2020-01-09 13:40:15 +03:00
if ( bytes ! = 4 )
2019-10-29 14:42:34 +03:00
return - EINVAL ;
2020-01-09 13:40:15 +03:00
index = offset ;
2019-10-29 14:42:34 +03:00
if ( in_hole ( context , index ) )
return - EINVAL ;
if ( in_ecc ( context , index ) ) {
pr_warn ( " ECC region, only program once \n " ) ;
mutex_lock ( & scu_ocotp_mutex ) ;
ret = imx_sc_misc_otp_fuse_read ( priv - > nvmem_ipc , index , & tmp ) ;
mutex_unlock ( & scu_ocotp_mutex ) ;
if ( ret )
return ret ;
if ( tmp ) {
pr_warn ( " ECC region, already has value: %x \n " , tmp ) ;
return - EIO ;
}
}
mutex_lock ( & scu_ocotp_mutex ) ;
2020-01-09 13:40:14 +03:00
arm_smccc_smc ( IMX_SIP_OTP_WRITE , index , * buf , 0 , 0 , 0 , 0 , 0 , & res ) ;
2019-10-29 14:42:34 +03:00
mutex_unlock ( & scu_ocotp_mutex ) ;
return res . a0 ;
}
2019-06-14 17:32:19 +03:00
static struct nvmem_config imx_scu_ocotp_nvmem_config = {
. name = " imx-scu-ocotp " ,
2019-10-29 14:42:34 +03:00
. read_only = false ,
2019-06-14 17:32:19 +03:00
. word_size = 4 ,
. stride = 1 ,
. owner = THIS_MODULE ,
. reg_read = imx_scu_ocotp_read ,
2019-10-29 14:42:34 +03:00
. reg_write = imx_scu_ocotp_write ,
2019-06-14 17:32:19 +03:00
} ;
static const struct of_device_id imx_scu_ocotp_dt_ids [ ] = {
{ . compatible = " fsl,imx8qxp-scu-ocotp " , ( void * ) & imx8qxp_data } ,
2019-08-18 12:33:45 +03:00
{ . compatible = " fsl,imx8qm-scu-ocotp " , ( void * ) & imx8qm_data } ,
2019-06-14 17:32:19 +03:00
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , imx_scu_ocotp_dt_ids ) ;
static int imx_scu_ocotp_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct ocotp_priv * priv ;
struct nvmem_device * nvmem ;
int ret ;
priv = devm_kzalloc ( dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
ret = imx_scu_get_handle ( & priv - > nvmem_ipc ) ;
if ( ret )
return ret ;
priv - > data = of_device_get_match_data ( dev ) ;
priv - > dev = dev ;
imx_scu_ocotp_nvmem_config . size = 4 * priv - > data - > nregs ;
imx_scu_ocotp_nvmem_config . dev = dev ;
imx_scu_ocotp_nvmem_config . priv = priv ;
nvmem = devm_nvmem_register ( dev , & imx_scu_ocotp_nvmem_config ) ;
return PTR_ERR_OR_ZERO ( nvmem ) ;
}
static struct platform_driver imx_scu_ocotp_driver = {
. probe = imx_scu_ocotp_probe ,
. driver = {
. name = " imx_scu_ocotp " ,
. of_match_table = imx_scu_ocotp_dt_ids ,
} ,
} ;
module_platform_driver ( imx_scu_ocotp_driver ) ;
MODULE_AUTHOR ( " Peng Fan <peng.fan@nxp.com> " ) ;
MODULE_DESCRIPTION ( " i.MX8 SCU OCOTP fuse box driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;