changeset 2301:67647617a206

Add MMC/SPI package contribution from eCosCentric
author jlarmour
date Thu, 21 Sep 2006 17:03:53 +0000
parents 654c837cc618
children acae40e4e448
files packages/ChangeLog packages/NEWS packages/devs/disk/generic/mmc/current/ChangeLog packages/devs/disk/generic/mmc/current/cdl/devs_disk_mmc.cdl packages/devs/disk/generic/mmc/current/doc/disk_mmc.sgml packages/devs/disk/generic/mmc/current/include/mmc_protocol.h packages/devs/disk/generic/mmc/current/src/mmc_spi.c packages/ecos.db
diffstat 8 files changed, 1739 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/packages/ChangeLog
+++ b/packages/ChangeLog
@@ -1,3 +1,7 @@
+2006-09-21  Jonathan Larmour  <jifl@eCosCentric.com>
+
+	* ecos.db: add MMC/SPI device driver.
+
 2006-08-06  Bart Veer  <bartv@ecoscentric.com>
 
 	* ecos.db: add M68K/MCF52xx ColdFire I2C driver
--- a/packages/NEWS
+++ b/packages/NEWS
@@ -1,3 +1,4 @@
+* MMC/SPI driver provided by eCosCentric Ltd.
 * USB device driver for Philips D12.
 * Port to ATMEL AT91SAM7X by John Eigelaar
 * Freescale MAC7100 varient and SIva MACE1 platfrom by Ilija Koco.
new file mode 100644
--- /dev/null
+++ b/packages/devs/disk/generic/mmc/current/ChangeLog
@@ -0,0 +1,94 @@
+2006-09-20  Jonathan Larmour  <jifl@eCosCentric.com>
+
+	* src/mmc_spi.c: DISK_FUNS, DISK_CHANNEL and DISK_CONTROLLER are now
+	implicitly static.
+	Update DISK_CHANNEL for new io/disk macro definition.
+
+2006-04-05  John Dallaway  <jld@ecoscentric.com>
+
+	* cdl/devs_disk_mmc.cdl: Add reference to package documentation.
+
+2006-03-17  Jonathan Larmour  <jifl@eCosCentric.com>
+
+	* src/mmc_spi.c (struct cyg_mmc_spi_disk_info_t): Store underlying
+	medium's read/write block length.
+	(mmc_spi_check_for_disk): Store underlying medium's
+	read/write block length.
+	(mmc_spi_disk_lookup): Report medium block size to disk layer.
+	(mmc_spi_disk_read): API now requires sector reads to be with length
+	of sectors not bytes.
+	(mmc_spi_disk_write): Ditto.
+
+2005-10-28  Jonathan Larmour  <jifl@eCosCentric.com>
+
+	* include/mmc_protocol.h: Move from src/ to here.
+	* src/mmc_spi.c: Reflect above move.
+
+2004-08-30  Bart Veer  <bartv@ecoscentric.com>
+
+	* src/mmc_spi.c (mmc_spi_write_disk_block): disable background
+	writes. Some MMC cards don't seem to implement this correctly so
+	if you are trying to talking to another SPI device the MMC card
+	still replies.
+
+2004-07-03  Bart Veer  <bartv@ecoscentric.com>
+
+	* src/mmc_spi.c (MMC_SPI_READ_DATA_TOKEN_RETRIES): increase the
+	number of iterations. With some cards a read can occasionally take
+	50* longer than normal.
+
+2004-07-01  Bart Veer  <bartv@ecoscentric.com>
+
+	* src/mmc_spi.c: reorganize write code, to work around problems
+	with some cards.
+
+2004-06-29  Bart Veer  <bartv@ecoscentric.com>
+
+	* src/mmc_spi.c: some cards require an extra delay during a mount
+	sequence, when doing the first read of a data block. Also added
+	some recovery code to the mount code in case a previous session
+	left the card still sending data.
+
+2004-06-15  Bart Veer  <bartv@ecoscentric.com>
+
+	* src/mmc_spi.c (mmc_spi_check_for_disk): allow platform HALs to
+	customize the mount sequence.
+
+	* doc/disk_mmc.sgml: document the above change.
+	
+2004-04-25  Bart Veer  <bartv@ecoscentric.com>
+
+	* MMC package created
+
+//===========================================================================
+//####ECOSGPLCOPYRIGHTBEGIN####
+// -------------------------------------------
+// This file is part of eCos, the Embedded Configurable Operating System.
+// Copyright (C) 2004, 2005, 2006 eCosCentric Ltd.
+//
+// eCos is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 2 or (at your option) any later version.
+//
+// eCos is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+// for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with eCos; if not, write to the Free Software Foundation, Inc.,
+// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+//
+// As a special exception, if other files instantiate templates or use macros
+// or inline functions from this file, or you compile this file and link it
+// with other works to produce a work based on this file, this file does not
+// by itself cause the resulting work to be covered by the GNU General Public
+// License. However the source code for this file must still be made available
+// in accordance with section (3) of the GNU General Public License.
+//
+// This exception does not invalidate any other reasons why a work based on
+// this file might be covered by the GNU General Public License.
+//
+// -------------------------------------------
+//####ECOSGPLCOPYRIGHTEND####
+//===========================================================================
new file mode 100644
--- /dev/null
+++ b/packages/devs/disk/generic/mmc/current/cdl/devs_disk_mmc.cdl
@@ -0,0 +1,106 @@
+# ====================================================================
+#
+#      devs_disk_mmc.cdl
+#
+#      Support for MMC cards
+#
+# ====================================================================
+#####ECOSGPLCOPYRIGHTBEGIN####
+## -------------------------------------------
+## This file is part of eCos, the Embedded Configurable Operating System.
+## Copyright (C) 2004, 2005, 2006 eCosCentric Limited
+##
+## eCos is free software; you can redistribute it and/or modify it under
+## the terms of the GNU General Public License as published by the Free
+## Software Foundation; either version 2 or (at your option) any later version.
+##
+## eCos is distributed in the hope that it will be useful, but WITHOUT ANY
+## WARRANTY; without even the implied warranty of MERCHANTABILITY or
+## FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+## for more details.
+##
+## You should have received a copy of the GNU General Public License along
+## with eCos; if not, write to the Free Software Foundation, Inc.,
+## 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+##
+## As a special exception, if other files instantiate templates or use macros
+## or inline functions from this file, or you compile this file and link it
+## with other works to produce a work based on this file, this file does not
+## by itself cause the resulting work to be covered by the GNU General Public
+## License. However the source code for this file must still be made available
+## in accordance with section (3) of the GNU General Public License.
+##
+## This exception does not invalidate any other reasons why a work based on
+## this file might be covered by the GNU General Public License.
+## -------------------------------------------
+#####ECOSGPLCOPYRIGHTEND####
+# ====================================================================
+######DESCRIPTIONBEGIN####
+#
+# Author(s):      bartv,jlarmour
+# Date:           2004-04-25
+#
+#####DESCRIPTIONEND####
+# ====================================================================
+
+cdl_package CYGPKG_DEVS_DISK_MMC {
+    display     "Disk driver for MMC cards"
+    doc         ecos-ref/devs-disk-mmc-part.html
+
+    include_dir cyg/io
+    
+    parent      CYGPKG_IO_DISK_DEVICES
+    active_if   CYGPKG_IO_DISK
+
+    cdl_interface CYGINT_DEVS_DISK_MMC_SPI_CONNECTORS {
+	active_if	CYGPKG_IO_SPI
+	display		"Number of MMC connectors accessed via SPI"
+    }
+
+    cdl_component CYGPKG_DEVS_DISK_MMC_SPI {
+	display		"Access an MMC card via an SPI bus"
+	default_value	{ CYGINT_DEVS_DISK_MMC_SPI_CONNECTORS > 0 }
+	compile		-library=libextras.a mmc_spi.c
+	requires	CYGPKG_ERROR CYGPKG_LIBC_STRING 
+	description "
+            This option enables support for accessing an MMC card via an
+            SPI bus."
+	
+	define_proc {
+	    puts $::cdl_system_header "/***** MMC/SPI disk driver output start *****/"
+            puts $::cdl_system_header "#ifndef CYGDAT_DEVS_DISK_CFG"
+	    puts $::cdl_system_header "#define CYGDAT_DEVS_DISK_CFG <pkgconf/devs_disk_mmc.h>"
+            puts $::cdl_system_header "#endif"
+	    puts $::cdl_system_header "/***** MMC/SPI disk driver output end *****/"
+	}
+
+	cdl_option CYGDAT_DEVS_DISK_MMC_SPI_DISK0_NAME {
+	    display		"Device name for the MMC/SPI disk 0 device"
+	    flavor		data
+	    default_value	{ "\"/dev/hd0/\"" }
+	    description "
+                This is the device name used to access the raw disk device
+                in eCos, for example for mount operations. Note that the
+                trailing slash must be present."
+
+	    # Testing support. For now just hard-code some values for partition 0
+	    # on the card.
+	    define_proc {
+		puts $::cdl_header "#define CYGDAT_DEVS_DISK_TEST_DEVICE CYGDAT_DEVS_DISK_MMC_SPI_DISK0_NAME \"1\""
+		puts $::cdl_header "#define CYGDAT_DEVS_DISK_TEST_MOUNTPOINT \"/dosfs\""
+		puts $::cdl_header "#define CYGDAT_DEVS_DISK_TEST_DIRECTORY  \"/test\""
+	    }
+	}
+	
+	cdl_option CYGIMP_DEVS_DISK_MMC_SPI_POLLED {
+	    display		"Run the driver in polled mode rather than interrupt-driven"
+	    default_value	!CYGPKG_KERNEL
+	    description "
+                By default the MMC disk driver will operate in interrupt-driven
+                mode if the kernel is present, i.e. if the application is likely
+                to be multi-threaded. Otherwise it will operate in polled mode."
+	}
+    }
+}
+
+# EOF devs_disk_mmc.cdl
new file mode 100644
--- /dev/null
+++ b/packages/devs/disk/generic/mmc/current/doc/disk_mmc.sgml
@@ -0,0 +1,214 @@
+<!-- DOCTYPE refentry  PUBLIC "-//OASIS//DTD DocBook V3.1//EN" -->
+
+<!-- {{{ Banner                         -->
+
+<!-- =============================================================== -->
+<!--                                                                 -->
+<!--     disk_mmc.sgml                                               -->
+<!--                                                                 -->
+<!--     Documentation for the MMC disk device driver                -->
+<!--                                                                 -->
+<!-- =============================================================== -->
+<!-- ####COPYRIGHTBEGIN####                                          -->
+<!--                                                                 -->
+<!-- =============================================================== -->
+<!-- Copyright (C) 2004 eCosCentric Limited                          -->
+<!-- This material may be distributed only subject to the terms      -->
+<!-- and conditions set forth in the Open Publication License, v1.0  -->
+<!-- or later (the latest version is presently available at          -->
+<!-- http://www.opencontent.org/openpub/)                            -->
+<!-- Distribution of the work or derivative of the work in any       -->
+<!-- standard (paper) book form is prohibited unless prior           -->
+<!-- permission obtained from the copyright holder                   -->
+<!-- =============================================================== -->
+<!--                                                                 -->      
+<!-- ####COPYRIGHTEND####                                            -->
+<!-- =============================================================== -->
+<!-- =============================================================== -->
+<!-- #####DESCRIPTIONBEGIN####                                       -->
+<!--                                                                 -->
+<!-- Author(s):   bartv                                              -->
+<!-- Contact(s):  bartv                                              -->
+<!-- Date:        2004/04/25                                         -->
+<!-- Version:     0.01                                               -->
+<!--                                                                 -->
+<!-- ####DESCRIPTIONEND####                                          -->
+<!-- =============================================================== -->
+
+<!-- }}} -->
+
+<part id="devs-disk-mmc-part"><title>MMC MultiMedia Card Disk Driver</title>
+
+<refentry id="devs-disk-mmc">
+  <refmeta>
+    <refentrytitle>Device Driver for MMC MultiMedia Cards</refentrytitle>
+  </refmeta>
+  <refnamediv>
+    <refname><varname>CYGPKG_DEVS_DISK_MMC</varname></refname>
+    <refpurpose>eCos Support for MMC MultiMedia Cards</refpurpose>
+  </refnamediv>
+
+  <refsect1 id="devs-disk-mmc-description"><title>Description</title>
+    <para>
+This package provides a disk device driver for MultiMediaCards (MMC).
+A MultiMediaCard provides non-volatile storage in a small footprint
+(24mm &ast; 32mm &ast; 1.4mm), and weighing less than 2 grams. Typical
+card sizes are 16MB to 128MB, with an upper limit of 4GB. It
+should be noted that these sizes are measured in millions of bytes,
+not 2^20. The <ulink url="http://www.mmca.org">MultiMediaCard
+Association</ulink> defines the standard for these cards.
+    </para>
+    <para>
+At the hardware level there are two ways of accessing an MMC card,
+either using a custom interface or via an SPI bus. A card will detect
+the interface in use at run-time. The custom interface allows for
+better performance but requires additional hardware. Currently only
+SPI mode is supported by this package.
+    </para>
+    <para>
+Theoretically an MMC card can be used with any file system. In
+practice all cards are formatted for PC compatibility, with a
+partition table in the first block and a single FAT file system on the
+rest of the card. Currently this package checks the format of the MMC
+card and will only allow access to a card if it is formatted this way.
+    </para>
+    <para>
+An MMC socket allows cards to be removed and inserted at any time. The
+device driver will detect removal events when the next I/O operation
+happens and will report them to higher-level code via an error code
+<literal>ENODEV</literal>. However there are no guarantees that the
+higher-level code will be able to recover from this error. The
+expected usage is that application code will explicitly
+<function>mount</function> the card before attempting any file I/O,
+and will <function>umount</function> the card before it is removed.
+Between these operations the system is likely to keep some disk blocks
+cached, for performance reasons. If the card is removed before the
+<function>umount</function> then it may end up with a corrupted file
+system, and the disk subsystem may be left in an inconsistent state.
+Regular uses of <function>sync</function> will reduce the risk
+of file system corruption.
+    </para>
+  </refsect1>
+
+  <refsect1 id="devs-disk-mmc-config"><title>Configuration Options</title>
+    <para>
+<varname>CYGPKG_DEVS_DISK_MMC</varname> is a hardware package which
+should get loaded automatically when you configure for a suitable eCos
+target platform. In this case suitable means that the hardware has an
+MMC socket connected to an SPI bus, that an SPI bus driver package
+exists and is also automatically loaded, and that the platform HAL
+provides <link linkend="devs-disk-mmc-porting">information</link>
+on how the card is connected to the SPI bus.
+    </para>
+    <para>
+The package depends on support from the generic disk package
+<varname>CYGPKG_IO_DISK</varname>. That will not be loaded
+automatically: the presence of an MMC socket on the board does not
+mean that the application has any need for a file system. Hence by
+default <varname>CYGPKG_DEVS_DISK_MMC</varname> will be inactive and
+will not contribute any code or data to the application's memory
+footprint. To activate the driver it will be necessary to add one or
+more packages to the configuration using
+<command>ecosconfig&nbsp;add</command> or the graphical configuration
+tool: the generic disk support <varname>CYGPKG_IO_DISK</varname>;
+usually a file system, <varname>CYGPKG_FS_FAT</varname>; support for
+the file I/O API <varname>CYGPKG_IO_FILEIO</varname>; and possibly
+additional support packages that may be needed by the file system, for
+example <varname>CYGPKG_LINUX_COMPAT</varname>. Depending on the
+template used to create the initial configuration some of these may be
+loaded already.
+    </para>
+    <para>
+The package provides two main configuration options.
+<varname>CYGDAT_DEVS_DISK_MMC_SPI_DISK0_NAME</varname> specifies the
+name of the raw disk device, for example <literal>/dev/hd0</literal>.
+Allowing for partition tables that makes <literal>/dev/hd0/1</literal>
+the first argument that shoul be passed to a
+<function>mount</function> call. If the hardware has multiple disk
+devices then each one will need a unique name.
+<varname>CYGIMP_DEVS_DISK_MMC_SPI_POLLED</varname> controls whether
+the SPI bus will be accessed in interrupt-driven or polled mode. It
+will default to interrupt-driven if the application is multi-threaded,
+which is assumed to be the case if the kernel is present. If the
+kernel is absent, for example in a RedBoot configuration, then the
+driver will default to polled mode. With some hardware polled mode may
+significantly increase disk throughput even in a multi-threaded
+application, but will consume cpu cycles that could be used by other
+threads.
+    </para>
+  </refsect1>
+  
+  <refsect1 id="devs-disk-mmc-extra"><title>Additional Functionality</title>
+    <para>
+The disk driver package exports a variable
+<varname>cyg_mmc_spi_polled</varname>. This defaults to true or false
+depending on the configuration option
+<varname>CYGIMP_DEVS_DISK_MMC_SPI_POLLED</varname>. If the default
+mode is interrupt-driven then file I/O, including mount operations,
+are only allowed when the scheduler has started and interrupts have
+been enabled. Any attempts at file I/O earlier during system
+initialization, for example inside a C++ static constructor, will lock
+up. If it is necessary to perform file I/O at this time then the
+driver can be temporarily switched to polling mode before the I/O
+operation by setting <varname>cyg_mmc_spi_polled</varname>, and
+clearing it again after the I/O. Alternatively the default mode can be
+changed to polling by editing the configuration, and then the
+<function>main()</function> thread can change the mode to
+interrupt-driven once the scheduler has started.
+    </para>
+  </refsect1>
+
+  <refsect1 id="devs-disk-mmc-porting"><title>Porting to New Hardware</title>
+    <para>
+Assuming that the MMC connector is hooked up to a standard SPI bus and
+that there is already an eCos SPI bus driver, porting the MMC disk
+driver package should be straightforward. Some other package, usually
+the platform HAL, should provide a <type>cyg_spi_device</type>
+structure <varname>cyg_spi_mmc_dev0</varname>. That structure contains
+the information needed by this package to interact with the MMC card
+via the usual SPI interface, for example how to activate the
+appropriate chip select. The platform HAL should also implement the
+CDL interface <varname>CYGINT_DEVS_DISK_MMC_SPI_CONNECTORS</varname>.
+    </para>
+    <para>
+When defining <varname>cyg_spi_mmc_dev0</varname> special care must be
+taken with the chip select. The MMC protocol is transaction-oriented.
+For example a read operation involves an initial command sent to the
+card, then a reply, then the actual data, and finally a checksum. The
+card's chip select must be kept asserted for the entire operation, and
+there can be no interactions with other devices on the same SPI bus
+during this time.
+    </para>
+    <para>
+Optionally the platform HAL may define a macro
+<function>HAL_MMC_SPI_INIT</function> which will be invoked during a
+mount operation. This can take any hardware-specific actions that may
+be necessary, for example manipulating GPIO pins. Usually no such
+macro is needed because the hardware is set up during platform
+initialization.
+    </para>
+    <para>
+Currently the package does not provide any support for accessing MMC
+cards using an interface other than SPI. On some targets there may be
+additional hardware to detect events such as card insertion or
+removal, but there is no support for exploiting such hardware at
+present.
+    </para>
+    <para>
+Only a single MMC socket is supported. Extending the package to
+support multiple sockets would be straightforward but it seems
+unlikely that any hardware would come with multiple MMC sockets. Given
+the nature of SPI buses there is a problem if the MMC socket is hooked
+up via an expansion connector rather than being attached to the main
+board. The platform HAL would not know about the socket so would not
+implement the CDL interface
+<varname>CYGINT_DEVS_DISK_MMC_SPI_CONNECTORS</varname>, and the
+<database>ecos.db</database> target entry would not include
+<varname>CYGPKG_DEVS_DISK_MMC</varname>. Because this is a hardware
+package it cannot easily be added by hand. Instead this scenario would
+require some editing of the existing platform HAL and target entry.
+    </para>
+  </refsect1>
+
+</refentry>  
+</part>
new file mode 100644
--- /dev/null
+++ b/packages/devs/disk/generic/mmc/current/include/mmc_protocol.h
@@ -0,0 +1,175 @@
+#ifndef CYGONCE_DEVS_DISK_MMC_PROTOCOL_H
+#define CYGONCE_DEVS_DISK_MMC_PROTOCOL_H
+//==========================================================================
+//
+//      mmc_protocol.h
+//
+//      Define the protocol used for interacting with MMC cards
+//
+//===========================================================================
+//####ECOSGPLCOPYRIGHTBEGIN####
+// -------------------------------------------
+// This file is part of eCos, the Embedded Configurable Operating System.
+// Copyright (C) 2004, 2005 eCosCentric Limited
+//
+// eCos is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 2 or (at your option) any later version.
+//
+// eCos is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+// for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with eCos; if not, write to the Free Software Foundation, Inc.,
+// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+//
+// As a special exception, if other files instantiate templates or use macros
+// or inline functions from this file, or you compile this file and link it
+// with other works to produce a work based on this file, this file does not
+// by itself cause the resulting work to be covered by the GNU General Public
+// License. However the source code for this file must still be made available
+// in accordance with section (3) of the GNU General Public License.
+//
+// This exception does not invalidate any other reasons why a work based on
+// this file might be covered by the GNU General Public License.
+//
+// -------------------------------------------
+//####ECOSGPLCOPYRIGHTEND####
+//===========================================================================
+//#####DESCRIPTIONBEGIN####
+//
+// Author:       bartv
+// Contributors: jlarmour
+// Date:         2004-04-25
+// Usage:        Only this package and implementing device drivers should
+//               include this header file.
+//
+//####DESCRIPTIONEND####
+//==========================================================================
+
+#include <cyg/infra/cyg_type.h>       /* Common types */
+
+// The MMC command set. A request is a six-byte sequence. First a
+// single command byte, one of the following with bit 6 (0x40) or'd in
+// and bit 7 clear as a start bit. Then a four-byte argument, usually
+// a 32-bit integer most significant byte first. Finally a 7-bit CRC
+// and a stop bit. In SPI mode the CRC is usually optional, except for
+// the GO_IDLE_STATE command.
+//
+// Commands ALL_SEND_CID, SEND_RELATIVE_ADDR, SET_DSR,
+// SELECT_DESELECT, READ_DATA_UNTIL_STOP, STOP_TRANSMISSION,
+// GO_INACTIVE, READ_MULTIPLE_BLOCK, WRITE_DATA_UNTIL_STOP,
+// WRITE_MULTIPLE_BLOCK, and PROGRAM_CID are MMC only, and not
+// available in SPI mode.
+//
+// Device drivers may use values with bit 7 set to indicate
+// driver-specific commands.
+
+#define MMC_REQUEST_GO_IDLE_STATE           0x00
+#define MMC_REQUEST_SEND_OP_COND            0x01
+#define MMC_REQUEST_ALL_SEND_CID            0x02
+#define MMC_REQUEST_SEND_RELATIVE_ADDR      0x03
+#define MMC_REQUEST_SET_DSR                 0x04
+#define MMC_REQUEST_SELECT_DESELECT         0x07
+#define MMC_REQUEST_SEND_CSD                0x09
+#define MMC_REQUEST_SEND_CID                0x0A
+#define MMC_REQUEST_READ_DATA_UNTIL_STOP    0x0B
+#define MMC_REQUEST_STOP_TRANSMISSION       0x0C
+#define MMC_REQUEST_SEND_STATUS             0x0D
+#define MMC_REQUEST_GO_INACTIVE             0x0F
+#define MMC_REQUEST_SET_BLOCKLEN            0x10
+#define MMC_REQUEST_READ_SINGLE_BLOCK       0x11
+#define MMC_REQUEST_READ_MULTIPLE_BLOCK     0x12
+#define MMC_REQUEST_WRITE_DATA_UNTIL_STOP   0x14
+#define MMC_REQUEST_WRITE_BLOCK             0x18
+#define MMC_REQUEST_WRITE_MULTIPLE_BLOCK    0x19
+#define MMC_REQUEST_PROGRAM_CID             0x1A
+#define MMC_REQUEST_PROGRAM_CSD             0x1B
+#define MMC_REQUEST_SET_WRITE_PROT          0x1C
+#define MMC_REQUEST_CLR_WRITE_PROT          0x1D
+#define MMC_REQUEST_SEND_WRITE_PROT         0x1E
+#define MMC_REQUEST_TAG_SECTOR_START        0x20
+#define MMC_REQUEST_TAG_SECTOR_END          0x21
+#define MMC_REQUEST_UNTAG_SECTOR            0x22
+#define MMC_REQUEST_TAG_ERASE_GROUP_START   0x23
+#define MMC_REQUEST_TAG_ERASE_GROUP_END     0x24
+#define MMC_REQUEST_UNTAG_ERASE_GROUP       0x25
+#define MMC_REQUEST_ERASE                   0x26
+#define MMC_REQUEST_LOCK_UNLOCK             0x2A
+#define MMC_REQUEST_READ_OCR                0x3A
+#define MMC_REQUEST_CRC_ON_OFF              0x3B
+
+// Response formats are different for MMC vs. SPI mode, so are defined
+// in the appropriate source files.
+
+// The CID register is generally treated as an opaque data structure
+// used only for unique identification of cards.
+typedef struct mmc_cid_register {
+    cyg_uint8   cid_data[16];
+} mmc_cid_register;
+
+#define MMC_CID_REGISTER_MID(_data_)                    ((_data_)->cid_data[0])
+#define MMC_CID_REGISTER_OID(_data_)                    (((_data_)->cid_data[1] << 8) | \
+                                                         ((_data_)->cid_data[2]))
+#define MMC_CID_REGISTER_PNM(_data_)                    ((const char*)&((_data_)->cid_data[3]))
+#define MMC_CID_REGISTER_PRV(_data_)                    ((_data_)->cid_data[9])
+#define MMC_CID_REGISTER_PSN(_data_)                    (((_data_)->cid_data[10] << 24) | \
+                                                         ((_data_)->cid_data[11] << 16) | \
+                                                         ((_data_)->cid_data[12] <<  8) | \
+                                                         ((_data_)->cid_data[13]))
+#define MMC_CID_REGISTER_MDT(_data_)                    ((_data_)->cid_data[14])
+#define MMC_CID_REGISTER_CRC(_data_)                    ((_data_)->cid_data[15] >> 1)
+                                                         
+
+// The CSD register is just lots of small bitfields. For now keep it
+// as an array of 16 bytes and provide macros to read the fields.
+typedef struct mmc_csd_register {
+    cyg_uint8   csd_data[16];
+} mmc_csd_register;
+
+#define MMC_CSD_REGISTER_CSD_STRUCTURE(_data_)          (((_data_)->csd_data[0] & 0x00C0) >> 6)
+#define MMC_CSD_REGISTER_SPEC_VERS(_data_)              (((_data_)->csd_data[0] & 0x003C) >> 2)
+#define MMC_CSD_REGISTER_TAAC_MANTISSA(_data_)          (((_data_)->csd_data[1] & 0x0078) >> 3)
+#define MMC_CSD_REGISTER_TAAC_EXPONENT(_data_)          ((_data_)->csd_data[1] & 0x0007)
+#define MMC_CSD_REGISTER_NSAC(_data_)                   ((_data_)->csd_data[2])
+#define MMC_CSD_REGISTER_TRAN_SPEED_MANTISSA(_data_)    (((_data_)->csd_data[3] & 0x0078) >> 3)
+#define MMC_CSD_REGISTER_TRAN_SPEED_EXPONENT(_data_)    ((_data_)->csd_data[3] & 0x0007)
+#define MMC_CSD_REGISTER_CCC(_data_)                    (((_data_)->csd_data[4] << 4) | (((_data_)->csd_data[5] & 0x00F0) >> 4))
+#define MMC_CSD_REGISTER_READ_BL_LEN(_data_)            ((_data_)->csd_data[5] & 0x000F)
+#define MMC_CSD_REGISTER_READ_BL_PARTIAL(_data_)        (((_data_)->csd_data[6] & 0x0080) >> 7)
+#define MMC_CSD_REGISTER_WRITE_BLK_MISALIGN(_data_)     (((_data_)->csd_data[6] & 0x0040) >> 6)
+#define MMC_CSD_REGISTER_READ_BLK_MISALIGN(_data_)      (((_data_)->csd_data[6] & 0x0020) >> 5)
+#define MMC_CSD_REGISTER_DSR_IMP(_data_)                (((_data_)->csd_data[6] & 0x0010) >> 4)
+#define MMC_CSD_REGISTER_C_SIZE(_data_)                 ((((_data_)->csd_data[6] & 0x0003) << 10) |     \
+                                                          ((_data_)->csd_data[7] << 2)            |     \
+                                                          (((_data_)->csd_data[8] & 0x00C0) >> 6))
+#define MMC_CSD_REGISTER_VDD_R_CURR_MIN(_data_)         (((_data_)->csd_data[8] & 0x0038)  >> 3)
+#define MMC_CSD_REGISTER_VDD_R_CURR_MAX(_data_)         ((_data_)->csd_data[8] & 0x0007)
+#define MMC_CSD_REGISTER_VDD_W_CURR_MIN(_data_)         (((_data_)->csd_data[9] & 0x00E0) >> 5)
+#define MMC_CSD_REGISTER_VDD_W_CURR_MAX(_data_)         (((_data_)->csd_data[9] & 0x001C) >> 2)
+#define MMC_CSD_REGISTER_C_SIZE_MULT(_data_)            ((((_data_)->csd_data[9] & 0x0003) << 1) |      \
+                                                         (((_data_)->csd_data[10] & 0x0080) >> 7))
+#define MMC_CSD_REGISTER_SECTOR_SIZE(_data_)            (((_data_)->csd_data[10] & 0x007C) >> 2)
+#define MMC_CSD_REGISTER_ERASE_GRP_SIZE(_data_)         ((((_data_)->csd_data[10] & 0x0003) << 3) |     \
+                                                         (((_data_)->csd_data[11] & 0x00E0) >> 5))
+#define MMC_CSD_REGISTER_WR_GRP_SIZE(_data_)            ((_data_)->csd_data[11] & 0x001F)
+#define MMC_CSD_REGISTER_WR_GRP_ENABLE(_data_)          (((_data_)->csd_data[12] & 0x0080) >> 7)
+#define MMC_CSD_REGISTER_DEFAULT_ECC(_data_)            (((_data_)->csd_data[12] & 0x0060) >> 5)
+#define MMC_CSD_REGISTER_R2W_FACTOR(_data_)             (((_data_)->csd_data[12] & 0x001C) >> 2)
+#define MMC_CSD_REGISTER_WRITE_BL_LEN(_data_)           ((((_data_)->csd_data[12] & 0x0003) << 2) |     \
+                                                         (((_data_)->csd_data[13] & 0x00C0) >> 6))
+#define MMC_CSD_REGISTER_WR_BL_PARTIAL(_data_)          (((_data_)->csd_data[13] & 0x0020) >> 5)
+#define MMC_CSD_REGISTER_FILE_FORMAT_GROUP(_data_)      (((_data_)->csd_data[14] & 0x0080) >> 7)
+#define MMC_CSD_REGISTER_COPY(_data_)                   (((_data_)->csd_data[14] & 0x0040) >> 6)
+#define MMC_CSD_REGISTER_PERM_WRITE_PROTECT(_data_)     (((_data_)->csd_data[14] & 0x0020) >> 5)
+#define MMC_CSD_REGISTER_TMP_WRITE_PROTECT(_data_)      (((_data_)->csd_data[14] & 0x0010) >> 4)
+#define MMC_CSD_REGISTER_FILE_FORMAT(_data_)            (((_data_)->csd_data[14] & 0x000C) >> 2)
+#define MMC_CSD_REGISTER_ECC(_data_)                    ((_data_)->csd_data[14] & 0x0003)
+#define MMC_CSD_REGISTER_CRC(_data_)                    (((_data_)->csd_data[15] & 0xFE) >> 1)
+
+
+#endif //  ifdef CYGONCE_DEVS_DISK_MMC_PROTOCOL_H
+
+/* EOF mmc_protocol.h */
new file mode 100644
--- /dev/null
+++ b/packages/devs/disk/generic/mmc/current/src/mmc_spi.c
@@ -0,0 +1,1137 @@
+//==========================================================================
+//
+//      mmc_spi.c
+//
+//      Provide a disk device driver for MMC cards over SPI
+//
+//==========================================================================
+//####ECOSGPLCOPYRIGHTBEGIN####
+// -------------------------------------------
+// This file is part of eCos, the Embedded Configurable Operating System.
+// Copyright (C) 2004, 2006 eCosCentric Limited
+//
+// eCos is free software; you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 2 or (at your option) any later version.
+//
+// eCos is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+// for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with eCos; if not, write to the Free Software Foundation, Inc.,
+// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+//
+// As a special exception, if other files instantiate templates or use macros
+// or inline functions from this file, or you compile this file and link it
+// with other works to produce a work based on this file, this file does not
+// by itself cause the resulting work to be covered by the GNU General Public
+// License. However the source code for this file must still be made available
+// in accordance with section (3) of the GNU General Public License.
+//
+// This exception does not invalidate any other reasons why a work based on
+// this file might be covered by the GNU General Public License.
+//
+// -------------------------------------------
+//####ECOSGPLCOPYRIGHTEND####
+//==========================================================================
+//#####DESCRIPTIONBEGIN####
+//
+// Author:       bartv
+// Date:         2004-04-25
+//
+//####DESCRIPTIONEND####
+//==========================================================================
+
+#include <pkgconf/system.h>
+#include <pkgconf/devs_disk_mmc.h>
+#include <cyg/infra/cyg_type.h>
+#include <cyg/infra/cyg_ass.h>
+#include <cyg/infra/diag.h>
+#include <cyg/hal/hal_arch.h>
+#include <cyg/hal/hal_intr.h>
+#include <string.h>
+#include <errno.h>
+#include <cyg/io/io.h>
+#include <cyg/io/spi.h>
+#include <cyg/io/devtab.h>
+#include <cyg/io/disk.h>
+#include <cyg/io/mmc_protocol.h>
+
+// Communication parameters. First some debug support
+#define DEBUG   0
+#if DEBUG > 0
+# define DEBUG1(format, ...)    diag_printf(format, ## __VA_ARGS__)
+#else
+# define DEBUG1(format, ...)
+#endif
+#if DEBUG > 1
+# define DEBUG2(format, ...)    diag_printf(format, ## __VA_ARGS__)
+#else
+# define DEBUG2(format, ...)
+#endif
+
+// Should the SPI operations run in polled or interrupt-driven mode?
+// The default value is determined by CDL, but can be overridden at
+// run-time if necessary. For example if configured for
+// interrupt-driven I/O then it will be impossible to perform disk
+// operations during system initialization, e.g. from a static
+// constructor, unless this flag is changed for the duration.
+#ifdef CYGIMP_DEVS_DISK_MMC_SPI_POLLED
+cyg_bool    cyg_mmc_spi_polled = true;
+#else
+cyg_bool    cyg_mmc_spi_polled = false;
+#endif
+
+// Should write operations be allowed to complete in the background,
+// or must the operation complete in the foreground. The latter
+// requires polling for potentially a long time, up to some 100's of
+// milliseconds, but the former appears unreliable if there are other
+// devices on the SPI bus. In theory the MMC card should detect that
+// the chip select line is dropped and tristate the output line, but
+// in practice this does not always happen.
+#undef MMC_SPI_BACKGROUND_WRITES
+
+// The size of each disk block
+#define MMC_SPI_BLOCK_SIZE      512
+// The number of retries during a mount operation when switching to
+// IDLE mode.
+#define MMC_SPI_GO_IDLE_RETRIES 16
+// The number of retries during a mount operation when switching from
+// idle to operational
+#define MMC_SPI_OP_COND_RETRIES 128
+// The number of retries when waiting for a response to any command
+#define MMC_SPI_COMMAND_RETRIES 32
+// Retries when waiting for a data response token during a read
+#define MMC_SPI_READ_DATA_TOKEN_RETRIES 32768
+// Retries during a write while waiting for completion
+#define MMC_SPI_WRITE_BUSY_RETRIES 32768
+
+// ----------------------------------------------------------------------------
+// SPI-specific parts of the MMC protocol.
+//
+// The main response byte. 0 indicates success, other bits
+// indicate various error conditions.
+#define MMC_REPLY_SUCCESS                   0x00
+#define MMC_REPLY_PARAMETER_ERROR           (0x01 << 6)
+#define MMC_REPLY_ADDRESS_ERROR             (0x01 << 5)
+#define MMC_REPLY_ERASE_SEQUENCE_ERROR      (0x01 << 4)
+#define MMC_REPLY_COM_CRC_ERROR             (0x01 << 3)
+#define MMC_REPLY_ILLEGAL_COMMAND           (0x01 << 2)
+#define MMC_REPLY_ERASE_RESET               (0x01 << 1)
+#define MMC_REPLY_IN_IDLE_STATE             (0x01 << 0)
+
+// A send-status command generates a second response byte
+#define MMC_REPLY2_OUT_OF_RANGE             (0x01 << 7)
+#define MMC_REPLY2_ERASE_PARAM              (0x01 << 6)
+#define MMC_REPLY2_WP_VIOLATION             (0x01 << 5)
+#define MMC_REPLY2_CARD_ECC_FAILED          (0x01 << 4)
+#define MMC_REPLY2_CC_ERROR                 (0x01 << 3)
+#define MMC_REPLY2_ERROR                    (0x01 << 2)
+#define MMC_REPLY2_WP_ERASE_SKIP            (0x01 << 1)
+// Alias for the above
+#define MMC_REPLY2_LOCK_UNLOCK_FAILED       (0x01 << 1)
+#define MMC_REPLY2_CARD_LOCKED              (0x01 << 0)
+
+// The data error token byte which may get sent if a read
+// operation fails. The top 3 bits will be 0. A successful
+// response will have these bits 1.
+#define MMC_DATA_TOKEN_SUCCESS                  (0x00FE)
+#define MMC_DATA_ERROR_TOKEN_CARD_LOCKED        (0x01 << 4)
+#define MMC_DATA_ERROR_TOKEN_OUT_OF_RANGE       (0x01 << 3)
+#define MMC_DATA_ERROR_TOKEN_CARD_ECC_FAILED    (0x01 << 2)
+#define MMC_DATA_ERROR_TOKEN_CC_ERROR           (0x01 << 1)
+#define MMC_DATA_ERROR_TOKEN_ERROR              (0x01 << 0)
+
+// ----------------------------------------------------------------------------
+// Structures and statics.
+//
+// There should be an SPI device cyg_spi_mmc_dev0, probably provided by
+// the HAL, possibly by the application. Because of the latter we cannot
+// assume the variable will be defined in a header.
+extern cyg_spi_device   cyg_spi_mmc_dev0;
+
+// When retrieving data it is necessary to send an 0xff byte stream,
+// which the card will not confuse with further commands. The largest
+// transfer is 512 bytes, too large a buffer to place on the stack.
+static cyg_uint8        mmc_spi_ff_data[512];
+#define MMC_SPI_INIT_FF_DATA()              \
+    CYG_MACRO_START                         \
+    memset(mmc_spi_ff_data, 0x00FF, 512);   \
+    CYG_MACRO_END
+
+// Details of a specific MMC card
+typedef struct cyg_mmc_spi_disk_info_t {
+    cyg_spi_device*     mmc_spi_dev;
+    cyg_uint32          mmc_saved_baudrate;
+    cyg_uint32          mmc_block_count;
+#ifdef MMC_SPI_BACKGROUND_WRITES    
+    cyg_bool            mmc_writing;
+#endif    
+    cyg_bool            mmc_read_only;
+    cyg_bool            mmc_connected;
+    cyg_uint32          mmc_heads_per_cylinder;
+    cyg_uint32          mmc_sectors_per_head;
+    cyg_uint32          mmc_read_block_length;
+    cyg_uint32          mmc_write_block_length;
+    mmc_cid_register    mmc_id;
+} cyg_mmc_spi_disk_info_t;
+
+// There is no need for a hardware-specific disk controller structure.
+// The closest equivalent is probably an SPI bus, i.e. if there were
+// MMC connectors attached to different SPI buses then these would
+// have separate controllers with independent locking. However that
+// can be handled without a cyg_mmc_spi_controller_info_t structure.
+
+// ----------------------------------------------------------------------------
+// The low-level MMC operations
+
+// After power-on an MMC card is in idle state and needs at least 74
+// clock cycles before any communication. These might be supplied
+// courtesy of another SPI device, but no guarantees, so generate some
+// ticks.
+static void
+mmc_spi_send_init(cyg_mmc_spi_disk_info_t* disk)
+{
+    cyg_spi_tick(disk->mmc_spi_dev, cyg_mmc_spi_polled, 10);
+}
+
+// Send the first part of a command sequence. This consists of the
+// command itself, an argument, a CRC, and then waiting for a
+// reply byte from the card.
+static cyg_uint32
+mmc_spi_send_command_start(cyg_mmc_spi_disk_info_t* disk, cyg_uint32 command, cyg_uint32 arg)
+{
+    cyg_spi_device* dev = disk->mmc_spi_dev;
+    cyg_uint8       request[7];
+    cyg_uint8       response[7];
+    cyg_uint8       reply;
+    int             i;
+
+#ifdef MMC_SPI_BACKGROUND_WRITES    
+    // If the last operation was a block write, those can take a while
+    // to complete. Rather than wait at the end of the write(), do so
+    // at the beginning of the next operation i.e. here. This also
+    // allows the chip select to be dropped while the write comples,
+    // so communication is possible with other devices. The polling is
+    // done as a sequence of transactions rather than in a single
+    // transaction, again to let other threads in to communicate with
+    // other devices.
+    //
+    // The card will send a stream of 0x00 bytes while the write
+    // completes. Some cards have been observed to send a byte 0x03 at
+    // the end, Either way, when the card sends a byte 0xff it should
+    // be ready for the next command.
+    if (disk->mmc_writing) {
+        DEBUG2("mmc_spi_send_command_start(): polling for completion of previous write\n");
+        disk->mmc_writing   = 0;
+        response[0]    = 0x00;
+        for (i = 0; (i < MMC_SPI_WRITE_BUSY_RETRIES) && (0x00FF != response[0]); i++) {
+            cyg_spi_transfer(dev, cyg_mmc_spi_polled, 1, mmc_spi_ff_data, response);
+        }
+    }
+#endif    
+    
+    request[0]  = command | 0x0040;
+    request[1]  = (arg >> 24) & 0x00FF;
+    request[2]  = (arg >> 16) & 0x00FF;
+    request[3]  = (arg >>  8) & 0x00FF;
+    request[4]  = arg         & 0x00FF;
+    // A CRC is needed for the go-idle-state command, because that
+    // command switches the device from MMC to SPI mode. That CRC is
+    // well-known. Once in SPI mode the card will not use CRCs by
+    // default.
+    request[5]  = (command == 0x00) ? 0x0095 : 0x00ff;
+    // There will need to be at least one extra byte transfer to get
+    // the card's response, so send that straightaway. Extra
+    // outgoing data like this should be 0xff so that the card
+    // does not confuse it with a new incoming command.
+    request[6]  = 0x00ff;
+
+    // Lock the SPI bus. It remains locked until a subsequent call to
+    // mmc_spi_end_command().
+    cyg_spi_transaction_begin(dev);
+    
+    // Transfer the whole command, and try to read the response back
+    // immediately.
+    cyg_spi_transaction_transfer(dev, cyg_mmc_spi_polled, 7, request, response, 0);
+    DEBUG2("Sent command %02x %d: reply bytes %02x %02x %02x %02x %02x %02x %02x\n", command, arg, \
+                response[0], response[1], response[2], response[3], response[4], response[5], response[6]);
+    
+    // The response will be a single byte with the top bit clear.
+    // The remaining bits are error/status flags. If the command
+    // involves an additional response then that will be handled
+    // by the calling code.
+    reply       = response[6];
+    for (i = 0; (i < MMC_SPI_COMMAND_RETRIES) && (0 != (reply & 0x0080)); i++) {
+        cyg_spi_transaction_transfer(dev, cyg_mmc_spi_polled, 1, mmc_spi_ff_data, response, 0);
+        reply = response[0];
+        DEBUG2("  loop %d, additional reply %02x\n", i, reply);
+    }
+
+    // Leave the interpretation of the reply code to the caller
+    return (cyg_uint32) reply;
+}
+
+// At the end of each command the card needs eight clocks to finish
+// its processing. A tick() call takes care of that, and will have
+// the side effect of dropping the chip select. Ending the transaction
+// unlocks the bus for other SPI I/O operations
+static void
+mmc_spi_end_command(cyg_mmc_spi_disk_info_t* disk)
+{
+    cyg_spi_device* dev = disk->mmc_spi_dev;
+    cyg_spi_transaction_tick(dev, cyg_mmc_spi_polled, 1);
+    cyg_spi_transaction_end(dev);
+}
+
+// A utility combination of the above two for simple commands which do
+// not involve any other data.
+static cyg_uint32
+mmc_spi_send_command(cyg_mmc_spi_disk_info_t* disk, cyg_uint32 command, cyg_uint32 arg)
+{
+    cyg_uint32  reply;
+    reply = mmc_spi_send_command_start(disk, command, arg);
+    mmc_spi_end_command(disk);
+    return reply;
+}
+
+// The card will return a data block when reading a disk block, or
+// for certain other commands like reading card registers. Each
+// data block consists of:
+//   1) some number of padding bytes 0xff while the card is still
+//      processing the command and preparing the data
+//   2) a data token byte, usually 0xFE for success
+//   3) n bytes of data
+//   4) two bytes of crc, which can be ignored.
+//
+// The data token byte is the only indication of success or failure,
+// so that gets returned.
+//
+// When mounting certain types of card an extra delay may be needed
+// before reading the first data block. This is handled by the
+// extra_delay argument.
+static cyg_uint32
+mmc_spi_read_data(cyg_mmc_spi_disk_info_t* disk, cyg_uint8* buf, cyg_uint32 count, cyg_bool extra_delay)
+{
+    cyg_spi_device* dev = disk->mmc_spi_dev;
+    int             i;
+    cyg_uint8       response[2];
+    cyg_uint32      retries;
+
+    if (extra_delay) {
+        retries = MMC_SPI_READ_DATA_TOKEN_RETRIES * 100;
+    } else {
+        retries = MMC_SPI_READ_DATA_TOKEN_RETRIES;
+    }
+
+    response[0] = 0x00FF;
+    for (i = 0; (i < retries) && (0x00FF == response[0]); i++) {
+        cyg_spi_transaction_transfer(dev, cyg_mmc_spi_polled, 1, mmc_spi_ff_data, response, 0);
+    }
+    
+    if (MMC_DATA_TOKEN_SUCCESS != response[0]) {
+        DEBUG1("mmc_spi_read_data(): got error response %02x after %d iterations\n", response[0], i);
+        return response[0];
+    }
+
+    // Now for the actual data. There is no way of detecting a failure from
+    // this point on.
+    cyg_spi_transaction_transfer(dev, cyg_mmc_spi_polled, count, mmc_spi_ff_data, buf, 0);
+    // And the CRC, which can be ignored
+    cyg_spi_transaction_transfer(dev, cyg_mmc_spi_polled, 2, mmc_spi_ff_data, response, 0);
+    DEBUG2("mmc_spi_read_data(): got data and CRC %02x %02x\n", response[0], response[1]);
+    
+    return MMC_DATA_TOKEN_SUCCESS;
+}
+
+// Read one of the card registers, e.g. CSD or CID
+static Cyg_ErrNo
+mmc_spi_read_register(cyg_mmc_spi_disk_info_t* disk, cyg_uint32 command, cyg_uint8* buf, cyg_uint32 count)
+{
+    cyg_uint32      reply;
+    
+    reply = mmc_spi_send_command_start(disk, command, 0);
+    if (MMC_REPLY_SUCCESS != reply) {
+        DEBUG1("mmc_spi_read_register(): unexpected response to command %02x, reply code %02x\n", command, reply);
+        mmc_spi_end_command(disk);
+        return (0x00FF == reply) ? -ENODEV : -EIO;
+    }
+    reply = mmc_spi_read_data(disk, buf, count, false);
+    mmc_spi_end_command(disk);
+    if (MMC_DATA_TOKEN_SUCCESS != reply) {
+        DEBUG1("mmc_spi_read_register(): unexpected response to command %02x, expected 0x00FE data token, got %02x\n", command, reply);
+        return -EIO;
+    }
+    return ENOERR;
+}
+
+// Reading a disk block is just a combination of the above utilities.
+// This code is also responsible for translating error codes, since
+// higher-level code does not get to see the initial response vs. the
+// data token byte.
+static Cyg_ErrNo
+mmc_spi_read_disk_block(cyg_mmc_spi_disk_info_t* disk, cyg_uint8* buf, cyg_uint32 block, cyg_bool extra_delay)
+{
+    cyg_uint32 reply;
+    
+    // First the command itself.
+    DEBUG2("mmc_spi_read_disk_block(%d): sending command\n", block);
+    reply = mmc_spi_send_command_start(disk, MMC_REQUEST_READ_SINGLE_BLOCK, block * MMC_SPI_BLOCK_SIZE);
+    if (MMC_REPLY_SUCCESS != reply) {
+        DEBUG1("mmc_spi_read_disk_block(%d): unexpected response to READ_SINGLE_BLOCK command, code %02x\n", block, reply);
+        mmc_spi_end_command(disk);
+        // A byte 0xFF indicates the card has been removed.
+        if (0x00FF == reply) {
+            return -ENODEV;
+        }
+        // Parameter or address error should not occur, higher-level
+        // code should have checked the block to ensure that it is
+        // in range.
+        if (0 != (reply & (MMC_REPLY_PARAMETER_ERROR | MMC_REPLY_ADDRESS_ERROR))) {
+            return -EINVAL;
+        }
+        // The disk should not be in idle state or in an erase sequence. The
+        // command is definitely legal and CRCs should be disabled. So everything
+        // else is an I/O error.
+        return -EIO;
+    }
+    
+    // Now read back the data block. That code can be shared with other read
+    // operations, e.g. for retrieving registers.
+    DEBUG2("mmc_spi_read_disk_block(%d): reading data token/data/crc\n", block);
+    reply = mmc_spi_read_data(disk, buf, MMC_SPI_BLOCK_SIZE, extra_delay);
+    mmc_spi_end_command(disk);
+    if (MMC_DATA_TOKEN_SUCCESS != reply) {
+        DEBUG1("mmc_spi_read_disk_block(%d): failed to retrieve data, error token %02x\n", block, reply);
+        
+        // Possibilities are password-locked, range error, ECC failure
+        // if the raw data is corrupt, CC error for an internal card
+        // error, or some other error. A byte 0xFF indicates the card
+        // has been removed.
+        if (0x00FF == reply) {
+            return -ENODEV;
+        } else if (0 != (MMC_DATA_ERROR_TOKEN_CARD_LOCKED & reply)) {
+            // This should have been caught by a mount operation.
+            return -EPERM;
+        } else if (0 != (MMC_DATA_ERROR_TOKEN_OUT_OF_RANGE & reply)) {
+            return -EINVAL;
+        } else {
+            return -EIO;
+        }
+    }
+    return ENOERR;
+}
+
+// Writing a block involves a bit more work. Some of this could be
+// moved into a utility routine if necessary, shared with code for
+// e.g. updating the CSD register, but for now that other functionality
+// is not needed.
+static Cyg_ErrNo
+mmc_spi_write_disk_block(cyg_mmc_spi_disk_info_t* disk, const cyg_uint8* buf, cyg_uint32 block)
+{
+    cyg_spi_device* dev = disk->mmc_spi_dev;
+    cyg_uint32      reply;
+    cyg_uint8       extra[4];
+    int             i;
+   
+    // First, send the command itself and get the initial response
+    DEBUG2("mmc_spi_write_disk_block(), sending command\n");
+    reply = mmc_spi_send_command_start(disk, MMC_REQUEST_WRITE_BLOCK, block * MMC_SPI_BLOCK_SIZE);
+    if (MMC_REPLY_SUCCESS != reply) {
+        DEBUG1("mmc_spi_write_disk_block(): unexpected response to WRITE_BLOCK command, code %02x\n", reply);
+        mmc_spi_end_command(disk);
+        if (0x00FF == reply) {
+            return -ENODEV;
+        }
+        // Parameter or address error should not occur, higher-level
+        // code should have checked the block to ensure that it is
+        // in range.
+        if (0 != (reply & (MMC_REPLY_PARAMETER_ERROR | MMC_REPLY_ADDRESS_ERROR))) {
+            return -EINVAL;
+        }
+        // The disk should not be in idle state or in an erase sequence. The
+        // command is definitely legal and CRCs should be disabled. So everything
+        // else is an I/O error.
+        return -EIO;
+    }
+
+    // The card is now expecting a data block. This consists of a single byte
+    // 0x00FE, then the data itself, and a dummy CRC. The reply from the card
+    // does not contain any useful information.
+    DEBUG2("mmc_spi_write_disk_block(): sending data token/data/crc\n");
+    extra[0]    = 0x00FE;
+    cyg_spi_transaction_transfer(dev, cyg_mmc_spi_polled, 1, extra, (cyg_uint8*)0, 0);
+    cyg_spi_transaction_transfer(dev, cyg_mmc_spi_polled, MMC_SPI_BLOCK_SIZE, buf, (cyg_uint8*)0, 0);
+    cyg_spi_transaction_transfer(dev, cyg_mmc_spi_polled, 2, mmc_spi_ff_data, (cyg_uint8*)0, 0);
+
+    // The card should respond immediately with a data response token.
+    cyg_spi_transaction_transfer(dev, cyg_mmc_spi_polled, 1, mmc_spi_ff_data, extra, 0);
+    DEBUG2("mmc_spi_write_disk_block(): got data response token %02x\n", extra[0]);
+
+    // The bottom five bits contain the response. 00101 indicates success,
+    // anything else is a CRC error. Everything else will have been checked
+    // before the data got transferred.
+    if (0x05 != (extra[0] & 0x1F)) {
+        DEBUG1("mmc_spi_write_disk_block(): invalid data response token %02x\n", extra[0]);
+        mmc_spi_end_command(disk);
+        if (0x00FF == extra[0]) {
+            return -ENODEV;
+        }
+        return -EIO;
+    }
+
+#ifdef MMC_SPI_BACKGROUND_WRITES    
+    // Mark the card as writing. The next operation will poll for completion.
+    disk->mmc_writing   = true;
+#else
+    // The card is now busy doing the write and will output a stream of 0's
+    // while busy. The timeout should really be calculated using the CSD
+    // register settings.
+    //
+    // It should be legal to drop the chip select here, i.e. to end
+    // the current transaction and start a new one for each poll
+    // operation. That would allow other SPI devices to be accessed.
+    // However it appears that this does not work with all MMC cards.
+    extra[0]    = 0x00;
+    for (i = 0; (i < MMC_SPI_WRITE_BUSY_RETRIES) && (0x00 == extra[0]); i++) {
+        cyg_spi_transaction_transfer(dev, cyg_mmc_spi_polled, 1, mmc_spi_ff_data, extra, 0);
+        DEBUG2("mmc_spi_write_disk_block(), polling for ! busy, got response %02x\n", extra[0]);
+    }
+#endif
+    
+    // Assume that the loop did in fact terminate.
+    mmc_spi_end_command(disk);
+    return ENOERR;
+}
+
+// MMC sockets will default to a slow clockrate. During a successful mount
+// the SPI device settings will be changed to the fastest supported by the
+// card, as per the CSD register. This will need to be undone during an
+// unmount, or if the final stages of a mount are unsuccessful.
+static void
+mmc_spi_restore_baud(cyg_mmc_spi_disk_info_t* disk)
+{
+    cyg_uint32  len = sizeof(cyg_uint32);
+    (void) cyg_spi_set_config(disk->mmc_spi_dev, CYG_IO_SET_CONFIG_SPI_CLOCKRATE, (void*) &(disk->mmc_saved_baudrate), &len);
+}
+
+// check_for_disk() tries to communicate with an MMC card that is not
+// currently mounted. It performs the appropriate initialization so
+// that read and write operations are possible, checks the disk format,
+// distinguishes between read-only and read-write cards, calculates the
+// card size, stores the unique id, etc.
+//
+// The main error conditions are ENODEV (no card), EIO (card not
+// responding sensibly to requests), ENOTDIR (wrong format), or EPERM
+// (card is password-locked).
+static Cyg_ErrNo
+mmc_spi_check_for_disk(cyg_mmc_spi_disk_info_t* disk)
+{
+    cyg_spi_device*     dev = disk->mmc_spi_dev;
+    int                 i;
+    cyg_uint32          reply;
+    Cyg_ErrNo           code;
+    mmc_csd_register    csd;
+
+#ifdef MMC_SPI_BACKGROUND_WRITES    
+    // If we have unmounted a disk and are remounting it, assume that
+    // any writes have completed.
+    disk->mmc_writing   = false;
+#endif    
+    reply               = 0x00ff;
+    
+    for (i = 0; (i < MMC_SPI_GO_IDLE_RETRIES) && (0x01 != reply); i++) {
+        // Allow platform HALs to provide additional initialization,
+        // if the hardware needs it.
+#ifdef HAL_MMC_SPI_INIT
+        HAL_MMC_SPI_INIT(dev, reply);
+        if (! reply) {
+            return -ENODEV;
+        }
+#endif
+        // MMC cards generic initialization. The card may have just
+        // been plugged in so there is no guarantee that any previous
+        // init() calls or other traffic will have affected this card.
+        mmc_spi_send_init(disk);
+    
+        // Now set the card to idle state. This involves the GO_IDLE_STATE
+        // command which will be accepted irrespective of whether the card is
+        // currently in MMC or SPI mode, and will leave the card in SPI mode.
+        reply = mmc_spi_send_command(disk, MMC_REQUEST_GO_IDLE_STATE, 0);
+
+        // The card should reply with 0x01. FF suggests that there is
+        // no card. Any other response indicates some synchronization
+        // problem. For example the card might still be responding to
+        // some request from a previous session which aborted at an
+        // inconvenient moment. Some dummy traffic is generated in the
+        // hope that this gets things back in sync.
+        if (0x01 != reply) {
+            DEBUG1("mmc_spi_check_for_disk(): loop %d, card did not enter idle state, code %02x\n", i, reply);
+            if (0x0ff != reply) {
+                cyg_spi_transfer(dev, cyg_mmc_spi_polled, 128, mmc_spi_ff_data, (cyg_uint8*) 0);
+            }
+        }
+    }
+    if (0x0ff == reply) {
+        DEBUG1("mmc_spi_check_for_disk(): unable to get a response from the MMC card: code %02x\n", reply);
+        // A working card should be returning some data
+        return -ENODEV;
+    }
+    if (0x01 != reply) {
+        DEBUG1("mmc_spi_check_for_disk(): card did not enter idle state, code %02x\n", reply);
+        return -EIO;
+    }
+    
+    // Next, wait for the card to initialize. This involves repeatedly
+    // trying the SEND_OP_COND command until we get a reply that is
+    // not idle
+    reply = 0x00ff;
+    for (i = 0; (i < MMC_SPI_OP_COND_RETRIES) && ((0x00ff == reply) || (0 != (MMC_REPLY_IN_IDLE_STATE & reply))); i++) {
+        reply = mmc_spi_send_command(disk, MMC_REQUEST_SEND_OP_COND, 0);
+    }
+    if (MMC_REPLY_SUCCESS != reply) {
+        DEBUG1("mmc_spi_check_for_disk(): card has not entered operational state: reply code %02x\n", reply);
+        return (0x00FF == reply) ? -ENODEV : -EIO;
+    }
+
+    // The card has now generated sufficient responses that we don't need to
+    // worry about a missing card anymore.
+    // Get hold of the card's unique ID and store it, to allow disk changes
+    // to be detected.
+    code = mmc_spi_read_register(disk, MMC_REQUEST_SEND_CID, (cyg_uint8*) &(disk->mmc_id), 16);
+    if (code) {
+        mmc_spi_end_command(disk);
+        return code;
+    }
+    DEBUG2("CID data: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n",               \
+           disk->mmc_id.cid_data[ 0], disk->mmc_id.cid_data[ 1], disk->mmc_id.cid_data[ 2], disk->mmc_id.cid_data[ 3],  \
+           disk->mmc_id.cid_data[ 4], disk->mmc_id.cid_data[ 5], disk->mmc_id.cid_data[ 6], disk->mmc_id.cid_data[ 7],  \
+           disk->mmc_id.cid_data[ 8], disk->mmc_id.cid_data[ 9], disk->mmc_id.cid_data[10], disk->mmc_id.cid_data[11],  \
+           disk->mmc_id.cid_data[12], disk->mmc_id.cid_data[13], disk->mmc_id.cid_data[14], disk->mmc_id.cid_data[15]);
+
+    // And retrieve the card's configuration data.
+    code = mmc_spi_read_register(disk, MMC_REQUEST_SEND_CSD, (cyg_uint8*) &csd, 16);
+    if (code) {
+        mmc_spi_end_command(disk);
+        return code;
+    }
+    DEBUG2("CSD data: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n",   \
+           csd.csd_data[ 0], csd.csd_data[ 1], csd.csd_data[ 2], csd.csd_data[3],                           \
+           csd.csd_data[ 4], csd.csd_data[ 5], csd.csd_data[ 6], csd.csd_data[7],                           \
+           csd.csd_data[ 8], csd.csd_data[ 9], csd.csd_data[10], csd.csd_data[11],                          \
+           csd.csd_data[12], csd.csd_data[13], csd.csd_data[14], csd.csd_data[15]);
+    
+    // Optionally dump the whole CSD register. This takes a lot of
+    // code but gives a lot of info about the card. If the info looks
+    // correct then we really are interacting properly with an MMC card.
+#if DEBUG > 0
+    DEBUG1("CSD data: structure 0x%02x, version 0x%02x\n", MMC_CSD_REGISTER_CSD_STRUCTURE(&csd), MMC_CSD_REGISTER_SPEC_VERS(&csd));
+    if ((0 != MMC_CSD_REGISTER_FILE_FORMAT_GROUP(&csd)) ||
+        ((0 != MMC_CSD_REGISTER_FILE_FORMAT(&csd)) && (1 != MMC_CSD_REGISTER_FILE_FORMAT(&csd)))) {
+        DEBUG1("        : unknown disk format, FILE_FORMAT_GROUP %d, FILE_FORMAT %d\n", \
+                    MMC_CSD_REGISTER_FILE_FORMAT_GROUP(&csd), MMC_CSD_REGISTER_FILE_FORMAT(&csd));
+    } else if (0 == MMC_CSD_REGISTER_FILE_FORMAT(&csd)) {
+        DEBUG1("        : partioned disk, FILE_FORMAT_GROUP 0, FILE_FORMAT 0\n");
+    } else {
+        DEBUG1("        : FAT disk, FILE_FORMAT_GROUP 0, FILE_FORMAT 1\n");
+    }
+    {
+        static cyg_uint32 mantissa_speeds_x10[16]   = { 0, 10, 12, 13, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 70, 80 };
+        static cyg_uint32 exponent_speeds_div10[8]  = { 10000, 100000, 1000000, 10000000, 0, 0, 0, 0 };
+        cyg_uint32 speed = mantissa_speeds_x10[MMC_CSD_REGISTER_TRAN_SPEED_MANTISSA(&csd)] *
+            exponent_speeds_div10[MMC_CSD_REGISTER_TRAN_SPEED_EXPONENT(&csd)];
+        speed /= 1000;
+        DEBUG1("        : TRAN_SPEED %d %d -> %d kbit/s\n", \
+               MMC_CSD_REGISTER_TRAN_SPEED_MANTISSA(&csd), MMC_CSD_REGISTER_TRAN_SPEED_EXPONENT(&csd), speed);
+    }        
+    DEBUG1("        : READ_BL_LEN block length 2^%d (%d)\n", MMC_CSD_REGISTER_READ_BL_LEN(&csd), \
+                0x01 << MMC_CSD_REGISTER_READ_BL_LEN(&csd));
+    DEBUG1("        : C_SIZE %d, C_SIZE_MULT %d\n", MMC_CSD_REGISTER_C_SIZE(&csd), MMC_CSD_REGISTER_C_SIZE_MULT(&csd));
+    {
+        cyg_uint32 block_len = 0x01 << MMC_CSD_REGISTER_READ_BL_LEN(&csd);
+        cyg_uint32 mult      = 0x01 << (MMC_CSD_REGISTER_C_SIZE_MULT(&csd) + 2);
+        cyg_uint32 size      = block_len * mult * (MMC_CSD_REGISTER_C_SIZE(&csd) + 1);
+        cyg_uint32 sizeK     = (cyg_uint32) (size / 1024);
+        cyg_uint32 sizeM     =  sizeK / 1024;
+        sizeK  -= (sizeM * 1024);
+        DEBUG1("        : total card size %dM%dK\n", sizeM, sizeK);
+    }
+    DEBUG1("        : WR_BL_LEN block length 2^%d (%d)\n", \
+           MMC_CSD_REGISTER_WRITE_BL_LEN(&csd), 0x01 << MMC_CSD_REGISTER_WRITE_BL_LEN(&csd));
+    DEBUG1("        : asynchronous read access time TAAC %dE%d ns\n", \
+           MMC_CSD_REGISTER_TAAC_MANTISSA(&csd), MMC_CSD_REGISTER_TAAC_EXPONENT(&csd));
+    DEBUG1("        : synchronous read access time NSAC %d * 100 cycles\n", \
+           MMC_CSD_REGISTER_NSAC(&csd));
+    DEBUG1("        : typical write program time %d * read time\n", MMC_CSD_REGISTER_R2W_FACTOR(&csd));
+    DEBUG1("        : CCC command classes 0x%04x\n", MMC_CSD_REGISTER_CCC(&csd));
+    DEBUG1("        : READ_BL_PARTIAL %d, WRITE_BLK_MISALIGN %d, READ_BLK_MISALIGN %D, DSR_IMP %d\n",   \
+           MMC_CSD_REGISTER_READ_BL_PARTIAL(&csd), MMC_CSD_REGISTER_WRITE_BLK_MISALIGN(&csd),           \
+           MMC_CSD_REGISTER_READ_BLK_MISALIGN(&csd), MMC_CSD_REGISTER_DSR_IMP(&csd));
+    DEBUG1("        : WR_BL_PARTIAL %d\n", MMC_CSD_REGISTER_WR_BL_PARTIAL(&csd));
+    {
+        static cyg_uint8    min_currents[8] = { 1, 1, 5, 10, 25, 35, 60, 100 };
+        static cyg_uint8    max_currents[8] = { 1, 5, 10, 25, 35, 45, 80, 200 };
+        DEBUG1("        : read current min %dmA, max %dmA\n",               \
+                    min_currents[MMC_CSD_REGISTER_VDD_R_CURR_MIN(&csd)],    \
+                    max_currents[MMC_CSD_REGISTER_VDD_R_CURR_MAX(&csd)]);
+        DEBUG1("        : write current min %dmA, max %dmA\n",              \
+                    min_currents[MMC_CSD_REGISTER_VDD_W_CURR_MIN(&csd)],    \
+                    max_currents[MMC_CSD_REGISTER_VDD_W_CURR_MAX(&csd)]);
+    }
+    DEBUG1("        : erase sector size %d, erase group size %d\n", \
+           MMC_CSD_REGISTER_SECTOR_SIZE(&csd) + 1, MMC_CSD_REGISTER_ERASE_GRP_SIZE(&csd) + 1);
+    DEBUG1("        : write group enable %d, write group size %d\n", \
+           MMC_CSD_REGISTER_WR_GRP_ENABLE(&csd), MMC_CSD_REGISTER_WR_GRP_SIZE(&csd) + 1);
+    DEBUG1("        : copy bit %d\n", MMC_CSD_REGISTER_COPY(&csd));
+    DEBUG1("        : permanent write protect %d, temporary write protect %d\n", \
+           MMC_CSD_REGISTER_PERM_WRITE_PROTECT(&csd), MMC_CSD_REGISTER_TMP_WRITE_PROTECT(&csd));
+    DEBUG1("        : ecc %d, default ecc %d\n", MMC_CSD_REGISTER_ECC(&csd), MMC_CSD_REGISTER_DEFAULT_ECC(&csd));
+    DEBUG1("        : crc 0x%08x\n", MMC_CSD_REGISTER_CRC(&csd));
+#endif                
+
+    // There is information available about the file format, e.g.
+    // partitioned vs. simple FAT. With the current version of the
+    // generic disk code this needs to be known statically, via
+    // the mbr field of the disk channel structure. If the card
+    // is inappropriately formatted, reject the mount request.
+    if ((0 != MMC_CSD_REGISTER_FILE_FORMAT_GROUP(&csd)) ||
+        (0 != MMC_CSD_REGISTER_FILE_FORMAT(&csd))) {
+        return -ENOTDIR;
+    }
+
+    // Look for a write-protect bit (permanent or temporary), and set
+    // the disk as read-only or read-write as appropriate. The
+    // temporary write-protect could be cleared by rewriting the CSD
+    // register (including recalculating the CRC) but the effort
+    // involves does not seem worth-while.
+    if ((0 != MMC_CSD_REGISTER_PERM_WRITE_PROTECT(&csd)) || (0 != MMC_CSD_REGISTER_TMP_WRITE_PROTECT(&csd))) {
+        disk->mmc_read_only   = true;
+    } else {
+        disk->mmc_read_only   = false;
+    }
+    DEBUG1("Disk read-only flag %d\n", disk->mmc_read_only);
+    
+    // Calculate the disk size, primarily for assertion purposes.
+    // By design MMC cards are limited to 4GB, which still doesn't
+    // quite fit into 32 bits.
+    disk->mmc_block_count = (((cyg_uint64)(0x01 << MMC_CSD_REGISTER_READ_BL_LEN(&csd))) *
+                             ((cyg_uint64)(0x01 << (MMC_CSD_REGISTER_C_SIZE_MULT(&csd) + 2))) *
+                             ((cyg_uint64)(MMC_CSD_REGISTER_C_SIZE(&csd) + 1))) / (cyg_uint64)MMC_SPI_BLOCK_SIZE;
+    DEBUG1("Disk blockcount %d (0x%08x)\n", disk->mmc_block_count, disk->mmc_block_count);
+    
+    // Assume for now that the block length is 512 bytes. This is
+    // probably a safe assumption since we have just got the card
+    // initialized out of idle state. If it ever proves to be a problem
+    // the SET_BLOCK_LEN command can be used.
+    // Nevertheless store the underlying block sizes
+    disk->mmc_read_block_length  = 0x01 << MMC_CSD_REGISTER_READ_BL_LEN(&csd);
+    disk->mmc_write_block_length = 0x01 << MMC_CSD_REGISTER_WRITE_BL_LEN(&csd);
+
+    // The CSD contains the maximum supported transfer speed. Adjust
+    // the SPI device to match, saving the old value for an unmount
+    // operation. It is assumed that the SPI bus driver will munge
+    // the supplied speed to something appropriate.
+    {
+        static const cyg_uint32 mantissa_speeds_x10[16]   = { 0, 10, 12, 13, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 70, 80 };
+        static const cyg_uint32 exponent_speeds_div10[8]  = { 10000, 100000, 1000000, 10000000, 0, 0, 0, 0 };
+        cyg_uint32 speed, len;
+        
+        len = sizeof(cyg_uint32);
+        if (cyg_spi_get_config(dev, CYG_IO_GET_CONFIG_SPI_CLOCKRATE, (void*) &disk->mmc_saved_baudrate, &len)) {
+            DEBUG1("Failed to retrieve current SPI device clockrate\n");
+            return -EIO;
+        }
+        speed = mantissa_speeds_x10[MMC_CSD_REGISTER_TRAN_SPEED_MANTISSA(&csd)] * exponent_speeds_div10[MMC_CSD_REGISTER_TRAN_SPEED_EXPONENT(&csd)];
+        if (speed > disk->mmc_saved_baudrate) {
+            DEBUG1("Old SPI speed %d, switching to %d\n", disk->mmc_saved_baudrate, speed);
+            cyg_spi_set_config(dev, CYG_IO_SET_CONFIG_SPI_CLOCKRATE, (void*) &speed, &len);
+        } else {
+            DEBUG1("Old SPI speed %d already greater than max speed %d, leaving it alone\n",
+                   disk->mmc_saved_baudrate, speed);
+        }
+    }
+
+    // Read the partition table off the card. This is a way of
+    // checking that the card is not password-locked. It also
+    // provides information about the "disk geometry" which is
+    // needed by higher-level code.
+    // FIXME: the higher-level code should be made to use LBA
+    // addressing instead.
+    {
+        cyg_uint8   data[MMC_SPI_BLOCK_SIZE];
+        cyg_uint8*  partition;
+        cyg_uint32  lba_first, lba_size, lba_end, head, cylinder, sector;
+        
+        code = mmc_spi_read_disk_block(disk, data, 0, true);
+        if (code) {
+            mmc_spi_restore_baud(disk);
+            return code;
+        }
+#if DEBUG > 0
+        DEBUG1("Read block 0 (partition table)\n");
+        DEBUG1("Signature 0x%02x 0x%02x, should be 0x55 0xaa\n", data[0x1fe], data[0x1ff]);
+        // There should be four 16-byte partition table entries at offsets
+        // 0x1be, 0x1ce, 0x1de and 0x1ee. The numbers are stored little-endian
+        for (i = 0; i < 4; i++) {
+            partition = &(data[0x1be + (0x10 * i)]);
+            DEBUG1("Partition %d: boot %02x, first sector %02x %02x %02x, file system %02x, last sector %02x %02x %02x\n", i,   \
+                   partition[0], partition[1], partition[2], partition[3], partition[4], \
+                   partition[5], partition[6], partition[7]);
+            DEBUG1("           : first sector (linear) %02x %02x %02x %02x, sector count %02x %02x %02x %02x\n", \
+                   partition[11], partition[10], partition[9], partition[8], \
+                   partition[15], partition[14], partition[13], partition[12]);
+        }
+#endif        
+        if ((0x0055 != data[0x1fe]) || (0x00aa != data[0x1ff])) {
+            mmc_spi_restore_baud(disk);
+            return -ENOTDIR;
+        }
+        partition   = &(data[0x1be]);
+        lba_first   = (partition[11] << 24) | (partition[10] << 16) | (partition[9] << 8) | partition[8];
+        lba_size    = (partition[15] << 24) | (partition[14] << 16) | (partition[13] << 8) | partition[12];
+        lba_end     = lba_first + lba_size - 1;
+
+        // First sector in c/h/s format
+        cylinder    = ((partition[2] & 0xC0) << 2) | partition[3];
+        head        = partition[1];
+        sector      = partition[2] & 0x3F;
+
+        // lba_start == (((cylinder * Nh) + head) * Ns) + sector - 1, where (Nh == heads/cylinder) and (Ns == sectors/head)
+        // Strictly speaking we should be solving some simultaneous
+        // equations here for lba_start/lba_end, but that gets messy.
+        // The first partition is at the start of the card so cylinder will be 0,
+        // and we can ignore Nh.
+        CYG_ASSERT(0 == cylinder, "Driver assumption - partition 0 is at start of card\n");
+        CYG_ASSERT(0 != head,     "Driver assumption - partition table is sensible\n");
+        disk->mmc_sectors_per_head = ((lba_first + 1) - sector) / head;
+
+        // Now for lba_end.
+        cylinder    = ((partition[6] & 0xC0) << 2) | partition[7];
+        head        = partition[5];
+        sector      = partition[6] & 0x3F;
+        disk->mmc_heads_per_cylinder = ((((lba_end + 1) - sector) / disk->mmc_sectors_per_head) - head) / cylinder;
+    }
+    
+    return ENOERR;
+}
+
+// Check that the current card is the one that was previously
+// accessed. This may fail if the card has been removed and the
+// slot is empty, or if the card has been removed and a different
+// one inserted. It may pass incorrectly if a card is removed,
+// modified elsewhere, and reinserted without eCos noticing.
+// There is no way around that without some way of detecting
+// disk removal in hardware.
+//
+// Re-reading the cid may actually be overkill. If a new card
+// has been plugged in then it will not have been initialized so
+// it will respond with 0xff anyway. It is very unlikely that
+// an init sequence will have happened by accident.
+static cyg_bool
+mmc_spi_disk_changed(cyg_mmc_spi_disk_info_t* disk)
+{
+    mmc_cid_register    cid;
+    Cyg_ErrNo           code;
+
+    code = mmc_spi_read_register(disk, MMC_REQUEST_SEND_CID, (cyg_uint8*) &cid, 16);
+    if (-ENODEV == code) {
+        return true;
+    }
+
+    if (0 != memcmp(&cid, &(disk->mmc_id), sizeof(mmc_cid_register))) {
+        return true;
+    }
+    return false;
+}
+
+// ----------------------------------------------------------------------------
+
+// No hardware initialization is performed here. Even if a card is
+// currently plugged in it may get removed before it gets mounted, so
+// there is no point looking at the card here. It is still necessary
+// to invoke the callback init function so that higher-level code gets
+// a chance to do its bit.
+static cyg_bool
+mmc_spi_disk_init(struct cyg_devtab_entry* tab)
+{
+    disk_channel*   chan    = (disk_channel*) tab->priv;
+    MMC_SPI_INIT_FF_DATA();
+    return (*chan->callbacks->disk_init)(tab);
+}
+
+// lookup() is called during a mount() operation, so this is the right
+// place to check whether or not there is a card.
+
+static char*
+mmc_spi_disk_lookup_itoa(cyg_uint32 num, char* where)
+{
+    if (0 == num) {
+        *where++ = '0';
+    } else {
+        char local[10];  // 2^32 just fits into 10 places
+        int  index = 9;
+        while (num > 0) {
+            local[index--] = (num % 10) + '0';
+            num /= 10;
+        }
+        for (index += 1; index < 10; index++) {
+            *where++ = local[index];
+        }
+    }
+    return where;
+}
+
+static Cyg_ErrNo
+mmc_spi_disk_lookup(struct cyg_devtab_entry** tab, struct cyg_devtab_entry *sub_tab, const char* name)
+{
+    disk_channel*               chan    = (disk_channel*) (*tab)->priv;
+    cyg_mmc_spi_disk_info_t*    disk    = (cyg_mmc_spi_disk_info_t*) chan->dev_priv;
+    Cyg_ErrNo                   result;
+
+    if (disk->mmc_connected) {
+        // There was a card plugged in last time we looked. Is it still there?
+        if (mmc_spi_disk_changed(disk)) {
+            // The old card is gone. Either there is no card plugged in, or
+            // it has been replaced with a different one. If the latter the
+            // existing mounts must be removed before anything sensible
+            // can be done.
+            disk->mmc_connected = false;
+            (*chan->callbacks->disk_disconnected)(chan);
+            if (0 != chan->info->mounts) {
+                return -ENODEV;
+            }
+        }
+    }
+
+    if ((0 != chan->info->mounts) && !disk->mmc_connected) {
+        // There are still mount points to an old card. We cannot accept
+        // new mount requests until those have been cleaned out.
+        return -ENODEV;
+    }
+
+    if (!disk->mmc_connected) {
+        cyg_disk_identify_t ident;
+        cyg_uint32          id_data;
+        char*               where;
+        int                 i;
+        
+        // The world is consistent and the higher-level code does not
+        // know anything about the current card, if any. Is there a
+        // card?
+        result = mmc_spi_check_for_disk(disk);
+        if (ENOERR != result) {
+            return result;
+        }
+        // A card has been found. Tell the higher-level code about it.
+        // This requires an identify structure, although it is not
+        // entirely clear what purpose that serves.
+        disk->mmc_connected = true;
+        // Serial number, up to 20 characters; The CID register contains
+        // various fields which can be used for this.
+        where   = &(ident.serial[0]);
+        id_data = disk->mmc_id.cid_data[0];   // 1-byte manufacturer id -> 3 chars, 17 left
+        where   = mmc_spi_disk_lookup_itoa(id_data, where);
+        id_data = (disk->mmc_id.cid_data[1] << 8) + disk->mmc_id.cid_data[2]; // 2-byte OEM ID, 5 chars, 12 left
+        where   = mmc_spi_disk_lookup_itoa(id_data, where);
+        id_data = (disk->mmc_id.cid_data[10] << 24) + (disk->mmc_id.cid_data[11] << 16) +
+            (disk->mmc_id.cid_data[12] << 8) + disk->mmc_id.cid_data[13];
+        where   = mmc_spi_disk_lookup_itoa(id_data, where); // 4-byte OEM ID, 10 chars, 2 left
+        // And terminate the string with a couple of places to spare.
+        *where = '\0';
+
+        // Firmware revision number. There is a one-byte product
+        // revision number in the CID, BCD-encoded
+        id_data = disk->mmc_id.cid_data[9] >> 4;
+        if (id_data <= 9) {
+            ident.firmware_rev[0] = id_data + '0';
+        } else {
+            ident.firmware_rev[0] = id_data - 10 + 'A';
+        }
+        id_data = disk->mmc_id.cid_data[9] & 0x0F;
+        if (id_data <= 9) {
+            ident.firmware_rev[1] = id_data + '0';
+        } else {
+            ident.firmware_rev[1] = id_data - 10 + 'A';
+        }
+        ident.firmware_rev[2]   = '\0';
+        
+        // Model number. There is a six-byte product name in the CID.
+        for (i = 0; i < 6; i++) {
+            if ((disk->mmc_id.cid_data[i + 3] >= 0x20) && (disk->mmc_id.cid_data[i+3] <= 0x7E)) {
+                ident.model_num[i] = disk->mmc_id.cid_data[i + 3];
+            } else {
+                break;
+            }
+        }
+        ident.model_num[i] = '\0';
+
+        // We don't have no cylinders, heads, or sectors, but
+        // higher-level code may interpret partition data using C/H/S
+        // addressing rather than LBA. Hence values for some of these
+        // settings were calculated above.
+        ident.cylinders_num     = 1;
+        ident.heads_num         = disk->mmc_heads_per_cylinder;
+        ident.sectors_num       = disk->mmc_sectors_per_head;
+        ident.lba_sectors_num   = disk->mmc_block_count;
+        ident.phys_block_size   = disk->mmc_write_block_length/512
+        ident.max_transfer      = disk->mmc_write_block_length;
+
+        DEBUG1("Calling disk_connected(): serial %s, firmware %s, model %s, heads %d, sectors %d, lba_sectors_num %d, phys_block_size %d\n", \
+               ident.serial, ident.firmware_rev, ident.model_num, ident.heads_num, ident.sectors_num,
+               ident.lba_sectors_num, ident.phys_block_size);
+        (*chan->callbacks->disk_connected)(*tab, &ident);
+
+        // We now have a valid card and higher-level code knows about it. Fall through.
+    }
+
+    // And leave it to higher-level code to finish the lookup, taking
+    // into accounts partitions etc.
+    return (*chan->callbacks->disk_lookup)(tab, sub_tab, name);
+}
+
+static Cyg_ErrNo
+mmc_spi_disk_read(disk_channel* chan, void* buf_arg, cyg_uint32 blocks, cyg_uint32 first_block)
+{
+    cyg_mmc_spi_disk_info_t*    disk    = (cyg_mmc_spi_disk_info_t*) chan->dev_priv;
+    cyg_uint32                  i;
+    cyg_uint8*                  buf = (cyg_uint8*) buf_arg;
+    Cyg_ErrNo                   code = ENOERR;
+    
+    DEBUG1("mmc_spi_disk_read(): first block %d, buf %p, len %lu blocks (%lu bytes)\n",
+           first_block, buf, (unsigned long)blocks, (unsigned long)blocks*512);
+
+    if (! disk->mmc_connected) {
+        return -ENODEV;
+    }
+    if ((first_block + blocks) >= disk->mmc_block_count) {
+        return -EINVAL;
+    }
+
+    for (i = 0; (i < blocks) && (ENOERR == code); i++) {
+        code = mmc_spi_read_disk_block(disk, buf, first_block + i, false);
+        buf += MMC_SPI_BLOCK_SIZE;
+    }
+    return code;
+}
+
+static Cyg_ErrNo
+mmc_spi_disk_write(disk_channel* chan, const void* buf_arg, cyg_uint32 blocks, cyg_uint32 first_block)
+{
+    cyg_mmc_spi_disk_info_t*    disk    = (cyg_mmc_spi_disk_info_t*) chan->dev_priv;
+    cyg_uint32                  i;
+    const cyg_uint8*            buf = (cyg_uint8*) buf_arg;
+    Cyg_ErrNo                   code = ENOERR;
+
+    DEBUG1("mmc_spi_disk_write(): first block %d, buf %p, len %lu blocks (%lu bytes)\n",
+           first_block, buf, (unsigned long)blocks, (unsigned long)blocks*512);
+
+    if (! disk->mmc_connected) {
+        return -ENODEV;
+    }
+    if (disk->mmc_read_only) {
+        return -EROFS;
+    }
+    if ((first_block + blocks) >= disk->mmc_block_count) {
+        return -EINVAL;
+    }
+
+    for (i = 0; (i < blocks) && (ENOERR == code); i++) {
+        code = mmc_spi_write_disk_block(disk, buf, first_block + i);
+        buf += MMC_SPI_BLOCK_SIZE;
+    }
+    return code;
+}
+
+// get_config() and set_config(). There are no supported get_config() operations
+// at this time.
+static Cyg_ErrNo
+mmc_spi_disk_get_config(disk_channel* chan, cyg_uint32 key, const void* buf, cyg_uint32* len)
+{
+    CYG_UNUSED_PARAM(disk_channel*, chan);
+    CYG_UNUSED_PARAM(cyg_uint32, key);
+    CYG_UNUSED_PARAM(const void*, buf);
+    CYG_UNUSED_PARAM(cyg_uint32*, len);
+    
+    return -EINVAL;
+}
+
+static Cyg_ErrNo
+mmc_spi_disk_set_config(disk_channel* chan, cyg_uint32 key, const void* buf, cyg_uint32* len)
+{
+    Cyg_ErrNo                   result  = ENOERR;
+    cyg_mmc_spi_disk_info_t*    disk    = (cyg_mmc_spi_disk_info_t*) chan->dev_priv;
+
+    switch(key) {
+      case CYG_IO_SET_CONFIG_DISK_MOUNT:
+        // There will have been a successful lookup(), so there's
+        // little point in checking the disk again.
+        break;
+
+      case CYG_IO_SET_CONFIG_DISK_UMOUNT:
+        if (0 == chan->info->mounts) {
+            // If this is the last unmount of the card, mark it as
+            // disconnected. If the user then removes the card and
+            // plugs in a new one everything works cleanly. Also
+            // reset the SPI device's clockrate.
+            disk->mmc_connected = false;
+            mmc_spi_restore_baud(disk);
+            result = (chan->callbacks->disk_disconnected)(chan);
+        }
+        break;
+    }
+
+    return result;
+}
+
+// ----------------------------------------------------------------------------
+// And finally the data structures that define this disk. Some of this
+// should be moved into an exported header file so that applications can
+// define additional disks.
+//
+// It is not obvious why there are quite so many structures. Apart
+// from the devtab entries there are no tables involved, so there is
+// no need to keep everything the same size. The cyg_disk_info_t could
+// be the common part of a h/w info_t. The channel structure is
+// redundant and its fields could be merged into the cyg_disk_info_t
+// structure. That would leave a devtab entry, a disk info structure
+// (h/w specific but with a common base), and a disk controller
+// structure (ditto).
+
+DISK_FUNS(cyg_mmc_spi_disk_funs,
+          mmc_spi_disk_read,
+          mmc_spi_disk_write,
+          mmc_spi_disk_get_config,
+          mmc_spi_disk_set_config
+          );
+
+static cyg_mmc_spi_disk_info_t cyg_mmc_spi_disk0_hwinfo = {
+    .mmc_spi_dev        = &cyg_spi_mmc_dev0,
+#ifdef MMC_SPI_BACKGROUND_WRITES    
+    .mmc_writing        = 0,
+#endif    
+    .mmc_connected      = 0
+};
+
+// No h/w controller structure is needed, but the address of the
+// second argument is taken anyway.
+DISK_CONTROLLER(cyg_mmc_spi_disk_controller_0, cyg_mmc_spi_disk0_hwinfo);
+
+DISK_CHANNEL(cyg_mmc_spi_disk0_channel,
+             cyg_mmc_spi_disk_funs,
+             cyg_mmc_spi_disk0_hwinfo,
+             cyg_mmc_spi_disk_controller_0,
+             true,                            /* MBR support */
+             1                                /* Number of partitions supported */
+             );
+             
+BLOCK_DEVTAB_ENTRY(cyg_mmc_spi_disk0_devtab_entry,
+                   CYGDAT_DEVS_DISK_MMC_SPI_DISK0_NAME,
+                   0,
+                   &cyg_io_disk_devio,
+                   &mmc_spi_disk_init,
+                   &mmc_spi_disk_lookup,
+                   &cyg_mmc_spi_disk0_channel);
+
+// EOF mmc_spi.c
--- a/packages/ecos.db
+++ b/packages/ecos.db
@@ -6253,6 +6253,14 @@ package CYGPKG_DEVS_DISK_IDE {
     description     "Disk driver for generic IDE interface." 
 }
 
+package CYGPKG_DEVS_DISK_MMC {
+    alias      { "Disk driver for MMC cards" mmcdisk mmc_disk_driver }
+    directory  devs/disk/generic/mmc
+    script     devs_disk_mmc.cdl
+    hardware
+    description "Disk driver for MMC cards"
+}
+
 package CYGPKG_IO_DISK {
     alias           { "Disk device drivers" disk io_disk }
     directory       io/disk