2022-04-29 12:20:18 +02:00
// SPDX-License-Identifier: GPL-2.0
2023-02-02 16:46:28 +02:00
# include <linux/debugfs.h>
2022-04-29 12:20:18 +02:00
# include <linux/mtd/spi-nor.h>
# include <linux/spi/spi.h>
# include <linux/spi/spi-mem.h>
# include "core.h"
# define SPI_NOR_DEBUGFS_ROOT "spi-nor"
# define SNOR_F_NAME(name) [ilog2(SNOR_F_##name)] = #name
static const char * const snor_f_names [ ] = {
SNOR_F_NAME ( HAS_SR_TB ) ,
SNOR_F_NAME ( NO_OP_CHIP_ERASE ) ,
SNOR_F_NAME ( BROKEN_RESET ) ,
SNOR_F_NAME ( 4 B_OPCODES ) ,
SNOR_F_NAME ( HAS_4BAIT ) ,
SNOR_F_NAME ( HAS_LOCK ) ,
SNOR_F_NAME ( HAS_16BIT_SR ) ,
SNOR_F_NAME ( NO_READ_CR ) ,
SNOR_F_NAME ( HAS_SR_TB_BIT6 ) ,
SNOR_F_NAME ( HAS_4BIT_BP ) ,
SNOR_F_NAME ( HAS_SR_BP3_BIT6 ) ,
SNOR_F_NAME ( IO_MODE_EN_VOLATILE ) ,
SNOR_F_NAME ( SOFT_RESET ) ,
SNOR_F_NAME ( SWP_IS_VOLATILE ) ,
2023-03-28 17:41:03 +02:00
SNOR_F_NAME ( RWW ) ,
2023-04-06 15:17:44 +09:00
SNOR_F_NAME ( ECC ) ,
2022-04-29 12:20:18 +02:00
} ;
# undef SNOR_F_NAME
static const char * spi_nor_protocol_name ( enum spi_nor_protocol proto )
{
switch ( proto ) {
case SNOR_PROTO_1_1_1 : return " 1S-1S-1S " ;
case SNOR_PROTO_1_1_2 : return " 1S-1S-2S " ;
case SNOR_PROTO_1_1_4 : return " 1S-1S-4S " ;
case SNOR_PROTO_1_1_8 : return " 1S-1S-8S " ;
case SNOR_PROTO_1_2_2 : return " 1S-2S-2S " ;
case SNOR_PROTO_1_4_4 : return " 1S-4S-4S " ;
case SNOR_PROTO_1_8_8 : return " 1S-8S-8S " ;
case SNOR_PROTO_2_2_2 : return " 2S-2S-2S " ;
case SNOR_PROTO_4_4_4 : return " 4S-4S-4S " ;
case SNOR_PROTO_8_8_8 : return " 8S-8S-8S " ;
case SNOR_PROTO_1_1_1_DTR : return " 1D-1D-1D " ;
case SNOR_PROTO_1_2_2_DTR : return " 1D-2D-2D " ;
case SNOR_PROTO_1_4_4_DTR : return " 1D-4D-4D " ;
case SNOR_PROTO_1_8_8_DTR : return " 1D-8D-8D " ;
case SNOR_PROTO_8_8_8_DTR : return " 8D-8D-8D " ;
}
return " <unknown> " ;
}
static void spi_nor_print_flags ( struct seq_file * s , unsigned long flags ,
const char * const * names , int names_len )
{
bool sep = false ;
int i ;
for ( i = 0 ; i < sizeof ( flags ) * BITS_PER_BYTE ; i + + ) {
if ( ! ( flags & BIT ( i ) ) )
continue ;
if ( sep )
seq_puts ( s , " | " ) ;
sep = true ;
if ( i < names_len & & names [ i ] )
seq_puts ( s , names [ i ] ) ;
else
seq_printf ( s , " 1<<%d " , i ) ;
}
}
static int spi_nor_params_show ( struct seq_file * s , void * data )
{
struct spi_nor * nor = s - > private ;
struct spi_nor_flash_parameter * params = nor - > params ;
struct spi_nor_erase_map * erase_map = & params - > erase_map ;
struct spi_nor_erase_region * region ;
const struct flash_info * info = nor - > info ;
char buf [ 16 ] , * str ;
int i ;
seq_printf ( s , " name \t \t %s \n " , info - > name ) ;
2022-08-11 00:06:50 +02:00
seq_printf ( s , " id \t \t %*ph \n " , SPI_NOR_MAX_ID_LEN , nor - > id ) ;
2022-04-29 12:20:18 +02:00
string_get_size ( params - > size , 1 , STRING_UNITS_2 , buf , sizeof ( buf ) ) ;
seq_printf ( s , " size \t \t %s \n " , buf ) ;
seq_printf ( s , " write size \t %u \n " , params - > writesize ) ;
seq_printf ( s , " page size \t %u \n " , params - > page_size ) ;
2022-07-25 12:24:59 +03:00
seq_printf ( s , " address nbytes \t %u \n " , nor - > addr_nbytes ) ;
2022-04-29 12:20:18 +02:00
seq_puts ( s , " flags \t \t " ) ;
spi_nor_print_flags ( s , nor - > flags , snor_f_names , sizeof ( snor_f_names ) ) ;
seq_puts ( s , " \n " ) ;
seq_puts ( s , " \n opcodes \n " ) ;
seq_printf ( s , " read \t \t 0x%02x \n " , nor - > read_opcode ) ;
seq_printf ( s , " dummy cycles \t %u \n " , nor - > read_dummy ) ;
seq_printf ( s , " erase \t \t 0x%02x \n " , nor - > erase_opcode ) ;
seq_printf ( s , " program \t 0x%02x \n " , nor - > program_opcode ) ;
switch ( nor - > cmd_ext_type ) {
case SPI_NOR_EXT_NONE :
str = " none " ;
break ;
case SPI_NOR_EXT_REPEAT :
str = " repeat " ;
break ;
case SPI_NOR_EXT_INVERT :
str = " invert " ;
break ;
default :
str = " <unknown> " ;
break ;
}
seq_printf ( s , " 8D extension \t %s \n " , str ) ;
seq_puts ( s , " \n protocols \n " ) ;
seq_printf ( s , " read \t \t %s \n " ,
spi_nor_protocol_name ( nor - > read_proto ) ) ;
seq_printf ( s , " write \t \t %s \n " ,
spi_nor_protocol_name ( nor - > write_proto ) ) ;
seq_printf ( s , " register \t %s \n " ,
spi_nor_protocol_name ( nor - > reg_proto ) ) ;
seq_puts ( s , " \n erase commands \n " ) ;
for ( i = 0 ; i < SNOR_ERASE_TYPE_MAX ; i + + ) {
struct spi_nor_erase_type * et = & erase_map - > erase_type [ i ] ;
if ( et - > size ) {
string_get_size ( et - > size , 1 , STRING_UNITS_2 , buf ,
sizeof ( buf ) ) ;
seq_printf ( s , " %02x (%s) [%d] \n " , et - > opcode , buf , i ) ;
}
}
if ( ! ( nor - > flags & SNOR_F_NO_OP_CHIP_ERASE ) ) {
string_get_size ( params - > size , 1 , STRING_UNITS_2 , buf , sizeof ( buf ) ) ;
seq_printf ( s , " %02x (%s) \n " , SPINOR_OP_CHIP_ERASE , buf ) ;
}
seq_puts ( s , " \n sector map \n " ) ;
seq_puts ( s , " region (in hex) | erase mask | flags \n " ) ;
seq_puts ( s , " ------------------+------------+---------- \n " ) ;
for ( region = erase_map - > regions ;
region ;
region = spi_nor_region_next ( region ) ) {
u64 start = region - > offset & ~ SNOR_ERASE_FLAGS_MASK ;
u64 flags = region - > offset & SNOR_ERASE_FLAGS_MASK ;
u64 end = start + region - > size - 1 ;
seq_printf ( s , " %08llx-%08llx | [%c%c%c%c] | %s \n " ,
start , end ,
flags & BIT ( 0 ) ? ' 0 ' : ' ' ,
flags & BIT ( 1 ) ? ' 1 ' : ' ' ,
flags & BIT ( 2 ) ? ' 2 ' : ' ' ,
flags & BIT ( 3 ) ? ' 3 ' : ' ' ,
flags & SNOR_OVERLAID_REGION ? " overlaid " : " " ) ;
}
return 0 ;
}
DEFINE_SHOW_ATTRIBUTE ( spi_nor_params ) ;
static void spi_nor_print_read_cmd ( struct seq_file * s , u32 cap ,
struct spi_nor_read_command * cmd )
{
seq_printf ( s , " %s%s \n " , spi_nor_protocol_name ( cmd - > proto ) ,
cap = = SNOR_HWCAPS_READ_FAST ? " (fast read) " : " " ) ;
seq_printf ( s , " opcode \t 0x%02x \n " , cmd - > opcode ) ;
seq_printf ( s , " mode cycles \t %u \n " , cmd - > num_mode_clocks ) ;
seq_printf ( s , " dummy cycles \t %u \n " , cmd - > num_wait_states ) ;
}
static void spi_nor_print_pp_cmd ( struct seq_file * s ,
struct spi_nor_pp_command * cmd )
{
seq_printf ( s , " %s \n " , spi_nor_protocol_name ( cmd - > proto ) ) ;
seq_printf ( s , " opcode \t 0x%02x \n " , cmd - > opcode ) ;
}
static int spi_nor_capabilities_show ( struct seq_file * s , void * data )
{
struct spi_nor * nor = s - > private ;
struct spi_nor_flash_parameter * params = nor - > params ;
u32 hwcaps = params - > hwcaps . mask ;
int i , cmd ;
seq_puts ( s , " Supported read modes by the flash \n " ) ;
for ( i = 0 ; i < sizeof ( hwcaps ) * BITS_PER_BYTE ; i + + ) {
if ( ! ( hwcaps & BIT ( i ) ) )
continue ;
cmd = spi_nor_hwcaps_read2cmd ( BIT ( i ) ) ;
if ( cmd < 0 )
continue ;
spi_nor_print_read_cmd ( s , BIT ( i ) , & params - > reads [ cmd ] ) ;
hwcaps & = ~ BIT ( i ) ;
}
seq_puts ( s , " \n Supported page program modes by the flash \n " ) ;
for ( i = 0 ; i < sizeof ( hwcaps ) * BITS_PER_BYTE ; i + + ) {
if ( ! ( hwcaps & BIT ( i ) ) )
continue ;
cmd = spi_nor_hwcaps_pp2cmd ( BIT ( i ) ) ;
if ( cmd < 0 )
continue ;
spi_nor_print_pp_cmd ( s , & params - > page_programs [ cmd ] ) ;
hwcaps & = ~ BIT ( i ) ;
}
if ( hwcaps )
seq_printf ( s , " \n unknown hwcaps 0x%x \n " , hwcaps ) ;
return 0 ;
}
DEFINE_SHOW_ATTRIBUTE ( spi_nor_capabilities ) ;
static void spi_nor_debugfs_unregister ( void * data )
{
struct spi_nor * nor = data ;
debugfs_remove ( nor - > debugfs_root ) ;
nor - > debugfs_root = NULL ;
}
2023-02-08 17:02:30 +01:00
static struct dentry * rootdir ;
2022-04-29 12:20:18 +02:00
void spi_nor_debugfs_register ( struct spi_nor * nor )
{
2023-02-08 17:02:30 +01:00
struct dentry * d ;
2022-04-29 12:20:18 +02:00
int ret ;
if ( ! rootdir )
rootdir = debugfs_create_dir ( SPI_NOR_DEBUGFS_ROOT , NULL ) ;
ret = devm_add_action ( nor - > dev , spi_nor_debugfs_unregister , nor ) ;
if ( ret )
return ;
d = debugfs_create_dir ( dev_name ( nor - > dev ) , rootdir ) ;
nor - > debugfs_root = d ;
debugfs_create_file ( " params " , 0444 , d , nor , & spi_nor_params_fops ) ;
debugfs_create_file ( " capabilities " , 0444 , d , nor ,
& spi_nor_capabilities_fops ) ;
}
2023-02-08 17:02:30 +01:00
void spi_nor_debugfs_shutdown ( void )
{
debugfs_remove ( rootdir ) ;
}