1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Debugfs support for hosts and cards |
4 | * |
5 | * Copyright (C) 2008 Atmel Corporation |
6 | */ |
7 | #include <linux/moduleparam.h> |
8 | #include <linux/export.h> |
9 | #include <linux/debugfs.h> |
10 | #include <linux/fs.h> |
11 | #include <linux/seq_file.h> |
12 | #include <linux/slab.h> |
13 | #include <linux/stat.h> |
14 | #include <linux/fault-inject.h> |
15 | #include <linux/time.h> |
16 | |
17 | #include <linux/mmc/card.h> |
18 | #include <linux/mmc/host.h> |
19 | #include <linux/mmc/mmc.h> |
20 | #include <linux/mmc/sd.h> |
21 | |
22 | #include "core.h" |
23 | #include "card.h" |
24 | #include "host.h" |
25 | #include "mmc_ops.h" |
26 | |
27 | #ifdef CONFIG_FAIL_MMC_REQUEST |
28 | |
29 | static DECLARE_FAULT_ATTR(fail_default_attr); |
30 | static char *fail_request; |
31 | module_param(fail_request, charp, 0); |
32 | MODULE_PARM_DESC(fail_request, "default fault injection attributes" ); |
33 | |
34 | #endif /* CONFIG_FAIL_MMC_REQUEST */ |
35 | |
36 | /* The debugfs functions are optimized away when CONFIG_DEBUG_FS isn't set. */ |
37 | static int mmc_ios_show(struct seq_file *s, void *data) |
38 | { |
39 | static const char *vdd_str[] = { |
40 | [8] = "2.0" , |
41 | [9] = "2.1" , |
42 | [10] = "2.2" , |
43 | [11] = "2.3" , |
44 | [12] = "2.4" , |
45 | [13] = "2.5" , |
46 | [14] = "2.6" , |
47 | [15] = "2.7" , |
48 | [16] = "2.8" , |
49 | [17] = "2.9" , |
50 | [18] = "3.0" , |
51 | [19] = "3.1" , |
52 | [20] = "3.2" , |
53 | [21] = "3.3" , |
54 | [22] = "3.4" , |
55 | [23] = "3.5" , |
56 | [24] = "3.6" , |
57 | }; |
58 | struct mmc_host *host = s->private; |
59 | struct mmc_ios *ios = &host->ios; |
60 | const char *str; |
61 | |
62 | seq_printf(m: s, fmt: "clock:\t\t%u Hz\n" , ios->clock); |
63 | if (host->actual_clock) |
64 | seq_printf(m: s, fmt: "actual clock:\t%u Hz\n" , host->actual_clock); |
65 | seq_printf(m: s, fmt: "vdd:\t\t%u " , ios->vdd); |
66 | if ((1 << ios->vdd) & MMC_VDD_165_195) |
67 | seq_printf(m: s, fmt: "(1.65 - 1.95 V)\n" ); |
68 | else if (ios->vdd < (ARRAY_SIZE(vdd_str) - 1) |
69 | && vdd_str[ios->vdd] && vdd_str[ios->vdd + 1]) |
70 | seq_printf(m: s, fmt: "(%s ~ %s V)\n" , vdd_str[ios->vdd], |
71 | vdd_str[ios->vdd + 1]); |
72 | else |
73 | seq_printf(m: s, fmt: "(invalid)\n" ); |
74 | |
75 | switch (ios->bus_mode) { |
76 | case MMC_BUSMODE_OPENDRAIN: |
77 | str = "open drain" ; |
78 | break; |
79 | case MMC_BUSMODE_PUSHPULL: |
80 | str = "push-pull" ; |
81 | break; |
82 | default: |
83 | str = "invalid" ; |
84 | break; |
85 | } |
86 | seq_printf(m: s, fmt: "bus mode:\t%u (%s)\n" , ios->bus_mode, str); |
87 | |
88 | switch (ios->chip_select) { |
89 | case MMC_CS_DONTCARE: |
90 | str = "don't care" ; |
91 | break; |
92 | case MMC_CS_HIGH: |
93 | str = "active high" ; |
94 | break; |
95 | case MMC_CS_LOW: |
96 | str = "active low" ; |
97 | break; |
98 | default: |
99 | str = "invalid" ; |
100 | break; |
101 | } |
102 | seq_printf(m: s, fmt: "chip select:\t%u (%s)\n" , ios->chip_select, str); |
103 | |
104 | switch (ios->power_mode) { |
105 | case MMC_POWER_OFF: |
106 | str = "off" ; |
107 | break; |
108 | case MMC_POWER_UP: |
109 | str = "up" ; |
110 | break; |
111 | case MMC_POWER_ON: |
112 | str = "on" ; |
113 | break; |
114 | default: |
115 | str = "invalid" ; |
116 | break; |
117 | } |
118 | seq_printf(m: s, fmt: "power mode:\t%u (%s)\n" , ios->power_mode, str); |
119 | seq_printf(m: s, fmt: "bus width:\t%u (%u bits)\n" , |
120 | ios->bus_width, 1 << ios->bus_width); |
121 | |
122 | switch (ios->timing) { |
123 | case MMC_TIMING_LEGACY: |
124 | str = "legacy" ; |
125 | break; |
126 | case MMC_TIMING_MMC_HS: |
127 | str = "mmc high-speed" ; |
128 | break; |
129 | case MMC_TIMING_SD_HS: |
130 | str = "sd high-speed" ; |
131 | break; |
132 | case MMC_TIMING_UHS_SDR12: |
133 | str = "sd uhs SDR12" ; |
134 | break; |
135 | case MMC_TIMING_UHS_SDR25: |
136 | str = "sd uhs SDR25" ; |
137 | break; |
138 | case MMC_TIMING_UHS_SDR50: |
139 | str = "sd uhs SDR50" ; |
140 | break; |
141 | case MMC_TIMING_UHS_SDR104: |
142 | str = "sd uhs SDR104" ; |
143 | break; |
144 | case MMC_TIMING_UHS_DDR50: |
145 | str = "sd uhs DDR50" ; |
146 | break; |
147 | case MMC_TIMING_MMC_DDR52: |
148 | str = "mmc DDR52" ; |
149 | break; |
150 | case MMC_TIMING_MMC_HS200: |
151 | str = "mmc HS200" ; |
152 | break; |
153 | case MMC_TIMING_MMC_HS400: |
154 | str = mmc_card_hs400es(card: host->card) ? |
155 | "mmc HS400 enhanced strobe" : "mmc HS400" ; |
156 | break; |
157 | default: |
158 | str = "invalid" ; |
159 | break; |
160 | } |
161 | seq_printf(m: s, fmt: "timing spec:\t%u (%s)\n" , ios->timing, str); |
162 | |
163 | switch (ios->signal_voltage) { |
164 | case MMC_SIGNAL_VOLTAGE_330: |
165 | str = "3.30 V" ; |
166 | break; |
167 | case MMC_SIGNAL_VOLTAGE_180: |
168 | str = "1.80 V" ; |
169 | break; |
170 | case MMC_SIGNAL_VOLTAGE_120: |
171 | str = "1.20 V" ; |
172 | break; |
173 | default: |
174 | str = "invalid" ; |
175 | break; |
176 | } |
177 | seq_printf(m: s, fmt: "signal voltage:\t%u (%s)\n" , ios->signal_voltage, str); |
178 | |
179 | switch (ios->drv_type) { |
180 | case MMC_SET_DRIVER_TYPE_A: |
181 | str = "driver type A" ; |
182 | break; |
183 | case MMC_SET_DRIVER_TYPE_B: |
184 | str = "driver type B" ; |
185 | break; |
186 | case MMC_SET_DRIVER_TYPE_C: |
187 | str = "driver type C" ; |
188 | break; |
189 | case MMC_SET_DRIVER_TYPE_D: |
190 | str = "driver type D" ; |
191 | break; |
192 | default: |
193 | str = "invalid" ; |
194 | break; |
195 | } |
196 | seq_printf(m: s, fmt: "driver type:\t%u (%s)\n" , ios->drv_type, str); |
197 | |
198 | return 0; |
199 | } |
200 | DEFINE_SHOW_ATTRIBUTE(mmc_ios); |
201 | |
202 | static int mmc_clock_opt_get(void *data, u64 *val) |
203 | { |
204 | struct mmc_host *host = data; |
205 | |
206 | *val = host->ios.clock; |
207 | |
208 | return 0; |
209 | } |
210 | |
211 | static int mmc_clock_opt_set(void *data, u64 val) |
212 | { |
213 | struct mmc_host *host = data; |
214 | |
215 | /* We need this check due to input value is u64 */ |
216 | if (val != 0 && (val > host->f_max || val < host->f_min)) |
217 | return -EINVAL; |
218 | |
219 | mmc_claim_host(host); |
220 | mmc_set_clock(host, hz: (unsigned int) val); |
221 | mmc_release_host(host); |
222 | |
223 | return 0; |
224 | } |
225 | |
226 | DEFINE_DEBUGFS_ATTRIBUTE(mmc_clock_fops, mmc_clock_opt_get, mmc_clock_opt_set, |
227 | "%llu\n" ); |
228 | |
229 | static int mmc_err_state_get(void *data, u64 *val) |
230 | { |
231 | struct mmc_host *host = data; |
232 | int i; |
233 | |
234 | if (!host) |
235 | return -EINVAL; |
236 | |
237 | *val = 0; |
238 | for (i = 0; i < MMC_ERR_MAX; i++) { |
239 | if (host->err_stats[i]) { |
240 | *val = 1; |
241 | break; |
242 | } |
243 | } |
244 | |
245 | return 0; |
246 | } |
247 | |
248 | DEFINE_DEBUGFS_ATTRIBUTE(mmc_err_state, mmc_err_state_get, NULL, "%llu\n" ); |
249 | |
250 | static int mmc_err_stats_show(struct seq_file *file, void *data) |
251 | { |
252 | struct mmc_host *host = file->private; |
253 | const char *desc[MMC_ERR_MAX] = { |
254 | [MMC_ERR_CMD_TIMEOUT] = "Command Timeout Occurred" , |
255 | [MMC_ERR_CMD_CRC] = "Command CRC Errors Occurred" , |
256 | [MMC_ERR_DAT_TIMEOUT] = "Data Timeout Occurred" , |
257 | [MMC_ERR_DAT_CRC] = "Data CRC Errors Occurred" , |
258 | [MMC_ERR_AUTO_CMD] = "Auto-Cmd Error Occurred" , |
259 | [MMC_ERR_ADMA] = "ADMA Error Occurred" , |
260 | [MMC_ERR_TUNING] = "Tuning Error Occurred" , |
261 | [MMC_ERR_CMDQ_RED] = "CMDQ RED Errors" , |
262 | [MMC_ERR_CMDQ_GCE] = "CMDQ GCE Errors" , |
263 | [MMC_ERR_CMDQ_ICCE] = "CMDQ ICCE Errors" , |
264 | [MMC_ERR_REQ_TIMEOUT] = "Request Timedout" , |
265 | [MMC_ERR_CMDQ_REQ_TIMEOUT] = "CMDQ Request Timedout" , |
266 | [MMC_ERR_ICE_CFG] = "ICE Config Errors" , |
267 | [MMC_ERR_CTRL_TIMEOUT] = "Controller Timedout errors" , |
268 | [MMC_ERR_UNEXPECTED_IRQ] = "Unexpected IRQ errors" , |
269 | }; |
270 | int i; |
271 | |
272 | for (i = 0; i < MMC_ERR_MAX; i++) { |
273 | if (desc[i]) |
274 | seq_printf(m: file, fmt: "# %s:\t %d\n" , |
275 | desc[i], host->err_stats[i]); |
276 | } |
277 | |
278 | return 0; |
279 | } |
280 | |
281 | static int mmc_err_stats_open(struct inode *inode, struct file *file) |
282 | { |
283 | return single_open(file, mmc_err_stats_show, inode->i_private); |
284 | } |
285 | |
286 | static ssize_t mmc_err_stats_write(struct file *filp, const char __user *ubuf, |
287 | size_t cnt, loff_t *ppos) |
288 | { |
289 | struct mmc_host *host = filp->f_mapping->host->i_private; |
290 | |
291 | pr_debug("%s: Resetting MMC error statistics\n" , __func__); |
292 | memset(host->err_stats, 0, sizeof(host->err_stats)); |
293 | |
294 | return cnt; |
295 | } |
296 | |
297 | static const struct file_operations mmc_err_stats_fops = { |
298 | .open = mmc_err_stats_open, |
299 | .read = seq_read, |
300 | .write = mmc_err_stats_write, |
301 | .release = single_release, |
302 | }; |
303 | |
304 | static int mmc_caps_get(void *data, u64 *val) |
305 | { |
306 | *val = *(u32 *)data; |
307 | return 0; |
308 | } |
309 | |
310 | static int mmc_caps_set(void *data, u64 val) |
311 | { |
312 | u32 *caps = data; |
313 | u32 diff = *caps ^ val; |
314 | u32 allowed = MMC_CAP_AGGRESSIVE_PM | |
315 | MMC_CAP_SD_HIGHSPEED | |
316 | MMC_CAP_MMC_HIGHSPEED | |
317 | MMC_CAP_UHS | |
318 | MMC_CAP_DDR; |
319 | |
320 | if (diff & ~allowed) |
321 | return -EINVAL; |
322 | |
323 | *caps = val; |
324 | |
325 | return 0; |
326 | } |
327 | |
328 | static int mmc_caps2_set(void *data, u64 val) |
329 | { |
330 | u32 allowed = MMC_CAP2_HSX00_1_8V | MMC_CAP2_HSX00_1_2V; |
331 | u32 *caps = data; |
332 | u32 diff = *caps ^ val; |
333 | |
334 | if (diff & ~allowed) |
335 | return -EINVAL; |
336 | |
337 | *caps = val; |
338 | |
339 | return 0; |
340 | } |
341 | |
342 | DEFINE_DEBUGFS_ATTRIBUTE(mmc_caps_fops, mmc_caps_get, mmc_caps_set, |
343 | "0x%08llx\n" ); |
344 | DEFINE_DEBUGFS_ATTRIBUTE(mmc_caps2_fops, mmc_caps_get, mmc_caps2_set, |
345 | "0x%08llx\n" ); |
346 | |
347 | void mmc_add_host_debugfs(struct mmc_host *host) |
348 | { |
349 | struct dentry *root; |
350 | |
351 | root = debugfs_create_dir(mmc_hostname(host), NULL); |
352 | host->debugfs_root = root; |
353 | |
354 | debugfs_create_file(name: "ios" , S_IRUSR, parent: root, data: host, fops: &mmc_ios_fops); |
355 | debugfs_create_file(name: "caps" , mode: 0600, parent: root, data: &host->caps, fops: &mmc_caps_fops); |
356 | debugfs_create_file(name: "caps2" , mode: 0600, parent: root, data: &host->caps2, |
357 | fops: &mmc_caps2_fops); |
358 | debugfs_create_file_unsafe(name: "clock" , S_IRUSR | S_IWUSR, parent: root, data: host, |
359 | fops: &mmc_clock_fops); |
360 | |
361 | debugfs_create_file_unsafe(name: "err_state" , mode: 0600, parent: root, data: host, |
362 | fops: &mmc_err_state); |
363 | debugfs_create_file(name: "err_stats" , mode: 0600, parent: root, data: host, |
364 | fops: &mmc_err_stats_fops); |
365 | |
366 | #ifdef CONFIG_FAIL_MMC_REQUEST |
367 | if (fail_request) |
368 | setup_fault_attr(attr: &fail_default_attr, str: fail_request); |
369 | host->fail_mmc_request = fail_default_attr; |
370 | fault_create_debugfs_attr(name: "fail_mmc_request" , parent: root, |
371 | attr: &host->fail_mmc_request); |
372 | #endif |
373 | } |
374 | |
375 | void mmc_remove_host_debugfs(struct mmc_host *host) |
376 | { |
377 | debugfs_remove_recursive(dentry: host->debugfs_root); |
378 | } |
379 | |
380 | void mmc_add_card_debugfs(struct mmc_card *card) |
381 | { |
382 | struct mmc_host *host = card->host; |
383 | struct dentry *root; |
384 | |
385 | if (!host->debugfs_root) |
386 | return; |
387 | |
388 | root = debugfs_create_dir(mmc_card_id(card), parent: host->debugfs_root); |
389 | card->debugfs_root = root; |
390 | |
391 | debugfs_create_x32(name: "state" , S_IRUSR, parent: root, value: &card->state); |
392 | } |
393 | |
394 | void mmc_remove_card_debugfs(struct mmc_card *card) |
395 | { |
396 | debugfs_remove_recursive(dentry: card->debugfs_root); |
397 | card->debugfs_root = NULL; |
398 | } |
399 | |