Improvements to the MMC over SPI driver.

git-svn-id: svn://svn.code.sf.net/p/chibios/svn/trunk@3741 35acf78f-673a-0410-8e92-d51de3d6d3f4
This commit is contained in:
gdisirio 2012-01-05 16:01:52 +00:00
parent 600b02f38f
commit 62b090c673
5 changed files with 344 additions and 205 deletions

View File

@ -19,7 +19,7 @@
*/ */
/** /**
* @file spi.h * @file mmc_spi.h
* @brief MMC over SPI driver header. * @brief MMC over SPI driver header.
* *
* @addtogroup MMC_SPI * @addtogroup MMC_SPI
@ -37,10 +37,12 @@
#define MMC_CMD0_RETRY 10 #define MMC_CMD0_RETRY 10
#define MMC_CMD1_RETRY 100 #define MMC_CMD1_RETRY 100
#define MMC_ACMD41_RETRY 100
#define MMC_WAIT_DATA 10000 #define MMC_WAIT_DATA 10000
#define MMC_CMDGOIDLE 0 #define MMC_CMDGOIDLE 0
#define MMC_CMDINIT 1 #define MMC_CMDINIT 1
#define MMC_CMDINTERFACE_CONDITION 8
#define MMC_CMDREADCSD 9 #define MMC_CMDREADCSD 9
#define MMC_CMDSTOP 12 #define MMC_CMDSTOP 12
#define MMC_CMDSETBLOCKLEN 16 #define MMC_CMDSETBLOCKLEN 16
@ -48,6 +50,9 @@
#define MMC_CMDREADMULTIPLE 18 #define MMC_CMDREADMULTIPLE 18
#define MMC_CMDWRITE 24 #define MMC_CMDWRITE 24
#define MMC_CMDWRITEMULTIPLE 25 #define MMC_CMDWRITEMULTIPLE 25
#define MMC_CMDAPP 55
#define MMC_CMDREADOCR 58
#define MMC_ACMDOPCONDITION 41
/*===========================================================================*/ /*===========================================================================*/
/* Driver pre-compile time settings. */ /* Driver pre-compile time settings. */
@ -180,6 +185,10 @@ typedef struct {
* @brief Insertion counter. * @brief Insertion counter.
*/ */
uint_fast8_t cnt; uint_fast8_t cnt;
/***
* @brief Addresses use blocks instead of bytes.
*/
bool_t block_addresses;
} MMCDriver; } MMCDriver;
/*===========================================================================*/ /*===========================================================================*/

View File

@ -17,6 +17,10 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
/*
Concepts and parts of this file have been contributed by Uladzimir Pylinsky
aka barthess.
*/
/** /**
* @file STM32/i2c_lld.c * @file STM32/i2c_lld.c
@ -112,6 +116,63 @@ static volatile uint16_t dbgCR2;
/* Driver local functions. */ /* Driver local functions. */
/*===========================================================================*/ /*===========================================================================*/
/**
* @brief Wakes up the waiting thread.
*
* @param[in] i2cp pointer to the @p I2CDriver object
* @param[in] msg wakeup message
*
* @notapi
*/
#define wakeup_isr(i2cp, msg) { \
chSysLockFromIsr(); \
if ((i2cp)->thread != NULL) { \
Thread *tp = (i2cp)->thread; \
(i2cp)->thread = NULL; \
tp->p_u.rdymsg = (msg); \
chSchReadyI(tp); \
} \
chSysUnlockFromIsr(); \
}
/**
* @brief Aborts an I2C transaction.
*
* @param[in] i2cp pointer to the @p I2CDriver object
*
* @notapi
*/
static void i2c_lld_abort_operation(I2CDriver *i2cp) {
/* Stops the I2C peripheral.*/
i2cp->i2c->CR1 = I2C_CR1_SWRST;
i2cp->i2c->CR1 = 0;
i2cp->i2c->SR1 = 0;
/* Stops the associated DMA streams.*/
dmaStreamDisable(i2cp->dmatx);
dmaStreamDisable(i2cp->dmarx);
dmaStreamClearInterrupt(i2cp->dmatx);
dmaStreamClearInterrupt(i2cp->dmarx);
}
/**
* @brief Handling of stalled I2C transactions.
*
* @param[in] i2cp pointer to the @p I2CDriver object
*
* @notapi
*/
static void i2c_lld_safety_timeout(void *p) {
I2CDriver *i2cp = (I2CDriver *)p;
if (i2cp->thread) {
i2c_lld_abort_operation(i2cp);
i2cp->thread->p_u.rdymsg = RDY_TIMEOUT;
chSchReadyI(i2cp->thread);
}
}
/** /**
* @brief Set clock speed. * @brief Set clock speed.
* *
@ -206,7 +267,6 @@ static void i2c_lld_set_opmode(I2CDriver *i2cp) {
regCR1 |= (I2C_CR1_SMBUS|I2C_CR1_SMBTYPE); regCR1 |= (I2C_CR1_SMBUS|I2C_CR1_SMBTYPE);
break; break;
} }
i2cp->i2c->CR1 = regCR1; i2cp->i2c->CR1 = regCR1;
} }
@ -242,7 +302,7 @@ static uint32_t i2c_get_event(I2CDriver *i2cp) {
* *
* @notapi * @notapi
*/ */
static void i2c_serve_event_interrupt(I2CDriver *i2cp) { static void i2c_lld_serve_event_interrupt(I2CDriver *i2cp) {
I2C_TypeDef *dp = i2cp->i2c; I2C_TypeDef *dp = i2cp->i2c;
switch (i2c_get_event(i2cp)) { switch (i2c_get_event(i2cp)) {
@ -260,13 +320,13 @@ static void i2c_serve_event_interrupt(I2CDriver *i2cp) {
case I2C_EV8_2_MASTER_BYTE_TRANSMITTED: case I2C_EV8_2_MASTER_BYTE_TRANSMITTED:
/* Catches BTF event after the end of transmission.*/ /* Catches BTF event after the end of transmission.*/
if (dmaStreamGetTransactionSize(i2cp->dmarx) > 0) { if (dmaStreamGetTransactionSize(i2cp->dmarx) > 0) {
/* Starts "read after write" operation.*/ /* Starts "read after write" operation, LSB = 1 -> receive.*/
i2cp->addr |= 0x01; /* LSB = 1 -> receive */ i2cp->addr |= 0x01;
i2cp->i2c->CR1 |= I2C_CR1_START | I2C_CR1_ACK; i2cp->i2c->CR1 |= I2C_CR1_START | I2C_CR1_ACK;
return; return;
} }
i2cp->i2c->CR1 |= I2C_CR1_STOP; i2cp->i2c->CR1 |= I2C_CR1_STOP;
i2c_lld_isr_code(i2cp); wakeup_isr(i2cp, RDY_OK);
break; break;
default: default:
break; break;
@ -277,14 +337,24 @@ static void i2c_serve_event_interrupt(I2CDriver *i2cp) {
* @brief DMA RX end IRQ handler. * @brief DMA RX end IRQ handler.
* *
* @param[in] i2cp pointer to the @p I2CDriver object * @param[in] i2cp pointer to the @p I2CDriver object
* @param[in] flags pre-shifted content of the ISR register
* *
* @notapi * @notapi
*/ */
static void i2c_lld_serve_rx_end_irq(I2CDriver *i2cp) { static void i2c_lld_serve_rx_end_irq(I2CDriver *i2cp, uint32_t flags) {
/* DMA errors handling.*/
#if defined(STM32_I2C_DMA_ERROR_HOOK)
if ((flags & (STM32_DMA_ISR_TEIF | STM32_DMA_ISR_DMEIF)) != 0) {
STM32_I2C_DMA_ERROR_HOOK(i2cp);
}
#else
(void)flags;
#endif
dmaStreamDisable(i2cp->dmarx); dmaStreamDisable(i2cp->dmarx);
i2cp->i2c->CR1 |= I2C_CR1_STOP; i2cp->i2c->CR1 |= I2C_CR1_STOP;
i2c_lld_isr_code(i2cp); wakeup_isr(i2cp, RDY_OK);
} }
/** /**
@ -294,7 +364,16 @@ static void i2c_lld_serve_rx_end_irq(I2CDriver *i2cp) {
* *
* @notapi * @notapi
*/ */
static void i2c_lld_serve_tx_end_irq(I2CDriver *i2cp) { static void i2c_lld_serve_tx_end_irq(I2CDriver *i2cp, uint32_t flags) {
/* DMA errors handling.*/
#if defined(STM32_I2C_DMA_ERROR_HOOK)
if ((flags & (STM32_DMA_ISR_TEIF | STM32_DMA_ISR_DMEIF)) != 0) {
STM32_I2C_DMA_ERROR_HOOK(i2cp);
}
#else
(void)flags;
#endif
dmaStreamDisable(i2cp->dmatx); dmaStreamDisable(i2cp->dmatx);
} }
@ -306,7 +385,7 @@ static void i2c_lld_serve_tx_end_irq(I2CDriver *i2cp) {
* *
* @notapi * @notapi
*/ */
static void i2c_serve_error_interrupt(I2CDriver *i2cp) { static void i2c_lld_serve_error_interrupt(I2CDriver *i2cp) {
i2cflags_t errors; i2cflags_t errors;
chSysLockFromIsr(); chSysLockFromIsr();
@ -351,8 +430,8 @@ static void i2c_serve_error_interrupt(I2CDriver *i2cp) {
/* If some error has been identified then sends wakes the waiting thread.*/ /* If some error has been identified then sends wakes the waiting thread.*/
if (errors != I2CD_NO_ERROR) { if (errors != I2CD_NO_ERROR) {
i2cp->errors |= errors; i2cp->errors = errors;
i2c_lld_isr_err_code(i2cp); wakeup_isr(i2cp, RDY_RESET);
} }
} }
@ -370,7 +449,7 @@ CH_IRQ_HANDLER(I2C1_EV_IRQHandler) {
CH_IRQ_PROLOGUE(); CH_IRQ_PROLOGUE();
i2c_serve_event_interrupt(&I2CD1); i2c_lld_serve_event_interrupt(&I2CD1);
CH_IRQ_EPILOGUE(); CH_IRQ_EPILOGUE();
} }
@ -382,7 +461,7 @@ CH_IRQ_HANDLER(I2C1_ER_IRQHandler) {
CH_IRQ_PROLOGUE(); CH_IRQ_PROLOGUE();
i2c_serve_error_interrupt(&I2CD1); i2c_lld_serve_error_interrupt(&I2CD1);
CH_IRQ_EPILOGUE(); CH_IRQ_EPILOGUE();
} }
@ -398,7 +477,7 @@ CH_IRQ_HANDLER(I2C2_EV_IRQHandler) {
CH_IRQ_PROLOGUE(); CH_IRQ_PROLOGUE();
i2c_serve_event_interrupt(&I2CD2); i2c_lld_serve_event_interrupt(&I2CD2);
CH_IRQ_EPILOGUE(); CH_IRQ_EPILOGUE();
} }
@ -412,7 +491,7 @@ CH_IRQ_HANDLER(I2C2_ER_IRQHandler) {
CH_IRQ_PROLOGUE(); CH_IRQ_PROLOGUE();
i2c_serve_error_interrupt(&I2CD2); i2c_lld_serve_error_interrupt(&I2CD2);
CH_IRQ_EPILOGUE(); CH_IRQ_EPILOGUE();
} }
@ -428,7 +507,7 @@ CH_IRQ_HANDLER(I2C3_EV_IRQHandler) {
CH_IRQ_PROLOGUE(); CH_IRQ_PROLOGUE();
i2c_serve_event_interrupt(&I2CD3); i2c_lld_serve_event_interrupt(&I2CD3);
CH_IRQ_EPILOGUE(); CH_IRQ_EPILOGUE();
} }
@ -442,12 +521,15 @@ CH_IRQ_HANDLER(I2C3_ER_IRQHandler) {
CH_IRQ_PROLOGUE(); CH_IRQ_PROLOGUE();
i2c_serve_error_interrupt(&I2CD3); i2c_lld_serve_error_interrupt(&I2CD3);
CH_IRQ_EPILOGUE(); CH_IRQ_EPILOGUE();
} }
#endif /* STM32_I2C_USE_I2C3 */ #endif /* STM32_I2C_USE_I2C3 */
/*===========================================================================*/
/* Driver exported functions. */
/*===========================================================================*/
/** /**
* @brief Low level I2C driver initialization. * @brief Low level I2C driver initialization.
@ -497,12 +579,11 @@ void i2c_lld_start(I2CDriver *i2cp) {
/* If in stopped state then enables the I2C and DMA clocks.*/ /* If in stopped state then enables the I2C and DMA clocks.*/
if (i2cp->state == I2C_STOP) { if (i2cp->state == I2C_STOP) {
i2c_lld_reset(i2cp);
#if STM32_I2C_USE_I2C1 #if STM32_I2C_USE_I2C1
if (&I2CD1 == i2cp) { if (&I2CD1 == i2cp) {
bool_t b; bool_t b;
rccResetI2C1();
b = dmaStreamAllocate(i2cp->dmarx, b = dmaStreamAllocate(i2cp->dmarx,
STM32_I2C_I2C1_IRQ_PRIORITY, STM32_I2C_I2C1_IRQ_PRIORITY,
(stm32_dmaisr_t)i2c_lld_serve_rx_end_irq, (stm32_dmaisr_t)i2c_lld_serve_rx_end_irq,
@ -528,6 +609,7 @@ void i2c_lld_start(I2CDriver *i2cp) {
if (&I2CD2 == i2cp) { if (&I2CD2 == i2cp) {
bool_t b; bool_t b;
rccResetI2C2();
b = dmaStreamAllocate(i2cp->dmarx, b = dmaStreamAllocate(i2cp->dmarx,
STM32_I2C_I2C2_IRQ_PRIORITY, STM32_I2C_I2C2_IRQ_PRIORITY,
(stm32_dmaisr_t)i2c_lld_serve_rx_end_irq, (stm32_dmaisr_t)i2c_lld_serve_rx_end_irq,
@ -553,6 +635,7 @@ void i2c_lld_start(I2CDriver *i2cp) {
if (&I2CD3 == i2cp) { if (&I2CD3 == i2cp) {
bool_t b; bool_t b;
rccResetI2C3();
b = dmaStreamAllocate(i2cp->dmarx, b = dmaStreamAllocate(i2cp->dmarx,
STM32_I2C_I2C3_IRQ_PRIORITY, STM32_I2C_I2C3_IRQ_PRIORITY,
(stm32_dmaisr_t)i2c_lld_serve_rx_end_irq, (stm32_dmaisr_t)i2c_lld_serve_rx_end_irq,
@ -575,6 +658,10 @@ void i2c_lld_start(I2CDriver *i2cp) {
#endif /* STM32_I2C_USE_I2C3 */ #endif /* STM32_I2C_USE_I2C3 */
} }
/* DMA streams mode preparation in advance.*/
dmaStreamSetMode(i2cp->dmatx, i2cp->dmamode | STM32_DMA_CR_DIR_M2P);
dmaStreamSetMode(i2cp->dmarx, i2cp->dmamode | STM32_DMA_CR_DIR_P2M);
/* I2C registers pointed by the DMA.*/ /* I2C registers pointed by the DMA.*/
dmaStreamSetPeripheral(i2cp->dmarx, &i2cp->i2c->DR); dmaStreamSetPeripheral(i2cp->dmarx, &i2cp->i2c->DR);
dmaStreamSetPeripheral(i2cp->dmatx, &i2cp->i2c->DR); dmaStreamSetPeripheral(i2cp->dmatx, &i2cp->i2c->DR);
@ -582,6 +669,7 @@ void i2c_lld_start(I2CDriver *i2cp) {
/* Reset i2c peripheral.*/ /* Reset i2c peripheral.*/
i2cp->i2c->CR1 = I2C_CR1_SWRST; i2cp->i2c->CR1 = I2C_CR1_SWRST;
i2cp->i2c->CR1 = 0; i2cp->i2c->CR1 = 0;
i2cp->i2c->CR2 = I2C_CR2_ITERREN | I2C_CR2_ITEVTEN;
/* Setup I2C parameters.*/ /* Setup I2C parameters.*/
i2c_lld_set_clock(i2cp); i2c_lld_set_clock(i2cp);
@ -603,12 +691,8 @@ void i2c_lld_stop(I2CDriver *i2cp) {
/* If not in stopped state then disables the I2C clock.*/ /* If not in stopped state then disables the I2C clock.*/
if (i2cp->state != I2C_STOP) { if (i2cp->state != I2C_STOP) {
i2c_lld_reset(i2cp); /* I2C disable.*/
i2c_lld_abort_operation(i2cp);
dmaStreamDisable(i2cp->dmatx);
dmaStreamDisable(i2cp->dmarx);
dmaStreamClearInterrupt(i2cp->dmatx);
dmaStreamClearInterrupt(i2cp->dmarx);
dmaStreamRelease(i2cp->dmatx); dmaStreamRelease(i2cp->dmatx);
dmaStreamRelease(i2cp->dmarx); dmaStreamRelease(i2cp->dmarx);
@ -638,33 +722,6 @@ void i2c_lld_stop(I2CDriver *i2cp) {
} }
} }
/**
* @brief Resets the interface via RCC.
*
* @param[in] i2cp pointer to the @p I2CDriver object
*
* @notapi
*/
void i2c_lld_reset(I2CDriver *i2cp) {
chDbgCheck((i2cp->state == I2C_STOP)||(i2cp->state == I2C_READY),
"i2c_lld_reset: invalid state");
#if STM32_I2C_USE_I2C1
if (&I2CD1 == i2cp)
rccResetI2C1();
#endif /* STM32_I2C_USE_I2C1 */
#if STM32_I2C_USE_I2C2
if (&I2CD2 == i2cp)
rccResetI2C2();
#endif /* STM32_I2C_USE_I2C2 */
#if STM32_I2C_USE_I2C3
if (&I2CD3 == i2cp)
rccResetI2C3();
#endif /* STM32_I2C_USE_I2C3 */
}
/** /**
* @brief Receives data via the I2C bus as master. * @brief Receives data via the I2C bus as master.
* @details Number of receiving bytes must be more than 1 because of stm32 * @details Number of receiving bytes must be more than 1 because of stm32
@ -682,53 +739,61 @@ void i2c_lld_reset(I2CDriver *i2cp) {
* @retval RDY_OK if the function succeeded. * @retval RDY_OK if the function succeeded.
* @retval RDY_RESET if one or more I2C errors occurred, the errors can * @retval RDY_RESET if one or more I2C errors occurred, the errors can
* be retrieved using @p i2cGetErrors(). * be retrieved using @p i2cGetErrors().
* @retval RDY_TIMEOUT if a timeout occurred before operation end. * @retval RDY_TIMEOUT if a timeout occurred before operation end. <b>After a
* timeout the driver must be stopped and restarted
* because the bus is in an uncertain state</b>.
* *
* @notapi * @notapi
*/ */
msg_t i2c_lld_master_receive_timeout(I2CDriver *i2cp, i2caddr_t addr, msg_t i2c_lld_master_receive_timeout(I2CDriver *i2cp, i2caddr_t addr,
uint8_t *rxbuf, size_t rxbytes, uint8_t *rxbuf, size_t rxbytes,
systime_t timeout) { systime_t timeout) {
VirtualTimer vt;
msg_t rdymsg; msg_t rdymsg;
chDbgCheck((rxbytes > 1), "i2c_lld_master_receive_timeout");
/* Global timeout for the whole operation.*/
chVTSetI(&vt, timeout, i2c_lld_safety_timeout, (void *)i2cp);
/* Releases the lock from high level driver.*/ /* Releases the lock from high level driver.*/
chSysUnlock(); chSysUnlock();
chDbgCheck((rxbytes > 1), "i2c_lld_master_receive_timeout");
/* Initializes driver fields, LSB = 1 -> receive.*/ /* Initializes driver fields, LSB = 1 -> receive.*/
i2cp->addr = (addr << 1) | 0x01; i2cp->addr = (addr << 1) | 0x01;
i2cp->errors = 0; i2cp->errors = 0;
/* TODO: DMA error handling */
/* RX DMA setup.*/ /* RX DMA setup.*/
dmaStreamSetMemory0(i2cp->dmarx, rxbuf); dmaStreamSetMemory0(i2cp->dmarx, rxbuf);
dmaStreamSetTransactionSize(i2cp->dmarx, rxbytes); dmaStreamSetTransactionSize(i2cp->dmarx, rxbytes);
dmaStreamSetMode(i2cp->dmarx, i2cp->dmamode | STM32_DMA_CR_DIR_P2M);
/* Waits until BUSY flag is reset.*/ /* Waits until BUSY flag is reset and the STOP from the previous operation
volatile uint32_t tmo = 1 + (STM32_SYSCLK / 1000000) * 20; is completed, alternatively for a timeout condition.*/
while((i2cp->i2c->SR2 & I2C_SR2_BUSY) && tmo) while ((i2cp->i2c->SR2 & I2C_SR2_BUSY) || (i2cp->i2c->CR1 & I2C_CR1_STOP)) {
tmo--; if (!chVTIsArmedI(&vt)) {
if (tmo == 0) chSysLock();
return RDY_RESET; return RDY_TIMEOUT;
}
/* wait stop bit from previous transaction*/ }
tmo = 1 + (STM32_SYSCLK / 1000000) * 20;
while((i2cp->i2c->CR1 & I2C_CR1_STOP) && tmo)
tmo--;
if (tmo == 0)
return RDY_RESET;
/* This lock will be released in high level driver.*/ /* This lock will be released in high level driver.*/
chSysLock(); chSysLock();
/* Within the critical zone in order to avoid race conditions.*/ /* Atomic check on the timer in order to make sure that a timeout didn't
i2cp->i2c->CR2 |= I2C_CR2_ITERREN | I2C_CR2_ITEVTEN; happen outside the critical zone.*/
if (!chVTIsArmedI(&vt))
return RDY_TIMEOUT;
/* Starts the operation.*/
i2cp->i2c->CR2 &= I2C_CR2_FREQ;
i2cp->i2c->CR1 |= I2C_CR1_START | I2C_CR1_ACK; i2cp->i2c->CR1 |= I2C_CR1_START | I2C_CR1_ACK;
/* Waits for the operation completion.*/ /* Waits for the operation completion or a timeout.*/
i2c_lld_wait_s(i2cp, timeout, rdymsg); i2cp->thread = chThdSelf();
chSchGoSleepS(THD_STATE_SUSPENDED);
rdymsg = chThdSelf()->p_u.rdymsg;
if (rdymsg != RDY_TIMEOUT)
chVTResetI(&vt);
return rdymsg; return rdymsg;
} }
@ -752,7 +817,9 @@ msg_t i2c_lld_master_receive_timeout(I2CDriver *i2cp, i2caddr_t addr,
* @retval RDY_OK if the function succeeded. * @retval RDY_OK if the function succeeded.
* @retval RDY_RESET if one or more I2C errors occurred, the errors can * @retval RDY_RESET if one or more I2C errors occurred, the errors can
* be retrieved using @p i2cGetErrors(). * be retrieved using @p i2cGetErrors().
* @retval RDY_TIMEOUT if a timeout occurred before operation end. * @retval RDY_TIMEOUT if a timeout occurred before operation end. <b>After a
* timeout the driver must be stopped and restarted
* because the bus is in an uncertain state</b>.
* *
* @notapi * @notapi
*/ */
@ -760,52 +827,57 @@ msg_t i2c_lld_master_transmit_timeout(I2CDriver *i2cp, i2caddr_t addr,
const uint8_t *txbuf, size_t txbytes, const uint8_t *txbuf, size_t txbytes,
uint8_t *rxbuf, size_t rxbytes, uint8_t *rxbuf, size_t rxbytes,
systime_t timeout) { systime_t timeout) {
VirtualTimer vt;
msg_t rdymsg; msg_t rdymsg;
/* Releases the lock from high level driver.*/
chSysUnlock();
chDbgCheck(((rxbytes == 0) || ((rxbytes > 1) && (rxbuf != NULL))), chDbgCheck(((rxbytes == 0) || ((rxbytes > 1) && (rxbuf != NULL))),
"i2c_lld_master_transmit_timeout"); "i2c_lld_master_transmit_timeout");
/* Global timeout for the whole operation.*/
chVTSetI(&vt, timeout, i2c_lld_safety_timeout, (void *)i2cp);
/* Releases the lock from high level driver.*/
chSysUnlock();
/* Initializes driver fields, LSB = 0 -> write.*/ /* Initializes driver fields, LSB = 0 -> write.*/
i2cp->addr = addr << 1; i2cp->addr = addr << 1;
i2cp->errors = 0; i2cp->errors = 0;
/* TODO: DMA error handling */
/* TX DMA setup.*/ /* TX DMA setup.*/
dmaStreamSetMemory0(i2cp->dmatx, txbuf); dmaStreamSetMemory0(i2cp->dmatx, txbuf);
dmaStreamSetTransactionSize(i2cp->dmatx, txbytes); dmaStreamSetTransactionSize(i2cp->dmatx, txbytes);
dmaStreamSetMode(i2cp->dmatx, i2cp->dmamode | STM32_DMA_CR_DIR_M2P);
/* RX DMA setup.*/ /* RX DMA setup.*/
dmaStreamSetMemory0(i2cp->dmarx, rxbuf); dmaStreamSetMemory0(i2cp->dmarx, rxbuf);
dmaStreamSetTransactionSize(i2cp->dmarx, rxbytes); dmaStreamSetTransactionSize(i2cp->dmarx, rxbytes);
dmaStreamSetMode(i2cp->dmarx, i2cp->dmamode | STM32_DMA_CR_DIR_P2M);
/* Waits until BUSY flag is reset.*/ /* Waits until BUSY flag is reset and the STOP from the previous operation
volatile uint32_t tmo = 1 + (STM32_SYSCLK / 1000000) * 20; is completed, alternatively for a timeout condition.*/
while((i2cp->i2c->SR2 & I2C_SR2_BUSY) && tmo) while ((i2cp->i2c->SR2 & I2C_SR2_BUSY) || (i2cp->i2c->CR1 & I2C_CR1_STOP)) {
tmo--; if (!chVTIsArmedI(&vt)) {
if (tmo == 0) chSysLock();
return RDY_RESET; return RDY_TIMEOUT;
}
/* Wait stop bit from previous transaction.*/ }
tmo = 1 + (STM32_SYSCLK / 1000000) * 20;
while((i2cp->i2c->CR1 & I2C_CR1_STOP) && tmo)
tmo--;
if (tmo == 0)
return RDY_RESET;
/* This lock will be released in high level driver.*/ /* This lock will be released in high level driver.*/
chSysLock(); chSysLock();
/* Within the critical zone in order to avoid race conditions.*/ /* Atomic check on the timer in order to make sure that a timeout didn't
i2cp->i2c->CR2 |= I2C_CR2_ITERREN | I2C_CR2_ITEVTEN; happen outside the critical zone.*/
if (!chVTIsArmedI(&vt))
return RDY_TIMEOUT;
/* Starts the operation.*/
i2cp->i2c->CR2 &= I2C_CR2_FREQ;
i2cp->i2c->CR1 |= I2C_CR1_START; i2cp->i2c->CR1 |= I2C_CR1_START;
/* Waits for the operation completion.*/ /* Waits for the operation completion or a timeout.*/
i2c_lld_wait_s(i2cp, timeout, rdymsg); i2cp->thread = chThdSelf();
chSchGoSleepS(THD_STATE_SUSPENDED);
rdymsg = chThdSelf()->p_u.rdymsg;
if (rdymsg != RDY_TIMEOUT)
chVTResetI(&vt);
return rdymsg; return rdymsg;
} }

View File

@ -17,6 +17,10 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
/*
Concepts and parts of this file have been contributed by Uladzimir Pylinsky
aka barthess.
*/
/** /**
* @file STM32/i2c_lld.h * @file STM32/i2c_lld.h
@ -367,87 +371,6 @@ struct I2CDriver{
/* Driver macros. */ /* Driver macros. */
/*===========================================================================*/ /*===========================================================================*/
/**
* @brief Waits for operation completion.
* @details This function waits for the driver to complete the current
* operation.
* @pre An operation must be running while the function is invoked.
* @note No more than one thread can wait on a I2C driver using
* this function.
*
* @param[in] i2cp pointer to the @p I2CDriver object
* @param[in] timeout the number of ticks before the operation timeouts,
* the following special values are allowed:
* - @a TIME_INFINITE no timeout.
* .
* @param[out] rdymsg received message on wakeup
*
* @notapi
*/
#define i2c_lld_wait_s(i2cp, timeout, rdymsg) { \
chDbgAssert((i2cp)->thread == NULL, \
"_i2c_wait(), #1", "already waiting"); \
(i2cp)->thread = chThdSelf(); \
rdymsg = chSchGoSleepTimeoutS(THD_STATE_SUSPENDED, timeout); \
}
/**
* @brief Wakes up the waiting thread.
*
* @param[in] i2cp pointer to the @p I2CDriver object
*
* @notapi
*/
#define i2c_lld_wakeup_isr(i2cp) { \
chSysLockFromIsr(); \
if ((i2cp)->thread != NULL) { \
Thread *tp = (i2cp)->thread; \
(i2cp)->thread = NULL; \
chSchReadyI(tp); \
} \
chSysUnlockFromIsr(); \
}
/**
* @brief Wakes up the waiting thread in case of errors.
*
* @param[in] i2cp pointer to the @p I2CDriver object
*
* @notapi
*/
#define i2c_lld_error_wakeup_isr(i2cp) { \
chSysLockFromIsr(); \
if ((i2cp)->thread != NULL) { \
Thread *tp = (i2cp)->thread; \
(i2cp)->thread = NULL; \
tp->p_u.rdymsg = RDY_RESET; \
chSchReadyI(tp); \
} \
chSysUnlockFromIsr(); \
}
/**
* @brief Common ISR code.
*
* @param[in] i2cp pointer to the @p I2CDriver object
*
* @notapi
*/
#define i2c_lld_isr_code(i2cp) { \
i2c_lld_wakeup_isr(i2cp); \
}
/**
* @brief Error ISR code.
*
* @param[in] i2cp pointer to the @p I2CDriver object
*
* @notapi
*/
#define i2c_lld_isr_err_code(i2cp) { \
i2c_lld_error_wakeup_isr(i2cp); \
}
/** /**
* @brief Get errors from I2C driver. * @brief Get errors from I2C driver.
* *
@ -473,20 +396,19 @@ extern I2CDriver I2CD2;
#if STM32_I2C_USE_I2C3 #if STM32_I2C_USE_I2C3
extern I2CDriver I2CD3; extern I2CDriver I2CD3;
#endif #endif
#endif #endif /* !defined(__DOXYGEN__) */
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
void i2c_lld_init(void); void i2c_lld_init(void);
void i2c_lld_reset(I2CDriver *i2cp); void i2c_lld_start(I2CDriver *i2cp);
void i2c_lld_start(I2CDriver *i2cp); void i2c_lld_stop(I2CDriver *i2cp);
void i2c_lld_stop(I2CDriver *i2cp); msg_t i2c_lld_master_transmit_timeout(I2CDriver *i2cp, i2caddr_t addr,
msg_t i2c_lld_master_transmit_timeout(I2CDriver *i2cp, i2caddr_t addr,
const uint8_t *txbuf, size_t txbytes, const uint8_t *txbuf, size_t txbytes,
uint8_t *rxbuf, size_t rxbytes, uint8_t *rxbuf, size_t rxbytes,
systime_t timeout); systime_t timeout);
msg_t i2c_lld_master_receive_timeout(I2CDriver *i2cp, i2caddr_t addr, msg_t i2c_lld_master_receive_timeout(I2CDriver *i2cp, i2caddr_t addr,
uint8_t *rxbuf, size_t rxbytes, uint8_t *rxbuf, size_t rxbytes,
systime_t timeout); systime_t timeout);
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -17,15 +17,20 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
/*
Parts of this file have been contributed by Matthias Blaicher.
*/
/** /**
* @file spi.c * @file mmc_spi.c
* @brief MMC over SPI driver code. * @brief MMC over SPI driver code.
* *
* @addtogroup MMC_SPI * @addtogroup MMC_SPI
* @{ * @{
*/ */
#include <string.h>
#include "ch.h" #include "ch.h"
#include "hal.h" #include "hal.h"
@ -43,10 +48,53 @@
/* Driver local variables. */ /* Driver local variables. */
/*===========================================================================*/ /*===========================================================================*/
/**
* @brief Lookup table for CRC-7 ( based on polynomial x^7 + x^3 + 1).
*/
static const uint8_t crc7_lookup_table[256] = {
0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, 0x48, 0x41, 0x5a, 0x53,
0x6c, 0x65, 0x7e, 0x77, 0x19, 0x10, 0x0b, 0x02, 0x3d, 0x34, 0x2f, 0x26,
0x51, 0x58, 0x43, 0x4a, 0x75, 0x7c, 0x67, 0x6e, 0x32, 0x3b, 0x20, 0x29,
0x16, 0x1f, 0x04, 0x0d, 0x7a, 0x73, 0x68, 0x61, 0x5e, 0x57, 0x4c, 0x45,
0x2b, 0x22, 0x39, 0x30, 0x0f, 0x06, 0x1d, 0x14, 0x63, 0x6a, 0x71, 0x78,
0x47, 0x4e, 0x55, 0x5c, 0x64, 0x6d, 0x76, 0x7f, 0x40, 0x49, 0x52, 0x5b,
0x2c, 0x25, 0x3e, 0x37, 0x08, 0x01, 0x1a, 0x13, 0x7d, 0x74, 0x6f, 0x66,
0x59, 0x50, 0x4b, 0x42, 0x35, 0x3c, 0x27, 0x2e, 0x11, 0x18, 0x03, 0x0a,
0x56, 0x5f, 0x44, 0x4d, 0x72, 0x7b, 0x60, 0x69, 0x1e, 0x17, 0x0c, 0x05,
0x3a, 0x33, 0x28, 0x21, 0x4f, 0x46, 0x5d, 0x54, 0x6b, 0x62, 0x79, 0x70,
0x07, 0x0e, 0x15, 0x1c, 0x23, 0x2a, 0x31, 0x38, 0x41, 0x48, 0x53, 0x5a,
0x65, 0x6c, 0x77, 0x7e, 0x09, 0x00, 0x1b, 0x12, 0x2d, 0x24, 0x3f, 0x36,
0x58, 0x51, 0x4a, 0x43, 0x7c, 0x75, 0x6e, 0x67, 0x10, 0x19, 0x02, 0x0b,
0x34, 0x3d, 0x26, 0x2f, 0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c,
0x3b, 0x32, 0x29, 0x20, 0x1f, 0x16, 0x0d, 0x04, 0x6a, 0x63, 0x78, 0x71,
0x4e, 0x47, 0x5c, 0x55, 0x22, 0x2b, 0x30, 0x39, 0x06, 0x0f, 0x14, 0x1d,
0x25, 0x2c, 0x37, 0x3e, 0x01, 0x08, 0x13, 0x1a, 0x6d, 0x64, 0x7f, 0x76,
0x49, 0x40, 0x5b, 0x52, 0x3c, 0x35, 0x2e, 0x27, 0x18, 0x11, 0x0a, 0x03,
0x74, 0x7d, 0x66, 0x6f, 0x50, 0x59, 0x42, 0x4b, 0x17, 0x1e, 0x05, 0x0c,
0x33, 0x3a, 0x21, 0x28, 0x5f, 0x56, 0x4d, 0x44, 0x7b, 0x72, 0x69, 0x60,
0x0e, 0x07, 0x1c, 0x15, 0x2a, 0x23, 0x38, 0x31, 0x46, 0x4f, 0x54, 0x5d,
0x62, 0x6b, 0x70, 0x79
};
/*===========================================================================*/ /*===========================================================================*/
/* Driver local functions. */ /* Driver local functions. */
/*===========================================================================*/ /*===========================================================================*/
/**
* @brief Calculate the MMC standard CRC-7 based on a lookup table.
*
* @param[in] crc start value for CRC
* @param[in] buffer pointer to data buffer
* @param[in] len length of data
* @return Calculated CRC
*/
static uint8_t crc7(uint8_t crc, const uint8_t *buffer, size_t len) {
while (len--)
crc = crc7_lookup_table[(crc << 1) ^ (*buffer++)];
return crc;
}
/** /**
* @brief Inserion monitor timer callback function. * @brief Inserion monitor timer callback function.
* *
@ -109,15 +157,15 @@ static void wait(MMCDriver *mmcp) {
* @brief Sends a command header. * @brief Sends a command header.
* *
* @param[in] mmcp pointer to the @p MMCDriver object * @param[in] mmcp pointer to the @p MMCDriver object
* @param cmd[in] the command id * @param[in] cmd the command id
* @param arg[in] the command argument * @param[in] arg the command argument
* *
* @notapi * @notapi
*/ */
static void send_hdr(MMCDriver *mmcp, uint8_t cmd, uint32_t arg) { static void send_hdr(MMCDriver *mmcp, uint8_t cmd, uint32_t arg) {
uint8_t buf[6]; uint8_t buf[6];
/* Wait for the bus to become idle if a write operation was in progress. */ /* Wait for the bus to become idle if a write operation was in progress.*/
wait(mmcp); wait(mmcp);
buf[0] = 0x40 | cmd; buf[0] = 0x40 | cmd;
@ -125,7 +173,9 @@ static void send_hdr(MMCDriver *mmcp, uint8_t cmd, uint32_t arg) {
buf[2] = arg >> 16; buf[2] = arg >> 16;
buf[3] = arg >> 8; buf[3] = arg >> 8;
buf[4] = arg; buf[4] = arg;
buf[5] = 0x95; /* Valid for CMD0 ignored by other commands. */ /* Calculate CRC for command header, shift to right position, add stop bit.*/
buf[5] = ((crc7(0, buf, 5) & 0x7F) << 1) | 0x01;
spiSend(mmcp->spip, 6, buf); spiSend(mmcp->spip, 6, buf);
} }
@ -150,18 +200,37 @@ static uint8_t recvr1(MMCDriver *mmcp) {
return 0xFF; return 0xFF;
} }
/**
* @brief Receives a three byte response.
*
* @param[in] mmcp pointer to the @p MMCDriver object
* @param[out] buffer pointer to four bytes wide buffer
* @return First response byte as an @p uint8_t value.
* @retval 0xFF timed out.
*
* @notapi
*/
static uint8_t recvr3(MMCDriver *mmcp, uint8_t* buffer) {
uint8_t r1;
r1 = recvr1(mmcp);
spiReceive(mmcp->spip, 4, buffer);
return r1;
}
/** /**
* @brief Sends a command an returns a single byte response. * @brief Sends a command an returns a single byte response.
* *
* @param[in] mmcp pointer to the @p MMCDriver object * @param[in] mmcp pointer to the @p MMCDriver object
* @param cmd[in] the command id * @param[in] cmd the command id
* @param arg[in] the command argument * @param[in] arg the command argument
* @return The response as an @p uint8_t value. * @return The response as an @p uint8_t value.
* @retval 0xFF timed out. * @retval 0xFF timed out.
* *
* @notapi * @notapi
*/ */
static uint8_t send_command(MMCDriver *mmcp, uint8_t cmd, uint32_t arg) { static uint8_t send_command_R1(MMCDriver *mmcp, uint8_t cmd, uint32_t arg) {
uint8_t r1; uint8_t r1;
spiSelect(mmcp->spip); spiSelect(mmcp->spip);
@ -171,6 +240,30 @@ static uint8_t send_command(MMCDriver *mmcp, uint8_t cmd, uint32_t arg) {
return r1; return r1;
} }
/**
* @brief Sends a command which returns a five bytes response (R3).
*
* @param[in] mmcp pointer to the @p MMCDriver object
* @param[in] cmd the command id
* @param[in] arg the command argument
* @param[out] response pointer to four bytes wide uint8_t buffer
* @return The first byte of the response (R1) as an @p
* uint8_t value.
* @retval 0xFF timed out.
*
* @notapi
*/
static uint8_t send_command_R3(MMCDriver *mmcp, uint8_t cmd, uint32_t arg,
uint8_t *response) {
uint8_t r1;
spiSelect(mmcp->spip);
send_hdr(mmcp, cmd, arg);
r1 = recvr3(mmcp, response);
spiUnselect(mmcp->spip);
return r1;
}
/** /**
* @brief Waits that the card reaches an idle state. * @brief Waits that the card reaches an idle state.
* *
@ -233,6 +326,7 @@ void mmcObjectInit(MMCDriver *mmcp, SPIDriver *spip,
mmcp->hscfg = hscfg; mmcp->hscfg = hscfg;
mmcp->is_protected = is_protected; mmcp->is_protected = is_protected;
mmcp->is_inserted = is_inserted; mmcp->is_inserted = is_inserted;
mmcp->block_addresses = FALSE;
chEvtInit(&mmcp->inserted_event); chEvtInit(&mmcp->inserted_event);
chEvtInit(&mmcp->removed_event); chEvtInit(&mmcp->removed_event);
} }
@ -315,17 +409,47 @@ bool_t mmcConnect(MMCDriver *mmcp) {
/* SPI mode selection.*/ /* SPI mode selection.*/
i = 0; i = 0;
while (TRUE) { while (TRUE) {
if (send_command(mmcp, MMC_CMDGOIDLE, 0) == 0x01) if (send_command_R1(mmcp, MMC_CMDGOIDLE, 0) == 0x01)
break; break;
if (++i >= MMC_CMD0_RETRY) if (++i >= MMC_CMD0_RETRY)
return TRUE; return TRUE;
chThdSleepMilliseconds(10); chThdSleepMilliseconds(10);
} }
/* Try to detect if this is a high capacity card and switch to block
* addresses if possible.
*
* This method is based on "How to support SDC Ver2 and high capacity cards"
* by ElmChan.
*
* */
uint8_t r3[4];
if(send_command_R3(mmcp, MMC_CMDINTERFACE_CONDITION, 0x01AA, r3) != 0x05){
/* Switch to SDHC mode */
i = 0;
while (TRUE) {
if ((send_command_R1(mmcp, MMC_CMDAPP, 0) == 0x01) &&
(send_command_R3(mmcp, MMC_ACMDOPCONDITION, 0x400001aa, r3) == 0x00))
break;
if (++i >= MMC_ACMD41_RETRY)
return TRUE;
chThdSleepMilliseconds(10);
}
/* Execute dedicated read on OCR register */
send_command_R3(mmcp, MMC_CMDREADOCR, 0, r3);
/* Check if CCS is set in response. Card operates in block mode if set */
if(r3[0] & 0x40)
mmcp->block_addresses = TRUE;
}
/* Initialization. */ /* Initialization. */
i = 0; i = 0;
while (TRUE) { while (TRUE) {
uint8_t b = send_command(mmcp, MMC_CMDINIT, 0); uint8_t b = send_command_R1(mmcp, MMC_CMDINIT, 0);
if (b == 0x00) if (b == 0x00)
break; break;
if (b != 0x01) if (b != 0x01)
@ -339,7 +463,7 @@ bool_t mmcConnect(MMCDriver *mmcp) {
spiStart(mmcp->spip, mmcp->hscfg); spiStart(mmcp->spip, mmcp->hscfg);
/* Setting block size.*/ /* Setting block size.*/
if (send_command(mmcp, MMC_CMDSETBLOCKLEN, MMC_SECTOR_SIZE) != 0x00) if (send_command_R1(mmcp, MMC_CMDSETBLOCKLEN, MMC_SECTOR_SIZE) != 0x00)
return TRUE; return TRUE;
/* Transition to MMC_READY state (if not extracted).*/ /* Transition to MMC_READY state (if not extracted).*/
@ -419,7 +543,12 @@ bool_t mmcStartSequentialRead(MMCDriver *mmcp, uint32_t startblk) {
spiStart(mmcp->spip, mmcp->hscfg); spiStart(mmcp->spip, mmcp->hscfg);
spiSelect(mmcp->spip); spiSelect(mmcp->spip);
if(mmcp->block_addresses)
send_hdr(mmcp, MMC_CMDREADMULTIPLE, startblk);
else
send_hdr(mmcp, MMC_CMDREADMULTIPLE, startblk * MMC_SECTOR_SIZE); send_hdr(mmcp, MMC_CMDREADMULTIPLE, startblk * MMC_SECTOR_SIZE);
if (recvr1(mmcp) != 0x00) { if (recvr1(mmcp) != 0x00) {
spiUnselect(mmcp->spip); spiUnselect(mmcp->spip);
chSysLock(); chSysLock();
@ -534,7 +663,12 @@ bool_t mmcStartSequentialWrite(MMCDriver *mmcp, uint32_t startblk) {
spiStart(mmcp->spip, mmcp->hscfg); spiStart(mmcp->spip, mmcp->hscfg);
spiSelect(mmcp->spip); spiSelect(mmcp->spip);
if(mmcp->block_addresses)
send_hdr(mmcp, MMC_CMDWRITEMULTIPLE, startblk);
else
send_hdr(mmcp, MMC_CMDWRITEMULTIPLE, startblk * MMC_SECTOR_SIZE); send_hdr(mmcp, MMC_CMDWRITEMULTIPLE, startblk * MMC_SECTOR_SIZE);
if (recvr1(mmcp) != 0x00) { if (recvr1(mmcp) != 0x00) {
spiUnselect(mmcp->spip); spiUnselect(mmcp->spip);
chSysLock(); chSysLock();

View File

@ -79,6 +79,8 @@
- FIX: Fixed SYSCFG clock not started in STM32L1/F4 HALs (bug 3449139). - FIX: Fixed SYSCFG clock not started in STM32L1/F4 HALs (bug 3449139).
- FIX: Fixed wrong definitions in STM32L-Discovery board file (bug 3449076). - FIX: Fixed wrong definitions in STM32L-Discovery board file (bug 3449076).
- OPT: Improved the exception exit code in the GCC Cortex-Mx ports. - OPT: Improved the exception exit code in the GCC Cortex-Mx ports.
- NEW: MMC over SPI driver improved to handle high capacity cards, by
Matthias Blaicher.
- NEW: Added PVD support to the HAL of all STM32s, by Barthess. - NEW: Added PVD support to the HAL of all STM32s, by Barthess.
- NEW: Added to the HAL driver the handling of an abstract realtime free - NEW: Added to the HAL driver the handling of an abstract realtime free
running counter, added the capability to all the STM32 HALs. running counter, added the capability to all the STM32 HALs.