@@ -1,5 +1,7 @@
.mmc.o.d
.mmc_cmds.o.d
+.mmc_cmds-emmc5.o.d
mmc
mmc.o
mmc_cmds.o
+mmc_cmds-emmc5.o
@@ -1,7 +1,7 @@
CC ?= gcc
AM_CFLAGS = -D_FILE_OFFSET_BITS=64 -D_FORTIFY_SOURCE=2
CFLAGS ?= -g -O2
-objects = mmc.o mmc_cmds.o
+objects = mmc.o mmc_cmds.o mmc_cmds-emmc5.o
CHECKFLAGS = -Wall -Werror -Wuninitialized -Wundef
@@ -44,7 +44,7 @@ clean:
$(MAKE) -C man clean
install: $(progs) install-man
- $(INSTALL) -m755 -d $(DESTDIR)$(bindir)
+ $(INSTALL) -m 755 -d $(DESTDIR)$(bindir)
$(INSTALL) $(progs) $(DESTDIR)$(bindir)
.PHONY: all clean install manpages install-man
@@ -20,7 +20,10 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <errno.h>
+#include <linux/types.h>
+#include "mmc.h"
#include "mmc_cmds.h"
#define MMC_VERSION "0.1"
@@ -37,9 +40,9 @@ struct Command {
if < 0, _minimum_ number of arguments */
char *verb; /* verb */
char *help; /* help lines; from the 2nd line onward they
- are automatically indented */
- char *adv_help; /* advanced help message; from the 2nd line
- onward they are automatically indented */
+ are automatically indented */
+ char *adv_help; /* advanced help message; from the 2nd line
+ onward they are automatically indented */
/* the following fields are run-time filled by the program */
char **cmds; /* array of subcommands */
@@ -110,9 +113,24 @@ static struct Command commands[] = {
"Send Sanitize command to the <device>.\nThis will delete the unmapped memory region of the device.",
NULL
},
+ { do_firmware_update, -2,
+ "firmware upload", "<firmware_file> " "<device>\n"
+ "Upload/Update firmware on <device> using <firmware_file>.\n",
+ NULL
+ },
{ 0, 0, 0, 0 }
};
+const char *manfid_lookup[0x100] = {
+ [MANFID_PANASONIC] = "Panasonic",
+ [MANFID_KINGSTON] = "Kingston",
+ [MANFID_SANDISK] = "Sandisk",
+ [MANFID_SAMSUNG] = "Samsung",
+ [MANFID_TOSHIBA] = "Toshiba",
+ [MANFID_SANDISK_SEM] = "Sandisk_SEM",
+ [MANFID_KINGSTON_MMC] = "Kingston_MMC"
+};
+
static char *get_prgname(char *programname)
{
char *np;
@@ -363,11 +381,10 @@ static int parse_args(int argc, char **argv,
return -2;
}
- if (prepare_args( nargs_, args_, prgname, matchcmd )){
- fprintf(stderr, "ERROR: not enough memory\\n");
+ if (prepare_args( nargs_, args_, prgname, matchcmd )){
+ fprintf(stderr, "ERROR: not enough memory\\n");
return -20;
- }
-
+ }
return 1;
}
@@ -24,12 +24,25 @@
#define MMC_BLOCK_MAJOR 179
/* From kernel linux/mmc/mmc.h */
+#define MMC_GO_IDLE_STATE 0 /* bc */
+#define MMC_ALL_SEND_CID 2 /* bcr R2 */
#define MMC_SWITCH 6 /* ac [31:0] See below R1b */
+#define MMC_SELECT_CARD 7 /* ac [31:16] RCA R1 */
#define MMC_SEND_EXT_CSD 8 /* adtc R1 */
-#define MMC_SEND_STATUS 13 /* ac [31:16] RCA R1 */
+#define MMC_SEND_CID 10 /* ac [31:16] RCA R2 */
+#define MMC_STOP_TRANSMISSION 12 /* ac R1b */
+#define MMC_SEND_STATUS 13 /* ac [31:16] RCA R1 */
+#define MMC_READ_SINGLE_BLOCK 17 /* adtc [31:0] data addr R1 */
+#define MMC_READ_MULTIPLE_BLOCK 18 /* adtc [31:0] data addr R1 */
+#define MMC_WRITE_BLOCK 24 /* adtc [31:0] data addr R1 */
+#define MMC_WRITE_MULTIPLE_BLOCK 25 /* adtc R1 */
+
+#define MMC_GEN_CMD 56 /* adtc [0] RD/WR R1 */
+
#define R1_SWITCH_ERROR (1 << 7) /* sx, c */
#define MMC_SWITCH_MODE_WRITE_BYTE 0x03 /* Set target to value */
+
/*
* EXT_CSD fields
*/
@@ -125,3 +138,17 @@
#define MMC_RSP_R1 (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE)
#define MMC_RSP_R1B (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE|MMC_RSP_BUSY)
+
+
+/* Manufacturer IDs as shown by /sys/block/mmcblk0/device/manfid */
+#define MANFID_PANASONIC 0x000001
+#define MANFID_KINGSTON 0x000002
+#define MANFID_SANDISK 0x000003
+#define MANFID_SAMSUNG 0x000015
+#define MANFID_TOSHIBA 0x00001d
+#define MANFID_SANDISK_SEM 0x000045 /* seen with "SEM16G" */
+#define MANFID_KINGSTON_MMC 0x000070 /* Seen on embedded device */
+
+#if 0
+const char *manfid_lookup[];
+#endif
new file mode 100644
@@ -0,0 +1,140 @@
+/*
+ * eMMC 5.0+ Specific tools
+ *
+ * Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License v2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 021110-1307, USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h> /* for memset() */
+#include <errno.h>
+#include <linux/types.h>
+#include <sys/ioctl.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <limits.h>
+#include <ctype.h>
+
+#include "mmc.h"
+#include "mmc_cmds.h"
+
+
+/* ext_csd[] offsets specific to FFU */
+#define FFU_STATUS 26
+#define FFU_ARG 487
+#define FFU_FEATURES 492
+#define SUPPORTED_MODES 493
+
+int do_emmc5_fw_update(int fd, __u8 *cid, __u8 *ext_csd,
+ char *emmc_fw, size_t fwsize)
+{
+ struct mmc_ioc_cmd idata;
+ int retcode = 0;
+ int ret;
+
+ /* 1) Confirm device supports FFU */
+ if ((ext_csd[SUPPORTED_MODES] & 1) == 0) {
+ fprintf(stderr, "eMMC 5.0 FFU not supported\n");
+ return -EINVAL;
+ }
+
+ /* 2) Host send CMD6 to set MODE_CONFIG[30] = 0x01 */
+ memset(&idata, 0, sizeof(idata));
+ idata.opcode = MMC_SWITCH;
+ idata.arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) |
+ (30 << 16) | /* index */
+ (1 << 8) | /* value */
+ EXT_CSD_CMD_SET_NORMAL;
+ idata.flags = MMC_RSP_R1B | MMC_CMD_AC;;
+ ret = ioctl(fd, MMC_IOC_CMD, &idata);
+ if (ret) {
+ retcode = ret;
+ printf("ioctl: MMC_SWITCH (eMMC 5.0 FW Update) %m\n");
+ goto abort_update;
+ }
+
+ /* 3) send CMD25 with FFU_ARG */
+ memset(&idata, 0, sizeof(idata));
+ idata.write_flag = 1;
+ idata.data_ptr = (__u64) ((unsigned long) emmc_fw); /* Write this FW */
+ idata.opcode = MMC_WRITE_MULTIPLE_BLOCK;
+ idata.arg = le32toh( *((__u32 *) &ext_csd[FFU_ARG]));
+ idata.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;;
+ ret = ioctl(fd, MMC_IOC_CMD, &idata);
+ if (ret) {
+ retcode = ret;
+ printf("ioctl:MMC_WRITE_MULTIPLE_BLOCK (eMMC 5.0 FW Update) %m\n");
+ goto abort_update;
+ }
+
+ /* 3) check FFU_STATUS[26] */
+ ret = read_extcsd(fd, ext_csd);
+ if (ret) {
+ retcode = ret;
+ fprintf(stderr, "read_extcsd error (%d): %m\n", ret);
+ goto abort_update;
+ }
+
+ switch(ext_csd[FFU_STATUS]) {
+ case 0: break;
+ case 0x10:
+ fprintf(stderr, "eMMC 5.0 FFU had general error and failed (EIO).\n");
+ retcode = -EIO;
+ break;
+ case 0x11:
+ fprintf(stderr, "eMMC 5.0 FFU did not complete (EAGAIN).\n");
+ retcode = -EAGAIN;
+ break;
+ case 0x12:
+ fprintf(stderr, "eMMC 5.0 FFU download failed: checksum "
+ "mismatch or was interrupted (EINTR).\n");
+ retcode = -EINTR;
+ break;
+ default:
+ fprintf(stderr, "eMMC 5.0 FFU unknown error: %x\n", ext_csd[FFU_STATUS]);
+ retcode = -EIO;
+ }
+
+ /* 4) check NUMBER_OF_FW_SECTORS_CORRECTLY_PROGRAMMED[305-302] */
+ ret = ext_csd[302] << 0 | (ext_csd[303] << 8) |
+ (ext_csd[304] << 16) | (ext_csd[305] << 24);
+
+ /* convert that gibberish to bytes */
+ ret *= 512;
+ if (ext_csd[61] == 1)
+ ret *= 8; /* 4K sectors */
+
+ if (ret != fwsize) {
+ fprintf(stderr, "eMMC 5.0 FFU Failed? (fwsize: %zu,"
+ " PROGRAMMED_SECTORS: %d)\n", fwsize, ret);
+ }
+
+abort_update:
+ /* 4) send CMD0
+ * This should reset the device and force use of the new firmware.
+ */
+ memset(&idata, 0, sizeof(idata));
+ idata.opcode = MMC_GO_IDLE_STATE;
+ ret = ioctl(fd, MMC_IOC_CMD, &idata);
+ if (ret) {
+ retcode = ret;
+ perror("ioctl:MMC_GO_IDLE_STATE (eMMC 5.0 FFU)");
+ }
+
+ return retcode;
+}
@@ -1,3 +1,4 @@
+
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
@@ -17,9 +18,10 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <sys/ioctl.h>
-#include <sys/types.h>
+#include <errno.h>
#include <dirent.h>
+#include <sys/ioctl.h>
+#include <linux/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
@@ -30,23 +32,27 @@
#include "mmc.h"
#include "mmc_cmds.h"
+#define EXT_CSD_SIZE 512
+#define CID_SIZE 16
+
+
int read_extcsd(int fd, __u8 *ext_csd)
{
int ret = 0;
struct mmc_ioc_cmd idata;
memset(&idata, 0, sizeof(idata));
- memset(ext_csd, 0, sizeof(__u8) * 512);
+ memset(ext_csd, 0, sizeof(__u8) * EXT_CSD_SIZE);
idata.write_flag = 0;
idata.opcode = MMC_SEND_EXT_CSD;
idata.arg = 0;
idata.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
- idata.blksz = 512;
+ idata.blksz = EXT_CSD_SIZE;
idata.blocks = 1;
mmc_ioc_cmd_set_data(idata, ext_csd);
ret = ioctl(fd, MMC_IOC_CMD, &idata);
if (ret)
- perror("ioctl");
+ perror("ioctl SEND_EXT_CSD");
return ret;
}
@@ -67,7 +73,30 @@ int write_extcsd_value(int fd, __u8 index, __u8 value)
ret = ioctl(fd, MMC_IOC_CMD, &idata);
if (ret)
- perror("ioctl");
+ perror("ioctl Write EXT CSD");
+
+ return ret;
+}
+
+int read_cid(int fd, __u8 *cid)
+{
+ int ret = 0;
+ struct mmc_ioc_cmd idata;
+
+ memset(&idata, 0, sizeof(idata));
+ memset(cid, 0, sizeof(__u8) * CID_SIZE);
+
+ idata.write_flag = 0;
+ idata.opcode = MMC_SEND_CID;
+ idata.arg = 0;
+ idata.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
+ idata.blksz = CID_SIZE;
+ idata.blocks = 1;
+ mmc_ioc_cmd_set_data(idata, cid);
+
+ ret = ioctl(fd, MMC_IOC_CMD, &idata);
+ if (ret)
+ perror("ioctl SEND_CID");
return ret;
}
@@ -103,17 +132,11 @@ void print_writeprotect_status(__u8 *ext_csd)
reg = ext_csd[EXT_CSD_BOOT_WP];
printf("Boot Area Write protection [BOOT_WP]: 0x%02x\n", reg);
- printf(" Power ro locking: ");
- if (reg & EXT_CSD_BOOT_WP_B_PWR_WP_DIS)
- printf("not possible\n");
- else
- printf("possible\n");
+ printf(" Power ro locking: %spossible\n",
+ (reg & EXT_CSD_BOOT_WP_B_PWR_WP_DIS) ? "not " : "");
- printf(" Permanent ro locking: ");
- if (reg & EXT_CSD_BOOT_WP_B_PERM_WP_DIS)
- printf("not possible\n");
- else
- printf("possible\n");
+ printf(" Permanent ro locking: %spossible\n",
+ (reg & EXT_CSD_BOOT_WP_B_PERM_WP_DIS) ? "not " : "");
printf(" ro lock status: ");
if (reg & EXT_CSD_BOOT_WP_B_PWR_WP_EN)
@@ -127,7 +150,7 @@ void print_writeprotect_status(__u8 *ext_csd)
int do_writeprotect_get(int nargs, char **argv)
{
- __u8 ext_csd[512];
+ __u8 ext_csd[EXT_CSD_SIZE];
int fd, ret;
char *device;
@@ -155,7 +178,7 @@ int do_writeprotect_get(int nargs, char **argv)
int do_writeprotect_set(int nargs, char **argv)
{
- __u8 ext_csd[512], value;
+ __u8 ext_csd[EXT_CSD_SIZE], value;
int fd, ret;
char *device;
@@ -191,7 +214,7 @@ int do_writeprotect_set(int nargs, char **argv)
int do_disable_512B_emulation(int nargs, char **argv)
{
- __u8 ext_csd[512], native_sector_size, data_sector_size, wr_rel_param;
+ __u8 ext_csd[EXT_CSD_SIZE], native_sector_size, data_sector_size, wr_rel_param;
int fd, ret;
char *device;
@@ -301,7 +324,7 @@ int do_write_boot_en(int nargs, char **argv)
int do_hwreset(int value, int nargs, char **argv)
{
- __u8 ext_csd[512];
+ __u8 ext_csd[EXT_CSD_SIZE];
int fd, ret;
char *device;
@@ -360,7 +383,7 @@ int do_hwreset_dis(int nargs, char **argv)
int do_write_bkops_en(int nargs, char **argv)
{
- __u8 ext_csd[512], value = 0;
+ __u8 ext_csd[EXT_CSD_SIZE], value = 0;
int fd, ret;
char *device;
@@ -502,7 +525,7 @@ int set_partitioning_setting_completed(int dry_run, const char * const device,
int do_enh_area_set(int nargs, char **argv)
{
__u8 value;
- __u8 ext_csd[512];
+ __u8 ext_csd[EXT_CSD_SIZE];
int fd, ret;
char *device;
int dry_run = 1;
@@ -640,7 +663,7 @@ int do_enh_area_set(int nargs, char **argv)
int do_write_reliability_set(int nargs, char **argv)
{
__u8 value;
- __u8 ext_csd[512];
+ __u8 ext_csd[EXT_CSD_SIZE];
int fd, ret;
int dry_run = 1;
@@ -701,7 +724,7 @@ int do_write_reliability_set(int nargs, char **argv)
int do_read_extcsd(int nargs, char **argv)
{
- __u8 ext_csd[512], ext_csd_rev, reg;
+ __u8 ext_csd[EXT_CSD_SIZE], ext_csd_rev, reg;
__u32 regl;
int fd, ret;
char *device;
@@ -1218,6 +1241,143 @@ int do_sanitize(int nargs, char **argv)
}
return ret;
-
}
+int do_firmware_update(int nargs, char **argv)
+{
+ char *emmc_fw;
+ char *device;
+ char *fwfilename;
+ struct stat fwfilestat;
+ size_t fwsize;
+ int ret = 0;
+ int devfd,fwfd;
+ __u8 ext_csd[EXT_CSD_SIZE];
+ __u8 cid[CID_SIZE];
+ __u8 ext_csd_rev;
+
+
+ CHECK(nargs != 4, "Usage: mmc firmware update"
+ " --I_want_to_destroy_my_drive"
+ " </path/to/mmcblkX> </path/to/FW.bin>\n",
+ exit(1));
+
+
+ /* Lesson from hdparm: user must be aware of the risks
+ * Key here is the additional command line flag be UNDOCUMENTED.
+ * User _must_ read and KNOW this is risky at runtime.
+ */
+ if (strncmp(argv[4], "--I_want_to_destroy_my_drive", 28)) {
+ fprintf(stderr,"ERROR: Please specify --I_want_to_destroy_my_drive"
+ " as first parameter to firmware update.\n");
+
+ exit(1);
+ }
+
+ emmc_fw = malloc(MMC_IOC_MAX_BYTES);
+ if (!emmc_fw)
+ return -ENOMEM;
+
+ device = argv[5];
+
+ devfd = open(device, O_RDWR);
+ if (devfd < 0) {
+ fprintf(stderr,"ERROR: open %s: %m\n", device); /* %m = errno */
+ goto out_free;
+ }
+
+ /* 1) read device version and attributes */
+ ret = read_extcsd(devfd, ext_csd);
+ if (ret) {
+ fprintf(stderr, "ERROR: Read EXT_CSD from %s: %m\n", device);
+ goto out_dev;
+ }
+
+ ext_csd_rev = ext_csd[192];
+
+ if (ext_csd_rev < 7) {
+ fprintf(stderr, "ERROR: Can not update firmware"
+ ": %s is pre-emmc 5.0 vintage\n", device);
+ goto out_dev;
+ }
+
+ printf("%s: FW is currently %c%c%c%c%c%c%c%c\n",
+ device,
+ ext_csd[254], ext_csd[255], ext_csd[256], ext_csd[257],
+ ext_csd[258], ext_csd[259], ext_csd[260], ext_csd[261]
+ );
+
+ /* 2) confirm SUPPORTED_MODES has FFU bit set */
+ if (!(ext_csd[493] & 1)) {
+ fprintf(stderr, "ERROR: %s is eMMC 5.0 device"
+ "but does not support FFU.\n", device);
+ goto out_dev;
+ }
+
+ /* 3) confirm FW updated is NOT disabled on this device */
+ if (ext_csd[169] & 1) {
+ fprintf(stderr, "ERROR: %s is eMMC 5.0 device"
+ "but FFU is disabled.\n", device);
+ goto out_dev;
+ }
+
+ /* 4) read the device manfid */
+ ret = read_cid(devfd, cid);
+ if (ret) {
+ fprintf(stderr, "ERROR: Read EXT_CSD from %s: %m\n", device);
+ goto out_dev;
+ }
+
+ /* 5) Fetch the FW image */
+ fwfilename = argv[2];
+
+ fwfd = open(fwfilename, O_RDONLY);
+ if (fwfd < 0) {
+ /* %m = errno */
+ fprintf(stderr,"ERROR: open %s: %m", fwfilename);
+ goto out_dev;
+ }
+
+ ret = fstat(fwfd, &fwfilestat);
+ if (ret) {
+ /* %m = errno */
+ fprintf(stderr,"ERROR: fstat %s: %m", fwfilename);
+ goto out_fw;
+ }
+
+ fwsize = fwfilestat.st_size;
+ if (fwsize > MMC_IOC_MAX_BYTES) {
+ fprintf(stderr,"ERROR: %s is > %ld bytes long (max allowed)\n",
+ fwfilename, MMC_IOC_MAX_BYTES);
+ goto out_fw;
+ }
+
+ ret = read(fwfd, emmc_fw, fwsize);
+ if (ret < fwsize) {
+ fprintf(stderr,"ERROR: did not read all %zu bytes of %s"
+ "(%d bytes long)\n",
+ fwsize, fwfilename, ret);
+ goto out_fw;
+ }
+
+ ret = do_emmc5_fw_update(devfd, cid, ext_csd, emmc_fw, fwsize);
+ if (ret) {
+ fprintf(stderr,"ERROR: emmc5_fw_update failed for %s: %d", fwfilename, ret);
+ goto out_fw;
+ }
+ cid[13] = '\0'; /* make sure string is NULL terminated */
+
+ printf("%s: FW updated to %8s (0x%2x%2x%2x%2x%2x%2x%2x%2x)\n",
+ device, &(ext_csd[254]),
+ ext_csd[254], ext_csd[255], ext_csd[256], ext_csd[257],
+ ext_csd[258], ext_csd[259], ext_csd[260], ext_csd[261]
+ );
+
+out_fw:
+ close(fwfd);
+out_dev:
+ close(devfd);
+out_free:
+ free(emmc_fw);
+ return ret;
+}
@@ -28,3 +28,9 @@ int do_sanitize(int nargs, char **argv);
int do_status_get(int nargs, char **argv);
int do_enh_area_set(int nargs, char **argv);
int do_write_reliability_set(int nargs, char **argv);
+int do_firmware_update(int nargs, char **argv);
+
+int read_extcsd(int fd, __u8 *ext_csd);
+int write_extcsd_value(int fd, __u8 index, __u8 value);
+
+int do_emmc5_fw_update(int devfd, __u8 *cid, __u8 *ext_csd, char *emmc_fw, size_t fwsize);
Field Firmware Update feature is new for 5.0 spec. Code written per JESD84-B50.pdf spec available from: http://www.jedec.org/standards-documents/technology-focus-areas/flash-memory-ssds-ufs-emmc/e-mmc Change-Id: I0a22e9862876421630f53ac27fc0a161a9c70131 Signed-off-by: Grant Grundler <grundler@chromium.org> --- V3: - reference FFU_ARG instead of hardcoding 0x0000ffff - Set MODE_CONFIG[30] to 1 as part of CMD6 - fixed sequence numbers in comments - fix compile warning. - Still missing support for MODE_OPERATION_CODES This version can be cherry-picked into local branch with: git fetch https://chromium.googlesource.com/chromiumos/third_party/mmc-utils refs/changes/71/185471/1 && git cherry-pick FETCH_HEAD Gwendal Grignou has added eMMC 5.0 support to "mmc extcsd read" and I don't see that in Chris' mmc-utils tree. Might want to first grab: "Decode EXT_CSD of eMMC 5.0 device" git fetch https://chromium.googlesource.com/chromiumos/third_party/mmc-utils refs/changes/75/184175/5 && git cherry-pick FETCH_HEAD V2: - Fix compiler borkage in use of strncmp(). - Use '\0' instead of 0 in char assignment. V1: This patch needs to be reviewed and tested by _ANY_ eMMC 5.0 HW vendor. If you represent an eMMC 5.0 HW vendor and expect your part will be used in a ChromeOS device, make sure FFU works with mmc-utils. ... .gitignore | 2 + Makefile | 4 +- mmc.c | 31 ++++++-- mmc.h | 29 +++++++- mmc_cmds-emmc5.c | 140 +++++++++++++++++++++++++++++++++++++ mmc_cmds.c | 210 ++++++++++++++++++++++++++++++++++++++++++++++++------- mmc_cmds.h | 6 ++ 7 files changed, 387 insertions(+), 35 deletions(-) create mode 100644 mmc_cmds-emmc5.c