/*
 * Copyright (C) 2005-2007 by egnite Software GmbH. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holders nor the names of
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * For additional information see http://www.ethernut.de/
 */

/*!
 * \brief Basic block device driver for multimedia cards.
 *
 * The driver uses SPI mode, but doesn't include any low level hardware
 * access. This must be provided by some additional routines.
 *
 * \verbatim
 *
 * $Log$
 * Revision 1.15  2009/02/13 14:52:05  haraldkipp
 * Include memdebug.h for heap management debugging support.
 *
 * Revision 1.14  2009/02/06 15:40:29  haraldkipp
 * Using newly available strdup() and calloc().
 * Replaced NutHeap routines by standard malloc/free.
 * Replaced pointer value 0 by NULL.
 *
 * Revision 1.13  2009/01/17 11:26:46  haraldkipp
 * Getting rid of two remaining BSD types in favor of stdint.
 * Replaced 'u_int' by 'unsinged int' and 'uptr_t' by 'uintptr_t'.
 *
 * Revision 1.12  2008/08/11 06:59:42  haraldkipp
 * BSD types replaced by stdint types (feature request #1282721).
 *
 * Revision 1.11  2008/07/14 13:09:30  haraldkipp
 * Allow small MultiMedia Cards without partition table.
 *
 * Revision 1.10  2007/08/30 12:15:06  haraldkipp
 * Configurable MMC timings.
 *
 * Revision 1.9  2006/10/08 16:48:09  haraldkipp
 * Documentation fixed
 *
 * Revision 1.8  2006/07/05 08:03:12  haraldkipp
 * Bugfix. Trailing slash in mount path not properly handled.
 * Thanks to Michael Fischer.
 *
 * Revision 1.7  2006/06/18 16:34:46  haraldkipp
 * Mutex deadlock fixed.
 *
 * Revision 1.6  2006/05/25 09:34:21  haraldkipp
 * Added mutual exclusion lock for multithreaded access.
 *
 * Revision 1.5  2006/04/07 12:29:03  haraldkipp
 * Number of read retries increased. Memory hole fixed.
 * Added ioctl(NUTBLKDEV_MEDIAAVAIL).
 * Card change ioctl() will also return 1 if no card is available.
 *
 * Revision 1.4  2006/02/23 15:43:56  haraldkipp
 * Timeout value increased. Some cards have long write latencies.
 *
 * Revision 1.3  2006/01/22 17:36:31  haraldkipp
 * Some cards need more time to enter idle state.
 * Card access now returns an error after card change detection.
 *
 * Revision 1.2  2006/01/19 18:40:08  haraldkipp
 * Timeouts increased and long time sleeps decreased for better performance.
 *
 * Revision 1.1  2006/01/05 16:30:49  haraldkipp
 * First check-in.
 *
 *
 * \endverbatim
 */

#include <cfg/os.h>
#include <cfg/mmci.h>

#if defined(NUTDEBUG)
#include <stdio.h>
#endif

#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <memdebug.h>

#include <sys/heap.h>
#include <sys/timer.h>
#include <sys/event.h>
#include <fs/dospart.h>
#include <fs/fs.h>

#include <dev/blockdev.h>
#include <dev/mmcard.h>

/*!
 * \addtogroup xgMmCard
 */
/*@{*/

#ifndef MMC_BLOCK_SIZE
/*!
 * \brief Block size.
 *
 * Block size in bytes. Do not change unless you are sure that both,
 * the file system and the hardware support it.
 */
#define MMC_BLOCK_SIZE          512
#endif

#ifndef MMC_MAX_INIT_POLLS
/*!
 * \brief Card init timeout.
 *
 * Max. number of loops waiting for card's idle mode after initialization.
 * An additional delay of 1 ms is added to each loop after one quarter of
 * this value elapsed.
 */
#define MMC_MAX_INIT_POLLS      512
#endif

#ifndef MMC_MAX_INIT_POLLS_SDHC
/*!
 * \brief Card init timeout for SDHC cards.
 */
#define MMC_MAX_INIT_POLLS_SDHC    4096
#endif


#ifndef MMC_MAX_RESET_POLLS
/*!
 * \brief Card reset timeout.
 *
 * Max. number of loops waiting for card's idle mode after resetting it.
 */
#define MMC_MAX_RESET_POLLS     255
#endif

#ifndef MMC_MAX_WRITE_POLLS
/*!
 * \brief Card write timeout.
 *
 * Max. number of loops waiting for card's idle mode after resetting it.
 * An additional delay of 1 ms is added to each loop after 31/32 of this
 * value elapsed.
 */
#define MMC_MAX_WRITE_POLLS     1024
#endif

#ifndef MMC_MAX_WRITE_RETRIES
/*!
 * \brief Card write retries.
 *
 * Max. number of retries while writing.
 */
#define MMC_MAX_WRITE_RETRIES   32
#endif

#ifndef MMC_MAX_READ_RETRIES
/*!
 * \brief Card read retries.
 *
 * Max. number of retries while reading.
 */
#define MMC_MAX_READ_RETRIES    8
#endif

#ifndef MMC_MAX_REG_POLLS
/*!
 * \brief Register read timeout.
 *
 * Max. number of loops while reading a card's register.
 */
#define MMC_MAX_REG_POLLS       512
#endif

#ifndef MMC_MAX_CMDACK_POLLS
/*!
 * \brief Command acknowledge timeout.
 *
 * Max. number of loops waiting for card's acknowledge of a command.
 * An additional delay of 1 ms is added to each loop after three quarter
 * of this value elapsed.
 */
#define MMC_MAX_CMDACK_POLLS    1024
#endif

#ifndef MMC_MAX_R1_POLLS
/*!
 * \brief R1 response timeout.
 *
 * Max. number of loops waiting for card's R1 response.
 */
#define MMC_MAX_R1_POLLS        1024
#endif

/*!
 * \brief Local multimedia card mount information.
 */
typedef struct _MMCFCB {
    /*! \brief Attached file system device.
     */
    NUTDEVICE *fcb_fsdev;

    /*! \brief Partition table entry of the currently mounted partition.
     */
    DOSPART fcb_part;

    /*! \brief Next block number to read.
     *
     * The file system driver will send a NUTBLKDEV_SEEK control command
     * to set this value before calling the read or the write routine.
     *
     * The number is partition relative.
     */
    uint32_t fcb_blknum;

    /*! \brief Internal block buffer.
     *
     * A file system driver may use this one or optionally provide it's
     * own buffers.
     *
     * Minimal systems may share their external bus interface with
     * device I/O lines, in which case the buffer must be located
     * in internal memory.
     */
    uint8_t fcb_blkbuf[MMC_BLOCK_SIZE];
} MMCFCB;

/*
 * Several routines call NutSleep, which results in a context switch.
 * This mutual exclusion semaphore takes care, that multiple threads
 * do not interfere with each other.
 */
static HANDLE mutex;

/*!
 * \brief Send command to multimedia card.
 *
 * \param ifc   Specifies the hardware interface.
 * \param cmd   Command code. See MMCMD_ macros.
 * \param param Optional command parameter.
 */
static void MmCardTxCmd(MMCIFC * ifc, uint8_t cmd, uint32_t param)
{
    unsigned int tmo = MMC_MAX_CMDACK_POLLS;
    uint8_t ch;

    /* Enable card select. */
    (*ifc->mmcifc_cs) (1);
    /*
     * Repeat sending nothing until we receive nothing. Actually
     * it should be sufficient to send a single 0xFF value, but
     * running a loop seems to fix certain kind of sync problems.
     */
    while ((ch = (*ifc->mmcifc_io) (0xFF)) != 0xFF) {
        if (--tmo == 0) {
#ifdef NUTDEBUG
            printf("[MMCmd%u Timeout %02X]\n", cmd, ch);
#endif
            break;
        }
        if (tmo < MMC_MAX_CMDACK_POLLS / 4) {
            NutSleep(1);
        }
    }
    /* Send command and parameter. */
    (*ifc->mmcifc_io) (MMCMD_HOST | cmd);
    (*ifc->mmcifc_io) ((uint8_t) (param >> 24));
    (*ifc->mmcifc_io) ((uint8_t) (param >> 16));
    (*ifc->mmcifc_io) ((uint8_t) (param >> 8));
    (*ifc->mmcifc_io) ((uint8_t) param);
    /*
     * We are running with CRC disabled. However, the reset and excsd commands 
     * must be send with a valid CRC. Fortunately these commands are sent with a
     * fixed parameter value of zero, which results in a fixed CRC value
     */

    if (cmd == MMCMD_SEND_IF_COND) {
        (*ifc->mmcifc_io) (MMCMD_IF_COND_CRC);
    } else {
        (*ifc->mmcifc_io) (MMCMD_RESET_CRC);
    }


}

/*!
 * \brief Receive an R1 response token from the card.
 *
 * In SPI mode, the card sends an R1 response after every command except
 * after the SEND_STATUS and the READ_OCR commands.
 *
 * \param ifc Specifies the hardware interface.
 *
 * \return R1 response token or 0xFF on timeout.
 */
static uint8_t MmCardRxR1(MMCIFC * ifc)
{
    uint8_t rc;
    int i;

    for (i = 0; i < MMC_MAX_R1_POLLS; i++) {
        if ((rc = (*ifc->mmcifc_io) (0xFF)) != 0xFF) {
            break;
        }
    }
    return rc;
}

/*!
 * \brief Receive an R2 response token from the card.
 *
 * In SPI mode the card sends this token in response to the SEND_STATUS
 * command.
 *
 * \param ifc Specifies the hardware interface.
 *
 * \return R2 response token or 0xFFFF on timeout.
 */
static uint16_t MmCardRxR2(MMCIFC * ifc)
{
    uint16_t rc;

    rc = MmCardRxR1(ifc);
    rc <<= 8;
    rc += (*ifc->mmcifc_io) (0xFF);

    return rc;
}

/*!
 * \brief Receive an R3 response token from the card.
 *
 * In SPI mode the card sends this token in response to the READ_OCR
 * command.
 *
 * \param ifc Specifies the hardware interface.
 * \param ocr Points to a buffer which receives the OCR register contents.
 *
 * \return R1 response token or 0xFF on timeout.
 */
static uint8_t MmCardRxR3(MMCIFC * ifc, uint32_t * ocr)
{
    uint8_t rc;
    int i;

    /* The first byte is equal to the R1 response. */
    rc = MmCardRxR1(ifc);
    /* Receive the operating condition. */
    for (i = 0; i < 4; i++) {
        *ocr <<= 8;
        *ocr |= (*ifc->mmcifc_io) (0xFF);
    }
    return rc;
}

/*!
 * \brief Configure card for SPI mode.
 *
 * \param ifc Specifies the hardware interface.
 *
 * \return 0 on success, -1 otherwise.
 */
static int MmCardReset(MMCIFC * ifc)
{
    int i;
    uint8_t rsp;

    /*
     * Initialize the low level card interface.
     */
    if ((*ifc->mmcifc_in) ()) {
        return -1;
    }

    /*
     * 80 bits of ones with deactivated chip select will put the card
     * in SPI mode.
     *
     * /note that spec 2.0 tells us that we need to send the CMD0 with CS inactive
     * to get the card in SPI-mode. This 'old' methode however still seems to work 
     * with 2.0 cards
     */
    (*ifc->mmcifc_cs) (0);
    for (i = 0; i < 10; i++) {
        (*ifc->mmcifc_io) (0xFF);
    }

    /*
     * Switch to idle state and wait until initialization is running
     * or idle state is reached.
     */
    for (i = 0; i < MMC_MAX_RESET_POLLS; i++) {
        MmCardTxCmd(ifc, MMCMD_GO_IDLE_STATE, 0);
        rsp = MmCardRxR1(ifc);
        (*ifc->mmcifc_cs) (0);
        if (rsp == MMR1_IDLE_STATE || rsp == MMR1_NOT_IDLE) {
            return 0;
        }
    }
    return -1;
}

/*!
 * \brief Initialize the multimedia card.
 *
 * This routine will put a newly powered up card into SPI mode.
 * It is called by MmCardMount().
 *
 * \param ifc Specifies the hardware interface.
 *
 * \return 0 on success, -1 otherwise.
 */
static int MmCardInit(MMCIFC * ifc)
{
    int i;
    uint8_t rsp;
    uint8_t ocr[4];

    /*
     *  assume BLOCK_MODE addressing for most cards.
     *  if the cards turns out to be a SD_HC card, the mode will
     *  be set to BYTE_MODE later on
     */
    (*ifc->mmcifc_sm) (MMC_BLOCK_MODE);    

    /*
     * Try to switch to SPI mode. Looks like a retry helps to fix
     * certain synchronization problems.
     */
    if (MmCardReset(ifc)) {
        if (MmCardReset(ifc)) {
#ifdef NUTDEBUG
            printf("[CardReset failed]");
#endif
            return -1;
        }
    }

    /*
     *  Let's check if we deal with a card that 
     *  support the 2.0 specs by sending the EXTCSD command. 
     *  If the card response is "illegal Command", we know 
     *  that the 2.0 specs are not supported. 
     *  Note that SD-HC cards always support the 2.0 specs
     */
    MmCardTxCmd(ifc, MMCMD_SEND_IF_COND, 0x1AA);
    rsp = MmCardRxR1(ifc);

    /* Receive the data. */
    for (i = 0; i < 4; i++) {
        ocr[i] = (*ifc->mmcifc_io) (0xFF);
    }

    if ((ocr[2] == 0x01) && (ocr[3] == 0xAA)) {
        // The card can work at vdd range of 2.7-3.6V 
#ifdef NUTDEBUG
        printf("[SD -> support for 2.0 specs]");
#endif

        /*
         * Wait for a really long time until card is initialized
         * and enters idle state.
         */
        for (i = 0; i < MMC_MAX_INIT_POLLS_SDHC; i++) {
            MmCardTxCmd(ifc, MMCMD_SEND_APP_CMD, 0);
            if (MmCardRxR1(ifc) <= MMR1_NOT_IDLE) {
                /* some cards really need a lot of time for their initialisation... */
                NutSleep(10);

                /* ACMD41 with HCS bit */
                MmCardTxCmd(ifc, MMCMD_SEND_APP_OP_COND, 1UL << 30);

                /* check response */
                if (MmCardRxR1(ifc)==MMR1_IDLE_STATE) {
#ifdef NUTDEBUG
                    printf("[CardIdle]");
#endif
                    // Check the CSS bit that tells us if we are dealing with HC-cards
                    MmCardTxCmd(ifc, MMCMD_READ_OCR, 0);
                    rsp = MmCardRxR1(ifc);

                    /* Receive the data. */
                    for (i = 0; i < 4; i++) {
                        ocr[i] = (*ifc->mmcifc_io) (0xFF);
                    }

                    if (ocr[0] & 0x40) {
                        /* set byte addressing mode for SD-HC cards */
                        (*ifc->mmcifc_sm) (MMC_BYTE_MODE);
#ifdef NUTDEBUG
                        printf("[Found SD-HC]");
#endif
                    }

                    /* Initialize MMC access mutex semaphore. */
                    NutEventPost(&mutex);
                    return 0;      /* initialisation finished */
                }
            }
        }
        /* Card stays in IDLE state for some reason... */
#ifdef NUTDEBUG
        printf("[CardInit failed [%d]]", rsp);
#endif

        return(-1);
    }
    else {
        /*
         * Wait for a really long time until card is initialized
         * and enters idle state.
         */
        for (i = 0; i < MMC_MAX_INIT_POLLS; i++) {
            /*
             * In SPI mode SEND_OP_COND is a dummy, used to poll the card
             * for initialization finished. Thus, there are no parameters
             * and no operation condition data is sent back.
             */
            MmCardTxCmd(ifc, MMCMD_SEND_OP_COND, 0);
            rsp = MmCardRxR1(ifc);

            (*ifc->mmcifc_cs) (0);

            if (rsp == MMR1_IDLE_STATE) {
#ifdef NUTDEBUG
                printf("[CardIdle]");
#endif
                /* Initialize MMC access mutex semaphore. */
                NutEventPost(&mutex);
                return 0;
            }
            if (i > MMC_MAX_INIT_POLLS / 4) {
                NutSleep(1);
            }
        }
#ifdef NUTDEBUG
        printf("[CardInit failed [%d]]", rsp);
#endif
    }
    return -1;
}

/*!
 * \brief Read or verify a single block.
 *
 * \param ifc Specifies the hardware interface.
 * \param blk Block number to read or verify.
 * \param buf Data buffer. Receives the data or is verified against the
 *            data being read from the card.
 *
 * \return 0 on success, -1 otherwise.
 */
static int MmCardReadOrVerify(MMCIFC * ifc, uint32_t blk, uint8_t * buf, int vflg)
{
    int rc = -1;
    int retries = 64;
    int i;
    uint8_t rsp;

    /* Gain mutex access. */
    NutEventWait(&mutex, 0);

    /*
     *  SD_HC cards use BYTE_ADDRESSING, which means that the partion info 
     *  that indicates the starting address of the BOOT_RECORD is in bytes (and
     *  not in sectors like with non SD_HC cards).
     *  Please note that the sector size is not officially known at this stage since
     *  this code does NOT read the CDS register. However, for SD-HC cards fortunately
     *  it is defined that the sectorsize is always 512.
     */
    if ((*ifc->mmcifc_gm) () == MMC_BLOCK_MODE) {
        blk <<= 9;
    }

    while (retries--) {
        MmCardTxCmd(ifc, MMCMD_READ_SINGLE_BLOCK, blk);
        if ((rsp = MmCardRxR1(ifc)) == 0x00) {
            if ((rsp = MmCardRxR1(ifc)) == 0xFE) {
                rc = 0;
                if (vflg) {
                    for (i = 0; i < MMC_BLOCK_SIZE; i++) {
                        if (*buf != (*ifc->mmcifc_io) (0xFF)) {
                            rc = -1;
                        }
                        buf++;
                    }
                } else {
                    for (i = 0; i < MMC_BLOCK_SIZE; i++) {
                        *buf = (*ifc->mmcifc_io) (0xFF);
                        buf++;
                    }
                }
                (*ifc->mmcifc_io) (0xff);
                (*ifc->mmcifc_io) (0xff);
                (*ifc->mmcifc_cs) (0);
                break;
            }
        }
        (*ifc->mmcifc_cs) (0);
    }

    /* Release mutex access. */
    NutEventPost(&mutex);

    return rc;
}

/*!
 * \brief Write a single block.
 *
 * \param ifc Specifies the hardware interface.
 * \param blk Block number to read or verify.
 * \param buf Pointer to a buffer which holds the data to write.
 *
 * \return 0 on success, -1 otherwise.
 */
static int MmCardWrite(MMCIFC * ifc, uint32_t blk, const uint8_t * buf)
{
    int rc = -1;
    int retries = MMC_MAX_WRITE_RETRIES;
    int tmo;
    int i;
    uint8_t rsp;

    /* Gain mutex access. */
    NutEventWait(&mutex, 0);

    /*
     *  SD_HC cards use BYTE_ADDRESSING, which means that the partion info 
     *  that indicates the starting address of the BOOT_RECORD is in bytes (and
     *  not in sectors like with non SD_HC cards).
     *  Please note that the sector size is not officially known at this stage since
     *  this code does NOT read the CDS register. However, for SD-HC cards fortunately
     *  it is defined that the sectorsize is always 512.
     */
    if ((*ifc->mmcifc_gm) () == MMC_BLOCK_MODE) {
        blk <<= 9;
    }

    while (retries--) {
        MmCardTxCmd(ifc, MMCMD_WRITE_BLOCK, blk);
        if ((rsp = MmCardRxR1(ifc)) == 0x00) {
            (*ifc->mmcifc_io) (0xFF);
            (*ifc->mmcifc_io) (0xFE);
            for (i = 0; i < MMC_BLOCK_SIZE; i++) {
                (*ifc->mmcifc_io) (*buf);
                buf++;
            }
            // (*ifc->mmcifc_io)(0xFF);
            // (*ifc->mmcifc_io)(0xFF);
            if ((rsp = MmCardRxR1(ifc)) == 0xE5) {
                for (tmo = 0; tmo < MMC_MAX_WRITE_POLLS; tmo++) {
                    if ((*ifc->mmcifc_io) (0xFF) == 0xFF) {
                        break;
                    }
                    if (tmo > MMC_MAX_WRITE_POLLS - MMC_MAX_WRITE_POLLS / 32) {
                        NutSleep(1);
                    }
                }
                if (tmo) {
                    rc = 0;
                    break;
                }
#ifdef NUTDEBUG
                printf("[MMCWR Timeout]\n");
#endif
            }
        }
        (*ifc->mmcifc_cs) (0);
    }
    (*ifc->mmcifc_cs) (0);

    /* Release mutex access. */
    NutEventPost(&mutex);

    return rc;
}

/*!
 * \brief Read data blocks from a mounted partition.
 *
 * Applications should not call this function directly, but use the
 * stdio interface.
 *
 * \param nfp    Pointer to a ::NUTFILE structure, obtained by a previous
 *               call to MmCardMount().
 * \param buffer Pointer to the data buffer to fill.
 * \param num    Maximum number of blocks to read. However, reading
 *               multiple blocks is not yet supported by this driver.
 *
 * \return The number of blocks actually read. A return value of -1
 *         indicates an error.
 */
int MmCardBlockRead(NUTFILE * nfp, void *buffer, int num)
{
    MMCFCB *fcb = (MMCFCB *) nfp->nf_fcb;
    uint32_t blk = fcb->fcb_blknum;
    NUTDEVICE *dev = (NUTDEVICE *) nfp->nf_dev;
    MMCIFC *ifc = (MMCIFC *) dev->dev_icb;

    if ((*ifc->mmcifc_cd) () != 1) {
        return -1;
    }
    if (buffer == 0) {
        buffer = fcb->fcb_blkbuf;
    }
    /*
     *  when using the filesystem, the sectornumbering is different then when 
     *  directly accesing the card. For example, the MBR can be found at the 
     *  sector 0 of the card, but the filesystem's first sector is the sector 
     *  where the start is of the FAT VolumeID (also called the BOOT SECTOR). 
     *  This position (or offset) is indicated by reading the partion-table, 
     *  more specific: by reading the LBA begin info.
     *  This offset we need to add here to the sector# we get in as 
     *  parameter. This way we acces the real sector on the card.
     *
     */
    blk += fcb->fcb_part.part_sect_offs;

#ifdef MMC_VERIFY_AFTER
    {
        int i;
        /*
         * It would be much better to verify the checksum than to re-read
         * and verify the data block.
         */
        for (i = 0; i < MMC_MAX_READ_RETRIES; i++) {
            if (MmCardReadOrVerify(ifc, blk, buffer, 0) == 0) {
                if (MmCardReadOrVerify(ifc, blk, buffer, 1) == 0) {
                    return 1;
                }
            }
        }
    }
#else
    if (MmCardReadOrVerify(ifc, blk, buffer, 0) == 0) {
        return 1;
    }
#endif
    return -1;
}

/*!
 * \brief Write data blocks to a mounted partition.
 *
 * Applications should not call this function directly, but use the
 * stdio interface.
 *
 * \param nfp    Pointer to a \ref NUTFILE structure, obtained by a previous
 *               call to MmCardMount().
 * \param buffer Pointer to the data to be written.
 * \param num    Maximum number of blocks to write. However, writing
 *               multiple blocks is not yet supported by this driver.
 *
 * \return The number of blocks written. A return value of -1 indicates an
 *         error.
 */
int MmCardBlockWrite(NUTFILE * nfp, const void *buffer, int num)
{
    MMCFCB *fcb = (MMCFCB *) nfp->nf_fcb;
    uint32_t blk = fcb->fcb_blknum;
    NUTDEVICE *dev = (NUTDEVICE *) nfp->nf_dev;
    MMCIFC *ifc = (MMCIFC *) dev->dev_icb;

    if ((*ifc->mmcifc_cd) () != 1) {
        return -1;
    }
    if (buffer == 0) {
        buffer = fcb->fcb_blkbuf;
    }
    blk += fcb->fcb_part.part_sect_offs;

#ifdef MMC_VERIFY_AFTER
    {
        int i;

        for (i = 0; i < MMC_MAX_READ_RETRIES; i++) {
            if (MmCardWrite(ifc, blk, buffer) == 0) {
                if (MmCardReadOrVerify(ifc, blk, (void *) buffer, 1) == 0) {
                    return 1;
                }
                if (MmCardReadOrVerify(ifc, blk, (void *) buffer, 1) == 0) {
                    return 1;
                }
            }
        }
    }
#else
    if (MmCardWrite(ifc, blk, buffer) == 0) {
        return 1;
    }
#endif
    return -1;
}

#ifdef __HARVARD_ARCH__
/*!
 * \brief Write data blocks from program space to a mounted partition.
 *
 * This function is not yet implemented and will always return -1.
 *
 * Similar to MmCardBlockWrite() except that the data is located in
 * program memory.
 *
 * Applications should not call this function directly, but use the
 * stdio interface.
 *
 * \param nfp    File pointer to a previously opened device.
 * \param buffer Pointer to the data bytes in program space to be written.
 * \param num    Maximum number of blocks to write. However, writing
 *               multiple blocks is not yet supported by this driver.
 *
 * \return The number of blocks written. A return value of -1 indicates an
 *         error.
 */
int MmCardBlockWrite_P(NUTFILE * nfp, PGM_P buffer, int num)
{
    return -1;
}
#endif

/*!
 * \brief Mount a partition.
 *
 * Nut/OS doesn't provide specific routines for mounting. Instead routines
 * for opening files are used.
 *
 * Applications should not directly call this function, but use the high
 * level stdio routines for opening a file.
 *
 * \param dev  Pointer to the MMC device.
 * \param name Partition number followed by a slash followed by a name
 *             of the file system device. Both items are optional. If no
 *             file system driver name is given, the first file system
 *             driver found in the list of registered devices will be
 *             used. If no partition number is specified or if partition
 *             zero is given, the first active primary partition will be
 *             used.
 * \param mode Opening mode. Currently ignored, but
 *             \code _O_RDWR | _O_BINARY \endcode should be used for
 *             compatibility with future enhancements.
 * \param acc  File attributes, ignored.
 *
 * \return Pointer to a newly created file pointer to the mounted
 *         partition or NUTFILE_EOF in case of any error.
 */
NUTFILE *MmCardMount(NUTDEVICE * dev, const char *name, int mode, int acc)
{
    int partno = 0;
    int i;
    NUTDEVICE *fsdev;
    NUTFILE *nfp;
    MMCFCB *fcb;
    DOSPART *part;
    MMCIFC *ifc = (MMCIFC *) dev->dev_icb;
    FSCP_VOL_MOUNT mparm;

    /* Return an error if no card is detected. */
    if ((*ifc->mmcifc_cd) () == 0) {
        errno = ENODEV;
        return NUTFILE_EOF;
    }

    /* Set the card in SPI mode and check for SD-HC cards. */
    if (MmCardInit(ifc)) {
        errno = ENODEV;
        return NUTFILE_EOF;
    }

    /* Parse the name for a partition number and a file system driver. */
    if (*name) {
        partno = atoi(name);
        do {
            name++;
        } while (*name && *name != '/');
        if (*name == '/') {
            name++;
        }
    }

    /*
     * Check the list of registered devices for the given name of the
     * files system driver. If none has been specified, get the first
     * file system driver in the list. Hopefully the application
     * registered one only.
     */
    for (fsdev = nutDeviceList; fsdev; fsdev = fsdev->dev_next) {
        if (*name == 0) {
            if (fsdev->dev_type == IFTYP_FS) {
                break;
            }
        } else 
        if (strcmp(fsdev->dev_name, name) == 0) {
            break;
        }
    }

    if (fsdev == 0) {
#ifdef NUTDEBUG
        printf("[No FSDriver]");
#endif
        errno = ENODEV;
        return NUTFILE_EOF;
    }

    if ((fcb = calloc(1, sizeof(MMCFCB))) == 0) {
        errno = ENOMEM;
        return NUTFILE_EOF;
    }
    fcb->fcb_fsdev = fsdev;

    /* Read MBR. */
    if (MmCardReadOrVerify(ifc, 0, fcb->fcb_blkbuf, 0)) {
        free(fcb);
        return NUTFILE_EOF;
    }
    /* Check for the cookie at the end of this sector. */
    if (fcb->fcb_blkbuf[DOSPART_MAGICPOS] != 0x55 || fcb->fcb_blkbuf[DOSPART_MAGICPOS + 1] != 0xAA) {
#ifdef NUTDEBUG
        printf("[MBR corrupted]");
#endif
        free(fcb);
        return NUTFILE_EOF;
    }
    /* Check for the partition table. */
    if(fcb->fcb_blkbuf[DOSPART_TYPEPOS] == 'F' &&
       fcb->fcb_blkbuf[DOSPART_TYPEPOS + 1] == 'A' &&
       fcb->fcb_blkbuf[DOSPART_TYPEPOS + 2] == 'T') {
        /* No partition table. Assume FAT12 and 32MB size. */
        fcb->fcb_part.part_type = PTYPE_FAT12;
        fcb->fcb_part.part_sect_offs = 0;
        fcb->fcb_part.part_sects = 65536; /* How to find out? */
    }
    else {
        /* Read partition table. */
        part = (DOSPART *) & fcb->fcb_blkbuf[DOSPART_SECTORPOS];
        for (i = 1; i <= 4; i++) {
            if (partno) {
                if (i == partno) {
                    /* Found specified partition number. */
                    fcb->fcb_part = *part;
                    break;
                }
            } else if (part->part_state & 0x80) {
                /* Located first active partition. */
                fcb->fcb_part = *part;
                break;
            }
            part++;
        }

        if (fcb->fcb_part.part_type == PTYPE_EMPTY) {
            free(fcb);
            return NUTFILE_EOF;
        }
    }

    if ((nfp = NutHeapAlloc(sizeof(NUTFILE))) == 0) {
        free(fcb);
        errno = ENOMEM;
        return NUTFILE_EOF;
    }
    nfp->nf_dev = dev;
    nfp->nf_fcb = fcb;

    /*
     * Mount the file system volume.
     */
    mparm.fscp_bmnt = nfp;
    mparm.fscp_part_type = fcb->fcb_part.part_type;
    if (fsdev->dev_ioctl(fsdev, FS_VOL_MOUNT, &mparm)) {
        MmCardUnmount(nfp);
        return NUTFILE_EOF;
    }
    return nfp;
}

/*!
 * \brief Unmount a previously mounted partition.
 *
 * Applications should not directly call this function, but use the high
 * level stdio routines for closing a previously opened file.
 *
 * \return 0 on success, -1 otherwise.
 */
int MmCardUnmount(NUTFILE * nfp)
{
    int rc = -1;

    if (nfp) {
        MMCFCB *fcb = (MMCFCB *) nfp->nf_fcb;

        if (fcb) {
            NUTDEVICE *dev = (NUTDEVICE *) nfp->nf_dev;
            MMCIFC *ifc = (MMCIFC *) dev->dev_icb;

            if ((*ifc->mmcifc_cd) () == 1) {
                rc = fcb->fcb_fsdev->dev_ioctl(fcb->fcb_fsdev, FS_VOL_UNMOUNT, NULL);
            }
            free(fcb);
        }
        free(nfp);
    }
    return rc;
}

/*!
 * \brief Retrieve contents of a card register.
 *
 * \param ifc Specifies the hardware interface.
 * \param cmd This command is sent to the card to retrieve the contents
 *            of a specific register.
 * \param rbp Pointer to the buffer that receives the register contents.
 * \param siz Size of the specified register.
 *
 * \return 0 on success, -1 otherwise.
 */
static int MmCardGetReg(MMCIFC * ifc, uint8_t cmd, uint8_t * rbp, int siz)
{
    int rc = -1;
    int retries = MMC_MAX_REG_POLLS;
    int i;

    /* Gain mutex access. */
    NutEventWait(&mutex, 0);

    while (retries--) {
        /* Send the command to the card. This will select the card. */
        MmCardTxCmd(ifc, cmd, 0);
        /* Wait for OK response from the card. */
        if (MmCardRxR1(ifc) == 0x00) {
            /* Wait for data from the card. */
            if (MmCardRxR1(ifc) == 0xFE) {
                for (i = 0; i < siz; i++) {
                    *rbp++ = (*ifc->mmcifc_io) (0xFF);
                }
                /* Ignore the CRC. */
                (*ifc->mmcifc_io) (0xFF);
                (*ifc->mmcifc_io) (0xFF);
                /* De-activate card selection. */
                (*ifc->mmcifc_cs) (0);
                /* Return a positive result. */
                rc = 0;
                break;
            }
        }
        /* De-activate card selection. */
        (*ifc->mmcifc_cs) (0);
    }

    /* Release mutex access. */
    NutEventPost(&mutex);

    return rc;
}

/*!
 * \brief Perform MMC control functions.
 *
 * This function is called by the ioctl() function of the C runtime
 * library. Applications should not directly call this function.
 *
 * \todo Card change detection should verify the serial card number.
 *
 * \param dev  Identifies the device that receives the device-control
 *             function.
 * \param req  Requested control function. May be set to one of the
 *             following constants:
 *             - \ref NUTBLKDEV_MEDIACHANGE
 *             - \ref NUTBLKDEV_INFO
 *             - \ref NUTBLKDEV_SEEK
 *             - \ref MMCARD_GETCID
 *             - \ref MMCARD_GETCSD
 *
 * \param conf Points to a buffer that contains any data required for
 *             the given control function or receives data from that
 *             function.
 * \return 0 on success, -1 otherwise.
 */
int MmCardIOCtl(NUTDEVICE * dev, int req, void *conf)
{
    int rc = 0;
    MMCIFC *ifc = (MMCIFC *) dev->dev_icb;

    switch (req) {
    case NUTBLKDEV_MEDIAAVAIL:
        {
            int *flg = (int *) conf;
            *flg = (*ifc->mmcifc_cd) ();
        }
        break;
    case NUTBLKDEV_MEDIACHANGE:
        {
            int *flg = (int *) conf;
            if ((*ifc->mmcifc_cd) () != 1) {
                *flg = 1;
            } else {
                *flg = 0;
            }
        }
        break;
    case NUTBLKDEV_INFO:
        {
            BLKPAR_INFO *par = (BLKPAR_INFO *) conf;
            MMCFCB *fcb = (MMCFCB *) par->par_nfp->nf_fcb;

            /*
             *  note that we don't read the card's CSD-register for this info,
             *  in stead, we use the info that we found in the partition table of
             *  the formatted card.
             */
            par->par_nblks = fcb->fcb_part.part_sects;
            par->par_blksz = MMC_BLOCK_SIZE;
            par->par_blkbp = fcb->fcb_blkbuf;
        }
        break;
    case NUTBLKDEV_SEEK:
        {
            BLKPAR_SEEK *par = (BLKPAR_SEEK *) conf;
            MMCFCB *fcb = (MMCFCB *) par->par_nfp->nf_fcb;

            fcb->fcb_blknum = par->par_blknum;
        }
        break;
    case MMCARD_GETSTATUS:
        {
            uint16_t *s = (uint16_t *) conf;

            /* Gain mutex access. */
            NutEventWait(&mutex, 0);

            MmCardTxCmd(ifc, MMCMD_SEND_STATUS, 0);
            *s = MmCardRxR2(ifc);

            /* Release mutex access. */
            NutEventPost(&mutex);
        }
        break;
    case MMCARD_GETOCR:
        /* Gain mutex access. */
        NutEventWait(&mutex, 0);

        MmCardTxCmd(ifc, MMCMD_READ_OCR, 0);
        if (MmCardRxR3(ifc, (uint32_t *) conf) != MMR1_IDLE_STATE) {
            rc = -1;
        }

        /* Release mutex access. */
        NutEventPost(&mutex);
        break;
    case MMCARD_GETCID:
        rc = MmCardGetReg(ifc, MMCMD_SEND_CID, (uint8_t *) conf, sizeof(MMC_CID));
        break;
    case MMCARD_GETCSD:
        rc = MmCardGetReg(ifc, MMCMD_SEND_CSD, (uint8_t *) conf, sizeof(MMC_CSD));
        break;
    default:
        rc = -1;
        break;
    }
    return rc;
}

/*!
 * \brief Initialize high level MMC driver.
 *
 * Applications should not directly call this function. It is
 * automatically executed during during device registration by
 * NutRegisterDevice().
 *
 * \param dev  Identifies the device to initialize.
 *
 * \return Always zero.
 */
int MmCardDevInit(NUTDEVICE * dev)
{
    return 0;
}

/*@}*/
