/* * Copyright (C) 2006 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 Low Level Multimedia Card Access. * * Low level MMC hardware routines for SPI emulation by software * (bit banging). * * These routines support SPI mode only and are required by the * basic MMC driver. * * \verbatim * * $Log$ * Revision 1.7 2008/08/11 06:59:42 haraldkipp * BSD types replaced by stdint types (feature request #1282721). * * Revision 1.6 2006/06/28 17:22:34 haraldkipp * Make it compile for AT91SAM7X256. * * Revision 1.5 2006/04/07 12:48:41 haraldkipp * Port configuration is now honored. * * Revision 1.4 2006/03/16 19:07:36 haraldkipp * Disabled inline assembly routine for ARM bit banging SPI. Stopped working * with optimizing compiler. * * Revision 1.3 2006/03/02 19:58:16 haraldkipp * Too lazy to port the inline assembly to ICCARM. * * Revision 1.2 2006/02/28 02:16:12 hwmaier * Added macro definition MCU_AT90CAN128 * * Revision 1.1 2006/02/23 15:38:03 haraldkipp * MMC low level bit banging SPI added. Compiles on all supported platforms, * but tested with AT91 only. * * * \endverbatim */ #include #include #include #include #include #include #if 0 /* Use for local debugging. */ #define NUTDEBUG #include #endif /*! * \addtogroup xgSbiMmc */ /*@{*/ #ifndef SPI0_CS_BIT /*! \brief Port bit for SPI chip select. */ #define SPI0_CS_BIT 6 #endif #ifndef SPI0_CLK_BIT /*! \brief Port bit for SPI clock. */ #define SPI0_CLK_BIT 4 #endif #ifndef SPI0_MOSI_BIT /*! \brief Port bit for SPI MOSI line. */ #define SPI0_MOSI_BIT 5 #endif #ifndef SPI0_MISO_BIT /*! \brief Port bit for SPI MISO line. */ #define SPI0_MISO_BIT 3 #endif #if defined(MCU_AT91R40008) || defined(MCU_AT91SAM7X) /* MCU ---------------------- */ #ifndef SPI0_PE_REG /*! \brief SPI port enable register. */ #define SPI0_PE_REG PIO_PER #endif #ifndef SPI0_PD_REG /*! \brief SPI port disable register. */ #define SPI0_PD_REG PIO_PDR #endif #ifndef SPI0_OE_REG /*! \brief SPI output enable register. */ #define SPI0_OE_REG PIO_OER #endif #ifndef SPI0_OD_REG /*! \brief SPI output disable register. */ #define SPI0_OD_REG PIO_ODR #endif #ifndef SPI0_SOD_REG /*! \brief SPI set output data register. */ #define SPI0_SOD_REG PIO_SODR #endif #ifndef SPI0_COD_REG /*! \brief SPI clear output data register. */ #define SPI0_COD_REG PIO_CODR #endif #ifndef SPI0_PDS_REG /*! \brief SPI port data status register. */ #define SPI0_PDS_REG PIO_PDSR #endif #ifndef SPI0_ODS_REG /*! \brief SPI port output status register. */ #define SPI0_ODS_REG PIO_ODSR #endif #ifdef MMC0_CD_BIT #if MMC0_CD_BIT == 9 #define SIG_MMC0DETECT sig_INTERRUPT0 #elif MMC0_CD_BIT == 10 #define SIG_MMC0DETECT sig_INTERRUPT1 #elif MMC0_CD_BIT == 11 #define SIG_MMC0DETECT sig_INTERRUPT2 #else #warning "No external interrupt for card detect" #undef MMC0_CD_BIT #endif #endif #elif defined(MCU_ATMEGA2561) || defined(MCU_AT90CAN128) || defined(MCU_ATMEGA128) || defined(MCU_ATMEGA103) /* MCU */ #ifndef inr #define inr(a) inb(a) #endif #ifndef outr #define outr(a, v) outb(a, v) #endif #ifndef SPI0_OE_REG #if SPI0_PORT == AVRPORTA #define SPI0_OE_REG DDRA #elif SPI0_PORT == AVRPORTC #define SPI0_OE_REG DDRC #elif SPI0_PORT == AVRPORTD #define SPI0_OE_REG DDRD #elif SPI0_PORT == AVRPORTE #define SPI0_OE_REG DDRE #elif SPI0_PORT == AVRPORTF #define SPI0_OE_REG DDRF #elif SPI0_PORT == AVRPORTG #define SPI0_OE_REG DDRG #else /*! \brief SPI port enable register. */ #define SPI0_OE_REG DDRB #endif #endif #ifndef SPI0_SOD_REG #if SPI0_PORT == AVRPORTA #define SPI0_SOD_REG PORTA #elif SPI0_PORT == AVRPORTC #define SPI0_SOD_REG PORTC #elif SPI0_PORT == AVRPORTD #define SPI0_SOD_REG PORTD #elif SPI0_PORT == AVRPORTE #define SPI0_SOD_REG PORTE #elif SPI0_PORT == AVRPORTF #define SPI0_SOD_REG PORTF #elif SPI0_PORT == AVRPORTG #define SPI0_SOD_REG PORTG #else /*! \brief SPI port output register. */ #define SPI0_SOD_REG PORTB #endif #endif #ifndef SPI0_PDS_REG #if SPI0_PORT == AVRPORTA #define SPI0_PDS_REG PINA #elif SPI0_PORT == AVRPORTC #define SPI0_PDS_REG PINC #elif SPI0_PORT == AVRPORTD #define SPI0_PDS_REG PIND #elif SPI0_PORT == AVRPORTE #define SPI0_PDS_REG PINE #elif SPI0_PORT == AVRPORTF #define SPI0_PDS_REG PINF #elif SPI0_PORT == AVRPORTG #define SPI0_PDS_REG PING #else /*! \brief SPI port input register. */ #define SPI0_PDS_REG PINB #endif #endif #elif !defined(SPI0_OE_REG) || !defined(SPI0_SOD_REG) || !defined(SPI0_PDS_REG) #warning "No SPI bit banging registers for unknown CPU." #endif /* MCU ---------------------- */ #ifdef SPI0_COD_REG /*! \brief Set specified MMC line low. */ #define MMC0_CLR_BIT(n) outr(SPI0_COD_REG, _BV(n)) /*! \brief Set specified MMC line high. */ #define MMC0_SET_BIT(n) outr(SPI0_SOD_REG, _BV(n)) #else #define MMC0_CLR_BIT(n) outr(SPI0_SOD_REG, inr(SPI0_SOD_REG) & ~_BV(n)) #define MMC0_SET_BIT(n) outr(SPI0_SOD_REG, inr(SPI0_SOD_REG) | _BV(n)) #endif #ifdef SPI0_ODS_REG /*! \brief Query status of a specified MMC output. */ #define MMC0_IS_BIT_SET(n) ((inr(SPI0_ODS_REG) & _BV(n)) == _BV(n)) #else #define MMC0_IS_BIT_SET(n) ((inr(SPI0_SOD_REG) & _BV(n)) == _BV(n)) #endif /*! \brief Query status of a specified MMC input. */ #define MMC0_TST_BIT(n) ((inr(SPI0_PDS_REG) & _BV(n)) == _BV(n)) /*! * \brief Private data of NPL card interface. */ typedef struct _MMCDCB { int dcb_avail; /*!< Card is available. */ int dcb_changed; /*!< Card has changed. */ int dcb_addr_mode; /*!< Card addressing mode (byte/block) */ } MMCDCB; static MMCDCB mmc0_dcb; /*! * \brief Initialize the card in slot 0. * * Called by the MMC driver during card reset. The card change * detection flag will be cleared. * * \return 0 on success, -1 if no card is detected. */ static int SbiMmCard0Init(void) { mmc0_dcb.dcb_changed = 0; if (mmc0_dcb.dcb_avail) { return 0; } return -1; } /*! * \brief Activate or deactivate chip select on card slot 0. * * \param on The card will be selected if 1, deselected if 0. * Any other value can be used to query the status. * * \return Previous select status. 1 if selected, 0 otherwise. */ static int SbiMmCard0Select(int on) { int rc = MMC0_IS_BIT_SET(SPI0_CS_BIT); /* MMC select is low active. */ if (on == 1) { MMC0_CLR_BIT(SPI0_CS_BIT); } else if (on == 0) { MMC0_SET_BIT(SPI0_CS_BIT); } return rc; } /*! * \brief Read the previously received byte and transmit a new one. * * \param val Byte to transmit. * * \return Last byte received. */ static uint8_t SbiMmCard0Io(uint8_t val) { uint8_t msk = 0x80; #ifdef NUTDEBUG putchar('['); if (val != 0xFF) { printf("s%02X", val); } #endif #if defined(MCU_AT91R40008) && defined(__GNUC__) && 0 /* ARM assembly version, tested on AT91R40008 only. */ asm volatile ( "\nspi_tran_l:\n\t" "str %7, [%3, %5]" "\n\t" /* SCK low. */ "tst %0, %2" "\n\t" /* Check data bit. */ "strne %8, [%3, %4]" "\n\t" /* MOSI high. */ "streq %8, [%3, %5]" "\n\t" /* MOSI low. */ "bic %0, %1, %2" "\n\t" /* Clear data bit */ "str %7, [%3, %4]" "\n\t" /* SCK high. */ "ldr r1, [%3, %6]" "\n\t" /* Read MISO */ "tst r1, %9" "\n\t" /* Test MISO */ "orrne %0, %1, %2" "\n\t" /* Set data bit if MOSI was high */ "movs %2, %2, lsr #1""\n\t" /* msk <<= 1 */ "bne spi_tran_l" /* Loop for next bit. */ : "=r" (val) /* %0 output */ : "0" (val) /* %1 input */ , "r" (msk) /* %2 input */ , "r" (PIO_BASE) /* %3 input */ , "J" (PIO_SODR - PIO_BASE) /* %4 input */ , "J" (PIO_CODR - PIO_BASE) /* %5 input */ , "J" (PIO_PDSR - PIO_BASE) /* %6 input */ , "r" _BV(SPI0_CLK_BIT) /* %7 input */ , "r" _BV(SPI0_MOSI_BIT) /* %8 input */ , "I" _BV(SPI0_MISO_BIT) /* %9 input */ : "r1" ); #else /* Generic code. */ while (msk) { MMC0_CLR_BIT(SPI0_CLK_BIT); if (val & msk) { MMC0_SET_BIT(SPI0_MOSI_BIT); } else { MMC0_CLR_BIT(SPI0_MOSI_BIT); } MMC0_SET_BIT(SPI0_CLK_BIT); if (MMC0_TST_BIT(SPI0_MISO_BIT)) { val |= msk; } else { val &= ~msk; } msk >>= 1; } #endif #ifdef NUTDEBUG if (val != 0xFF) { printf("r%02X", val); } putchar(']'); #endif return val; } /*! * \brief Check if card is available in slot 0. * * \todo Card change should verify the card identifier. Right now * any detection of removing and re-inserting a card counts * as a card change. * * \return 0 if no card is detected, 1 if a card is available or 2 if * a card change had been detected after the last mount. */ int SbiMmCard0Avail(void) { if (mmc0_dcb.dcb_avail) { if (mmc0_dcb.dcb_changed) { return 2; } return 1; } return 0; } /*! * \brief Check if card in slot 0 is write protected. * * \todo Not implemented. * * \return Always 0. */ int SbiMmCard0WrProt(void) { return 0; } /*! * \brief set addressing mode * */ int SbiMmCard0SetAdrMode(int mode) { mmc0_dcb.dcb_addr_mode = mode; return(0); } /*! * \brief get addressing mode * */ int SbiMmCard0GetAdrMode(void) { return(mmc0_dcb.dcb_addr_mode); } #ifdef MMC0_CD_BIT /*! * \brief Card slot 0 insertion interrupt routine. * * \todo A different routine is required to support multiple cards. * * \param arg Pointer to the device information structure. */ static void SbiMmCard0DetectIrq(void *arg) { int mode = NutIrqSetMode(&SIG_MMC0DETECT, NUT_IRQMODE_LOWLEVEL); if (mode == NUT_IRQMODE_HIGHLEVEL) { mmc0_dcb.dcb_avail = 0; } else { NutIrqSetMode(&SIG_MMC0DETECT, NUT_IRQMODE_HIGHLEVEL); mmc0_dcb.dcb_avail = 1; } mmc0_dcb.dcb_changed = 1; } #endif /*! * \brief Initialize MMC hardware interface. * * This function is automatically executed during during device * registration via NutRegisterDevice(). * * \param dev Identifies the device to initialize. */ static int SbiMmcIfcInit(NUTDEVICE * dev) { #ifdef SPI0_PE_REG /* Enable all SPI ports, if a port enable register is defined. */ outr(SPI0_PE_REG, _BV(SPI0_CLK_BIT) | _BV(SPI0_MOSI_BIT) | _BV(SPI0_CS_BIT) | _BV(SPI0_MISO_BIT)); #endif #ifdef SPI0_OD_REG /* Enable CLK, MOSI and CS output pins. */ outr(SPI0_OE_REG, _BV(SPI0_CLK_BIT) | _BV(SPI0_MOSI_BIT) | _BV(SPI0_CS_BIT)); /* Enable MISO input pin. */ outr(SPI0_OD_REG, _BV(SPI0_MISO_BIT)); #else /* * If this CPU hasn't got a specific output disable register, we * assume that inputs are enabled by clearing the corresponding bit * in the output enable register. */ outr(SPI0_OE_REG, (inr(SPI0_OE_REG) & ~_BV(SPI0_MISO_BIT)) | _BV(SPI0_CLK_BIT) | _BV(SPI0_MOSI_BIT) | _BV(SPI0_CS_BIT)); #endif /* Set all outputs high. */ outr(SPI0_SOD_REG, _BV(SPI0_CLK_BIT) | _BV(SPI0_MOSI_BIT) | _BV(SPI0_CS_BIT)); #ifdef MMC0_CD_BIT #ifdef SPI0_PD_REG outr(SPI0_PD_REG, _BV(MMC0_CD_BIT)); #endif /* Register card detection interrupts. */ if (NutRegisterIrqHandler(&SIG_MMC0DETECT, SbiMmCard0DetectIrq, 0)) { mmc0_dcb.dcb_avail = 1; } else { mmc0_dcb.dcb_avail = 0; NutIrqSetMode(&SIG_MMC0DETECT, NUT_IRQMODE_LOWLEVEL); NutIrqEnable(&SIG_MMC0DETECT); } #else mmc0_dcb.dcb_avail = 1; #endif /* MMC0_CD_BIT */ mmc0_dcb.dcb_changed = 0; return MmCardDevInit(dev); } static MMCIFC mmc0_ifc = { SbiMmCard0Init, /*!< mmcifc_in */ SbiMmCard0Io, /*!< mmcifc_io */ SbiMmCard0Select, /*!< mmcifc_cs */ SbiMmCard0Avail, /*!< mmcifc_cd */ SbiMmCard0WrProt, /*!< mmcifc_wp */ SbiMmCard0SetAdrMode, /*!< mmcifc_sm */ SbiMmCard0GetAdrMode /*!< mmcifc_gm */ }; /*! * \brief Multimedia card device information structure. * * A pointer to this structure must be passed to NutRegisterDevice() * to bind this driver to the Nut/OS kernel. An application may then * call * /verbatim * _open("MMC0:", _O_RDWR | _O_BINARY); * /endverbatim * to mount the first active primary partition with any previously * registered file system driver (typically devPhat0). */ NUTDEVICE devSbiMmc0 = { 0, /*!< Pointer to next device, dev_next. */ {'M', 'M', 'C', '0', 0, 0, 0, 0, 0} , /*!< Unique device name, dev_name. */ 0, /*!< Type of device, dev_type. Obsolete. */ 0, /*!< Base address, dev_base. Unused. */ 0, /*!< First interrupt number, dev_irq. Unused. */ &mmc0_ifc, /*!< Interface control block, dev_icb. */ &mmc0_dcb, /*!< Driver control block used by the low level part, dev_dcb. */ SbiMmcIfcInit, /*!< Driver initialization routine, dev_init. */ MmCardIOCtl, /*!< Driver specific control function, dev_ioctl. */ MmCardBlockRead, /*!< Read data from a file, dev_read. */ MmCardBlockWrite, /*!< Write data to a file, dev_write. */ #ifdef __HARVARD_ARCH__ MmCardBlockWrite_P, /*!< Write data from program space to a file, dev_write_P. */ #endif MmCardMount, /*!< Mount a file system, dev_open. */ MmCardUnmount, /*!< Unmount a file system, dev_close. */ NULL, /*!< Return file size, dev_size. */ NULL, /*!< Select function, optional, not yet implemented */ }; /*@}*/