1 | // SPDX-License-Identifier: GPL-2.0 |
2 | |
3 | #include <linux/debugfs.h> |
4 | #include <linux/mtd/spi-nor.h> |
5 | #include <linux/spi/spi.h> |
6 | #include <linux/spi/spi-mem.h> |
7 | |
8 | #include "core.h" |
9 | |
10 | #define SPI_NOR_DEBUGFS_ROOT "spi-nor" |
11 | |
12 | #define SNOR_F_NAME(name) [ilog2(SNOR_F_##name)] = #name |
13 | static const char *const snor_f_names[] = { |
14 | SNOR_F_NAME(HAS_SR_TB), |
15 | SNOR_F_NAME(NO_OP_CHIP_ERASE), |
16 | SNOR_F_NAME(BROKEN_RESET), |
17 | SNOR_F_NAME(4B_OPCODES), |
18 | SNOR_F_NAME(HAS_4BAIT), |
19 | SNOR_F_NAME(HAS_LOCK), |
20 | SNOR_F_NAME(HAS_16BIT_SR), |
21 | SNOR_F_NAME(NO_READ_CR), |
22 | SNOR_F_NAME(HAS_SR_TB_BIT6), |
23 | SNOR_F_NAME(HAS_4BIT_BP), |
24 | SNOR_F_NAME(HAS_SR_BP3_BIT6), |
25 | SNOR_F_NAME(IO_MODE_EN_VOLATILE), |
26 | SNOR_F_NAME(SOFT_RESET), |
27 | SNOR_F_NAME(SWP_IS_VOLATILE), |
28 | SNOR_F_NAME(RWW), |
29 | SNOR_F_NAME(ECC), |
30 | SNOR_F_NAME(NO_WP), |
31 | }; |
32 | #undef SNOR_F_NAME |
33 | |
34 | static const char *spi_nor_protocol_name(enum spi_nor_protocol proto) |
35 | { |
36 | switch (proto) { |
37 | case SNOR_PROTO_1_1_1: return "1S-1S-1S" ; |
38 | case SNOR_PROTO_1_1_2: return "1S-1S-2S" ; |
39 | case SNOR_PROTO_1_1_4: return "1S-1S-4S" ; |
40 | case SNOR_PROTO_1_1_8: return "1S-1S-8S" ; |
41 | case SNOR_PROTO_1_2_2: return "1S-2S-2S" ; |
42 | case SNOR_PROTO_1_4_4: return "1S-4S-4S" ; |
43 | case SNOR_PROTO_1_8_8: return "1S-8S-8S" ; |
44 | case SNOR_PROTO_2_2_2: return "2S-2S-2S" ; |
45 | case SNOR_PROTO_4_4_4: return "4S-4S-4S" ; |
46 | case SNOR_PROTO_8_8_8: return "8S-8S-8S" ; |
47 | case SNOR_PROTO_1_1_1_DTR: return "1D-1D-1D" ; |
48 | case SNOR_PROTO_1_2_2_DTR: return "1D-2D-2D" ; |
49 | case SNOR_PROTO_1_4_4_DTR: return "1D-4D-4D" ; |
50 | case SNOR_PROTO_1_8_8_DTR: return "1D-8D-8D" ; |
51 | case SNOR_PROTO_8_8_8_DTR: return "8D-8D-8D" ; |
52 | } |
53 | |
54 | return "<unknown>" ; |
55 | } |
56 | |
57 | static void spi_nor_print_flags(struct seq_file *s, unsigned long flags, |
58 | const char *const *names, int names_len) |
59 | { |
60 | bool sep = false; |
61 | int i; |
62 | |
63 | for (i = 0; i < sizeof(flags) * BITS_PER_BYTE; i++) { |
64 | if (!(flags & BIT(i))) |
65 | continue; |
66 | if (sep) |
67 | seq_puts(m: s, s: " | " ); |
68 | sep = true; |
69 | if (i < names_len && names[i]) |
70 | seq_puts(m: s, s: names[i]); |
71 | else |
72 | seq_printf(m: s, fmt: "1<<%d" , i); |
73 | } |
74 | } |
75 | |
76 | static int spi_nor_params_show(struct seq_file *s, void *data) |
77 | { |
78 | struct spi_nor *nor = s->private; |
79 | struct spi_nor_flash_parameter *params = nor->params; |
80 | struct spi_nor_erase_map *erase_map = ¶ms->erase_map; |
81 | struct spi_nor_erase_region *region = erase_map->regions; |
82 | const struct flash_info *info = nor->info; |
83 | char buf[16], *str; |
84 | unsigned int i; |
85 | |
86 | seq_printf(m: s, fmt: "name\t\t%s\n" , info->name); |
87 | seq_printf(m: s, fmt: "id\t\t%*ph\n" , SPI_NOR_MAX_ID_LEN, nor->id); |
88 | string_get_size(size: params->size, blk_size: 1, units: STRING_UNITS_2, buf, len: sizeof(buf)); |
89 | seq_printf(m: s, fmt: "size\t\t%s\n" , buf); |
90 | seq_printf(m: s, fmt: "write size\t%u\n" , params->writesize); |
91 | seq_printf(m: s, fmt: "page size\t%u\n" , params->page_size); |
92 | seq_printf(m: s, fmt: "address nbytes\t%u\n" , nor->addr_nbytes); |
93 | |
94 | seq_puts(m: s, s: "flags\t\t" ); |
95 | spi_nor_print_flags(s, flags: nor->flags, names: snor_f_names, names_len: sizeof(snor_f_names)); |
96 | seq_puts(m: s, s: "\n" ); |
97 | |
98 | seq_puts(m: s, s: "\nopcodes\n" ); |
99 | seq_printf(m: s, fmt: " read\t\t0x%02x\n" , nor->read_opcode); |
100 | seq_printf(m: s, fmt: " dummy cycles\t%u\n" , nor->read_dummy); |
101 | seq_printf(m: s, fmt: " erase\t\t0x%02x\n" , nor->erase_opcode); |
102 | seq_printf(m: s, fmt: " program\t0x%02x\n" , nor->program_opcode); |
103 | |
104 | switch (nor->cmd_ext_type) { |
105 | case SPI_NOR_EXT_NONE: |
106 | str = "none" ; |
107 | break; |
108 | case SPI_NOR_EXT_REPEAT: |
109 | str = "repeat" ; |
110 | break; |
111 | case SPI_NOR_EXT_INVERT: |
112 | str = "invert" ; |
113 | break; |
114 | default: |
115 | str = "<unknown>" ; |
116 | break; |
117 | } |
118 | seq_printf(m: s, fmt: " 8D extension\t%s\n" , str); |
119 | |
120 | seq_puts(m: s, s: "\nprotocols\n" ); |
121 | seq_printf(m: s, fmt: " read\t\t%s\n" , |
122 | spi_nor_protocol_name(proto: nor->read_proto)); |
123 | seq_printf(m: s, fmt: " write\t\t%s\n" , |
124 | spi_nor_protocol_name(proto: nor->write_proto)); |
125 | seq_printf(m: s, fmt: " register\t%s\n" , |
126 | spi_nor_protocol_name(proto: nor->reg_proto)); |
127 | |
128 | seq_puts(m: s, s: "\nerase commands\n" ); |
129 | for (i = 0; i < SNOR_ERASE_TYPE_MAX; i++) { |
130 | struct spi_nor_erase_type *et = &erase_map->erase_type[i]; |
131 | |
132 | if (et->size) { |
133 | string_get_size(size: et->size, blk_size: 1, units: STRING_UNITS_2, buf, |
134 | len: sizeof(buf)); |
135 | seq_printf(m: s, fmt: " %02x (%s) [%d]\n" , et->opcode, buf, i); |
136 | } |
137 | } |
138 | |
139 | if (!(nor->flags & SNOR_F_NO_OP_CHIP_ERASE)) { |
140 | string_get_size(size: params->size, blk_size: 1, units: STRING_UNITS_2, buf, len: sizeof(buf)); |
141 | seq_printf(m: s, fmt: " %02x (%s)\n" , nor->params->die_erase_opcode, buf); |
142 | } |
143 | |
144 | seq_puts(m: s, s: "\nsector map\n" ); |
145 | seq_puts(m: s, s: " region (in hex) | erase mask | overlaid\n" ); |
146 | seq_puts(m: s, s: " ------------------+------------+----------\n" ); |
147 | for (i = 0; i < erase_map->n_regions; i++) { |
148 | u64 start = region[i].offset; |
149 | u64 end = start + region[i].size - 1; |
150 | u8 erase_mask = region[i].erase_mask; |
151 | |
152 | seq_printf(m: s, fmt: " %08llx-%08llx | [%c%c%c%c] | %s\n" , |
153 | start, end, |
154 | erase_mask & BIT(0) ? '0' : ' ', |
155 | erase_mask & BIT(1) ? '1' : ' ', |
156 | erase_mask & BIT(2) ? '2' : ' ', |
157 | erase_mask & BIT(3) ? '3' : ' ', |
158 | region[i].overlaid ? "yes" : "no" ); |
159 | } |
160 | |
161 | return 0; |
162 | } |
163 | DEFINE_SHOW_ATTRIBUTE(spi_nor_params); |
164 | |
165 | static void spi_nor_print_read_cmd(struct seq_file *s, u32 cap, |
166 | struct spi_nor_read_command *cmd) |
167 | { |
168 | seq_printf(m: s, fmt: " %s%s\n" , spi_nor_protocol_name(proto: cmd->proto), |
169 | cap == SNOR_HWCAPS_READ_FAST ? " (fast read)" : "" ); |
170 | seq_printf(m: s, fmt: " opcode\t0x%02x\n" , cmd->opcode); |
171 | seq_printf(m: s, fmt: " mode cycles\t%u\n" , cmd->num_mode_clocks); |
172 | seq_printf(m: s, fmt: " dummy cycles\t%u\n" , cmd->num_wait_states); |
173 | } |
174 | |
175 | static void spi_nor_print_pp_cmd(struct seq_file *s, |
176 | struct spi_nor_pp_command *cmd) |
177 | { |
178 | seq_printf(m: s, fmt: " %s\n" , spi_nor_protocol_name(proto: cmd->proto)); |
179 | seq_printf(m: s, fmt: " opcode\t0x%02x\n" , cmd->opcode); |
180 | } |
181 | |
182 | static int spi_nor_capabilities_show(struct seq_file *s, void *data) |
183 | { |
184 | struct spi_nor *nor = s->private; |
185 | struct spi_nor_flash_parameter *params = nor->params; |
186 | u32 hwcaps = params->hwcaps.mask; |
187 | int i, cmd; |
188 | |
189 | seq_puts(m: s, s: "Supported read modes by the flash\n" ); |
190 | for (i = 0; i < sizeof(hwcaps) * BITS_PER_BYTE; i++) { |
191 | if (!(hwcaps & BIT(i))) |
192 | continue; |
193 | |
194 | cmd = spi_nor_hwcaps_read2cmd(BIT(i)); |
195 | if (cmd < 0) |
196 | continue; |
197 | |
198 | spi_nor_print_read_cmd(s, BIT(i), cmd: ¶ms->reads[cmd]); |
199 | hwcaps &= ~BIT(i); |
200 | } |
201 | |
202 | seq_puts(m: s, s: "\nSupported page program modes by the flash\n" ); |
203 | for (i = 0; i < sizeof(hwcaps) * BITS_PER_BYTE; i++) { |
204 | if (!(hwcaps & BIT(i))) |
205 | continue; |
206 | |
207 | cmd = spi_nor_hwcaps_pp2cmd(BIT(i)); |
208 | if (cmd < 0) |
209 | continue; |
210 | |
211 | spi_nor_print_pp_cmd(s, cmd: ¶ms->page_programs[cmd]); |
212 | hwcaps &= ~BIT(i); |
213 | } |
214 | |
215 | if (hwcaps) |
216 | seq_printf(m: s, fmt: "\nunknown hwcaps 0x%x\n" , hwcaps); |
217 | |
218 | return 0; |
219 | } |
220 | DEFINE_SHOW_ATTRIBUTE(spi_nor_capabilities); |
221 | |
222 | static void spi_nor_debugfs_unregister(void *data) |
223 | { |
224 | struct spi_nor *nor = data; |
225 | |
226 | debugfs_remove(dentry: nor->debugfs_root); |
227 | nor->debugfs_root = NULL; |
228 | } |
229 | |
230 | static struct dentry *rootdir; |
231 | |
232 | void spi_nor_debugfs_register(struct spi_nor *nor) |
233 | { |
234 | struct dentry *d; |
235 | int ret; |
236 | |
237 | if (!rootdir) |
238 | rootdir = debugfs_create_dir(SPI_NOR_DEBUGFS_ROOT, NULL); |
239 | |
240 | ret = devm_add_action(nor->dev, spi_nor_debugfs_unregister, nor); |
241 | if (ret) |
242 | return; |
243 | |
244 | d = debugfs_create_dir(name: dev_name(dev: nor->dev), parent: rootdir); |
245 | nor->debugfs_root = d; |
246 | |
247 | debugfs_create_file(name: "params" , mode: 0444, parent: d, data: nor, fops: &spi_nor_params_fops); |
248 | debugfs_create_file(name: "capabilities" , mode: 0444, parent: d, data: nor, |
249 | fops: &spi_nor_capabilities_fops); |
250 | } |
251 | |
252 | void spi_nor_debugfs_shutdown(void) |
253 | { |
254 | debugfs_remove(dentry: rootdir); |
255 | } |
256 | |