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

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

Commit Message

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

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
Sebastian Rasmussen Feb. 2, 2016, 10:04 p.m. UTC | #2
> 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.

> 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?

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

 / Sebastian
--
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
Baolin Wang Feb. 3, 2016, 2:26 a.m. UTC | #3
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 Feb. 3, 2016, 2:34 a.m. UTC | #4
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

Patch
diff mbox

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);