diff mbox

[v2] mmc-utils: Merge the lsmmc tool into mmc-utils

Message ID d59ece1ff066b7fd7c8b538a560f726751fb1f9a.1454401105.git.baolin.wang@linaro.org
State New
Headers show

Commit Message

(Exiting) Baolin Wang Feb. 2, 2016, 8:21 a.m. UTC
The lsmmc tools contains an extensive parser of the CID, CSD, SCR, EXT_CSD
registers from userspace. The utility works as-is and uses sysfs to read
the register values.

The original code is created by Sebastian Rasmussen and still lives in
private git. It need to be merged into mmc-utils repository, which is
convenient for testing MMC device from userspace.

Signed-off-by: Baolin Wang <baolin.wang@linaro.org>

---
 Makefile   |    1 +
 lsmmc.c    | 4138 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 lsmmc.ids  |   27 +
 mmc.c      |   18 +
 mmc_cmds.h |    3 +
 5 files changed, 4187 insertions(+)
 create mode 100644 lsmmc.c
 create mode 100644 lsmmc.ids

-- 
1.7.9.5

--
To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Comments

Linus Walleij Feb. 2, 2016, 9:39 p.m. UTC | #1
On Tue, Feb 2, 2016 at 9:21 AM, Baolin Wang <baolin.wang@linaro.org> wrote:

> The lsmmc tools contains an extensive parser of the CID, CSD, SCR, EXT_CSD

> registers from userspace. The utility works as-is and uses sysfs to read

> the register values.

>

> The original code is created by Sebastian Rasmussen and still lives in

> private git. It need to be merged into mmc-utils repository, which is

> convenient for testing MMC device from userspace.

>

> Signed-off-by: Baolin Wang <baolin.wang@linaro.org>


Nitpicky formalia:

The SoB-chain should represent the delivery path of the code, so I think
it should be:

Signed-off-by: Sebastian Rasmussen <sebras@gmail.com>

Signed-off-by: Chris Ball <chris@printf.net>

Signed-off-by: Baolin Wang <baolin.wang@linaro.org>


..and then when Ulf merges it it get his SoB too.

Then the Author: has no strong rule by usually should
represent who wrote the majority of the code, so unless you've changes
more than 50% of the code, consider
git commit --amend --author="Sebastian Rasmussen <sebras@gmail.com>"
for this commit.

I usually don't care a lot about the latter but since it's a big and initial
commit I think it matters. If it's too diverse authors and unclear
contributor distribution, a mailing list can be used as author, c.f.
commit a8c21a5451d831e67b7a6fb910f9ca8bc7b43554

Thanks for working on this!

Yours,
Linus Walleij
--
To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
(Exiting) Baolin Wang Feb. 3, 2016, 2:26 a.m. UTC | #2
On 3 February 2016 at 05:39, Linus Walleij <linus.walleij@linaro.org> wrote:
> On Tue, Feb 2, 2016 at 9:21 AM, Baolin Wang <baolin.wang@linaro.org> wrote:

>

>> The lsmmc tools contains an extensive parser of the CID, CSD, SCR, EXT_CSD

>> registers from userspace. The utility works as-is and uses sysfs to read

>> the register values.

>>

>> The original code is created by Sebastian Rasmussen and still lives in

>> private git. It need to be merged into mmc-utils repository, which is

>> convenient for testing MMC device from userspace.

>>

>> Signed-off-by: Baolin Wang <baolin.wang@linaro.org>

>

> Nitpicky formalia:

>

> The SoB-chain should represent the delivery path of the code, so I think

> it should be:

>

> Signed-off-by: Sebastian Rasmussen <sebras@gmail.com>

> Signed-off-by: Chris Ball <chris@printf.net>

> Signed-off-by: Baolin Wang <baolin.wang@linaro.org>

>

> ..and then when Ulf merges it it get his SoB too.

>

> Then the Author: has no strong rule by usually should

> represent who wrote the majority of the code, so unless you've changes

> more than 50% of the code, consider

> git commit --amend --author="Sebastian Rasmussen <sebras@gmail.com>"

> for this commit.

>

> I usually don't care a lot about the latter but since it's a big and initial

> commit I think it matters. If it's too diverse authors and unclear

> contributor distribution, a mailing list can be used as author, c.f.

> commit a8c21a5451d831e67b7a6fb910f9ca8bc7b43554


OK, make sense. Thanks.

>

> Thanks for working on this!

>

> Yours,

> Linus Walleij




-- 
Baolin.wang
Best Regards
--
To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
(Exiting) Baolin Wang Feb. 3, 2016, 2:34 a.m. UTC | #3
On 3 February 2016 at 06:04, Sebastian Rasmussen <sebras@gmail.com> wrote:
>> The original code is created by Sebastian Rasmussen and still lives in

>> private git.

>

> Actually the company at which I developed the code has since been

> dissolved so I believe that the only place where this code currently

> lives is as an attachment in the mail archive of linux-mmc.


Got it.

>

>> It need to be merged into mmc-utils repository, which is

>> convenient for testing MMC device from userspace.

>

> I fully agree. Though maybe the subcommands csd/cid/scr read all should be

> brought to conform to the other subcommands in terms of using e.g.

> CHECK() and checking for errors using perror(). Basically I would want

> the new subcommands to be indistiguishable from the existing commands.

> Even if I developed the code at my previous employer it just looks

> nasty if it is tacked on without being a proper part of the existing

> code. :)

>

> Also I'm a bit wary of that lsmmc.ids database as it is currently not

> installed if you do "make install" as it probably should be. Maybe it

> is better to hardcode this internally in the program and avoid having

> to deal with installation?


OK. So it can remove the '-f' parameter.

>

> Either way, I'm happy to continue working getting this into mmc-utils.


That's great. I think you can continue doing it. Thanks a lot.

>

>  / Sebastian




-- 
Baolin.wang
Best Regards
--
To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/Makefile b/Makefile
index 0533be3..5e4eb1c 100644
--- a/Makefile
+++ b/Makefile
@@ -4,6 +4,7 @@  CFLAGS ?= -g -O2
 objects = \
 	mmc.o \
 	mmc_cmds.o \
+	lsmmc.o \
 	3rdparty/hmac_sha/hmac_sha2.o \
 	3rdparty/hmac_sha/sha2.o
 
diff --git a/lsmmc.c b/lsmmc.c
new file mode 100644
index 0000000..3161222
--- /dev/null
+++ b/lsmmc.c
@@ -0,0 +1,4138 @@ 
+/*
+ * Copyright (C) ST-Ericsson SA 2010-2011
+ * Author: Sebastian Rasmussen <sebastian.rasmussen@stericsson.com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *   2. Redistributions in binary form must reproduce the above
+ *      copyright notice, this list of conditions and the following
+ *      disclaimer in the documentation and/or other materials
+ *      provided with the distribution.
+ *   3. Neither the name of the ST-Ericsson SA nor the names of its
+ *      contributors may be used to endorse or promote products
+ *      derived from this software without specific prior written
+ *      permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define MASKTOBIT0(high)	\
+	((high >= 0) ? ((1ull << ((high) + 1ull)) - 1ull) : 0ull)
+#define MASK(high, low)		(MASKTOBIT0(high) & ~MASKTOBIT0(low - 1))
+#define BITS(value, high, low)	(((value) & MASK((high), (low))) >> (low))
+
+struct config {
+	char *idsfile;
+	char *dir;
+	bool verbose;
+	int interfaces;
+	char **interface;
+	char **mmc_ids;
+	char **sd_ids;
+
+	char *type;
+	char *cid;
+	char *csd;
+	char *scr;
+	char *ext_csd;
+};
+
+enum REG_TYPE {
+	CID = 0,
+	CSD,
+	SCR,
+	EXT_CSD,
+};
+
+/* Command line parsing functions */
+void usage(void)
+{
+	printf("Usage: print mmc [-f idsfile] [-h] [-v] <device path ...>\n");
+	printf("\n");
+	printf("Options:\n");
+	printf("\t-h\tShow this help.\n");
+	printf("\t-v\tEnable verbose mode.\n");
+	printf("\t-f\tPath to manufacturer ID database.\n");
+}
+
+int parse_opts(int argc, char **argv, struct config *config)
+{
+	int c;
+
+	while ((c = getopt(argc, argv, "f:hv")) != -1) {
+		switch (c) {
+		case 'f':
+			config->idsfile = strdup(optarg);
+			break;
+		case 'h':
+			usage();
+			return -1;
+		case 'v':
+			config->verbose = true;
+			break;
+		case '?':
+			fprintf(stderr,
+				"Unknown option '%c' encountered.\n\n", c);
+			usage();
+			return -1;
+		case ':':
+			fprintf(stderr,
+				"Argument for option '%c' missing.\n\n", c);
+			usage();
+			return -1;
+		default:
+			fprintf(stderr,
+				"Unimplemented option '%c' encountered.\n", c);
+			break;
+		}
+	}
+
+	if (optind >= argc) {
+		fprintf(stderr, "Expected mmc interface arguments.\n\n");
+		usage();
+		return -1;
+	}
+
+	config->dir = strdup(argv[optind]);
+	return 0;
+}
+
+int parse_id(struct config *config, char *line)
+{
+	char *type, *id, *manufacturer, *colon, *remaining;
+	unsigned long value;
+	char **ids;
+
+	if (!line) {
+		fprintf(stderr, "MMC/SD id parse error, empty line");
+		return -1;
+	}
+
+	type = line;
+	colon = strchr(type, ':');
+	if (!colon) {
+		fprintf(stderr, "MMC/SD id parse error, no delimiter found");
+		return -1;
+	}
+	*colon = '\0';
+
+	if (!strcmp(type, "mmc")) {
+		ids = config->mmc_ids;
+	} else if (!strcmp(type, "sd")) {
+		ids = config->sd_ids;
+	} else {
+		fprintf(stderr, "MMC/SD id parse error, unknown type: '%s'.\n",
+			type);
+		return -1;
+	}
+
+	id = colon + 1;
+	colon = strchr(id, ':');
+	if (colon)
+		*colon = '\0';
+
+	value = strtoul(id, &remaining, 16);
+	if (strlen(remaining) > 0) {
+		fprintf(stderr,
+			"MMC/SD id parse error, unintelligible id: '%s'.\n",
+			remaining);
+		return -1;
+	}
+
+	if (value == ULONG_MAX) {
+		fprintf(stderr,
+			"MMC/SD id parse error, id out of range: '%s'.\n", id);
+		return -1;
+	}
+
+	manufacturer = colon + 1;
+
+	if (ids[value]) {
+		fprintf(stderr, "Duplicate entries: type='%s', id='0x%1x'.\n",
+			type, *id);
+		return -1;
+	}
+
+	ids[value] = strdup(manufacturer);
+
+	return 0;
+}
+
+int parse_ids(struct config *config)
+{
+	char *line = NULL;
+	FILE *f;
+	int c;
+
+	if (!config->idsfile)
+		return 0;
+
+	f = fopen(config->idsfile, "r");
+	if (!f) {
+		fprintf(stderr, "Unable to open MMC/SD id file '%s'.\n",
+			config->idsfile);
+		return -1;
+	}
+
+	do {
+		c = fgetc(f);
+
+		if (c == '\n' || c == '\r') {
+			if (line) {
+				parse_id(config, line);
+				free(line);
+				line = NULL;
+			}
+		} else if (c != EOF) {
+			char nc[2] = { c, '\0' };
+			int len = line ? strlen(line) : 0;
+
+			line = (char *)realloc(line, len + sizeof(char) * 2);
+			if (!line)
+				goto realloc_fail;
+
+			memcpy(&line[len], nc, 2);
+		}
+	} while (c != EOF);
+
+realloc_fail:
+	if (fclose(f)) {
+		fprintf(stderr, "Unable to close MMC/SD id file '%s'.\n",
+			config->idsfile);
+		return -1;
+	}
+
+	if (line)
+		free(line);
+
+	return 0;
+}
+
+/* MMC/SD file parsing functions */
+char *read_file(char *name)
+{
+	char *preparsed;
+	char line[4096];
+	FILE *f;
+
+	f = fopen(name, "r");
+	if (!f) {
+		fprintf(stderr, "Could not open MMC/SD file '%s'.\n", name);
+		return NULL;
+	}
+
+	preparsed = fgets(line, sizeof(line), f);
+	if (!preparsed) {
+		if (ferror(f))
+			fprintf(stderr, "Could not read MMC/SD file '%s'.\n",
+				name);
+		else
+			fprintf(stderr,
+				"Could not read data from MMC/SD file '%s'.\n",
+				name);
+
+		if (fclose(f))
+			fprintf(stderr, "Could not close MMC/SD file '%s'.\n",
+				name);
+		return NULL;
+	}
+
+	if (fclose(f)) {
+		fprintf(stderr, "Could not close MMC/SD file '%s'.\n", name);
+		return NULL;
+	}
+
+	line[sizeof(line) - 1] = '\0';
+
+	while (isspace(line[strlen(line) - 1]))
+		line[strlen(line) - 1] = '\0';
+
+	while (isspace(line[0]))
+		strncpy(&line[0], &line[1], sizeof(line));
+
+	return strdup(line);
+}
+
+/* Hexadecimal string parsing functions */
+char *to_binstr(char *hexstr)
+{
+	char *bindigits[] = {
+		"0000", "0001", "0010", "0011", "0100", "0101", "0110", "0111",
+		"1000", "1001", "1010", "1011", "1100", "1101", "1110", "1111",
+	};
+	char *binstr;
+
+	binstr = calloc(strlen(hexstr) * 4 + 1, sizeof(char));
+
+	while (hexstr && *hexstr != '\0') {
+		if (!isxdigit(*hexstr))
+			return NULL;
+
+		if (isdigit(*hexstr))
+			strcat(binstr, bindigits[*hexstr - '0']);
+		else if (islower(*hexstr))
+			strcat(binstr, bindigits[*hexstr - 'a']);
+		else
+			strcat(binstr, bindigits[*hexstr - 'A']);
+
+		hexstr++;
+	}
+
+	return binstr;
+}
+
+void bin_to_unsigned(unsigned int *u, char *binstr, int width)
+{
+	*u = 0;
+	assert(width <= 32);
+
+	while (binstr && *binstr != '\0' && width > 0) {
+		*u <<= 1;
+		*u |= *binstr == '0' ? 0 : 1;
+
+		binstr++;
+		width--;
+	}
+}
+
+void bin_to_ascii(char *a, char *binstr, int width)
+{
+	assert(width % 8 == 0);
+	*a = '\0';
+
+	while (binstr && *binstr != '\0' && width > 0) {
+		unsigned int u;
+		char c[2] = { '\0', '\0' };
+		char *s = &c[0];
+
+		bin_to_unsigned(&u, binstr, 8);
+		c[0] = u;
+
+		strcat(a, s);
+		binstr += 8;
+		width -= 8;
+	}
+}
+
+void parse_bin(char *hexstr, char *fmt, ...)
+{
+	va_list args;
+	char *origstr;
+	char *binstr;
+	unsigned long width = 0;
+
+	binstr = to_binstr(hexstr);
+	origstr = binstr;
+
+	va_start(args, fmt);
+
+	while (binstr && fmt && *fmt != '\0') {
+		if (isdigit(*fmt)) {
+			char *rest;
+
+			errno = 0;
+			width = strtoul(fmt, &rest, 10);
+			if (width == ULONG_MAX && errno != 0)
+				fprintf(stderr, "strtoul()");
+			fmt = rest;
+		} else if (*fmt == 'u') {
+			unsigned int *u = va_arg(args, unsigned int *);
+
+			if (u)
+				bin_to_unsigned(u, binstr, width);
+			binstr += width;
+			width = 0;
+			fmt++;
+		} else if (*fmt == 'r') {
+			binstr += width;
+			width = 0;
+			fmt++;
+		} else if (*fmt == 'a') {
+			char *c = va_arg(args, char *);
+
+			if (c)
+				bin_to_ascii(c, binstr, width);
+			binstr += width;
+			width = 0;
+			fmt++;
+		} else {
+			fmt++;
+		}
+	}
+
+	va_end(args);
+	free(origstr);
+}
+
+/* MMC/SD information parsing functions */
+void print_sd_cid(struct config *config, char *cid)
+{
+	static const char *months[] = {
+		"jan", "feb", "mar", "apr", "may", "jun",
+		"jul", "aug", "sep", "oct", "nov", "dec",
+		"invalid0", "invalid1", "invalid2", "invalid3",
+	};
+	unsigned int mid;
+	char oid[3];
+	char pnm[6];
+	unsigned int prv_major;
+	unsigned int prv_minor;
+	unsigned int psn;
+	unsigned int mdt_month;
+	unsigned int mdt_year;
+	unsigned int crc;
+
+	parse_bin(cid, "8u16a40a4u4u32u4r8u4u7u1r",
+		&mid, &oid[0], &pnm[0], &prv_major, &prv_minor, &psn,
+		&mdt_year, &mdt_month, &crc);
+
+	oid[2] = '\0';
+	pnm[5] = '\0';
+
+	if (config->verbose) {
+		printf("======SD/CID======\n");
+
+		printf("\tMID: 0x%02x (", mid);
+		if (config->sd_ids[mid])
+			printf("%s)\n", config->sd_ids[mid]);
+		else
+			printf("Unlisted)\n");
+
+		printf("\tOID: %s\n", oid);
+		printf("\tPNM: %s\n", pnm);
+		printf("\tPRV: 0x%01x%01x ", prv_major, prv_minor);
+		printf("(%d.%d)\n", prv_major, prv_minor);
+		printf("\tPSN: 0x%08x\n", psn);
+		printf("\tMDT: 0x%02x%01x %d %s\n", mdt_year, mdt_month,
+		       2000 + mdt_year, months[mdt_month]);
+		printf("\tCRC: 0x%02x\n", crc);
+	} else {
+		if (config->sd_ids[mid])
+			printf("manufacturer: '%s' '%s'\n",
+			       config->sd_ids[mid], oid);
+		else
+			printf("manufacturer: 'Unlisted' '%s'\n", oid);
+
+		printf("product: '%s' %d.%d\n", pnm, prv_major, prv_minor);
+		printf("serial: 0x%08x\n", psn);
+		printf("manfacturing date: %d %s\n", 2000 + mdt_year,
+		       months[mdt_month]);
+	}
+}
+
+void print_mmc_cid(struct config *config, char *cid)
+{
+	static const char *months[] = {
+		"jan", "feb", "mar", "apr", "may", "jun",
+		"jul", "aug", "sep", "oct", "nov", "dec",
+		"invalid0", "invalid1", "invalid2", "invalid3",
+	};
+	unsigned int mid;
+	unsigned int cbx;
+	unsigned int oid;
+	char pnm[7];
+	unsigned int prv_major;
+	unsigned int prv_minor;
+	unsigned int psn;
+	unsigned int mdt_month;
+	unsigned int mdt_year;
+	unsigned int crc;
+
+	parse_bin(cid, "8u6r2u8u48a4u4u32u4u4u7u1r",
+		&mid, &cbx, &oid, &pnm[0], &psn, &prv_major, &prv_minor,
+		&mdt_year, &mdt_month, &crc);
+
+	pnm[6] = '\0';
+
+	if (config->verbose) {
+		printf("======MMC/CID======\n");
+
+		printf("\tMID: 0x%02x (", mid);
+		if (config->mmc_ids[mid])
+			printf("%s)\n", config->mmc_ids[mid]);
+		else
+			printf("Unlisted)\n");
+
+		printf("\tCBX: 0x%01x (", cbx);
+		switch (cbx) {
+		case 0:
+			printf("card)\n");
+			break;
+		case 1:
+			printf("BGA)\n");
+			break;
+		case 2:
+			printf("PoP)\n");
+			break;
+		case 3:
+			printf("reserved)\n");
+			break;
+		}
+
+		printf("\tOID: 0x%01x\n", oid);
+		printf("\tPNM: %s\n", pnm);
+		printf("\tPRV: 0x%01x%01x ", prv_major, prv_minor);
+		printf("(%d.%d)\n", prv_major, prv_minor);
+		printf("\tPSN: 0x%08x\n", psn);
+		printf("\tMDT: 0x%01x%01x %d %s\n", mdt_month, mdt_year,
+		       1997 + mdt_year, months[mdt_month]);
+		printf("\tCRC: 0x%02x\n", crc);
+	} else {
+		if (config->mmc_ids[mid])
+			printf("manufacturer: '%s' '%c'\n",
+			       config->mmc_ids[mid], oid);
+		else
+			printf("manufacturer: 'Unlisted' '%c'\n", oid);
+
+		printf("product: '%s' %d.%d\n", pnm, prv_major, prv_minor);
+		printf("serial: 0x%08x\n", psn);
+		printf("manfacturing date: %d %s\n", 1997 + mdt_year,
+		       months[mdt_month]);
+	}
+}
+
+void print_sd_csd(struct config *config, char *csd)
+{
+	unsigned int csd_structure;
+	unsigned int taac_timevalue;
+	unsigned int taac_timeunit;
+	unsigned int nsac;
+	unsigned int tran_speed_timevalue;
+	unsigned int tran_speed_transferrateunit;
+	unsigned int ccc;
+	unsigned int read_bl_len;
+	unsigned int read_bl_partial;
+	unsigned int write_blk_misalign;
+	unsigned int read_blk_misalign;
+	unsigned int dsr_imp;
+	unsigned int c_size;
+	unsigned int vdd_r_curr_min;
+	unsigned int vdd_r_curr_max;
+	unsigned int vdd_w_curr_min;
+	unsigned int vdd_w_curr_max;
+	unsigned int c_size_mult;
+	unsigned int erase_blk_en;
+	unsigned int sector_size;
+	unsigned int wp_grp_size;
+	unsigned int wp_grp_enable;
+	unsigned int r2w_factor;
+	unsigned int write_bl_len;
+	unsigned int write_bl_partial;
+	unsigned int file_format_grp;
+	unsigned int copy;
+	unsigned int perm_write_protect;
+	unsigned int tmp_write_protect;
+	unsigned int file_format;
+	unsigned int crc;
+	unsigned int taac;
+	unsigned int tran_speed;
+
+	parse_bin(csd, "2u", &csd_structure);
+
+	if (csd_structure == 0) {
+		parse_bin(csd, "2u6r1r4u3u8u1r4u3u12u4u1u1u1u1u2r12u3u3u3u3u3u"
+			  "1u7u7u1u2r3u4u1u5r1u1u1u1u2u2r7u1r",
+			  NULL, &taac_timevalue, &taac_timeunit, &nsac,
+			  &tran_speed_timevalue,
+			  &tran_speed_transferrateunit, &ccc,
+			  &read_bl_len, &read_bl_partial,
+			  &write_blk_misalign, &read_blk_misalign,
+			  &dsr_imp, &c_size, &vdd_r_curr_min,
+			  &vdd_r_curr_max, &vdd_w_curr_min,
+			  &vdd_w_curr_max, &c_size_mult, &erase_blk_en,
+			  &sector_size, &wp_grp_size, &wp_grp_enable,
+			  &r2w_factor, &write_bl_len, &write_bl_partial,
+			  &file_format_grp, &copy, &perm_write_protect,
+			  &tmp_write_protect, &file_format, &crc);
+	} else if (csd_structure == 1) {
+		parse_bin(csd, "2u6r1r4u3u8u1r4u3u12u4u1u1u1u1u6r22u1r1u7u7u1u"
+			  "2r3u4u1u5r1u1u1u1u2u2r7u1r",
+			  NULL, &taac_timevalue, &taac_timeunit, &nsac,
+			  &tran_speed_timevalue,
+			  &tran_speed_transferrateunit, &ccc,
+			  &read_bl_len, &read_bl_partial,
+			  &write_blk_misalign, &read_blk_misalign,
+			  &dsr_imp, &c_size, &erase_blk_en, &sector_size,
+			  &wp_grp_size, &wp_grp_enable, &r2w_factor,
+			  &write_bl_len, &write_bl_partial,
+			  &file_format_grp, &copy, &perm_write_protect,
+			  &tmp_write_protect, &file_format, &crc);
+
+		vdd_r_curr_min = 0;
+		c_size_mult = 0;
+	} else {
+		printf("Unknown CSD structure: 0x%1x\n", csd_structure);
+		return;
+	}
+
+	taac = taac_timevalue << 3 | taac_timeunit;
+	tran_speed = tran_speed_timevalue << 3 | tran_speed_transferrateunit;
+
+	if (config->verbose) {
+		float value;
+		unsigned long long blocks = 0;
+		int block_size = 0;
+		unsigned long long memory_capacity;
+
+		printf("======SD/CSD======\n");
+
+		printf("\tCSD_STRUCTURE: %d\n", csd_structure);
+		printf("\tTAAC: 0x%02x (", taac);
+
+		switch (taac_timevalue) {
+		case 0x0:
+			value = 0.0f;
+			break;
+		case 0x1:
+			value = 1.0f;
+			break;
+		case 0x2:
+			value = 1.2f;
+			break;
+		case 0x3:
+			value = 1.3f;
+			break;
+		case 0x4:
+			value = 1.5f;
+			break;
+		case 0x5:
+			value = 2.0f;
+			break;
+		case 0x6:
+			value = 2.5f;
+			break;
+		case 0x7:
+			value = 3.0f;
+			break;
+		case 0x8:
+			value = 3.5f;
+			break;
+		case 0x9:
+			value = 4.0f;
+			break;
+		case 0xa:
+			value = 4.5f;
+			break;
+		case 0xb:
+			value = 5.0f;
+			break;
+		case 0xc:
+			value = 5.5f;
+			break;
+		case 0xd:
+			value = 6.0f;
+			break;
+		case 0xe:
+			value = 7.0f;
+			break;
+		case 0xf:
+			value = 8.0f;
+			break;
+		default:
+			value = 0.0f;
+			break;
+		}
+
+		switch (taac_timeunit) {
+		case 0x0:
+			printf("%.2fns)\n", value * 1.0f);
+			break;
+		case 0x1:
+			printf("%.2fns)\n", value * 10.0f);
+			break;
+		case 0x2:
+			printf("%.2fns)\n", value * 100.0f);
+			break;
+		case 0x3:
+			printf("%.2fus)\n", value * 1.0f);
+			break;
+		case 0x4:
+			printf("%.2fus)\n", value * 10.0f);
+			break;
+		case 0x5:
+			printf("%.2fus)\n", value * 100.0f);
+			break;
+		case 0x6:
+			printf("%.2fms)\n", value * 1.0f);
+			break;
+		case 0x7:
+			printf("%.2fms)\n", value * 10.0f);
+			break;
+		}
+
+		if (csd_structure == 1 && taac != 0x0e)
+			printf("Warn: Invalid TAAC (should be 0x0e)\n");
+
+		printf("\tNSAC: %d clocks\n", nsac);
+		if (csd_structure == 1 && nsac != 0x00)
+			printf("Warn: Invalid NSAC (should be 0x00)\n");
+
+		printf("\tTRAN_SPEED: 0x%02x (", tran_speed);
+		switch (tran_speed_timevalue) {
+		case 0x0:
+			value = 0.0f;
+			break;
+		case 0x1:
+			value = 1.0f;
+			break;
+		case 0x2:
+			value = 1.2f;
+			break;
+		case 0x3:
+			value = 1.3f;
+			break;
+		case 0x4:
+			value = 1.5f;
+			break;
+		case 0x5:
+			value = 2.0f;
+			break;
+		case 0x6:
+			value = 2.5f;
+			break;
+		case 0x7:
+			value = 3.0f;
+			break;
+		case 0x8:
+			value = 3.5f;
+			break;
+		case 0x9:
+			value = 4.0f;
+			break;
+		case 0xa:
+			value = 4.5f;
+			break;
+		case 0xb:
+			value = 5.0f;
+			break;
+		case 0xc:
+			value = 5.5f;
+			break;
+		case 0xd:
+			value = 6.0f;
+			break;
+		case 0xe:
+			value = 7.0f;
+			break;
+		case 0xf:
+			value = 8.0f;
+			break;
+		default:
+			value = 0.0f;
+			break;
+		}
+
+		switch (tran_speed_transferrateunit) {
+		case 0x0:
+			printf("%.2fkbit/s)\n", value * 100.0f);
+			break;
+		case 0x1:
+			printf("%.2fMbit/s)\n", value * 1.0f);
+			break;
+		case 0x2:
+			printf("%.2fMbit/s)\n", value * 10.0f);
+			break;
+		case 0x3:
+			printf("%.2fMbit/s)\n", value * 100.0f);
+			break;
+		default:
+			printf("reserved)\n");
+			break;
+		}
+		if (csd_structure == 0 &&
+		    (tran_speed != 0x32 && tran_speed != 0x5a))
+			printf("Warn: Invalid TRAN_SPEED "
+			       "(should be 0x32 or 0x5a)\n");
+		if (csd_structure == 1 && tran_speed != 0x32 &&
+		    tran_speed != 0x5a && tran_speed != 0x0b &&
+		    tran_speed != 0x2b)
+			printf("Warn: Invalid TRAN_SPEED "
+			       "(should be 0x32, 0x5a, 0x0b or 0x2b\n");
+
+		printf("\tCCC: 0x%03x (class: ", ccc);
+		if (ccc & 0x800)
+			printf("11, ");
+		if (ccc & 0x400)
+			printf("10, ");
+		if (ccc & 0x200)
+			printf("9, ");
+		if (ccc & 0x100)
+			printf("8, ");
+		if (ccc & 0x080)
+			printf("7, ");
+		if (ccc & 0x040)
+			printf("6, ");
+		if (ccc & 0x020)
+			printf("5, ");
+		if (ccc & 0x010)
+			printf("4, ");
+		if (ccc & 0x008)
+			printf("3, ");
+		if (ccc & 0x004)
+			printf("2, ");
+		if (ccc & 0x002)
+			printf("1, ");
+		if (ccc & 0x001)
+			printf("0, ");
+		printf("  )\n");
+
+		if (csd_structure == 0 &&
+		    (ccc != 0x5b5 && ccc != 0x7b5 && ccc != 0x5f5))
+			printf("Warn: Invalid CCC (should be 0x5b5, "
+			       "0x7b5 or 0x5f5)\n");
+		else if (csd_structure == 1 && ccc != 0x5b5 && ccc != 0x7b5)
+			printf("Warn: Invalid CCC (should be 0x5b5 or 0x7b5)\n");
+
+		printf("\tREAD_BL_LEN: 0x%01x (", read_bl_len);
+		switch (read_bl_len) {
+		case 0x9:
+			printf("512 bytes)\n");
+			break;
+		case 0xa:
+			printf("1024 bytes)\n");
+			break;
+		case 0xb:
+			printf("2048 bytes)\n");
+			break;
+		default:
+			printf("reserved bytes)\n");
+			break;
+		}
+
+		if (csd_structure == 1 && read_bl_len != 0x9)
+			printf("Warn: Invalid READ_BL_LEN (should be 0x9)\n");
+
+		printf("\tREAD_BL_PARTIAL: 0x%01x\n", read_bl_partial);
+		if (csd_structure == 0 && read_bl_partial != 0x01)
+			printf("Warn: Invalid READ_BL_PARTIAL (should be 0x01)\n");
+		else if (csd_structure == 1 && read_bl_partial != 0x00)
+			printf("Warn: Invalid READ_BL_PARTIAL (should be 0x00)\n");
+
+		printf("\tWRITE_BLK_MISALIGN: 0x%01x\n", write_blk_misalign);
+		if (csd_structure == 1 && write_blk_misalign != 0x00)
+			printf("Warn: Invalid WRITE_BLK_MISALIGN (should be 0x00)\n");
+
+		printf("\tREAD_BLK_MISALIGN: 0x%01x\n", read_blk_misalign);
+		if (csd_structure == 1 && read_blk_misalign != 0x00)
+			printf("Warn: Invalid READ_BLK_MISALIGN (should be 0x00)\n");
+
+		printf("\tDSR_IMP: 0x%01x\n", dsr_imp);
+
+		if (csd_structure == 0) {
+			int mult;
+			int blocknr;
+			int block_len;
+
+			printf("\tC_SIZE: 0x%03x\n", c_size);
+			printf("\tVDD_R_CURR_MIN: 0x%01x (", vdd_r_curr_min);
+			switch (vdd_r_curr_min) {
+			case 0x0:
+				printf("0.5mA)\n");
+				break;
+			case 0x1:
+				printf("1mA)\n");
+				break;
+			case 0x2:
+				printf("5mA)\n");
+				break;
+			case 0x3:
+				printf("10mA)\n");
+				break;
+			case 0x4:
+				printf("25mA)\n");
+				break;
+			case 0x5:
+				printf("35mA)\n");
+				break;
+			case 0x6:
+				printf("60mA)\n");
+				break;
+			case 0x7:
+				printf("100mA)\n");
+				break;
+			}
+
+			printf("\tVDD_R_CURR_MAX: 0x%01x (", vdd_r_curr_max);
+			switch (vdd_r_curr_max) {
+			case 0x0:
+				printf("1mA)\n");
+				break;
+			case 0x1:
+				printf("5mA)\n");
+				break;
+			case 0x2:
+				printf("10mA)\n");
+				break;
+			case 0x3:
+				printf("25mA)\n");
+				break;
+			case 0x4:
+				printf("35mA)\n");
+				break;
+			case 0x5:
+				printf("45mA)\n");
+				break;
+			case 0x6:
+				printf("80mA)\n");
+				break;
+			case 0x7:
+				printf("200mA)\n");
+				break;
+			}
+
+			printf("\tVDD_W_CURR_MIN: 0x%01x (", vdd_w_curr_min);
+			switch (vdd_w_curr_min) {
+			case 0x0:
+				printf("0.5mA)\n");
+				break;
+			case 0x1:
+				printf("1mA)\n");
+				break;
+			case 0x2:
+				printf("5mA)\n");
+				break;
+			case 0x3:
+				printf("10mA)\n");
+				break;
+			case 0x4:
+				printf("25mA)\n");
+				break;
+			case 0x5:
+				printf("35mA)\n");
+				break;
+			case 0x6:
+				printf("60mA)\n");
+				break;
+			case 0x7:
+				printf("100mA)\n");
+				break;
+			}
+
+			printf("\tVDD_W_CURR_MAX: 0x%01x (", vdd_w_curr_max);
+			switch (vdd_w_curr_max) {
+			case 0x0:
+				printf("1mA)\n");
+				break;
+			case 0x1:
+				printf("5mA)\n");
+				break;
+			case 0x2:
+				printf("10mA)\n");
+				break;
+			case 0x3:
+				printf("25mA)\n");
+				break;
+			case 0x4:
+				printf("35mA)\n");
+				break;
+			case 0x5:
+				printf("45mA)\n");
+				break;
+			case 0x6:
+				printf("80mA)\n");
+				break;
+			case 0x7:
+				printf("200mA)\n");
+				break;
+			}
+
+			printf("\tC_SIZE_MULT: 0x%01x\n", c_size_mult);
+
+			mult = 1 << (c_size_mult + 2);
+			blocknr = (c_size + 1) * mult;
+			block_len = 1 << read_bl_len;
+			blocks = blocknr;
+			block_size = block_len;
+		} else if (csd_structure == 1) {
+			printf("\tC_SIZE: 0x%06x\n", c_size);
+
+			printf("\tERASE_BLK_EN: 0x%01x\n", erase_blk_en);
+			if (erase_blk_en != 0x01)
+				printf("Warn: Invalid ERASE_BLK_EN (should be 0x01)\n");
+
+			printf("\tSECTOR_SIZE: 0x%02x (Erasable sector: %d blocks)\n",
+			       sector_size, sector_size + 1);
+			if (sector_size != 0x7f)
+				printf("Warn: Invalid SECTOR_SIZE (should be 0x7f)\n");
+
+			printf("\tWP_GRP_SIZE: 0x%02x (Write protect group: %d blocks)\n",
+			       wp_grp_size, wp_grp_size + 1);
+			if (wp_grp_size != 0x00)
+				printf("Warn: Invalid WP_GRP_SIZE (should be 0x00)\n");
+
+			printf("\tWP_GRP_ENABLE: 0x%01x\n", wp_grp_enable);
+			if (wp_grp_enable != 0x00)
+				printf("Warn: Invalid WP_GRP_ENABLE (should be 0x00)\n");
+
+			printf("\tR2W_FACTOR: 0x%01x (Write %d times read)\n",
+			       r2w_factor, r2w_factor);
+			if (r2w_factor != 0x02)
+				printf("Warn: Invalid R2W_FACTOR (should be 0x02)\n");
+
+			printf("\tWRITE_BL_LEN: 0x%01x (", write_bl_len);
+			switch (write_bl_len) {
+			case 9:
+				printf("512 bytes)\n");
+				break;
+			case 10:
+				printf("1024 bytes)\n");
+				break;
+			case 11:
+				printf("2048 bytes)\n");
+				break;
+			default:
+				printf("reserved)\n");
+				break;
+			}
+
+			if (write_bl_len != 0x09)
+				printf("Warn: Invalid WRITE_BL_LEN (should be 0x09)\n");
+
+			printf("\tWRITE_BL_PARTIAL: 0x%01x\n", write_bl_partial);
+			if (write_bl_partial != 0x00)
+				printf("Warn: Invalid WRITE_BL_PARTIAL (should be 0x00)\n");
+
+			printf("\tFILE_FORMAT_GRP: 0x%01x\n", file_format_grp);
+			if (file_format_grp != 0x00)
+				printf("Warn: Invalid FILE_FORMAT_GRP (should be 0x00)\n");
+
+			printf("\tCOPY: 0x%01x\n", copy);
+			printf("\tPERM_WRITE_PROTECT: 0x%01x\n",
+			       perm_write_protect);
+			printf("\tTMP_WRITE_PROTECT: 0x%01x\n",
+			       tmp_write_protect);
+			printf("\tFILE_FORMAT: 0x%01x (",
+			       file_format);
+
+			if (file_format_grp == 1) {
+				printf("reserved)\n");
+			} else {
+				switch (file_format) {
+				case 0:
+					printf("partition table)\n");
+					break;
+				case 1:
+					printf("no partition table)\n");
+					break;
+				case 2:
+					printf("Universal File Format)\n");
+					break;
+				case 3:
+					printf("Others/unknown)\n");
+					break;
+				}
+			}
+
+			if (file_format != 0x00)
+				printf("Warn: Invalid FILE_FORMAT (should be 0x00)\n");
+
+			printf("\tCRC: 0x%01x\n", crc);
+
+			memory_capacity = (c_size + 1) * 512ull * 1024ull;
+			block_size = 512;
+			blocks = memory_capacity / block_size;
+		}
+
+		memory_capacity = blocks * block_size;
+
+		printf("\tCAPACITY: ");
+		if (memory_capacity / (1024ull * 1024ull * 1024ull) > 0)
+			printf("%.2fGbyte",
+			       memory_capacity / (1024.0 * 1024.0 * 1024.0));
+		else if (memory_capacity / (1024ull * 1024ull) > 0)
+			printf("%.2fMbyte", memory_capacity / (1024.0 * 1024.0));
+		else if (memory_capacity / (1024ull) > 0)
+			printf("%.2fKbyte", memory_capacity / (1024.0));
+		else
+			printf("%.2fbyte", memory_capacity * 1.0);
+
+		printf(" (%lld bytes, %lld sectors, %d bytes each)\n",
+		       memory_capacity, blocks, block_size);
+	} else {
+		unsigned long long blocks = 0;
+		int block_size = 0;
+		unsigned long long memory_capacity;
+
+		printf("card classes: ");
+		if (ccc & 0x800)
+			printf("11 extension, ");
+		if (ccc & 0x400)
+			printf("10 switch, ");
+		if (ccc & 0x200)
+			printf("9 I/O mode, ");
+		if (ccc & 0x100)
+			printf("8 application specific, ");
+		if (ccc & 0x080)
+			printf("7 lock card, ");
+		if (ccc & 0x040)
+			printf("6 write protection, ");
+		if (ccc & 0x020)
+			printf("5 erase, ");
+		if (ccc & 0x010)
+			printf("4 block write, ");
+		if (ccc & 0x008)
+			printf("3 reserved, ");
+		if (ccc & 0x004)
+			printf("2 block read, ");
+		if (ccc & 0x002)
+			printf("1 reserved, ");
+		if (ccc & 0x001)
+			printf("0 basic, ");
+		printf("\b\b\n");
+
+		if (csd_structure == 0) {
+			int mult;
+			int blocknr;
+			int block_len;
+
+			mult = 1 << (c_size_mult + 2);
+			blocknr = (c_size + 1) * mult;
+			block_len = 1 << read_bl_len;
+			blocks = blocknr;
+			block_size = block_len;
+		} else if (csd_structure == 1) {
+			memory_capacity = (c_size + 1) * 512ull * 1024ull;
+			block_size = 512;
+			blocks = memory_capacity / block_size;
+		}
+
+		memory_capacity = blocks * block_size;
+
+		printf("capacity: ");
+		if (memory_capacity / (1024ull * 1024ull * 1024ull) > 0)
+			printf("%.2fGbyte",
+			       memory_capacity / (1024.0 * 1024.0 * 1024.0));
+		else if (memory_capacity / (1024ull * 1024ull) > 0)
+			printf("%.2fMbyte", memory_capacity / (1024.0 * 1024.0));
+		else if (memory_capacity / (1024ull) > 0)
+			printf("%.2fKbyte", memory_capacity / (1024.0));
+		else
+			printf("%.2fbyte", memory_capacity * 1.0);
+
+		printf(" (%lld bytes, %lld sectors, %d bytes each)\n",
+		       memory_capacity, blocks, block_size);
+	}
+}
+
+void print_mmc_csd(struct config *config, char *csd)
+{
+	unsigned int csd_structure;
+	unsigned int spec_vers;
+	unsigned int taac_timevalue;
+	unsigned int taac_timeunit;
+	unsigned int nsac;
+	unsigned int tran_speed_timevalue;
+	unsigned int tran_speed_transferrateunit;
+	unsigned int ccc;
+	unsigned int read_bl_len;
+	unsigned int read_bl_partial;
+	unsigned int write_blk_misalign;
+	unsigned int read_blk_misalign;
+	unsigned int dsr_imp;
+	unsigned int c_size;
+	unsigned int vdd_r_curr_min;
+	unsigned int vdd_r_curr_max;
+	unsigned int vdd_w_curr_min;
+	unsigned int vdd_w_curr_max;
+	unsigned int c_size_mult;
+	unsigned int erase_grp_size;
+	unsigned int erase_grp_mult;
+	unsigned int wp_grp_size;
+	unsigned int wp_grp_enable;
+	unsigned int default_ecc;
+	unsigned int r2w_factor;
+	unsigned int write_bl_len;
+	unsigned int write_bl_partial;
+	unsigned int content_prot_app;
+	unsigned int file_format_grp;
+	unsigned int copy;
+	unsigned int perm_write_protect;
+	unsigned int tmp_write_protect;
+	unsigned int file_format;
+	unsigned int ecc;
+	unsigned int crc;
+	unsigned int taac;
+	unsigned int tran_speed;
+
+	parse_bin(csd, "2u4u2r1r4u3u8u1r4u3u12u4u1u1u1u1u2r12u3u3u3u3u3u"
+		  "5u5u5u1u2u3u4u1u4r1u1u1u1u1u2u2u7u1r",
+		  &csd_structure, &spec_vers, &taac_timevalue,
+		  &taac_timeunit, &nsac, &tran_speed_timevalue,
+		  &tran_speed_transferrateunit, &ccc, &read_bl_len,
+		  &read_bl_partial, &write_blk_misalign,
+		  &read_blk_misalign, &dsr_imp, &c_size,
+		  &vdd_r_curr_min, &vdd_r_curr_max,
+		  &vdd_w_curr_min, &vdd_w_curr_max, &c_size_mult,
+		  &erase_grp_size, &erase_grp_mult, &wp_grp_size,
+		  &wp_grp_enable, &default_ecc, &r2w_factor,
+		  &write_bl_len, &write_bl_partial, &content_prot_app,
+		  &file_format_grp, &copy, &perm_write_protect,
+		  &tmp_write_protect, &file_format, &ecc, &crc);
+
+	taac = taac_timevalue << 3 | taac_timeunit;
+	tran_speed = tran_speed_timevalue << 3 | tran_speed_transferrateunit;
+
+	if (config->verbose) {
+		float value;
+		int mult;
+		int blocknr;
+		int block_len;
+		unsigned long long blocks = 0;
+		int block_size = 0;
+		unsigned long long memory_capacity;
+
+		printf("======MMC/CSD======\n");
+
+		printf("\tCSD_STRUCTURE: 0x%01x (", csd_structure);
+		switch (csd_structure) {
+		case 0x0:
+			printf("v1.0)\n");
+			break;
+		case 0x1:
+			printf("v1.1)\n");
+			break;
+		case 0x2:
+			printf("v1.2)\n");
+			break;
+		case 0x3:
+			printf("version in ext_csd)\n");
+			break;
+		}
+
+		printf("\tSPEC_VERS: 0x%01x (", spec_vers);
+		switch (spec_vers) {
+		case 0x0:
+			printf("v1.0-v1.2)\n");
+			break;
+		case 0x1:
+			printf("v1.4)\n");
+			break;
+		case 0x2:
+			printf("v2.0-v2.2)\n");
+			break;
+		case 0x3:
+			printf("v3.1-v3.31)\n");
+			break;
+		case 0x4:
+			printf("v4.0-v4.3)\n");
+			break;
+		default:
+			printf("reserved)\n");
+			break;
+		}
+
+		printf("\tTAAC: 0x%02x (", taac);
+		switch (taac_timevalue) {
+		case 0x0:
+			value = 0.0f;
+			break;
+		case 0x1:
+			value = 1.0f;
+			break;
+		case 0x2:
+			value = 1.2f;
+			break;
+		case 0x3:
+			value = 1.3f;
+			break;
+		case 0x4:
+			value = 1.5f;
+			break;
+		case 0x5:
+			value = 2.0f;
+			break;
+		case 0x6:
+			value = 2.5f;
+			break;
+		case 0x7:
+			value = 3.0f;
+			break;
+		case 0x8:
+			value = 3.5f;
+			break;
+		case 0x9:
+			value = 4.0f;
+			break;
+		case 0xa:
+			value = 4.5f;
+			break;
+		case 0xb:
+			value = 5.0f;
+			break;
+		case 0xc:
+			value = 5.5f;
+			break;
+		case 0xd:
+			value = 6.0f;
+			break;
+		case 0xe:
+			value = 7.0f;
+			break;
+		case 0xf:
+			value = 8.0f;
+			break;
+		default:
+			value = 0.0f;
+			break;
+		}
+
+		switch (taac_timeunit) {
+		case 0x0:
+			printf("%.2fns)\n", value * 1.0f);
+			break;
+		case 0x1:
+			printf("%.2fns)\n", value * 10.0f);
+			break;
+		case 0x2:
+			printf("%.2fns)\n", value * 100.0f);
+			break;
+		case 0x3:
+			printf("%.2fus)\n", value * 1.0f);
+			break;
+		case 0x4:
+			printf("%.2fus)\n", value * 10.0f);
+			break;
+		case 0x5:
+			printf("%.2fus)\n", value * 100.0f);
+			break;
+		case 0x6:
+			printf("%.2fms)\n", value * 1.0f);
+			break;
+		case 0x7:
+			printf("%.2fms)\n", value * 10.0f);
+			break;
+		}
+
+		printf("\tNSAC: %d clocks\n", nsac);
+		printf("\tTRAN_SPEED: 0x%02x (", tran_speed);
+		switch (tran_speed_timevalue) {
+		case 0x0:
+			value = 0.0f;
+			break;
+		case 0x1:
+			value = 1.0f;
+			break;
+		case 0x2:
+			value = 1.2f;
+			break;
+		case 0x3:
+			value = 1.3f;
+			break;
+		case 0x4:
+			value = 1.5f;
+			break;
+		case 0x5:
+			value = 2.0f;
+			break;
+		case 0x6:
+			value = 2.6f;
+			break;
+		case 0x7:
+			value = 3.0f;
+			break;
+		case 0x8:
+			value = 3.5f;
+			break;
+		case 0x9:
+			value = 4.0f;
+			break;
+		case 0xa:
+			value = 4.5f;
+			break;
+		case 0xb:
+			value = 5.2f;
+			break;
+		case 0xc:
+			value = 5.5f;
+			break;
+		case 0xd:
+			value = 6.0f;
+			break;
+		case 0xe:
+			value = 7.0f;
+			break;
+		case 0xf:
+			value = 8.0f;
+			break;
+		default:
+			value = 0.0f;
+			break;
+		}
+
+		switch (tran_speed_transferrateunit) {
+		case 0x0:
+			printf("%.2fKHz/s)\n", value * 100.0f);
+			break;
+		case 0x1:
+			printf("%.2fMHz/s)\n", value * 1.0f);
+			break;
+		case 0x2:
+			printf("%.2fMHz/s)\n", value * 10.0f);
+			break;
+		case 0x3:
+			printf("%.2fMHz/s)\n", value * 100.0f);
+			break;
+		default:
+			printf("reserved)\n");
+			break;
+		}
+
+		printf("\tCCC: 0x%03x (class: ", ccc);
+		if (ccc & 0x800)
+			printf("11, ");
+		if (ccc & 0x400)
+			printf("10, ");
+		if (ccc & 0x200)
+			printf("9, ");
+		if (ccc & 0x100)
+			printf("8, ");
+		if (ccc & 0x080)
+			printf("7, ");
+		if (ccc & 0x040)
+			printf("6, ");
+		if (ccc & 0x020)
+			printf("5, ");
+		if (ccc & 0x010)
+			printf("4, ");
+		if (ccc & 0x008)
+			printf("3, ");
+		if (ccc & 0x004)
+			printf("2, ");
+		if (ccc & 0x002)
+			printf("1, ");
+		if (ccc & 0x001)
+			printf("0, ");
+		printf("  )\n");
+
+		printf("\tREAD_BL_LEN: 0x%01x (", read_bl_len);
+		switch (read_bl_len) {
+		case 0x0:
+			printf("1 byte)\n");
+			break;
+		case 0x1:
+			printf("2 byte)\n");
+			break;
+		case 0x2:
+			printf("4 byte)\n");
+			break;
+		case 0x3:
+			printf("8 byte)\n");
+			break;
+		case 0x4:
+			printf("16 byte)\n");
+			break;
+		case 0x5:
+			printf("32 byte)\n");
+			break;
+		case 0x6:
+			printf("64 byte)\n");
+			break;
+		case 0x7:
+			printf("128 byte)\n");
+			break;
+		case 0x8:
+			printf("256 byte)\n");
+			break;
+		case 0x9:
+			printf("512 bytes)\n");
+			break;
+		case 0xa:
+			printf("1024 bytes)\n");
+			break;
+		case 0xb:
+			printf("2048 bytes)\n");
+			break;
+		case 0xc:
+			printf("4096 bytes)\n");
+			break;
+		case 0xd:
+			printf("8192 bytes)\n");
+			break;
+		case 0xe:
+			printf("16K bytes)\n");
+			break;
+		default:
+			printf("reserved bytes)\n");
+			break;
+		}
+
+		printf("\tREAD_BL_PARTIAL: 0x%01x (", read_bl_partial);
+		switch (read_bl_partial) {
+		case 0x0:
+			printf("only 512 byte and READ_BL_LEN block size)\n");
+			break;
+		case 0x1:
+			printf("less than READ_BL_LEN block size can be used)\n");
+			break;
+		}
+
+		printf("\tWRITE_BLK_MISALIGN: 0x%01x (", write_blk_misalign);
+		switch (write_blk_misalign) {
+		case 0x0:
+			printf("writes across block boundaries are invalid)\n");
+			break;
+		case 0x1:
+			printf("writes across block boundaries are allowed)\n");
+			break;
+		}
+
+		printf("\tREAD_BLK_MISALIGN: 0x%01x (", read_blk_misalign);
+		switch (read_blk_misalign) {
+		case 0x0:
+			printf("reads across block boundaries are invalid)\n");
+			break;
+		case 0x1:
+			printf("reads across block boundaries are allowed)\n");
+			break;
+		}
+
+		printf("\tDSR_IMP: 0x%01x (", dsr_imp);
+		switch (dsr_imp) {
+		case 0x0:
+			printf("configurable driver stage not available)\n");
+			break;
+		case 0x1:
+			printf("configurable driver state available)\n");
+			break;
+		}
+
+		printf("\tC_SIZE: 0x%03x\n", c_size);
+		printf("\tVDD_R_CURR_MIN: 0x%01x (", vdd_r_curr_min);
+		switch (vdd_r_curr_min) {
+		case 0x0:
+			printf("0.5mA)\n");
+			break;
+		case 0x1:
+			printf("1mA)\n");
+			break;
+		case 0x2:
+			printf("5mA)\n");
+			break;
+		case 0x3:
+			printf("10mA)\n");
+			break;
+		case 0x4:
+			printf("25mA)\n");
+			break;
+		case 0x5:
+			printf("35mA)\n");
+			break;
+		case 0x6:
+			printf("60mA)\n");
+			break;
+		case 0x7:
+			printf("100mA)\n");
+			break;
+		}
+
+		printf("\tVDD_R_CURR_MAX: 0x%01x (", vdd_r_curr_max);
+		switch (vdd_r_curr_max) {
+		case 0x0:
+			printf("1mA)\n");
+			break;
+		case 0x1:
+			printf("5mA)\n");
+			break;
+		case 0x2:
+			printf("10mA)\n");
+			break;
+		case 0x3:
+			printf("25mA)\n");
+			break;
+		case 0x4:
+			printf("35mA)\n");
+			break;
+		case 0x5:
+			printf("45mA)\n");
+			break;
+		case 0x6:
+			printf("80mA)\n");
+			break;
+		case 0x7:
+			printf("200mA)\n");
+			break;
+		}
+
+		printf("\tVDD_W_CURR_MIN: 0x%01x (", vdd_w_curr_min);
+		switch (vdd_w_curr_min) {
+		case 0x0:
+			printf("0.5mA)\n");
+			break;
+		case 0x1:
+			printf("1mA)\n");
+			break;
+		case 0x2:
+			printf("5mA)\n");
+			break;
+		case 0x3:
+			printf("10mA)\n");
+			break;
+		case 0x4:
+			printf("25mA)\n");
+			break;
+		case 0x5:
+			printf("35mA)\n");
+			break;
+		case 0x6:
+			printf("60mA)\n");
+			break;
+		case 0x7:
+			printf("100mA)\n");
+			break;
+		}
+
+		printf("\tVDD_W_CURR_MAX: 0x%01x (", vdd_w_curr_max);
+		switch (vdd_w_curr_max) {
+		case 0x0:
+			printf("1mA)\n");
+			break;
+		case 0x1:
+			printf("5mA)\n");
+			break;
+		case 0x2:
+			printf("10mA)\n");
+			break;
+		case 0x3:
+			printf("25mA)\n");
+			break;
+		case 0x4:
+			printf("35mA)\n");
+			break;
+		case 0x5:
+			printf("45mA)\n");
+			break;
+		case 0x6:
+			printf("80mA)\n");
+			break;
+		case 0x7:
+			printf("200mA)\n");
+			break;
+		}
+
+		printf("\tC_SIZE_MULT: 0x%01x\n", c_size_mult);
+		printf("\tERASE_GRP_SIZE: 0x%02x\n", erase_grp_size);
+		printf("\tERASE_GRP_MULT: 0x%02x (%d write blocks/erase group)\n",
+		       erase_grp_mult, (erase_grp_size + 1) *
+		       (erase_grp_mult + 1));
+		printf("\tWP_GRP_SIZE: 0x%02x (%d blocks/write protect group)\n",
+		       wp_grp_size, wp_grp_size + 1);
+		printf("\tWP_GRP_ENABLE: 0x%01x\n", wp_grp_enable);
+
+		printf("\tDEFAULT_ECC: 0x%01x (", default_ecc);
+		switch (default_ecc) {
+		case 0:
+			printf("none)\n");
+			break;
+		case 1:
+			printf("BCH)\n");
+			break;
+		default:
+			printf("reserved)\n");
+			break;
+		}
+
+		printf("\tR2W_FACTOR: 0x%01x (Write %d times read)\n",
+		       r2w_factor, r2w_factor);
+
+		printf("\tWRITE_BL_LEN: 0x%01x (", write_bl_len);
+		switch (write_bl_len) {
+		case 0x0:
+			printf("1 byte)\n");
+			break;
+		case 0x1:
+			printf("2 byte)\n");
+			break;
+		case 0x2:
+			printf("4 byte)\n");
+			break;
+		case 0x3:
+			printf("8 byte)\n");
+			break;
+		case 0x4:
+			printf("16 byte)\n");
+			break;
+		case 0x5:
+			printf("32 byte)\n");
+			break;
+		case 0x6:
+			printf("64 byte)\n");
+			break;
+		case 0x7:
+			printf("128 byte)\n");
+			break;
+		case 0x8:
+			printf("256 byte)\n");
+			break;
+		case 0x9:
+			printf("512 bytes)\n");
+			break;
+		case 0xa:
+			printf("1024 bytes)\n");
+			break;
+		case 0xb:
+			printf("2048 bytes)\n");
+			break;
+		case 0xc:
+			printf("4096 bytes)\n");
+			break;
+		case 0xd:
+			printf("8192 bytes)\n");
+			break;
+		case 0xe:
+			printf("16K bytes)\n");
+			break;
+		default:
+			printf("reserved bytes)\n");
+			break;
+		}
+
+		printf("\tWRITE_BL_PARTIAL: 0x%01x (", write_bl_partial);
+		switch (write_bl_partial) {
+		case 0x0:
+			printf("only 512 byte and WRITE_BL_LEN block size)\n");
+			break;
+		case 0x1:
+			printf("less than WRITE_BL_LEN block size can be used)\n");
+			break;
+		}
+
+		printf("\tCONTENT_PROT_APP: 0x%01x\n", content_prot_app);
+		printf("\tFILE_FORMAT_GRP: 0x%01x\n", file_format_grp);
+		if (file_format_grp != 0)
+			printf("Warn: Invalid FILE_FORMAT_GRP\n");
+
+		printf("\tCOPY: 0x%01x\n", copy);
+		printf("\tPERM_WRITE_PROTECT: 0x%01x\n", perm_write_protect);
+		printf("\tTMP_WRITE_PROTECT: 0x%01x\n", tmp_write_protect);
+		printf("\tFILE_FORMAT: 0x%01x (", file_format);
+		if (file_format != 0)
+			printf("Warn: Invalid FILE_FORMAT\n");
+
+		if (file_format_grp == 1) {
+			printf("reserved)\n");
+		} else {
+			switch (file_format) {
+			case 0:
+				printf("partition table)\n");
+				break;
+			case 1:
+				printf("no partition table)\n");
+				break;
+			case 2:
+				printf("Universal File Format)\n");
+				break;
+			case 3:
+				printf("Others/unknown)\n");
+				break;
+			}
+		}
+
+		printf("\tECC: 0x%01x (", ecc);
+		switch (ecc) {
+		case 0:
+			printf("none)\n");
+			break;
+		case 1:
+			printf("BCH(542,512))\n");
+			break;
+		default:
+			printf("reserved)\n");
+			break;
+		}
+
+		printf("\tCRC: 0x%01x\n", crc);
+
+		mult = 1 << (c_size_mult + 2);
+		blocknr = (c_size + 1) * mult;
+		block_len = 1 << read_bl_len;
+		blocks = blocknr;
+		block_size = block_len;
+
+		memory_capacity = blocks * block_size;
+
+		printf("\tCAPACITY: ");
+		if (memory_capacity / (1024ull * 1024ull * 1024ull) > 0)
+			printf("%.2fGbyte",
+			       memory_capacity / (1024.0 * 1024.0 * 1024.0));
+		else if (memory_capacity / (1024ull * 1024ull) > 0)
+			printf("%.2fMbyte", memory_capacity / (1024.0 * 1024.0));
+		else if (memory_capacity / (1024ull) > 0)
+			printf("%.2fKbyte", memory_capacity / (1024.0));
+		else
+			printf("%.2fbyte", memory_capacity * 1.0);
+
+		printf(" (%lld bytes, %lld sectors, %d bytes each)\n",
+		       memory_capacity, blocks, block_size);
+	} else {
+		int mult;
+		int blocknr;
+		int block_len;
+		unsigned long long blocks = 0;
+		int block_size = 0;
+		unsigned long long memory_capacity;
+
+		printf("version: ");
+		switch (spec_vers) {
+		case 0x0:
+			printf("MMC v1.0-v1.2\n");
+			break;
+		case 0x1:
+			printf("MMC v1.4\n");
+			break;
+		case 0x2:
+			printf("MMC v2.0-v2.2\n");
+			break;
+		case 0x3:
+			printf("MMC v3.1-v3.31\n");
+			break;
+		case 0x4:
+			printf("MMC v4.0-v4.3\n");
+			break;
+		default:
+			printf("reserved\n");
+			break;
+		}
+
+		printf("card classes: ");
+		if (ccc & 0x800)
+			printf("11, ");
+		if (ccc & 0x400)
+			printf("10, ");
+		if (ccc & 0x200)
+			printf("9, ");
+		if (ccc & 0x100)
+			printf("8, ");
+		if (ccc & 0x080)
+			printf("7, ");
+		if (ccc & 0x040)
+			printf("6, ");
+		if (ccc & 0x020)
+			printf("5, ");
+		if (ccc & 0x010)
+			printf("4, ");
+		if (ccc & 0x008)
+			printf("3, ");
+		if (ccc & 0x004)
+			printf("2, ");
+		if (ccc & 0x002)
+			printf("1, ");
+		if (ccc & 0x001)
+			printf("0, ");
+		printf("\b\b\n");
+
+		mult = 1 << (c_size_mult + 2);
+		blocknr = (c_size + 1) * mult;
+		block_len = 1 << read_bl_len;
+		blocks = blocknr;
+		block_size = block_len;
+
+		memory_capacity = blocks * block_size;
+
+		printf("capacity: ");
+		if (memory_capacity / (1024ull * 1024ull * 1024ull) > 0)
+			printf("%.2fGbyte",
+			       memory_capacity / (1024.0 * 1024.0 * 1024.0));
+		else if (memory_capacity / (1024ull * 1024ull) > 0)
+			printf("%.2fMbyte", memory_capacity / (1024.0 * 1024.0));
+		else if (memory_capacity / (1024ull) > 0)
+			printf("%.2fKbyte", memory_capacity / (1024.0));
+		else
+			printf("%.2fbyte", memory_capacity * 1.0);
+		printf(" (%lld bytes, %lld sectors, %d bytes each)\n",
+		       memory_capacity, blocks, block_size);
+	}
+}
+
+char *speed_class_speed(unsigned char id, bool ddr)
+{
+	if (ddr) {
+		switch (id) {
+		case 0x00: return "<4.8MB/s";
+		case 0x08: return " 4.8MB/s";
+		case 0x0a: return " 6.0MB/s";
+		case 0x0f: return " 9.0MB/s";
+		case 0x14: return "12.0MB/s";
+		case 0x1e: return "18.0MB/s";
+		case 0x28: return "24.0MB/s";
+		case 0x32: return "30.0MB/s";
+		case 0x3c: return "36.0MB/s";
+		case 0x46: return "42.0MB/s";
+		case 0x50: return "48.0MB/s";
+		case 0x64: return "60.0MB/s";
+		case 0x78: return "72.0MB/s";
+		case 0x8c: return "84.0MB/s";
+		case 0xa0: return "96.0MB/s";
+		default: return "??.?MB/s";
+		}
+	} else {
+		switch (id) {
+		case 0x00: return "<2.4MB/s";
+		case 0x08: return " 2.4MB/s";
+		case 0x0a: return " 3.0MB/s";
+		case 0x0f: return " 4.5MB/s";
+		case 0x14: return " 6.0MB/s";
+		case 0x1e: return " 9.0MB/s";
+		case 0x28: return "12.0MB/s";
+		case 0x32: return "15.0MB/s";
+		case 0x3c: return "18.0MB/s";
+		case 0x46: return "21.0MB/s";
+		case 0x50: return "24.0MB/s";
+		case 0x64: return "30.0MB/s";
+		case 0x78: return "36.0MB/s";
+		case 0x8c: return "42.0MB/s";
+		case 0xa0: return "48.0MB/s";
+		default: return "??.?MB/s";
+		}
+	}
+}
+
+char speed_class_name(unsigned char id)
+{
+	switch (id) {
+	case 0x00: return '?';
+	case 0x08: return 'A';
+	case 0x0a: return 'B';
+	case 0x0f: return 'C';
+	case 0x14: return 'D';
+	case 0x1e: return 'E';
+	case 0x28: return 'F';
+	case 0x32: return 'G';
+	case 0x3c: return 'H';
+	case 0x46: return 'J';
+	case 0x50: return 'K';
+	case 0x64: return 'M';
+	case 0x78: return 'O';
+	case 0x8c: return 'R';
+	case 0xa0: return 'T';
+	default: return '?';
+	}
+}
+
+char *power_class_consumption(unsigned int id, bool volt360)
+{
+	if (volt360) {
+		switch (id) {
+		case 0x0: return "100-200mA";
+		case 0x1: return "120-220mA";
+		case 0x2: return "150-250mA";
+		case 0x3: return "180-280mA";
+		case 0x4: return "200-300mA";
+		case 0x5: return "220-320mA";
+		case 0x6: return "250-350mA";
+		case 0x7: return "300-400mA";
+		case 0x8: return "350-450mA";
+		case 0x9: return "400-500mA";
+		case 0xa: return "450-550mA";
+		default: return "reserved";
+		}
+	} else {
+		switch (id) {
+		case 0x0: return "65-130mA";
+		case 0x1: return "70-140mA";
+		case 0x2: return "80-160mA";
+		case 0x3: return "90-180mA";
+		case 0x4: return "100-200mA";
+		case 0x5: return "120-220mA";
+		case 0x6: return "140-240mA";
+		case 0x7: return "160-260mA";
+		case 0x8: return "180-280mA";
+		case 0x9: return "200-300mA";
+		case 0xa: return "250-350mA";
+		default: return "reserved";
+		}
+	}
+}
+
+char *sleep_consumption(unsigned int id)
+{
+	switch (id) {
+	case 0x00: return "not defined";
+	case 0x01: return "2uA";
+	case 0x02: return "4uA";
+	case 0x03: return "8uA";
+	case 0x04: return "16uA";
+	case 0x05: return "32uA";
+	case 0x06: return "64uA";
+	case 0x07: return "128uA";
+	case 0x08: return "0.256mA";
+	case 0x09: return "0.512mA";
+	case 0x0a: return "1.024mA";
+	case 0x0b: return "2.048mA";
+	case 0x0c: return "4.096mA";
+	case 0x0d: return "8.192mA";
+	default: return "reserved";
+	}
+}
+
+void print_mmc_ext_csd(struct config *config, char *ext_csd)
+{
+	unsigned int s_cmd_set;
+	unsigned int hpi_features;
+	unsigned int bkops_support;
+	unsigned int bkops_status;
+	unsigned int correctly_prg_sectors_num;
+	unsigned int ini_timeout_ap;
+	unsigned int pwr_cl_ddr_52_360;
+	unsigned int pwr_cl_ddr_52_195;
+	unsigned int min_perf_ddr_w_8_52;
+	unsigned int min_perf_ddr_r_8_52;
+	unsigned int trim_mult;
+	unsigned int sec_feature_support;
+	unsigned int sec_erase_mult;
+	unsigned int sec_trim_mult;
+	unsigned int boot_info;
+	unsigned int boot_size_mult;
+	unsigned int acc_size;
+	unsigned int hc_erase_grp_size;
+	unsigned int erase_timeout_mult;
+	unsigned int rel_wr_sec_c;
+	unsigned int hc_wp_grp_size;
+	unsigned int s_c_vcc;
+	unsigned int s_c_vccq;
+	unsigned int s_a_timeout;
+	unsigned int sec_count;
+	unsigned int min_perf_w_8_52;
+	unsigned int min_perf_r_8_52;
+	unsigned int min_perf_w_8_26_4_52;
+	unsigned int min_perf_r_8_26_4_52;
+	unsigned int min_perf_w_4_26;
+	unsigned int min_perf_r_4_26;
+	unsigned int pwr_cl_26_360;
+	unsigned int pwr_cl_52_360;
+	unsigned int pwr_cl_26_195;
+	unsigned int pwr_cl_52_195;
+	unsigned int partition_switch_time;
+	unsigned int out_of_interrupt_time;
+	unsigned int card_type;
+	unsigned int csd_structure;
+	unsigned int ext_csd_rev;
+	unsigned int cmd_set;
+	unsigned int cmd_set_rev;
+	unsigned int power_class;
+	unsigned int hs_timing;
+	unsigned int bus_width;
+	unsigned int erased_mem_cont;
+	unsigned int partition_config;
+	unsigned int boot_config_prot;
+	unsigned int boot_bus_width;
+	unsigned int erase_group_def;
+	unsigned int boot_wp;
+	unsigned int user_wp;
+	unsigned int fw_config;
+	unsigned int rpmb_size_mult;
+	unsigned int wr_rel_set;
+	unsigned int wr_rel_param;
+	unsigned int bkops_start;
+	unsigned int bkops_en;
+	unsigned int rst_n_function;
+	unsigned int hpi_mgmt;
+	unsigned int partitioning_support;
+	unsigned int max_enh_size_mult;
+	unsigned int partitions_attribute;
+	unsigned int partition_setting_completed;
+	unsigned int gp_size_mult_gp0;
+	unsigned int gp_size_mult_gp1;
+	unsigned int gp_size_mult_gp2;
+	unsigned int gp_size_mult_gp3;
+	unsigned int enh_size_mult;
+	unsigned int enh_start_addr;
+	unsigned int sec_bad_blk_mgmnt;
+
+	parse_bin(ext_csd, "56r8u8u8u2040r8u32u8u8r8u8u16r8u8u8r8u8u8u8u8u8r"
+		  "8u8u8u8u8u8u8u8u8r8u8r32u8r8u8u8u8u8u8u8r8u8u8u8u8u8u8r8u8r"
+		  "8u8r8u8u8r8u8r8u8r8u8r8u8r8u8r8u8u8u8r8u8r8u8r8u8r8u8u8u8u8r"
+		  "8u8u8u8u8u24u8u8u12u12u12u12u24u32u8r8u1072r",
+		  &s_cmd_set, &hpi_features, &bkops_support,
+		  &bkops_status, &correctly_prg_sectors_num,
+		  &ini_timeout_ap, &pwr_cl_ddr_52_360, &pwr_cl_ddr_52_195,
+		  &min_perf_ddr_w_8_52, &min_perf_ddr_r_8_52,
+		  &trim_mult, &sec_feature_support, &sec_erase_mult,
+		  &sec_trim_mult, &boot_info, &boot_size_mult, &acc_size,
+		  &hc_erase_grp_size, &erase_timeout_mult, &rel_wr_sec_c,
+		  &hc_wp_grp_size, &s_c_vcc, &s_c_vccq, &s_a_timeout, &sec_count,
+		  &min_perf_w_8_52, &min_perf_r_8_52, &min_perf_w_8_26_4_52,
+		  &min_perf_r_8_26_4_52, &min_perf_w_4_26, &min_perf_r_4_26,
+		  &pwr_cl_26_360, &pwr_cl_52_360, &pwr_cl_26_195, &pwr_cl_52_195,
+		  &partition_switch_time, &out_of_interrupt_time, &card_type,
+		  &csd_structure, &ext_csd_rev, &cmd_set, &cmd_set_rev,
+		  &power_class, &hs_timing, &bus_width, &erased_mem_cont,
+		  &partition_config, &boot_config_prot, &boot_bus_width,
+		  &erase_group_def, &boot_wp, &user_wp, &fw_config,
+		  &rpmb_size_mult, &wr_rel_set, &wr_rel_param, &bkops_start,
+		  &bkops_en, &rst_n_function, &hpi_mgmt, &partitioning_support,
+		  &max_enh_size_mult, &partitions_attribute,
+		  &partition_setting_completed, &gp_size_mult_gp0,
+		  &gp_size_mult_gp1, &gp_size_mult_gp2, &gp_size_mult_gp3,
+		  &enh_size_mult, &enh_start_addr, &sec_bad_blk_mgmnt);
+
+	if (config->verbose) {
+		printf("======MMC/EXT_CSD======\n");
+
+		printf("\tS_CMD_SET: 0x%02x (", s_cmd_set);
+		if (s_cmd_set & 0x4)
+			printf("Content Protection SecureMMC, ");
+		if (s_cmd_set & 0x2)
+			printf("SecureMMC, ");
+		if (s_cmd_set & 0x1)
+			printf("Standard MMC, ");
+		printf(")\n");
+
+		printf("\tHPI_FEATURES: 0x%02x (", hpi_features);
+		if (hpi_features & 0x1)
+			printf("HPI based on CMD%d",
+			       hpi_features & 0x2 ? 12 : 13);
+		printf(")\n");
+
+		printf("\tBKOPS_SUPPORT: 0x%02x (", bkops_support);
+		printf("background operations%s supported)\n",
+		       bkops_support & 0x1 ? "" : "not ");
+
+		printf("\tBKOPS_STATUS: 0x%02x (", bkops_status);
+		switch (bkops_status & 0x3) {
+		case 0x0:
+			printf("no ops required)\n");
+			break;
+		case 0x1:
+			printf("ops outstanding (non-critical))\n");
+			break;
+		case 0x2:
+			printf("ops outstanding (performance impacted))\n");
+			break;
+		case 0x3:
+			printf("ops outstanding (critical))\n");
+			break;
+		}
+
+		printf("\tCORRECTLY_PRG_SECTORS_NUM: 0x%02x %d\n",
+		       correctly_prg_sectors_num, correctly_prg_sectors_num);
+		printf("\tINI_TIMEOUT_PA: 0x%02x %dms\n",
+		       ini_timeout_ap, 100 * ini_timeout_ap);
+		printf("\tTRIM_MULT: 0x%02x %dms\n",
+		       trim_mult, 300 * trim_mult);
+
+		printf("\tSEC_FEATURE_SUPPORT: 0x%02x (", sec_feature_support);
+		if (sec_feature_support & 0x10)
+			printf("secure/insecure trim supported, ");
+		if (sec_feature_support & 0x4)
+			printf("secure purge on defective portions supported, ");
+		if (sec_feature_support & 0x1)
+			printf("secure purge supported, ");
+		printf(")\n");
+
+		printf("\tSEC_ERASE_MULT: 0x%02x %dms\n", sec_erase_mult,
+		       300 * erase_timeout_mult * sec_erase_mult);
+		printf("\tSEC_TRIM_MULT: 0x%02x %dms\n", sec_trim_mult,
+		       300 * erase_timeout_mult * sec_trim_mult);
+
+		printf("\tBOOT_INFO: 0x%02x (", boot_info);
+		if (boot_info & 0x4)
+			printf("high speed timing during boot supported, ");
+		if (boot_info & 0x2)
+			printf("alternate dual data rate during boot supported, ");
+		if (boot_info & 0x1)
+			printf("alternate boot supported, ");
+		printf(")\n");
+
+		printf("\tBOOT_SIZE_MULT: 0x%02x %dKbytes\n", boot_size_mult,
+		       128 * boot_size_mult);
+
+		printf("\tACC_SIZE_MULT: 0x%02x (", acc_size);
+		switch (acc_size & 0xf) {
+		case 0x0:
+			printf("not defined)\n");
+			break;
+		case 0x1:
+			printf("512 bytes)\n");
+			break;
+		case 0x2:
+			printf("1Kbytes)\n");
+			break;
+		case 0x3:
+			printf("2Kbytes)\n");
+			break;
+		case 0x4:
+			printf("4Kbytes)\n");
+			break;
+		case 0x5:
+			printf("8Kbytes)\n");
+			break;
+		case 0x6:
+			printf("16Kbytes)\n");
+			break;
+		case 0x7:
+			printf("32Kbytes)\n");
+			break;
+		case 0x8:
+			printf("64Kbytes)\n");
+			break;
+		default:
+			printf("reserved)\n");
+			break;
+		}
+
+		printf("\tHC_ERASE_GRP_SIZE: 0x%02x ", hc_erase_grp_size);
+		if (hc_erase_grp_size == 0x00)
+			printf("(no high-capacity erase-unit size support)\n");
+		else
+			printf("(%0.1fMbyte high-capcity erase-unit size)\n",
+			       (512 * 1024 * hc_erase_grp_size) / 1048576.0f);
+
+		printf("\tERASE_TIMEOUT_MULT: 0x%02x %dms\n",
+		       erase_timeout_mult, 300 * erase_timeout_mult);
+		printf("\tREL_WR_SEC_C: 0x%02x %dsectors\n",
+		       rel_wr_sec_c, rel_wr_sec_c);
+		printf("\tHC_WP_GRP_SIZE: 0x%02x %dKbyte\n", hc_wp_grp_size,
+		       512 * hc_erase_grp_size * hc_wp_grp_size);
+
+		printf("\tS_C_VCC: 0x%02x (", s_c_vcc);
+		switch (s_c_vcc) {
+		case 0x00:
+			printf("not defined)\n");
+			break;
+		case 0x01:
+			printf("2uA)\n");
+			break;
+		case 0x02:
+			printf("4uA)\n");
+			break;
+		case 0x03:
+			printf("8uA)\n");
+			break;
+		case 0x04:
+			printf("16uA)\n");
+			break;
+		case 0x05:
+			printf("32uA)\n");
+			break;
+		case 0x06:
+			printf("64uA)\n");
+			break;
+		case 0x07:
+			printf("128uA)\n");
+			break;
+		case 0x08:
+			printf("0.256mA)\n");
+			break;
+		case 0x09:
+			printf("0.512mA)\n");
+			break;
+		case 0x0a:
+			printf("1.024mA)\n");
+			break;
+		case 0x0b:
+			printf("2.048mA)\n");
+			break;
+		case 0x0c:
+			printf("4.096mA)\n");
+			break;
+		case 0x0d:
+			printf("8.192mA)\n");
+			break;
+		default:
+			printf("reserved)\n");
+			break;
+		}
+
+		printf("\tS_C_VCCQ: 0x%02x (", s_c_vccq);
+		switch (s_c_vccq) {
+		case 0x00:
+			printf("not defined)\n");
+			break;
+		case 0x01:
+			printf("2uA)\n");
+			break;
+		case 0x02:
+			printf("4uA)\n");
+			break;
+		case 0x03:
+			printf("8uA)\n");
+			break;
+		case 0x04:
+			printf("16uA)\n");
+			break;
+		case 0x05:
+			printf("32uA)\n");
+			break;
+		case 0x06:
+			printf("64uA)\n");
+			break;
+		case 0x07:
+			printf("128uA)\n");
+			break;
+		case 0x08:
+			printf("0.256mA)\n");
+			break;
+		case 0x09:
+			printf("0.512mA)\n");
+			break;
+		case 0x0a:
+			printf("1.024mA)\n");
+			break;
+		case 0x0b:
+			printf("2.048mA)\n");
+			break;
+		case 0x0c:
+			printf("4.096mA)\n");
+			break;
+		case 0x0d:
+			printf("8.192mA)\n");
+			break;
+		default:
+			printf("reserved)\n");
+			break;
+		}
+
+		printf("\tS_A_TIMEOUT: 0x%02x %dns", s_a_timeout,
+		       100 * 1 << s_a_timeout);
+		printf("\tSEC_COUNT: 0x%02x (%u sectors, 512bytes each, "
+		       "%lubytes in total)\n", sec_count, sec_count,
+		       sec_count * 512ul);
+
+		printf("\tMIN_PERF_DDR_W_8_52: 0x%02x (", min_perf_ddr_w_8_52);
+		switch (min_perf_ddr_w_8_52) {
+		case 0x00:
+			printf("card doesn't reach 4.8MB/s)\n");
+			break;
+		case 0x08:
+			printf("class A: 4.8MB/s)\n");
+			break;
+		case 0x0a:
+			printf("class B: 6.0MB/s)\n");
+			break;
+		case 0x0f:
+			printf("class C: 9.0MB/s)\n");
+			break;
+		case 0x14:
+			printf("class D: 12.0MB/s)\n");
+			break;
+		case 0x1e:
+			printf("class E: 18.0MB/s)\n");
+			break;
+		case 0x28:
+			printf("class F: 24.0MB/s)\n");
+			break;
+		case 0x32:
+			printf("class G: 30.0MB/s)\n");
+			break;
+		case 0x3c:
+			printf("class H: 36.0MB/s)\n");
+			break;
+		case 0x46:
+			printf("class J: 42.0MB/s)\n");
+			break;
+		case 0x50:
+			printf("class K: 48.0MB/s)\n");
+			break;
+		case 0x64:
+			printf("class M: 60.0MB/s)\n");
+			break;
+		case 0x78:
+			printf("class O: 72.0MB/s)\n");
+			break;
+		case 0x8c:
+			printf("class R: 84.0MB/s)\n");
+			break;
+		case 0xa0:
+			printf("class T: 96.0MB/s)\n");
+			break;
+		}
+
+		printf("\tMIN_PERF_DDR_R_8_52: 0x%02x (", min_perf_ddr_r_8_52);
+		switch (min_perf_ddr_r_8_52) {
+		case 0x00:
+			printf("card doesn't reach 4.8MB/s)\n");
+			break;
+		case 0x08:
+			printf("class A: 4.8MB/s)\n");
+			break;
+		case 0x0a:
+			printf("class B: 6.0MB/s)\n");
+			break;
+		case 0x0f:
+			printf("class C: 9.0MB/s)\n");
+			break;
+		case 0x14:
+			printf("class D: 12.0MB/s)\n");
+			break;
+		case 0x1e:
+			printf("class E: 18.0MB/s)\n");
+			break;
+		case 0x28:
+			printf("class F: 24.0MB/s)\n");
+			break;
+		case 0x32:
+			printf("class G: 30.0MB/s)\n");
+			break;
+		case 0x3c:
+			printf("class H: 36.0MB/s)\n");
+			break;
+		case 0x46:
+			printf("class J: 42.0MB/s)\n");
+			break;
+		case 0x50:
+			printf("class K: 48.0MB/s)\n");
+			break;
+		case 0x64:
+			printf("class M: 60.0MB/s)\n");
+			break;
+		case 0x78:
+			printf("class O: 72.0MB/s)\n");
+			break;
+		case 0x8c:
+			printf("class R: 84.0MB/s)\n");
+			break;
+		case 0xa0:
+			printf("class T: 96.0MB/s)\n");
+			break;
+		}
+
+		printf("\tMIN_PERF_W_8_52: 0x%02x (", min_perf_w_8_52);
+		switch (min_perf_w_8_52) {
+		case 0x00:
+			printf("card doesn't reach 2.4MB/s)\n");
+			break;
+		case 0x08:
+			printf("class A: 2.4MB/s)\n");
+			break;
+		case 0x0a:
+			printf("class B: 3.0MB/s)\n");
+			break;
+		case 0x0f:
+			printf("class C: 4.5MB/s)\n");
+			break;
+		case 0x14:
+			printf("class D: 6.0MB/s)\n");
+			break;
+		case 0x1e:
+			printf("class E: 9.0MB/s)\n");
+			break;
+		case 0x28:
+			printf("class F: 12.0MB/s)\n");
+			break;
+		case 0x32:
+			printf("class G: 15.0MB/s)\n");
+			break;
+		case 0x3c:
+			printf("class H: 18.0MB/s)\n");
+			break;
+		case 0x46:
+			printf("class J: 21.0MB/s)\n");
+			break;
+		case 0x50:
+			printf("class K: 24.0MB/s)\n");
+			break;
+		case 0x64:
+			printf("class M: 30.0MB/s)\n");
+			break;
+		case 0x78:
+			printf("class O: 36.0MB/s)\n");
+			break;
+		case 0x8c:
+			printf("class R: 42.0MB/s)\n");
+			break;
+		case 0xa0:
+			printf("class T: 48.0MB/s)\n");
+			break;
+		}
+
+		printf("\tMIN_PERF_R_8_52: 0x%02x (", min_perf_r_8_52);
+		switch (min_perf_r_8_52) {
+		case 0x00:
+			printf("card doesn't reach 2.4MB/s)\n");
+			break;
+		case 0x08:
+			printf("class A: 2.4MB/s)\n");
+			break;
+		case 0x0a:
+			printf("class B: 3.0MB/s)\n");
+			break;
+		case 0x0f:
+			printf("class C: 4.5MB/s)\n");
+			break;
+		case 0x14:
+			printf("class D: 6.0MB/s)\n");
+			break;
+		case 0x1e:
+			printf("class E: 9.0MB/s)\n");
+			break;
+		case 0x28:
+			printf("class F: 12.0MB/s)\n");
+			break;
+		case 0x32:
+			printf("class G: 15.0MB/s)\n");
+			break;
+		case 0x3c:
+			printf("class H: 18.0MB/s)\n");
+			break;
+		case 0x46:
+			printf("class J: 21.0MB/s)\n");
+			break;
+		case 0x50:
+			printf("class K: 24.0MB/s)\n");
+			break;
+		case 0x64:
+			printf("class M: 30.0MB/s)\n");
+			break;
+		case 0x78:
+			printf("class O: 36.0MB/s)\n");
+			break;
+		case 0x8c:
+			printf("class R: 42.0MB/s)\n");
+			break;
+		case 0xa0:
+			printf("class T: 48.0MB/s)\n");
+			break;
+		}
+
+		printf("\tMIN_PERF_W_8_26_4_52: 0x%02x (",
+		       min_perf_w_8_26_4_52);
+		switch (min_perf_w_8_26_4_52) {
+		case 0x00:
+			printf("card doesn't reach 2.4MB/s)\n");
+			break;
+		case 0x08:
+			printf("class A: 2.4MB/s)\n");
+			break;
+		case 0x0a:
+			printf("class B: 3.0MB/s)\n");
+			break;
+		case 0x0f:
+			printf("class C: 4.5MB/s)\n");
+			break;
+		case 0x14:
+			printf("class D: 6.0MB/s)\n");
+			break;
+		case 0x1e:
+			printf("class E: 9.0MB/s)\n");
+			break;
+		case 0x28:
+			printf("class F: 12.0MB/s)\n");
+			break;
+		case 0x32:
+			printf("class G: 15.0MB/s)\n");
+			break;
+		case 0x3c:
+			printf("class H: 18.0MB/s)\n");
+			break;
+		case 0x46:
+			printf("class J: 21.0MB/s)\n");
+			break;
+		case 0x50:
+			printf("class K: 24.0MB/s)\n");
+			break;
+		case 0x64:
+			printf("class M: 30.0MB/s)\n");
+			break;
+		case 0x78:
+			printf("class O: 36.0MB/s)\n");
+			break;
+		case 0x8c:
+			printf("class R: 42.0MB/s)\n");
+			break;
+		case 0xa0:
+			printf("class T: 48.0MB/s)\n");
+			break;
+		}
+
+		printf("\tMIN_PERF_R_8_26_4_52: 0x%02x (",
+		       min_perf_r_8_26_4_52);
+		switch (min_perf_r_8_26_4_52) {
+		case 0x00:
+			printf("card doesn't reach 2.4MB/s)\n");
+			break;
+		case 0x08:
+			printf("class A: 2.4MB/s)\n");
+			break;
+		case 0x0a:
+			printf("class B: 3.0MB/s)\n");
+			break;
+		case 0x0f:
+			printf("class C: 4.5MB/s)\n");
+			break;
+		case 0x14:
+			printf("class D: 6.0MB/s)\n");
+			break;
+		case 0x1e:
+			printf("class E: 9.0MB/s)\n");
+			break;
+		case 0x28:
+			printf("class F: 12.0MB/s)\n");
+			break;
+		case 0x32:
+			printf("class G: 15.0MB/s)\n");
+			break;
+		case 0x3c:
+			printf("class H: 18.0MB/s)\n");
+			break;
+		case 0x46:
+			printf("class J: 21.0MB/s)\n");
+			break;
+		case 0x50:
+			printf("class K: 24.0MB/s)\n");
+			break;
+		case 0x64:
+			printf("class M: 30.0MB/s)\n");
+			break;
+		case 0x78:
+			printf("class O: 36.0MB/s)\n");
+			break;
+		case 0x8c:
+			printf("class R: 42.0MB/s)\n");
+			break;
+		case 0xa0:
+			printf("class T: 48.0MB/s)\n");
+			break;
+		}
+
+		printf("\tMIN_PERF_W_4_26: 0x%02x (", min_perf_w_4_26);
+		switch (min_perf_w_4_26) {
+		case 0x00:
+			printf("card doesn't reach 2.4MB/s)\n");
+			break;
+		case 0x08:
+			printf("class A: 2.4MB/s)\n");
+			break;
+		case 0x0a:
+			printf("class B: 3.0MB/s)\n");
+			break;
+		case 0x0f:
+			printf("class C: 4.5MB/s)\n");
+			break;
+		case 0x14:
+			printf("class D: 6.0MB/s)\n");
+			break;
+		case 0x1e:
+			printf("class E: 9.0MB/s)\n");
+			break;
+		case 0x28:
+			printf("class F: 12.0MB/s)\n");
+			break;
+		case 0x32:
+			printf("class G: 15.0MB/s)\n");
+			break;
+		case 0x3c:
+			printf("class H: 18.0MB/s)\n");
+			break;
+		case 0x46:
+			printf("class J: 21.0MB/s)\n");
+			break;
+		case 0x50:
+			printf("class K: 24.0MB/s)\n");
+			break;
+		case 0x64:
+			printf("class M: 30.0MB/s)\n");
+			break;
+		case 0x78:
+			printf("class O: 36.0MB/s)\n");
+			break;
+		case 0x8c:
+			printf("class R: 42.0MB/s)\n");
+			break;
+		case 0xa0:
+			printf("class T: 48.0MB/s)\n");
+			break;
+		}
+
+		printf("\tMIN_PERF_R_4_26: 0x%02x (", min_perf_r_4_26);
+		switch (min_perf_r_4_26) {
+		case 0x00:
+			printf("card doesn't reach 2.4MB/s)\n");
+			break;
+		case 0x08:
+			printf("class A: 2.4MB/s)\n");
+			break;
+		case 0x0a:
+			printf("class B: 3.0MB/s)\n");
+			break;
+		case 0x0f:
+			printf("class C: 4.5MB/s)\n");
+			break;
+		case 0x14:
+			printf("class D: 6.0MB/s)\n");
+			break;
+		case 0x1e:
+			printf("class E: 9.0MB/s)\n");
+			break;
+		case 0x28:
+			printf("class F: 12.0MB/s)\n");
+			break;
+		case 0x32:
+			printf("class G: 15.0MB/s)\n");
+			break;
+		case 0x3c:
+			printf("class H: 18.0MB/s)\n");
+			break;
+		case 0x46:
+			printf("class J: 21.0MB/s)\n");
+			break;
+		case 0x50:
+			printf("class K: 24.0MB/s)\n");
+			break;
+		case 0x64:
+			printf("class M: 30.0MB/s)\n");
+			break;
+		case 0x78:
+			printf("class O: 36.0MB/s)\n");
+			break;
+		case 0x8c:
+			printf("class R: 42.0MB/s)\n");
+			break;
+		case 0xa0:
+			printf("class T: 48.0MB/s)\n");
+			break;
+		}
+
+		printf("\tPWR_CL_DDR_52_360: 0x%02x (8bit -> ",
+		       pwr_cl_ddr_52_360);
+		switch ((pwr_cl_ddr_52_360 & 0xf0) >> 0) {
+		case 0x0:
+			printf("100-200mA, ");
+			break;
+		case 0x1:
+			printf("120-220mA, ");
+			break;
+		case 0x2:
+			printf("150-250mA, ");
+			break;
+		case 0x3:
+			printf("180-280mA, ");
+			break;
+		case 0x4:
+			printf("200-300mA, ");
+			break;
+		case 0x5:
+			printf("220-320mA, ");
+			break;
+		case 0x6:
+			printf("250-350mA, ");
+			break;
+		case 0x7:
+			printf("300-400mA, ");
+			break;
+		case 0x8:
+			printf("350-450mA, ");
+			break;
+		case 0x9:
+			printf("400-500mA, ");
+			break;
+		case 0xa:
+			printf("450-550mA, ");
+			break;
+		default:
+			printf("reserved, ");
+			break;
+		}
+
+		printf("4bit -> ");
+		switch (pwr_cl_ddr_52_360 & 0x0f) {
+		case 0x0:
+			printf("100-200mA)\n");
+			break;
+		case 0x1:
+			printf("120-220mA)\n");
+			break;
+		case 0x2:
+			printf("150-250mA)\n");
+			break;
+		case 0x3:
+			printf("180-280mA)\n");
+			break;
+		case 0x4:
+			printf("200-300mA)\n");
+			break;
+		case 0x5:
+			printf("220-320mA)\n");
+			break;
+		case 0x6:
+			printf("250-350mA)\n");
+			break;
+		case 0x7:
+			printf("300-400mA)\n");
+			break;
+		case 0x8:
+			printf("350-450mA)\n");
+			break;
+		case 0x9:
+			printf("400-500mA)\n");
+			break;
+		case 0xa:
+			printf("450-550mA)\n");
+			break;
+		default:
+			printf("reserved)\n");
+			break;
+		}
+
+		printf("\tPWR_CL_DDR_52_195: 0x%02x (8bit -> ",
+		       pwr_cl_ddr_52_195);
+		switch ((pwr_cl_ddr_52_195 & 0xf0) >> 0) {
+		case 0x0:
+			printf("65-130mA, ");
+			break;
+		case 0x1:
+			printf("70-140mA, ");
+			break;
+		case 0x2:
+			printf("80-160mA, ");
+			break;
+		case 0x3:
+			printf("90-180mA, ");
+			break;
+		case 0x4:
+			printf("100-200mA, ");
+			break;
+		case 0x5:
+			printf("120-220mA, ");
+			break;
+		case 0x6:
+			printf("140-240mA, ");
+			break;
+		case 0x7:
+			printf("160-260mA, ");
+			break;
+		case 0x8:
+			printf("180-280mA, ");
+			break;
+		case 0x9:
+			printf("200-300mA, ");
+			break;
+		case 0xa:
+			printf("250-350mA, ");
+			break;
+		default:
+			printf("reserved, ");
+			break;
+		}
+		printf("4bit -> ");
+		switch (pwr_cl_ddr_52_195 & 0x0f) {
+		case 0x0:
+			printf("65-130mA)\n");
+			break;
+		case 0x1:
+			printf("70-140mA)\n");
+			break;
+		case 0x2:
+			printf("80-160mA)\n");
+			break;
+		case 0x3:
+			printf("90-180mA)\n");
+			break;
+		case 0x4:
+			printf("100-200mA)\n");
+			break;
+		case 0x5:
+			printf("120-220mA)\n");
+			break;
+		case 0x6:
+			printf("140-240mA)\n");
+			break;
+		case 0x7:
+			printf("160-260mA)\n");
+			break;
+		case 0x8:
+			printf("180-280mA)\n");
+			break;
+		case 0x9:
+			printf("200-300mA)\n");
+			break;
+		case 0xa:
+			printf("250-350mA)\n");
+			break;
+		default:
+			printf("reserved)\n");
+			break;
+		}
+
+		printf("\tPWR_CL_26_360: 0x%02x (8bit -> ", pwr_cl_26_360);
+		switch ((pwr_cl_26_360 & 0xf0) >> 0) {
+		case 0x0:
+			printf("100-200mA, ");
+			break;
+		case 0x1:
+			printf("120-220mA, ");
+			break;
+		case 0x2:
+			printf("150-250mA, ");
+			break;
+		case 0x3:
+			printf("180-280mA, ");
+			break;
+		case 0x4:
+			printf("200-300mA, ");
+			break;
+		case 0x5:
+			printf("220-320mA, ");
+			break;
+		case 0x6:
+			printf("250-350mA, ");
+			break;
+		case 0x7:
+			printf("300-400mA, ");
+			break;
+		case 0x8:
+			printf("350-450mA, ");
+			break;
+		case 0x9:
+			printf("400-500mA, ");
+			break;
+		case 0xa:
+			printf("450-550mA, ");
+			break;
+		default:
+			printf("reserved, ");
+			break;
+		}
+		printf("4bit -> ");
+		switch (pwr_cl_26_360 & 0x0f) {
+		case 0x0:
+			printf("100-200mA)\n");
+			break;
+		case 0x1:
+			printf("120-220mA)\n");
+			break;
+		case 0x2:
+			printf("150-250mA)\n");
+			break;
+		case 0x3:
+			printf("180-280mA)\n");
+			break;
+		case 0x4:
+			printf("200-300mA)\n");
+			break;
+		case 0x5:
+			printf("220-320mA)\n");
+			break;
+		case 0x6:
+			printf("250-350mA)\n");
+			break;
+		case 0x7:
+			printf("300-400mA)\n");
+			break;
+		case 0x8:
+			printf("350-450mA)\n");
+			break;
+		case 0x9:
+			printf("400-500mA)\n");
+			break;
+		case 0xa:
+			printf("450-550mA)\n");
+			break;
+		default:
+			printf("reserved)\n");
+			break;
+		}
+
+		printf("\tPWR_CL_52_360: 0x%02x (8bit -> ", pwr_cl_52_360);
+		switch ((pwr_cl_52_360 & 0xf0) >> 0) {
+		case 0x0:
+			printf("100-200mA, ");
+			break;
+		case 0x1:
+			printf("120-220mA, ");
+			break;
+		case 0x2:
+			printf("150-250mA, ");
+			break;
+		case 0x3:
+			printf("180-280mA, ");
+			break;
+		case 0x4:
+			printf("200-300mA, ");
+			break;
+		case 0x5:
+			printf("220-320mA, ");
+			break;
+		case 0x6:
+			printf("250-350mA, ");
+			break;
+		case 0x7:
+			printf("300-400mA, ");
+			break;
+		case 0x8:
+			printf("350-450mA, ");
+			break;
+		case 0x9:
+			printf("400-500mA, ");
+			break;
+		case 0xa:
+			printf("450-550mA, ");
+			break;
+		default:
+			printf("reserved, ");
+			break;
+		}
+		printf("4bit -> ");
+		switch (pwr_cl_52_360 & 0x0f) {
+		case 0x0:
+			printf("100-200mA)\n");
+			break;
+		case 0x1:
+			printf("120-220mA)\n");
+			break;
+		case 0x2:
+			printf("150-250mA)\n");
+			break;
+		case 0x3:
+			printf("180-280mA)\n");
+			break;
+		case 0x4:
+			printf("200-300mA)\n");
+			break;
+		case 0x5:
+			printf("220-320mA)\n");
+			break;
+		case 0x6:
+			printf("250-350mA)\n");
+			break;
+		case 0x7:
+			printf("300-400mA)\n");
+			break;
+		case 0x8:
+			printf("350-450mA)\n");
+			break;
+		case 0x9:
+			printf("400-500mA)\n");
+			break;
+		case 0xa:
+			printf("450-550mA)\n");
+			break;
+		default:
+			printf("reserved)\n");
+			break;
+		}
+
+		printf("\tPWR_CL_26_195: 0x%02x (8bit -> ", pwr_cl_26_195);
+		switch ((pwr_cl_26_195 & 0xf0) >> 0) {
+		case 0x0:
+			printf("65-130mA, ");
+			break;
+		case 0x1:
+			printf("70-140mA, ");
+			break;
+		case 0x2:
+			printf("80-160mA, ");
+			break;
+		case 0x3:
+			printf("90-180mA, ");
+			break;
+		case 0x4:
+			printf("100-200mA, ");
+			break;
+		case 0x5:
+			printf("120-220mA, ");
+			break;
+		case 0x6:
+			printf("140-240mA, ");
+			break;
+		case 0x7:
+			printf("160-260mA, ");
+			break;
+		case 0x8:
+			printf("180-280mA, ");
+			break;
+		case 0x9:
+			printf("200-300mA, ");
+			break;
+		case 0xa:
+			printf("250-350mA, ");
+			break;
+		default:
+			printf("reserved, ");
+			break;
+		}
+		printf("4bit -> ");
+		switch (pwr_cl_26_195 & 0x0f) {
+		case 0x0:
+			printf("65-130mA)\n");
+			break;
+		case 0x1:
+			printf("70-140mA)\n");
+			break;
+		case 0x2:
+			printf("80-160mA)\n");
+			break;
+		case 0x3:
+			printf("90-180mA)\n");
+			break;
+		case 0x4:
+			printf("100-200mA)\n");
+			break;
+		case 0x5:
+			printf("120-220mA)\n");
+			break;
+		case 0x6:
+			printf("140-240mA)\n");
+			break;
+		case 0x7:
+			printf("160-260mA)\n");
+			break;
+		case 0x8:
+			printf("180-280mA)\n");
+			break;
+		case 0x9:
+			printf("200-300mA)\n");
+			break;
+		case 0xa:
+			printf("250-350mA)\n");
+			break;
+		default:
+			printf("reserved)\n");
+			break;
+		}
+
+		printf("\tPWR_CL_52_195: 0x%02x (8bit -> ", pwr_cl_52_195);
+		switch ((pwr_cl_52_195 & 0xf0) >> 0) {
+		case 0x0:
+			printf("65-130mA, ");
+			break;
+		case 0x1:
+			printf("70-140mA, ");
+			break;
+		case 0x2:
+			printf("80-160mA, ");
+			break;
+		case 0x3:
+			printf("90-180mA, ");
+			break;
+		case 0x4:
+			printf("100-200mA, ");
+			break;
+		case 0x5:
+			printf("120-220mA, ");
+			break;
+		case 0x6:
+			printf("140-240mA, ");
+			break;
+		case 0x7:
+			printf("160-260mA, ");
+			break;
+		case 0x8:
+			printf("180-280mA, ");
+			break;
+		case 0x9:
+			printf("200-300mA, ");
+			break;
+		case 0xa:
+			printf("250-350mA, ");
+			break;
+		default:
+			printf("reserved, ");
+			break;
+		}
+		printf("4bit -> ");
+		switch (pwr_cl_52_195 & 0x0f) {
+		case 0x0:
+			printf("65-130mA)\n");
+			break;
+		case 0x1:
+			printf("70-140mA)\n");
+			break;
+		case 0x2:
+			printf("80-160mA)\n");
+			break;
+		case 0x3:
+			printf("90-180mA)\n");
+			break;
+		case 0x4:
+			printf("100-200mA)\n");
+			break;
+		case 0x5:
+			printf("120-220mA)\n");
+			break;
+		case 0x6:
+			printf("140-240mA)\n");
+			break;
+		case 0x7:
+			printf("160-260mA)\n");
+			break;
+		case 0x8:
+			printf("180-280mA)\n");
+			break;
+		case 0x9:
+			printf("200-300mA)\n");
+			break;
+		case 0xa:
+			printf("250-350mA)\n");
+			break;
+		default:
+			printf("reserved)\n");
+			break;
+		}
+
+		printf("\tPARTITION_SWITCH_TIME: 0x%02x %dms\n",
+		       partition_switch_time, 10 * partition_switch_time);
+
+		printf("\tOUT_OF_INTERRUPT_TIME: 0x%02x %dms\n",
+		       out_of_interrupt_time, 10 * out_of_interrupt_time);
+
+		printf("\tCARD_TYPE: 0x%02x (", card_type);
+		if (card_type & 0x8)
+			printf("High-speed DDR MMC @ 52Mhz 1.2V I/O, ");
+		if (card_type & 0x4)
+			printf("High-speed DDR MMC @ 52Mhz 1.8V/3V I/O, ");
+		if (card_type & 0x2)
+			printf("High-speed MMC @ 52Mhz, ");
+		if (card_type & 0x1)
+			printf("High-speed MMC @ 26Mhz, ");
+		printf(")\n");
+
+		printf("\tCSD_STRUCTURE: 0x%02x (", csd_structure);
+		switch (csd_structure) {
+		case 0x0:
+			printf("v1.0)\n");
+			break;
+		case 0x1:
+			printf("v1.1)\n");
+			break;
+		case 0x2:
+			printf("v1.2)\n");
+			break;
+		case 0x3:
+			printf("v1.3)\n");
+			break;
+		default:
+			printf("reserved)\n");
+			break;
+		}
+
+		printf("\tEXT_CSD_REV: 0x%02x (", ext_csd_rev);
+		switch (ext_csd_rev) {
+		case 0x0:
+			printf("rev 1.0 for MMC v4.0)\n");
+			break;
+		case 0x1:
+			printf("rev 1.1 for MMC v4.1)\n");
+			break;
+		case 0x2:
+			printf("rev 1.2 for MMC v4.2)\n");
+			break;
+		case 0x3:
+			printf("rev 1.3 for MMC v4.3)\n");
+			break;
+		case 0x4:
+			printf("rev 1.4 for MMC v4.4)\n");
+			break;
+		case 0x5:
+			printf("rev 1.5 for MMC v4.41)\n");
+			break;
+		default:
+			printf("reserved)\n");
+			break;
+		}
+
+		printf("\tCMD_SET: 0x%02x\n", cmd_set);
+		printf("\tCMD_SET_REV: 0x%02x (", cmd_set_rev);
+		if (cmd_set_rev == 0x00)
+			printf("MMC v4.0 command set)\n");
+		else
+			printf("reserved)\n");
+
+		printf("\tPOWER_CLASS: 0x%02x (", power_class);
+		switch (power_class & 0xf) {
+		case 0x0:
+			printf("65-130mA or ");
+			break;
+		case 0x1:
+			printf("70-140mA or ");
+			break;
+		case 0x2:
+			printf("80-160mA or ");
+			break;
+		case 0x3:
+			printf("90-180mA or ");
+			break;
+		case 0x4:
+			printf("100-200mA or ");
+			break;
+		case 0x5:
+			printf("120-220mA or ");
+			break;
+		case 0x6:
+			printf("140-240mA or ");
+			break;
+		case 0x7:
+			printf("160-260mA or ");
+			break;
+		case 0x8:
+			printf("180-280mA or ");
+			break;
+		case 0x9:
+			printf("200-300mA or ");
+			break;
+		case 0xa:
+			printf("250-350mA or ");
+			break;
+		default:
+			printf("reserved or ");
+			break;
+		}
+
+		switch (power_class & 0xf) {
+		case 0x0:
+			printf("100-200mA)\n");
+			break;
+		case 0x1:
+			printf("120-220mA)\n");
+			break;
+		case 0x2:
+			printf("150-250mA)\n");
+			break;
+		case 0x3:
+			printf("180-280mA)\n");
+			break;
+		case 0x4:
+			printf("200-300mA)\n");
+			break;
+		case 0x5:
+			printf("220-320mA)\n");
+			break;
+		case 0x6:
+			printf("250-350mA)\n");
+			break;
+		case 0x7:
+			printf("300-400mA)\n");
+			break;
+		case 0x8:
+			printf("350-450mA)\n");
+			break;
+		case 0x9:
+			printf("400-500mA)\n");
+			break;
+		case 0xa:
+			printf("450-550mA)\n");
+			break;
+		default:
+			printf("reserved)\n");
+			break;
+		}
+
+		printf("\tHS_TIMING: 0x%02x\n", hs_timing);
+
+		printf("\tBUS_WIDTH: 0x%02x (", bus_width);
+		switch (bus_width) {
+		case 0x0:
+			printf("8bit data bus DDR)\n");
+			break;
+		case 0x1:
+			printf("4bit data bus DDR)\n");
+			break;
+		case 0x2:
+			printf("8bit data bus)\n");
+			break;
+		case 0x5:
+			printf("4bit data bus)\n");
+			break;
+		case 0x6:
+			printf("1bit data bus)\n");
+			break;
+		default:
+			printf("reserved)\n");
+			break;
+		}
+
+		printf("\tERASED_MEM_CONT: 0x%02x (", erased_mem_cont);
+		if (erased_mem_cont & 0x01)
+			printf("erased is 1)\n");
+		else
+			printf("erased is 0)\n");
+
+
+		printf("\tPARTITION_CONFIG: 0x%02x (", partition_config);
+		if (partition_config & 0x40)
+			printf("boot acknowledge during boot, ");
+		switch ((partition_config & 0x38) >> 3) {
+		case 0x0:
+			printf("device boot not enabled, ");
+			break;
+		case 0x1:
+			printf("boot partition 1 enabled for boot, ");
+			break;
+		case 0x2:
+			printf("boot partition 2 enabled for boot, ");
+			break;
+		case 0x7:
+			printf("user area enabled for boot, ");
+			break;
+		default:
+			printf("reserved, ");
+			break;
+		}
+
+		switch (partition_config & 0x3) {
+		case 0x0:
+			printf("no access to boot partition, ");
+			break;
+		case 0x1:
+			printf("R/W boot partition 1, ");
+			break;
+		case 0x2:
+			printf("R/W boot partition 2, ");
+			break;
+		case 0x3:
+			printf("R/W RPMB, ");
+			break;
+		case 0x4:
+			printf("general partition 1 access, ");
+			break;
+		case 0x5:
+			printf("general partition 2 access, ");
+			break;
+		case 0x6:
+			printf("general partition 3 access, ");
+			break;
+		case 0x7:
+			printf("general partition 4 access, ");
+			break;
+		}
+
+		printf("\tBOOT_CONFIG_PROT: 0x%02x (", boot_config_prot);
+		if (boot_config_prot & 0x10)
+			printf("boot configuration register bits locked, ");
+		if (boot_config_prot & 0x01)
+			printf("boot configuration regsiter bits temporarily locked, ");
+		printf(")\n");
+
+		printf("\tBOOT_BUS_WIDTH: 0x%02x (", boot_bus_width);
+		switch ((boot_bus_width & 0x18) >> 3) {
+		case 0x0:
+			printf("SDR + normal timing in boot, ");
+			break;
+		case 0x1:
+			printf("SDR + high-speed timing in boot, ");
+			break;
+		case 0x2:
+			printf("DDR in boot, ");
+			break;
+		default:
+			printf("reserved, ");
+			break;
+		}
+
+		if (boot_bus_width & 0x4)
+			printf("retain bus width/boot mode after boot, ");
+		else
+			printf("reset to 1bit SDR normal timing after boot, ");
+		switch (boot_bus_width & 0x3) {
+		case 0x0:
+			printf("1bit SDR or 4bit DDR during boot)\n");
+			break;
+		case 0x1:
+			printf("4bit SDR/DDR during boot)\n");
+			break;
+		case 0x2:
+			printf("8bit SDR/DDR during boot)\n");
+			break;
+		case 0x3:
+			printf("reserved)\n");
+			break;
+		}
+
+		printf("\tERASE_GROUP_DEF: 0x%02x (", erase_group_def);
+		if (erase_group_def & 0x1)
+			printf("Use high-capacity erase unit/erase "
+			       "timeout/write protect group)\n");
+		else
+			printf("Use normal erase unit/erase timeout/write "
+			       "protect group)\n");
+
+		printf("\tBOOT_WP: 0x%02x (", boot_wp);
+		if (boot_wp & 0x40)
+			printf("allow power-on protection in boot area, ");
+		if (boot_wp & 0x10)
+			printf("allow boot area write protection, ");
+		if (boot_wp & 0x4)
+			printf("boot area permanently write protected, ");
+		if (boot_wp & 0x1)
+			printf("power-on boot area write protection, ");
+		printf(")\n");
+
+		printf("\tUSER_WP: 0x%02x (", user_wp);
+		if (user_wp & 0x80)
+			printf("password protection features disabled, ");
+		if (user_wp & 0x40)
+			printf("disable permanent write protection, ");
+		if (user_wp & 0x10)
+			printf("write protection group write protection disabled, ");
+		if (user_wp & 0x8)
+			printf("power-on period write protection group write "
+			       "proection disabled, ");
+		if (user_wp & 0x4)
+			printf("apply permanent write protection to protection "
+			       "group, ");
+		if (user_wp & 0x1)
+			printf("apply power-on period write protection to "
+			       "protection group, ");
+		printf(")\n");
+
+		printf("\tFW_CONFIG: 0x%02x (", fw_config);
+		if (fw_config & 0x1)
+			printf("FW upgrade disabled");
+		printf(")\n");
+
+		printf("\tRPMB_SIZE_MULT: 0x%02x %dKbyte\n",
+			rpmb_size_mult, 128 * rpmb_size_mult);
+
+		printf("\tWR_REL_SET: 0x%02x (reliable write enabled for: ",
+		       wr_rel_set);
+		if (wr_rel_set & 0x10)
+			printf("general partition 4, ");
+		if (wr_rel_set & 0x8)
+			printf("general partition 3, ");
+		if (wr_rel_set & 0x4)
+			printf("general partition 2, ");
+		if (wr_rel_set & 0x2)
+			printf("general partition 1, ");
+		if (wr_rel_set & 0x1)
+			printf("user data area, ");
+		printf(")\n");
+
+		printf("\tWR_REL_PARAM: 0x%02x (", wr_rel_param);
+		if (wr_rel_param & 0x1)
+			printf("host may alter reliable write settings, ");
+		if (wr_rel_param & 0x4)
+			printf("enhanced reliable write support)\n");
+		else
+			printf("legacy reliable write support)\n");
+
+		printf("\tBKOPS_START: 0x%02x\n", bkops_start);
+
+		printf("\tBKOPS_EN: 0x%02x (", bkops_en);
+		if (bkops_en & 0x1)
+			printf("host will use background operations");
+		else
+			printf("device will have to handle background "
+			       "operations itself");
+		printf(")\n");
+
+		printf("\tRST_n_FUNCTION: 0x%02x (", rst_n_function);
+		switch (rst_n_function & 0x3) {
+		case 0x0:
+			printf("RST_n is temporarily disabled)\n");
+			break;
+		case 0x1:
+			printf("RST_n is permanently enabled)\n");
+			break;
+		case 0x2:
+			printf("RST_n is permanently disabled)\n");
+			break;
+		case 0x3:
+			printf("reserved)\n");
+			break;
+		}
+
+		printf("\tHPI_MGMT: 0x%02x (", hpi_mgmt);
+		if (hpi_mgmt & 0x1)
+			printf("HPI is enabled");
+		printf(")\n");
+
+		printf("\tPARTITIONING_SUPPORT: 0x%02x (",
+		       partitioning_support);
+		if (partitioning_support & 0x02)
+			printf("device supports enhanced technology features, ");
+		if (partitioning_support & 0x1)
+			printf("device supports partitioning, ");
+		printf(")\n");
+
+		printf("\tMAX_ENH_SIZE_MULT: 0x%06x %luKbyte\n",
+		       max_enh_size_mult, max_enh_size_mult * hc_wp_grp_size *
+		       hc_erase_grp_size * 512ul);
+
+		printf("\tPARTITIONS_ATTRIBUTE: 0x%02x (enhanced areas: ",
+		       partitions_attribute);
+		if (partitions_attribute & 0x10)
+			printf("general partition 4, ");
+		if (partitions_attribute & 0x08)
+			printf("general partition 3, ");
+		if (partitions_attribute & 0x04)
+			printf("general partition 2, ");
+		if (partitions_attribute & 0x02)
+			printf("general partition 1, ");
+		if (partitions_attribute & 0x01)
+			printf("user data area, ");
+		printf(")\n");
+
+		printf("\tPARTITION_SETTING_COMPLETED: 0x%02x\n",
+		       partition_setting_completed);
+		printf("\tGP_SIZE_MULT_GP0: 0x%06x %luKbytes\n",
+		       gp_size_mult_gp0,
+		       gp_size_mult_gp0 * hc_wp_grp_size *
+		       hc_erase_grp_size * 512ul);
+		printf("\tGP_SIZE_MULT_GP1: 0x%06x %luKbytes\n",
+		       gp_size_mult_gp1,
+		       gp_size_mult_gp1 * hc_wp_grp_size *
+		       hc_erase_grp_size * 512ul);
+		printf("\tGP_SIZE_MULT_GP2: 0x%06x %luKbytes\n",
+		       gp_size_mult_gp2,
+		       gp_size_mult_gp2 * hc_wp_grp_size *
+		       hc_erase_grp_size * 512ul);
+		printf("\tGP_SIZE_MULT_GP3: 0x%06x %luKbytes\n",
+		       gp_size_mult_gp3,
+		       gp_size_mult_gp3 * hc_wp_grp_size *
+		       hc_erase_grp_size * 512ul);
+		printf("\tENH_SIZE_MULT: 0x%06x %luKbytes\n",
+		       enh_size_mult,
+		       enh_size_mult * hc_wp_grp_size *
+		       hc_erase_grp_size * 512ul);
+		printf("\tENH_START_ADDR: 0x%08x\n", enh_start_addr);
+
+		printf("\tSEC_BAD_BLK_MGMNT: 0x%02x (", sec_bad_blk_mgmnt);
+		if (sec_bad_blk_mgmnt & 0x1)
+			printf("retired memory regions are purged");
+		printf(")\n");
+	} else {
+		unsigned long long blocks = 0;
+		int block_size = 0;
+		unsigned long long memory_capacity;
+
+		printf("bus width: ");
+		switch (bus_width) {
+		case 0x0:
+			printf("8bit data bus ddr\n");
+			break;
+		case 0x1:
+			printf("4bit data bus ddr\n");
+			break;
+		case 0x2:
+			printf("8bit data bus\n");
+			break;
+		case 0x5:
+			printf("4bit data bus\n");
+			break;
+		case 0x6:
+			printf("1bit data bus\n");
+			break;
+		default:
+			printf("reserved\n");
+			break;
+		}
+
+		block_size = 512;
+		blocks = sec_count;
+		memory_capacity = blocks * block_size;
+
+		printf("high-cacpacity size: ");
+		if (memory_capacity / (1024ull * 1024ull * 1024ull) > 0)
+			printf("%.2fGbyte",
+			       memory_capacity / (1024.0 * 1024.0 * 1024.0));
+		else if (memory_capacity / (1024ull * 1024ull) > 0)
+			printf("%.2fMbyte",
+			       memory_capacity / (1024.0 * 1024.0));
+		else if (memory_capacity / (1024ull) > 0)
+			printf("%.2fKbyte", memory_capacity / (1024.0));
+		else
+			printf("%.2fbyte", memory_capacity * 1.0);
+		printf(" (%lld bytes, %lld sectors, %d bytes each)\n",
+		       memory_capacity, blocks, block_size);
+
+		printf("speed class:\n");
+		printf("\t%20s: %s (%c) / %s (%c)\n",
+		       "8bit@52mhz ddr r/w",
+		       speed_class_speed(min_perf_ddr_r_8_52, true),
+		       speed_class_name(min_perf_ddr_r_8_52),
+		       speed_class_speed(min_perf_ddr_w_8_52, true),
+		       speed_class_name(min_perf_ddr_w_8_52));
+		printf("\t%20s: %s (%c) / %s (%c)\n",
+		       "8bit@52mhz r/w",
+		       speed_class_speed(min_perf_r_8_52, false),
+		       speed_class_name(min_perf_r_8_52),
+		       speed_class_speed(min_perf_w_8_52, false),
+		       speed_class_name(min_perf_w_8_52));
+		printf("\t%20s: %s (%c) / %s (%c)\n",
+		       "8bit@26mhz r/w",
+		       speed_class_speed(min_perf_r_8_26_4_52, false),
+		       speed_class_name(min_perf_r_8_26_4_52),
+		       speed_class_speed(min_perf_w_8_26_4_52, false),
+		       speed_class_name(min_perf_w_8_26_4_52));
+		printf("\t%20s: %s (%c) / %s (%c)\n",
+		       "4bit@26mhz r/w",
+		       speed_class_speed(min_perf_r_4_26, false),
+		       speed_class_name(min_perf_r_4_26),
+		       speed_class_speed(min_perf_w_4_26, false),
+		       speed_class_name(min_perf_w_4_26));
+
+		printf("power class:\n");
+		printf("\t%25s: %s / %s\n",
+		       "8bit@52mhz 3.6v/1.95v",
+		       power_class_consumption((pwr_cl_52_360 >> 4) & 0xf,
+					       true),
+		       power_class_consumption((pwr_cl_52_195 >> 4) & 0xf,
+					       false));
+		printf("\t%25s: %s / %s\n",
+		       "8bit@26mhz 3.6v/1.95v",
+		       power_class_consumption((pwr_cl_26_360 >> 4) & 0xf,
+					       true),
+		       power_class_consumption((pwr_cl_26_195 >> 4) & 0xf,
+					       false));
+		printf("\t%25s: %s / %s\n",
+		       "4bit@52mhz 3.6v/1.95v",
+		       power_class_consumption(pwr_cl_52_360 & 0xf, true),
+		       power_class_consumption(pwr_cl_52_195 & 0xf, false));
+		printf("\t%25s: %s / %s\n",
+		       "4bit@26mhz 3.6v/1.95v",
+		       power_class_consumption(pwr_cl_26_360 & 0xf, true),
+		       power_class_consumption(pwr_cl_26_195 & 0xf, false));
+		printf("\t%25s: %s / %s\n",
+		       "sleep core/io",
+		       sleep_consumption(s_c_vcc),
+		       sleep_consumption(s_c_vccq));
+
+		printf("erase group size: %dKbyte\n", 512 * hc_erase_grp_size);
+		printf("write protect group size: %dKbyte\n",
+		       512 * hc_erase_grp_size * hc_wp_grp_size);
+
+		printf("access size: ");
+		switch (acc_size & 0xf) {
+		case 0x0:
+			printf("not defined\n");
+			break;
+		case 0x1:
+			printf("512 bytes\n");
+			break;
+		case 0x2:
+			printf("1Kbytes\n");
+			break;
+		case 0x3:
+			printf("2Kbytes\n");
+			break;
+		case 0x4:
+			printf("4Kbytes\n");
+			break;
+		case 0x5:
+			printf("8Kbytes\n");
+			break;
+		case 0x6:
+			printf("16Kbytes\n");
+			break;
+		case 0x7:
+			printf("32Kbytes\n");
+			break;
+		case 0x8:
+			printf("64Kbytes\n");
+			break;
+		default:
+			printf("reserved\n");
+			break;
+		}
+
+		printf("features: ");
+		if (hpi_features & 0x1)
+			printf("hpi, ");
+		if (bkops_support & 0x1)
+			printf("bkops, ");
+		printf("\n");
+
+		printf("background ops status: ");
+		switch (bkops_status & 0x3) {
+		case 0x0:
+			printf("bkops not required\n");
+			break;
+		case 0x1:
+			printf("bkops required (non-critical)\n");
+			break;
+		case 0x2:
+			printf("bkops required (performance impacted)\n");
+			break;
+		case 0x3:
+			printf("bkops required (critical)\n");
+			break;
+		}
+	}
+}
+
+void print_sd_scr(struct config *config, char *scr)
+{
+	unsigned int scr_structure;
+	unsigned int sd_spec;
+	unsigned int data_stat_after_erase;
+	unsigned int sd_security;
+	unsigned int sd_bus_widths;
+	unsigned int sd_spec3;
+	unsigned int ex_security;
+	unsigned int cmd_support;
+
+	parse_bin(scr, "4u4u1u3u4u1u4u9r2u32r",
+		&scr_structure, &sd_spec, &data_stat_after_erase,
+		&sd_security, &sd_bus_widths, &sd_spec3,
+		&ex_security, &cmd_support);
+
+	if (config->verbose) {
+		printf("======SD/SCR======\n");
+
+		printf("\tSCR_STRUCTURE: 0x%01x (", scr_structure);
+		switch (scr_structure) {
+		case 0:
+			printf("SCR v1.0)\n");
+			break;
+		default:
+			printf("reserved)\n");
+			break;
+		}
+
+		printf("\tSD_SPEC: 0x%01x (", sd_spec);
+		switch (sd_spec) {
+		case 0:
+			printf("SD v1.0/1.01)\n");
+			break;
+		case 1:
+			printf("SD v1.10)\n");
+			break;
+		case 2:
+			printf("SD v2.00/v3.0x)\n");
+			break;
+		case 3:
+			printf("SD v4.00)\n");
+			break;
+		default:
+			printf("reserved)\n");
+			break;
+		}
+
+		printf("\tDATA_STAT_AFTER_ERASE: 0x%01x\n",
+		       data_stat_after_erase);
+
+		printf("\tSD_SECURITY: 0x%01x (", sd_security);
+		switch (sd_security) {
+		case 0:
+			printf("no security)\n");
+			break;
+		case 1:
+			printf("not used)\n");
+			break;
+		case 2:
+			printf("SDSC card/security v1.01)\n");
+			break;
+		case 3:
+			printf("SDHC card/security v2.00)\n");
+			break;
+		case 4:
+			printf("SDXC card/security v3.xx)\n");
+			break;
+		default:
+			printf("reserved)\n");
+			break;
+		}
+
+		printf("\tSD_BUS_WIDTHS: 0x%01x (", sd_bus_widths);
+		if (BITS(sd_bus_widths, 2, 2))
+			printf("4bit, ");
+		if (BITS(sd_bus_widths, 0, 0))
+			printf("1bit, ");
+		printf(" bus)\n");
+
+		printf("\tSD_SPEC3: 0x%01x (", sd_spec3);
+		if (sd_spec >= 2) {
+			switch (sd_spec3) {
+			case 0:
+				printf("SD v2.00)\n");
+				break;
+			case 1:
+				printf("SD v3.0x)\n");
+				break;
+			}
+		} else {
+			printf("SD 1.xx)\n");
+		}
+
+		printf("\tEX_SECURITY: 0x%01x\n", ex_security);
+
+		printf("\tCMD_SUPPORT: 0x%01x (", cmd_support);
+		if (BITS(cmd_support, 1, 1))
+			printf("CMD23 ");
+		if (BITS(cmd_support, 0, 0))
+			printf("CMD20 ");
+		printf(" )\n");
+	} else {
+		printf("version: ");
+		switch (sd_spec) {
+		case 0:
+			printf("SD 1.0/1.01\n");
+			break;
+		case 1:
+			printf("SD 1.10\n");
+			break;
+		case 2:
+			switch (sd_spec3) {
+			case 0:
+				printf("SD 2.00\n");
+				break;
+			case 1:
+				printf("SD 3.0x\n");
+				break;
+			default:
+				printf("unknown\n");
+				break;
+			}
+			break;
+		case 3:
+			printf("SD 4.00\n");
+			break;
+		default:
+			printf("unknown\n");
+			break;
+		}
+
+		printf("bus widths: ");
+		if (BITS(sd_bus_widths, 2, 2))
+			printf("4bit, ");
+		if (BITS(sd_bus_widths, 0, 0))
+			printf("1bit, ");
+		printf("\b\b\n");
+	}
+}
+
+/* MMC/SD interface processing functions */
+void print_info(struct config *config, char *type,
+	char *cid, char *csd, char *scr, char *ext_csd)
+{
+	printf("type: '%s'\n", type);
+
+	if (!strcmp(type, "SD") && cid)
+		print_sd_cid(config, cid);
+	else if (!strcmp(type, "MMC") && cid)
+		print_mmc_cid(config, cid);
+
+	if (!strcmp(type, "SD") && scr)
+		print_sd_scr(config, scr);
+
+	if (!strcmp(type, "MMC") && csd)
+		print_mmc_csd(config, csd);
+	else if (!strcmp(type, "SD") && csd)
+		print_sd_csd(config, csd);
+
+	if (!strcmp(type, "MMC") && ext_csd)
+		print_mmc_ext_csd(config, ext_csd);
+}
+
+int process_dir(struct config *config, enum REG_TYPE reg)
+{
+	char *type = NULL, *cid = NULL, *csd = NULL, *scr = NULL, *ext_csd = NULL;
+	int ret = 0;
+
+	if (chdir(config->dir) < 0) {
+		fprintf(stderr,
+			"MMC/SD information directory '%s' does not exist.\n",
+			config->dir);
+		return -1;
+	}
+
+	type = read_file("type");
+	if (!type) {
+		fprintf(stderr,
+			"Could not read card interface type in directory '%s'.\n",
+			config->dir);
+		return -1;
+	}
+
+	if (strcmp(type, "MMC") && strcmp(type, "SD")) {
+		fprintf(stderr, "Unknown type: '%s'\n", type);
+		ret = -1;
+		goto err;
+	}
+
+	switch (reg) {
+	case CID:
+		cid = read_file("cid");
+		if (!cid) {
+			fprintf(stderr,
+				"Could not read card identity in directory '%s'.\n",
+				config->dir);
+			ret = -1;
+			goto err;
+		}
+		break;
+	case CSD:
+		csd = read_file("csd");
+		if (!csd) {
+			fprintf(stderr,
+				"Could not read card specific data in "
+				"directory '%s'.\n", config->dir);
+			ret = -1;
+			goto err;
+		}
+		break;
+	case SCR:
+		if (!strcmp(type, "SD")) {
+			scr = read_file("scr");
+			if (!scr) {
+				fprintf(stderr, "Could not read SD card "
+					"configuration in directory '%s'.\n",
+					config->dir);
+				ret = -1;
+				goto err;
+			}
+		}
+		break;
+	case EXT_CSD:
+		if (!strcmp(type, "MMC")) {
+			ext_csd = read_file("ext_csd");
+			if (!ext_csd) {
+				fprintf(stderr, "Could not read extra specific "
+					"data in directory '%s'.\n",
+					config->dir);
+				ret = -1;
+				goto err;
+			}
+		}
+		break;
+	default:
+		goto err;
+	}
+
+	print_info(config, type, cid, csd, scr, ext_csd);
+
+err:
+	free(ext_csd);
+	free(scr);
+	free(csd);
+	free(cid);
+	free(type);
+
+	return ret;
+}
+
+int lsmmc_main(struct config *config, int argc, char **argv)
+{
+	int ret;
+
+	ret =  parse_opts(argc, argv, config);
+	if (ret)
+		return ret;
+
+	if (!config->idsfile) {
+		int i = 0;
+		char *ids_path[] = {
+			"/etc/lsmmc.ids",
+			"/system/etc/lsmmc.ids",
+			"lsmmc.ids",
+			NULL,
+		};
+
+		while (!config->idsfile && ids_path[i]) {
+			if (!access(ids_path[i], F_OK))
+				config->idsfile = strdup(ids_path[i]);
+			i++;
+		}
+	}
+
+	return parse_ids(config);
+}
+
+int do_read_csd(int argc, char **argv)
+{
+	struct config config;
+	int ret, i;
+
+	memset(&config, 0, sizeof(config));
+	config.mmc_ids = calloc(256, sizeof(char *));
+	config.sd_ids = calloc(256, sizeof(char *));
+
+	ret = lsmmc_main(&config, argc, argv);
+	if (ret)
+		goto out;
+
+	if (config.dir)
+		ret = process_dir(&config, CSD);
+
+out:
+	for (i = 0; i < 256; i++) {
+		free(config.mmc_ids[i]);
+		free(config.sd_ids[i]);
+	}
+
+	free(config.mmc_ids);
+	free(config.sd_ids);
+	free(config.dir);
+	free(config.idsfile);
+
+	return ret;
+}
+
+int do_read_cid(int argc, char **argv)
+{
+	struct config config;
+	int ret, i;
+
+	memset(&config, 0, sizeof(config));
+	config.mmc_ids = calloc(256, sizeof(char *));
+	config.sd_ids = calloc(256, sizeof(char *));
+
+	ret = lsmmc_main(&config, argc, argv);
+	if (ret)
+		goto out;
+
+	if (config.dir)
+		ret = process_dir(&config, CID);
+
+out:
+	for (i = 0; i < 256; i++) {
+		free(config.mmc_ids[i]);
+		free(config.sd_ids[i]);
+	}
+
+	free(config.mmc_ids);
+	free(config.sd_ids);
+	free(config.dir);
+	free(config.idsfile);
+
+	return ret;
+}
+
+int do_read_scr(int argc, char **argv)
+{
+	struct config config;
+	int ret, i;
+
+	memset(&config, 0, sizeof(config));
+	config.mmc_ids = calloc(256, sizeof(char *));
+	config.sd_ids = calloc(256, sizeof(char *));
+
+	ret = lsmmc_main(&config, argc, argv);
+	if (ret)
+		goto out;
+
+	if (config.dir)
+		ret = process_dir(&config, SCR);
+
+out:
+	for (i = 0; i < 256; i++) {
+		free(config.mmc_ids[i]);
+		free(config.sd_ids[i]);
+	}
+
+	free(config.mmc_ids);
+	free(config.sd_ids);
+	free(config.dir);
+	free(config.idsfile);
+
+	return ret;
+}
diff --git a/lsmmc.ids b/lsmmc.ids
new file mode 100644
index 0000000..e79932d
--- /dev/null
+++ b/lsmmc.ids
@@ -0,0 +1,27 @@ 
+sd:0x01:Panasonic
+sd:0x02:Toshiba/Kingston/Viking
+sd:0x03:SanDisk
+sd:0x08:Silicon Power
+sd:0x18:Infineon
+sd:0x1b:Transcend
+sd:0x1c:Transcend
+sd:0x1d:Corsair
+sd:0x1e:Transcend
+sd:0x1f:Kingston
+sd:0x28:Lexar
+sd:0x30:SanDisk
+sd:0x33:STMicroelectronics
+sd:0x41:Kingston
+sd:0x6f:STMicroelectronics
+sd:0x89:Unknown
+mmc:0x00:SanDisk
+mmc:0x02:Kingston/SanDisk
+mmc:0x03:Toshiba
+mmc:0x05:Unknown
+mmc:0x06:Unknown
+mmc:0x11:Toshiba
+mmc:0x15:Samsung/SanDisk/LG
+mmc:0x37:KingMax
+mmc:0x44:SanDisk
+mmc:0x2c:Kingston
+mmc:0x70:Kingston
diff --git a/mmc.c b/mmc.c
index a13d9ae..ed5bbf5 100644
--- a/mmc.c
+++ b/mmc.c
@@ -175,6 +175,24 @@  static struct Command commands[] = {
 		"NOTE! The cache is an optional feature on devices >= eMMC4.5.",
 	  NULL
 	},
+	{ do_read_csd, -1,
+	  "csd read", "<device path>\n"
+		  "Print CSD data from <device path>.\n"
+		  "The device path should specify the csd file directory.",
+	  NULL
+	},
+	{ do_read_cid, -1,
+	  "cid read", "<device path>\n"
+		  "Print CID data from <device path>.\n"
+		  "The device path should specify the cid file directory.",
+	  NULL
+	},
+	{ do_read_scr, -1,
+	  "scr read", "<device path>\n"
+		  "Print SCR data from <device path>.\n"
+		  "The device path should specify the scr file directory.",
+	  NULL
+	},
 	{ 0, 0, 0, 0 }
 };
 
diff --git a/mmc_cmds.h b/mmc_cmds.h
index 75d8f8c..32a4001 100644
--- a/mmc_cmds.h
+++ b/mmc_cmds.h
@@ -36,3 +36,6 @@  int do_rpmb_read_block(int nargs, char **argv);
 int do_rpmb_write_block(int nargs, char **argv);
 int do_cache_en(int nargs, char **argv);
 int do_cache_dis(int nargs, char **argv);
+int do_read_scr(int argc, char **argv);
+int do_read_cid(int argc, char **argv);
+int do_read_csd(int argc, char **argv);