summaryrefslogtreecommitdiffstats
path: root/ports/beagleboneblack/am335x_mmc.c
blob: 10aef239abbbde1d65b0a9486b052930920edbf6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
/*
 * Copyright (c) 2015 Jarielle Catbagan <jcatbagan93@gmail.com>
 *
 * The license and distribution terms for this file may be
 * found in the file LICENSE in this distribution or at
 * http://www.apache.org/licenses/LICENSE-2.0
 */

#include "genlib.h"
#include "cli.h"
#include "stddefs.h"
#include "am335x.h"
#include "am335x_mmc.h"

uint16_t mmcrca;
int mmcInum;

char *mmcHelp[] = {
	"MultiMediaCard Interface",
	"[options] {operation} [args]...",
#if INCLUDE_VERBOSEHELP
	"",
	"Options:",
	" -i ##	interface # (default is 0)",
	" -v 	additive verbosity",
	"",
	"Operations:",
	" init",
	" read {dest} {blk} {blktot}",
	" write {source} {blk} {blktot}",
#endif /* INCLUDE_VERBOSEHELP */
	0
};

int
mmccmd(uint32_t cmd, uint32_t arg, uint32_t resp[4])
{
	/* Clear the SD_STAT register for proper update of status bits after CMD invocation */
	MMC1_REG(SD_STAT) = 0xFFFFFFFF;

	MMC1_REG(SD_ARG) = arg;
	MMC1_REG(SD_CMD) = cmd;

	/* CMDx complete? */
	while (!(MMC1_REG(SD_STAT) & (SD_STAT_CC | SD_STAT_ERRI)));

	resp[0] = MMC1_REG(SD_RSP10);
	resp[1] = MMC1_REG(SD_RSP32);
	resp[2] = MMC1_REG(SD_RSP54);
	resp[3] = MMC1_REG(SD_RSP76);

	/* CMDx error? */
	if (MMC1_REG(SD_STAT) & SD_STAT_ERRI)
		return(-1);
	else
		return(0);
}

int
mmc(int argc, char *argv[])
{
	char *cmd, *buf;
	int opt, verbose, mmcret, blknum, blkcnt;

	verbose = 0;

	while ((opt = getopt(argc, argv, "i:v")) != -1) {
		switch (opt) {
			case 'i':
				mmcInum = atoi(optarg);
				break;
			case 'v':
				verbose++;
				break;
			default:
				return(CMD_PARAM_ERROR);
		}
	}

	if (argc < optind + 1)
		return(CMD_PARAM_ERROR);

	cmd = argv[optind];

	if (mmcInstalled(mmcInum) == 0) {
		printf("MMC not installed\n");
		return(CMD_FAILURE);
	}

	if (strcmp(cmd, "init") == 0) {
		mmcret = mmcInit(mmcInum, verbose);
		if(mmcret < 0) {
			printf("mmcInit returned %d\n", mmcret);
			return(CMD_FAILURE);
		}
	}
	else if (strcmp(cmd, "read") == 0) {
		if (argc != (optind + 4))
			return(CMD_PARAM_ERROR);

		buf = (char *)strtoul(argv[optind + 1], 0, 0);
		blknum = strtoul(argv[optind + 2], 0, 0);
		blkcnt = strtoul(argv[optind + 3], 0, 0);

		mmcret = mmcRead(mmcInum, buf, blknum, blkcnt);
		if (mmcret < 0) {
			printf("mmcRead returned %d\n", mmcret);
			return(CMD_FAILURE);
		}
	}
	else if (strcmp(cmd, "write") == 0) {
		if (argc != (optind + 4))
			return(CMD_PARAM_ERROR);

		buf = (char *)strtoul(argv[optind + 1], 0, 0);
		blknum = strtoul(argv[optind + 2], 0, 0);
		blkcnt = strtoul(argv[optind + 3], 0, 0);

		mmcret = mmcWrite(mmcInum, buf, blknum, blkcnt);
		if (mmcret < 0) {
			printf("mmcWrite returned %d\n", mmcret);
			return(CMD_FAILURE);
		}
	}
	else {
		printf("mmc op <%s> not found\n", cmd);
		return(CMD_FAILURE);
	}

	return(CMD_SUCCESS);
}

int
mmcInit(int interface, int verbose)
{
	uint32_t cmd, arg, resp[4];

	/* Enable MMC1 clocks */
	CM_PER_REG(CM_PER_MMC1_CLKCTRL) |= CM_PER_MMC1_CLKCTRL_MODULEMODE_ENABLE;
	while (CM_PER_REG(CM_PER_MMC1_CLKCTRL) & CM_PER_MMC0_CLKCTRL_IDLEST);

	/* Reset the MMC1 Controller */
	MMC1_REG(SD_SYSCONFIG) = SD_SYSCONFIG_SOFTRESET;
	while (!(MMC1_REG(SD_SYSSTATUS) & SD_SYSSTATUS_RESETDONE));

	/* Reset the command and data lines */
	MMC1_REG(SD_SYSCTL) |= SD_SYSCTL_SRA;
	while (MMC1_REG(SD_SYSCTL) & SD_SYSCTL_SRA);

	/* Configure the MMC1 controller capabilities to enable 3.0 V operating voltage */
	MMC1_REG(SD_CAPA) |= SD_CAPA_VS30;

	/* Configure SD_IE register to update certain status bits in SD_STAT */
	MMC1_REG(SD_IE) = SD_IE_BADA_ENABLE | SD_IE_CERR_ENABLE | SD_IE_ACE_ENABLE |
		SD_IE_DEB_ENABLE | SD_IE_DCRC_ENABLE | SD_IE_DTO_ENABLE | SD_IE_CIE_ENABLE |
		SD_IE_CEB_ENABLE | SD_IE_CCRC_ENABLE | SD_IE_CIRQ_ENABLE | SD_IE_CREM_ENABLE |
		SD_IE_CINS_ENABLE | SD_IE_BRR_ENABLE | SD_IE_BWR_ENABLE |
		SD_IE_TC_ENABLE | SD_IE_CC_ENABLE;

	/* Configure the operating voltage to 3.0 V */
	MMC1_REG(SD_HCTL) &= ~(SD_HCTL_SDVS);
	MMC1_REG(SD_HCTL) |= SD_HCTL_SDVS_VS30;

	/* Turn on the bus */
	MMC1_REG(SD_HCTL) |= SD_HCTL_SDBP;
	while (!(MMC1_REG(SD_HCTL) & SD_HCTL_SDBP));

	/* Enable the internal clock */
	MMC1_REG(SD_SYSCTL) |= SD_SYSCTL_ICE;

	/* Configure Clock Frequency Select to 100 KHz */
	MMC1_REG(SD_SYSCTL) = (MMC1_REG(SD_SYSCTL) & ~SD_SYSCTL_CLKD) | (960 << 6);

	/* Wait for clock to stabilize */
	while (!(MMC1_REG(SD_SYSCTL) & SD_SYSCTL_ICS));

	/* Configure SD_SYSCONFIG */
	MMC1_REG(SD_SYSCONFIG) &= ~(SD_SYSCONFIG_CLOCKACTIVITY | SD_SYSCONFIG_SIDLEMODE);
	MMC1_REG(SD_SYSCONFIG) |= SD_SYSCONFIG_SIDLEMODE_WKUP | SD_SYSCONFIG_ENAWAKEUP_ENABLE |
		SD_SYSCONFIG_AUTOIDLE_AUTOGATE;

	/* Enable the clock to the eMMC */
	MMC1_REG(SD_SYSCTL) |= SD_SYSCTL_CEN;

	/* Perform the Initialization Stream as specified in the AM335x TRM, Section 18.3.3.2
	   "Card Detection, Identification, and Selection" */
	MMC1_REG(SD_CON) |= SD_CON_INIT;
	/* Clear the SD_STAT register */
	MMC1_REG(SD_STAT) = 0xFFFFFFFF;
	MMC1_REG(SD_ARG) = 0x00000000;
	MMC1_REG(SD_CMD) = 0x00000000;
	while (!(MMC1_REG(SD_STAT) & SD_STAT_CC));
	/* Clear CC flag in SD_STAT */
	MMC1_REG(SD_STAT) |= SD_STAT_CC;
	MMC1_REG(SD_CON) &= ~SD_CON_INIT;

	/* Clear the SD_STAT register */
	MMC1_REG(SD_STAT) = 0xFFFFFFFF;

	/* Enable open-drain mode until we enter Stand-by State as illustrated in the
	   JEDEC JESD84-A43 Embedded MultiMediaCard Product Standard specification, Table 5 */
	MMC1_REG(SD_CON) |= SD_CON_OD;

	/* Send CMD0/GO_IDLE_STATE to reset the eMMC on MMC1 interface */
	arg = 0x00000000;
	cmd = SD_CMD_CMD0_GO_IDLE_STATE | SD_CMD_CMD_TYPE_NORMAL | SD_CMD_DP_NO_DATA_PRESENT |
		SD_CMD_CICE_DISABLE | SD_CMD_CCCE_DISABLE | SD_CMD_RSP_TYPE_NO_RESPONSE;
	if (mmccmd(cmd, arg, resp) == -1)
		return(-1);

	/* Send CMD1 and poll busy bit in response */
	do {
		arg = 0x40FF8000;
		cmd = SD_CMD_CMD1_SEND_OP_COND | SD_CMD_CMD_TYPE_NORMAL | SD_CMD_DP_NO_DATA_PRESENT |
			SD_CMD_CICE_DISABLE | SD_CMD_CCCE_DISABLE | SD_CMD_RSP_TYPE_R3;
		if (mmccmd(cmd, arg, resp) == -1)
			return(-1);
	} while (!(MMC1_REG(SD_RSP10) & 0x80000000));

	/* Send CMD2, i.e. ALL_SEND_CID */
	arg = 0x00000000;
	cmd = SD_CMD_CMD2_ALL_SEND_CID | SD_CMD_CMD_TYPE_NORMAL | SD_CMD_DP_NO_DATA_PRESENT |
		SD_CMD_CICE_DISABLE | SD_CMD_CCCE_ENABLE | SD_CMD_RSP_TYPE_R2;
	if (mmccmd(cmd, arg, resp) == -1)
		return(-1);

	/* Set RCA of eMMC */
	mmcrca = 0x3A3A;

	/* Send CMD3 to set the relative card address (RCA) of the eMMC */
	arg = (mmcrca << 16) & 0xFFFF0000;
	cmd = SD_CMD_CMD3_SET_RELATIVE_ADDR | SD_CMD_CMD_TYPE_NORMAL | SD_CMD_DP_NO_DATA_PRESENT |
		SD_CMD_CICE_ENABLE | SD_CMD_CCCE_ENABLE | SD_CMD_RSP_TYPE_R1;
	if (mmccmd(cmd, arg, resp) == -1)
		return(-1);

	/* Wait for the eMMC to enter Stand-by State */
	do {
		/* Send CMD13 to get the status of the MMC */
		arg = (mmcrca << 16) & 0xFFFF0000;
		cmd = SD_CMD_CMD13_SEND_STATUS | SD_CMD_CMD_TYPE_NORMAL | SD_CMD_DP_NO_DATA_PRESENT |
			SD_CMD_CICE_ENABLE | SD_CMD_CCCE_ENABLE | SD_CMD_RSP_TYPE_R1;
		if (mmccmd(cmd, arg, resp) == -1)
			return(-1);
	} while ((resp[0] & SD_RSP10_R1_CURRENT_STATE) != SD_RSP10_R1_CURRENT_STATE_STANDBY);

	/* Disable open-drain mode */
	MMC1_REG(SD_CON) &= ~SD_CON_OD;

	/* Send CMD7 to put the eMMC into Transfer State */
	arg = (mmcrca << 16) & 0xFFFF0000;
	cmd = SD_CMD_CMD7_SELECT_DESELECT_CARD | SD_CMD_CMD_TYPE_NORMAL | SD_CMD_DP_NO_DATA_PRESENT |
		SD_CMD_CICE_ENABLE | SD_CMD_CCCE_ENABLE | SD_CMD_RSP_TYPE_R1;
	if (mmccmd(cmd, arg, resp) == -1)
		return(-1);

	/* Wait for eMMC to enter Transfer State */
	do {
		/* Send CMD13 to get the status of the eMMC */
		arg = (mmcrca << 16) & 0xFFFF0000;
		cmd = SD_CMD_CMD13_SEND_STATUS | SD_CMD_CMD_TYPE_NORMAL | SD_CMD_DP_NO_DATA_PRESENT |
			SD_CMD_CICE_ENABLE | SD_CMD_CCCE_ENABLE | SD_CMD_RSP_TYPE_R1;
		if (mmccmd(cmd, arg, resp) == -1)
			return(-1);
	} while ((resp[0] & SD_RSP10_R1_CURRENT_STATE) != SD_RSP10_R1_CURRENT_STATE_TRANSFER);

	/* Send CMD6 to change bus-width to 8-bits */
	arg = (3 << 24) | (183 << 16) | (2 << 8);
	cmd = SD_CMD_CMD6_SWITCH | SD_CMD_CMD_TYPE_NORMAL | SD_CMD_DP_NO_DATA_PRESENT |
		SD_CMD_CICE_ENABLE | SD_CMD_CCCE_ENABLE | SD_CMD_RSP_TYPE_R1B;
	if (mmccmd(cmd, arg, resp) == -1)
		return(-1);
	while (!(MMC1_REG(SD_STAT) & SD_STAT_TC));

	/* Wait while CMD6 is still in effect, i.e. while eMMC is not in Transfer State */
	do {
		arg = (mmcrca << 16) & 0xFFFF0000;
		cmd = SD_CMD_CMD13_SEND_STATUS | SD_CMD_CMD_TYPE_NORMAL | SD_CMD_DP_NO_DATA_PRESENT |
			SD_CMD_CICE_ENABLE | SD_CMD_CCCE_ENABLE | SD_CMD_RSP_TYPE_R1;
		if (mmccmd(cmd, arg, resp) == -1)
			return(-1);
	} while ((resp[0] & SD_RSP10_R1_CURRENT_STATE) != SD_RSP10_R1_CURRENT_STATE_TRANSFER);

	/* Configure the MMC1 controller to use an 8-bit data width */
	MMC1_REG(SD_CON) |= SD_CON_DW8_8BIT;

	/* Send CMD6 to change to high-speed mode */
	arg = 0x03B90100;
	cmd = SD_CMD_CMD6_SWITCH | SD_CMD_CMD_TYPE_NORMAL | SD_CMD_DP_NO_DATA_PRESENT |
		SD_CMD_CICE_ENABLE | SD_CMD_CCCE_ENABLE | SD_CMD_RSP_TYPE_R1B;
	if (mmccmd(cmd, arg, resp) == -1)
		return(-1);
	while (!(MMC1_REG(SD_STAT) & SD_STAT_TC));

	/* Wait while CMD6 is still in effect, i.e. while eMMC is not in Transfer State */
	do {
		arg = (mmcrca << 16) & 0xFFFF0000;
		cmd = SD_CMD_CMD13_SEND_STATUS | SD_CMD_CMD_TYPE_NORMAL | SD_CMD_DP_NO_DATA_PRESENT |
			SD_CMD_CICE_ENABLE | SD_CMD_CCCE_ENABLE | SD_CMD_RSP_TYPE_R1;
		if (mmccmd(cmd, arg, resp) == -1)
			return(-1);
	} while ((resp[0] & SD_RSP10_R1_CURRENT_STATE) != SD_RSP10_R1_CURRENT_STATE_TRANSFER);

	/* Change the clock frequency to 48 MHz and set the DTO to the maximum value setting */
	MMC1_REG(SD_SYSCTL) &= ~SD_SYSCTL_DTO;
	MMC1_REG(SD_SYSCTL) |= SD_SYSCTL_DTO_TCF_2_27;
	MMC1_REG(SD_SYSCTL) = (MMC1_REG(SD_SYSCTL) & ~SD_SYSCTL_CLKD) | (2 << 6);

	/* Wait for clock to stabilize */
	while ((MMC1_REG(SD_SYSCTL) & SD_SYSCTL_ICS) != SD_SYSCTL_ICS);

	/* Put the eMMC into Stand-by State */
	arg = 0x00000000;
	cmd = SD_CMD_CMD7_SELECT_DESELECT_CARD | SD_CMD_CMD_TYPE_NORMAL | SD_CMD_DP_NO_DATA_PRESENT |
		SD_CMD_CICE_DISABLE | SD_CMD_CCCE_DISABLE | SD_CMD_RSP_TYPE_NO_RESPONSE;
	if (mmccmd(cmd, arg, resp) == -1)
		return(-1);

	/* Wait for the eMMC to enter Stand-by State */
	do {
		arg = (mmcrca << 16) & 0xFFFF0000;
		cmd = SD_CMD_CMD13_SEND_STATUS | SD_CMD_CMD_TYPE_NORMAL | SD_CMD_DP_NO_DATA_PRESENT |
			SD_CMD_CICE_ENABLE | SD_CMD_CCCE_ENABLE | SD_CMD_RSP_TYPE_R1;
		if (mmccmd(cmd, arg, resp) == -1)
			return(-1);
	} while ((resp[0] & SD_RSP10_R1_CURRENT_STATE) != SD_RSP10_R1_CURRENT_STATE_STANDBY);

	return(0);
}

int
mmcRead(int interface, char *buf, int blknum, int blkcnt)
{
	uint32_t cmd, arg, resp[4];
	uint32_t *wordptr = (uint32_t *) buf;
	int byteindex;

	/* Get the SD card's status via CMD13 */
	arg = (mmcrca << 16) & 0xFFFF0000;
	cmd = SD_CMD_CMD13_SEND_STATUS | SD_CMD_CMD_TYPE_NORMAL | SD_CMD_DP_NO_DATA_PRESENT |
		SD_CMD_CICE_ENABLE | SD_CMD_CCCE_ENABLE | SD_CMD_RSP_TYPE_R1;
	if (mmccmd(cmd, arg, resp) == -1)
		return(-1);

	/* Ensure that the card is in Transfer State before proceeding */
	if ((resp[0] & SD_RSP10_R1_CURRENT_STATE) != SD_RSP10_R1_CURRENT_STATE_TRANSFER) {
		arg = (mmcrca << 16) & 0xFFFF0000;
		cmd = SD_CMD_CMD7_SELECT_DESELECT_CARD | SD_CMD_CMD_TYPE_NORMAL |
			SD_CMD_DP_NO_DATA_PRESENT | SD_CMD_CICE_ENABLE | SD_CMD_CCCE_ENABLE |
			SD_CMD_RSP_TYPE_R1B;
		if (mmccmd(cmd, arg, resp) == -1)
			return(-1);

		/* Wait for the SD card to enter Transfer State */
		do {
			arg = (mmcrca << 16) & 0xFFFF0000;
			cmd = SD_CMD_CMD13_SEND_STATUS | SD_CMD_CMD_TYPE_NORMAL |
				SD_CMD_DP_NO_DATA_PRESENT | SD_CMD_CICE_ENABLE | SD_CMD_CCCE_ENABLE |
				SD_CMD_RSP_TYPE_R1;
			if (mmccmd(cmd, arg, resp) == -1)
				return(-1);
		} while ((resp[0] & SD_RSP10_R1_CURRENT_STATE) != SD_RSP10_R1_CURRENT_STATE_TRANSFER);
	}

	/* Set the block length and the number of blocks to read */
	MMC1_REG(SD_BLK) = 0x200 | (blkcnt << 16);
	/* Read multiple blocks via CMD18 */
	arg = blknum;
	cmd = SD_CMD_CMD18_READ_MULTIPLE_BLOCK | SD_CMD_CMD_TYPE_NORMAL | SD_CMD_DP_DATA_PRESENT |
		SD_CMD_CICE_ENABLE | SD_CMD_CCCE_ENABLE | SD_CMD_RSP_TYPE_R1 | SD_CMD_MSBS_MULTIPLE |
		SD_CMD_DDIR_READ | SD_CMD_ACEN_CMD12_ENABLE | SD_CMD_BCE_ENABLE;
	if (mmccmd(cmd, arg, resp) == -1)
		return(-1);

	/* Check the data buffer to see if there is data to be read */
	do {
		while (!(MMC1_REG(SD_STAT) & SD_STAT_BRR));

		/* Clear the BRR status bit in SD_STAT */
		MMC1_REG(SD_STAT) |= SD_STAT_BRR;

		for (byteindex = 0; byteindex < (0x200 / 4); byteindex++) {
			*wordptr = MMC1_REG(SD_DATA);
			wordptr++;
		}
	} while (!(MMC1_REG(SD_STAT) & SD_STAT_TC));

	/* Put the eMMC into Stand-by State */
	arg = 0;
	cmd = SD_CMD_CMD7_SELECT_DESELECT_CARD | SD_CMD_CMD_TYPE_NORMAL | SD_CMD_DP_NO_DATA_PRESENT |
		SD_CMD_CICE_DISABLE | SD_CMD_CCCE_DISABLE | SD_CMD_RSP_TYPE_NO_RESPONSE;
	if (mmccmd(cmd, arg, resp) == -1)
		return(-1);

	/* Wait for the eMMC to enter Stand-by State */
	do {
		arg = (mmcrca << 16) & 0xFFFF0000;
		cmd = SD_CMD_CMD13_SEND_STATUS | SD_CMD_CMD_TYPE_NORMAL | SD_CMD_DP_NO_DATA_PRESENT |
			SD_CMD_CICE_ENABLE | SD_CMD_CCCE_ENABLE | SD_CMD_RSP_TYPE_R1;
		if (mmccmd(cmd, arg, resp) == -1)
			return(-1);
	} while ((resp[0] & SD_RSP10_R1_CURRENT_STATE) != SD_RSP10_R1_CURRENT_STATE_STANDBY);

	return(0);
}

int
mmcWrite(int interface, char *buf, int blknum, int blkcnt)
{
	uint32_t cmd, arg, resp[4];
	uint32_t *wordptr = (uint32_t *) buf;
	int byteindex;

	/* Get the eMMC status by sending CMD13 */
	arg = (mmcrca << 16) & 0xFFFF0000;
	cmd = SD_CMD_CMD13_SEND_STATUS | SD_CMD_CMD_TYPE_NORMAL | SD_CMD_DP_NO_DATA_PRESENT |
		SD_CMD_CICE_ENABLE | SD_CMD_CCCE_ENABLE | SD_CMD_RSP_TYPE_R1;
	if (mmccmd(cmd, arg, resp) == -1)
		return(-1);

	/* Ensure that the eMMC is in the Transfer State before proceeding */
	if ((resp[0] & SD_RSP10_R1_CURRENT_STATE) != SD_RSP10_R1_CURRENT_STATE_TRANSFER) {
		arg = (mmcrca << 16) & 0xFFFF0000;
		cmd  = SD_CMD_CMD7_SELECT_DESELECT_CARD | SD_CMD_CMD_TYPE_NORMAL |
			SD_CMD_DP_NO_DATA_PRESENT | SD_CMD_CICE_ENABLE | SD_CMD_CCCE_ENABLE |
			SD_CMD_RSP_TYPE_R1B;
		if (mmccmd(cmd, arg, resp) == -1)
			return(-1);

		/* Wait for eMMC to enter Transfer State */
		do {
			arg = (mmcrca << 16) & 0xFFFF0000;
			cmd = SD_CMD_CMD13_SEND_STATUS | SD_CMD_CMD_TYPE_NORMAL |
				SD_CMD_DP_NO_DATA_PRESENT | SD_CMD_CICE_ENABLE | SD_CMD_CCCE_ENABLE |
				SD_CMD_RSP_TYPE_R1;
			if (mmccmd(cmd, arg, resp) == -1)
				return(-1);
		} while ((resp[0] & SD_RSP10_R1_CURRENT_STATE) != SD_RSP10_R1_CURRENT_STATE_TRANSFER);
	}

	/* Set the block length in bytes and the number of blocks to write to the SD card */
	MMC1_REG(SD_BLK) = 0x200 | (blkcnt << 16);
	/* Send CMD25, that is write the number of blocks specified in 'blkcount' from 'buf' to the
	 * location that is 512 byte aligned in the SD card specified by the block number 'blknum'
	 */
	arg = blknum;
	cmd = SD_CMD_CMD25_WRITE_MULTIPLE_BLOCK | SD_CMD_CMD_TYPE_NORMAL | SD_CMD_DP_DATA_PRESENT |
		SD_CMD_CICE_ENABLE | SD_CMD_CCCE_ENABLE | SD_CMD_RSP_TYPE_R1 | SD_CMD_MSBS_MULTIPLE |
		SD_CMD_DDIR_WRITE | SD_CMD_ACEN_CMD12_ENABLE | SD_CMD_BCE_ENABLE;
	if (mmccmd(cmd, arg, resp) == -1)
		return(-1);

	/* Write the data */
	do {
		/* Wait until data is ready to be written */
		while (!(MMC1_REG(SD_STAT) & (SD_STAT_BWR | SD_STAT_TC)));

		if (MMC1_REG(SD_STAT) & SD_STAT_TC)
			break;

		/* Clear the BWR status bit in SD_STAT */
		MMC1_REG(SD_STAT) |= SD_STAT_BWR;

		for (byteindex = 0; byteindex < (0x200 / 4); byteindex++)
			MMC1_REG(SD_DATA) = *wordptr++;
	} while (!(MMC1_REG(SD_STAT) & SD_STAT_TC));

	/* Put the eMMC into Stand-by State */
	arg = 0x00000000;
	cmd = SD_CMD_CMD7_SELECT_DESELECT_CARD | SD_CMD_CMD_TYPE_NORMAL | SD_CMD_DP_NO_DATA_PRESENT |
		SD_CMD_CICE_ENABLE | SD_CMD_CCCE_ENABLE | SD_CMD_RSP_TYPE_NO_RESPONSE;
	if (mmccmd(cmd, arg, resp) == -1)
		return(-1);

	/* Wait for eMMC to enter Stand-by State */
	do {
		arg = (mmcrca << 16) & 0xFFFF0000;
		cmd  = SD_CMD_CMD13_SEND_STATUS | SD_CMD_CMD_TYPE_NORMAL | SD_CMD_DP_NO_DATA_PRESENT |
			SD_CMD_CICE_ENABLE | SD_CMD_CCCE_ENABLE | SD_CMD_RSP_TYPE_R1;
		if (mmccmd(cmd, arg, resp) == -1)
			return(-1);
	} while ((resp[0] & SD_RSP10_R1_CURRENT_STATE) != SD_RSP10_R1_CURRENT_STATE_STANDBY);

	return(0);
}

int
mmcInstalled(int interface)
{
	return(1);
}