Mercurial > nand-ecoscentric
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 * 32mm * 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 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
