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 = &params->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), &params->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, &params->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