2019-10-29 11:42:40 +00:00
// SPDX-License-Identifier: GPL-2.0-only
/*
* Rockchip OTP Driver
*
* Copyright ( c ) 2018 Rockchip Electronics Co . Ltd .
* Author : Finley Xiao < finley . xiao @ rock - chips . com >
*/
# include <linux/clk.h>
# include <linux/delay.h>
# include <linux/device.h>
# include <linux/io.h>
# include <linux/iopoll.h>
# include <linux/module.h>
# include <linux/nvmem-provider.h>
# include <linux/reset.h>
# include <linux/slab.h>
# include <linux/of.h>
# include <linux/of_platform.h>
# include <linux/platform_device.h>
/* OTP Register Offsets */
# define OTPC_SBPI_CTRL 0x0020
# define OTPC_SBPI_CMD_VALID_PRE 0x0024
# define OTPC_SBPI_CS_VALID_PRE 0x0028
# define OTPC_SBPI_STATUS 0x002C
# define OTPC_USER_CTRL 0x0100
# define OTPC_USER_ADDR 0x0104
# define OTPC_USER_ENABLE 0x0108
# define OTPC_USER_Q 0x0124
# define OTPC_INT_STATUS 0x0304
# define OTPC_SBPI_CMD0_OFFSET 0x1000
# define OTPC_SBPI_CMD1_OFFSET 0x1004
/* OTP Register bits and masks */
# define OTPC_USER_ADDR_MASK GENMASK(31, 16)
# define OTPC_USE_USER BIT(0)
# define OTPC_USE_USER_MASK GENMASK(16, 16)
# define OTPC_USER_FSM_ENABLE BIT(0)
# define OTPC_USER_FSM_ENABLE_MASK GENMASK(16, 16)
# define OTPC_SBPI_DONE BIT(1)
# define OTPC_USER_DONE BIT(2)
# define SBPI_DAP_ADDR 0x02
# define SBPI_DAP_ADDR_SHIFT 8
# define SBPI_DAP_ADDR_MASK GENMASK(31, 24)
# define SBPI_CMD_VALID_MASK GENMASK(31, 16)
# define SBPI_DAP_CMD_WRF 0xC0
# define SBPI_DAP_REG_ECC 0x3A
# define SBPI_ECC_ENABLE 0x00
# define SBPI_ECC_DISABLE 0x09
# define SBPI_ENABLE BIT(0)
# define SBPI_ENABLE_MASK GENMASK(16, 16)
# define OTPC_TIMEOUT 10000
2023-06-11 15:03:13 +01:00
struct rockchip_data {
int size ;
const char * const * clks ;
int num_clks ;
nvmem_reg_read_t reg_read ;
} ;
2019-10-29 11:42:40 +00:00
struct rockchip_otp {
struct device * dev ;
void __iomem * base ;
2023-06-11 15:03:13 +01:00
struct clk_bulk_data * clks ;
2019-10-29 11:42:40 +00:00
struct reset_control * rst ;
2023-06-11 15:03:13 +01:00
const struct rockchip_data * data ;
2019-10-29 11:42:40 +00:00
} ;
static int rockchip_otp_reset ( struct rockchip_otp * otp )
{
int ret ;
ret = reset_control_assert ( otp - > rst ) ;
if ( ret ) {
dev_err ( otp - > dev , " failed to assert otp phy %d \n " , ret ) ;
return ret ;
}
udelay ( 2 ) ;
ret = reset_control_deassert ( otp - > rst ) ;
if ( ret ) {
dev_err ( otp - > dev , " failed to deassert otp phy %d \n " , ret ) ;
return ret ;
}
return 0 ;
}
2023-06-11 15:03:14 +01:00
static int rockchip_otp_wait_status ( struct rockchip_otp * otp ,
unsigned int reg , u32 flag )
2019-10-29 11:42:40 +00:00
{
u32 status = 0 ;
int ret ;
2023-06-11 15:03:14 +01:00
ret = readl_poll_timeout_atomic ( otp - > base + reg , status ,
2019-10-29 11:42:40 +00:00
( status & flag ) , 1 , OTPC_TIMEOUT ) ;
if ( ret )
return ret ;
/* clean int status */
2023-06-11 15:03:14 +01:00
writel ( flag , otp - > base + reg ) ;
2019-10-29 11:42:40 +00:00
return 0 ;
}
static int rockchip_otp_ecc_enable ( struct rockchip_otp * otp , bool enable )
{
int ret = 0 ;
writel ( SBPI_DAP_ADDR_MASK | ( SBPI_DAP_ADDR < < SBPI_DAP_ADDR_SHIFT ) ,
otp - > base + OTPC_SBPI_CTRL ) ;
writel ( SBPI_CMD_VALID_MASK | 0x1 , otp - > base + OTPC_SBPI_CMD_VALID_PRE ) ;
writel ( SBPI_DAP_CMD_WRF | SBPI_DAP_REG_ECC ,
otp - > base + OTPC_SBPI_CMD0_OFFSET ) ;
if ( enable )
writel ( SBPI_ECC_ENABLE , otp - > base + OTPC_SBPI_CMD1_OFFSET ) ;
else
writel ( SBPI_ECC_DISABLE , otp - > base + OTPC_SBPI_CMD1_OFFSET ) ;
writel ( SBPI_ENABLE_MASK | SBPI_ENABLE , otp - > base + OTPC_SBPI_CTRL ) ;
2023-06-11 15:03:14 +01:00
ret = rockchip_otp_wait_status ( otp , OTPC_INT_STATUS , OTPC_SBPI_DONE ) ;
2019-10-29 11:42:40 +00:00
if ( ret < 0 )
dev_err ( otp - > dev , " timeout during ecc_enable \n " ) ;
return ret ;
}
2023-06-11 15:03:13 +01:00
static int px30_otp_read ( void * context , unsigned int offset ,
void * val , size_t bytes )
2019-10-29 11:42:40 +00:00
{
struct rockchip_otp * otp = context ;
u8 * buf = val ;
2023-06-11 15:03:13 +01:00
int ret ;
2019-10-29 11:42:40 +00:00
ret = rockchip_otp_reset ( otp ) ;
if ( ret ) {
dev_err ( otp - > dev , " failed to reset otp phy \n " ) ;
2023-06-11 15:03:13 +01:00
return ret ;
2019-10-29 11:42:40 +00:00
}
ret = rockchip_otp_ecc_enable ( otp , false ) ;
if ( ret < 0 ) {
dev_err ( otp - > dev , " rockchip_otp_ecc_enable err \n " ) ;
2023-06-11 15:03:13 +01:00
return ret ;
2019-10-29 11:42:40 +00:00
}
writel ( OTPC_USE_USER | OTPC_USE_USER_MASK , otp - > base + OTPC_USER_CTRL ) ;
udelay ( 5 ) ;
while ( bytes - - ) {
writel ( offset + + | OTPC_USER_ADDR_MASK ,
otp - > base + OTPC_USER_ADDR ) ;
writel ( OTPC_USER_FSM_ENABLE | OTPC_USER_FSM_ENABLE_MASK ,
otp - > base + OTPC_USER_ENABLE ) ;
2023-06-11 15:03:14 +01:00
ret = rockchip_otp_wait_status ( otp , OTPC_INT_STATUS , OTPC_USER_DONE ) ;
2019-10-29 11:42:40 +00:00
if ( ret < 0 ) {
dev_err ( otp - > dev , " timeout during read setup \n " ) ;
goto read_end ;
}
* buf + + = readb ( otp - > base + OTPC_USER_Q ) ;
}
read_end :
writel ( 0x0 | OTPC_USE_USER_MASK , otp - > base + OTPC_USER_CTRL ) ;
2023-06-11 15:03:13 +01:00
return ret ;
}
static int rockchip_otp_read ( void * context , unsigned int offset ,
void * val , size_t bytes )
{
struct rockchip_otp * otp = context ;
int ret ;
if ( ! otp - > data | | ! otp - > data - > reg_read )
return - EINVAL ;
ret = clk_bulk_prepare_enable ( otp - > data - > num_clks , otp - > clks ) ;
if ( ret < 0 ) {
dev_err ( otp - > dev , " failed to prepare/enable clks \n " ) ;
return ret ;
}
ret = otp - > data - > reg_read ( context , offset , val , bytes ) ;
clk_bulk_disable_unprepare ( otp - > data - > num_clks , otp - > clks ) ;
2019-10-29 11:42:40 +00:00
return ret ;
}
static struct nvmem_config otp_config = {
. name = " rockchip-otp " ,
. owner = THIS_MODULE ,
. read_only = true ,
. stride = 1 ,
. word_size = 1 ,
. reg_read = rockchip_otp_read ,
} ;
2023-06-11 15:03:13 +01:00
static const char * const px30_otp_clocks [ ] = {
" otp " , " apb_pclk " , " phy " ,
} ;
2019-10-29 11:42:40 +00:00
static const struct rockchip_data px30_data = {
. size = 0x40 ,
2023-06-11 15:03:13 +01:00
. clks = px30_otp_clocks ,
. num_clks = ARRAY_SIZE ( px30_otp_clocks ) ,
. reg_read = px30_otp_read ,
2019-10-29 11:42:40 +00:00
} ;
static const struct of_device_id rockchip_otp_match [ ] = {
{
. compatible = " rockchip,px30-otp " ,
. data = ( void * ) & px30_data ,
} ,
{
. compatible = " rockchip,rk3308-otp " ,
. data = ( void * ) & px30_data ,
} ,
{ /* sentinel */ } ,
} ;
MODULE_DEVICE_TABLE ( of , rockchip_otp_match ) ;
static int rockchip_otp_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct rockchip_otp * otp ;
const struct rockchip_data * data ;
struct nvmem_device * nvmem ;
int ret , i ;
data = of_device_get_match_data ( dev ) ;
if ( ! data ) {
dev_err ( dev , " failed to get match data \n " ) ;
return - EINVAL ;
}
otp = devm_kzalloc ( & pdev - > dev , sizeof ( struct rockchip_otp ) ,
GFP_KERNEL ) ;
if ( ! otp )
return - ENOMEM ;
2023-06-11 15:03:13 +01:00
otp - > data = data ;
2019-10-29 11:42:40 +00:00
otp - > dev = dev ;
otp - > base = devm_platform_ioremap_resource ( pdev , 0 ) ;
if ( IS_ERR ( otp - > base ) )
return PTR_ERR ( otp - > base ) ;
2023-06-11 15:03:13 +01:00
otp - > clks = devm_kcalloc ( dev , data - > num_clks , sizeof ( * otp - > clks ) ,
GFP_KERNEL ) ;
2019-10-29 11:42:40 +00:00
if ( ! otp - > clks )
return - ENOMEM ;
2023-06-11 15:03:13 +01:00
for ( i = 0 ; i < data - > num_clks ; + + i )
otp - > clks [ i ] . id = data - > clks [ i ] ;
2019-10-29 11:42:40 +00:00
2023-06-11 15:03:13 +01:00
ret = devm_clk_bulk_get ( dev , data - > num_clks , otp - > clks ) ;
2019-10-29 11:42:40 +00:00
if ( ret )
return ret ;
2023-06-11 15:03:15 +01:00
otp - > rst = devm_reset_control_array_get_exclusive ( dev ) ;
2019-10-29 11:42:40 +00:00
if ( IS_ERR ( otp - > rst ) )
return PTR_ERR ( otp - > rst ) ;
otp_config . size = data - > size ;
otp_config . priv = otp ;
otp_config . dev = dev ;
nvmem = devm_nvmem_register ( dev , & otp_config ) ;
return PTR_ERR_OR_ZERO ( nvmem ) ;
}
static struct platform_driver rockchip_otp_driver = {
. probe = rockchip_otp_probe ,
. driver = {
. name = " rockchip-otp " ,
. of_match_table = rockchip_otp_match ,
} ,
} ;
module_platform_driver ( rockchip_otp_driver ) ;
MODULE_DESCRIPTION ( " Rockchip OTP driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;