Code: Select all
DEFINITION MODULE MCI;
CONST
(* card types *)
cardTypeUnknown* = 0;
cardTypeMMC* = 1;
cardTypeSD* = 2;
cardTypeSDHC* = 3;
writeBlocksAdr* = 7FD00000H;
writeBlocksLen* = 8192; (* B *)
readBlocksAdr* = 7FD00000H;
readBlocksLen* = 8192; (* B *)
blockLen* = 512; (* B *)
(* err codes *)
errOk* = 0;
errNotInTrans* = 80;
errTxUnderrun* = 200;
errRxOverrun* = 205;
VAR
cardType*: INTEGER;
nBlocks*: INTEGER;
rate*: INTEGER; (* Hz *)
err*: INTEGER;
serialNo*: INTEGER;
PROCEDURE Init* (MCIPWRActiveHigh: BOOLEAN; VAR ok: BOOLEAN);
PROCEDURE* Busy* (): BOOLEAN;
PROCEDURE StartWrite* (blockN, blocks: INTEGER; adr: INTEGER; VAR ok: BOOLEAN);
PROCEDURE StartRead* (blockN, blocks: INTEGER; VAR ok: BOOLEAN);
END MCI.
Code: Select all
MODULE MCI;
(*
A. V. Shiryaev, 2012.05, 2012.06
LPC2378/LPC2388
MCI
DMA channel 1
Supported card types:
MMC, [micro]SD, [micro]SDHC
3.3 V
Tested cards:
"Kingston, MMC+, 512 M"
"Samsung, microSD, 2 G"
"Transcend, microSDHC, Class 10, 8 G"
"Transcend, SDHC, Class 10, 32 G"
"KingMax, microSDHC, Class 6, 32 G"
References:
NXP Code Bundle (LPC23xx)
Keil RL FlashFS (MCB2300_SD_File/MCI_LPC23xx)
OpenBSD sdmmc kernel driver (src/sys/dev/sdmmc)
http://www.sdmmc.org/:
Part_1_Physical_Layer_Simplified_Specification_Ver_3.01_Final_100518.pdf
TODO:
SDXC:
...
1 TB limit
DMA:
DMAMove: transfer size???
? STOP_TRANSMISSION if resp # TRANS if s0 IN {1,4}
*)
IMPORT SYSTEM, LPC2378, LPC;
CONST
(* card types *)
cardTypeUnknown* = 0;
cardTypeMMC* = 1;
cardTypeSD* = 2;
cardTypeSDHC* = 3;
respTypeNone = 0;
respTypeShort = 1;
respTypeLong = 2;
(* DMA mode *)
M2P = 1;
P2M = 2;
dmaAdr0 = 7FD00000H;
dmaLen0 = 8192;
writeBlocksAdr* = dmaAdr0;
writeBlocksLen* = dmaLen0;
readBlocksAdr* = dmaAdr0;
readBlocksLen* = dmaLen0;
(* from OpenBSD src/sys/dev/sdmmc/sdmmcreg.h, v 1.4 2009/01/09 *)
(* MMC command; response *)
MMCGOIDLESTATE = 0; (* R0 *) (* no resp *)
MMCSENDOPCOND = 1; (* R3 *) (* short resp, ignCRC, ignRespCmd *)
MMCALLSENDCID = 2; (* R2 *) (* long resp, ignRespCmd *)
MMCSETRELATIVEADDR = 3; (* R1 *) (* short resp *)
MMCSELECTCARD = 7; (* R1 *) (* short resp *)
MMCSENDCSD = 9; (* R2 *) (* long resp, ignRespCmd *)
MMCSTOPTRANSMISSION = 12; (* R1B *) (* short resp, ignCRC *)
MMCSENDSTATUS = 13; (* R1 *) (* short resp *)
MMCSETBLOCKLEN = 16; (* R1 *) (* short resp *)
MMCREADBLOCKSINGLE = 17; (* R1 *) (* short resp *)
MMCREADBLOCKMULTIPLE = 18; (* R1 *)
MMCSETBLOCKCOUNT = 23; (* R1 *)
MMCWRITEBLOCKSINGLE = 24; (* R1 *) (* short resp *)
MMCWRITEBLOCKMULTIPLE = 25; (* R1 *)
MMCAPPCMD = 55; (* R1 *) (* short resp *)
(* SD command; response *)
SDSENDRELATIVEADDR = 3; (* R6 *) (* short resp *)
SDSENDIFCOND = 8; (* R7 *) (* short resp *)
(* SD application command; response *)
SDAPPSETBUSWIDTH = 6; (* R1 *) (* short resp *)
SDAPPOPCOND = 41; (* R3 *) (* short resp, ignCRC, ignRespCmd *)
(* MCIStatus bits *)
CmdCrcFail = 0; (* Command response received (CRC check failed) *)
DataCrcFail = 1; (* Data block sent/received (CRC check failed) *)
CmdTimeOut = 2; (* Command response timeout *)
DataTimeOut = 3; (* Data timeout *)
TxUnderrun = 4; (* Transmit FIFO underrun error *)
RxOverrun = 5; (* Receive FIFO overrun error *)
CmdRespEnd = 6; (* Command response received (CRC check passed) *)
CmdSent = 7; (* Command sent (no response required) *)
DataEnd = 8; (* Data end (data counter is zero) *)
StartBitErr = 9; (* Start bit not detected on all data signals in wide bus mode *)
DataBlockEnd = 10; (* Data block sent/received (CRC check passed) *)
CmdActive = 11; (* Command transfer in progress *)
TxActive = 12; (* Data transmit in progress *)
RxActive = 13; (* Data receive in progress *)
TxFifoHalfEmpty = 14; (* Transmit FIFO half empty *)
RxFifoHalfFull = 15; (* Receive FIFO half full *)
TxFifoFull = 16; (* Transmit FIFO full *)
RxFifoFull = 17; (* Receive FIFO full *)
TxFifoEmpty = 18; (* Transmit FIFO empty *)
RxFifoEmpty = 19; (* Receive FIFO empty *)
TxDataAvbl = 20; (* Data available in transmit FIFO *)
RxDataAvbl = 21; (* Data available in receive FIFO *)
MCIClearMask = {0..10};
(* from NXP code bundle *)
OCRINDEX = 00FF8000H; (* 2.7 -- 3.6 V *)
blockLenN = 9; (* >= 2 *)
blockLen* = 512; (* 2^blockLenN;
readBlocksLen MOD blockLen = 0; readBlocksLen >= blockLen *)
(* err codes *)
errOk* = 0;
errNotInTrans* = 80;
errTxUnderrun* = 200;
errRxOverrun* = 205;
VAR
cardType*: INTEGER;
CardRCA: INTEGER;
nBlocks*,
MCLK: INTEGER;
rate*: INTEGER; (* Hz *)
(* info *)
serialNo*: INTEGER;
s0: INTEGER;
(*
0: idle
1: get status resp
2: get read block resp
3: wait read block
[ 3 { 3 } 7 ]
0
4: get status resp
5: get write block resp
6: wait write block
[ 6 { 6 } 8 ]
0
intermediate:
7: get stop resp (multiple blocks read)
0
8: get stop resp (multiple blocks write)
*)
s1: INTEGER; (* start block number to read/write *)
s2: INTEGER; (* number of blocks to read/write *)
err*: INTEGER;
PROCEDURE* SendCmd0 (cmd, arg, respType: INTEGER);
VAR x: SET;
BEGIN
ASSERT(cmd >= 0, 100);
ASSERT(cmd <= 63, 101); (* 6 bits *)
x := SYSTEM.VAL(SET, cmd) + {10};
CASE respType OF respTypeNone:
| respTypeShort:
x := x + {6}
| respTypeLong:
x := x + {6,7}
END;
SYSTEM.PUT(LPC2378.MCIArgument, arg);
SYSTEM.PUT(LPC2378.MCICommand, x)
END SendCmd0;
(* to call from InterruptsHandlerMCI *)
PROCEDURE StartSendReadBlock (blockN: INTEGER; multiple: BOOLEAN);
VAR cmd, arg: INTEGER;
BEGIN
IF multiple THEN
cmd := MMCREADBLOCKMULTIPLE
ELSE
cmd := MMCREADBLOCKSINGLE
END;
CASE cardType OF cardTypeMMC,cardTypeSD:
arg := blockN * blockLen
| cardTypeSDHC:
arg := blockN
END;
SendCmd0(cmd, arg, respTypeShort)
END StartSendReadBlock;
PROCEDURE StartSendWriteBlock (blockN: INTEGER; multiple: BOOLEAN);
VAR cmd, arg: INTEGER;
BEGIN
IF multiple THEN
cmd := MMCWRITEBLOCKMULTIPLE
ELSE
cmd := MMCWRITEBLOCKSINGLE
END;
CASE cardType OF cardTypeMMC,cardTypeSD:
arg := blockN * blockLen
| cardTypeSDHC:
arg := blockN
END;
SendCmd0(cmd, arg, respTypeShort)
END StartSendWriteBlock;
PROCEDURE StartSendStop;
BEGIN
(* XXX: arg: RCA? see OpenBSD srcs.; 0 works *)
SendCmd0(MMCSTOPTRANSMISSION, 0, respTypeShort)
END StartSendStop;
PROCEDURE* DMAMove (channel, mode: INTEGER);
CONST
MBS = 4; (* : 32 *)
(* MBS = 2; (* : 8 *) *)
VAR x: SET;
BEGIN
(*
USB RAM is used for test.
Please note, Ethernet has its own SRAM, but GPDMA can't access
that. GPDMA can access USB SRAM and IRAM. Ethernet DMA controller can
access both IRAM and Ethernet SRAM.
*)
CASE channel OF 0:
SYSTEM.PUT(LPC2378.DMACIntTCClear, {0});
SYSTEM.PUT(LPC2378.DMACIntERRClear, {0});
CASE mode OF M2P:
(* Ch0 set for M2P transfer from mempry to MCI FIFO. *)
SYSTEM.PUT(LPC2378.DMACC0SrcAddr, writeBlocksAdr);
SYSTEM.PUT(LPC2378.DMACC0DestAddr, LPC2378.MCIFIFO);
(* The burst size is set to 8, the size is 8 bit too. *)
SYSTEM.PUT(LPC2378.DMACC0Control,
blockLen MOD 1000H (* XXX *)
+ LSL(MBS, 12) (* Source burst size *)
+ LSL(2, 15) (* Destination burst size: 8 *)
+ LSL(2, 18) (* Source transfer width: 32 bit *)
+ LSL(2, 21) (* Destination transfer width: 32 bit *)
+ LSL(1, 26) (* Source increment *)
+ LSL(1, 31) (* Terminal count interrupt enable *)
)
| P2M:
(* Ch0 set for P2M transfer from MCI FIFO to memory. *)
SYSTEM.PUT(LPC2378.DMACC0SrcAddr, LPC2378.MCIFIFO);
SYSTEM.PUT(LPC2378.DMACC0DestAddr, readBlocksAdr);
(* The burst size is set to 8, the size is 8 bit too. *)
SYSTEM.PUT(LPC2378.DMACC0Control,
blockLen MOD 1000H (* XXX *)
+ LSL(2, 12) (* Source burst size: 8 *)
+ LSL(MBS, 15) (* Destination burst size *)
+ LSL(2, 18) (* Source transfer width: 32 bit *)
+ LSL(2, 21) (* Destination transfer width: 32 bit *)
+ LSL(1, 27) (* Destination increment *)
+ LSL(1, 31) (* Terminal count interrupt enable *)
)
END
| 1:
SYSTEM.PUT(LPC2378.DMACIntTCClear, {1});
SYSTEM.PUT(LPC2378.DMACIntERRClear, {1});
CASE mode OF M2P:
(* Ch1 set for M2P transfer from memory to MCI FIFO. *)
SYSTEM.PUT(LPC2378.DMACC1SrcAddr, writeBlocksAdr);
SYSTEM.PUT(LPC2378.DMACC1DestAddr, LPC2378.MCIFIFO);
(* The burst size is set to 8, the size is 8 bit too. *)
SYSTEM.PUT(LPC2378.DMACC1Control,
blockLen MOD 1000H (* XXX *)
+ LSL(MBS, 12) (* Source burst size *)
+ LSL(2, 15) (* Destination burst size: 8 *)
+ LSL(2, 18) (* Source transfer width: 32 bit *)
+ LSL(2, 21) (* Destination transfer width: 32 bit *)
+ LSL(1, 26) (* Source increment *)
+ LSL(1, 31) (* Terminal count interrupt enable *)
)
| P2M:
(* Ch1 set for P2M transfer from MCI_FIFO to memory. *)
SYSTEM.PUT(LPC2378.DMACC1SrcAddr, LPC2378.MCIFIFO);
SYSTEM.PUT(LPC2378.DMACC1DestAddr, readBlocksAdr);
(* The burst size is set to 8, the size is 8 bit too. *)
SYSTEM.PUT(LPC2378.DMACC1Control,
blockLen MOD 1000H (* XXX *)
+ LSL(2, 12) (* Source burst size: 8 *)
+ LSL(MBS, 15) (* Destination burst size *)
+ LSL(2, 18) (* Source transfer width: 32 bit *)
+ LSL(2, 21) (* Destination transfer width: 32 bit *)
+ LSL(1, 27) (* Destination increment *)
+ LSL(1, 31) (* Terminal count interrupt enable *)
)
END
END;
SYSTEM.PUT(LPC2378.DMACConfiguration, {0}) (* Enable DMA channels, little endian *)
END DMAMove;
PROCEDURE StartReadBlocksTransfer (blocks: INTEGER);
VAR DataCtrl: INTEGER;
BEGIN
(* XXX: by TAAC *)
SYSTEM.PUT(LPC2378.MCIDataTimer, blocks * 100 * (rate DIV 1000)); (* ~ 100 ms per block *)
SYSTEM.PUT(LPC2378.MCIDataLength, blockLen * blocks);
DMAMove(1, P2M);
SYSTEM.PUT(LPC2378.DMACC1Config,
10001H (* Enable, Lock *)
+ LSL(4, 1) (* Source peripherial: SD/MMC *)
+ LSL(6, 11) (* Flow control: peripherial to memory *)
+ LSL(3, 14) (* IE, ITC *)
);
DataCtrl := 1 + 2 + 8 (* enable block data transfer from card to controller, DMA enabled *)
+ LSL(blockLenN, 4);
SYSTEM.PUT(LPC2378.MCIDataCtrl, DataCtrl)
END StartReadBlocksTransfer;
PROCEDURE StartWriteBlocksTransfer (blocks: INTEGER);
VAR DataCtrl: INTEGER;
BEGIN
(* XXX: by TAAC & R2W *)
SYSTEM.PUT(LPC2378.MCIDataTimer, blocks * 300 * (rate DIV 1000)); (* ~300 ms per block *)
SYSTEM.PUT(LPC2378.MCIDataLength, blockLen * blocks);
DMAMove(1, M2P);
SYSTEM.PUT(LPC2378.DMACC1Config,
10001H (* Enable, Lock *)
+ LSL(4, 6) (* Destination peripherial: SD/MMC *)
+ LSL(5, 11) (* Flow control: memory to peripherial *)
+ LSL(3, 14) (* IE, ITC *)
);
(* Write, block transfer, DMA, and data length *)
DataCtrl := 1 + 8 + LSL(blockLenN, 4);
SYSTEM.PUT(LPC2378.MCIDataCtrl, DataCtrl)
END StartWriteBlocksTransfer;
PROCEDURE* Err0 (n: INTEGER);
BEGIN
err := n;
s0 := 0
END Err0;
PROCEDURE* Err (n: INTEGER);
BEGIN
err := n;
s0 := 0;
SYSTEM.PUT(LPC2378.MCIDataCtrl, 0)
END Err;
PROCEDURE E0 (e: INTEGER);
BEGIN
IF s0 = 3 THEN (* wait read block(s) *)
IF s2 = 1 THEN
Err(e)
ELSE (* s2 > 1 *)
err := e + 1; (* may be overwritten if STOPTRANSMISSION fail *)
s0 := 7;
StartSendStop
END
ELSIF s0 = 6 THEN (* wait write block(s) *)
IF s2 = 1 THEN
Err(e + 2)
ELSE (* s2 > 1 *)
err := e + 3; (* may be overwritten if STOPTRANSMISSION fail *)
s0 := 8;
StartSendStop
END
ELSE
Err(e + 4)
END
END E0;
PROCEDURE InterruptsHandlerMCI [4]; (* 4 for IRQ or FIQ *)
VAR x: SET;
resp: SET;
y: INTEGER;
BEGIN
SYSTEM.GET(LPC2378.VICIRQStatus, x);
IF 24 IN x THEN
SYSTEM.GET(LPC2378.MCIStatus, x);
SYSTEM.PUT(LPC2378.MCIClear, x * MCIClearMask);
IF s0 = 0 THEN
Err(1)
ELSIF x * {CmdCrcFail,DataCrcFail,CmdTimeOut,DataTimeOut,TxUnderrun,RxOverrun,StartBitErr,CmdSent} # {} THEN
IF CmdCrcFail IN x THEN (* Command response received (CRC check failed) *)
IF s0 IN {7,8} THEN (* get stop resp => ignore CRC *)
s0 := 0;
SYSTEM.PUT(LPC2378.MCIDataCtrl, 0)
(* XXX
SYSTEM.PUT(LPC2378.MCICommand, 0);
SYSTEM.PUT(LPC2378.MCIArgument, -1)
*)
(* XXX: check cmd and resp *)
ELSE
E0(5)
END
ELSIF DataCrcFail IN x THEN (* Data block sent/received (CRC check failed) *)
E0(10)
ELSIF CmdTimeOut IN x THEN (* Command response timeout *)
IF s0 IN {1,2,4,5,7,8} THEN
(* XXX:
SYSTEM.PUT(LPC2378.MCICommand, 0);
SYSTEM.PUT(LPC2378.MCIArgument, -1)
*)
Err(15)
(*
case of card re-insertion (s0 IN {1,4})
*)
ELSE
E0(20)
END
ELSIF DataTimeOut IN x THEN (* Data timeout *)
E0(25)
ELSIF TxUnderrun IN x THEN (* Transmit FIFO underrun error *)
IF s0 = 6 THEN
(*
2012.06.19
this may occur if some LPC2378 peripherial touched
(such as REPEAT UNTIL SYSTEM.BIT(LPC.U0LSR, 5) etc.)
when Busy() after StartWrite;
send STOP in any case, because card not in TRANS state here
*)
SYSTEM.PUT(LPC2378.MCIDataCtrl, 0);
err := errTxUnderrun; (* may be overwritten if STOPTRANSMISSION fail *)
s0 := 8;
StartSendStop
ELSE
E0(30)
END
ELSIF RxOverrun IN x THEN (* Receive FIFO overrun error *)
IF s0 = 3 THEN
(*
2012.06.19
this may occur if some LPC2378 peripherial touched
(such as REPEAT UNTIL SYSTEM.BIT(LPC.U0LSR, 5) etc.)
when Busy() after StartRead;
send STOP in any case, because card not in TRANS state here
*)
SYSTEM.PUT(LPC2378.MCIDataCtrl, 0); (* required too *)
err := errRxOverrun; (* may be overwritten if STOPTRANSMISSION fail *)
s0 := 7;
StartSendStop
ELSE
E0(35)
END
ELSIF StartBitErr IN x THEN (* Start bit not detected on all data signals in wide bus mode *)
E0(40)
ELSIF CmdSent IN x THEN (* Command sent (no response required) *)
E0(45) (* not requested *)
END
ELSE
IF CmdRespEnd IN x THEN (* Command response received (CRC check passed) *)
SYSTEM.GET(LPC2378.MCIRespCmd, y); y := y MOD 64;
SYSTEM.GET(LPC2378.MCIResponse0, resp);
IF s0 IN {1,2,4,5} THEN (* get status/read/status/write resp *)
IF resp * {8..11} = {8,11} THEN (* status: ready, TRAN *)
CASE s0 OF 1:
IF y = MMCSENDSTATUS THEN
s0 := 2; StartSendReadBlock(s1, s2 > 1)
ELSE
Err(50)
END
| 2:
IF s2 = 1 THEN
IF y = MMCREADBLOCKSINGLE THEN
s0 := 3; StartReadBlocksTransfer(s2)
ELSE
Err(55)
END
ELSE (* s2 > 1 *)
IF y = MMCREADBLOCKMULTIPLE THEN
s0 := 3; StartReadBlocksTransfer(s2)
ELSE
Err(60)
END
END
| 4:
IF y = MMCSENDSTATUS THEN
s0 := 5; StartSendWriteBlock(s1, s2 > 1)
ELSE
Err(65)
END
| 5:
IF s2 = 1 THEN
IF y = MMCWRITEBLOCKSINGLE THEN
s0 := 6; StartWriteBlocksTransfer(1)
ELSE
Err(70)
END
ELSE (* s2 > 1 *)
IF y = MMCWRITEBLOCKMULTIPLE THEN
s0 := 6; StartWriteBlocksTransfer(s2)
ELSE
Err(75)
END
END
END
ELSIF s0 IN {1,4} THEN (* get status resp (before r/w) *)
IF y = MMCSENDSTATUS THEN
Err0(errNotInTrans)
ELSE
Err(85)
END
ELSE
Err0(90)
END
ELSIF s0 IN {7,8} THEN (* get stop resp *)
IF y = MMCSTOPTRANSMISSION THEN
IF s0 = 7 THEN
IF 8 IN resp THEN
s0 := 0
ELSE
Err(95)
END
ELSE (* s0 = 8 *)
(* XXX: check in PRG state?... *)
s0 := 0
END
ELSE
Err(105)
END
ELSE
E0(110)
END
END;
(*
IF DataBlockEnd IN x THEN (* Data block sent/received (CRC check passed) *)
END;
*)
IF s0 # 0 THEN
IF DataEnd IN x THEN (* Data end (data counter is zero) *)
IF s0 = 3 THEN (* wait read block(s) *)
IF s2 = 1 THEN
s0 := 0
ELSE (* s2 > 1 *)
s0 := 7;
StartSendStop
END
ELSIF s0 = 6 THEN (* wait write block(s) *)
IF s2 = 1 THEN
s0 := 0
ELSE (* s2 > 1 *)
s0 := 8;
StartSendStop
END
ELSE
Err(115)
END
END
END
END
(* ELSE ASSERT(x = {}) *) (* spurious interrupt? *) (* FIXME *)
END;
SYSTEM.PUT(LPC2378.VICAddress, 0)
END InterruptsHandlerMCI;
PROCEDURE* InitDMA;
CONST
(* PCONP bits *)
PCGPDMA = 29;
VAR x: SET;
BEGIN
(* GPDMA *)
SYSTEM.GET(LPC2378.PCONP, x);
SYSTEM.PUT(LPC2378.PCONP, x + {PCGPDMA});
(* clear all interrupts on channel 1 *)
SYSTEM.PUT(LPC2378.DMACIntTCClear, {1});
SYSTEM.PUT(LPC2378.DMACIntERRClear, {1});
SYSTEM.PUT(LPC2378.DMACConfiguration, {0}); (* Enable DMA channels, little endian *)
REPEAT UNTIL SYSTEM.BIT(LPC2378.DMACConfiguration, 0)
END InitDMA;
PROCEDURE* DecodeCID (CID: ARRAY OF SET; VAR serialNo: INTEGER);
BEGIN
CASE cardType OF cardTypeMMC:
serialNo := LSL(SYSTEM.VAL(INTEGER, CID[2]), 16) + LSR(SYSTEM.VAL(INTEGER, CID[3]), 16)
| cardTypeSD,cardTypeSDHC:
serialNo := LSL(SYSTEM.VAL(INTEGER, CID[2]), 8) + LSR(SYSTEM.VAL(INTEGER, CID[3]), 24)
END
END DecodeCID;
PROCEDURE DecodeCSD (CSD: ARRAY OF SET; VAR READBLLEN, WRITEBLLEN, nBlocks, rate: INTEGER);
VAR CSDSTRUCTURE: INTEGER;
CSIZE, CSIZEMULT: INTEGER;
PROCEDURE* DecodeTRANSPEED (TRANSPEED: INTEGER): INTEGER;
VAR x: INTEGER;
BEGIN
CASE TRANSPEED MOD 8 OF 0: x := 100000
| 1: x := 1000000
| 2: x := 10000000
| 3: x := 100000000
END;
CASE TRANSPEED DIV 8 MOD 16 OF 1:
| 2: x := (x * 6) DIV 5
| 3: x := (x * 13) DIV 10
| 4: x := (x * 3) DIV 2
| 5: x := x * 2
| 6: x := (x * 5) DIV 2
| 7: x := x * 3
| 8: x := (x * 7) DIV 2
| 9: x := x * 4
| 10: x := (x * 9) DIV 2
| 11: x := x * 5
| 12: x := (x * 11) DIV 2
| 13: x := x * 6
| 14: x := x * 7
| 15: x := x * 8
END
RETURN x
END DecodeTRANSPEED;
BEGIN
rate := DecodeTRANSPEED(SYSTEM.VAL(INTEGER, CSD[0]) MOD 256);
READBLLEN := LSR(SYSTEM.VAL(INTEGER, CSD[1]), 16) MOD 16;
WRITEBLLEN := LSR(SYSTEM.VAL(INTEGER, CSD[3]), 22) MOD 16;
CASE cardType OF cardTypeMMC:
| cardTypeSD,cardTypeSDHC:
CSDSTRUCTURE := LSR(SYSTEM.VAL(INTEGER, CSD[0]), 30);
CASE cardType OF cardTypeSD:
ASSERT(CSDSTRUCTURE = 0, 105); (* 1.0 *)
ASSERT(READBLLEN IN {9,10,11}, 106) (* 512 B | 1024 B | 2048 B *)
| cardTypeSDHC:
ASSERT(CSDSTRUCTURE = 1, 107); (* 2.0 *)
ASSERT(READBLLEN = 9, 108) (* 512 B *)
END;
ASSERT(READBLLEN = WRITEBLLEN, 109)
END;
CASE cardType OF cardTypeMMC,cardTypeSD:
CSIZE := (LSL(SYSTEM.VAL(INTEGER, CSD[1]), 2) + LSR(SYSTEM.VAL(INTEGER, CSD[2]), 30)) MOD 4096;
CSIZEMULT := LSR(SYSTEM.VAL(INTEGER, CSD[2]), 15) MOD 8;
| cardTypeSDHC:
CSIZE := LSL(SYSTEM.VAL(INTEGER, CSD[1]) MOD 64, 16) + LSR(SYSTEM.VAL(INTEGER, CSD[2]), 16);
(* (cardType = cardTypeSDXC) = (CSIZE >= 65535) *)
CSIZEMULT := 8
END;
nBlocks := LSL(CSIZE + 1, CSIZEMULT + 2)
END DecodeCSD;
PROCEDURE Delay0 (n: INTEGER);
VAR i: INTEGER;
BEGIN
i := 0; WHILE i < n DO INC(i) END
END Delay0;
PROCEDURE Init* (MCIPWRActiveHigh: BOOLEAN; VAR ok: BOOLEAN);
CONST
(* PCONP bits *)
PCSDC = 28;
VAR x: SET;
PROCEDURE SetMCIClock (MCLCLK: INTEGER);
CONST
(* MCIClock bits *)
PwrSave = 9;
(* extra = {}; *)
extra = {PwrSave}; (* as in Keil MCI_LPC23xx.c *)
VAR clkDiv: INTEGER;
x: SET;
BEGIN
ASSERT(MCLCLK > 0, 110);
clkDiv := MCLK DIV (2 * MCLCLK) - 1;
ASSERT(clkDiv >= 0, 111);
ASSERT(clkDiv <= 255, 112);
SYSTEM.GET(LPC2378.MCIClock, x);
SYSTEM.PUT(LPC2378.MCIClock, x * {9,10,11} + {8} + extra + SYSTEM.VAL(SET, clkDiv));
Delay0(10H) (* delay 3MCLK + 2PCLK before next write *)
END SetMCIClock;
PROCEDURE Configure0;
VAR ok: BOOLEAN;
READBLLEN, WRITEBLLEN: INTEGER;
CID, CSD: ARRAY 4 OF SET;
PROCEDURE SetOpenDrainMode (mode: BOOLEAN);
VAR x: SET;
BEGIN
SYSTEM.GET(LPC2378.MCIPower, x);
IF mode THEN
SYSTEM.PUT(LPC2378.MCIPower, x + {6}) (* Set Open Drain output control for MMC *)
ELSE (* PUSHPULLMODE *)
SYSTEM.PUT(LPC2378.MCIPower, x - {6}) (* Clear Open Drain output control for SD *)
END;
Delay0(3000H)
END SetOpenDrainMode;
PROCEDURE Command (cmd, arg, respType: INTEGER; VAR status: SET; VAR resp: ARRAY OF SET);
PROCEDURE WaitResp0 (cmd, respType: INTEGER; VAR status: SET; VAR resp: ARRAY OF SET);
VAR y: INTEGER;
(* ignore CRC errors *)
PROCEDURE* Cmd0 (cmd: INTEGER): BOOLEAN;
RETURN (cmd = MMCSENDOPCOND) OR (cmd = SDAPPOPCOND) OR (cmd = MMCSTOPTRANSMISSION)
END Cmd0;
(* ignore RespCmd *)
PROCEDURE* Cmd1 (cmd: INTEGER): BOOLEAN;
RETURN (cmd = MMCSENDOPCOND) OR (cmd = SDAPPOPCOND) OR (cmd = MMCALLSENDCID) OR (cmd = MMCSENDCSD)
END Cmd1;
BEGIN
CASE respType OF respTypeNone:
(* wait until command finished *)
REPEAT UNTIL ~SYSTEM.BIT(LPC2378.MCIStatus, CmdActive);
SYSTEM.PUT(LPC2378.MCIClear, MCIClearMask);
status := {}
| respTypeShort,respTypeLong:
REPEAT
SYSTEM.GET(LPC2378.MCIStatus, status)
UNTIL status * {CmdTimeOut,CmdCrcFail,CmdRespEnd} # {};
SYSTEM.PUT(LPC2378.MCIClear, status * MCIClearMask);
IF CmdTimeOut IN status THEN
SYSTEM.PUT(LPC2378.MCICommand, 0);
SYSTEM.PUT(LPC2378.MCIArgument, -1)
ELSIF (CmdCrcFail IN status) & ~Cmd0(cmd) THEN
ELSE (* (CmdRespEnd IN status) OR (CmdCrcFail IN status) & Cmd0(cmd) THEN *)
IF CmdCrcFail IN status THEN
SYSTEM.PUT(LPC2378.MCICommand, 0);
SYSTEM.PUT(LPC2378.MCIArgument, -1)
END;
SYSTEM.GET(LPC2378.MCIRespCmd, y);
IF (y MOD 64 = cmd) OR Cmd1(cmd) THEN
SYSTEM.GET(LPC2378.MCIResponse0, resp[0]);
IF respType = respTypeLong THEN
SYSTEM.GET(LPC2378.MCIResponse1, resp[1]);
SYSTEM.GET(LPC2378.MCIResponse2, resp[2]);
SYSTEM.GET(LPC2378.MCIResponse3, resp[3])
END;
status := {}
ELSE
status := {0..31} (* invalid resp *)
END
END
END
END WaitResp0;
BEGIN
ASSERT( ~SYSTEM.BIT(LPC2378.MCIStatus, CmdActive), 113 );
SendCmd0(cmd, arg, respType);
WaitResp0(cmd, respType, status, resp)
END Command;
PROCEDURE SendACMD (i: INTEGER): BOOLEAN;
CONST
ACMDENABLE = 5;
VAR ok: BOOLEAN;
arg: INTEGER;
status: SET; resp: ARRAY 4 OF SET;
BEGIN
ASSERT(i > 0, 114);
CASE cardType OF cardTypeUnknown(*ok*),cardTypeMMC:
arg := 0
| cardTypeSD,cardTypeSDHC:
arg := CardRCA
END;
ok := FALSE;
REPEAT
Command(MMCAPPCMD, arg, respTypeShort, status, resp);
IF (status = {}) & (ACMDENABLE IN resp[0]) THEN
ok := TRUE
ELSE
Delay0(20H);
DEC(i)
END
UNTIL (i = 0) OR ok
RETURN ok
END SendACMD;
PROCEDURE DetectCardType (): INTEGER;
VAR res: INTEGER;
isVer20: BOOLEAN;
(* CMD0, very first command *)
PROCEDURE GoIdleState (): BOOLEAN;
VAR ok: BOOLEAN;
i: INTEGER;
status: SET; resp: ARRAY 4 OF SET;
BEGIN
i := 20H;
REPEAT
Command(MMCGOIDLESTATE, 0, respTypeNone, status, resp);
DEC(i)
UNTIL (i = 0) OR (status = {})
RETURN status = {}
END GoIdleState;
(* CMD8 *)
PROCEDURE SendIfCmd (i: INTEGER): BOOLEAN;
VAR ok: BOOLEAN;
status: SET;
resp: ARRAY 4 OF SET;
BEGIN
ASSERT(i > 0, 115);
REPEAT
(* Send CMD8 command repeatedly until the response is back correctly *)
Command(SDSENDIFCOND, 1AAH, respTypeShort, status, resp);
ok := (status = {}) & (resp[0] * {0..11} = SYSTEM.VAL(SET, 1AAH));
IF ~ok THEN
Delay0(20H)
END;
DEC(i)
UNTIL (i = 0) OR ok
RETURN ok
END SendIfCmd;
(* CMD1, for MMC *)
PROCEDURE SendOPCond (i: INTEGER): BOOLEAN;
VAR ok: BOOLEAN;
status: SET; resp: ARRAY 4 OF SET;
BEGIN
ASSERT(i > 0, 116);
ok := FALSE;
REPEAT
Command(MMCSENDOPCOND, OCRINDEX, respTypeShort, status, resp);
IF ~(CmdTimeOut IN status) & (31 IN resp[0]) THEN
ok := TRUE
ELSE
Delay0(20H);
DEC(i)
END
UNTIL (i = 0) OR ok
RETURN ok
END SendOPCond;
(*
res:
0: fail
1: ok (SD)
2: ok (SDHC)
*)
PROCEDURE SendACMDOPCond (i: INTEGER; isVer20: BOOLEAN): INTEGER;
VAR useHCS: INTEGER;
res: INTEGER;
status: SET; resp: ARRAY 4 OF SET;
BEGIN
ASSERT(i > 0, 117);
IF isVer20 THEN
useHCS := 40000000H
ELSE
useHCS := 0
END;
res := 0;
REPEAT
IF SendACMD(20) THEN
Command(SDAPPOPCOND, OCRINDEX + useHCS, respTypeShort, status, resp);
IF ~(CmdTimeOut IN status) & (31 IN resp[0]) & (resp[0] # {0..31}) THEN
(* response is back and correct. *)
(* if the card is V2.0, check if the card is SDHC *)
IF isVer20 & (30 IN resp[0]) THEN
res := 2
ELSE
res := 1
END
END
END;
IF res = 0 THEN
Delay0(20H);
DEC(i)
END
UNTIL (i = 0) OR (res # 0)
RETURN res
END SendACMDOPCond;
BEGIN
res := cardTypeUnknown;
IF GoIdleState() THEN
isVer20 := FALSE;
(* Start sending CMD8, and check if this is SD card V2.0 *)
IF SendIfCmd(5) THEN
isVer20 := TRUE
ELSE
(* If this is not SD V2.0, check if it is MMC card *)
SetOpenDrainMode(TRUE);
IF SendOPCond(200H) THEN
res := cardTypeMMC
ELSE
SetOpenDrainMode(FALSE)
END
END;
IF res = cardTypeUnknown THEN
res := SendACMDOPCond(200H, isVer20);
CASE res OF 0:
res := cardTypeUnknown
| 1:
res := cardTypeSD
| 2:
res := cardTypeSDHC
END
END
END
RETURN res
END DetectCardType;
(* CMD2, after CMD1 (MMC) or ACMD41 (SD) *)
PROCEDURE ReadCID (VAR resp: ARRAY OF SET; VAR ok: BOOLEAN);
VAR i: INTEGER;
status: SET;
BEGIN
i := 50;
ok := FALSE;
REPEAT
Command(MMCALLSENDCID, 0, respTypeLong, status, resp);
IF ~(CmdTimeOut IN status) THEN
ok := TRUE
ELSE
Delay0(20H);
DEC(i)
END
UNTIL (i = 0) OR ok
END ReadCID;
(* CMD9, after CMD3 *)
PROCEDURE ReadCSD (VAR resp: ARRAY OF SET; VAR ok: BOOLEAN);
VAR i: INTEGER;
arg: INTEGER;
status: SET;
BEGIN
CASE cardType OF cardTypeMMC:
arg := 10000H
| cardTypeSD,cardTypeSDHC:
arg := CardRCA
END;
i := 20H;
ok := FALSE;
REPEAT
Command(MMCSENDCSD, arg, respTypeLong, status, resp);
IF status = {} THEN
ok := TRUE
ELSE
Delay0(20H);
DEC(i)
END
UNTIL (i = 0) OR ok
END ReadCSD;
(* CMD3, after CMD2 *)
PROCEDURE SetAddress (VAR ok: BOOLEAN);
VAR i: INTEGER;
arg: INTEGER;
status: SET; resp: ARRAY 4 OF SET;
BEGIN
CASE cardType OF cardTypeMMC:
arg := 10000H
| cardTypeSD,cardTypeSDHC:
arg := 0
END;
i := 20H;
ok := FALSE;
REPEAT
Command(MMCSETRELATIVEADDR, arg, respTypeShort, status, resp);
IF ~(CmdTimeOut IN status) & (resp[0] * {8..11} = {8,10}) THEN
CardRCA := SYSTEM.VAL(INTEGER, resp[0] * {16..31});
ok := TRUE
ELSE
Delay0(20H);
DEC(i)
END
UNTIL (i = 0) OR ok
END SetAddress;
(* CMD7, after CMD9 *)
PROCEDURE SelectCard (VAR ok: BOOLEAN);
VAR i: INTEGER;
arg: INTEGER;
status: SET; resp: ARRAY 4 OF SET;
BEGIN
CASE cardType OF cardTypeMMC:
arg := 10000H
| cardTypeSD,cardTypeSDHC:
arg := CardRCA
END;
i := 20H;
ok := FALSE;
REPEAT
Command(MMCSELECTCARD, arg, respTypeShort, status, resp);
IF (status = {}) OR (resp[0] * {8..11} = {8,9,10}) THEN
ok := TRUE
ELSE
Delay0(20H);
DEC(i)
END
UNTIL (i = 0) OR ok
END SelectCard;
(* ACMD6 *)
PROCEDURE SDSetBusWidth (bw4bit: BOOLEAN; VAR ok: BOOLEAN);
VAR x: SET;
i: INTEGER;
arg: INTEGER;
status: SET; resp: ARRAY 4 OF SET;
BEGIN
ASSERT(cardType IN {cardTypeSD,cardTypeSDHC}, 118);
Delay0(10H); (* delay 3MCLK + 2PCLK *)
SYSTEM.GET(LPC2378.MCIClock, x);
IF bw4bit THEN (* 4 bit bus *)
SYSTEM.PUT(LPC2378.MCIClock, x + {11});
arg := 10
ELSE (* 1 bit bus *)
SYSTEM.PUT(LPC2378.MCIClock, x - {11});
arg := 0
END;
i := 20H;
ok := FALSE;
REPEAT
IF SendACMD(20) THEN
Command(SDAPPSETBUSWIDTH, arg, respTypeShort, status, resp);
IF (status = {}) & (resp[0] * {8..11} = {8,11}) THEN
ok := TRUE
END
END;
IF ~ok THEN
Delay0(20H);
DEC(i)
END
UNTIL (i = 0) OR ok
END SDSetBusWidth;
(* CMD16, after CMD7 *)
(* blockLen, B *)
PROCEDURE SetBlockLen (blockLen: INTEGER; VAR ok: BOOLEAN);
VAR i: INTEGER;
status: SET; resp: ARRAY 4 OF SET;
BEGIN
ASSERT(blockLen > 0, 119);
(* ASSERT(blockLen MOD 4 = 0, 120); *)
i := 20H;
ok := FALSE;
REPEAT
Command(MMCSETBLOCKLEN, blockLen, respTypeShort, status, resp);
IF (status = {}) & (resp[0] * {8..11} = {8,11}) THEN
ok := TRUE
ELSE
Delay0(20H);
DEC(i)
END
UNTIL (i = 0) OR ok
END SetBlockLen;
BEGIN
cardType := DetectCardType();
IF cardType IN {cardTypeMMC,cardTypeSD,cardTypeSDHC} THEN
ReadCID(CID, ok); (* CMD2 *)
ASSERT(ok, 131);
DecodeCID(CID, serialNo);
SetAddress(ok); (* CMD3 *)
ASSERT(ok, 132);
ReadCSD(CSD, ok); (* CMD9 *)
ASSERT(ok, 133);
DecodeCSD(CSD, READBLLEN, WRITEBLLEN, nBlocks, rate);
ASSERT(rate >= 400000, 134);
IF rate > 25000000 THEN rate := 25000000 END;
ASSERT(READBLLEN = WRITEBLLEN, 135);
IF READBLLEN # blockLenN THEN
ASSERT(READBLLEN > blockLenN, 136);
nBlocks := nBlocks * LSL(1, READBLLEN - blockLenN)
END;
ASSERT(nBlocks > 0, 139); (* 1 TB limit *)
IF cardType = cardTypeMMC THEN (* Disable Open Drain mode for MMC *)
SetOpenDrainMode(FALSE)
END;
SelectCard(ok); (* CMD7 *)
ASSERT(ok, 140);
SetMCIClock(rate);
CASE cardType OF cardTypeMMC:
| cardTypeSD,cardTypeSDHC:
SDSetBusWidth(TRUE, ok);
ASSERT(ok, 141)
END;
SetBlockLen(blockLen, ok); (* CMD16 *)
ASSERT(ok, 142)
END
END Configure0;
BEGIN
SYSTEM.PUT(LPC2378.VICIntEnClr, {24}); (* disable MCI interrupts *)
(* MCI *)
cardType := cardTypeUnknown;
serialNo := 0;
CardRCA := 0;
s0 := 0; err := 0;
nBlocks := 0;
serialNo := 0;
SYSTEM.GET(LPC2378.PCONP, x);
SYSTEM.PUT(LPC2378.PCONP, x + {PCSDC});
SYSTEM.GET(LPC2378.PCLKSEL1, x);
SYSTEM.PUT(LPC2378.PCLKSEL1, x + {24} - {25}); (* MCLK = CCLK *)
MCLK := LPC.CCLK;
SYSTEM.PUT(LPC2378.MCIClock, 0);
SYSTEM.PUT(LPC2378.MCIPower, 0);
Delay0(1000H);
(* disable interrupts *)
SYSTEM.PUT(LPC2378.MCIMask0, {});
SYSTEM.PUT(LPC2378.MCIMask1, {});
(* PINs *)
SYSTEM.GET(LPC2378.PINSEL1, x);
SYSTEM.PUT(LPC2378.PINSEL1, x + {7,9,11,13} - {6,8,10,12}); (* P0.19 -- P0.22: MCI function *)
SYSTEM.GET(LPC2378.PINSEL4, x);
SYSTEM.PUT(LPC2378.PINSEL4, x + {23,25,27} - {22,24,26}); (* P2.11 -- P2.13: MCI function *)
(* PINMODE not touched *)
(* MCIPWR Active Level *)
SYSTEM.GET(LPC2378.SCS, x);
IF MCIPWRActiveHigh THEN
SYSTEM.PUT(LPC2378.SCS, x + {3}) (* MCIPWR pin is high *)
ELSE
SYSTEM.PUT(LPC2378.SCS, x - {3}) (* MCIPWR pin is low *)
END;
SYSTEM.PUT(LPC2378.MCICommand, 0);
SYSTEM.PUT(LPC2378.MCIDataCtrl, 0);
SYSTEM.PUT(LPC2378.MCIClear, MCIClearMask);
SYSTEM.PUT(LPC2378.MCIPower, {1}); (* power up *)
REPEAT UNTIL SYSTEM.BIT(LPC2378.MCIPower, 1);
Delay0(100H);
(*
During identification phase, the clock should be less than
400Khz. Once we pass this phase, the normal clock can be set up
to 25Mhz on SD card and 20Mhz on MMC card.
*)
rate := 400000;
SetMCIClock(rate);
SYSTEM.PUT(LPC2378.MCIPower, {0,1});
Delay0(2000H);
(* Configure the Vectored Interrupt Controller *)
(* Use IRQ not FIQ for MCI *)
SYSTEM.GET(LPC2378.VICIntSelect, x);
SYSTEM.PUT(LPC2378.VICIntSelect, x - {24});
(* Install handler *)
SYSTEM.PUT(LPC2378.VICVectAddr24, SYSTEM.ADR(InterruptsHandlerMCI));
Configure0;
CASE cardType OF cardTypeMMC,cardTypeSD,cardTypeSDHC:
InitDMA;
SYSTEM.GET(LPC2378.MCIStatus, x);
ASSERT(x * MCIClearMask = {}, 143);
SYSTEM.PUT(LPC2378.MCIMask0, MCIClearMask);
SYSTEM.PUT(LPC2378.MCIMask1, MCIClearMask);
(* Enable MCI interrupt *)
SYSTEM.PUT(LPC2378.VICIntEnable, {24});
ok := TRUE
| cardTypeUnknown:
ok := FALSE
END
END Init;
PROCEDURE StartSendStatus;
VAR arg: INTEGER;
BEGIN
CASE cardType OF cardTypeMMC:
arg := 10000H
| cardTypeSD,cardTypeSDHC:
arg := CardRCA
END;
SendCmd0(MMCSENDSTATUS, arg, respTypeShort)
END StartSendStatus;
PROCEDURE* Copy4 (srcAdr, dstAdr: INTEGER; n: INTEGER);
VAR x: INTEGER;
BEGIN
ASSERT(n > 0, 144);
REPEAT
SYSTEM.GET(srcAdr, x);
SYSTEM.PUT(dstAdr, x);
INC(srcAdr, 4);
INC(dstAdr, 4);
DEC(n)
UNTIL n = 0
END Copy4;
PROCEDURE* Busy* (): BOOLEAN;
(* CRITICAL *)
RETURN s0 # 0
END Busy;
PROCEDURE StartWrite* (blockN, blocks: INTEGER; adr: INTEGER; VAR ok: BOOLEAN);
BEGIN
ASSERT(blockN >= 0, 145); (* 1 TB limit *)
ASSERT(blocks > 0);
ASSERT(blocks <= writeBlocksLen DIV blockLen);
ASSERT(blockN <= nBlocks - blocks, 146); (* 1 TB limit *)
ASSERT(adr MOD 4 = 0, 147);
IF ~Busy() THEN
IF adr # writeBlocksAdr THEN
Copy4(adr, writeBlocksAdr, blocks * (blockLen DIV 4))
END;
err := 0;
s0 := 4;
s1 := blockN;
s2 := blocks;
StartSendStatus;
ok := TRUE
ELSE
ok := FALSE
END
END StartWrite;
PROCEDURE StartRead* (blockN, blocks: INTEGER; VAR ok: BOOLEAN);
BEGIN
ASSERT(blockN >= 0, 148); (* 1 TB limit *)
ASSERT(blocks > 0);
ASSERT(blocks <= readBlocksLen DIV blockLen);
ASSERT(blockN <= nBlocks - blocks, 149); (* 1 TB limit *)
IF ~Busy() THEN
err := 0;
s0 := 1;
s1 := blockN;
s2 := blocks;
StartSendStatus;
ok := TRUE
ELSE
ok := FALSE
END
END StartRead;
END MCI.
Code: Select all
PROCEDURE Test1;
CONST MCIPWRActiveHigh = FALSE; (* see your circuity for details *)
VAR ok: BOOLEAN;
i: INTEGER;
x: CHAR;
BEGIN
MCI.Init(MCIPWRActiveHigh, ok);
ASSERT(ok); (* card not present, card type unknown or circuity problem *)
(* fill 1 data block in RAM *)
i := 0;
WHILE i < MCI.blockLen DO
SYSTEM.PUT(MCI.writeBlocksAdr + i, 0X);
INC(i)
END;
(* write 1 data block from RAM to card *)
MCI.StartWrite(0 (* block index *), 1 (* number of blocks *), MCI.writeBlocksAdr, ok);
ASSERT(ok); (* ~MCI.Busy() before StartWrite *)
REPEAT UNTIL ~MCI.Busy(); (* wait until operation complete *)
ASSERT(MCI.err = MCI.errOk); (* # MCI.errNotInTrans, because this is very first write operation *)
(* notInTrans means write in progress *)
(* handle MCI.err when ~MCI.Busy() only *)
(* wait write complete, then read 1 data block from card to RAM *)
REPEAT
MCI.StartRead(0 (* block index *), 1 (* number of blocks *), ok);
ASSERT(ok); (* ~MCI.Busy() before MCI.StartRead *)
REPEAT UNTIL ~MCI.Busy(); (* wait until operation complete *)
ASSERT((MCI.err = MCI.errOk) OR (MCI.err = MCI.errNotInTrans))
UNTIL MCI.err = MCI.errOk;
(* check read block in RAM *)
i := 0;
WHILE i < MCI.blockLen DO
SYSTEM.GET(MCI.readBlocksAdr + i, x);
ASSERT(x = 0X)
END
END Test1.
Code: Select all
MODULE EasyMCI;
(*
A. V. Shiryaev, 2012.05, 2012.06
Easy, blocking MCI read/write operations
*)
IMPORT MCI;
PROCEDURE Write* (blockN, blocks: INTEGER; adr: INTEGER; VAR res: INTEGER);
VAR ok: BOOLEAN;
BEGIN
REPEAT
MCI.StartWrite(blockN, blocks, adr, ok);
IF ok THEN (* ~MCI.Busy() before MCI.StartWrite *)
REPEAT UNTIL ~MCI.Busy()
END
UNTIL ok & (MCI.err # MCI.errNotInTrans);
res := MCI.err
END Write;
PROCEDURE Read* (blockN, blocks: INTEGER; VAR res: INTEGER);
VAR ok: BOOLEAN;
BEGIN
REPEAT
MCI.StartRead(blockN, blocks, ok);
IF ok THEN (* ~MCI.Busy() before MCI.StartRead *)
REPEAT UNTIL ~MCI.Busy()
END
UNTIL ok & (MCI.err # MCI.errNotInTrans);
res := MCI.err
END Read;
END EasyMCI.