162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci 362306a36Sopenharmony_ci#include <linux/debugfs.h> 462306a36Sopenharmony_ci#include <linux/mtd/spi-nor.h> 562306a36Sopenharmony_ci#include <linux/spi/spi.h> 662306a36Sopenharmony_ci#include <linux/spi/spi-mem.h> 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include "core.h" 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#define SPI_NOR_DEBUGFS_ROOT "spi-nor" 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#define SNOR_F_NAME(name) [ilog2(SNOR_F_##name)] = #name 1362306a36Sopenharmony_cistatic const char *const snor_f_names[] = { 1462306a36Sopenharmony_ci SNOR_F_NAME(HAS_SR_TB), 1562306a36Sopenharmony_ci SNOR_F_NAME(NO_OP_CHIP_ERASE), 1662306a36Sopenharmony_ci SNOR_F_NAME(BROKEN_RESET), 1762306a36Sopenharmony_ci SNOR_F_NAME(4B_OPCODES), 1862306a36Sopenharmony_ci SNOR_F_NAME(HAS_4BAIT), 1962306a36Sopenharmony_ci SNOR_F_NAME(HAS_LOCK), 2062306a36Sopenharmony_ci SNOR_F_NAME(HAS_16BIT_SR), 2162306a36Sopenharmony_ci SNOR_F_NAME(NO_READ_CR), 2262306a36Sopenharmony_ci SNOR_F_NAME(HAS_SR_TB_BIT6), 2362306a36Sopenharmony_ci SNOR_F_NAME(HAS_4BIT_BP), 2462306a36Sopenharmony_ci SNOR_F_NAME(HAS_SR_BP3_BIT6), 2562306a36Sopenharmony_ci SNOR_F_NAME(IO_MODE_EN_VOLATILE), 2662306a36Sopenharmony_ci SNOR_F_NAME(SOFT_RESET), 2762306a36Sopenharmony_ci SNOR_F_NAME(SWP_IS_VOLATILE), 2862306a36Sopenharmony_ci SNOR_F_NAME(RWW), 2962306a36Sopenharmony_ci SNOR_F_NAME(ECC), 3062306a36Sopenharmony_ci SNOR_F_NAME(NO_WP), 3162306a36Sopenharmony_ci}; 3262306a36Sopenharmony_ci#undef SNOR_F_NAME 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_cistatic const char *spi_nor_protocol_name(enum spi_nor_protocol proto) 3562306a36Sopenharmony_ci{ 3662306a36Sopenharmony_ci switch (proto) { 3762306a36Sopenharmony_ci case SNOR_PROTO_1_1_1: return "1S-1S-1S"; 3862306a36Sopenharmony_ci case SNOR_PROTO_1_1_2: return "1S-1S-2S"; 3962306a36Sopenharmony_ci case SNOR_PROTO_1_1_4: return "1S-1S-4S"; 4062306a36Sopenharmony_ci case SNOR_PROTO_1_1_8: return "1S-1S-8S"; 4162306a36Sopenharmony_ci case SNOR_PROTO_1_2_2: return "1S-2S-2S"; 4262306a36Sopenharmony_ci case SNOR_PROTO_1_4_4: return "1S-4S-4S"; 4362306a36Sopenharmony_ci case SNOR_PROTO_1_8_8: return "1S-8S-8S"; 4462306a36Sopenharmony_ci case SNOR_PROTO_2_2_2: return "2S-2S-2S"; 4562306a36Sopenharmony_ci case SNOR_PROTO_4_4_4: return "4S-4S-4S"; 4662306a36Sopenharmony_ci case SNOR_PROTO_8_8_8: return "8S-8S-8S"; 4762306a36Sopenharmony_ci case SNOR_PROTO_1_1_1_DTR: return "1D-1D-1D"; 4862306a36Sopenharmony_ci case SNOR_PROTO_1_2_2_DTR: return "1D-2D-2D"; 4962306a36Sopenharmony_ci case SNOR_PROTO_1_4_4_DTR: return "1D-4D-4D"; 5062306a36Sopenharmony_ci case SNOR_PROTO_1_8_8_DTR: return "1D-8D-8D"; 5162306a36Sopenharmony_ci case SNOR_PROTO_8_8_8_DTR: return "8D-8D-8D"; 5262306a36Sopenharmony_ci } 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci return "<unknown>"; 5562306a36Sopenharmony_ci} 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_cistatic void spi_nor_print_flags(struct seq_file *s, unsigned long flags, 5862306a36Sopenharmony_ci const char *const *names, int names_len) 5962306a36Sopenharmony_ci{ 6062306a36Sopenharmony_ci bool sep = false; 6162306a36Sopenharmony_ci int i; 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci for (i = 0; i < sizeof(flags) * BITS_PER_BYTE; i++) { 6462306a36Sopenharmony_ci if (!(flags & BIT(i))) 6562306a36Sopenharmony_ci continue; 6662306a36Sopenharmony_ci if (sep) 6762306a36Sopenharmony_ci seq_puts(s, " | "); 6862306a36Sopenharmony_ci sep = true; 6962306a36Sopenharmony_ci if (i < names_len && names[i]) 7062306a36Sopenharmony_ci seq_puts(s, names[i]); 7162306a36Sopenharmony_ci else 7262306a36Sopenharmony_ci seq_printf(s, "1<<%d", i); 7362306a36Sopenharmony_ci } 7462306a36Sopenharmony_ci} 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_cistatic int spi_nor_params_show(struct seq_file *s, void *data) 7762306a36Sopenharmony_ci{ 7862306a36Sopenharmony_ci struct spi_nor *nor = s->private; 7962306a36Sopenharmony_ci struct spi_nor_flash_parameter *params = nor->params; 8062306a36Sopenharmony_ci struct spi_nor_erase_map *erase_map = ¶ms->erase_map; 8162306a36Sopenharmony_ci struct spi_nor_erase_region *region; 8262306a36Sopenharmony_ci const struct flash_info *info = nor->info; 8362306a36Sopenharmony_ci char buf[16], *str; 8462306a36Sopenharmony_ci int i; 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci seq_printf(s, "name\t\t%s\n", info->name); 8762306a36Sopenharmony_ci seq_printf(s, "id\t\t%*ph\n", SPI_NOR_MAX_ID_LEN, nor->id); 8862306a36Sopenharmony_ci string_get_size(params->size, 1, STRING_UNITS_2, buf, sizeof(buf)); 8962306a36Sopenharmony_ci seq_printf(s, "size\t\t%s\n", buf); 9062306a36Sopenharmony_ci seq_printf(s, "write size\t%u\n", params->writesize); 9162306a36Sopenharmony_ci seq_printf(s, "page size\t%u\n", params->page_size); 9262306a36Sopenharmony_ci seq_printf(s, "address nbytes\t%u\n", nor->addr_nbytes); 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci seq_puts(s, "flags\t\t"); 9562306a36Sopenharmony_ci spi_nor_print_flags(s, nor->flags, snor_f_names, sizeof(snor_f_names)); 9662306a36Sopenharmony_ci seq_puts(s, "\n"); 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci seq_puts(s, "\nopcodes\n"); 9962306a36Sopenharmony_ci seq_printf(s, " read\t\t0x%02x\n", nor->read_opcode); 10062306a36Sopenharmony_ci seq_printf(s, " dummy cycles\t%u\n", nor->read_dummy); 10162306a36Sopenharmony_ci seq_printf(s, " erase\t\t0x%02x\n", nor->erase_opcode); 10262306a36Sopenharmony_ci seq_printf(s, " program\t0x%02x\n", nor->program_opcode); 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci switch (nor->cmd_ext_type) { 10562306a36Sopenharmony_ci case SPI_NOR_EXT_NONE: 10662306a36Sopenharmony_ci str = "none"; 10762306a36Sopenharmony_ci break; 10862306a36Sopenharmony_ci case SPI_NOR_EXT_REPEAT: 10962306a36Sopenharmony_ci str = "repeat"; 11062306a36Sopenharmony_ci break; 11162306a36Sopenharmony_ci case SPI_NOR_EXT_INVERT: 11262306a36Sopenharmony_ci str = "invert"; 11362306a36Sopenharmony_ci break; 11462306a36Sopenharmony_ci default: 11562306a36Sopenharmony_ci str = "<unknown>"; 11662306a36Sopenharmony_ci break; 11762306a36Sopenharmony_ci } 11862306a36Sopenharmony_ci seq_printf(s, " 8D extension\t%s\n", str); 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci seq_puts(s, "\nprotocols\n"); 12162306a36Sopenharmony_ci seq_printf(s, " read\t\t%s\n", 12262306a36Sopenharmony_ci spi_nor_protocol_name(nor->read_proto)); 12362306a36Sopenharmony_ci seq_printf(s, " write\t\t%s\n", 12462306a36Sopenharmony_ci spi_nor_protocol_name(nor->write_proto)); 12562306a36Sopenharmony_ci seq_printf(s, " register\t%s\n", 12662306a36Sopenharmony_ci spi_nor_protocol_name(nor->reg_proto)); 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci seq_puts(s, "\nerase commands\n"); 12962306a36Sopenharmony_ci for (i = 0; i < SNOR_ERASE_TYPE_MAX; i++) { 13062306a36Sopenharmony_ci struct spi_nor_erase_type *et = &erase_map->erase_type[i]; 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci if (et->size) { 13362306a36Sopenharmony_ci string_get_size(et->size, 1, STRING_UNITS_2, buf, 13462306a36Sopenharmony_ci sizeof(buf)); 13562306a36Sopenharmony_ci seq_printf(s, " %02x (%s) [%d]\n", et->opcode, buf, i); 13662306a36Sopenharmony_ci } 13762306a36Sopenharmony_ci } 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci if (!(nor->flags & SNOR_F_NO_OP_CHIP_ERASE)) { 14062306a36Sopenharmony_ci string_get_size(params->size, 1, STRING_UNITS_2, buf, sizeof(buf)); 14162306a36Sopenharmony_ci seq_printf(s, " %02x (%s)\n", SPINOR_OP_CHIP_ERASE, buf); 14262306a36Sopenharmony_ci } 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci seq_puts(s, "\nsector map\n"); 14562306a36Sopenharmony_ci seq_puts(s, " region (in hex) | erase mask | flags\n"); 14662306a36Sopenharmony_ci seq_puts(s, " ------------------+------------+----------\n"); 14762306a36Sopenharmony_ci for (region = erase_map->regions; 14862306a36Sopenharmony_ci region; 14962306a36Sopenharmony_ci region = spi_nor_region_next(region)) { 15062306a36Sopenharmony_ci u64 start = region->offset & ~SNOR_ERASE_FLAGS_MASK; 15162306a36Sopenharmony_ci u64 flags = region->offset & SNOR_ERASE_FLAGS_MASK; 15262306a36Sopenharmony_ci u64 end = start + region->size - 1; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci seq_printf(s, " %08llx-%08llx | [%c%c%c%c] | %s\n", 15562306a36Sopenharmony_ci start, end, 15662306a36Sopenharmony_ci flags & BIT(0) ? '0' : ' ', 15762306a36Sopenharmony_ci flags & BIT(1) ? '1' : ' ', 15862306a36Sopenharmony_ci flags & BIT(2) ? '2' : ' ', 15962306a36Sopenharmony_ci flags & BIT(3) ? '3' : ' ', 16062306a36Sopenharmony_ci flags & SNOR_OVERLAID_REGION ? "overlaid" : ""); 16162306a36Sopenharmony_ci } 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci return 0; 16462306a36Sopenharmony_ci} 16562306a36Sopenharmony_ciDEFINE_SHOW_ATTRIBUTE(spi_nor_params); 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_cistatic void spi_nor_print_read_cmd(struct seq_file *s, u32 cap, 16862306a36Sopenharmony_ci struct spi_nor_read_command *cmd) 16962306a36Sopenharmony_ci{ 17062306a36Sopenharmony_ci seq_printf(s, " %s%s\n", spi_nor_protocol_name(cmd->proto), 17162306a36Sopenharmony_ci cap == SNOR_HWCAPS_READ_FAST ? " (fast read)" : ""); 17262306a36Sopenharmony_ci seq_printf(s, " opcode\t0x%02x\n", cmd->opcode); 17362306a36Sopenharmony_ci seq_printf(s, " mode cycles\t%u\n", cmd->num_mode_clocks); 17462306a36Sopenharmony_ci seq_printf(s, " dummy cycles\t%u\n", cmd->num_wait_states); 17562306a36Sopenharmony_ci} 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_cistatic void spi_nor_print_pp_cmd(struct seq_file *s, 17862306a36Sopenharmony_ci struct spi_nor_pp_command *cmd) 17962306a36Sopenharmony_ci{ 18062306a36Sopenharmony_ci seq_printf(s, " %s\n", spi_nor_protocol_name(cmd->proto)); 18162306a36Sopenharmony_ci seq_printf(s, " opcode\t0x%02x\n", cmd->opcode); 18262306a36Sopenharmony_ci} 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_cistatic int spi_nor_capabilities_show(struct seq_file *s, void *data) 18562306a36Sopenharmony_ci{ 18662306a36Sopenharmony_ci struct spi_nor *nor = s->private; 18762306a36Sopenharmony_ci struct spi_nor_flash_parameter *params = nor->params; 18862306a36Sopenharmony_ci u32 hwcaps = params->hwcaps.mask; 18962306a36Sopenharmony_ci int i, cmd; 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci seq_puts(s, "Supported read modes by the flash\n"); 19262306a36Sopenharmony_ci for (i = 0; i < sizeof(hwcaps) * BITS_PER_BYTE; i++) { 19362306a36Sopenharmony_ci if (!(hwcaps & BIT(i))) 19462306a36Sopenharmony_ci continue; 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci cmd = spi_nor_hwcaps_read2cmd(BIT(i)); 19762306a36Sopenharmony_ci if (cmd < 0) 19862306a36Sopenharmony_ci continue; 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci spi_nor_print_read_cmd(s, BIT(i), ¶ms->reads[cmd]); 20162306a36Sopenharmony_ci hwcaps &= ~BIT(i); 20262306a36Sopenharmony_ci } 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci seq_puts(s, "\nSupported page program modes by the flash\n"); 20562306a36Sopenharmony_ci for (i = 0; i < sizeof(hwcaps) * BITS_PER_BYTE; i++) { 20662306a36Sopenharmony_ci if (!(hwcaps & BIT(i))) 20762306a36Sopenharmony_ci continue; 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci cmd = spi_nor_hwcaps_pp2cmd(BIT(i)); 21062306a36Sopenharmony_ci if (cmd < 0) 21162306a36Sopenharmony_ci continue; 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci spi_nor_print_pp_cmd(s, ¶ms->page_programs[cmd]); 21462306a36Sopenharmony_ci hwcaps &= ~BIT(i); 21562306a36Sopenharmony_ci } 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci if (hwcaps) 21862306a36Sopenharmony_ci seq_printf(s, "\nunknown hwcaps 0x%x\n", hwcaps); 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci return 0; 22162306a36Sopenharmony_ci} 22262306a36Sopenharmony_ciDEFINE_SHOW_ATTRIBUTE(spi_nor_capabilities); 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_cistatic void spi_nor_debugfs_unregister(void *data) 22562306a36Sopenharmony_ci{ 22662306a36Sopenharmony_ci struct spi_nor *nor = data; 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci debugfs_remove(nor->debugfs_root); 22962306a36Sopenharmony_ci nor->debugfs_root = NULL; 23062306a36Sopenharmony_ci} 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_cistatic struct dentry *rootdir; 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_civoid spi_nor_debugfs_register(struct spi_nor *nor) 23562306a36Sopenharmony_ci{ 23662306a36Sopenharmony_ci struct dentry *d; 23762306a36Sopenharmony_ci int ret; 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci if (!rootdir) 24062306a36Sopenharmony_ci rootdir = debugfs_create_dir(SPI_NOR_DEBUGFS_ROOT, NULL); 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci ret = devm_add_action(nor->dev, spi_nor_debugfs_unregister, nor); 24362306a36Sopenharmony_ci if (ret) 24462306a36Sopenharmony_ci return; 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ci d = debugfs_create_dir(dev_name(nor->dev), rootdir); 24762306a36Sopenharmony_ci nor->debugfs_root = d; 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci debugfs_create_file("params", 0444, d, nor, &spi_nor_params_fops); 25062306a36Sopenharmony_ci debugfs_create_file("capabilities", 0444, d, nor, 25162306a36Sopenharmony_ci &spi_nor_capabilities_fops); 25262306a36Sopenharmony_ci} 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_civoid spi_nor_debugfs_shutdown(void) 25562306a36Sopenharmony_ci{ 25662306a36Sopenharmony_ci debugfs_remove(rootdir); 25762306a36Sopenharmony_ci} 258