Created
August 29, 2014 13:14
-
-
Save utzig/3e73cd655fb53ed8ff99 to your computer and use it in GitHub Desktop.
ChibiOS/RT Kinetis I2C driver preview
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @file KINETIS/i2c_lld.c | |
* @brief KINETIS I2C subsystem low level driver source. | |
* | |
* @addtogroup I2C | |
* @{ | |
*/ | |
#include "osal.h" | |
#include "hal.h" | |
#if HAL_USE_I2C || defined(__DOXYGEN__) | |
/*===========================================================================*/ | |
/* Driver local definitions. */ | |
/*===========================================================================*/ | |
/*===========================================================================*/ | |
/* Driver exported variables. */ | |
/*===========================================================================*/ | |
/** | |
* @brief I2C0 driver identifier. | |
*/ | |
#if KINETIS_I2C_USE_I2C0 || defined(__DOXYGEN__) | |
I2CDriver I2CD1; | |
#endif | |
/** | |
* @brief I2C1 driver identifier. | |
*/ | |
#if KINETIS_I2C_USE_I2C1 || defined(__DOXYGEN__) | |
I2CDriver I2CD2; | |
#endif | |
/*===========================================================================*/ | |
/* Driver local variables and types. */ | |
/*===========================================================================*/ | |
/*===========================================================================*/ | |
/* Driver local functions. */ | |
/*===========================================================================*/ | |
/** | |
* @brief Common IRQ handler. | |
* @note Tries hard to clear all the pending interrupt sources, we don't | |
* want to go through the whole ISR and have another interrupt soon | |
* after. | |
* | |
* @param[in] i2cp pointer to an I2CDriver | |
*/ | |
static void serve_interrupt(I2CDriver *i2cp) { | |
I2C_TypeDef *dev = i2cp->dev; | |
intstate_t state = i2cp->intstate; | |
if (dev->S & I2Cx_S_ARBL) { | |
i2cp->errors |= I2C_ARBITRATION_LOST; | |
dev->S |= I2Cx_S_ARBL; | |
} else if (state == STATE_SEND) { | |
if (dev->S & I2Cx_S_RXAK) | |
i2cp->errors |= I2C_ACK_FAILURE; | |
else if (i2cp->txbuf != NULL && i2cp->txidx < i2cp->txbytes) | |
dev->D = i2cp->txbuf[i2cp->txidx++]; | |
else | |
i2cp->intstate = STATE_STOP; | |
} else if (state == STATE_DUMMY) { | |
if (dev->S & I2Cx_S_RXAK) | |
i2cp->errors |= I2C_ACK_FAILURE; | |
else { | |
dev->C1 &= ~I2Cx_C1_TX; | |
if (i2cp->rxbytes > 1) | |
dev->C1 &= ~I2Cx_C1_TXAK; | |
else | |
dev->C1 |= I2Cx_C1_TXAK; | |
(void) dev->D; | |
i2cp->intstate = STATE_RECV; | |
} | |
} else if (state == STATE_RECV) { | |
if (i2cp->rxbytes > 1) { | |
if (i2cp->rxidx == (i2cp->rxbytes - 2)) | |
dev->C1 |= I2Cx_C1_TXAK; | |
else | |
dev->C1 &= ~I2Cx_C1_TXAK; | |
} | |
if (i2cp->rxidx == i2cp->rxbytes - 1) | |
dev->C1 &= ~(I2Cx_C1_TX | I2Cx_C1_MST); | |
i2cp->rxbuf[i2cp->rxidx++] = dev->D; | |
if (i2cp->rxidx == i2cp->rxbytes) | |
i2cp->intstate = STATE_STOP; | |
} | |
/* Reset interrupt flag */ | |
dev->S |= I2Cx_S_IICIF; | |
if (i2cp->errors != I2C_NO_ERROR) | |
_i2c_wakeup_error_isr(i2cp); | |
if (i2cp->intstate == STATE_STOP) | |
_i2c_wakeup_isr(i2cp); | |
} | |
/*===========================================================================*/ | |
/* Driver interrupt handlers. */ | |
/*===========================================================================*/ | |
#if KINETIS_I2C_USE_I2C0 || defined(__DOXYGEN__) | |
/* FIXME: It's Vector6C on K2x only */ | |
PORT_IRQ_HANDLER(Vector6C) { | |
PORT_IRQ_PROLOGUE(); | |
serve_interrupt(&I2CD1); | |
PORT_IRQ_EPILOGUE(); | |
} | |
#endif | |
#if KINETIS_I2C_USE_I2C1 || defined(__DOXYGEN__) | |
/* FIXME: There's no I2C1 on K2x */ | |
PORT_IRQ_HANDLER(Vector64) { | |
PORT_IRQ_PROLOGUE(); | |
serve_interrupt(&I2CD2); | |
PORT_IRQ_EPILOGUE(); | |
} | |
#endif | |
/*===========================================================================*/ | |
/* Driver exported functions. */ | |
/*===========================================================================*/ | |
/** | |
* @brief Low level I2C driver initialization. | |
* | |
* @notapi | |
*/ | |
void i2c_lld_init(void) { | |
#if KINETIS_I2C_USE_I2C0 | |
i2cObjectInit(&I2CD1); | |
I2CD1.thread = NULL; | |
I2CD1.dev = I2C0; | |
#endif | |
#if KINETIS_I2C_USE_I2C1 | |
i2cObjectInit(&I2CD2); | |
I2CD2.thread = NULL; | |
I2CD2.dev = I2C1; | |
#endif | |
} | |
/** | |
* @brief Configures and activates the I2C peripheral. | |
* | |
* @param[in] i2cp pointer to the @p I2CDriver object | |
* | |
* @notapi | |
*/ | |
void i2c_lld_start(I2CDriver *i2cp) { | |
if (i2cp->state == I2C_STOP) { | |
/* TODO: | |
* The PORT must be enabled somewhere. The PIN multiplexer | |
* will map the I2C functionality to some PORT which must | |
* than be enabled. The easier way is enabling all PORTs at | |
* startup, which is currently being done in __early_init. | |
*/ | |
#if KINETIS_I2C_USE_I2C0 | |
if (&I2CD1 == i2cp) { | |
SIM->SCGC4 |= SIM_SCGC4_I2C0; | |
nvicEnableVector(I2C0_IRQn, KINETIS_I2C_I2C0_PRIORITY); | |
} | |
#endif | |
#if KINETIS_I2C_USE_I2C1 | |
if (&I2CD2 == i2cp) { | |
SIM->SCGC4 |= SIM_SCGC4_I2C1; | |
nvicEnableVector(I2C1_IRQn, KINETIS_I2C_I2C1_PRIORITY); | |
} | |
#endif | |
} | |
i2cp->dev->F = 0x20; | |
i2cp->dev->C1 |= I2Cx_C1_IICEN | I2Cx_C1_IICIE; | |
i2cp->intstate = STATE_STOP; | |
} | |
/** | |
* @brief Deactivates the I2C peripheral. | |
* | |
* @param[in] i2cp pointer to the @p I2CDriver object | |
* | |
* @notapi | |
*/ | |
void i2c_lld_stop(I2CDriver *i2cp) { | |
if (i2cp->state != I2C_STOP) { | |
#if KINETIS_I2C_USE_I2C0 | |
if (&I2CD1 == i2cp) { | |
} | |
#endif | |
#if KINETIS_I2C_USE_I2C1 | |
if (&I2CD2 == i2cp) { | |
} | |
#endif | |
} | |
} | |
static inline msg_t _i2c_txrx_timeout(I2CDriver *i2cp, i2caddr_t addr, | |
const uint8_t *txbuf, size_t txbytes, | |
uint8_t *rxbuf, size_t rxbytes, | |
systime_t timeout) { | |
(void)timeout; | |
msg_t msg; | |
uint8_t rw = (txbuf == NULL || txbytes == 0) ? 1 : 0; | |
i2cp->errors = I2C_NO_ERROR; | |
i2cp->addr = addr; | |
//i2cp->intstate = (rw == 1) ? STATE_RECV : STATE_SEND; | |
i2cp->intstate = STATE_SEND; | |
i2cp->txbuf = txbuf; | |
i2cp->txbytes = txbytes; | |
i2cp->txidx = 0; | |
i2cp->rxbuf = rxbuf; | |
i2cp->rxbytes = rxbytes; | |
i2cp->rxidx = 0; | |
/* send START */ | |
i2cp->dev->C1 |= I2Cx_C1_MST; | |
i2cp->dev->C1 |= I2Cx_C1_TX; | |
/* FIXME: busy waiting?!? SRSLY??? */ | |
while (!(i2cp->dev->S & I2Cx_S_BUSY)); | |
i2cp->dev->D = addr << 1 | rw; | |
msg = osalThreadSuspendTimeoutS(&i2cp->thread, TIME_INFINITE); | |
/* FIXME */ | |
//if (i2cp->dev->S & I2Cx_S_RXAK) | |
// i2cp->errors |= I2C_ACK_FAILURE; | |
if (msg == MSG_OK && txbuf != NULL && rxbuf != NULL) { | |
i2cp->dev->C1 |= I2Cx_C1_RSTA; | |
/* FIXME */ | |
while (!(i2cp->dev->S & I2Cx_S_BUSY)); | |
i2cp->intstate = STATE_DUMMY; | |
i2cp->dev->D = i2cp->addr << 1 | 1; | |
msg = osalThreadSuspendTimeoutS(&i2cp->thread, TIME_INFINITE); | |
} | |
i2cp->dev->C1 &= ~(I2Cx_C1_TX | I2Cx_C1_MST); | |
/* FIXME */ | |
while (i2cp->dev->S & I2Cx_S_BUSY); | |
return msg; | |
} | |
/** | |
* @brief Receives data via the I2C bus as master. | |
* @details Number of receiving bytes must be more than 1 on STM32F1x. This is | |
* hardware restriction. | |
* | |
* @param[in] i2cp pointer to the @p I2CDriver object | |
* @param[in] addr slave device address | |
* @param[out] rxbuf pointer to the receive buffer | |
* @param[in] rxbytes number of bytes to be received | |
* @param[in] timeout the number of ticks before the operation timeouts, | |
* the following special values are allowed: | |
* - @a TIME_INFINITE no timeout. | |
* . | |
* @return The operation status. | |
* @retval MSG_OK if the function succeeded. | |
* @retval MSG_RESET if one or more I2C errors occurred, the errors can | |
* be retrieved using @p i2cGetErrors(). | |
* @retval MSG_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 | |
*/ | |
msg_t i2c_lld_master_receive_timeout(I2CDriver *i2cp, i2caddr_t addr, | |
uint8_t *rxbuf, size_t rxbytes, | |
systime_t timeout) { | |
return _i2c_txrx_timeout(i2cp, addr, NULL, 0, rxbuf, rxbytes, timeout); | |
} | |
/** | |
* @brief Transmits data via the I2C bus as master. | |
* @details Number of receiving bytes must be 0 or more than 1 on STM32F1x. | |
* This is hardware restriction. | |
* | |
* @param[in] i2cp pointer to the @p I2CDriver object | |
* @param[in] addr slave device address | |
* @param[in] txbuf pointer to the transmit buffer | |
* @param[in] txbytes number of bytes to be transmitted | |
* @param[out] rxbuf pointer to the receive buffer | |
* @param[in] rxbytes number of bytes to be received | |
* @param[in] timeout the number of ticks before the operation timeouts, | |
* the following special values are allowed: | |
* - @a TIME_INFINITE no timeout. | |
* . | |
* @return The operation status. | |
* @retval MSG_OK if the function succeeded. | |
* @retval MSG_RESET if one or more I2C errors occurred, the errors can | |
* be retrieved using @p i2cGetErrors(). | |
* @retval MSG_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 | |
*/ | |
msg_t i2c_lld_master_transmit_timeout(I2CDriver *i2cp, i2caddr_t addr, | |
const uint8_t *txbuf, size_t txbytes, | |
uint8_t *rxbuf, size_t rxbytes, | |
systime_t timeout) { | |
return _i2c_txrx_timeout(i2cp, addr, txbuf, txbytes, rxbuf, rxbytes, timeout); | |
} | |
#endif /* HAL_USE_I2C */ | |
/** @} */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment