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; |
82 | const struct flash_info *info = nor->info; |
83 | char buf[16], *str; |
84 | 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" , SPINOR_OP_CHIP_ERASE, buf); |
142 | } |
143 | |
144 | seq_puts(m: s, s: "\nsector map\n" ); |
145 | seq_puts(m: s, s: " region (in hex) | erase mask | flags\n" ); |
146 | seq_puts(m: s, s: " ------------------+------------+----------\n" ); |
147 | for (region = erase_map->regions; |
148 | region; |
149 | region = spi_nor_region_next(region)) { |
150 | u64 start = region->offset & ~SNOR_ERASE_FLAGS_MASK; |
151 | u64 flags = region->offset & SNOR_ERASE_FLAGS_MASK; |
152 | u64 end = start + region->size - 1; |
153 | |
154 | seq_printf(m: s, fmt: " %08llx-%08llx | [%c%c%c%c] | %s\n" , |
155 | start, end, |
156 | flags & BIT(0) ? '0' : ' ', |
157 | flags & BIT(1) ? '1' : ' ', |
158 | flags & BIT(2) ? '2' : ' ', |
159 | flags & BIT(3) ? '3' : ' ', |
160 | flags & SNOR_OVERLAID_REGION ? "overlaid" : "" ); |
161 | } |
162 | |
163 | return 0; |
164 | } |
165 | DEFINE_SHOW_ATTRIBUTE(spi_nor_params); |
166 | |
167 | static void spi_nor_print_read_cmd(struct seq_file *s, u32 cap, |
168 | struct spi_nor_read_command *cmd) |
169 | { |
170 | seq_printf(m: s, fmt: " %s%s\n" , spi_nor_protocol_name(proto: cmd->proto), |
171 | cap == SNOR_HWCAPS_READ_FAST ? " (fast read)" : "" ); |
172 | seq_printf(m: s, fmt: " opcode\t0x%02x\n" , cmd->opcode); |
173 | seq_printf(m: s, fmt: " mode cycles\t%u\n" , cmd->num_mode_clocks); |
174 | seq_printf(m: s, fmt: " dummy cycles\t%u\n" , cmd->num_wait_states); |
175 | } |
176 | |
177 | static void spi_nor_print_pp_cmd(struct seq_file *s, |
178 | struct spi_nor_pp_command *cmd) |
179 | { |
180 | seq_printf(m: s, fmt: " %s\n" , spi_nor_protocol_name(proto: cmd->proto)); |
181 | seq_printf(m: s, fmt: " opcode\t0x%02x\n" , cmd->opcode); |
182 | } |
183 | |
184 | static int spi_nor_capabilities_show(struct seq_file *s, void *data) |
185 | { |
186 | struct spi_nor *nor = s->private; |
187 | struct spi_nor_flash_parameter *params = nor->params; |
188 | u32 hwcaps = params->hwcaps.mask; |
189 | int i, cmd; |
190 | |
191 | seq_puts(m: s, s: "Supported read modes by the flash\n" ); |
192 | for (i = 0; i < sizeof(hwcaps) * BITS_PER_BYTE; i++) { |
193 | if (!(hwcaps & BIT(i))) |
194 | continue; |
195 | |
196 | cmd = spi_nor_hwcaps_read2cmd(BIT(i)); |
197 | if (cmd < 0) |
198 | continue; |
199 | |
200 | spi_nor_print_read_cmd(s, BIT(i), cmd: ¶ms->reads[cmd]); |
201 | hwcaps &= ~BIT(i); |
202 | } |
203 | |
204 | seq_puts(m: s, s: "\nSupported page program modes by the flash\n" ); |
205 | for (i = 0; i < sizeof(hwcaps) * BITS_PER_BYTE; i++) { |
206 | if (!(hwcaps & BIT(i))) |
207 | continue; |
208 | |
209 | cmd = spi_nor_hwcaps_pp2cmd(BIT(i)); |
210 | if (cmd < 0) |
211 | continue; |
212 | |
213 | spi_nor_print_pp_cmd(s, cmd: ¶ms->page_programs[cmd]); |
214 | hwcaps &= ~BIT(i); |
215 | } |
216 | |
217 | if (hwcaps) |
218 | seq_printf(m: s, fmt: "\nunknown hwcaps 0x%x\n" , hwcaps); |
219 | |
220 | return 0; |
221 | } |
222 | DEFINE_SHOW_ATTRIBUTE(spi_nor_capabilities); |
223 | |
224 | static void spi_nor_debugfs_unregister(void *data) |
225 | { |
226 | struct spi_nor *nor = data; |
227 | |
228 | debugfs_remove(dentry: nor->debugfs_root); |
229 | nor->debugfs_root = NULL; |
230 | } |
231 | |
232 | static struct dentry *rootdir; |
233 | |
234 | void spi_nor_debugfs_register(struct spi_nor *nor) |
235 | { |
236 | struct dentry *d; |
237 | int ret; |
238 | |
239 | if (!rootdir) |
240 | rootdir = debugfs_create_dir(SPI_NOR_DEBUGFS_ROOT, NULL); |
241 | |
242 | ret = devm_add_action(nor->dev, spi_nor_debugfs_unregister, nor); |
243 | if (ret) |
244 | return; |
245 | |
246 | d = debugfs_create_dir(name: dev_name(dev: nor->dev), parent: rootdir); |
247 | nor->debugfs_root = d; |
248 | |
249 | debugfs_create_file(name: "params" , mode: 0444, parent: d, data: nor, fops: &spi_nor_params_fops); |
250 | debugfs_create_file(name: "capabilities" , mode: 0444, parent: d, data: nor, |
251 | fops: &spi_nor_capabilities_fops); |
252 | } |
253 | |
254 | void spi_nor_debugfs_shutdown(void) |
255 | { |
256 | debugfs_remove(dentry: rootdir); |
257 | } |
258 | |