Skip to content

Instantly share code, notes, and snippets.

@utzig
Created August 29, 2014 13:14
Show Gist options
  • Save utzig/3e73cd655fb53ed8ff99 to your computer and use it in GitHub Desktop.
Save utzig/3e73cd655fb53ed8ff99 to your computer and use it in GitHub Desktop.
ChibiOS/RT Kinetis I2C driver preview
/**
* @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