Sfoglia il codice sorgente

added SD driver code;
added additional USB-specific code which is currently used only on ARM

git-svn-id: https://svn.inf.ethz.ch/svn/lecturers/a2/trunk@8315 8c9fc860-2736-0410-a75d-ab315db34111

eth.morozova 7 anni fa
parent
commit
7454dba8e9
7 ha cambiato i file con 4426 aggiunte e 2 eliminazioni
  1. 591 0
      source/ARM.UsbInfo.Mod
  2. 143 0
      source/ARM.UsbMouse.Mod
  3. 21 2
      source/Release.Tool
  4. 3067 0
      source/Sd.Mod
  5. 268 0
      source/SdDisks.Mod
  6. 223 0
      source/SdInspect.Mod
  7. 113 0
      source/Zynq.SdEnvironment.Mod

+ 591 - 0
source/ARM.UsbInfo.Mod

@@ -0,0 +1,591 @@
+MODULE UsbInfo; (** AUTHOR "staubesv"; PURPOSE "USB topology info" *)
+(**
+ * This Module doesn't add any functionality to the USB system software. Its purpose is to represent the current state of
+ * the USB system software and to control trace options.
+ *
+ * Usage:
+ *
+ *	(* Information *)
+ *	UsbInfo.Show ~ will display the current USB topology
+ *	UsbInfo.Show details ~ will display the current USB topology with all available information (descriptors, configurations,...)
+ *	UsbInfo.ShowDrivers ~ will display information about registered USB device drivers and their instances
+ *	UsbInfo.ShowHc ~ will display all registered USB host controllers
+ *	UsbInfo.ShowHc details ~ will display diagnostic information of all registered USB host controllers
+ * 	UsbInfo.ShowHc schedule ~ will display the scheduling data structures of all host controllers
+ *	UsbInfo.ShowHc pipes ~ will display all allocated pipes
+ *	UsbInfo.ShowHc pipemore ~ will display all allocated pipes including their QH/TDs
+ *	UsbInfo.ShowHc all ~ will display both the HC diagnostic information and its scheduling data structures
+ *
+ *	(* Trace options *)
+ *	UsbInfo.TraceAll ~ enables all trace options
+ *	UsbInfo.TraceNone ~ disables all trace options
+ *	UsbInfo.TraceShow ~ show state of trace options
+ *
+ *	(* See UsbDebug.Mod for a description of the individual trace optinos *)
+ *	UsbInfo.TraceOn Dm~ 			UsbInfo.TraceOff  Dm~
+ *	UsbInfo.TraceOn Parsing~ 		UsbInfo.TraceOff  Parsing~
+ *	UsbInfo.TraceOn DeviceStates~	UsbInfo.TraceOff  DeviceStates~
+ *	UsbInfo.TraceOn Control~ 		UsbInfo.TraceOff  Control~
+ *	UsbInfo.TraceOn ControlData~ 	UsbInfo.TraceOff  ControlData~
+ *	UsbInfo.TraceOn Transfers~ 		UsbInfo.TraceOff  Transfers~
+ *	UsbInfo.TraceOn Failed~ 			UsbInfo.TraceOff  Failed~
+ *	UsbInfo.TraceOn ShortPackets ~	UsbInfo.TraceOff  ShortPackets ~
+ *	UsbInfo.TraceOn Pipes~ 			UsbInfo.TraceOff  Pipes~
+ *	UsbInfo.TraceOn Copying~ 		UsbInfo.TraceOff  Copying~
+ *	UsbInfo.TraceOn Ioc~ 			UsbInfo.TraceOff  Ioc~
+ *	UsbInfo.TraceOn Init~ 			UsbInfo.TraceOff  Init~
+ *	UsbInfo.TraceOn Interrupts~ 		UsbInfo.TraceOff  Interrupts~
+ *	UsbInfo.TraceOn Queuing~ 		UsbInfo.TraceOff  Queuing~
+ *	UsbInfo.TraceOn HubRequests~ 	UsbInfo.TraceOff  HubRequests~
+ *	UsbInfo.TraceOn Suspend~		UsbInfo.TraceOff  Suspend~
+ *	UsbInfo.TraceOn Connects~		UsbInfo.TraceOff  Connects~
+ *	UsbInfo.TraceOn Info~ 			UsbInfo.TraceOff  Info~
+ *	UsbInfo.TraceOn Sensing~		UsbInfo.TraceOff  Sensing~
+ *	UsbInfo.TraceOn ScRequests~ 	UsbInfo.TraceOff  ScRequests~
+ *	UsbInfo.TraceOn ScTransfers~ 	UsbInfo.TraceOff  ScTransfers~
+ *	UsbInfo.TraceOn CSWs~ 			UsbInfo.TraceOff  CSWs~
+ *	UsbInfo.TraceOn CBWs~ 			UsbInfo.TraceOff  CBWs~
+ *	UsbInfo.TraceOn ScInit~		 	UsbInfo.TraceOff  ScInit~
+ *	UsbInfo.TraceOn Custom~		UsbInfo.TraceOff Custom~
+ *
+ *	UsbInfo.TraceOn Info Sensing ScRequests ScTransfers CSWs CBWs ScInit~ turns on all mass storage device related trace options
+ *	UsbInfo.TraceOff Info Sensing ScRequests ScTransfers CSWs CBWs ScInit~ turns off ...
+ *
+ *	UsbInfo.TraceOn Dm Parsing DeviceStates Failed Pipes Init HubRequests Connects Info ~ is interesting when connecting devices
+ *
+ *	SystemTools.Free UsbInfo ~
+ *
+ * History:
+ *
+ *	17.11.2005	Created (staubesv)
+ *	12.12.2005	Added schedule, pipes & all parameter to ShowHc (staubesv)
+ *	01.02.2006	Adapted ShowHc to UsbHcdi changes (staubesv)
+ *	06.02.2006	Added trace option control (staubesv)
+ *	26.02.2006	Added Custom trace option (staubesv)
+ *	28.06.2006	Adapted to modified Usb.GetRootHubs procedure (staubesv)
+ *	04.07.2006	Added ShowHc pipemore (staubesv)
+ *	05.01.2007	Added ShortPackets trace option, call ShowConfiguration in ShowDevice (staubesv)
+ *)
+
+IMPORT
+	SYSTEM,
+	Streams, Commands, Plugins, Strings,
+	UsbHcdi, Usb, Usbdi, UsbDebug;
+
+PROCEDURE ShowDeviceName(dev : Usb.UsbDevice; out : Streams.Writer);
+VAR descriptor : Usb.DeviceDescriptor;
+BEGIN
+	descriptor := dev.descriptor (Usb.DeviceDescriptor);
+	IF (descriptor # NIL) & (descriptor.sManufacturer # NIL) OR (descriptor.sProduct # NIL) THEN
+		IF descriptor.sManufacturer # NIL THEN out.String(descriptor.sManufacturer^); out.Char(" "); END;
+		IF descriptor.sProduct # NIL THEN out.String(descriptor.sProduct^); END;
+	ELSE
+		out.String("unknown device");
+	END;
+END ShowDeviceName;
+
+(* Shows device descriptor / qualifier information and all configurations including its interfaces and endpoints. *)
+PROCEDURE ShowDevice(dev : Usb.UsbDevice; indent : LONGINT; details : BOOLEAN; out : Streams.Writer);
+VAR a, c, e, i : LONGINT;
+BEGIN
+	IF dev.hubFlag THEN
+		IF dev.parent = dev THEN out.String("Root "); END;
+		out.String("Hub with "); out.Int(dev.nbrOfPorts, 0); out.String(" ports: ");
+	END;
+	ShowDeviceName(dev, out);
+	IF ~details OR (dev.hubFlag & (dev.parent = dev)) THEN RETURN END;
+	out.String("(S/N: ");
+	IF dev.descriptor(Usb.DeviceDescriptor).sSerialNumber # NIL THEN out.String(dev.descriptor(Usb.DeviceDescriptor).sSerialNumber^); out.String(")");
+	ELSE out.String("Not available)");
+	END;
+	out.Ln;
+	Indent(indent+4, out);
+	out.String("Address: "); out.Int(dev.address, 0); out.String(" ");
+	IF dev.speed = UsbHcdi.LowSpeed THEN out.String(" [LowSpeed]");
+	ELSIF dev.speed = UsbHcdi.FullSpeed THEN out.String(" [FullSpeed]");
+	ELSIF dev.speed = UsbHcdi.HighSpeed THEN out.String(" [HighSpeed]");
+	ELSE out.String(" [UnknownSpeed!!!]");
+	END;
+	out.Ln;
+	Indent(indent+4, out); out.String("Device descriptor information: ");
+	out.Ln;
+	ShowDescriptor(dev.descriptor (Usb.DeviceDescriptor), indent+8, out);
+	(* List all configurations *)
+	FOR c := 0 TO dev.descriptor.bNumConfigurations-1 DO
+		Indent(indent+12, out);
+		out.String("Configuration "); out.Int(c, 0); out.String(":");
+		IF dev.configurations[c](Usb.ConfigurationDescriptor).sConfiguration # NIL THEN  out.String(dev.configurations[c](Usb.ConfigurationDescriptor).sConfiguration^); END;
+		IF dev.actConfiguration = dev.configurations[c] THEN out.String(" [active]"); END; out.Ln;
+		ShowConfiguration(dev.configurations[c](Usb.ConfigurationDescriptor), indent + 16, out);
+		out.Ln;
+		(* List all interfaces *)
+		FOR i := 0 TO dev.configurations[c].bNumInterfaces - 1 DO
+			Indent(indent+16, out);
+			out.String("Interface "); out.Int(i, 0); out.String(": "); out.Ln;
+			ShowInterface(dev.configurations[c].interfaces[i] (Usb.InterfaceDescriptor), indent+20, out); out.Ln;
+			(* List all endpoints *)
+			FOR e := 0 TO dev.configurations[c].interfaces[i].bNumEndpoints-1 DO
+				ShowEndpoint(dev.configurations[c].interfaces[i].endpoints[e] (Usb.EndpointDescriptor), indent+24, out);
+			END;
+			(* List alternate interface if available *)
+			Indent(indent+20, out);
+			out.String("Alternate interface: ");
+			IF dev.configurations[c].interfaces[i].numAlternateInterfaces = 0 THEN out.String("n/a");  out.Ln;
+			ELSE
+				FOR a := 0 TO dev.configurations[c].interfaces[i].numAlternateInterfaces-1 DO
+					Indent(indent+20, out); out.String("Alternate Interface "); out.Int(a, 0); out.String(": ");
+					ShowInterface(dev.configurations[c].interfaces[i].alternateInterfaces[a] (Usb.InterfaceDescriptor), indent+20, out); out.Ln;
+					(* List all endpoints *)
+					FOR e := 0 TO dev.configurations[c].interfaces[i].bNumEndpoints-1 DO
+						ShowEndpoint(dev.configurations[c].interfaces[i].alternateInterfaces[a].endpoints[e] (Usb.EndpointDescriptor), indent+24, out);
+					END;
+				END;
+				out.Ln;
+			END;
+		END;
+	END;
+	Indent(indent+4, out);
+	out.String("Device qualifier information: ");
+	IF dev.qualifier = NIL THEN out.String("n/a"); out.Ln;
+	ELSE
+		out.Ln;
+		ShowDescriptor(dev.qualifier (Usb.DeviceDescriptor), indent + 8, out);
+		FOR c := 0 TO dev.qualifier.bNumConfigurations-1 DO
+			Indent(indent+12, out);
+			out.String("Other-Speed Configuration "); out.Int(c, 0); out.String(":");
+			IF dev.otherconfigurations[c](Usb.ConfigurationDescriptor).sConfiguration#NIL THEN  out.String(dev.configurations[c](Usb.ConfigurationDescriptor).sConfiguration^); END;
+			(* List all interfaces *)
+			FOR i := 0 TO dev.otherconfigurations[c].bNumInterfaces - 1 DO
+				out.Ln; Indent(indent+16, out);
+				out.String("Interface "); out.Int(i, 0); out.String(": "); out.Ln;
+				ShowInterface(dev.otherconfigurations[c].interfaces[i] (Usb.InterfaceDescriptor), indent+20, out); out.Ln;
+				(* List all endpoints *)
+				FOR e := 0 TO dev.otherconfigurations[c].interfaces[i].bNumEndpoints-1 DO
+					ShowEndpoint(dev.otherconfigurations[c].interfaces[i].endpoints[e] (Usb.EndpointDescriptor), indent+24, out);
+				END;
+				(* List alternate interface if available *)
+				Indent(indent+16, out);
+				out.String("Alternate interface: ");
+				IF dev.otherconfigurations[c].interfaces[i].numAlternateInterfaces = 0 THEN out.String("n/a");  out.Ln;
+				ELSE
+					FOR a := 0 TO dev.otherconfigurations[c].interfaces[i].numAlternateInterfaces-1 DO
+						out.String("Alternate Interface "); out.Int(a, 0); out.String(": "); out.Ln;
+						ShowInterface(dev.otherconfigurations[c].interfaces[i].alternateInterfaces[a] (Usb.InterfaceDescriptor), indent+20, out); out.Ln;
+						(* List all endpoints *)
+						FOR e := 0 TO dev.otherconfigurations[c].interfaces[i].bNumEndpoints-1 DO
+							ShowEndpoint(dev.otherconfigurations[c].interfaces[i].alternateInterfaces[a].endpoints[e] (Usb.EndpointDescriptor), indent+24, out);
+						END;
+					END;
+				END;
+			END;
+		END;
+	END;
+END ShowDevice;
+
+(* Display textual respresenation of device descriptor or device qualifier *)
+PROCEDURE ShowDescriptor(d : Usb.DeviceDescriptor;  indent : LONGINT; out : Streams.Writer);
+BEGIN
+	Indent(indent, out);
+	out.String("USB Version: "); PrintHex(LSH(d.bcdUSB, -8), out); out.Char("."); PrintHex(d.bcdUSB MOD 100H, out);
+	out.String(", Device Class: "); PrintHex(d.bDeviceClass, out);
+	out.String("H, Subclass: "); PrintHex(d.bDeviceSubClass, out);
+	out.String("H, Protocol: "); PrintHex(d.bDeviceProtocol, out); out.String("H");
+	out.Ln;
+	Indent(indent, out);
+	out.String("MaxPacketSize0: "); out.Int(d.bMaxPacketSize0, 0); out.String(" Bytes"); out.Ln;
+	Indent(indent, out);
+	out.String("idVendor: "); PrintHex(d.idVendor, out);
+	out.String("H,  idProduct: "); PrintHex(d.idProduct, out);
+	out.String("H,  Device Version: "); PrintHex(LSH(d.bcdDevice, -8), out); out.Char("."); PrintHex(d.bcdDevice MOD 100H, out);
+	out.Ln;
+END ShowDescriptor;
+
+(* Display textual respresentation of a USB device configuration *)
+PROCEDURE ShowConfiguration(c : Usb.ConfigurationDescriptor; indent : LONGINT; out : Streams.Writer);
+BEGIN
+	Indent(indent, out); out.String("ConfigurationValue: "); out.Int(c.bConfigurationValue, 0); out.Ln;
+	Indent(indent, out); out.String ("MaxPower: "); out.Int(c.bMaxPower, 0); out.String(" mA  "); out.Ln;
+	Indent(indent, out); out.String("Power support: ");
+	IF c.bmAttributes * {6} # {} THEN out.String("Self-Powered"); ELSE out.String("Bus-Powered"); END;
+	out.Ln;
+	Indent(indent, out); out.String("Remote Wake-up support: ");
+	IF c.bmAttributes * {5} # {} THEN out.String("Yes"); ELSE out.String("No"); END;
+END ShowConfiguration;
+
+(* Display textual representation of a USB device interface *)
+PROCEDURE ShowInterface(i : Usb.InterfaceDescriptor; indent : LONGINT; out : Streams.Writer);
+VAR drv : Usbdi.Driver;
+BEGIN
+	Indent(indent, out);
+	IF i.sInterface # NIL THEN out.String(i.sInterface^); out.String(": "); END;
+	out.String("[Class: "); PrintHex(i.bInterfaceClass, out);
+	out.String("H Subclass: "); PrintHex(i.bInterfaceSubClass, out);
+	out.String("H Protocol: "); PrintHex(i.bInterfaceProtocol, out);
+	out.String("H #Endpoints: "); out.Int(i.bNumEndpoints, 0);
+	out.String("]"); out.Ln;
+	Indent(indent, out);
+	drv := i.driver;
+	out.String("Driver: ");
+	IF drv # NIL THEN
+		out.String("["); out.String(drv.name);
+		out.String("("); out.String(drv.desc); out.String(")]");
+	ELSE out.String("[No driver installed for this interface]");
+	END;
+END ShowInterface;
+
+(* Display textual representation of a USB device endpoint *)
+PROCEDURE ShowEndpoint(e : Usb.EndpointDescriptor; indent : LONGINT; out : Streams.Writer);
+VAR attr : LONGINT;
+BEGIN
+	Indent(indent, out);
+	out.String("Endpoint "); 	out.Int(e.bEndpointAddress MOD 16, 0);
+	out.String(":"); out.String(" [Type: ");
+	attr := SYSTEM.VAL(LONGINT, e.bmAttributes);
+	CASE attr OF
+		0 : out.String("Control");
+		|1 : out.String("Isochronous");
+		|2 : out.String("Bulk");
+		|3 : out.String("Interrupt");
+	ELSE
+		out.String("unknown");
+	END;
+	IF (attr#0) & (attr<4) THEN
+		out.String("(");
+		IF (SYSTEM.VAL(SET, e.bEndpointAddress) * {7}) # {} THEN	 out.String("IN"); out.String(")");
+		ELSE out.String("OUT)");
+		END;
+	END;
+	IF attr = 1 THEN
+		out.String(", Synchronization: ");
+		CASE SYSTEM.VAL(LONGINT, LSH(SYSTEM.VAL(SET, attr) * {2..3}, -2)) OF
+			0 : out.String("None");
+			|1: out.String("Asynchronous");
+			|2: out.String("Adaptive");
+			|3: out.String("Synchronous");
+		END;
+		out.String(", Usage: ");
+		CASE SYSTEM.VAL(LONGINT, LSH(SYSTEM.VAL(SET, attr) * {2..3}, -2)) OF
+			0 : out.String("Data");
+			|1: out.String("Feedback");
+			|2: out.String("Implicit Feedback");
+			|3: out.String("Reserved");
+		END;
+	END;
+	out.String(" MaxPacketSize: "); out.Int(e.wMaxPacketSize, 0);
+	out.String(" Bytes IRQinterval: "); out.Int(e.bInterval, 0); out.String(" ms]");
+	out.Ln;
+END ShowEndpoint;
+
+(* Display textual representation of the specified device and its descendants *)
+PROCEDURE ShowDeviceChain(dev : Usb.UsbDevice; indent : LONGINT; details : BOOLEAN; out : Streams.Writer);
+VAR i, j : LONGINT;
+BEGIN
+	IF dev = NIL THEN out.String("No device attached");
+	ELSIF dev.hubFlag THEN
+		ShowDevice(dev, indent, details, out); out.Ln;
+		FOR i := 0 TO dev.nbrOfPorts - 1 DO
+			FOR j := 0 TO indent - 1 DO out.Char(" "); END;
+			out.String("    Port "); out.Int(i+1, 0); out.String(": ");
+			IF dev.deviceAtPort[i] = NIL THEN out.String("No device attached."); out.Ln;
+			ELSIF dev.portPermanentDisabled[i] THEN out.String("Permanent disable (error)"); out.Ln;
+			ELSE ShowDeviceChain(dev.deviceAtPort[i], indent+8, details, out);
+			END;
+		END;
+	ELSE ShowDevice(dev, indent, details, out); out.Ln;
+	END;
+END ShowDeviceChain;
+
+PROCEDURE DoShow*(out : Streams.Writer; details: BOOLEAN);
+VAR
+	i : LONGINT;
+	rootHubs : Usb.RootHubArray;
+BEGIN
+	out.String("Usb: Topology and device information: "); out.Ln;
+	Usb.GetRootHubs(rootHubs);
+	BEGIN {EXCLUSIVE}
+		IF rootHubs # NIL THEN
+			FOR i := 0 TO LEN(rootHubs)-1 DO
+				ShowDeviceChain(rootHubs[i], 0, details, out);
+				rootHubs[i] := NIL;
+			END;
+		ELSE out.String("No USB host controllers found."); out.Ln;
+		END;
+	END;
+END DoShow;
+
+
+(** Prints information about current usb tree *)
+PROCEDURE Show*(context : Commands.Context);
+VAR
+	details : BOOLEAN;
+	pstr : ARRAY 10 OF CHAR;
+BEGIN
+	context.arg.SkipWhitespace; context.arg.String(pstr);
+	details :=  Strings.Match("details", pstr);
+	DoShow(context.out, details);
+END Show;
+
+(** Shows all registered drivers and their instances *)
+PROCEDURE ShowDrivers*(context : Commands.Context);
+VAR instances : Plugins.Table; i : LONGINT;
+BEGIN
+	Usb.drivers.Show;
+	context.out.Ln; context.out.String("Usb: Instances of registered device drivers: "); context.out.Ln;
+	Usb.usbDrivers.GetAll(instances);
+	IF instances=NIL THEN
+		context.out.String("no device drivers instances installed"); context.out.Ln;
+	ELSE
+		FOR i:=0 TO LEN(instances)-1 DO
+			context.out.String("   ");
+			context.out.String(instances[i].name); context.out.String(" (");
+			context.out.String(instances[i].desc); context.out.String(")");
+			context.out.Ln;
+		END;
+	END;
+END ShowDrivers;
+
+(** Shows all registered USB host controllers. *)
+PROCEDURE ShowHc*(context : Commands.Context); (* ["details"|"schedule"|"all"] ~ *)
+VAR
+	table : Plugins.Table; hcd : UsbHcdi.Hcd;
+	pstr : ARRAY 10 OF CHAR;
+	i : LONGINT;
+
+	PROCEDURE ShowPipes(hcd : UsbHcdi.Hcd; details : BOOLEAN);
+	VAR i, j, k : LONGINT; pipe : UsbHcdi.Pipe;
+	BEGIN
+		FOR i := 0 TO 127 DO (* search all addresses ... *)
+			FOR j := 0 TO 15 DO (* ... and all endpoints for presence of a pipe *)
+				FOR k := 0 TO 1 DO
+					pipe := hcd.pipes[i][k][j];
+					IF pipe # NIL THEN
+						context.out.String("ADR: "); context.out.Int(i, 0); context.out.String(": "); context.out.Update; pipe.Show(details);
+						IF details THEN TRACE(hcd); hcd.ShowPipe(pipe) END;
+					END;
+				END;
+			END;
+		END;
+	END ShowPipes;
+
+BEGIN
+	context.arg.SkipWhitespace; context.arg.String(pstr);
+	UsbHcdi.controllers.GetAll(table);
+	IF table # NIL THEN
+		FOR i := 0 TO LEN(table)-1 DO
+			hcd := table[i] (UsbHcdi.Hcd);
+			context.out.String("**** "); context.out.String(hcd.name); context.out.String(" ("); context.out.String(hcd.desc); context.out.String(")"); context.out.Ln;
+			context.out.Update;
+			IF Strings.Match("schedule", pstr) THEN
+				IF UsbDebug.Trace THEN hcd.ShowSchedule; ELSE context.out.String("UsbInfo: UsbDebug.Trace is FALSE. Cannot show schedule."); context.out.Ln; END;
+			ELSIF Strings.Match("details", pstr) THEN
+				IF UsbDebug.Trace THEN hcd.Diag; ELSE context.out.String("UsbInfo: UsbDebug.Trace is FALSE. Cannot show diagnostics."); context.out.Ln; END;
+			ELSIF Strings.Match("pipes", pstr) THEN
+				ShowPipes(hcd, FALSE);
+			ELSIF Strings.Match("pipemore", pstr) THEN
+				ShowPipes(hcd, TRUE);
+			ELSIF Strings.Match("all", pstr) THEN
+				IF UsbDebug.Trace THEN hcd.Diag; hcd.ShowSchedule;
+				ELSE
+					context.out.String("UsbInfo: UsbDebug.Trace is FALSE. Cannot show schedule/diagnostics."); context.out.Ln;
+				END;
+				ShowPipes(hcd, TRUE);
+			END;
+			context.out.Ln;
+		END;
+	ELSE
+		context.out.String("UsbInfo: No USB host controllers found."); context.out.Ln;
+	END;
+END ShowHc;
+
+(* Helper: Displays the number <was> in hex to the kernel log *)
+PROCEDURE PrintHex(was: LONGINT; out : Streams.Writer);
+VAR z,d,h,i:LONGINT;
+BEGIN
+	z := 0;
+	d := 16*16*16*16*16*16*16; (* what a quick hack *)
+	FOR i:=0 TO 7 DO
+		h := (was DIV d) MOD 16;
+		IF (z = 1) OR (h # 0) OR (i = 7) THEN
+			z := 1;
+			IF h < 10 THEN out.Int(h,0); ELSE out.Char(CHR(ORD("A")+h-10)); END;
+		END;
+		d:=d DIV 16;
+	END;
+END PrintHex;
+
+(* Helper *)
+PROCEDURE Indent(indent : LONGINT; out : Streams.Writer);
+VAR i : LONGINT;
+BEGIN
+	FOR i := 0 TO indent-1 DO out.Char(" "); END;
+END Indent;
+
+(** Trace options interface *)
+
+PROCEDURE TraceAll*(context : Commands.Context);
+BEGIN
+	IF UsbDebug.Trace THEN
+		context.out.String("UsbInfo: All trace options enabled."); context.out.Ln;
+		UsbDebug.traceDm := TRUE;
+		UsbDebug.traceParsing := TRUE;
+		UsbDebug.traceDeviceStates := TRUE;
+		UsbDebug.traceControl := TRUE;
+		UsbDebug.traceControlData := TRUE;
+		UsbDebug.traceTransfers := TRUE;
+		UsbDebug.traceFailed := TRUE;
+		UsbDebug.traceShortPackets := TRUE;
+		UsbDebug.tracePipes := TRUE;
+		UsbDebug.traceCopying := TRUE;
+		UsbDebug.traceIoc := TRUE;
+		UsbDebug.traceInit := TRUE;
+		UsbDebug.traceInterrupts := TRUE;
+		UsbDebug.traceQueuing := TRUE;
+		UsbDebug.traceHubRequests := TRUE;
+		UsbDebug.traceSuspend := TRUE;
+		UsbDebug.traceConnects := TRUE;
+		UsbDebug.traceInfo := TRUE;
+		UsbDebug.traceSensing := TRUE;
+		UsbDebug.traceScRequests := TRUE;
+		UsbDebug.traceScTransfers := TRUE;
+		UsbDebug.traceCSWs := TRUE;
+		UsbDebug.traceCBWs := TRUE;
+		UsbDebug.traceScInit := TRUE;
+		UsbDebug.traceCustom := TRUE;
+	ELSE
+		context.out.String("UsbInfo: UsbDebug.Trace is FALSE... cannot enable tracing."); context.out.Ln;
+	END;
+END TraceAll;
+
+PROCEDURE TraceNone*(context : Commands.Context);
+BEGIN
+	UsbDebug.traceDm := FALSE;
+	UsbDebug.traceParsing := FALSE;
+	UsbDebug.traceDeviceStates := FALSE;
+	UsbDebug.traceControl := FALSE;
+	UsbDebug.traceControlData := FALSE;
+	UsbDebug.traceTransfers := FALSE;
+	UsbDebug.traceFailed := FALSE;
+	UsbDebug.traceShortPackets := FALSE;
+	UsbDebug.tracePipes := FALSE;
+	UsbDebug.traceCopying := FALSE;
+	UsbDebug.traceIoc := FALSE;
+	UsbDebug.traceInit := FALSE;
+	UsbDebug.traceInterrupts := FALSE;
+	UsbDebug.traceQueuing := FALSE;
+	UsbDebug.traceHubRequests := FALSE;
+	UsbDebug.traceSuspend := FALSE;
+	UsbDebug.traceConnects := FALSE;
+	UsbDebug.traceInfo := FALSE;
+	UsbDebug.traceSensing := FALSE;
+	UsbDebug.traceScRequests := FALSE;
+	UsbDebug.traceScTransfers := FALSE;
+	UsbDebug.traceCSWs := FALSE;
+	UsbDebug.traceCBWs := FALSE;
+	UsbDebug.traceScInit := FALSE;
+	UsbDebug.traceCustom := FALSE;
+	context.out.String("UsbInfo: All trace options disabled."); context.out.Ln;
+END TraceNone;
+
+PROCEDURE TraceShow*(context : Commands.Context);
+
+	PROCEDURE ShowOn(on : BOOLEAN);
+	BEGIN
+		IF on THEN context.out.String("On"); ELSE context.out.String("Off"); END;
+	END ShowOn;
+
+BEGIN
+	context.out.String("UsbInfo: Trace options state: "); context.out.Ln;
+	context.out.String("UsbDebug.Trace: "); ShowOn(UsbDebug.Trace); context.out.Ln;
+	context.out.String("traceDm: "); ShowOn(UsbDebug.traceDm); context.out.Ln;
+	context.out.String("traceParsing: "); ShowOn(UsbDebug.traceParsing); context.out.Ln;
+	context.out.String("traceDeviceStates: "); ShowOn(UsbDebug.traceDeviceStates); context.out.Ln;
+	context.out.String("traceControl: "); ShowOn(UsbDebug.traceControl); context.out.Ln;
+	context.out.String("traceControlData: "); ShowOn(UsbDebug.traceControlData); context.out.Ln;
+	context.out.String("traceTransfers: "); ShowOn(UsbDebug.traceTransfers); context.out.Ln;
+	context.out.String("traceFailed: "); ShowOn(UsbDebug.traceFailed); context.out.Ln;
+	context.out.String("traceShortPackets: "); ShowOn(UsbDebug.traceShortPackets); context.out.Ln;
+	context.out.String("tracePipes: "); ShowOn(UsbDebug.tracePipes); context.out.Ln;
+	context.out.String("traceCopying: "); ShowOn(UsbDebug.traceCopying); context.out.Ln;
+	context.out.String("traceIoc: "); ShowOn(UsbDebug.traceIoc); context.out.Ln;
+	context.out.String("traceInit: "); ShowOn(UsbDebug.traceInit); context.out.Ln;
+	context.out.String("traceInterrupts: "); ShowOn(UsbDebug.traceInterrupts); context.out.Ln;
+	context.out.String("traceQueuing: "); ShowOn(UsbDebug.traceQueuing); context.out.Ln;
+	context.out.String("traceHubRequests: "); ShowOn(UsbDebug.traceHubRequests); context.out.Ln;
+	context.out.String("traceSuspend: "); ShowOn(UsbDebug.traceSuspend); context.out.Ln;
+	context.out.String("traceConnects: "); ShowOn(UsbDebug.traceConnects); context.out.Ln;
+	context.out.String("traceInfo: "); ShowOn(UsbDebug.traceInfo); context.out.Ln;
+	context.out.String("traceSensing: "); ShowOn(UsbDebug.traceSensing); context.out.Ln;
+	context.out.String("traceScRequests: "); ShowOn(UsbDebug.traceScRequests); context.out.Ln;
+	context.out.String("traceScTransfers: "); ShowOn(UsbDebug.traceScTransfers); context.out.Ln;
+	context.out.String("traceCSWs: "); ShowOn(UsbDebug.traceCSWs); context.out.Ln;
+	context.out.String("traceCBWs: "); ShowOn(UsbDebug.traceCBWs); context.out.Ln;
+	context.out.String("traceScInit: "); ShowOn(UsbDebug.traceScInit); context.out.Ln;
+	context.out.String("traceCustom: "); ShowOn(UsbDebug.traceCustom); context.out.Ln;
+END TraceShow;
+
+PROCEDURE TraceOn*(context : Commands.Context);
+BEGIN
+	IF UsbDebug.Trace THEN
+		TraceOnOff(context, TRUE);
+	ELSE
+		context.out.String("UsbInfo: UsbDebug.Trace is FALSE... cannot use tracing."); context.out.Ln;
+	END;
+END TraceOn;
+
+PROCEDURE TraceOff*(context : Commands.Context);
+BEGIN
+	TraceOnOff(context, FALSE);
+END TraceOff;
+
+PROCEDURE TraceOnOff(context : Commands.Context; on : BOOLEAN);
+VAR pstr : ARRAY 32 OF CHAR; invalid : BOOLEAN;
+BEGIN
+	WHILE 	context.arg.GetString(pstr) DO
+		 invalid := FALSE;
+		IF Strings.Match("Dm", pstr) THEN UsbDebug.traceDm := on;
+		ELSIF Strings.Match("Parsing", pstr) THEN UsbDebug.traceParsing := on;
+		ELSIF Strings.Match("DeviceStates", pstr) THEN UsbDebug.traceDeviceStates := on;
+		ELSIF Strings.Match("Control", pstr) THEN UsbDebug.traceControl := on;
+		ELSIF Strings.Match("ControlData", pstr) THEN UsbDebug.traceControlData := on;
+		ELSIF Strings.Match("Transfers", pstr) THEN UsbDebug.traceTransfers := on;
+		ELSIF Strings.Match("Failed", pstr) THEN UsbDebug.traceFailed := on;
+		ELSIF Strings.Match("ShortPackets", pstr) THEN UsbDebug.traceShortPackets := on;
+		ELSIF Strings.Match("Pipes", pstr) THEN UsbDebug.tracePipes := on;
+		ELSIF Strings.Match("Copying", pstr) THEN UsbDebug.traceCopying := on;
+		ELSIF Strings.Match("Ioc", pstr) THEN UsbDebug.traceIoc := on;
+		ELSIF Strings.Match("Init", pstr) THEN UsbDebug.traceInit := on;
+		ELSIF Strings.Match("Interrupts", pstr) THEN UsbDebug.traceInterrupts := on;
+		ELSIF Strings.Match("Queuing", pstr) THEN UsbDebug.traceQueuing := on;
+		ELSIF Strings.Match("HubRequests", pstr) THEN UsbDebug.traceHubRequests := on;
+		ELSIF Strings.Match("Suspend", pstr) THEN UsbDebug.traceSuspend := on;
+		ELSIF Strings.Match("Connects", pstr) THEN UsbDebug.traceConnects := on;
+		ELSIF Strings.Match("Info", pstr) THEN UsbDebug.traceInfo := on;
+		ELSIF Strings.Match("Sensing", pstr) THEN UsbDebug.traceSensing := on;
+		ELSIF Strings.Match("ScRequests", pstr) THEN UsbDebug.traceScRequests := on;
+		ELSIF Strings.Match("ScTransfers", pstr) THEN UsbDebug.traceScTransfers := on;
+		ELSIF Strings.Match("CSWs", pstr) THEN UsbDebug.traceCSWs := on;
+		ELSIF Strings.Match("CBWs", pstr) THEN UsbDebug.traceCBWs := on;
+		ELSIF Strings.Match("ScInit", pstr) THEN UsbDebug.traceScInit := on;
+		ELSIF Strings.Match("Custom", pstr) THEN UsbDebug.traceCustom := on;
+		ELSE
+			context.error.String("Trace option '"); context.error.String(pstr); context.error.String("' not known."); context.error.Ln;
+			invalid := TRUE;
+		END;
+		IF ~invalid THEN
+			context.out.String("Trace option '"); context.out.String(pstr); context.out.String("' turned ");
+			IF on THEN context.out.String("on."); ELSE context.out.String("off."); END;
+		END;
+		context.out.Ln;
+	END;
+END TraceOnOff;
+
+END UsbInfo.
+
+UsbInfo.Open ~
+UsbInfo.Show ~
+UsbInfo.Show details ~
+UsbInfo.ShowDrivers ~
+UsbInfo.ShowHc ~
+UsbInfo.ShowHc details ~
+
+SystemTools.Free UsbInfo ~

+ 143 - 0
source/ARM.UsbMouse.Mod

@@ -0,0 +1,143 @@
+MODULE UsbMouse;  (** AUTHOR "staubesv"; PURPOSE "USB Mouse Driver"; *)
+(**
+ * Bluebottle USB Mouse Driver (HID boot protocol)
+ *
+ * Usage:
+ *	UsbMouse.Install ~ loads this driver
+ *	SystemTools.Free UsbMouse ~ unloads this driver
+ *
+ *	The HID boot protocol for USB mice supports 3 buttons and 2 axes.
+ *
+ * References:
+ *	Device Class Definition for Human Interface Devices (HID), Version 1.11, 27.06.2001, www.usb.org
+ *
+ * History:
+ *	01.12.2005	Added MouseSpeed & MouseAcceleration (staubesv)
+ *	10.10.2006	Adapted to UsbHid (staubesv)
+ *	28.02.2007	Removed mouse wheel hack since new HID driver can correctly handle it (staubesv)
+ *)
+
+IMPORT SYSTEM, KernelLog, Modules, Inputs, Usbdi, UsbHid;
+
+CONST
+
+	Name = "UsbMouse";
+	Description = "HID boot protocol mouse driver";
+
+	(* Mouse Configuration *)
+	MouseSpeed = 50;
+	MouseAcceleration = 0;
+
+	Debug = TRUE;
+
+TYPE
+
+	MouseDriver= OBJECT (UsbHid.HidDriver);
+	VAR
+		buffer : Usbdi.BufferPtr;
+		pipe : Usbdi.Pipe;
+
+		lastDx, lastDy : LONGINT;
+		accelX, accelY : REAL;
+
+		PROCEDURE HandleEvent(status : Usbdi.Status; actLen : LONGINT);
+		VAR mm : Inputs.MouseMsg; dx, dy : LONGINT;
+		BEGIN
+			IF (status = Usbdi.Ok) OR ((status = Usbdi.ShortPacket) & (actLen >= 3))  THEN
+				dx := SYSTEM.VAL(SHORTINT, buffer[1]);
+				dy := SYSTEM.VAL(SHORTINT, buffer[2]);
+
+				accelX := 1.0 + ABS(dx - lastDx) / 128 * MouseAcceleration;
+				accelY := 1.0 + ABS(dy - lastDy) / 128 * MouseAcceleration;
+
+				lastDx := dx;
+				lastDy := dy;
+
+				mm.dx :=ENTIER(MouseSpeed / 50.0 *  dx * accelX);
+				mm.dy := ENTIER(MouseSpeed / 50.0 * dy * accelY);
+
+				IF (SYSTEM.VAL(SET, buffer[0]) * {0}) # {} THEN mm.keys := mm.keys + {0}; END;
+				IF (SYSTEM.VAL(SET, buffer[0]) * {1}) # {} THEN mm.keys := mm.keys + {2}; END;
+				IF (SYSTEM.VAL(SET, buffer[0]) * {2}) # {} THEN mm.keys := mm.keys + {1}; END;
+
+				Inputs.mouse.Handle(mm);
+				status := pipe.Transfer(pipe.maxPacketSize, 0, buffer);
+			ELSE
+				IF status = Usbdi.Stalled THEN
+					IF pipe.ClearHalt() THEN
+						IF Debug THEN KernelLog.String("UsbMouse: Stall on Interrupt Pipe cleared."); KernelLog.Ln; END;
+						status := pipe.Transfer(pipe.maxPacketSize, 0, buffer); (* ignore status *)
+					ELSE
+						IF Debug THEN KernelLog.String("UsbMouse: Couldn't clear stall on interrupt pipe. Abort."); KernelLog.Ln; END;
+						device.FreePipe(pipe);
+					END;
+				END;
+			END;
+		END HandleEvent;
+
+		PROCEDURE Connect() : BOOLEAN;
+		VAR endpoint, i : LONGINT; status : Usbdi.Status;
+		BEGIN
+			(* Set the HID boot protocol *)
+			IF SetProtocol(UsbHid.BootProtocol) = FALSE THEN
+				IF Debug THEN KernelLog.String("UsbMouse: Error: Cannot set boot protocol."); KernelLog.Ln; END;
+				RETURN FALSE
+			END;
+
+			(* Look for the first interrupt IN endpoint of this device *)
+			LOOP
+				IF i >= LEN(interface.endpoints) THEN EXIT; END;
+				IF interface.endpoints[i].type = Usbdi.InterruptIn THEN
+					endpoint := interface.endpoints[i].bEndpointAddress;
+					EXIT;
+				END;
+				INC(i);
+			END;
+
+			IF endpoint = 0 THEN
+				IF Debug THEN KernelLog.String("UsbMouse: No interrupt IN endpoint found."); KernelLog.Ln; END;
+				RETURN FALSE;
+			END;
+
+			pipe := device.GetPipe(endpoint);
+			IF pipe = NIL THEN RETURN FALSE END;
+
+			NEW(buffer, pipe.maxPacketSize);
+
+			pipe.SetTimeout(0);
+			pipe.SetCompletionHandler(HandleEvent);
+
+			status := pipe.Transfer(pipe.maxPacketSize, 0, buffer); (* ignore res *)
+			RETURN TRUE;
+		END Connect;
+
+		PROCEDURE Disconnect;
+		BEGIN
+			KernelLog.String("USB mouse disconnected."); KernelLog.Ln;
+		END Disconnect;
+
+	END MouseDriver;
+
+PROCEDURE Probe(dev : Usbdi.UsbDevice; id : Usbdi.InterfaceDescriptor) : Usbdi.Driver;
+VAR driver : MouseDriver;
+BEGIN
+	IF id.bInterfaceClass # 3 THEN RETURN NIL END; (* HID class *)
+	IF id.bInterfaceSubClass # 1 THEN RETURN NIL END; (* Boot protocol subclass *)
+	IF id.bInterfaceProtocol # 2 THEN RETURN NIL END; (* Mouse *)
+	NEW(driver); RETURN driver;
+END Probe;
+
+PROCEDURE Install*;
+END Install;
+
+PROCEDURE Cleanup;
+BEGIN
+	Usbdi.drivers.Remove(Name);
+END Cleanup;
+
+BEGIN
+	Modules.InstallTermHandler(Cleanup);
+	Usbdi.drivers.Add(Probe, Name, Description, 10)
+END UsbMouse.
+
+UsbMouse.Install ~  SystemTools.Free UsbMouse ~ 

+ 21 - 2
source/Release.Tool

@@ -630,6 +630,10 @@ PACKAGE Drivers ARCHIVE "Drivers.zip" SOURCE "DriversSrc.zip" DESCRIPTION "Devic
 		#USB <-> RS232 FTDI driver
 		UsbFTDI.Mod
 	}
+	
+	NATIVE & ARM {
+		ARM.UsbMouse.Mod
+	}
 
 	BIOS & I386 { I386.UsbKeyboard.Mod }
 	BIOS & AMD64 { AMD64.UsbKeyboard.Mod }
@@ -672,6 +676,13 @@ PACKAGE Drivers ARCHIVE "Drivers.zip" SOURCE "DriversSrc.zip" DESCRIPTION "Devic
 		# Floppy disk driver
 		BIOS.Diskettes.Mod
 	}
+	
+	ZYNQ & ~COOP {
+		Zynq.SdEnvironment.Mod
+		Sd.Mod
+		SdDisks.Mod
+		Zynq.SdControllers.Mod
+	}
 END
 
 PACKAGE Compiler ARCHIVE "Compiler.zip" SOURCE "CompilerSrc.zip" DESCRIPTION "Active Oberon Compiler"
@@ -842,9 +853,17 @@ PACKAGE ApplicationsMini ARCHIVE "ApplicationsMini.zip" SOURCE "ApplicationsMini
 
 	# Animation codecs
 	AnimationCodec.Mod
-
+	
+	NATIVE & ~ARM { # USB info tools & documentation
+		UsbInfo.Mod
+	}
+	
+	NATIVE & ARM {
+		ARM.UsbInfo.Mod
+	}
+	
 	NATIVE { # USB info tools & documentation
-		UsbInfo.Mod UsbSkeleton.Mod UsbTools.Mod Usb.Text
+		UsbSkeleton.Mod UsbTools.Mod Usb.Text
 	}
 
 	# Partitions Framework

+ 3067 - 0
source/Sd.Mod

@@ -0,0 +1,3067 @@
+MODULE Sd;
+(**
+	AUTHOR Timothée Martiel, 2015
+	PURPOSE SD Card Host Controller Driver
+*)
+
+IMPORT
+	SYSTEM, SdEnvironment, Log := SdEnvironment;
+
+CONST
+	BlockSize * = 512;
+	InitialClockFrequency * = 400000; (* Hz *)
+
+	(* Commands *) (*! Do not change values *)
+	CMD_GO_IDLE_STATE * = 0; (** CMD0 bc [31:0] stuff bits - *)
+	CMD_ALL_SEND_CID * = 2; (** CMD2 bcr [31:0] stuff bits R2 *)
+	CMD_SEND_RELATIVE_ADDR * = 3; (** CMD3 bcr [31:0] stuff bits R6 *)
+	CMD_SET_DSR * = 4; (** CMD4 bc [31:16] DSR [15:0] stuff bits - *)
+	CMD_IO_SEND_OP_COND * = 5; (** CMD5 ?? [31:25] stuff bits [24] switch 1.8V request [23:0] I/O OCR R4 *)
+	CMD_SWITCH_FUNC * = 6; (** CMD6 adtc [31] Mode 0:Check function 1:Switch function [30:24] reserved (All '0') [23:20] reserved for function group 6 (0h or Fh) [19:16] reserved for function group 5 (0h or Fh) [15:12] function group 4 for current limit [11:8] funciton group 3 for drive strength [7:4] function group 2 for command system [3:0] function group 1 for access mode R1 *)
+	CMD_SELECT_DESELECT_CARD * = 7; (** CMD7 ac [31:16] RCA [15:0] stuff bits R1b (only from the selected card) *)
+	CMD_SEND_IF_COND * = 8; (** CMD8 bcr [31:12] reserved bits [11:8] supply voltage(VHS) [7:0]check pattern R7 *)
+	CMD_SEND_CSD * = 9; (** CMD9 ac [31:16] RCA [15:0] stuff bits R2 *)
+	CMD_SEND_CID * = 10; (** CMD10 ac [31:16] RCA [15:0] stuff bits R2 *)
+	CMD_VOLTAGE_SWITCH * = 11; (** CMD11 ac [31:0] reserved bits (all 0) R1 *)
+	CMD_STOP_TRANSMISSION * = 12; (** CMD12 ac [31:0] stuff bits R1b *)
+	CMD_SEND_STATUS * = 13; (** CMD13 ac [31:16] RCA [15:0] stuff bits R1 *)
+	CMD_GO_INACTIVE_STATE * = 15; (** CMD15 ac [31:16] RCA [15:0] reserved bits - *)
+	CMD_SET_BLOCKLEN * = 16; (** CMD16 ac [31:0] block length R1 *)
+	CMD_READ_SINGLE_BLOCK * = 17; (** CMD17 adtc [31:0] data address2 R1 *)
+	CMD_READ_MULTIPLE_BLOCK * = 18; (** CMD18 adtc [31:0] data address2 R1 *)
+	CMD_SEND_TUNING_BLOCK * = 19; (** CMD19 adtc [31:0] reserved bits (all 0) R1 *)
+	CMD_SPEED_CLASS_CONTROL * = 20; (** CMD20 ac [31:28]Speed Class Control [27:0] Reserved (all-0) R1b *)
+	CMD_SET_BLOCK_COUNT * = 23; (** CMD23 ac [31:0] Block Count R1 *)
+	CMD_WRITE_BLOCK * = 24; (** CMD24 adtc [31:0] data address2 R1 *)
+	CMD_WRITE_MULTIPLE_BLOCK * = 25; (** CMD25 adtc [31:0] data address2 R1 *)
+	CMD_PROGRAM_CSD * = 27; (** CMD27 adtc [31:0] stuff bits R1 *)
+	CMD_SET_WRITE_PROT * = 28; (** CMD28 ac [31:0] data address2 R1b *)
+	CMD_CLR_WRITE_PROT * = 29; (** CMD29 ac [31:0] data address2 R1b *)
+	CMD_SEND_WRITE_PROT * = 30; (** CMD30 adtc [31:0] write protect data address2 R1 *)
+	CMD_ERASE_WR_BLK_START * = 32; (** CMD32 ac [31:0] data address1 R1 *)
+	CMD_ERASE_WR_BLK_END * = 33; (** CMD33 ac [31:0] data address1 R1 *)
+	CMD_ERASE * = 38; (** CMD38 ac [31:0] stuff bits R1b *)
+	CMD_LOCK_UNLOCK * = 42; (** CMD42 adtc [31:0] Reserved bits (Set all 0) R1 *)
+	CMD_APP_CMD * = 55; (** CMD55 ac [31:16] RCA [15:0] stuff bits R1 *)
+	CMD_GEN_CMD * = 56; (** CMD56 adtc [31:1] stuff bits. [0] RD/WR R1 *)
+
+	(** Application Commands *) (*! Do Not Change Values *)
+	ACMD_SET_BUS_WIDTH * = 6; (** ACMD6 ac [31:2] stuff bits [1:0] bus width R1 *)
+	ACMD_SD_STATUS * = 13; (** ACMD13 adtc [31:0] stuff bits R1 *)
+	ACMD_SEND_NUM_WR_BLOCKS * = 22; (** ACMD22 adtc [31:0] stuff bits R1 *)
+	ACMD_SET_WR_BLK_ERASE_COUNT * = 23; (** ACMD23 ac [31:23] stuff bits [22:0] Number of blocks R1 *)
+	ACMD_SD_SEND_OP_COND * = 41; (** ACMD41 bcr [31]reserved bit [30] HCS(OCR[30]) [29] reserved for eSD [28] XPC [27:25] reserved bits [24] S18R [23:0] VDD Voltage Window(OCR[23:0]) R3 *)
+	ACMD_SET_CLR_CARD_DETECT * = 42; (** ACMD42 ac [31:1] stuff bits [0] set_cd R1 *)
+	ACMD_SEND_SCR * = 51; (** ACMD51 adtc [31:0] stuff bits R1 *)
+
+	(** Errors *)
+	ErrorNone * = 0; (** No error *)
+	ErrorCmdTimeout * = 1; (** Timeout on command line *)
+	ErrorCmdCrc * = 2; (** CRC error on command line *)
+	ErrorDatTimeout * = 3; (** Timeout on data line *)
+	ErrorDatCrc * = 4; (** CRC error on data line *)
+	ErrorNoCard * = 5; (** No card present *)
+	ErrorCard * = 6; (** Card failed to perform operation *)
+	ErrorUnrecoverable * = 7; (** Host controller in an unrecoverable state *)
+	ErrorInvalidParameters * = 8; (** Invalid parameters *)
+
+	(** Card Versions: maximal SD physical layer specifications version supported by the card *)
+	Version1 * = 0; (** v1.00 or v1.01 *)
+	Version1p1 * = 1; (** v1.10 *)
+	Version2 * = 2; (** v2.00 *)
+	Version3 * = 3; (** v3.01 *)
+	Version4 * = 4; (** v4.10 *)
+	Version5 * = 5; (** v5.10 *)
+	Version6 * =6; (** v6.0 *)
+
+	(** Card Type *)
+	TypeNone * = 0; (** Unknow *)
+	TypeSDSC * = 1; (** SD standard capacity - physical specs v1.0 or v1.1, limited to 2 GB *)
+	TypeSDHC * = 2; (** SD High Capacity - 2 GB to 32 GB *)
+	TypeSDXC * = 3; (** SD Extended Capacity - 32 GB to 2 TB *)
+
+	(** Card Events *)
+	OnInitialization * = 0;
+	OnRemoval * = 1;
+	OnReadComplete * = 2;
+	OnWriteComplete * = 3;
+
+	(** Command Record Flags *)
+	FlagData * = 0;
+	FlagRead * = 1;
+	FlagAutoCmd12 * = 2;
+	FlagAutoCmd23 * = 3;
+	FlagMultipleBlockTx * = 4;
+	FlagCountBlocks * = 5;
+	FlagAbort * = 7;
+	FlagApplicationCmd * = 8;
+	FlagIgnoreIllegalCmd * = 9;
+
+	(** Response Types *) (*! Do not change values *)
+	ResponseNone	 * = -1;
+	ResponseR1 * = 0;
+	ResponseR1b * = 1;
+	ResponseR2 * = 2;
+	ResponseR3 * = 3;
+	ResponseR4 * = 4;
+	ResponseR5 * = 5;
+	ResponseR5b * = 6;
+	ResponseR6 * = 7;
+	ResponseR7 * = 8;
+
+	(** Host Controller States *)
+	HcOperational * = 0; (** Host controller is operational *)
+	HcConfiguring * = 1; (** Host controller is waiting for configuration input *)
+	HcError * = 2; (** Error occurred *)
+
+	(** Card States *)
+	CardIdle = 0;
+	CardReady = 1;
+	CardIdentification = 2;
+	CardStandby = 3;
+	CardTransfer = 4;
+	CardData = 5;
+	CardReceive = 6;
+	CardProgram = 7;
+	CardDisabled = 8;
+
+	(** Operation modes *)
+	OpCpu = 0;
+	OpSdma = 1;
+	OpAdma = 2;
+
+	(* Present State bits *)
+	PresentState_CommandInhibitCmd = 0;
+	PresentState_CommandInhibitDat = 1;
+	PresentState_DatLineActive = 2;
+	PresentState_RetuningRequest = 3;
+	PresentState_WriteTransferActive = 8;
+	PresentState_ReadTransferActive = 9;
+	PresentState_BufferWriteEnable = 10;
+	PresentState_BufferReadEnable = 11;
+	PresentState_CardInserted = 16;
+	PresentState_CardStateStable = 17;
+	PresentState_CardDetectPinLevel = 18;
+	PresentState_WriteProtectSwitchPinLevel = 19;
+	PresentState_CmdLineSignalLevel = 24;
+	PresentState_DatLineSignalLevelOfs = 20;
+	PresentState_DatLineSignalLevelMask = {20 .. 23};
+
+	(* Interrupt Status, Status Enable, Signal Enable bits *)
+	Interrupt_Normal_CommandComplete = 0;
+	Interrupt_Normal_TransferComplete = 1;
+	Interrupt_Normal_BlockGapEvent = 2;
+	Interrupt_Normal_DmaInterrupt = 3;
+	Interrupt_Normal_BufferWriteReady = 4;
+	Interrupt_Normal_BufferReadReady = 5;
+	Interrupt_Normal_CardInsertion = 6;
+	Interrupt_Normal_CardRemoval = 7;
+	Interrupt_Normal_CardInterrupt = 8;
+	Interrupt_Normal_IntA = 9;
+	Interrupt_Normal_IntB = 10;
+	Interrupt_Normal_IntC = 11;
+	Interrupt_Normal_RetuningEvent = 12;
+	Interrupt_Normal_ErrorInterrupt = 15;
+	Interrupt_Error_CommandTimeout = 16;
+	Interrupt_Error_CommandCrc = 17;
+	Interrupt_Error_CommandEndBit = 18;
+	Interrupt_Error_CommandIndex = 19;
+	Interrupt_Error_DataTimeout = 20;
+	Interrupt_Error_DataCrc = 21;
+	Interrupt_Error_DataEndBit = 22;
+	Interrupt_Error_CurrentLimit = 23;
+	Interrupt_Error_AutoCmd12 = 24;
+	Interrupt_Error_Adma = 25;
+	Interrupt_Error_Tuning = 26;
+	Interrupt_Normal_All = {Interrupt_Normal_CommandComplete, Interrupt_Normal_TransferComplete, Interrupt_Normal_BlockGapEvent, Interrupt_Normal_DmaInterrupt,
+									Interrupt_Normal_BufferWriteReady, Interrupt_Normal_BufferReadReady, Interrupt_Normal_CardInsertion, Interrupt_Normal_CardRemoval,
+									Interrupt_Normal_CardInterrupt, Interrupt_Normal_IntA, Interrupt_Normal_IntB, Interrupt_Normal_IntC, Interrupt_Normal_RetuningEvent,
+									Interrupt_Normal_ErrorInterrupt};
+	Interrupt_Error_All = {Interrupt_Error_CommandTimeout, Interrupt_Error_CommandCrc, Interrupt_Error_CommandEndBit,
+									Interrupt_Error_CommandIndex, Interrupt_Error_DataTimeout, Interrupt_Error_DataCrc, Interrupt_Error_DataEndBit, Interrupt_Error_CurrentLimit,
+									Interrupt_Error_AutoCmd12, Interrupt_Error_Adma, Interrupt_Error_Tuning};
+	Interrupt_All = Interrupt_Normal_All + Interrupt_Error_All;
+
+	(* Transfer Mode Register bits *)
+	TransferMode_DmaEnable = 0;
+	TransferMode_BlockCountEnable = 1;
+	TransferMode_AutoCmdOfs = 2;
+	TransferMode_AutoCmdMask = {2 .. 3};
+	TransferMode_DataTxDirection = 4;
+	TransferMode_MultipleBlocks = 5;
+	TransferMode_AutoCmd_None = {};
+	TransferMode_AutoCmd_Cmd12 = {2};
+	TransferMode_AutoCmd_Cmd23 = {3};
+
+	(* Command Register bits *)
+	Command_ResponseTypeOffset = 0;
+	Command_CrcCheckEnable = 3;
+	Command_IndexCheckEnable = 4;
+	Command_DataPresent = 5;
+	Command_CommandTypeOffset = 6;
+	Command_CommandTypeMask = {6 .. 7};
+	Command_CommandIndexOffset = 8;
+	Command_CommandIndexMask = {8 .. 13};
+
+	Command_ResponseType_None = 0;
+	Command_ResponseType_136b = 1;
+	Command_ResponseType_48b = 2;
+	Command_ResponseType_48bBusy = 3;
+
+	(* Capabilities Register *)
+	(* Low Word *)
+	Capabilities_TimeoutClockFrequencyOfs = 0;
+	Capabilities_TimeoutClockFrequencyMask = {0 .. 5};
+	Capabilities_TimeoutClockUnit = 7;
+	Capabilities_BaseClockFreqSdOfs = 8;
+	Capabilities_BaseClockFreqSdMask = {8 .. 15};
+	Capabilities_MaxBlockLenOfs = 16;
+	Capabilities_MaxBlockLenMask = {16 .. 17};
+	Capabilities_8BitEmbedded = 18;
+	Capabilities_ADMA2 = 19;
+	Capabilities_HighSpeed = 21;
+	Capabilities_SDMA = 22;
+	Capabilities_SuspendResume = 23;
+	Capabilities_Voltage33 = 24;
+	Capabilities_Voltage30 = 25;
+	Capabilities_Voltage18 = 26;
+	Capabilities_64BitBus = 28;
+	Capabilities_AsyncInterrupt = 29;
+	Capabilities_SlotTypeOfs = 30;
+	Capabilities_SlotTypeMask = {30 .. 31};
+	(* High Word *)
+	Capabilities_SDR50 = 0;
+	Capabilities_SDR104 = 1;
+	Capabilities_DDR50 = 2;
+	Capabilities_DriverTypeA = 4;
+	Capabilities_DriverTypeC = 5;
+	Capabilities_DriverTypeD = 6;
+	Capabilities_TimerCountRetuningOfs = 8;
+	Capabilities_TimerCountRetuningMask = {8 .. 11};
+	Capabilities_TuningSDR50 = 13;
+	Capabilities_RetuningModesOfs = 14;
+	Capabilities_RetuningModesMask = {14 .. 15};
+	Capabilities_ClockMultiplierOfs = 16;
+	Capabilities_ClockMultiplierMask = {16 .. 23};
+	(* Patterns *)
+	Capabilities_SlotType_Removable = {};
+	Capabilities_SlotType_Embedded = {30};
+	Capabilities_SlotType_SharedBus = {31};
+
+	(* Host Control 1 register values *)
+	HostControl1_LedControl = 0;
+	HostControl1_DataTransferWidth = 1;
+	HostControl1_HighSpeedEnable = 2;
+	HostControl1_DmaSelectOfs = 3;
+	HostControl1_DmaSelectMask = {3 .. 4};
+	HostControl1_ExtendedDataTxWidth = 5;
+	HostControl1_CardDetectTestLevel = 6;
+	HostControl1_CardDetectSignalSelection = 7;
+	HostControl1_DmaSelect_Sdma = {};
+	HostControl1_DmaSelect_32Adma = {4};
+
+	(* SoftwareReset register values *)
+	SoftwareResetAll = 1;
+	SoftwareResetCmd = 2;
+	SoftwareResetDat = 4;
+
+	(* Clock Control register values *)
+	ClockControl_InternalClockEnable = 0;
+	ClockControl_InternalClockState = 1;
+	ClockControl_SdClockEnable = 2;
+	ClockControl_ClockGeneratorSelect = 5;
+	ClockControl_SdClockFreqUpperOfs = 6;
+	ClockControl_SdClockFreqUpperMask = {6, 7};
+	ClockControl_SdClockFreqOfs = 8;
+	ClockControl_SdClockFreqMask = {8 .. 15};
+
+	(* Power Control register values *)
+	PowerControl_SDBusPower* = 0;
+	PowerControl_SDBusVoltageOfs = 1;
+	PowerControl_SDBusVoltageMask = {1 .. 3};
+	PowerControl_SDBusVoltage_18 = {1, 3};
+	PowerControl_SDBusVoltage_30 = {2, 3};
+	PowerControl_SDBusVoltage_33 = {1, 2, 3};
+
+	(* Host Controller Version *)
+	HostControllerVersion_SpecificationMask = {0 .. 7};
+	HostControllerVersion_VendorOfs = 8;
+	HostControllerVersion_VendorMask = {8 .. 15};
+
+
+	(* SD Status fields *)
+	SdStatus_FuleSupport = 312;
+	SdStatus_DiscardSupport = 313;
+	SdStatus_PerformanceEnhanceOfs = 335;
+	SdStatus_PerformanceEnhanceWidth = 8;
+	SdStatus_AppPerfClassOfs = 336;
+	SdStatus_AppPerfClassWidth = 4;
+	SdStatus_SusAddrOfs = 346;
+	SdStatus_SusAddrWidth = 22;
+	SdStatus_VscAuSizeOfs = 368;
+	SdStatus_VscAuSizeWidth = 10;
+	SdStatus_VideoSpeedClassOfs = 384;
+	SdStatus_VideoSpeedClassWidth = 8;
+	SdStatus_UhsAuSizeOfs =  392;
+	SdStatus_UhsAuSizeWidth = 4;
+	SdStatus_UhsSpeedGradeOfs = 396;
+	SdStatus_UhsSpeedGradeWidth = 4;
+	SdStatus_EraseOffsetOfs = 400;
+	SdStatus_EraseOffsetWidth = 2;
+	SdStatus_EraseTimeoutOfs = 402;
+	SdStatus_EraseTimeoutWidth = 6;
+	SdStatus_EraseSizeOfs = 408;
+	SdStatus_EraseSizeWidth = 16;
+	SdStatus_AuSizeOfs = 428;
+	SdStatus_AuSizeWidth = 4;
+	SdStatus_PerformanceMoveOfs = 432;
+	SdStatus_PerformanceMoveWidth = 8;
+	SdStatus_SpeedClassOfs = 440;
+	SdStatus_SpeedClassWidth = 8; 
+	SdStatus_SizeOfProtectedAreaOfs = 448;
+	SdStatus_SizeOfProtectedAreaWidth = 32;
+	SdStatus_SdCardTypeOfs = 480;
+	SdStatus_SdCardTypeWidth = 16;
+	SdStatus_SecuredMode = 509;
+	SdStatus_DatBusWidthOfs = 510;
+	SdStatus_DatBusWidthWidth = 2;
+
+	(* Card Status register -- R1 *)
+	CardStatus_AkeSpecError = 3;
+	CardStatus_AppCmd = 5;
+	CardStatus_ReadyForData = 8;
+	CardStatus_CurrentStateOffset = 9;
+	CardStatus_CurrentStateMask = {9 .. 12};
+	CardStatus_EraseReset = 13;
+	CardStatus_CardEccDisable = 14;
+	CardStatus_WpEraseSkip = 15;
+	CardStatus_CsdOverwrite = 16;
+	CardStatus_Error = 19;
+	CardStatus_CcError = 20;
+	CardStatus_CardEccFailed = 21;
+	CardStatus_IllegalCommand = 22;
+	CardStatus_ComCrcError = 23;
+	CardStatus_LockUnlockFailed = 24;
+	CardStatus_CardIsLocked = 25;
+	CardStatus_WpViolation = 26;
+	CardStatus_EraseParam = 27;
+	CardStatus_EraseSeqError = 28;
+	CardStatus_BlockLenError = 29;
+	CardStatus_AddressError = 30;
+	CardStatus_OutOfRange = 31;
+
+	(* OCR Registers *)
+	CardOcr_Vdd27_28 = 15;
+	CardOcr_Vdd28_29 = 16;
+	CardOcr_Vdd29_30 = 17;
+	CardOcr_Vdd30_31 = 18;
+	CardOcr_Vdd31_32 = 19;
+	CardOcr_Vdd32_33 = 20;
+	CardOcr_Vdd33_34 = 21;
+	CardOcr_Vdd34_35 = 22;
+	CardOcr_Vdd35_36 = 23;
+	CardOcr_S18A = 24;
+	CardOcr_UHS2CardStatus = 29;
+	CardOcr_CardCapacityStatus = 30;
+	CardOcr_PowerUpStatus = 31;
+
+	(* CID Register *)
+	CardCid_ManufacturerIdOfs = 112;
+	CardCid_ManufacturerIdWidth = 8;
+	CardCid_OEM_ApplicationIdOfs = 96;
+	CardCid_OEM_ApplicationIdWidth = 16;
+	CardCid_ProductNameOfs = 56;
+	CardCid_ProductNameWidth = 40;
+	CardCid_ProductRevisionOfs = 48;
+	CardCid_ProductRevisionWidth = 8;
+	CardCid_ProductSerialNbOfs = 16;
+	CardCid_ProductSerialNbWidth = 32;
+	CardCid_ProductManufacturingDateOfs = 0;
+	CardCid_ProductManufacturingDateWidth = 12;
+
+	(* CSD Register. This excludes the CRC7 of the specifications. *)
+	CardCsd_FileFormatOfs = 2;
+	CardCsd_FileFormatWidth = 2;
+	CardCsd_TmpWriteProtect = 4;
+	CardCsd_PermWriteProtect = 5;
+	CardCsd_Copy = 6;
+	CardCsd_FileFormatGrp = 7;
+	CardCsd_WriteBlPartial = 13;
+	CardCsd_WriteBlLenOfs = 14;
+	CardCsd_WriteBlLenWidth = 4;
+	CardCsd_R2wFactorOfs = 18;
+	CardCsd_R2wFactorWidth = 3;
+	CardCsd_WpGrpEnable = 23;
+	CardCsd_WpGrpSizeOfs = 24;
+	CardCsd_WpGrpSizeWidth = 7;
+	CardCsd_SectorSizeOfs = 31;
+	CardCsd_SectorSizeWidth = 7;
+	CardCsd_EraseBlkEn = 38;
+	CardCsd_CSizeMultOfs1 = 39; (** V1 *)
+	CardCsd_CSizeMultWidth1 = 3; (** V1 *)
+	CardCsd_VddWCurrMaxOfs1 = 42; (** V1 *)
+	CardCsd_VddWCurrMaxWidth1 = 3; (** V1 *)
+	CardCsd_VddWCurrMinOfs1 = 45; (** V1 *)
+	CardCsd_VddWCurrMinWidth1 = 3; (** V1 *)
+	CardCsd_VddRCurrMaxOfs1 = 48; (** V1 *)
+	CardCsd_VddRCurrMaxWidth1 = 3; (** V1 *)
+	CardCsd_VddRCurrMinOfs1 = 51; (** V1 *)
+	CardCsd_VdddRCurrMaxWidth1 = 3; (** V1 *)
+	CardCsd_CSizeOfs1 = 54; (** V1 *)
+	CardCsd_CSizeWidth1 = 12; (** V1 *)
+	CardCsd_CSizeOfs2 = 40; (** V2 *)
+	CardCsd_CSizeWidth2 = 22; (** V2 *)
+	CardCsd_DsrImp = 68;
+	CardCsd_ReadBlkMisalign = 69;
+	CardCsd_WriteBlkMisalign = 70;
+	CardCsd_ReadBlPartial = 71;
+	CardCsd_ReadBlLenOfs = 72;
+	CardCsd_ReadBlLenWidth = 4;
+	CardCsd_CccOfs = 76;
+	CardCsd_CccWidth = 12;
+	CardCsd_TranSpeedOfs = 88;
+	CardCsd_TranSpeedWidth = 8;
+	CardCsd_NsacOfs = 96;
+	CardCsd_NsacWidth = 8;
+	CardCsd_TaacOfs = 104;
+	CardCsd_TaacWidth = 8;
+	CardCsd_CsdStructureOfs = 118;
+	CardCsd_CsdStructureWidth = 2;
+
+	(* SCR Register *)
+	CardScr_CommandSupportOfs = 32;
+	CardScr_CommandSupportWidth = 4;
+	CardScr_SpecVXOfs = 38;
+	CardScr_SpecVXWidth = 4;
+	CardScr_SpecV4 = 42;
+	CardScr_ExtendedSecurityOfs = 43;
+	CardScr_ExtendedSecurityWidth = 4;
+	CardScr_SpecV3 = 47;
+	CardScr_BusWidthsOfs = 48;
+	CardScr_BusWidthsWidth = 4;
+	CardScr_SecurityOfs = 52;
+	CardScr_SecurityWidth = 3;
+	CardScr_DataStateAfterErase = 55;
+	CardScr_SpecVersionOfs = 56;
+	CardScr_SpecVersionWidth = 4;
+	CardScr_StructureOfs = 60;
+	CardScr_StructureWidth = 4;
+
+	(* SCR register fields values *)
+	CardScr_SpecVX_v5 = 1;
+	CardScr_SpecVX_v6 = 2;
+
+	(* Card categories, used in SD status card type *)
+	CategoryRW * = 0;
+	CategoryRO * = 1;
+	CategoryOTP * = 2;
+
+	(* Performance enhancing features, used in perfEnhance in SD status *)
+	PerformanceCardMaintenance * = 0; (** Card supports card-initiated maintenance *)
+	PerformanceHostMaintenance * = 1; (** Card supports host-initiated maintenance *)
+	PerformanceCache * = 2; (** Card supports internal caching *)
+	PerformanceQueue * =3; (** Card supports command queue *)
+
+	(* Transfer options *)
+	TxDma = TRUE; (** Use DMA for transfers on all hosts that support it *)
+	TxBufferSize = 4096; (** Buffer size used for DMA transfers *)
+	TxBufferAlign = 32; (** Alignment requirement on DMA buffer: here cache line size of ARM *)
+
+	(* ADMA2 flags *) (*! Do not change values *)
+	Adma2Valid = 0; (** Valid entry *)
+	Adma2End = 1; (** Last entry *)
+	Adma2Int = 2; (** Entry generates interrupt on completion *)
+	Adma2Nop = {}; (** No-operation, just continue to next *)
+	Adma2Trans = {5}; (** Transfer descriptor *)
+	Adma2Link = {4, 5}; (** Link descriptor *)
+	Adma2ActMask = {4, 5}; (** Mask for Nop, Trans and Link *)
+
+	(* Timeout values *)
+	TimeoutCardInit = 100; (** Timeout used in card initialization, ms *)
+	TimeoutReadFactor = 100; (** Timeout factor on typical read time (according to CSD) *)
+	TimeoutReadFix = 100; (** Maximal read timeout in ms. To be used unconditionally with SDHC *)
+	TimeoutWriteFactor = 100; (** Timeout factor on typical write time (according to CSD) *)
+	TimeoutWriteFix = 250; (** Maximal write timeout in ms. To be used unconditionally with SDHC *)
+	TimeoutErase = 250; (** Typical timeout per erased block, ms *)
+
+	DefaultTimeout = 1000; (* Default timeout for blocking, in ms *)
+
+	(* Multi-threading *)
+	Synchronize * = FALSE (*TRUE*); (** Do we need to take care of concurrency? *)
+
+	(* Tracing options for debugging *)
+	EnableTraceCmd* = FALSE;
+	EnableTrace* = FALSE;
+
+TYPE
+	(** Command execution procedure *)
+	CommandProcedure * = PROCEDURE (VAR command: Command; VAR result: LONGINT): BOOLEAN;
+	(** Data command execution procedure *)
+	TransferProcedure * = PROCEDURE (VAR command: Command; VAR data: ARRAY OF SYSTEM.BYTE; ofs, len: LONGINT; VAR result: LONGINT): BOOLEAN;
+	(** Procedure called to wait for interrupt. mask specifies which interrupts are expected, timeout is in ms. Returns FALSE if timeout occurred *)
+	Blocker * = PROCEDURE {DELEGATE} (hc: HostController; mask: SET; timeout: LONGINT): BOOLEAN;
+
+	(**
+		SD Host controller descriptor.
+	*)
+	HostController * = POINTER TO HostControllerDesc;
+	HostControllerDesc * = RECORD
+		state -, (** HC state *)
+		version -: LONGINT; (** Specifications version *)
+
+		execute -: CommandProcedure; (** Method to execute commands *)
+		transfer -: TransferProcedure; (** Method to execute data commands *)
+		acquire *, release *: PROCEDURE {DELEGATE}; (** Procedures used for locking *)
+
+		baseFrequency, (** Base hc clock frequency *)
+		frequency, (** Bus frequency *)
+		timeoutFrequency: HUGEINT; (** Timeout clock frequency *)
+		lastRca: LONGINT; (** Last RCA selected by the controller. *)
+
+		handle: EventHandler; (** Card event handler *)
+		handlerParam: ANY; (** Parameter of the eventHandler *)
+		block: Blocker; (** Procedure called to wait for interrupts *)
+
+		regs-: HcRegisters; (** Memory-mapped I/O registers *)
+		cards: Card; (** List of cards on this HC *)
+		next: HostController; (** Linked list of controllers for interrupt handling *)
+		desc{ALIGNED(32)}: ARRAY 32 OF HUGEINT; (** DMA descriptor *)
+	END;
+
+	(**
+		Command record.
+		This record type is used to describe a command and its result.
+		To execute a command, fill in the fields 'hc', 'command', 'argument', 'responseType'.
+		If the command uses the DAT line or is an application command, setup then necessary flags.
+		If the command is an application command, you also need to specify the RCA for CMD55.
+		After command execution, you can read the command response in the 'response' field. Use the
+		'GetR*' procedures to extract all information from this field in a convenient way.
+	*)
+	Command * = RECORD
+		hc *: HostController; (** Host controller on which the command is executed *)
+		rca *, (** Optional RCA parameter. Required only for ACMDs *)
+		command *, (** Command number *)
+		argument *, (** Command argument *)
+		responseType *, (** Response type *)
+		blockSize *, (** Block size for read, write or erase *)
+		dataTimeout *: LONGINT; (** Timeout value used for data line *)
+		flags *: SET; (** Command flags *)
+		response *: ARRAY 4 OF LONGINT; (** Response *)
+	END;
+
+	(** SWITCH_FUNC returned status *)
+	SwitchFuncStatus * = RECORD
+		current -: LONGINT; (** Current for specified config *)
+		functionGroups -: ARRAY 6 OF SET; (** Supported function in each group *)
+		functionStatus -: ARRAY 6 OF LONGINT; (** Function status *)
+	END;
+
+	(** Card event handler. 'card' is the card for which an event is reported (can be a new card object) and 'event' is one of 'On*' constants *)
+	EventHandler * = PROCEDURE {DELEGATE} (card: Card; event: LONGINT; param: ANY);
+
+	(** Host controller registers *)
+	HcRegisters * = POINTER {UNSAFE,UNTRACED} TO RECORD
+		SDMASystemAddress * {ALIGNED(1)}: LONGINT; (** offset = 0H *)
+		BlockSize * {ALIGNED(1)}, (** offset = 4H *)
+		BlockCount * {ALIGNED(1)}: INTEGER; (** offset = 6H *)
+		Argument1 * {ALIGNED(1)}: LONGINT; (** offset = 8H *)
+		TransferMode * {ALIGNED(1)}, (** offset = 0CH *)
+		Command * {ALIGNED(1)}: INTEGER; (** offset = 0EH *)
+		Response * {ALIGNED(1)}: ARRAY 4 OF LONGINT; (** offset = 10H *)
+		BufferData * {ALIGNED(1)}: LONGINT;
+		PresentState * {ALIGNED(1)}: SET;
+		HostControl1 * {ALIGNED(1)},
+		PowerControl * {ALIGNED(1)},
+		BlockGapControl * {ALIGNED(1)},
+		WakeupControl * {ALIGNED(1)}: SHORTINT;
+		ClockControl * {ALIGNED(1)}: INTEGER;
+		TimeoutControl * {ALIGNED(1)},
+		SoftwareReset * {ALIGNED(1)}: SHORTINT;
+		InterruptStatus * {ALIGNED(1)},
+		InterruptStatusEnable * {ALIGNED(1)},
+		InterruptSignalEnable * {ALIGNED(1)}: SET;
+		AutoCmdErrorStatus * {ALIGNED(1)},
+		HostControl2 * {ALIGNED(1)}: INTEGER;
+		Capabilities * {ALIGNED(1)}: ARRAY 2 OF SET;
+		MaximumCurrentCapabilities * {ALIGNED(1)}: HUGEINT;
+		ForceEventAutoCmdErrorStatus * {ALIGNED(1)},
+		ForceEventErrorInterruptStatus * {ALIGNED(1)}: INTEGER;
+		AdmaErrorStatus * {ALIGNED(1)}: SHORTINT;
+		padding0 * {ALIGNED(1)}: ARRAY 3 OF SHORTINT;
+		AdmaSystemAddress * {ALIGNED(1)}: HUGEINT;
+		PresetValues * {ALIGNED(1)}: ARRAY 8 OF INTEGER;
+		padding1 * {ALIGNED(1)}: ARRAY 28 OF LONGINT;
+		SharedBusControl * {ALIGNED(1)}: LONGINT;
+		padding2 * {ALIGNED(1)}: ARRAY 6 OF LONGINT;
+		SlotInterruptStatus * {ALIGNED(1)},
+		HostControllerVersion * {ALIGNED(1)}: INTEGER;
+	END;
+
+VAR
+	(** List of all host controllers *)
+	hcs: HostController;
+
+	(** Statistics *)
+	NbyteRead -,
+	NbyteWritten -,
+	Nread -,
+	Nwrite -: HUGEINT;
+	Tread -,
+	Twrite -: HUGEINT;
+	start, stop: HUGEINT;
+
+	(* ==================== Host Controller (Low-Level) Interface ==================== *)
+	(**
+		Create an host controller descriptor and initializes it with the given info.
+		'baseAddress' is the base address of the IO registers.
+		'extClockFreq' is an optional external clock frequency. It is used iff the host controller has no information about its clock frequency.
+		'handler' is the event handler and 'param' is user parameter.
+	*)
+	PROCEDURE InitHostController * (hc: HostController; baseAddress: ADDRESS)(*: HostController*);
+	VAR
+		val: LONGINT;
+	BEGIN
+		hc.regs := baseAddress;
+		IF ~Reset(hc, TRUE, TRUE) THEN (*RETURN NIL*) END;
+		hc.baseFrequency := LSH(SYSTEM.VAL(LONGINT, hc.regs.Capabilities[0] * Capabilities_BaseClockFreqSdMask), -Capabilities_BaseClockFreqSdOfs);
+		hc.timeoutFrequency := LSH(SYSTEM.VAL(LONGINT, hc.regs.Capabilities[0] * Capabilities_TimeoutClockFrequencyMask), -Capabilities_TimeoutClockFrequencyOfs) * 1000;
+		IF Capabilities_TimeoutClockUnit IN hc.regs.Capabilities[0] THEN hc.timeoutFrequency := hc.timeoutFrequency * 1000 END;
+		(*SetBusClock(hc, InitialClockFrequency);
+		SetTimeout(hc, 100);*)
+
+		(* Power select 3.3V bus voltage *)
+		hc.regs.PowerControl :=  SYSTEM.VAL(SHORTINT, PowerControl_SDBusVoltage_33 + {PowerControl_SDBusPower});
+
+		(* Enable All Interrupts *)
+		hc.regs.InterruptStatusEnable := Interrupt_All;
+		hc.regs.InterruptSignalEnable := {Interrupt_Normal_CardInsertion, Interrupt_Normal_CardRemoval};
+		hc.regs.BlockGapControl := 0;
+
+		val := LONGINT(hc.regs.HostControllerVersion);
+		hc.version := SYSTEM.VAL(LONGINT, SYSTEM.VAL(SET, val) * HostControllerVersion_SpecificationMask) + 1;
+
+		IF EnableTrace THEN
+			Log.String("[SD] New Host Controller v");
+			Log.Int(hc.version, 0);
+			Log.String(" at ");
+			Log.Address(baseAddress);
+			Log.Ln;
+			PrintCapabilities(hc);
+		END;
+
+		IF Synchronize THEN SdEnvironment.GetLock(hc.acquire, hc.release) END;
+
+		(* Select method according to DMA support *)
+		IF TxDma THEN
+			IF (Capabilities_ADMA2 IN hc.regs.Capabilities[0]) THEN
+				hc.transfer := ExecuteAdmaCommand;
+				hc.regs.HostControl1 := SYSTEM.VAL(SHORTINT, SYSTEM.VAL(SET, LONGINT(hc.regs.HostControl1)) + HostControl1_DmaSelect_32Adma);
+			ELSE
+				(*! ADMA1 and SDMA are not implemented yet *)
+				hc.transfer := ExecuteDataCommand
+			END
+		ELSE
+			hc.transfer := ExecuteDataCommand
+		END;
+		hc.execute := ExecuteCommand;
+		hc.block := SpinBlock; (* Use spin block by default *)
+
+		IF (hc.baseFrequency = 0) OR (hc.timeoutFrequency = 0) THEN
+			hc.state := HcConfiguring
+		ELSE
+			hc.state := HcOperational
+		END;
+
+		(* If there is a card already, initialize it *)
+		(*IF (PresentState_CardInserted IN hc.regs.PresentState) & (PresentState_CardStateStable IN hc.regs.PresentState) THEN
+			NEW(hc.cards);
+			IF InitCard(hc, hc.cards, result) & (hc.handle # NIL) THEN
+				hc.handle(hc.cards, OnInitialization, hc.handlerParam)
+			ELSIF hc.handle # NIL THEN
+				Log.String("[SD] Could not initialize inserted card: error ");
+				Log.Int(result, 0);
+				Log.Ln;
+			END
+		END;*)
+
+		hc.next := hcs;
+		hcs := hc;
+	END InitHostController;
+
+	(** Set external clock for a host controller. Host state must be HcConfiguring. bus is the SD bus clock frequency, timeout is the timeout clock frequency. *)
+	PROCEDURE SetExternalClock * (hc: HostController; bus, timeout: HUGEINT; VAR result: LONGINT): BOOLEAN;
+	BEGIN
+		IF hc.state # HcConfiguring THEN
+			result := ErrorInvalidParameters;
+			RETURN FALSE
+		END;
+		hc.baseFrequency := bus;
+		hc.timeoutFrequency := timeout;
+		hc.state := HcOperational;
+		RETURN TRUE
+	END SetExternalClock;
+
+	(** Set an event handler for a host controller *)
+	PROCEDURE SetEventHandler * (hc: HostController; handler: EventHandler; param: ANY);
+	VAR
+		result: LONGINT;
+	BEGIN
+		hc.handle := handler;
+		hc.handlerParam := param;
+
+		(* Handle events if necessary *)
+		IF (PresentState_CardInserted IN hc.regs.PresentState) & (PresentState_CardStateStable IN hc.regs.PresentState) THEN
+			NEW(hc.cards);
+			IF InitCard(hc, hc.cards, result) & (hc.handle # NIL) THEN
+				hc.handle(hc.cards, OnInitialization, hc.handlerParam)
+			ELSIF hc.handle # NIL THEN
+				Log.String("[SD] Could not initialize inserted card: error ");
+				Log.Int(result, 0);
+				Log.Ln;
+			END
+		END;
+	END SetEventHandler;
+
+	(** Change the wait for interrupt method *)
+	PROCEDURE SetBlocker * (hc: HostController; blocker: Blocker);
+	BEGIN
+		hc.block := blocker
+	END SetBlocker;
+
+	(** Turns the busy LED on or off *)
+	PROCEDURE SetLedState * (hc: HostController; on: BOOLEAN);
+	VAR
+		reg: SET;
+	BEGIN
+		reg := SYSTEM.VAL(SET, hc.regs.HostControl1);
+		IF on THEN
+			INCL(reg, HostControl1_LedControl)
+		ELSE
+			EXCL(reg, HostControl1_LedControl)
+		END;
+		hc.regs.HostControl1 := SYSTEM.VAL(SHORTINT, reg)
+	END SetLedState;
+
+	(** Get the state of the busy LED *)
+	PROCEDURE GetLedState * (hc: HostController): BOOLEAN;
+	BEGIN
+		RETURN HostControl1_LedControl IN SYSTEM.VAL(SET, hc.regs.HostControl1)
+	END GetLedState;
+
+	(**
+		Execute the command 'command', without data transfer. If you need data transfer, use 'ExecuteDataCommand'.
+		Performs all necessary steps for executing a command:
+			o Runs CMD55 if command is an application command
+			o Execute the command
+			o Wait for response
+	*)
+	PROCEDURE ExecuteCommand (VAR command: Command; VAR result: LONGINT): BOOLEAN;
+	VAR
+		status, r1: SET;
+	BEGIN
+		IF Synchronize THEN command.hc.acquire END;
+		result := ErrorNone;
+		(* Check Parameters *)
+		IF {FlagData, FlagRead, FlagAutoCmd12, FlagAutoCmd23, FlagMultipleBlockTx, FlagCountBlocks} * command.flags # {} THEN
+			result := ErrorInvalidParameters;
+			IF Synchronize THEN command.hc.release END;
+			RETURN FALSE
+		END;
+
+		IF (FlagApplicationCmd IN command.flags) THEN
+			IF ~StartCommand(command.hc, CMD_APP_CMD, LSH(command.rca, 16), ResponseR1, FlagRead IN command.flags, FALSE, FALSE, FALSE, status, result) THEN
+				IF Synchronize THEN command.hc.release END;
+				RETURN FALSE
+			END;
+			r1 := GetR1(command);
+			IF EnableTrace THEN PrintCardStatus(r1) END;
+			IF ~(FlagIgnoreIllegalCmd IN command.flags) & (CardStatus_Error IN r1) THEN
+				result := ErrorCard;
+				IF Synchronize THEN command.hc.release END;
+				RETURN FALSE
+			END
+		END;
+
+		IF ~StartCommand(
+				command.hc, command.command, command.argument, command.responseType, FlagRead IN command.flags, FALSE,
+				FALSE (*command.responseType = ResponseR1b*), FlagAbort IN command.flags, status, result) THEN
+			IF Synchronize THEN command.hc.release END;
+			RETURN FALSE
+		END;
+		GetResponse(command.hc, command.responseType, command.response);
+		IF command.command = CMD_SELECT_DESELECT_CARD THEN
+			command.hc.lastRca := LSH(command.argument, -16)
+		ELSIF command.command = CMD_GO_IDLE_STATE THEN
+			command.hc.lastRca := 0
+		END;
+		IF Synchronize THEN command.hc.release END;
+		RETURN TRUE
+	END ExecuteCommand;
+
+	(**
+		Execute command with data transfer using CPU.
+		Data is read from/written to [data[ofs], data[ofs + len]).
+	*)
+	PROCEDURE ExecuteDataCommand (VAR command: Command; VAR data: ARRAY OF SYSTEM.BYTE; ofs, len: LONGINT; VAR result: LONGINT): BOOLEAN;
+	TYPE
+		DataBytes = ARRAY 4 OF CHAR;
+	VAR
+		tmp: DataBytes;
+		i, stepLen: LONGINT;
+		hc: HostController;
+		r1, status, mask: SET;
+	BEGIN
+		ASSERT(ofs + len <= LEN(data), 7);
+		result := ErrorNone;
+		IF ~(FlagData IN command.flags) THEN
+			result := ErrorInvalidParameters;
+			RETURN FALSE
+		END;
+		hc := command.hc;
+
+		IF Synchronize THEN hc.acquire END;
+		(* Set timeout *)
+		SetTimeout(hc, command.dataTimeout);
+		(*IF (*~Reset(hc, TRUE, FALSE) OR*) ~Reset(hc, FALSE, TRUE) THEN
+			result := ErrorCard;
+			IF Synchronize THEN hc.release END;
+			RETURN FALSE
+		END;*)
+
+		IF (FlagApplicationCmd IN command.flags) THEN
+			IF ~StartCommand(hc, CMD_APP_CMD, LSH(command.rca, 16), ResponseR1, FlagRead IN command.flags, FALSE, FALSE, FALSE, status, result) THEN
+				IF Synchronize THEN hc.release END;
+				RETURN FALSE
+			END;
+			r1 := SYSTEM.VAL(SET, command.response[0]);
+			IF CardStatus_Error IN r1 THEN
+				IF Synchronize THEN hc.release END;
+				RETURN FALSE
+			END;
+			IF EnableTrace THEN
+				Log.String("[SD] CMD55 Status:");
+				Log.Ln;
+				PrintCardStatus(r1)
+			END
+		END;
+
+		(* 1 *)
+		IF ~(FlagApplicationCmd IN command.flags) &
+				((command.command = CMD_READ_SINGLE_BLOCK) OR (command.command = CMD_READ_MULTIPLE_BLOCK) OR
+				(command.command = CMD_WRITE_BLOCK) OR (command.command = CMD_WRITE_MULTIPLE_BLOCK) OR
+				(command.command = 53)(* SDIO Command *)) THEN
+			IF len <= BlockSize THEN
+				hc.regs.BlockSize := INTEGER(len);
+				hc.regs.BlockCount := 1
+			ELSE
+				hc.regs.BlockSize := BlockSize;
+				hc.regs.BlockCount := INTEGER(len DIV BlockSize)
+			END;
+
+			IF EnableTrace THEN
+				Log.String("[SD] ");
+				IF FlagRead IN command.flags THEN Log.String("Read")
+				ELSE Log.String("Write") END;
+				Log.String(" parameters:"); Log.Ln;
+				Log.String("[SD] 	Block Size = "); Log.Int(hc.regs.BlockSize, 0); Log.Ln;
+				Log.String("[SD] 	Block Count = "); Log.Int(hc.regs.BlockCount, 0); Log.Ln;
+				Log.String("[SD] 	CMD"); Log.Int(command.command, 0); Log.Ln;
+				Log.String("[SD] 	Argument = "); Log.Address(command.argument); Log.Ln
+			END
+		END;
+
+		(* 3 - 8 *)
+		IF FlagRead IN command.flags THEN
+			(*REPEAT UNTIL (Interrupt_Normal_BufferReadReady IN hc.regs.InterruptStatus) OR (Interrupt_Normal_ErrorInterrupt IN hc.regs.InterruptStatus)*)
+			mask := {Interrupt_Normal_BufferReadReady, Interrupt_Normal_ErrorInterrupt}
+		ELSE
+			(*REPEAT UNTIL (Interrupt_Normal_BufferWriteReady IN hc.regs.InterruptStatus) OR (Interrupt_Normal_ErrorInterrupt IN hc.regs.InterruptStatus)*)
+			mask := {Interrupt_Normal_BufferWriteReady, Interrupt_Normal_ErrorInterrupt}
+		END;
+
+		IF ~StartCommand(hc, command.command, command.argument, ResponseR1, FlagRead IN command.flags, FALSE, TRUE, FALSE, status, result) THEN RETURN FALSE END;
+		r1 := SYSTEM.VAL(SET, command.response[0]);
+		IF CardStatus_Error IN r1 THEN
+			IF Synchronize THEN hc.release END;
+			RETURN FALSE
+		END;
+		IF EnableTrace THEN PrintCardStatus(r1) END;
+		WHILE len > 0 DO
+			(* 14 *)
+			IF ~hc.block(hc, mask, command.dataTimeout * 1000) THEN
+				Log.String("[SD] Error: interrupt timeout");
+				Log.Ln;
+				RETURN FALSE
+			END;
+			status := hc.regs.InterruptStatus;
+
+			IF Interrupt_Normal_ErrorInterrupt IN hc.regs.InterruptStatus THEN
+				IF ErrorRecovery(hc, result, status) THEN END;
+				IF Interrupt_Error_DataTimeout IN status THEN
+					result := ErrorDatTimeout
+				ELSIF Interrupt_Error_DataCrc IN status THEN
+					result := ErrorDatCrc
+				ELSIF Interrupt_Error_DataEndBit IN status THEN
+					result := ErrorCard
+				END;
+				IF Synchronize THEN hc.release END;
+				RETURN FALSE
+			END;
+			(* 15 *)
+			(*INCL(hc.regs.InterruptStatus, Interrupt_Normal_BufferReadReady);*)
+			hc.regs.InterruptStatus := {Interrupt_Normal_BufferReadReady};
+
+			(* 16 *)
+			stepLen := MIN(BlockSize, len);
+			IF FlagRead IN command.flags THEN
+				FOR i := 0 TO stepLen - 1 BY 4 DO
+					SYSTEM.PUT32(ADDRESSOF(data[ofs + i]), hc.regs.BufferData);
+					(*SYSTEM.VAL(LONGINT, tmp) := hc.regs.BufferData;
+					SYSTEM.VAL(DataBytes, data[ofs + i]) := tmp*)
+				END
+			ELSE
+				FOR i := 0 TO stepLen - 1 BY 4 DO
+					tmp := SYSTEM.VAL(DataBytes, data[ofs + i]);
+					hc.regs.BufferData := SYSTEM.VAL(LONGINT, tmp);
+				END
+			END;
+			(* 17 *)
+			INC(ofs, stepLen);
+			DEC(len, stepLen)
+		END;
+
+		(* 18 -> Not infinite block *)
+		(*REPEAT UNTIL Interrupt_Normal_TransferComplete IN hc.regs.InterruptStatus;*)
+		IF ~hc.block(hc, {Interrupt_Normal_TransferComplete}, command.dataTimeout * 1000) THEN
+			Log.String("[SD] Error: timeout interrupt");
+			Log.Ln;
+			RETURN FALSE
+		END;
+		(* 19 *)
+		(*INCL(hc.regs.InterruptStatus, Interrupt_Normal_TransferComplete);*)
+		hc.regs.InterruptStatus := {Interrupt_Normal_TransferComplete};
+
+		(*DEC(hc.regs.ClockControl, LSH(1, ClockControl_SdClockEnable));
+		hc.regs.SoftwareReset := SYSTEM.VAL(SHORTINT, {SoftwareResetCmd, SoftwareResetDat});
+		REPEAT UNTIL hc.regs.SoftwareReset = 0;
+		INC(hc.regs.ClockControl, LSH(1, ClockControl_SdClockEnable));*)
+
+		IF Synchronize THEN hc.release END;
+		RETURN TRUE (*Reset(hc, TRUE, FALSE) & Reset(hc, FALSE, TRUE)*)
+	END ExecuteDataCommand;
+
+	(**
+		Execute Command with data transfers using ADMA.
+		command: ADMA command
+		data, ofs, len: Buffer
+		result: error code
+	*)
+	PROCEDURE ExecuteAdmaCommand (VAR command: Command; VAR data: ARRAY OF SYSTEM.BYTE; ofs, len: LONGINT; VAR result: LONGINT): BOOLEAN;
+	VAR
+		tt: SdEnvironment.Time;
+		hc: HostController;
+		r1, status, flags: SET;
+		address, a: ADDRESS;
+		blocks, blockSize, desc, txlen, l, tx: LONGINT;
+		padd: BOOLEAN; (* Padd transfer to next block length? *)
+
+		PROCEDURE WriteAdma2Desc (address: ADDRESS; len: LONGINT; flags: SET): HUGEINT;
+		BEGIN
+			IF EnableTrace THEN
+				Log.String("[SD] ADMA2 Entry; address = "); Log.Hex(address, -8); Log.String(", size = "); Log.Int(len, 0);
+				Log.String(", flags = ");
+				IF flags * Adma2ActMask = Adma2Nop THEN Log.String("NOP ")
+				ELSIF flags * Adma2ActMask = Adma2Trans THEN Log.String("TRANS ")
+				ELSIF flags * Adma2ActMask = Adma2Link THEN Log.String("LINK ")
+				END;
+				IF Adma2Valid IN flags THEN Log.String("VALID ") END;
+				IF Adma2End IN flags THEN Log.String("END ") END;
+				IF Adma2Int IN flags THEN Log.String("INT ") END;
+				Log.Ln
+			END;
+			ASSERT(address MOD 4 = 0);
+			ASSERT((len > 0) OR (flags * Adma2ActMask # Adma2Trans));
+			RETURN LSH(HUGEINT(address), 32) + LSH(len MOD 65536, 16) + SYSTEM.VAL(LONGINT, flags)
+		END WriteAdma2Desc;
+
+	BEGIN
+		result := ErrorNone;
+		hc := command.hc;
+		IF Synchronize THEN hc.acquire END;
+
+		(* Setup descriptors *)
+		address := ADDRESSOF(data[ofs]);
+		(*IF ~(FlagRead IN command.flags) THEN*) SdEnvironment.FlushDCacheRange(address, len); (*END;*)
+		txlen := len;
+		flags := Adma2Trans + {Adma2Valid};
+		padd := FALSE; (*(len > BlockSize) & (len MOD BlockSize # 0);*)
+		l := len;
+		a := address;
+		WHILE l > 0 DO
+			IF ~padd & (l <= 65536) THEN flags := flags + {Adma2End, Adma2Int} END;
+			tx := MIN(l, 65536);
+			hc.desc[desc] := WriteAdma2Desc(a, tx, flags);
+			DEC(l, tx);
+			INC(a, tx);
+			INC(desc);
+		END;
+		(*hc.desc[desc] := WriteAdma2Desc(0, 0, {Adma2Valid, Adma2End, Adma2Int} + Adma2Nop); INC(desc);*)
+		SdEnvironment.FlushDCacheRange(ADDRESSOF(hc.desc[0]), (desc + 1) * 8);
+
+		(* Reset command and data lines *)
+		IF (*~Reset(hc, TRUE, FALSE) OR*) ~Reset(hc, FALSE, TRUE) THEN
+			result := ErrorCard;
+			IF Synchronize THEN hc.release END;
+			RETURN FALSE
+		END;
+
+		(* 1 *)
+		hc.regs.AdmaSystemAddress := ADDRESSOF(hc.desc[0]);
+
+		IF (FlagApplicationCmd IN command.flags) THEN
+			IF ~StartCommand(command.hc, CMD_APP_CMD, LSH(command.rca, 16), ResponseR1, FlagRead IN command.flags, FALSE, FALSE, FALSE, status, result) THEN
+				IF Synchronize THEN hc.release END;
+				RETURN FALSE
+			END;
+			r1 := SYSTEM.VAL(SET, command.response[0]);
+			IF EnableTrace THEN
+				Log.String("[SD] CMD55 Status:");
+				Log.Ln;
+				PrintCardStatus(r1)
+			END;
+			IF CardStatus_Error IN r1 THEN
+				result := ErrorCard;
+				IF Synchronize THEN hc.release END;
+				RETURN FALSE
+			END
+		END;
+
+		(* 2-3 *)
+		IF command.blockSize = 0 THEN
+			blockSize := BlockSize
+		ELSE
+			blockSize := command.blockSize
+		END;
+		IF FlagData IN command.flags THEN
+			IF txlen <= blockSize THEN
+				hc.regs.BlockSize := INTEGER(txlen);
+				hc.regs.BlockCount := 1;
+				blocks := 1
+			ELSE
+				hc.regs.BlockSize := INTEGER(blockSize);
+				blocks := txlen DIV blockSize;
+				hc.regs.BlockCount := INTEGER(blocks)
+			END;
+
+			(* Set data timeout *)
+			ASSERT(command.dataTimeout > 0);
+			SetTimeout(hc, command.dataTimeout);
+
+			IF EnableTrace THEN
+				Log.String("[SD] ");
+				IF FlagRead IN command.flags THEN Log.String("Read")
+				ELSE Log.String("Write") END;
+				Log.String(" parameters:"); Log.Ln;
+				Log.String("[SD] 	Block Size = "); Log.Int(hc.regs.BlockSize, 0); Log.Ln;
+				Log.String("[SD] 	Block Count = "); Log.Int(hc.regs.BlockCount, 0); Log.Ln;
+				Log.String("[SD] 	CMD"); Log.Int(command.command, 0); Log.Ln;
+				Log.String("[SD] 	Argument = "); Log.Address(command.argument); Log.Ln
+			END
+		END;
+
+		status := hc.regs.InterruptStatus;
+		ASSERT({Interrupt_Normal_TransferComplete, Interrupt_Normal_ErrorInterrupt} * status = {});
+		(*!start := SdEnvironment.GetTimeCounter();*)
+		IF ~StartCommand(command.hc, command.command, command.argument, command.responseType, FlagRead IN command.flags, TRUE, TRUE, FALSE, status, result) THEN
+			IF Synchronize THEN hc.release END;
+			RETURN FALSE
+		END;
+		r1 := SYSTEM.VAL(SET, command.response[0]);
+		IF CardStatus_Error IN r1 THEN
+			result := ErrorCard;
+			IF Synchronize THEN hc.release END;
+			RETURN FALSE
+		END;
+		IF EnableTrace THEN PrintCardStatus(r1) END;
+
+		(*t := SdEnvironment.GetTimeCounter();
+		tt := t + SdEnvironment.FromMilli(1000);*)
+		(*WHILE ({Interrupt_Normal_TransferComplete, Interrupt_Normal_ErrorInterrupt, Interrupt_Error_Adma} * status = {}) (*& (SdEnvironment.GetTimeCounter() <= tt)*) DO
+			status := hc.regs.InterruptStatus
+		END;*)
+		(*IF SdEnvironment.GetTimeCounter() > tt(*{Interrupt_Normal_TransferComplete, Interrupt_Normal_ErrorInterrupt} * status = {}*) THEN*)
+		IF ~hc.block(hc, {Interrupt_Normal_TransferComplete, Interrupt_Normal_ErrorInterrupt}, command.dataTimeout * 1000) THEN
+			Log.String("[SD] Error: timeout has expired!"); Log.Ln;
+			PrintHcRegisters(hc.regs);
+			IF Synchronize THEN hc.release END;
+			HALT(512);
+			RETURN FALSE;
+		END;
+		(*!stop := SdEnvironment.GetTimeCounter();*)
+		status := hc.regs.InterruptStatus;
+
+		IF Interrupt_Normal_ErrorInterrupt IN status THEN
+			IF ErrorRecovery(hc, result, status) THEN END;
+			IF Interrupt_Error_DataTimeout IN status THEN
+				result := ErrorDatTimeout;
+				IF ~Reset(hc, FALSE, TRUE) THEN END
+			ELSIF Interrupt_Error_DataCrc IN status THEN
+				result := ErrorDatCrc
+			ELSIF Interrupt_Error_DataEndBit IN status THEN
+				result := ErrorCard
+			ELSIF Interrupt_Error_Adma IN status THEN
+				HALT(182)
+			END;
+			IF Synchronize THEN hc.release END;
+			RETURN FALSE
+		END;
+		hc.regs.InterruptStatus := status;
+		IF FlagRead IN command.flags THEN
+			SdEnvironment.InvalidateDCacheRange(address, len)
+		END;
+		(*!IF FlagRead IN command.flags THEN
+			INC(Tread, stop - start);
+		ELSE
+			INC(Twrite, stop - start);
+		END;*)
+		IF Synchronize THEN hc.release END;
+		RETURN TRUE
+	END ExecuteAdmaCommand;
+
+	(** Get the response registers in 'response' *)
+	PROCEDURE GetResponse (hc: HostController; responseType: LONGINT; VAR response: ARRAY 4 OF LONGINT);
+	BEGIN
+		response := hc.regs.Response
+	END GetResponse;
+
+	(** Issue an SD Card Transaction. [Simplified specs. 3.7.1.1 pp. 106-108] *)
+	PROCEDURE StartCommand (hc: HostController; cmd, argument, responseType: LONGINT; read, dma, busy, abort: BOOLEAN; VAR status: SET; VAR result: LONGINT): BOOLEAN;
+	VAR
+		t: HUGEINT;
+		reg: LONGINT;
+		flags, txFlags: SET;
+	BEGIN
+		IF EnableTraceCmd THEN
+			Log.String("[SD] Sending Command CMD"); Log.Int(cmd, 0); Log.Ln;
+			Log.String("[SD]	Argument: "); Log.Hex(argument, -8); Log.Ln
+		END;
+
+		(* 1 *)
+		t := SdEnvironment.GetTimeCounter() + SdEnvironment.FromMilli(1000);
+		WHILE (PresentState_CommandInhibitCmd IN hc.regs.PresentState) & (t > SdEnvironment.GetTimeCounter()) DO END;
+		IF t < SdEnvironment.GetTimeCounter() THEN
+			Log.String("[SD] Timeout error in StartCommand (1)");
+			Log.Ln;
+			PrintHcRegisters(hc);
+			RETURN FALSE
+		END;
+
+		(* 2 *)
+		IF busy THEN
+			(* 3 *)
+			IF ~abort THEN
+				(* 4 *)
+				t := SdEnvironment.GetTimeCounter() + SdEnvironment.FromMilli(1000);
+				WHILE (PresentState_CommandInhibitDat IN hc.regs.PresentState) & (t > SdEnvironment.GetTimeCounter()) DO END;
+				IF t < SdEnvironment.GetTimeCounter() THEN
+					Log.String("[SD] Timeout error in StartCommand (2)");
+					Log.Ln;
+					PrintHcRegisters(hc);
+					RETURN FALSE
+				END;
+			END
+		END;
+
+		(* 5 *)
+		hc.regs.Argument1 := argument;
+
+		(* 6 *)
+		(* The response type determines the response-type and CRC/Index checks enabling *)
+		CASE responseType OF
+			 ResponseNone:
+			 	reg := Command_ResponseType_None;
+			 	IF EnableTraceCmd THEN
+			 		Log.String("[SD]	No Response Expected");
+				 	Log.Ln
+				 END
+			|ResponseR1, ResponseR5, ResponseR6, ResponseR7:
+				reg := Command_ResponseType_48b;
+				flags := {Command_CrcCheckEnable, Command_IndexCheckEnable};
+				IF EnableTraceCmd THEN
+					Log.String("[SD]	48 Bit Response"); Log.Ln;
+					Log.String("[SD]	Enabling CRC Check and Index Check"); Log.Ln
+				END
+			|ResponseR3, ResponseR4:
+				reg := Command_ResponseType_48b;
+				IF EnableTraceCmd THEN Log.String("[SD]	48 Bit Response"); Log.Ln END
+			|ResponseR1b, ResponseR5b:
+				reg := Command_ResponseType_48bBusy;
+				flags := {Command_CrcCheckEnable, Command_IndexCheckEnable};
+				IF EnableTraceCmd THEN
+					Log.String("[SD]	48 Bit Response"); Log.Ln;
+					Log.String("[SD]	Enabling Index Check"); Log.Ln
+				END
+			|ResponseR2:
+				reg := Command_ResponseType_136b;
+				flags := {Command_CrcCheckEnable};
+				IF EnableTraceCmd THEN
+					Log.String("[SD]	136 Bit Response"); Log.Ln;
+					Log.String("[SD]	Enabling Command CRC Check"); Log.Ln
+				END
+		END;
+
+		(* Command determines data-enable *)
+		IF busy THEN
+			INCL(flags, Command_DataPresent);
+			IF EnableTraceCmd THEN Log.String("[SD]	Using DAT Line"); Log.Ln END;
+
+			txFlags := {};
+			IF (*(cmd = CMD_READ_SINGLE_BLOCK) OR (cmd = CMD_READ_MULTIPLE_BLOCK) OR (cmd = ACMD_SEND_SCR)*) read THEN
+				IF EnableTraceCmd THEN Log.String("[SD] 	Data Read"); Log.Ln END;
+				INCL(txFlags, TransferMode_DataTxDirection)
+			ELSIF EnableTraceCmd THEN
+				Log.String("[SD] 	Data Write");
+				Log.Ln
+			END;
+			IF (cmd = CMD_READ_MULTIPLE_BLOCK) OR (cmd = CMD_WRITE_MULTIPLE_BLOCK) THEN
+				IF EnableTraceCmd THEN Log.String("[SD] 	Multiple blocks: using Auto CMD12 & activating block count"); Log.Ln END;
+				txFlags := txFlags + TransferMode_AutoCmd_Cmd12 + {TransferMode_MultipleBlocks, TransferMode_BlockCountEnable}
+			ELSIF EnableTraceCmd THEN
+				Log.String("[SD] 	Single Block");
+				Log.Ln
+			END;
+			IF dma THEN INCL(txFlags, TransferMode_DmaEnable) END;
+			hc.regs.TransferMode := SYSTEM.VAL(INTEGER, txFlags)
+		END;
+		hc.regs.Command := SYSTEM.VAL(INTEGER, SYSTEM.VAL(SET, reg + LSH(cmd, Command_CommandIndexOffset)) + flags);
+
+		(* 7 *)
+		RETURN WaitForCompletion(hc, ~abort, status, result)
+		(* Steps 7, 8 and 9 of WaitForCompletion have to be done by caller *)
+	END StartCommand;
+
+	(**
+		Perform error recovery as specified by the triggered error interrupts.
+		Returns an error code (result) and the interrupt status register before recovery (status)
+	*)
+	PROCEDURE ErrorRecovery (hc: HostController; VAR result: LONGINT; VAR status: SET): BOOLEAN;
+	BEGIN
+		(* 1 is done *)
+		status := hc.regs.InterruptStatus;
+		hc.regs.InterruptStatusEnable := {0 .. 14};
+		EXCL(hc.regs.InterruptStatusEnable, Interrupt_Normal_ErrorInterrupt);
+		EXCL(hc.regs.InterruptStatusEnable, Interrupt_Normal_ErrorInterrupt);
+		EXCL(hc.regs.InterruptStatusEnable, Interrupt_Normal_ErrorInterrupt);
+
+		(* 2 *)
+		IF (Interrupt_Error_CommandTimeout IN status) OR (Interrupt_Error_CommandCrc IN status) OR (Interrupt_Error_CommandEndBit IN status)
+				OR (Interrupt_Error_CommandIndex IN status) THEN
+			(* 3 & 4 *)
+			IF ~Reset(hc, TRUE, FALSE) THEN
+				result := ErrorUnrecoverable;
+				RETURN FALSE
+			END
+		END;
+
+		(* 5 *)
+		IF Interrupt_Error_DataTimeout IN status THEN
+			Log.String("[SD]	Got data timeout error"); Log.Ln
+		END;
+		IF (Interrupt_Error_DataTimeout IN status) OR (Interrupt_Error_DataCrc IN status) OR (Interrupt_Error_DataEndBit IN status) THEN
+			(* 6 & 7 *)
+			IF ~Reset(hc, FALSE, TRUE) THEN
+				result := ErrorUnrecoverable;
+				RETURN FALSE
+			END
+		END;
+
+		(* 8 & 9 *)
+		hc.regs.InterruptStatus := status;
+
+		(* 10 & 11 *)
+		(*IF ~Command(hc, CMD_STOP_TRANSMISSION, 0, ResponseR1b, FALSE, TRUE, result) THEN
+			(* 12 *)
+			IF (Interrupt_Error_CommandTimeout IN status) OR (Interrupt_Error_CommandCrc IN status) OR (Interrupt_Error_CommandEndBit IN status)
+					OR (Interrupt_Error_CommandIndex IN status) THEN
+				TRACE('CMD12 CMD LINE ERROR');
+				result := ErrorUnrecoverable;
+				RETURN FALSE
+			END;
+			(* 13 *)
+			IF ~WaitForTransfer(hc, result) THEN
+				result := ErrorUnrecoverable;
+				RETURN FALSE
+			END;
+			TRACE(hc.regs.BufferData);
+		END;
+		(* 15 *)
+		IF hc.regs.PresentState * PresentState_DatLineSignalLevelMask # PresentState_DatLineSignalLevelMask THEN
+			TRACE('CMD12 DAT LINE ERROR');
+			result := ErrorUnrecoverable;
+			RETURN FALSE
+		END;*)
+		hc.regs.InterruptStatusEnable := {0 .. 31};
+		INCL(hc.regs.InterruptStatusEnable, Interrupt_Normal_ErrorInterrupt);
+		result := ErrorNone;
+		RETURN TRUE
+	END ErrorRecovery;
+
+	(** Wait for completion of an SD command [Simplified specs. 3.7.1.2 pp. 109-110 *)
+	PROCEDURE WaitForCompletion (hc: HostController; tryRecover: BOOLEAN; 	VAR status: SET; VAR result: LONGINT): BOOLEAN;
+	BEGIN
+		result := ErrorNone;
+
+		(* 1 *)
+		IF ~hc.block(hc, {Interrupt_Normal_CommandComplete, Interrupt_Normal_ErrorInterrupt}, DefaultTimeout) THEN
+			Log.String("[SD] Timeout while waiting for completion");
+			Log.Ln;
+			PrintHcRegisters(hc);
+			RETURN FALSE
+		END;
+		status := hc.regs.InterruptStatus;
+
+		IF Interrupt_Normal_ErrorInterrupt IN status THEN
+			IF ~tryRecover THEN
+				hc.regs.InterruptStatus := status;
+				result := ErrorUnrecoverable;
+				RETURN Reset(hc, TRUE, FALSE) & Reset(hc, FALSE, TRUE)
+			END;
+			IF ~ErrorRecovery(hc, result, status) THEN RETURN FALSE END;
+			IF Interrupt_Error_CommandTimeout IN status THEN
+				IF EnableTraceCmd THEN Log.String("[SD] 	Timeout in command"); Log.Ln END;
+				result := ErrorCmdTimeout;
+				IF ~Reset(hc, TRUE, FALSE) THEN END;
+			ELSIF Interrupt_Error_CommandCrc IN status THEN
+				result := ErrorCmdCrc;
+			ELSE
+				result := ErrorCard;
+			END;
+			RETURN FALSE
+		END;
+
+		(* 2 *)
+		hc.regs.InterruptStatus := {Interrupt_Normal_CommandComplete};
+
+		IF EnableTraceCmd THEN Log.String("[SD] 	Command successful"); Log.Ln END;
+		RETURN TRUE
+	END WaitForCompletion;
+
+	(** Wait for transfer complete interrupt *)
+	PROCEDURE WaitForTransfer (hc: HostController; VAR result: LONGINT): BOOLEAN;
+	VAR
+		status: SET;
+	BEGIN
+		(* 5 *)
+		IF ~hc.block(hc, {Interrupt_Normal_TransferComplete, Interrupt_Normal_ErrorInterrupt}, DefaultTimeout) THEN
+			Log.String("[SD] Timeout failed in WaitForTransfer");
+			RETURN FALSE;
+		END;
+		(*REPEAT UNTIL (Interrupt_Normal_TransferComplete IN hc.regs.InterruptStatus) OR (Interrupt_Normal_ErrorInterrupt IN hc.regs.InterruptStatus);*)
+		status := hc.regs.InterruptStatus;
+
+		IF Interrupt_Normal_ErrorInterrupt IN hc.regs.InterruptStatus THEN
+			IF ~ErrorRecovery(hc, result, status) THEN RETURN FALSE END;
+			result := ErrorCard;
+			RETURN FALSE
+		END;
+
+		(* 6 *)
+		(*INCL(hc.regs.InterruptStatus, Interrupt_Normal_TransferComplete)*)
+		hc.regs.InterruptStatus := {Interrupt_Normal_TransferComplete}
+	END WaitForTransfer;
+
+	PROCEDURE SpinBlock (hc: HostController; mask: SET; timeout: LONGINT): BOOLEAN;
+	VAR
+		deadline: SdEnvironment.Time;
+	BEGIN
+		deadline := SdEnvironment.GetTimeCounter() + SdEnvironment.FromMilli(timeout);
+		REPEAT UNTIL (mask * hc.regs.InterruptStatus # {}) OR (SdEnvironment.GetTimeCounter() > deadline);
+		RETURN mask * hc.regs.InterruptStatus # {}
+	END SpinBlock;
+
+	(** Sends CMD7 if necessary to select the given card *)
+	PROCEDURE SelectCard * (hc: HostController; card: LONGINT; VAR result: LONGINT): BOOLEAN;
+	VAR
+		command: Command;
+		status: SET;
+	BEGIN
+		result := ErrorNone;
+		IF hc.lastRca = card THEN
+			IF EnableTrace THEN
+				Log.String("[SD] Card Already Selected");
+				Log.Ln
+			END;
+			RETURN TRUE
+		END;
+
+		IF EnableTrace THEN Log.String("[SD] Selecting Card "); Log.Int(card, 0); Log.Ln END;
+		command.hc := hc;
+		command.command := CMD_SELECT_DESELECT_CARD;
+		command.argument := LSH(card, 16);
+		command.responseType := ResponseR1b;
+		IF ~hc.execute(command, result) THEN RETURN FALSE END;
+		status := GetR1(command);
+		IF CardStatus_Error IN status THEN result := ErrorCard; RETURN FALSE END;
+		RETURN TRUE
+	END SelectCard;
+
+	(** Deselects all cards *)
+	PROCEDURE DeselectCards * (hc: HostController; VAR result: LONGINT): BOOLEAN;
+	VAR
+		command: Command;
+		ignoreRes: LONGINT;
+		ignoreBool: BOOLEAN;
+	BEGIN
+		result := ErrorNone;
+		IF hc.lastRca = 0 THEN
+			IF EnableTrace THEN Log.String("[SD] No card selected"); Log.Ln END;
+			RETURN TRUE
+		END;
+
+		IF EnableTrace THEN Log.String("[SD] Deselecting cards"); Log.Ln END;
+		command.hc := hc;
+		command.command := CMD_SELECT_DESELECT_CARD;
+		command.responseType := ResponseR1b;
+		ignoreBool := hc.execute(command, ignoreRes);
+		hc.lastRca := 0;
+		RETURN TRUE
+	END DeselectCards;
+
+	(** Read response R1 or R1b from command record *)
+	PROCEDURE GetR1 * (CONST command: Command): SET;
+	VAR
+		idx: LONGINT;
+	BEGIN
+		idx := 0;
+		(*IF (FlagAutoCmd12 IN command.flags) OR (FlagAutoCmd23 IN command.flags) THEN
+			idx := 3
+		END;*)
+		RETURN SYSTEM.VAL(SET, command.response[idx])
+	END GetR1;
+
+	(** Read response R2 from command record *)
+	PROCEDURE GetR2 * (CONST command: Command; VAR response: ARRAY OF LONGINT);
+	VAR
+		i: LONGINT;
+	BEGIN
+		FOR i := 0 TO 3 DO
+			response[i] := command.response[i]
+		END
+	END GetR2;
+
+	(** Read response R3 from command record *)
+	PROCEDURE GetR3 * (CONST command: Command): LONGINT;
+	BEGIN
+		RETURN command.response[0]
+	END GetR3;
+
+	(** Read response R4 from command record *)
+	PROCEDURE GetR4 * (CONST command: Command): LONGINT;
+	BEGIN
+		RETURN command.response[0]
+	END GetR4;
+
+	(** Read response R5 from command record *)
+	PROCEDURE GetR5 * (CONST command: Command): LONGINT;
+	BEGIN
+		RETURN command.response[0]
+	END GetR5;
+
+	(** Read response R6 from command record *)
+	PROCEDURE GetR6 * (CONST command: Command): LONGINT;
+	BEGIN
+		RETURN command.response[0]
+	END GetR6;
+
+	(** Read response R7 from command record *)
+	PROCEDURE GetR7 * (CONST command: Command): LONGINT;
+	BEGIN
+		RETURN command.response[0]
+	END GetR7;
+
+	(** Reset command and/or data lines of a host controller *)
+	PROCEDURE Reset (hc: HostController; cmd, dat: BOOLEAN): BOOLEAN;
+	VAR
+		val: SHORTINT;
+	BEGIN
+		IF cmd & dat THEN
+			val := SoftwareResetAll
+		ELSIF cmd THEN
+			val := SoftwareResetCmd
+		ELSIF dat THEN
+			val := SoftwareResetDat
+		ELSE
+			RETURN FALSE
+		END;
+		hc.regs.SoftwareReset := val;
+		REPEAT
+		UNTIL hc.regs.SoftwareReset # val;
+		RETURN TRUE
+	END Reset;
+
+	(** Set host controller bust clock *)
+	PROCEDURE SetBusClock (hc: HostController; freq: LONGINT);
+	VAR
+		val: LONGINT;
+	BEGIN
+		hc.regs.ClockControl := 0;
+
+		IF freq < hc.baseFrequency THEN
+
+			val := 1;
+			WHILE (val < 8) & (LSH(hc.baseFrequency,-val) > freq) DO
+				INC(val);
+			END;
+
+			IF EnableTrace THEN
+				Log.String(" [SD] baseFreq=");
+				Log.Int(hc.baseFrequency, 0);
+				Log.String(", val=");
+				Log.Int(val, 0);
+				Log.Ln
+			END;
+
+			hc.regs.ClockControl := INTEGER(LSH(LSH(LONGINT(1), val-1), 8) + SYSTEM.VAL(INTEGER, {ClockControl_InternalClockEnable}));
+			hc.frequency := LSH(hc.baseFrequency, -val);
+		ELSE
+			hc.regs.ClockControl := SYSTEM.VAL(INTEGER, {ClockControl_InternalClockEnable});
+			hc.frequency := hc.baseFrequency;
+		END;
+
+		REPEAT val := hc.regs.ClockControl UNTIL ClockControl_InternalClockState IN SYSTEM.VAL(SET, val);
+		val := hc.regs.ClockControl;
+		hc.regs.ClockControl := SYSTEM.VAL(INTEGER, SYSTEM.VAL(SET, val) + {ClockControl_SdClockEnable});
+		IF EnableTrace THEN
+			Log.String("[SD] Selecting Bus Clock Frequency: ");
+			Log.Int(hc.frequency, 0); Log.String(" Hz");
+			Log.Ln
+		END;
+	END SetBusClock;
+
+	(** Set host controller data timeout, in ms *)
+	PROCEDURE SetTimeout (hc: HostController; timeout: LONGINT);
+	VAR
+		ratio, val: LONGINT;
+	BEGIN
+		EXCL(hc.regs.InterruptStatusEnable, Interrupt_Error_DataTimeout);
+		ratio := LSH(LONGINT(hc.timeoutFrequency * timeout DIV 1000), -13);
+		val := 0;
+		WHILE (val < 15) & (LSH(LONGINT(1), val) < ratio) DO INC(val) END;
+		ASSERT(LSH(LONGINT(1), val) >= ratio);
+		hc.regs.TimeoutControl := SYSTEM.VAL(SHORTINT, val);
+		INCL(hc.regs.InterruptStatusEnable, Interrupt_Error_DataTimeout)
+	END SetTimeout;
+
+	(** Set the data bus width for a given RCA. *)
+	PROCEDURE SetBusWidth (hc: HostController; card: Card; width: LONGINT; VAR result: LONGINT): BOOLEAN;
+	VAR
+		command: Command;
+		val: LONGINT;
+	BEGIN
+		IF ~SelectCard(card.hc, card.rca, result) THEN RETURN FALSE END;
+
+		(*command.hc := card.hc;
+		command.command := ACMD_SET_CLR_CARD_DETECT;
+		command.argument := 0;
+		command.responseType := ResponseR1;
+		command.flags := {FlagApplicationCmd};
+		command.rca := card.rca;
+		IF ~ExecuteCommand(command, result) THEN RETURN FALSE END;*)
+
+		(* 1 *)
+		EXCL(hc.regs.InterruptStatusEnable, Interrupt_Normal_CardInterrupt);
+
+		(* 2: driver supports SD cards only *)
+		(* 3: not implemented *)
+
+		(* 4 *)
+		command.command := ACMD_SET_BUS_WIDTH;
+		CASE width OF
+			 1: command.argument := 0
+			|4: command.argument := 2
+		END;
+		command.hc := hc;
+		command.rca := card.rca;
+		command.flags := {FlagApplicationCmd};
+		command.responseType := ResponseR1;
+		IF ~ExecuteCommand(command, result) THEN RETURN FALSE END;
+
+		(* 5 *)
+		val := hc.regs.HostControl1;
+		CASE width OF
+			 1: EXCL(SYSTEM.VAL(SET, val), HostControl1_DataTransferWidth)
+			|4: INCL(SYSTEM.VAL(SET, val), HostControl1_DataTransferWidth)
+		END;
+		hc.regs.HostControl1 := SHORTINT(val);
+
+		(* 6: SD card only *)
+		(* 8 *)
+		INCL(hc.regs.InterruptStatusEnable, Interrupt_Normal_CardInterrupt);
+		RETURN TRUE
+	END SetBusWidth;
+
+	(** Executes a switch function command *)
+	PROCEDURE SwitchFunc (card: Card; switch: BOOLEAN; funcs: ARRAY 6 OF LONGINT; VAR sts: SwitchFuncStatus; VAR res: LONGINT): BOOLEAN;
+	VAR
+		status: ARRAY 64 OF CHAR;
+		command: Command;
+
+		PROCEDURE GetStatus (CONST status: ARRAY OF CHAR; VAR sts: SwitchFuncStatus);
+		VAR
+			i: LONGINT;
+		BEGIN
+			sts.current := LONGINT(ORD(status[0])) * 100H + LONGINT(ORD(status[1]));
+			FOR i := 0 TO 5 DO
+				sts.functionGroups[i] := SYSTEM.VAL(SET, LONGINT(ORD(status[2 + 2 * (5 - i)])) * 100H + LONGINT(ORD(status[1 + 2 + 2 * (5 - i)])))
+			END;
+			FOR i := 0 TO 5 BY 2 DO
+				sts.functionStatus[i] := ORD(status[14 + 2 - i DIV 2]) MOD 10H;
+				sts.functionStatus[i + 1] := ORD(status[14 + 2 - i DIV 2]) DIV 10H
+			END;
+		END GetStatus;
+
+	BEGIN
+		IF ~SelectCard(card.hc, card.rca, res) THEN RETURN FALSE END;
+		command.hc := card.hc;
+		command.command := CMD_SWITCH_FUNC;
+		command.responseType := ResponseR1;
+		command.argument := (funcs[0] MOD 10H) + 10H * (funcs[1] MOD 10H) + 100H * (funcs[2] MOD 10H) + 1000H * (funcs[3] MOD 10H) + 10000H * (funcs[4] MOD 10H) + 100000H * (funcs[5] MOD 10H);
+		IF switch THEN INCL(SYSTEM.VAL(SET, command.argument), 31) END;
+		command.flags := {FlagRead, FlagData};
+		command.dataTimeout := card.readTimeout;
+		IF ~card.hc.transfer(command, status, 0, 64, res) THEN RETURN FALSE END;
+		GetStatus(status, sts);
+		RETURN TRUE
+	END SwitchFunc;
+
+	(** Outputs the switch function status on the log *)
+	PROCEDURE PrintSwitchFuncStatus(CONST status: SwitchFuncStatus);
+	VAR
+		i: LONGINT;
+	BEGIN
+		Log.String("Switch Func Status");
+		Log.Ln;
+		Log.String("Current = ");
+		IF status.current = 0 THEN
+			Log.String("ERROR")
+		ELSE
+			Log.Int(status.current, 0);
+			Log.String(" mA");
+			Log.Ln
+		END;
+		FOR i := 0 TO 5 DO
+			Log.String("Function Group #");
+			Log.Int(i, 0);
+			Log.String(": functions = ");
+			Log.Set(status.functionGroups[i]);
+			Log.String(", status = ");
+			CASE status.functionStatus[i] OF
+				 0: Log.String("switchable")
+				|1: Log.String("switched")
+				|0FH: Log.String("ERROR")
+			END;
+			Log.Ln
+		END
+	END PrintSwitchFuncStatus;
+
+	(** Interrupt handler *)
+	PROCEDURE HandleInterrupt * (hc: HostController);
+	VAR
+		card: Card;
+		result: LONGINT;
+		interruptStatus: SET;
+	BEGIN
+		interruptStatus := hc.regs.InterruptStatus;
+
+		(*IF {Interrupt_Normal_TransferComplete, Interrupt_Normal_ErrorInterrupt} * interruptStatus # {} THEN
+			BEGIN {EXCLUSIVE} continue := TRUE END;
+			RETURN
+		END;*)
+
+		(*!TODO: make interrupt handling as quick as possible - handle all events in SdControllers asynchronously! *)
+		IF Interrupt_Normal_CardInsertion IN interruptStatus THEN
+			hc.regs.InterruptStatus := {Interrupt_Normal_CardInsertion};
+			IF EnableTrace THEN Log.String("[SD] Card Insertion"); Log.Ln END;
+			NEW(card);
+			IF InitCard(hc, card, result) & (hc.handle # NIL) THEN
+				hc.handle(card, OnInitialization, hc.handlerParam)
+			ELSIF EnableTrace THEN
+				Log.String("[SD] Could not initialize card"); Log.Ln
+			END
+		ELSIF Interrupt_Normal_CardRemoval IN interruptStatus THEN
+			hc.regs.InterruptStatus := {Interrupt_Normal_CardRemoval};
+			IF EnableTrace THEN Log.String("[SD] Card Removal"); Log.Ln END;
+			card := hc.cards;
+			IF hc.handle # NIL THEN hc.handle(card, OnRemoval, hc.handlerParam) END;
+			hc.cards := hc.cards.next
+		END;
+	END HandleInterrupt;
+
+(* ==================== Card (High-Level) Interface ==================== *)
+TYPE
+	(** Card descriptor *)
+	Card * = POINTER TO CardDesc;
+	CardDesc * = RECORD
+		state -, (** Card state. Currently not used *)
+		rca -: LONGINT; (** Card RCA *)
+		cid -: Cid; (** CID *)
+		csd -: Csd; (** CSD *)
+		scr -: Scr; (** SCR *)
+		sdStatus -: SdStatus; (** Current SD status for this card *)
+		hc -: HostController; (** Host controller on which the card is attached *)
+		acquire *, release *: PROCEDURE {DELEGATE}; (** Used for synchronization *)
+		readTimeout, (** Read timeout computed for this card, in ms *)
+		writeTimeout, (** Write timeout computed for this card, in ms *)
+		eraseTimeout: LONGINT; (** Erase timeout computed for this card, in ms *)
+		next: Card; (** Internal linked list of cards attached to the same HC *)
+	END;
+
+	(** SD Status *)
+	SdStatus * = RECORD
+		dataBusWidth -: LONGINT; (** Number of DAT bus lines currently used by card *)
+		securedMode -: BOOLEAN; (** Is card in secured mode? *)
+		cardType -: LONGINT; (** Card type, one of Category* *)
+		sizeProtArea -: HUGEINT; (** Size of the protected area, in bytes *)
+		perfMove -: LONGINT; (** Performance of move, in MB/s. 0 means 'sequential write', 255 means 'infinity' *)
+		eraseSize -: LONGINT; (** Numbers of AUs erased at a time. 0 means not supported *)
+		eraseTimeout -: LONGINT; (** Erase timeout value in second, per erase unit. 0 means not supported *)
+		eraseOffset -: LONGINT; (** Fixed timeout for erase timeout, in second *)
+		speedClass -: LONGINT; (** 3.x V speed class: 0 is 'unknown', 2, 4, 6 or 10 *)
+		auSize -: LONGINT; (** AU size for 3.x V *)
+		uhsSpeedGrade -: LONGINT; (** UHS speed grade: 0 is 'not supported', 1 or 3 *)
+		uhsAuSize -: LONGINT; (** AU size in UHS mode *)
+		vscSpeedClass -: LONGINT; (** Video mode speed class: 0 is 'not supported', 6, 10, 30, 60 or 90 *)
+		vscAuSize -: LONGINT; (** AU size in video mode *)
+		suspensionAdr -: LONGINT; (** Current suspension address, in blocks. See CMD20 *)
+		appPerfClass -: LONGINT; (** Application performance class: 0 is 'not supported', 1 or 2 *)
+		perfEnhance -: SET; (** Performance enhancing features of the card. Contains Performance* *)
+		commandQueueDepth -: LONGINT; (** Command queue depth. Valid only if PerformanceCommandQueue IN perfEnhance *)
+		discardSupport -: BOOLEAN; (** Card supports discard? *)
+		fuleSupport -: BOOLEAN; (** card supports FULE? *)
+	END;
+
+	(** Card Identification register *)
+	Cid * = RECORD
+		manufacturingDate -: RECORD (** Manufacturing date, with year and month *)
+			year -,
+			month -: LONGINT
+		END;
+		productSerialNumber -: ADDRESS; (** Serial number *)
+		productRevision -: RECORD (** Revision n.m *)
+			n-, m-: LONGINT
+		END;
+		productName -: ARRAY 6 OF CHAR; (** Product name *)
+		oemId -: ARRAY 3 OF CHAR; (** OEM/Application ID *)
+		manufacturerId -: LONGINT; (** Manufacturer identifier *)
+	END;
+
+	(* Card Registers *)
+	Csd * = RECORD
+		(* Card properties *)
+		capacity -: HUGEINT; (** Card capacity in bytes *)
+		commandClasses -: SET; (** Command classes supported by the card *)
+
+		(* Timing info *)
+		r2w -: LONGINT; (** Read to write time factor = read time / write time *)
+		taac -: REAL; (** Asynchronous access time, in s *)
+		nsac -: LONGINT; (** Worst-case clock dependent access time, in clock cycles *)
+		txSpeed -: LONGINT; (** Max transfer speed in bit/s *)
+
+		(* Block read properties *)
+		partialRead -,
+		misalignedRead -: BOOLEAN;
+		readBlockSize -: LONGINT;
+
+		(* Block write properties *)
+		partialWrite -,
+		misalignedWrite -: BOOLEAN;
+		writeBlockSize -: LONGINT;
+	END;
+
+	(** SCR register *)
+	Scr * = RECORD
+		version -, (** Card physical spec. version: one of Version* *)
+		security -: LONGINT; (** Card security type: one of Type* *)
+		busWidth -: SET; (** Bus widths supported by the card *)
+		commandSupport -: SET; (** Supported commands *)
+	END;
+
+	(**
+		Initializes a new card on the host controller.
+		Executes all commands until either an error occurs or the card is ready for data transfers.
+	*)
+	PROCEDURE InitCard * (hc: HostController; card: Card; VAR result: LONGINT): BOOLEAN;
+	VAR
+		response: LONGINT;
+		status: SET;
+		f8, sdio: BOOLEAN;
+		command: Command;
+	BEGIN
+		SetBusClock(hc, InitialClockFrequency);
+
+		FOR response := 0 TO 10000000 DO END;
+		command.hc := hc;
+
+		(* 1 *)
+		command.command := CMD_GO_IDLE_STATE;
+		command.argument := 0;
+		command.responseType := ResponseNone;
+		command.flags := {};
+		IF ~ExecuteCommand(command, result) THEN RETURN FALSE END;
+		(* 2 *)
+		command.command := CMD_SEND_IF_COND;
+		command.argument := 1AAH;
+		command.responseType := ResponseR7;
+		command.flags := {};
+		IF ~ExecuteCommand(command, result) THEN RETURN FALSE END;
+		(* 3 *)
+		response := GetR7(command);
+		IF response # 1AAH THEN
+			result := ErrorCard;
+			RETURN FALSE
+		END;
+		f8 := TRUE;
+
+		(* 5 *)
+		command.command := CMD_IO_SEND_OP_COND;
+		command.argument := 0;
+		command.responseType := ResponseR4;
+		command.flags := {};
+		IF ~ExecuteCommand(command, result) & (result = ErrorCmdTimeout) THEN
+			sdio := FALSE;
+			result := ErrorNone;
+			IF ~Reset(hc ,TRUE, FALSE) THEN RETURN FALSE END;
+		ELSIF result # ErrorNone THEN
+			RETURN FALSE
+		ELSE
+			sdio := TRUE;
+		END;
+		IF EnableTrace THEN Log.String("[SD] Card is SDIO: "); Log.Boolean(sdio); Log.Ln END;
+
+		(* 6 *)
+		IF sdio THEN
+			(*! NOT IMPLEMENTED YET *)
+			HALT(100);
+			(* 7 *)
+			(*IF ~StartCommand(hc, CMD_IO_SEND_OP_COND, 800H, ResponseR4, FALSE, FALSE, res) THEN
+				RETURN FALSE
+			END;
+			RETURN TRUE*)
+		END;
+
+		(* A *)
+		(* 12 & 19 *)
+		command.command := ACMD_SD_SEND_OP_COND;
+		command.argument := 0;
+		command.responseType := ResponseR3;
+		command.flags := {FlagApplicationCmd, FlagIgnoreIllegalCmd};
+		IF ~ExecuteCommand(command, result) THEN RETURN FALSE END;
+		response := GetR3(command);
+		IF EnableTrace THEN
+			Log.String("[SD] VDD: Ranges Supported by Card:"); Log.Ln;
+			IF CardOcr_Vdd27_28 IN SYSTEM.VAL(SET, response) THEN Log.String("[SD] 	2.7 - 2.8 V"); Log.Ln END;
+			IF CardOcr_Vdd28_29 IN SYSTEM.VAL(SET, response) THEN Log.String("[SD] 	2.8 - 2.9 V"); Log.Ln END;
+			IF CardOcr_Vdd29_30 IN SYSTEM.VAL(SET, response) THEN Log.String("[SD] 	2.9 - 3.0 V"); Log.Ln END;
+			IF CardOcr_Vdd30_31 IN SYSTEM.VAL(SET, response) THEN Log.String("[SD] 	3.0 - 3.1 V"); Log.Ln END;
+			IF CardOcr_Vdd31_32 IN SYSTEM.VAL(SET, response) THEN Log.String("[SD] 	3.1 - 3.2 V"); Log.Ln END;
+			IF CardOcr_Vdd32_33 IN SYSTEM.VAL(SET, response) THEN Log.String("[SD] 	3.2 - 3.3 V"); Log.Ln END;
+			IF CardOcr_Vdd33_34 IN SYSTEM.VAL(SET, response) THEN Log.String("[SD] 	3.3 - 3.4 V"); Log.Ln END;
+			IF CardOcr_Vdd34_35 IN SYSTEM.VAL(SET, response) THEN Log.String("[SD] 	3.4 - 3.5 V"); Log.Ln END;
+			IF CardOcr_Vdd35_36 IN SYSTEM.VAL(SET, response) THEN Log.String("[SD] 	3.5 - 3.6 V"); Log.Ln END
+		END;
+
+		status := {30};
+		IF Capabilities_Voltage30 IN hc.regs.Capabilities[0] THEN
+			IF EnableTrace THEN Log.String("[SD] Selecting 3.0 V"); Log.Ln END;
+			INCL(status, CardOcr_Vdd30_31)
+		ELSIF Capabilities_Voltage33 IN hc.regs.Capabilities[0] THEN
+			IF EnableTrace THEN Log.String("[SD] Selecting 3.3 V"); Log.Ln END;
+			INCL(status, CardOcr_Vdd32_33)
+		END;
+
+		command.command := ACMD_SD_SEND_OP_COND;
+		command.argument := SYSTEM.VAL(LONGINT, status);
+		command.responseType := ResponseR3;
+		command.flags := {FlagApplicationCmd, FlagIgnoreIllegalCmd};
+		REPEAT
+			IF ~ExecuteCommand(command, result) THEN RETURN FALSE END;
+			status := SYSTEM.VAL(SET, GetR3(command));
+		UNTIL (CardOcr_PowerUpStatus IN status);
+		IF EnableTrace & (CardOcr_S18A IN status) THEN
+			Log.String("[SD] Card supports 1.8V");
+			Log.Ln
+		END;
+		IF f8 & (CardOcr_CardCapacityStatus IN status) THEN
+			IF EnableTrace THEN Log.String("[SD] Card: SDHC or SDXC") END;
+			card.scr.security := TypeSDHC
+		ELSIF f8 THEN
+			IF EnableTrace THEN Log.String("[SD] Card: SDSC v2 or v3") END;
+			card.scr.security := TypeSDSC
+		ELSE
+			IF EnableTrace THEN Log.String("[SD] Card: SDSC v1.0 or v1.1") END;
+			card.scr.security := TypeSDSC
+		END;
+		IF EnableTrace THEN Log.Ln END;
+
+		(* 32 *)
+		command.command := CMD_ALL_SEND_CID;
+		command.argument := 0;
+		command.responseType := ResponseR2;
+		command.flags := {};
+		IF ~ExecuteCommand(command, result) THEN RETURN FALSE END;
+		DecodeCid(command.response, card.cid);
+		PrintCardCid(card.cid);
+
+		(* 33 *)
+		command.command := CMD_SEND_RELATIVE_ADDR;
+		command.argument := 0;
+		command.responseType := ResponseR6;
+		command.flags := {};
+		REPEAT
+			IF ~ExecuteCommand(command, result) THEN RETURN FALSE END;
+			card.rca := LSH(GetR6(command), -16)
+		UNTIL card.rca # 0;
+		IF EnableTrace THEN Log.String("[SD] New Card with RCA: "); Log.Hex(card.rca, -4); Log.Ln END;
+		status := SYSTEM.VAL(SET, GetR6(command));
+		status := status * {0 .. 15};
+		(* status is a modified CardStatus: reform corresponding card status *)
+		IF 15 IN status THEN EXCL(status, 15); INCL(status, 23) END;
+		IF 14 IN status THEN EXCL(status, 14); INCL(status, 22) END;
+		IF 13 IN status THEN EXCL(status, 13); INCL(status, 19) END;
+		IF EnableTrace THEN PrintCardStatus(status) END;
+		card.hc := hc;
+		card.next := hc.cards;
+		hc.cards := card;
+		IF Synchronize THEN SdEnvironment.GetLock(card.acquire, card.release) END;
+
+		(* Get Additional card registers: CSD *)
+		IF ~ReadCsd(card, result) THEN RETURN FALSE END;
+
+		SetBusClock(hc, card.csd.txSpeed);
+		(* Reasonable default timeout values, later rewritten with the ones given by the card *)
+		card.readTimeout := 100;
+		card.writeTimeout := 250;
+		card.eraseTimeout := 250;
+
+		(*IF ~Reset(hc, TRUE, FALSE) OR ~Reset(hc, FALSE, TRUE) THEN RETURN FALSE END;*)
+		IF ~ReadScr(card, result) THEN RETURN FALSE END;
+		ComputeTimeouts(card);
+
+		IF (card.scr.version >= Version1p1) THEN
+			IF EnableTrace THEN
+				Log.String("[SD] Enabling high-speed mode");
+				Log.Ln
+			END;
+			IF ~SelectSpeedMode(card, TRUE, result) THEN RETURN FALSE END;
+			IF EnableTrace THEN
+				Log.String("[SD] High-speed mode enabled");
+				Log.Ln
+			END;
+
+			(* Get CSD again: transfer speed might have changed *)
+			IF ~ReadCsd(card, result) THEN RETURN FALSE END;
+			ComputeTimeouts(card);
+			SetBusClock(hc, card.csd.txSpeed)
+		END;
+
+		IF 4 IN card.scr.busWidth THEN
+
+			IF EnableTrace THEN
+				Log.String("[SD] Changing bus width to 4 bits");
+				Log.Ln
+			END;
+
+			IF ~SetBusWidth(card.hc, card, 4, result) THEN RETURN FALSE END;
+			IF EnableTrace THEN
+				Log.String("[SD] Bus width changed");
+				Log.Ln
+			END
+		END;
+		IF ~Reset(hc, TRUE, FALSE) OR ~Reset(hc, FALSE, TRUE) THEN RETURN FALSE END;
+		GetSdStatus(card);
+
+		(*ResetStatistics; NEW(testbuf);
+		FOR response := 1 TO 100 DO
+			IF ~Read(card, 33554432, LEN(testbuf), testbuf^, 0, result) THEN RETURN FALSE END;
+			IF ~Write(card, 33554432, LEN(testbuf), testbuf^, 0, result) THEN RETURN FALSE END;
+		END;
+		Statistics(Commands.GetContext());
+		HALT(512);*)
+		RETURN TRUE
+	END InitCard;
+
+	(** Write 'data[ofs, ofs + len)' to 'card', starting at block 'firstBlock'. *)
+	PROCEDURE Write * (card: Card; firstBlock, len: LONGINT; VAR data: ARRAY OF SYSTEM.BYTE; ofs: LONGINT; VAR result: LONGINT): BOOLEAN;
+	VAR
+		command: Command;
+		start, stop: HUGEINT;
+		ret: BOOLEAN;
+	BEGIN
+		result := ErrorNone;
+		IF Synchronize THEN card.acquire END;
+		start := SdEnvironment.GetTimeCounter();
+		IF ~SelectCard(card.hc, card.rca, result) THEN
+			IF Synchronize THEN card.release END;
+			RETURN FALSE
+		END;
+
+		command.hc := card.hc;
+		command.rca := card.rca;
+
+		(* Pre-erase blocks *)
+		command.command := ACMD_SET_WR_BLK_ERASE_COUNT;
+		command.flags := {FlagApplicationCmd};
+		command.responseType := ResponseR1;
+		command.argument := (len + BlockSize - 1) DIV BlockSize;
+		IF ~ExecuteCommand(command, result) THEN
+			IF Synchronize THEN card.release END;
+			RETURN FALSE
+		END;
+
+		command.argument := firstBlock;
+		command.responseType := ResponseR1;
+		command.flags := {FlagData};
+		command.dataTimeout := card.writeTimeout;
+		command.blockSize := card.csd.writeBlockSize;
+		IF len > card.csd.writeBlockSize THEN
+			INCL(command.flags, FlagMultipleBlockTx);
+			INCL(command.flags, FlagCountBlocks);
+			INCL(command.flags, FlagAutoCmd12);
+			command.command := CMD_WRITE_MULTIPLE_BLOCK;
+		ELSE
+			command.command := CMD_WRITE_BLOCK
+		END;
+		ret := command.hc.transfer(command, data, ofs, len, result);
+		stop := SdEnvironment.GetTimeCounter();
+		IF Synchronize THEN card.release END;
+		IF ret THEN
+			INC(Nwrite);
+			INC(NbyteWritten, len);
+			INC(Twrite, stop - start)
+		END;
+		RETURN ret
+	END Write;
+
+	(** Read 'len' bytes starting from 'firstBlock' of 'card' to 'data[ofs, ofs + len)' *)
+	PROCEDURE Read * (card: Card; firstBlock, len: LONGINT; VAR data: ARRAY OF SYSTEM.BYTE; ofs: LONGINT; VAR result: LONGINT): BOOLEAN;
+	VAR
+		command: Command;
+		start, stop: HUGEINT;
+		ret: BOOLEAN;
+	BEGIN
+		IF Synchronize THEN card.acquire END;
+		start := SdEnvironment.GetTimeCounter();
+		IF ~SelectCard(card.hc, card.rca, result) THEN
+			IF Synchronize THEN card.release END;
+			RETURN FALSE
+		END;
+
+		command.hc := card.hc;
+		command.argument := firstBlock;
+		command.responseType := ResponseR1;
+		(*command.flags := {FlagData, FlagRead};*)
+		command.rca := card.rca;
+		command.dataTimeout := card.readTimeout;
+		command.blockSize := card.csd.readBlockSize;
+		IF len > card.csd.readBlockSize THEN
+			command.flags := {FlagData, FlagRead, FlagMultipleBlockTx, FlagCountBlocks, FlagAutoCmd12};
+			(*INCL(command.flags, FlagMultipleBlockTx);
+			INCL(command.flags, FlagCountBlocks);
+			INCL(command.flags, FlagAutoCmd12);*)
+			command.command := CMD_READ_MULTIPLE_BLOCK
+		ELSE
+			command.flags := {FlagData, FlagRead};
+			command.command := CMD_READ_SINGLE_BLOCK
+		END;
+		ret := command.hc.transfer(command, data, ofs, len, result) & ~(CardStatus_Error IN GetR1(command));
+		stop := SdEnvironment.GetTimeCounter();
+		IF Synchronize THEN card.release END;
+		IF ret THEN
+			INC(Nread);
+			INC(NbyteRead, len);
+			INC(Tread, stop - start)
+		END;
+		RETURN ret
+	END Read;
+
+	(** Erase blocks [block, block + num) on specified card. *)
+	PROCEDURE Erase * (card: Card; block, num: LONGINT; VAR result: LONGINT): BOOLEAN;
+	VAR
+		command: Command;
+		r1: SET;
+	BEGIN
+		command.hc := card.hc;
+		command.flags := {};
+		command.rca := card.rca;
+		command.dataTimeout := card.eraseTimeout;
+
+		command.responseType := ResponseR1;
+		command.argument := block;
+		command.command := CMD_ERASE_WR_BLK_START;
+		IF ~ExecuteCommand(command, result) & (CardStatus_Error IN GetR1(command)) THEN RETURN FALSE END;
+
+		command.argument := block + num - 1;
+		command.command := CMD_ERASE_WR_BLK_END;
+		IF ~ExecuteCommand(command, result) & (CardStatus_Error IN GetR1(command)) THEN RETURN FALSE END;
+
+		command.argument := 0;
+		command.command := CMD_ERASE;
+		command.responseType := ResponseR1b;
+		IF ~ExecuteCommand(command, result) & (CardStatus_Error IN GetR1(command)) THEN RETURN FALSE END;
+
+		(*WHILE 20 IN card.hc.regs.PresentState DO END;*)
+		command.command := CMD_SEND_STATUS;
+		command.responseType := ResponseR1;
+		REPEAT
+			IF ~ExecuteCommand(command, result) THEN RETURN FALSE END;
+			r1 := GetR1(command);
+			IF CardStatus_Error IN r1 THEN RETURN FALSE END
+		UNTIL CardStatus_ReadyForData IN r1;
+		RETURN TRUE
+	END Erase;
+
+	PROCEDURE ReadCsd (card: Card; VAR result: LONGINT): BOOLEAN;
+	VAR
+		command: Command;
+		csd: ARRAY 4 OF LONGINT;
+	BEGIN
+		result := ErrorNone;
+		IF ~DeselectCards(card.hc, result) THEN RETURN FALSE END;
+
+		command.hc := card.hc;
+		command.command := CMD_SEND_CSD;
+		command.argument := LSH(card.rca, 16);
+		command.responseType := ResponseR2;
+		command.flags := {};
+		IF ~ExecuteCommand(command, result) THEN RETURN FALSE END;
+		GetR2(command, csd);
+		DecodeCsd(csd, card.csd);
+		IF EnableTrace THEN
+			PrintCardCsd(csd);
+			Log.String("[SD] Card Capacity: ");
+			PrintSize(card.csd.capacity);
+			Log.Ln;
+			Log.String("[SD] Bus frequency: "); Log.Int(card.csd.txSpeed, 0); Log.String(" Hz"); Log.Ln
+		END;
+		RETURN TRUE
+	END ReadCsd;
+
+	(** Read the SD card Configuration Register of a card *)
+	PROCEDURE ReadScr (card: Card; VAR result: LONGINT): BOOLEAN;
+	VAR
+		command: Command;
+		ofs: LONGINT;
+		scr: ARRAY 8 OF CHAR;
+	BEGIN
+		IF ~SelectCard(card.hc, card.rca, result) THEN RETURN FALSE END;
+
+		(*
+			disconnect the pull-up resistor on D3 line (important for high-speed mode with 4 lines)
+		*)
+		(*command.hc := card.hc;
+		command.command := ACMD_SET_CLR_CARD_DETECT;
+		command.argument := 0;
+		command.responseType := ResponseR1;
+		command.flags := {FlagApplicationCmd};
+		command.rca := card.rca;
+		IF ~ExecuteCommand(command, result) THEN RETURN FALSE END;*)
+
+		(* Get Card Register: SCR *)
+		command.hc := card.hc;
+		command.command := ACMD_SEND_SCR;
+		command.argument := 0;
+		command.responseType := ResponseR1;
+		command.flags := {FlagApplicationCmd, FlagData, FlagRead};
+		command.rca := card.rca;
+		command.dataTimeout := card.readTimeout;
+		(*ofs := 32 - ADDRESSOF(scr[0]) MOD 32;*)
+		IF ~card.hc.transfer(command, scr, ofs, 8, result) THEN RETURN FALSE END;
+		IF CardStatus_Error IN GetR1(command) THEN result := ErrorCard; RETURN FALSE END;
+		DecodeScr(scr, ofs, card.scr);
+		IF EnableTrace THEN PrintCardScr(card.scr) END;
+		RETURN TRUE
+	END ReadScr;
+
+	PROCEDURE GetSdStatus (card: Card);
+	TYPE Bitfield = ARRAY 16 OF LONGINT;
+	VAR
+		status: ARRAY 64 OF CHAR;
+		command: Command;
+		ignore: LONGINT;
+		bitfield: POINTER {UNSAFE,UNTRACED} TO Bitfield;
+		c: CHAR;
+		i: LONGINT;
+	BEGIN
+		ASSERT(SelectCard(card.hc, card.rca, ignore));
+		command.hc := card.hc;
+		command.rca := card.rca;
+		command.command := ACMD_SD_STATUS;
+		command.flags := {FlagApplicationCmd, FlagData, FlagRead};
+		command.responseType := ResponseR1;
+		command.dataTimeout := card.readTimeout;
+		IF ~card.hc.transfer(command, status, 0, LEN(status), ignore) THEN RETURN END;
+
+		FOR i := 0 TO LEN(status) DIV 2 DO
+			c := status[i];
+			status[i] := status[LEN(status) - 1 - i];
+			status[LEN(status) - 1 - i] := c
+		END;
+
+		bitfield := ADDRESSOF(status[0]);
+		DecodeSdStatus(bitfield^, card.sdStatus);
+		PrintSdStatus(card.sdStatus);
+	END GetSdStatus;
+
+	PROCEDURE GetCid (card: Card);
+	VAR
+		cid: Cid;
+		command: Command;
+		res: LONGINT;
+	BEGIN
+		IF ~DeselectCards(card.hc, res) THEN HALT(512) END;
+		command.hc := card.hc;
+		command.command := CMD_SEND_CID;
+		command.argument := LSH(card.rca, 16);
+		command.flags := {};
+		command.responseType := ResponseR2;
+		IF ~ExecuteCommand(command, res) THEN HALT(512) END;
+		DecodeCid(command.response, cid);
+		PrintCardCid(cid)
+	END GetCid;
+
+	(**
+		Computes data timeouts for read, write and erase operations according to SD specifications.
+		Stores them in card.readTimeout, writeTimeout, eraseTimeout.
+	*)
+	PROCEDURE ComputeTimeouts (card: Card);
+	VAR
+		readTime: LONGINT;
+	BEGIN
+		(*
+			Read timeout is the lower of:
+				o	(CSD.TAAC + CSD.NSAC / busfreq * 100) * TimeoutReadFactor
+				o	TimeoutReadFix
+			for a normal SD card and
+				TimeoutReadFix
+			for a SDHC card.
+		
+			Write timeout is very similar to read timeout, except that (CSD.TAAC + CSD.NSAC) * CSD.R2W is used.
+		*)
+		IF card.scr.version >= TypeSDHC THEN
+			card.readTimeout := TimeoutReadFix;
+			card.writeTimeout := TimeoutWriteFix
+		ELSE
+			readTime := 100 * LONGINT((card.csd.taac + REAL(card.csd.nsac * 100) / REAL(card.hc.frequency)) * 1000.0);
+			card.readTimeout := MIN(readTime, TimeoutReadFix);
+			card.writeTimeout := MIN(readTime * card.csd.r2w, TimeoutWriteFix);
+		END;
+		card.eraseTimeout := TimeoutErase;
+
+		IF EnableTrace THEN
+			Log.String("[SD] Read timeout = "); Log.Int(card.readTimeout, 0); Log.String(" ms"); Log.Ln;
+			Log.String("[SD] Write timeout = "); Log.Int(card.writeTimeout, 0); Log.String(" ms"); Log.Ln;
+			Log.String("[SD] Erase timeout = "); Log.Int(card.eraseTimeout, 0); Log.String(" ms"); Log.Ln
+		END
+	END ComputeTimeouts;
+
+	(** Select card speed mode. It is necessary to set it to high in order to use higher bus clock frequencies. *)
+	PROCEDURE SelectSpeedMode(card: Card; high: BOOLEAN; VAR res: LONGINT): BOOLEAN;
+	VAR
+		funcs: ARRAY 6 OF LONGINT;
+		status: SwitchFuncStatus;
+		val: SET;
+	BEGIN
+		funcs[0] := 1;
+		IF ~SwitchFunc(card, FALSE, funcs, status, res) THEN RETURN FALSE END;
+		IF EnableTrace THEN
+			Log.String("[SD] Select speed, before:");
+			Log.Ln;
+			PrintSwitchFuncStatus(status)
+		END;
+		IF ~(1 IN status.functionGroups[0]) THEN
+			Log.String("[SD] HIGH-SPEED MODE NOT SUPPORTED");
+			Log.Ln;
+			RETURN TRUE
+		END;
+		IF ~SwitchFunc(card, TRUE, funcs, status, res) THEN RETURN FALSE END;
+		IF EnableTrace THEN
+			Log.String("[SD] Select speed, after:");
+			Log.Ln;
+			PrintSwitchFuncStatus(status)
+		END;
+		val := SYSTEM.VAL(SET, LONGINT(card.hc.regs.HostControl1));
+		IF high THEN
+			INCL(val, HostControl1_HighSpeedEnable)
+		ELSE
+			EXCL(val, HostControl1_HighSpeedEnable)
+		END;
+		card.hc.regs.HostControl1 := SYSTEM.VAL(SHORTINT, val);
+		RETURN TRUE
+	END SelectSpeedMode;
+
+	PROCEDURE DecodeSdStatus (CONST raw: ARRAY OF LONGINT; VAR status: SdStatus);
+	VAR
+		val: LONGINT;
+	BEGIN
+		val := ReadBitfield(raw, SdStatus_DatBusWidthOfs, SdStatus_DatBusWidthWidth);
+		CASE val OF
+			 0: status.dataBusWidth := 1
+			|2: status.dataBusWidth := 4
+		ELSE
+			status.dataBusWidth := 0
+		END;
+		status.securedMode := ReadBit(raw, SdStatus_SecuredMode);
+		val := ReadBitfield(raw, SdStatus_SdCardTypeOfs, SdStatus_SdCardTypeWidth);
+		CASE val OF
+			 0: status.cardType := CategoryRW
+			|1: status.cardType := CategoryRO
+			|2: status.cardType := CategoryOTP
+		END;
+		status.sizeProtArea := ReadBitfield(raw, SdStatus_SizeOfProtectedAreaOfs, SdStatus_SizeOfProtectedAreaWidth);
+		(*! TODO: multiply sizeProtArea by MULT * BLOCK_LEN if SDSC *)
+		val := ReadBitfield(raw, SdStatus_SpeedClassOfs, SdStatus_SpeedClassWidth);
+		CASE val OF
+			 0 .. 3: status.speedClass := 2 * val
+			|4: status.speedClass := 10
+		END;
+		status.perfMove := ReadBitfield(raw, SdStatus_PerformanceMoveOfs, SdStatus_PerformanceMoveWidth);
+		status.auSize := ReadBitfield(raw, SdStatus_AuSizeOfs, SdStatus_AuSizeWidth) * 16 * 1024;
+		status.eraseSize := ReadBitfield(raw, SdStatus_EraseSizeOfs, SdStatus_EraseSizeWidth);
+		status.eraseTimeout := ReadBitfield(raw, SdStatus_EraseTimeoutOfs, SdStatus_EraseTimeoutWidth);
+		status.eraseOffset := ReadBitfield(raw, SdStatus_EraseOffsetOfs, SdStatus_EraseOffsetWidth);
+		val := ReadBitfield(raw, SdStatus_UhsSpeedGradeOfs, SdStatus_UhsSpeedGradeWidth);
+		CASE val OF
+			0, 1, 3: status.uhsSpeedGrade := val
+		END;
+		status.uhsAuSize := ReadBitfield(raw, SdStatus_UhsAuSizeOfs, SdStatus_UhsAuSizeWidth) * 1024 * 1024;
+		status.vscSpeedClass := ReadBitfield(raw, SdStatus_VideoSpeedClassOfs, SdStatus_VideoSpeedClassWidth);
+		status.vscAuSize := ReadBitfield(raw, SdStatus_VscAuSizeOfs, SdStatus_VscAuSizeWidth) * 1024 * 1024;
+		status.suspensionAdr := ReadBitfield(raw, SdStatus_SusAddrOfs, SdStatus_SusAddrWidth);
+		status.appPerfClass := ReadBitfield(raw, SdStatus_AppPerfClassOfs, SdStatus_AppPerfClassWidth);
+		val := ReadBitfield(raw, SdStatus_PerformanceEnhanceOfs, SdStatus_PerformanceEnhanceWidth);
+		status.perfEnhance := SYSTEM.VAL(SET, val) * {PerformanceCardMaintenance .. PerformanceCache};
+		IF SYSTEM.VAL(SET, val) * {3 .. MAX(SET)} # {} THEN
+			INCL(status.perfEnhance, PerformanceQueue);
+			status.commandQueueDepth := LSH(val, -3) + 1
+		END;
+		status.discardSupport := ReadBit(raw, SdStatus_DiscardSupport);
+		status.fuleSupport := ReadBit(raw, SdStatus_FuleSupport)
+	END DecodeSdStatus;
+
+	PROCEDURE DecodeCid (CONST raw: ARRAY OF LONGINT; VAR cid: Cid);
+	VAR
+		val: LONGINT;
+	BEGIN
+		val := ReadBitfield(raw, CardCid_ProductManufacturingDateOfs, CardCid_ProductManufacturingDateWidth);
+		cid.manufacturingDate.year := val DIV 10H + 2000;
+		cid.manufacturingDate.month := val MOD 10H;
+		cid.productSerialNumber := SYSTEM.VAL(ADDRESS, ReadBitfield(raw, CardCid_ProductSerialNbOfs, CardCid_ProductSerialNbWidth));
+		val := ReadBitfield(raw, CardCid_ProductRevisionOfs, CardCid_ProductRevisionWidth);
+		cid.productRevision.n := val DIV 10H;
+		cid.productRevision.m := val MOD 10H;
+		SYSTEM.MOVE(ADDRESSOF(raw[0]) + CardCid_ProductNameOfs DIV 8, ADDRESSOF(cid.productName), CardCid_ProductNameWidth DIV 8);
+		SYSTEM.MOVE(ADDRESSOF(raw[0]) + CardCid_OEM_ApplicationIdOfs DIV 8, ADDRESSOF(cid.oemId), CardCid_OEM_ApplicationIdWidth DIV 8);
+		cid.manufacturerId := ReadBitfield(raw, CardCid_ManufacturerIdOfs, CardCid_ManufacturerIdWidth)
+	END DecodeCid;
+
+	(** Fills a Csd record from the raw csd bytes *)
+	PROCEDURE DecodeCsd (CONST raw: ARRAY OF LONGINT; VAR csd: Csd);
+	VAR
+		sizeMult, val, version: LONGINT;
+		real: REAL;
+	BEGIN
+		version := ReadBitfield(raw, CardCsd_CsdStructureOfs, CardCsd_CsdStructureWidth) + 1;
+
+		IF version = 1 THEN
+			sizeMult := LSH(LONGINT(2), ReadBitfield(raw, CardCsd_CSizeMultOfs1, CardCsd_CSizeMultWidth1));
+			csd.capacity := sizeMult * (1 + ReadBitfield(raw, CardCsd_CSizeOfs1, CardCsd_CSizeWidth1)) * 512;
+		ELSE
+			csd.capacity := 512 * 1024 * (HUGEINT(ReadBitfield(raw, CardCsd_CSizeOfs2, CardCsd_CSizeWidth2)) + 1);
+		END;
+
+		csd.commandClasses := SYSTEM.VAL(SET, ReadBitfield(raw, CardCsd_CccOfs, CardCsd_CccWidth));
+		csd.nsac := ReadBitfield(raw, CardCsd_NsacOfs, CardCsd_NsacWidth) * 100;
+
+		val := ReadBitfield(raw, CardCsd_R2wFactorOfs, CardCsd_R2wFactorWidth);
+		IF val >= 6 THEN
+			csd.r2w := 0
+		ELSE
+			csd.r2w := LSH(LONGINT(1), val);
+		END;
+
+		val := ReadBitfield(raw, CardCsd_TranSpeedOfs, CardCsd_TranSpeedWidth);
+		CASE val DIV 8 OF
+			 1: csd.txSpeed := 10
+			|2: csd.txSpeed := 12
+			|3: csd.txSpeed := 13
+			|4: csd.txSpeed := 15
+			|5: csd.txSpeed := 20
+			|6: csd.txSpeed := 25
+			|7: csd.txSpeed := 30
+			|8: csd.txSpeed := 35
+			|9: csd.txSpeed := 40
+			|10: csd.txSpeed := 45
+			|11: csd.txSpeed := 50
+			|12: csd.txSpeed := 55
+			|13: csd.txSpeed := 60
+			|14: csd.txSpeed := 70
+			|15: csd.txSpeed := 80
+		ELSE
+			csd.txSpeed := 00
+		END;
+		csd.txSpeed := csd.txSpeed * 100;
+		CASE val MOD 8 OF
+			 0: csd.txSpeed := csd.txSpeed * 100
+			|1: csd.txSpeed := csd.txSpeed * 1000
+			|2: csd.txSpeed := csd.txSpeed * 10000
+			|3: csd.txSpeed := csd.txSpeed * 100000
+		END;
+
+		val := ReadBitfield(raw, CardCsd_TaacOfs, CardCsd_TaacWidth);
+		CASE val DIV 8 OF
+			 1: real := 1.0
+			|2: real := 1.2
+			|3: real := 1.3
+			|4: real := 1.5
+			|5: real := 2.0
+			|6: real := 2.5
+			|7: real := 3.0
+			|8: real := 3.5
+			|9: real := 4.0
+			|10: real := 4.5
+			|11: real := 5.0
+			|12: real := 5.5
+			|13: real := 6.0
+			|14: real := 7.0
+			|15: real := 8.0
+		ELSE
+			real := 0.0
+		END;
+		CASE val MOD 8 OF
+			 0: real := real * 1.0E-9
+			|1: real := real * 1.0E-8
+			|2: real := real * 1.0E-7
+			|3: real := real * 1.0E-6
+			|4: real := real * 1.0E-5
+			|5: real := real * 1.0E-4
+			|6: real := real * 1.0E-3
+			|7: real := real * 1.0E-2
+		END;
+		csd.taac := real;
+
+		csd.partialRead := ReadBitfield(raw, CardCsd_ReadBlPartial, 1) = 1;
+		csd.misalignedRead := ReadBitfield(raw, CardCsd_ReadBlkMisalign, 1) = 1;
+		val := ReadBitfield(raw, CardCsd_ReadBlLenOfs, CardCsd_ReadBlLenWidth);
+		IF (val <= 8) OR (val >= 12) THEN
+			csd.readBlockSize := BlockSize
+		ELSE
+			csd.readBlockSize := LSH(LONGINT(1), val)
+		END;
+
+		csd.partialWrite := ReadBitfield(raw, CardCsd_WriteBlPartial, 1) = 1;
+		csd.misalignedWrite := ReadBitfield(raw, CardCsd_WriteBlkMisalign, 1) = 1;
+		val := ReadBitfield(raw, CardCsd_WriteBlLenOfs, CardCsd_WriteBlLenWidth);
+		IF (val <= 8) OR (val >= 12) THEN
+			csd.writeBlockSize := BlockSize
+		ELSE
+			csd.writeBlockSize := LSH(LONGINT(1), val)
+		END
+	END DecodeCsd;
+
+	(** Fills a SCR record from raw data bytes *)
+	PROCEDURE DecodeScr (CONST raw: ARRAY OF CHAR; ofs: LONGINT; VAR scr: Scr);
+	TYPE
+		Array = ARRAY 8 OF CHAR;
+	VAR
+		bfield: ARRAY 2 OF LONGINT;
+		i: LONGINT;
+		val: SET;
+	BEGIN
+		FOR i := 0 TO 7 DO SYSTEM.VAL(Array, bfield)[7-i] := raw[ofs + i] END;
+		IF ReadBitfield(bfield, CardScr_StructureOfs, CardScr_StructureWidth) # 0 THEN RETURN END;
+		scr.version := ReadBitfield(bfield, CardScr_SpecVersionOfs, CardScr_SpecVersionWidth);
+		IF ReadBitfield(bfield, CardScr_SpecV3, 1) # 0 THEN scr.version := Version3 END;
+		IF ReadBitfield(bfield, CardScr_SpecV4, 1) # 0 THEN scr.version := Version4 END;
+		i := ReadBitfield(bfield, CardScr_SpecVXOfs, CardScr_SpecVXWidth);
+		IF i = CardScr_SpecVX_v5 THEN scr.version := Version5
+		ELSIF i = CardScr_SpecVX_v6 THEN scr.version := Version6
+		END;
+
+		CASE ReadBitfield(bfield, CardScr_SecurityOfs, CardScr_SecurityWidth) OF
+			 0: scr.security := TypeNone
+			|2: scr.security := TypeSDSC
+			|3: scr.security := TypeSDHC
+			|4: scr.security := TypeSDXC
+		END;
+		val := SYSTEM.VAL(SET, ReadBitfield(bfield, CardScr_BusWidthsOfs, CardScr_BusWidthsWidth));
+		FOR i := 0 TO MAX(SET) - 1 DO
+			IF i IN val THEN
+				INCL(scr.busWidth, LSH(LONGINT(1), i))
+			END
+		END
+	END DecodeScr;
+
+	(**
+		Print the state of all Host Controller registers
+	*)
+	PROCEDURE PrintHcRegisters * (regs: HcRegisters);
+	BEGIN
+		IF EnableTrace THEN
+			Log.String("[SD] HC registers status:"); Log.Ln;
+			Log.String("[SD] 	SDMASystemAddress: 0x"); Log.Hex(regs.SDMASystemAddress,-SIZEOF(LONGINT)*2); Log.Ln;
+			Log.String("[SD] 	BlockSize: 0x"); Log.Hex(regs.BlockSize,-SIZEOF(INTEGER)*2); Log.Ln;
+			Log.String("[SD] 	BlockCount: 0x"); Log.Hex(regs.BlockCount,-SIZEOF(INTEGER)*2); Log.Ln;
+			Log.String("[SD] 	Argument1: 0x"); Log.Hex(regs.Argument1,-SIZEOF(LONGINT)*2); Log.Ln;
+			Log.String("[SD] 	TransferMode: 0x"); Log.Hex(regs.TransferMode,-SIZEOF(INTEGER)*2); Log.Ln;
+			Log.String("[SD] 	Command: 0x"); Log.Hex(regs.Command,-SIZEOF(INTEGER)*2); Log.Ln;
+
+			Log.String("[SD] 	Response: 0x");
+			Log.Hex(regs.Response[0],-SIZEOF(LONGINT)*2); Log.String(" 0x");
+			Log.Hex(regs.Response[1],-SIZEOF(LONGINT)*2); Log.String(" 0x");
+			Log.Hex(regs.Response[2],-SIZEOF(LONGINT)*2); Log.String(" 0x");
+			Log.Hex(regs.Response[3],-SIZEOF(LONGINT)*2); Log.Ln;
+
+			Log.String("[SD] 	BufferData: 0x"); Log.Hex(regs.BufferData,-SIZEOF(LONGINT)*2); Log.Ln;
+			Log.String("[SD] 	PresentState: "); Log.Set(regs.PresentState); Log.Ln;
+			Log.String("[SD] 	HostControl1: 0x"); Log.Hex(regs.HostControl1,-SIZEOF(SHORTINT)*2); Log.Ln;
+			Log.String("[SD] 	PowerControl: 0x"); Log.Hex(regs.PowerControl,-SIZEOF(SHORTINT)*2); Log.Ln;
+			Log.String("[SD] 	BlockGapControl: 0x"); Log.Hex(regs.BlockGapControl,-SIZEOF(SHORTINT)*2); Log.Ln;
+			Log.String("[SD] 	WakeupControl: 0x"); Log.Hex(regs.WakeupControl,-SIZEOF(SHORTINT)*2); Log.Ln;
+			Log.String("[SD] 	ClockControl: 0x"); Log.Hex(regs.ClockControl,-SIZEOF(INTEGER)*2); Log.Ln;
+			Log.String("[SD] 	TimeoutControl: 0x"); Log.Hex(regs.TimeoutControl,-SIZEOF(SHORTINT)*2); Log.Ln;
+			Log.String("[SD] 	SoftwareReset: 0x"); Log.Hex(regs.SoftwareReset,-SIZEOF(SHORTINT)*2); Log.Ln;
+			Log.String("[SD] 	InterruptStatus: "); Log.Set(regs.InterruptStatus); Log.Ln;
+			Log.String("[SD] 	InterruptStatusEnable: "); Log.Set(regs.InterruptStatusEnable); Log.Ln;
+			Log.String("[SD] 	InterruptSignalEnable: "); Log.Set(regs.InterruptSignalEnable); Log.Ln;
+			Log.String("[SD] 	AutoCmdErrorStatus: 0x"); Log.Hex(regs.AutoCmdErrorStatus,-SIZEOF(INTEGER)*2); Log.Ln;
+			Log.String("[SD] 	HostControl2: 0x"); Log.Hex(regs.HostControl2,-SIZEOF(INTEGER)*2); Log.Ln;
+
+			Log.String("[SD] 	Capabilities: ");
+			Log.Set(regs.Capabilities[0]); Log.String(" ");
+			Log.Set(regs.Capabilities[1]); Log.Ln;
+
+			(*Log.String("[SD] 	MaximumCurrentCapabilities: 0x"); Log.Hex(regs.MaximumCurrentCapabilities,-SIZEOF(HUGEINT)*2); Log.Ln;
+			Log.String("[SD] 	ForceEventAutoCmdErrorStatus: 0x"); Log.Hex(regs.ForceEventAutoCmdErrorStatus,-SIZEOF(INTEGER)*2); Log.Ln;
+			Log.String("[SD] 	ForceEventErrorInterruptStatus: 0x"); Log.Hex(regs.ForceEventErrorInterruptStatus,-SIZEOF(INTEGER)*2); Log.Ln;
+			Log.String("[SD] 	AdmaErrorStatus: 0x"); Log.Hex(regs.AdmaErrorStatus,-SIZEOF(SHORTINT)*2); Log.Ln;
+			Log.String("[SD] 	AdmaSystemAddress: 0x"); Log.Hex(regs.AdmaSystemAddress,-SIZEOF(HUGEINT)*2); Log.Ln;*)
+
+			Log.String("[SD] 	PresetValues: 0x");
+			Log.Hex(regs.PresetValues[0],-SIZEOF(INTEGER)*2); Log.String(" 0x");
+			Log.Hex(regs.PresetValues[1],-SIZEOF(INTEGER)*2); Log.String(" 0x");
+			Log.Hex(regs.PresetValues[2],-SIZEOF(INTEGER)*2); Log.String(" 0x");
+			Log.Hex(regs.PresetValues[3],-SIZEOF(INTEGER)*2); Log.String(" 0x");
+			Log.Hex(regs.PresetValues[4],-SIZEOF(INTEGER)*2); Log.String(" 0x");
+			Log.Hex(regs.PresetValues[5],-SIZEOF(INTEGER)*2); Log.String(" 0x");
+			Log.Hex(regs.PresetValues[6],-SIZEOF(INTEGER)*2); Log.String(" 0x");
+			Log.Hex(regs.PresetValues[7],-SIZEOF(INTEGER)*2); Log.Ln;
+
+			Log.String("[SD] 	SharedBusControl: 0x"); Log.Hex(regs.SharedBusControl,-SIZEOF(LONGINT)*2); Log.Ln;
+			Log.String("[SD] 	SlotInterruptStatus: 0x"); Log.Hex(regs.SlotInterruptStatus,-SIZEOF(INTEGER)*2); Log.Ln;
+			Log.String("[SD] 	HostControllerVersion: 0x"); Log.Hex(regs.HostControllerVersion,-SIZEOF(INTEGER)*2); Log.Ln
+		END
+	END PrintHcRegisters;
+
+	(** Outputs the capabilities of a host controller on the log. *)
+	PROCEDURE PrintCapabilities * (hc: HostController);
+	VAR
+		c0, c1: SET;
+	BEGIN
+		IF EnableTrace THEN
+			c0 := hc.regs.Capabilities[0];
+			c1 := hc.regs.Capabilities[1];
+			Log.String("[SD] "); Log.String("Host Capabilities:"); Log.Ln;
+			Log.String("[SD] "); Log.String("	Timeout Clock Frequency: ");
+			IF c0 * Capabilities_TimeoutClockFrequencyMask = {} THEN
+				Log.String("Unknown")
+			ELSE
+				Log.Int(SYSTEM.VAL(LONGINT, c0 * Capabilities_TimeoutClockFrequencyMask), 0);
+				IF Capabilities_TimeoutClockUnit IN c0 THEN
+					Log.String(" MHz")
+				ELSE
+					Log.String(" kHz")
+				END
+			END;
+			Log.Ln;
+
+			Log.String("[SD] "); Log.String("	Base Clock Frequency: ");
+			IF c0 * Capabilities_BaseClockFreqSdMask = {} THEN
+				Log.String("Unknown")
+			ELSE
+				Log.Int(LSH(SYSTEM.VAL(LONGINT, c0 * Capabilities_BaseClockFreqSdMask), -Capabilities_BaseClockFreqSdOfs), 0)
+			END;
+			Log.Ln;
+
+			Log.String("[SD] "); Log.String("	Max Block Length: ");
+			Log.Int(512 * (1 + LSH(SYSTEM.VAL(LONGINT, c0 * Capabilities_MaxBlockLenMask), -Capabilities_MaxBlockLenOfs)), 0);
+			Log.String(" B");
+			Log.Ln;
+
+			Log.String("[SD] "); Log.String("	8 Bit Support for Embedded Device: ");
+			Log.Boolean(Capabilities_8BitEmbedded IN c0);
+			Log.Ln;
+
+			Log.String("[SD] "); Log.String("	Support for ADMA2: ");
+			Log.Boolean(Capabilities_ADMA2 IN c0);
+			Log.Ln;
+
+			Log.String("[SD] "); Log.String("	Support for High Speed: ");
+			Log.Boolean(Capabilities_HighSpeed IN c0);
+			Log.Ln;
+
+			Log.String("[SD] "); Log.String("	Support for SDMA: ");
+			Log.Boolean(Capabilities_SDMA IN c0);
+			Log.Ln;
+
+			Log.String("[SD] "); Log.String("	Support for Suspend/Resume: ");
+			Log.Boolean(Capabilities_SuspendResume IN c0);
+			Log.Ln;
+
+			Log.String("[SD] "); Log.String("	Voltage Support for 3.3 V: ");
+			Log.Boolean(Capabilities_Voltage33 IN c0);
+			Log.Ln;
+
+			Log.String("[SD] "); Log.String("	Voltage Support for 3.0 V: ");
+			Log.Boolean(Capabilities_Voltage30 IN c0);
+			Log.Ln;
+
+			Log.String("[SD] "); Log.String("	Voltage Support for 1.8 V: ");
+			Log.Boolean(Capabilities_Voltage18 IN c0);
+			Log.Ln;
+
+			Log.String("[SD] "); Log.String("	Support for 64 Bit Bus: ");
+			Log.Boolean(Capabilities_64BitBus IN c0);
+			Log.Ln;
+
+			Log.String("[SD] "); Log.String("	Support for Asynchronous Interrupts: ");
+			Log.Boolean(Capabilities_AsyncInterrupt IN c0);
+			Log.Ln;
+
+			Log.String("[SD] "); Log.String("	Slot Type: ");
+			IF c0 * Capabilities_SlotTypeMask = Capabilities_SlotType_Removable THEN
+				Log.String("Removable Card Slot")
+			ELSIF c0 * Capabilities_SlotTypeMask = Capabilities_SlotType_Embedded THEN
+				Log.String("Embedded Slot for One Device")
+			ELSIF c0 * Capabilities_SlotTypeMask = Capabilities_SlotType_SharedBus THEN
+				Log.String("Shared Bus Slot")
+			END;
+			Log.Ln;
+
+			IF hc.version = 3 THEN
+				Log.String("[SD] "); Log.String("	Support for SDR50: ");
+				Log.Boolean(Capabilities_SDR50 IN c1);
+				Log.Ln;
+
+				Log.String("[SD] "); Log.String("	Support for SDR104: ");
+				Log.Boolean(Capabilities_SDR104 IN c1);
+				Log.Ln;
+
+				Log.String("[SD] "); Log.String("	Support for DDR50: ");
+				Log.Boolean(Capabilities_DDR50 IN c1);
+				Log.Ln;
+
+				Log.String("[SD] "); Log.String("	Support for Driver Type A: ");
+				Log.Boolean(Capabilities_DriverTypeA IN c1);
+				Log.Ln;
+
+				Log.String("[SD] "); Log.String("	Support for Driver Type C: ");
+				Log.Boolean(Capabilities_DriverTypeC IN c1);
+				Log.Ln;
+
+				Log.String("[SD] "); Log.String("	Support for Driver Type D: ");
+				Log.Boolean(Capabilities_DriverTypeD IN c1);
+				Log.Ln;
+
+				Log.String("[SD] "); Log.String("	Timer Count for Retuning: ");
+				IF c1 * Capabilities_TimerCountRetuningMask = Capabilities_TimerCountRetuningMask THEN
+					Log.String("Unknown")
+				ELSIF c1 * Capabilities_TimerCountRetuningMask = {} THEN
+					Log.String("Disabled")
+				ELSE
+					Log.Int(LSH(LONGINT(1), LSH(SYSTEM.VAL(LONGINT, c1 * Capabilities_TimerCountRetuningMask), -Capabilities_TimerCountRetuningOfs)), 0);
+					Log.String(" s")
+				END;
+				Log.Ln;
+
+				Log.String("[SD] "); Log.String("	SDR50 Requires Retuning: ");
+				Log.Boolean(Capabilities_TuningSDR50 IN c1);
+				Log.Ln;
+
+				Log.String("[SD] "); Log.String("	Retuning Mode: ");
+				Log.Int(LSH(SYSTEM.VAL(LONGINT, c1 * Capabilities_RetuningModesMask), -Capabilities_RetuningModesOfs), 0);
+				Log.Ln;
+
+				Log.String("[SD] "); Log.String("	Clock Multiplier: ");
+				IF c1 * Capabilities_ClockMultiplierMask = {} THEN
+					Log.String("Not Supported")
+				ELSE
+					Log.Int(LSH(SYSTEM.VAL(LONGINT, c1 * Capabilities_ClockMultiplierMask), -Capabilities_ClockMultiplierOfs) + 1, 0)
+				END;
+				Log.Ln
+			END
+		END
+	END PrintCapabilities;
+
+	(** Print a card status response on the log. *)
+	PROCEDURE PrintCardStatus * (status: SET);
+	BEGIN
+		IF EnableTrace THEN
+			Log.String("[SD] Card Status:"); Log.Ln;
+			Log.String("[SD]	AKE Error: "); Log.Boolean(CardStatus_AkeSpecError IN status); Log.Ln;
+			Log.String("[SD]	App Command: "); Log.Boolean(CardStatus_AppCmd IN status); Log.Ln;
+			Log.String("[SD]	Ready For Data: "); Log.Boolean(CardStatus_ReadyForData IN status); Log.Ln;
+			Log.String("[SD]	Card State: ");
+			CASE LSH(SYSTEM.VAL(LONGINT, status * CardStatus_CurrentStateMask), -CardStatus_CurrentStateOffset) OF
+				 CardIdle: Log.String("Idle")
+				|CardReady: Log.String("Ready")
+				|CardIdentification: Log.String("Identification")
+				|CardStandby: Log.String("Standby")
+				|CardTransfer: Log.String("Transfer")
+				|CardData: Log.String("Sending Data")
+				|CardReceive: Log.String("Receiving Data")
+				|CardProgram: Log.String("Programming")
+				|CardDisabled: Log.String("Disabled")
+			END;
+			Log.Ln;
+			Log.String("[SD]	Erase Reset: "); Log.Boolean(CardStatus_EraseReset IN status); Log.Ln;
+			Log.String("[SD]	Internal ECC Enable: "); Log.Boolean(CardStatus_CardEccDisable IN status); Log.Ln;
+			Log.String("[SD]	Write_Protection Erase Skip: "); Log.Boolean(CardStatus_WpEraseSkip IN status); Log.Ln;
+			Log.String("[SD]	CSD Overwrite: "); Log.Boolean(CardStatus_CsdOverwrite IN status); Log.Ln;
+			Log.String("[SD]	Error: "); Log.Boolean(CardStatus_Error IN status); Log.Ln;
+			Log.String("[SD]	Card Controller Error: "); Log.Boolean(CardStatus_CcError IN status); Log.Ln;
+			Log.String("[SD]	Card ECC Failed: "); Log.Boolean(CardStatus_CardEccFailed IN status); Log.Ln;
+			Log.String("[SD]	Illegal Command: "); Log.Boolean(CardStatus_IllegalCommand IN status); Log.Ln;
+			Log.String("[SD]	Command CRC Error: "); Log.Boolean(CardStatus_ComCrcError IN status); Log.Ln;
+			Log.String("[SD]	Lock/Unlock Failed: "); Log.Boolean(CardStatus_LockUnlockFailed IN status); Log.Ln;
+			Log.String("[SD]	Card is Locked: "); Log.Boolean(CardStatus_CardIsLocked IN status); Log.Ln;
+			Log.String("[SD]	Write-Protection Violation: "); Log.Boolean(CardStatus_WpViolation IN status); Log.Ln;
+			Log.String("[SD]	Invalid Erase Parameters: "); Log.Boolean(CardStatus_EraseParam IN status); Log.Ln;
+			Log.String("[SD]	Erase Sequence Error: "); Log.Boolean(CardStatus_EraseSeqError IN status); Log.Ln;
+			Log.String("[SD]	Block Length Error: "); Log.Boolean(CardStatus_BlockLenError IN status); Log.Ln;
+			Log.String("[SD]	Address Error: "); Log.Boolean(CardStatus_AddressError IN status); Log.Ln;
+			Log.String("[SD]	Argument Out of Range: "); Log.Boolean(CardStatus_OutOfRange IN status); Log.Ln
+		END
+	END PrintCardStatus;
+
+	(** Print SD Status record *)
+	PROCEDURE PrintSdStatus * (status: SdStatus);
+	BEGIN
+		IF EnableTrace THEN
+			Log.String("[SD] SD Status"); Log.Ln;
+			Log.String("[SD]	data bus width: "); Log.Int(status.dataBusWidth, 0); Log.Ln;
+			Log.String("[SD]	secured mode enabled: "); Log.Boolean(status.securedMode); Log.Ln;
+			Log.String("[SD]	cardType: ");
+			CASE status.cardType OF
+				 CategoryRW: Log.String("normal RW card")
+				|CategoryRO: Log.String("read-only card")
+				|CategoryOTP: Log.String("one-time programmable card")
+			END; Log.Ln;
+			Log.String("[SD]	size of protected area: "); PrintSize(status.sizeProtArea); Log.Ln;
+			Log.String("[SD]	move performance: ");
+			CASE status.perfMove OF
+				 0: Log.String("sequential write")
+				|255: Log.String("infinite")
+			ELSE
+				Log.Int(status.perfMove, 0);
+				Log.String(" MB/s")
+			END; Log.Ln;
+			Log.String("[SD]	erase size: "); Log.Int(status.eraseSize, 0); Log.String(" AUs"); Log.Ln;
+			Log.String("[SD]	erase timeout: ");
+			IF status.eraseTimeout = 0 THEN
+				Log.String("not supported")
+			ELSE
+				Log.Int(status.eraseTimeout, 0); Log.String(" s")
+			END; Log.Ln;
+			Log.String("[SD]	erase timeout offset: "); Log.Int(status.eraseOffset, 0); Log.String(" s"); Log.Ln;
+			Log.String("[SD]	speed class: "); Log.Int(status.speedClass, 0); Log.Ln;
+			Log.String("[SD]	AU size: "); PrintSize(status.auSize); Log.Ln;
+			Log.String("[SD]	UHS speed grade: "); Log.Int(status.uhsSpeedGrade, 0); Log.Ln;
+			Log.String("[SD]	UHS AU size: "); PrintSize(status.uhsAuSize); Log.Ln;
+			Log.String("[SD]	video speed class: "); Log.Int(status.vscSpeedClass, 0); Log.Ln;
+			Log.String("[SD]	VSC AU size: "); PrintSize(status.vscAuSize); Log.Ln;
+			Log.String("[SD]	suspension address: "); Log.Int(status.suspensionAdr, 0); Log.Ln;
+			Log.String("[SD]	application performance class: ");
+			IF status.appPerfClass = 0 THEN
+				Log.String("not supported")
+			ELSE
+				Log.String("A");
+				Log.Int(status.appPerfClass, 0)
+			END; Log.Ln;
+			Log.String("[SD]	performance enhance: ");
+			IF status.perfEnhance = {} THEN Log.String("none") END;
+			IF PerformanceCardMaintenance IN status.perfEnhance THEN Log.String("card-initiated maintenance; ") END;
+			IF PerformanceHostMaintenance IN status.perfEnhance THEN Log.String("host-initiated maintenance; ") END;
+			IF PerformanceCache IN status.perfEnhance THEN Log.String("cache; ") END;
+			IF PerformanceQueue IN status.perfEnhance THEN
+				Log.String("command queue;"); Log.Ln;
+				Log.String("[SD]	command queue depth: ");
+				Log.Int(status.commandQueueDepth, 0)
+			END; Log.Ln;
+			Log.String("[SD]	discard supported: "); Log.Boolean(status.discardSupport); Log.Ln;
+			Log.String("[SD]	FULE supported: "); Log.Boolean(status.fuleSupport); Log.Ln
+		END
+	END PrintSdStatus;
+
+	(** Print the CID of a card to the log. *)
+	PROCEDURE PrintCardCid * (cid: Cid);
+	BEGIN
+		IF EnableTrace THEN
+			Log.String("[SD] CID"); Log.Ln;
+			Log.String("[SD]	manufacturing date: "); Log.Int(cid.manufacturingDate.month, 0); Log.String("/"); Log.Int(cid.manufacturingDate.year, 0); Log.Ln;
+			Log.String("[SD]	product serial number: "); Log.Int(cid.productSerialNumber, 0); Log.Ln;
+			Log.String("[SD]	product revision: "); Log.Int(cid.productRevision.n, 0); Log.String("."); Log.Int(cid.productRevision.m, 0); Log.Ln;
+			Log.String("[SD]	product name: "); Log.String(cid.productName); Log.Ln;
+			Log.String("[SD]	OEM/application id: "); Log.String(cid.oemId); Log.Ln;
+			Log.String("[SD]	manufacturer id: "); Log.Int(cid.manufacturerId, 0); Log.Ln
+		END
+	END PrintCardCid;
+
+	(** Print the CSD of a card to the log. *)
+	PROCEDURE PrintCardCsd * (CONST csd: ARRAY OF LONGINT);
+	VAR
+		cap: HUGEINT;
+		val, version, sizeMult: LONGINT;
+	BEGIN
+		IF EnableTrace THEN
+			version := ReadBitfield(csd, CardCsd_CsdStructureOfs, CardCsd_CsdStructureWidth) + 1;
+			Log.String("[SD] "); Log.String("Card CSD:"); Log.Ln;
+			Log.String("[SD] "); Log.String("	Version: "); Log.Int(version, 0); Log.Ln;
+
+			(* Common Fields *)
+			Log.String("[SD] "); Log.String("	File Format: ");
+			val := ReadBitfield(csd, CardCsd_FileFormatOfs, CardCsd_FileFormatWidth);
+			IF ReadBitfield(csd, CardCsd_FileFormatGrp, 1) = 1 THEN
+				Log.String("Unknown Value (");
+				Log.Int(val, 0);
+				Log.String(")")
+			ELSIF val = 0 THEN
+				Log.String("Hard-disk file system with partition table")
+			ELSIF val = 1 THEN
+				Log.String("FAT")
+			ELSIF val = 2 THEN
+				Log.String("Universal File Format")
+			ELSE
+				Log.String("Other")
+			END;
+			Log.Ln;
+
+			Log.String("[SD] "); Log.String("	Temporary Write Protection: "); Log.Boolean(ReadBitfield(csd, CardCsd_TmpWriteProtect, 1) = 1); Log.Ln;
+			Log.String("[SD] "); Log.String("	Permanent Write Protection: "); Log.Boolean(ReadBitfield(csd, CardCsd_PermWriteProtect, 1) = 1); Log.Ln;
+			Log.String("[SD] "); Log.String("	Copy: "); Log.Boolean(ReadBitfield(csd, CardCsd_Copy, 1) = 1); Log.Ln;
+			Log.String("[SD] "); Log.String("	Partial Block Write: "); Log.Boolean(ReadBitfield(csd, CardCsd_WriteBlPartial, 1) = 1); Log.Ln;
+			Log.String("[SD] "); Log.String("	Maximum Write Block Length: ");
+			val := ReadBitfield(csd, CardCsd_WriteBlLenOfs, CardCsd_WriteBlLenWidth);
+			IF (val <= 8) OR (val >= 12) THEN
+				Log.String("Unknown Value (");
+				Log.Int(val, 0);
+				Log.String(")")
+			ELSE
+				Log.Int(LSH(LONGINT(1), val), 0);
+			END;
+			Log.Ln;
+			Log.String("[SD] "); Log.String("	Block Program Time / Block Read Time: ");
+			val := ReadBitfield(csd, CardCsd_R2wFactorOfs, CardCsd_R2wFactorWidth);
+			IF val >= 6 THEN
+				Log.String("Unknown Value (");
+				Log.Int(val, 0);
+				Log.String(")")
+			ELSE
+				Log.Int(LSH(LONGINT(1), val), 0);
+			END;
+			Log.Ln;
+			Log.String("[SD] "); Log.String("	Group Write Protection: "); Log.Boolean(ReadBitfield(csd, CardCsd_WpGrpEnable, 1) = 1); Log.Ln;
+			Log.String("[SD] "); Log.String("	WpGrpSize: "); Log.Int(ReadBitfield(csd, CardCsd_WpGrpSizeOfs, CardCsd_WpGrpSizeWidth) + 1, 0); Log.String(" sectors"); Log.Ln;
+			Log.String("[SD] "); Log.String("	Sector Size: "); Log.Int(ReadBitfield(csd, CardCsd_SectorSizeOfs, CardCsd_SectorSizeWidth) + 1, 0); Log.Ln;
+			Log.String("[SD] "); Log.String("	Erase Block Enable: "); Log.Boolean(ReadBitfield(csd, CardCsd_EraseBlkEn, 1) = 1); Log.Ln;
+
+			IF version = 1 THEN
+				sizeMult := LSH(LONGINT(2), ReadBitfield(csd, CardCsd_CSizeMultOfs1, CardCsd_CSizeMultWidth1));
+				cap := sizeMult * (1 + ReadBitfield(csd, CardCsd_CSizeOfs1, CardCsd_CSizeWidth1)) * 512;
+			ELSE
+				cap := 512 * 1024 * (HUGEINT(ReadBitfield(csd, CardCsd_CSizeOfs2, CardCsd_CSizeWidth2)) + 1);
+			END;
+			Log.String("[SD] "); Log.String("	Card Capacity: "); Log.Int(cap, 0); Log.String(" B"); Log.Ln;
+			Log.String("[SD] "); Log.String("	DSR Implemented: "); Log.Boolean(ReadBitfield(csd, CardCsd_DsrImp, 1) = 1); Log.Ln;
+			Log.String("[SD] "); Log.String("	Misaligned Block Read: "); Log.Boolean(ReadBitfield(csd, CardCsd_ReadBlkMisalign, 1) = 1); Log.Ln;
+			Log.String("[SD] "); Log.String("	Misaligned Block Write: "); Log.Boolean(ReadBitfield(csd, CardCsd_WriteBlkMisalign, 1) = 1); Log.Ln;
+			Log.String("[SD] "); Log.String("	Partial Block Read: "); Log.Boolean(ReadBitfield(csd, CardCsd_ReadBlPartial, 1) = 1); Log.Ln;
+			(*ASSERT(ReadBitfield(csd, CardCsd_ReadBlPartial, 1) = 1);*)
+			Log.String("[SD] "); Log.String("	Maximal Block Read Length: ");
+			val := ReadBitfield(csd, CardCsd_ReadBlLenOfs, CardCsd_ReadBlLenWidth);
+			IF (val <= 8) OR (val >= 12) THEN
+				Log.String("Unknown Value (");
+				Log.Int(val, 0);
+				Log.String(")")
+			ELSE
+				Log.Int(LSH(LONGINT(1), val), 0); Log.String(" B")
+			END;
+			Log.Ln;
+			Log.String("[SD] "); Log.String("	Supported Command Classes: "); Log.Set(SYSTEM.VAL(SET, ReadBitfield(csd, CardCsd_CccOfs, CardCsd_CccWidth))); Log.Ln;
+			Log.String("[SD] "); Log.String("	Transfer Speed: ");
+			val := ReadBitfield(csd, CardCsd_TranSpeedOfs, CardCsd_TranSpeedWidth);
+			CASE val DIV 8 OF
+				 1: Log.String("1.0")
+				|2: Log.String("1.2")
+				|3: Log.String("1.3")
+				|4: Log.String("1.5")
+				|5: Log.String("2.0")
+				|6: Log.String("2.0")
+				|7: Log.String("2.5")
+				|8: Log.String("3.5")
+				|9: Log.String("4.0")
+				|10: Log.String("4.5")
+				|11: Log.String("5.0")
+				|12: Log.String("5.5")
+				|13: Log.String("6.0")
+				|14: Log.String("7.0")
+				|15: Log.String("8.0")
+			ELSE
+				Log.String("Unknown Value (");
+				Log.Int(val, 0);
+				Log.String(")")
+			END;
+			Log.String(" * 1");
+			CASE val MOD 8 OF
+				 0: Log.String("00 k")
+				|1: Log.String(" M")
+				|2: Log.String("0 M")
+				|3: Log.String("00 M")
+			END;
+			Log.String("Bit/s");
+			Log.Ln;
+
+			Log.String("[SD] "); Log.String("	Clock-Dependent Access Time: "); Log.Int(ReadBitfield(csd, CardCsd_NsacOfs, CardCsd_NsacWidth) * 100, 0); Log.String("000 clock cycles"); Log.Ln;
+			Log.String("[SD] "); Log.String("	Asynchronous Access Time: ");
+			val := ReadBitfield(csd, CardCsd_TaacOfs, CardCsd_TaacWidth);
+			CASE val DIV 8 OF
+				 1: Log.String("1.0")
+				|2: Log.String("1.2")
+				|3: Log.String("1.3")
+				|4: Log.String("1.5")
+				|5: Log.String("2.0")
+				|6: Log.String("2.5")
+				|7: Log.String("3.0")
+				|8: Log.String("3.5")
+				|9: Log.String("4.0")
+				|10: Log.String("4.5")
+				|11: Log.String("5.0")
+				|12: Log.String("5.5")
+				|13: Log.String("6.0")
+				|14: Log.String("7.0")
+				|15: Log.String("8.0")
+			ELSE
+				Log.String("Unknown Value (");
+				Log.Int(val, 0);
+				Log.String(")")
+			END;
+			Log.String(" * 1");
+			CASE val MOD 8 OF
+				 0: Log.String(" ns")
+				|1: Log.String("0 ns")
+				|2: Log.String("00 ns")
+				|3: Log.String(" microsecond")
+				|4: Log.String("0 microsecond")
+				|5: Log.String("00 microsecond")
+				|6: Log.String(" ms")
+				|7: Log.String("0 ms")
+			END;
+			Log.Ln
+		END
+	END PrintCardCsd;
+
+	PROCEDURE PrintCardScr * (CONST scr: Scr);
+	VAR i: LONGINT;
+	BEGIN
+		IF EnableTrace THEN
+			Log.String("[SD] Card SCR"); Log.Ln;
+			Log.String("[SD]	physical layer version: ");
+			CASE scr.version OF
+				 Version1: Log.String("1")
+				|Version1p1: Log.String("1.1")
+				|Version2: Log.String("2")
+				|Version3: Log.String("3")
+				|Version4: Log.String("4")
+				|Version5: Log.String("5")
+				|Version6: Log.String("6")
+			ELSE
+				Log.String("unknown")
+			END;
+			Log.Ln;
+			Log.String("[SD]	security support: ");
+			CASE scr.security OF
+				 TypeNone: Log.String("none")
+				|TypeSDSC: Log.String("SDSC")
+				|TypeSDHC: Log.String("SDHC")
+				|TypeSDXC: Log.String("SDXC")
+			END;
+			Log.Ln;
+			Log.String("[SD]	supported bus widths: "); Log.Set(scr.busWidth); Log.Ln
+		END
+	END PrintCardScr;
+
+	(** Helper to write a size in a human-readable format *)
+	PROCEDURE PrintSize (size: HUGEINT);
+	VAR
+		prefix: ARRAY 8 OF CHAR;
+		i: LONGINT;
+
+	BEGIN
+		IF size < 1024 THEN
+			Log.Int(size, 0);
+			Log.String(" ")
+		ELSE
+			prefix := 'kMGT';
+			i := 0;
+			size := size DIV 1024;
+			WHILE size > 1024 DO
+				size := size DIV 1024;
+				INC(i)
+			END;
+			Log.Int(size, 0);
+			Log.String(" ");
+			Log.Char(prefix[i])
+		END;
+		Log.String("B")
+	END PrintSize;
+
+	(**
+		Helper procedure to read bit fields in a wide register.
+			field: large bitfield
+			ofs: offset of first bit to extract
+			width: number of bits to extract
+		Returns the bits as a LONGINT.
+	*)
+	PROCEDURE ReadBitfield (CONST field: ARRAY OF LONGINT; ofs, width: LONGINT): LONGINT;
+	VAR
+		adr, bits: ADDRESS;
+	BEGIN
+		ASSERT(ofs MOD 8 + width <= 32);
+		adr := ADDRESSOF(field[0]) + ofs DIV 8;
+		bits := SYSTEM.GET8(adr) MOD 100H;
+		IF ofs MOD 8 + width > 8 THEN
+			bits := bits + LSH(ADDRESS(SYSTEM.GET8(adr + 1)) MOD 100H, 8);
+		END;
+		IF ofs MOD 8 + width > 16 THEN
+			bits := bits + LSH(ADDRESS(SYSTEM.GET8(adr + 2)) MOD 100H, 16);
+		END;
+		IF ofs MOD 8 + width > 24 THEN
+			bits := bits + LSH(ADDRESS(SYSTEM.GET8(adr + 3)) MOD 100H, 24)
+		END;
+		RETURN SYSTEM.VAL(LONGINT, SYSTEM.VAL(SET, LSH(bits, -(ofs MOD 8))) * {0 .. width - 1})
+	END ReadBitfield;
+
+	PROCEDURE ReadBit (CONST field: ARRAY OF LONGINT; bit: LONGINT): BOOLEAN;
+	BEGIN
+		RETURN SYSTEM.VAL(BOOLEAN, LSH(SYSTEM.GET8(ADDRESSOF(field[0])) + bit DIV 8, -(bit MOD 8)))
+	END ReadBit;
+
+	PROCEDURE ResetStatistics *;
+	BEGIN
+		NbyteRead := 0;
+		Nread := 0;
+		Tread := 0;
+		NbyteWritten := 0;
+		Nwrite := 0;
+		Twrite := 0
+	END ResetStatistics;
+END Sd.

+ 268 - 0
source/SdDisks.Mod

@@ -0,0 +1,268 @@
+MODULE SdDisks;
+(**
+	AUTHOR Timothée Martiel
+	PURPOSE Disk driver for SD cards.
+*)
+IMPORT
+	SYSTEM,
+	Objects, Kernel, Plugins, Disks, DiskCaches, Strings,
+	Sd, SdEnvironment, KernelLog;
+
+CONST
+	NameBase		= "SD";
+
+	BlockSize		= 512; (** Size of a SD block *)
+
+	EnableCache* = TRUE;
+
+TYPE
+
+	(**
+		SD Card Disk Device
+	*)
+	Device * = OBJECT (Disks.Device)
+	VAR
+		card: Sd.Card;
+		next: Device;
+		cache: DiskCaches.Cache;
+
+		PROCEDURE & InitSdDevice (card: Sd.Card);
+		VAR
+			bufferSize: LONGINT;
+		BEGIN
+			SELF.card := card;
+			blockSize := BlockSize;
+(*			CASE card.sdStatus.speedClass OF
+				 2, 4:
+				 	IF card.csd.capacity > 1024*1024*1024 THEN
+				 		bufferSize := 32 * 1024 DIV BlockSize
+				 	ELSE
+				 		bufferSize := 16 * 1024 DIV BlockSize
+				 	END
+				|6: bufferSize := 64 * 1024 DIV BlockSize
+				|10: bufferSize := 512 * 1024 DIV BlockSize
+			END;*)
+
+			bufferSize := 16 * 1024 DIV BlockSize;
+
+			INCL(flags, Disks.Removable);
+			IF EnableCache THEN
+				NEW(cache,Transfer0,bufferSize);
+			END;
+		END InitSdDevice;
+
+		PROCEDURE Transfer0 * (op, block, num: LONGINT; VAR data: ARRAY OF CHAR; ofs: LONGINT; VAR res: LONGINT);
+		VAR ok: BOOLEAN;
+		BEGIN
+			CASE op OF
+				 Disks.Write:
+				 	ok := Sd.Write(card, block, num*blockSize, data, ofs, res)
+				|Disks.Read:
+					ok := Sd.Read(card, block, num*blockSize, data, ofs, res);
+			END
+		END Transfer0;
+
+		PROCEDURE Transfer * (op, block, num: LONGINT; VAR data: ARRAY OF CHAR; offs: LONGINT; VAR res: LONGINT);
+		VAR
+			t: SdEnvironment.Time;
+		BEGIN{EXCLUSIVE}
+			t := SdEnvironment.GetTimeCounter();
+			IF EnableCache THEN
+				cache.Transfer(op, block, num, data, offs, res);
+			ELSE
+				Transfer0(op, block, num, data, offs, res);
+			END;
+
+			t := SdEnvironment.GetTimeCounter() - t;
+			IF op = Disks.Read THEN
+				INC(rdTime,t);
+			ELSE
+				INC(wrTime,t);
+			END;
+(*			TRACE(block,num,t);*)
+		END Transfer;
+
+		PROCEDURE GetSize * (VAR size, res: LONGINT);
+		BEGIN
+			size := LONGINT(card.csd.capacity);
+			IF size < 0 THEN size := MAX(LONGINT) END;
+			res := Disks.Ok
+		END GetSize;
+
+		PROCEDURE Handle * (VAR msg: Disks.Message;  VAR res: LONGINT);
+		BEGIN
+			res := 0;
+			IF msg IS Disks.LockMsg THEN
+				Sd.SetLedState(card.hc, TRUE)
+			ELSIF msg IS Disks.UnlockMsg THEN
+				Sd.SetLedState(card.hc, FALSE);
+				Sync;
+			ELSIF (msg IS Disks.EjectMsg) OR (msg IS Disks.SyncMsg) THEN
+				Sync;
+			END
+		END Handle;
+(*
+		(** Make sure that the RU ru is in cache entry idx *)
+		PROCEDURE UpdateCacheEntry (ru, idx: LONGINT; VAR res: LONGINT): BOOLEAN;
+		VAR entry: POINTER {UNSAFE} TO CacheEntry;
+		BEGIN
+			entry := ADDRESSOF(cache[idx]);
+			IF ~entry.active OR (entry.ru # ru) THEN
+				INC(NcacheMiss);
+				IF entry.active THEN INC(NcacheEvict) END;
+				(*IF entry.active & entry.mod THEN
+					INC(NcacheWriteback);
+					IF ~Sd.Write(card, entry.ru * bufferSize, bufferSize * BlockSize, entry.buffer^, 0, res) THEN RETURN FALSE END
+				END;*)
+				entry.active := TRUE;
+				(*entry.mod := FALSE;*)
+				entry.ru := ru;
+				IF ~Sd.Read(card, ru * bufferSize, bufferSize * BlockSize, entry.buffer^, 0, res) THEN RETURN FALSE END
+			ELSE
+				INC(NcacheHits)
+			END;
+			RETURN TRUE
+		END UpdateCacheEntry;
+*)
+		(** Write back all modified cache entries to disk and invalidate all entries *)
+		PROCEDURE Sync;
+		(*VAR
+			i, len, res, ofs: LONGINT;
+			wbentry, wbnext: POINTER {UNSAFE} TO WriteBufferEntry;
+			ignore: BOOLEAN;*)
+		BEGIN {EXCLUSIVE}
+(*			wbentry := ADDRESSOF(wbuffer[head MOD WBSize]);
+			i := 1;
+			len := wbentry.len;
+			LOOP
+				IF i = size THEN EXIT END;
+				wbnext := ADDRESSOF(wbuffer[(head + i) MOD WBSize]);
+				IF wbentry.block + len DIV BlockSize # wbnext.block THEN EXIT END;
+				ofs := wbnext.block MOD bufferSize;
+				SYSTEM.MOVE(ADDRESSOF(wbnext.buffer[ofs]), ADDRESSOF(wbentry.buffer[ofs]), wbnext.len);
+				INC(len, wbnext.len);
+				INC(i)
+			END;
+			ignore := Sd.Write(card, wbentry.block, len, wbentry.buffer^, 0, res);
+			INC(head, i);
+			DEC(size, i);
+			INC(NbufferWrites);
+			INC(NbufferSize, len)*)
+		END Sync;
+
+		PROCEDURE Stop;
+		BEGIN {EXCLUSIVE}
+(*			stop := TRUE*)
+		END Stop;
+
+	(*BEGIN {ACTIVE, PRIORITY(Objects.Normal)}
+		LOOP
+			BEGIN {EXCLUSIVE}
+				AWAIT(stop OR (size > 0));
+				IF stop THEN EXIT END;
+			END;
+			IF EnableCache THEN Sync; END;
+		END;
+		IF EnableCache THEN Sync; END;*)
+	END Device;
+
+	(** Handle SD Controller Events: create & register a new disk on card insertion, remove disk on card removal *)
+	PROCEDURE HandleSdEvent * (card: Sd.Card; event: LONGINT);
+	VAR
+		disk, prev: Device;
+		name, id: ARRAY 32 OF CHAR;
+		result: LONGINT;
+	BEGIN
+		CASE event OF
+			 Sd.OnInitialization:
+				NEW(disk, card);
+				Strings.IntToStr(diskId, id);
+				name := NameBase;
+				Strings.Append(name, id);
+				disk.SetName(name);
+
+				disk.desc := "SD";
+				CASE card.scr.security OF
+					 Sd.TypeNone, Sd.TypeSDSC:
+					|Sd.TypeSDHC: Strings.Append(disk.desc, "HC")
+					|Sd.TypeSDXC: Strings.Append(disk.desc, "XC")
+				ELSE
+					Strings.Append(disk.desc, "??");
+					KernelLog.String("[SD] unknown card type: "); KernelLog.Int(card.scr.security, 0); KernelLog.Ln;
+				END;
+				Strings.Append(disk.desc, " card, v");
+				CASE card.scr.version OF
+					 Sd.Version1: Strings.Append(disk.desc, "1")
+					|Sd.Version1p1: Strings.Append(disk.desc, "1.10")
+					|Sd.Version2: Strings.Append(disk.desc, "2")
+					|Sd.Version3: Strings.Append(disk.desc, "3")
+					|Sd.Version4: Strings.Append(disk.desc, "4")
+					|Sd.Version5: Strings.Append(disk.desc, "5")
+					|Sd.Version6: Strings.Append(disk.desc, "6")
+				ELSE
+					Strings.Append(disk.desc, "?");
+					KernelLog.String("[SD] unknown card version: "); KernelLog.Int(card.scr.version, 0); KernelLog.Ln;
+				END;
+
+				Disks.registry.Add(disk, result);
+
+				IF result # Plugins.Ok THEN
+					SdEnvironment.String("Error registering disk");
+					SdEnvironment.Ln
+				ELSE
+					INC(diskId);
+					disk.next := devices;
+					devices := disk;
+					SdEnvironment.String("Disk ");
+					SdEnvironment.String(name);
+					SdEnvironment.String(" is now available");
+					SdEnvironment.Ln
+				END
+
+			|Sd.OnRemoval:
+				ASSERT(devices # NIL);
+				IF devices.card = card THEN
+					devices.Stop;
+					SdEnvironment.String("Removed disk ");
+					SdEnvironment.String(devices.name);
+					SdEnvironment.Ln;
+					devices := devices.next
+				ELSE
+					disk := devices;
+					WHILE (disk # NIL) & (disk.card # card) DO
+						prev := disk;
+						disk := disk.next
+					END;
+					ASSERT(disk # NIL);
+					disk.Stop;
+					SdEnvironment.String("Removed disk ");
+					SdEnvironment.String(disk.name);
+					SdEnvironment.Ln;
+					prev.next := disk.next
+				END;
+		END
+	END HandleSdEvent;
+
+VAR
+	devices: Device;
+	diskId: LONGINT;
+
+	(* Statistics *)
+	NcacheHits *, NcacheMiss *, NcacheEvict *, NbufferWrites *, NbufferSize *, NbufferQueueSize *, NbufferQueueSamples *: LONGINT;
+
+	rdTime, wrTime: SdEnvironment.Time;
+
+	PROCEDURE ResetStats *;
+	BEGIN
+
+		TRACE(rdTime, wrTime);
+		rdTime := 0; wrTime := 0;
+
+		NcacheHits := 0;
+		NcacheMiss := 0;
+		NcacheEvict := 0;
+		NbufferWrites := 0;
+		NbufferSize := 0
+	END ResetStats;
+
+END SdDisks.

+ 223 - 0
source/SdInspect.Mod

@@ -0,0 +1,223 @@
+MODULE SdInspect; (** AUTHOR ""; PURPOSE ""; *)
+
+IMPORT
+	BootConfig, Commands, KernelLog, Options,
+	Sd;
+
+VAR
+	hc: Sd.HostController;
+	card: Sd.Card;
+	result: LONGINT;
+
+	(** SdInspect.Command [-a|--acmd] [-d|--data] [-r|--read] [--auto12] [--auto23] [-m|-multi] [-c|--count] [--dma] cmd arg resp rca [datalen] ~ *)
+	PROCEDURE Command * (context: Commands.Context);
+	VAR
+		rsp: ARRAY 32 OF CHAR;
+		opt: Options.Options;
+		command: Sd.Command;
+		datalen, rca: LONGINT;
+		data: POINTER TO ARRAY OF CHAR;
+	BEGIN
+		NEW(opt);
+		opt.Add('d', "data", Options.Flag);
+		opt.Add('r', "read", Options.Flag);
+		opt.Add(0X, "auto12", Options.Flag);
+		opt.Add(0X, "auto23", Options.Flag);
+		opt.Add('m', "multi", Options.Flag);
+		opt.Add('c', "count", Options.Flag);
+		opt.Add(0X, "dma", Options.Flag);
+		opt.Add('a', "acmd", Options.Flag);
+		IF ~opt.Parse(context.arg, context.error) THEN RETURN END;
+
+		command.hc := hc;
+		IF context.arg.GetInteger(command.command, FALSE) THEN
+			IF context.arg.GetInteger(command.argument, TRUE) THEN
+				IF context.arg.GetString(rsp) THEN
+					IF rsp = "R1" THEN
+						command.responseType := Sd.ResponseR1
+					ELSIF rsp = "R1b" THEN
+						command.responseType := Sd.ResponseR1b
+					ELSIF rsp = "R2" THEN
+						command.responseType := Sd.ResponseR2
+					ELSIF rsp = "R3" THEN
+						command.responseType := Sd.ResponseR3
+					ELSIF rsp = "R4" THEN
+						command.responseType := Sd.ResponseR4
+					ELSIF rsp = "R5" THEN
+						command.responseType := Sd.ResponseR5
+					ELSIF rsp = "R6" THEN
+						command.responseType := Sd.ResponseR6
+					ELSIF rsp = "R7" THEN
+						command.responseType := Sd.ResponseR7
+					ELSE
+						context.error.String("Unknown response type ");
+						context.error.String(rsp);
+						context.error.Ln;
+						RETURN
+					END;
+
+					IF context.arg.GetInteger(command.rca, TRUE) THEN
+						(* flags *)
+						IF opt.GetFlag("read") THEN INCL(command.flags, Sd.FlagRead) END;
+						IF opt.GetFlag("auto12") THEN INCL(command.flags, Sd.FlagAutoCmd12) END;
+						IF opt.GetFlag("auto23") THEN INCL(command.flags, Sd.FlagAutoCmd23) END;
+						IF opt.GetFlag("multi") THEN INCL(command.flags, Sd.FlagMultipleBlockTx) END;
+						IF opt.GetFlag("count") THEN INCL(command.flags, Sd.FlagCountBlocks) END;
+						IF opt.GetFlag("dma") THEN INCL(command.flags, Sd.FlagUseDma) END;
+						IF opt.GetFlag("acmd") THEN INCL(command.flags, Sd.FlagApplicationCmd) END;
+						IF opt.GetFlag("data") THEN
+							INCL(command.flags, Sd.FlagData);
+							IF context.arg.GetInteger(datalen, TRUE) THEN
+								NEW(data, datalen);
+								IF hc.execute(command, data^, 0, datalen, result) THEN
+									context.out.String("Command successful");
+									context.out.Ln;
+									context.out.Update;
+									IF Sd.FlagRead IN command.flags THEN
+										KernelLog.String("Data:");
+										KernelLog.Ln;
+										KernelLog.Buffer(data^, 0, datalen)
+									END
+								ELSE
+									context.out.String("Command failed: ");
+									context.out.Int(result, 0);
+									context.out.Ln
+								END
+							ELSE
+								context.error.String("Expected data length");
+								context.error.Ln
+							END
+						ELSE
+							IF Sd.ExecuteCommand(command, result) THEN
+								context.out.String("Command successful");
+								context.out.Ln
+							ELSE
+								context.out.String("Command failed: ");
+								context.out.Int(result, 0);
+								context.out.Ln
+							END
+						END;
+
+						context.out.String("Response: ");
+						CASE command.responseType OF
+							 Sd.ResponseR1, Sd.ResponseR1b:
+								context.out.Update;
+								Sd.PrintCardStatus(Sd.GetR1(command))
+							|Sd.ResponseR2:
+								context.out.Hex(command.response[0], -8);
+								context.out.Char(' ');
+								context.out.Hex(command.response[1], -8);
+								context.out.Char(' ');
+								context.out.Hex(command.response[2], -8);
+								context.out.Char(' ');
+								context.out.Hex(command.response[3], -8);
+								context.out.Char(' ')
+							|Sd.ResponseR3: context.out.Hex(Sd.GetR3(command), -8)
+							|Sd.ResponseR4: context.out.Hex(Sd.GetR4(command), -8)
+							|Sd.ResponseR5: context.out.Hex(Sd.GetR5(command), -8)
+							|Sd.ResponseR6: context.out.Hex(Sd.GetR6(command), -8)
+							|Sd.ResponseR7: context.out.Hex(Sd.GetR7(command), -8)
+						END;
+						context.out.Ln
+					ELSE
+						context.error.String("Expected RCA");
+						context.error.Ln
+					END
+				ELSE
+					context.error.String("Expected response type");
+					context.error.Ln
+				END
+			ELSE
+				context.error.String("Expected argument number");
+				context.error.Ln
+			END
+		ELSE
+			context.error.String("Expected cmd number");
+			context.error.Ln
+		END
+	END Command;
+
+	PROCEDURE Read *;
+	VAR
+		data: ARRAY 512 OF CHAR;
+		res: LONGINT;
+	BEGIN
+		IF ~Sd.Read(card, 0, 512, data, 0, res) THEN
+			KernelLog.String("Read Failed: ");
+			KernelLog.Int(res, 0);
+			KernelLog.Ln
+		ELSE
+			KernelLog.String("Read succeeded");
+			KernelLog.Ln;
+			KernelLog.Buffer(data, 0, 512)
+		END
+	END Read;
+
+	PROCEDURE Reads *;
+	CONST
+		Size = 1024 * 1024;
+	VAR
+		data: ARRAY Size OF CHAR;
+		res: LONGINT;
+	BEGIN
+		IF ~Sd.Read(card, 0, Size, data, 0, res) THEN
+			KernelLog.String("Read Failed: ");
+			KernelLog.Int(res, 0);
+			KernelLog.Ln
+		ELSE
+			KernelLog.String("Read succeeded");
+			KernelLog.Ln;
+			KernelLog.Buffer(data, 0, 512)
+		END
+	END Reads;
+
+	PROCEDURE Write *;
+	VAR
+		data: ARRAY 512 OF CHAR;
+		res: LONGINT;
+	BEGIN
+		IF ~Sd.Write(card, 0, 512, data, 0, res) THEN
+			KernelLog.String("Write Failed: ");
+			KernelLog.Int(res, 0);
+			KernelLog.Ln
+		ELSE
+			KernelLog.String("Write succeeded");
+			KernelLog.Ln;
+			KernelLog.Buffer(data, 0, 512)
+		END
+	END Write;
+
+	PROCEDURE Writes *;
+	VAR
+		data: ARRAY 8 * 512 OF CHAR;
+		res: LONGINT;
+	BEGIN
+		IF ~Sd.Write(card, 0, 8 * 512, data, 0, res) THEN
+			KernelLog.String("Write Failed: ");
+			KernelLog.Int(res, 0);
+			KernelLog.Ln
+		ELSE
+			KernelLog.String("Write succeeded");
+			KernelLog.Ln;
+			KernelLog.Buffer(data, 0, 512)
+		END
+	END Writes;
+
+	PROCEDURE Test *;
+	VAR
+		command: Sd.Command;
+		res: LONGINT;
+	BEGIN
+		command.hc := hc;
+		command.rca := 7;
+		command.command := Sd.CMD_SELECT_DESELECT_CARD;
+		command.argument := 70000H;
+		command.responseType := Sd.ResponseR1b;
+		TRACE(Sd.ExecuteCommand(command, res));
+		TRACE(res)
+	END Test;
+
+BEGIN
+	hc := Sd.New(ADDRESS(0E0100000H), BootConfig.GetIntValue("PsRefClockHz"), NIL, NIL);
+	NEW(card);
+END SdInspect.

+ 113 - 0
source/Zynq.SdEnvironment.Mod

@@ -0,0 +1,113 @@
+MODULE SdEnvironment;
+(**
+	AUTHOR Timothée Martiel, 2015
+	PURPOSE Runtime Environment abstraction for SD Host Controller driver, A2 version
+*)
+
+IMPORT
+	Machine, Objects, KernelLog, Strings, Locks, PsConfig, BootConfig;
+	
+TYPE
+	Time* = HUGEINT; (* Time type *)
+
+VAR
+	InstallHandler *: PROCEDURE (handler: Objects.EventHandler; irq: LONGINT);
+
+	Char *: PROCEDURE (c: CHAR);
+	String *: PROCEDURE (CONST str: ARRAY OF CHAR);
+	Int *: PROCEDURE (i: HUGEINT; w: LONGINT);
+	Hex *: PROCEDURE (i: HUGEINT; w: LONGINT);
+	Address *: PROCEDURE (a: ADDRESS);
+	Set *: PROCEDURE (s: SET);
+	Boolean *: PROCEDURE (b: BOOLEAN);
+	Ln *: PROCEDURE;
+	FlushDCacheRange *,
+	InvalidateDCacheRange *: PROCEDURE (adr: ADDRESS; len: SIZE);
+	
+	GetTimeCounter-: PROCEDURE(): Time;
+	
+	cpuClockHz: LONGINT;
+
+	PROCEDURE Enable * (sd: LONGINT): BOOLEAN;
+	VAR
+		key, value: ARRAY 32 OF CHAR;
+	BEGIN
+		key := "SdEnable";
+		Strings.IntToStr(sd, value);
+		Strings.Append(key, value);
+		Machine.GetConfig(key, value);
+		RETURN value = "1"
+	END Enable;
+
+	PROCEDURE HcClock * (sd: LONGINT): LONGINT;
+	VAR
+		clock, res: LONGINT;
+	BEGIN
+		clock := LONGINT(PsConfig.GetIoClockFrequency(PsConfig.IoSdio, res));
+		ASSERT(res = 0);
+		RETURN clock
+	END HcClock;
+
+	PROCEDURE GetLock * (VAR acq, rel: PROCEDURE {DELEGATE});
+	VAR
+		lock: Locks.Lock;
+	BEGIN
+		NEW(lock);
+		acq := lock.Acquire;
+		rel := lock.Release
+	END GetLock;
+	
+	(** Convert microseconds to time counts *)
+	PROCEDURE FromMicro*(us: Time): Time;
+	BEGIN
+		RETURN us * ENTIERH(0.5D0+LONGREAL(cpuClockHz)/2.0D6);
+	END FromMicro;
+
+	(** Convert time counts to microseconds *)
+	PROCEDURE ToMicro*(time: Time): Time;
+	BEGIN
+		RETURN ENTIERH((0.5D0 + time) / (LONGREAL(cpuClockHz)) * 2.0D6)
+	END ToMicro;
+
+	(** Convert milliseconds to time counts *)
+	PROCEDURE FromMilli*(ms: Time): Time;
+	BEGIN
+		RETURN ms * ENTIERH(0.5D0 + LONGREAL(cpuClockHz)/2.0D3);
+	END FromMilli;
+
+	(** Convert time counts to milliseconds *)
+	PROCEDURE ToMilli*(time: Time): Time;
+	BEGIN
+		RETURN ENTIERH((0.5D0 + time) / (LONGREAL(cpuClockHz)) * 2.0D3)
+	END ToMilli;
+
+	PROCEDURE WaitMilli*(ms: Time);
+	VAR t: Time;
+	BEGIN
+		t := GetTimeCounter() + FromMilli(ms);
+		WHILE GetTimeCounter() <= t DO END;
+	END WaitMilli;
+	
+	PROCEDURE WaitMicro*(us: Time);
+	VAR t: Time;
+	BEGIN
+		t := GetTimeCounter() + FromMicro(us);
+		WHILE GetTimeCounter() <= t DO END;
+	END WaitMicro;
+	
+BEGIN
+	InstallHandler := Objects.InstallHandler;
+	Char := KernelLog.Char;
+	String := KernelLog.String;
+	Int := KernelLog.Int;
+	Hex := KernelLog.Hex;
+	Address := KernelLog.Address;
+	Set := KernelLog.Set;
+	Boolean := KernelLog.Boolean;
+	Ln := KernelLog.Ln;
+	FlushDCacheRange := Machine.FlushDCacheRange;
+	InvalidateDCacheRange := Machine.InvalidateDCacheRange;
+	cpuClockHz := BootConfig.GetIntValue("CpuClockHz");
+	
+	GetTimeCounter := Machine.GetTimer;
+END SdEnvironment.2