From 7668607e63bde8848abf43040291ff0a3ef5691d Mon Sep 17 00:00:00 2001 From: utzig Date: Fri, 29 Aug 2014 19:09:38 +0000 Subject: [KINETIS] Initial I2C driver for K2x git-svn-id: svn://svn.code.sf.net/p/chibios/svn/trunk@7206 35acf78f-673a-0410-8e92-d51de3d6d3f4 --- os/hal/ports/KINETIS/K20x/i2c_lld.c | 374 ++++++++++++++++++++++++++++++++++ os/hal/ports/KINETIS/K20x/i2c_lld.h | 208 +++++++++++++++++++ os/hal/ports/KINETIS/K20x/platform.mk | 1 + 3 files changed, 583 insertions(+) create mode 100644 os/hal/ports/KINETIS/K20x/i2c_lld.c create mode 100644 os/hal/ports/KINETIS/K20x/i2c_lld.h diff --git a/os/hal/ports/KINETIS/K20x/i2c_lld.c b/os/hal/ports/KINETIS/K20x/i2c_lld.c new file mode 100644 index 000000000..65110bfe8 --- /dev/null +++ b/os/hal/ports/KINETIS/K20x/i2c_lld.c @@ -0,0 +1,374 @@ +/* + ChibiOS/RT - Copyright (C) 2014 Fabio Utzig + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/** + * @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. */ +/*===========================================================================*/ + +void config_frequency(I2CDriver *i2cp) { + /* TODO */ + i2cp->i2c->F = 0x20; +} + +/** + * @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 *i2c = i2cp->i2c; + intstate_t state = i2cp->intstate; + + if (i2c->S & I2Cx_S_ARBL) { + + i2cp->errors |= I2C_ARBITRATION_LOST; + i2c->S |= I2Cx_S_ARBL; + + } else if (state == STATE_SEND) { + + if (i2c->S & I2Cx_S_RXAK) + i2cp->errors |= I2C_ACK_FAILURE; + else if (i2cp->txbuf != NULL && i2cp->txidx < i2cp->txbytes) + i2c->D = i2cp->txbuf[i2cp->txidx++]; + else + i2cp->intstate = STATE_STOP; + + } else if (state == STATE_DUMMY) { + + if (i2c->S & I2Cx_S_RXAK) + i2cp->errors |= I2C_ACK_FAILURE; + else { + i2c->C1 &= ~I2Cx_C1_TX; + + if (i2cp->rxbytes > 1) + i2c->C1 &= ~I2Cx_C1_TXAK; + else + i2c->C1 |= I2Cx_C1_TXAK; + (void) i2c->D; + i2cp->intstate = STATE_RECV; + } + + } else if (state == STATE_RECV) { + + if (i2cp->rxbytes > 1) { + if (i2cp->rxidx == (i2cp->rxbytes - 2)) + i2c->C1 |= I2Cx_C1_TXAK; + else + i2c->C1 &= ~I2Cx_C1_TXAK; + } + + if (i2cp->rxidx == i2cp->rxbytes - 1) + i2c->C1 &= ~(I2Cx_C1_TX | I2Cx_C1_MST); + + i2cp->rxbuf[i2cp->rxidx++] = i2c->D; + + if (i2cp->rxidx == i2cp->rxbytes) + i2cp->intstate = STATE_STOP; + } + + /* Reset interrupt flag */ + i2c->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: Vector6C on K2x; Vector60 on KL2x */ +PORT_IRQ_HANDLER(Vector6C) { + + PORT_IRQ_PROLOGUE(); + serve_interrupt(&I2CD1); + PORT_IRQ_EPILOGUE(); +} + +#endif + +#if KINETIS_I2C_USE_I2C1 || defined(__DOXYGEN__) + +/* FIXME: KL2x has I2C1 on Vector64; K2x don't have I2C1! */ +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.i2c = I2C0; +#endif + +#if KINETIS_I2C_USE_I2C1 + i2cObjectInit(&I2CD2); + I2CD2.thread = NULL; + I2CD2.i2c = 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 + + } + + config_frequency(i2cp); + i2cp->i2c->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) { + + i2cp->i2c->C1 &= ~(I2Cx_C1_IICEN | I2Cx_C1_IICIE); + +#if KINETIS_I2C_USE_I2C0 + if (&I2CD1 == i2cp) { + SIM->SCGC4 &= ~SIM_SCGC4_I2C0; + nvicDisableVector(I2C0_IRQn); + } +#endif + +#if KINETIS_I2C_USE_I2C1 + if (&I2CD2 == i2cp) { + SIM->SCGC4 &= ~SIM_SCGC4_I2C1; + nvicDisableVector(I2C1_IRQn); + } +#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 op = (i2cp->intstate == STATE_SEND) ? 0 : 1; + + i2cp->errors = I2C_NO_ERROR; + i2cp->addr = addr; + + i2cp->txbuf = txbuf; + i2cp->txbytes = txbytes; + i2cp->txidx = 0; + + i2cp->rxbuf = rxbuf; + i2cp->rxbytes = rxbytes; + i2cp->rxidx = 0; + + /* send START */ + i2cp->i2c->C1 |= I2Cx_C1_MST; + i2cp->i2c->C1 |= I2Cx_C1_TX; + + /* FIXME: should not use busy waiting! */ + while (!(i2cp->i2c->S & I2Cx_S_BUSY)); + + i2cp->i2c->D = addr << 1 | op; + + msg = osalThreadSuspendTimeoutS(&i2cp->thread, TIME_INFINITE); + + /* FIXME */ + //if (i2cp->i2c->S & I2Cx_S_RXAK) + // i2cp->errors |= I2C_ACK_FAILURE; + + if (msg == MSG_OK && txbuf != NULL && rxbuf != NULL) { + i2cp->i2c->C1 |= I2Cx_C1_RSTA; + /* FIXME */ + while (!(i2cp->i2c->S & I2Cx_S_BUSY)); + + i2cp->intstate = STATE_DUMMY; + i2cp->i2c->D = i2cp->addr << 1 | 1; + + msg = osalThreadSuspendTimeoutS(&i2cp->thread, TIME_INFINITE); + } + + i2cp->i2c->C1 &= ~(I2Cx_C1_TX | I2Cx_C1_MST); + /* FIXME */ + while (i2cp->i2c->S & I2Cx_S_BUSY); + + return msg; +} + +/** + * @brief Receives data via the I2C bus as master. + * + * @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. After a + * timeout the driver must be stopped and restarted + * because the bus is in an uncertain state. + * + * @notapi + */ +msg_t i2c_lld_master_receive_timeout(I2CDriver *i2cp, i2caddr_t addr, + uint8_t *rxbuf, size_t rxbytes, + systime_t timeout) { + + i2cp->intstate = STATE_DUMMY; + return _i2c_txrx_timeout(i2cp, addr, NULL, 0, rxbuf, rxbytes, timeout); +} + +/** + * @brief Transmits data via the I2C bus as master. + * + * @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. After a + * timeout the driver must be stopped and restarted + * because the bus is in an uncertain state. + * + * @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) { + + i2cp->intstate = STATE_SEND; + return _i2c_txrx_timeout(i2cp, addr, txbuf, txbytes, rxbuf, rxbytes, timeout); +} + +#endif /* HAL_USE_I2C */ + +/** @} */ diff --git a/os/hal/ports/KINETIS/K20x/i2c_lld.h b/os/hal/ports/KINETIS/K20x/i2c_lld.h new file mode 100644 index 000000000..a3ce29620 --- /dev/null +++ b/os/hal/ports/KINETIS/K20x/i2c_lld.h @@ -0,0 +1,208 @@ +/* + ChibiOS/RT - Copyright (C) 2014 Fabio Utzig + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/** + * @file KINETIS/i2c_lld.h + * @brief KINETIS I2C subsystem low level driver header. + * + * @addtogroup I2C + * @{ + */ + +#ifndef _I2C_LLD_H_ +#define _I2C_LLD_H_ + +#if HAL_USE_I2C || defined(__DOXYGEN__) + +/*===========================================================================*/ +/* Driver constants. */ +/*===========================================================================*/ + +#define STATE_STOP 0x00 +#define STATE_SEND 0x01 +#define STATE_RECV 0x02 +#define STATE_DUMMY 0x03 + +/*===========================================================================*/ +/* Driver pre-compile time settings. */ +/*===========================================================================*/ + +/** + * @name Configuration options + * @{ + */ +/** + * @brief I2C0 driver enable switch. + * @details If set to @p TRUE the support for I2C0 is included. + * @note The default is @p FALSE. + */ +#if !defined(KINETIS_I2C_USE_I2C0) || defined(__DOXYGEN__) +#define KINETIS_I2C_USE_I2C0 FALSE +#endif + +/** + * @brief I2C1 driver enable switch. + * @details If set to @p TRUE the support for I2C1 is included. + * @note The default is @p FALSE. + */ +#if !defined(KINETIS_I2C_USE_I2C1) || defined(__DOXYGEN__) +#define KINETIS_I2C_USE_I2C1 FALSE +#endif +/** @} */ + +/*===========================================================================*/ +/* Derived constants and error checks. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* Driver data structures and types. */ +/*===========================================================================*/ + +/* @brief Type representing I2C address. */ +typedef uint8_t i2caddr_t; + +/* @brief Type of I2C Driver condition flags. */ +typedef uint32_t i2cflags_t; + +/* @brief Type used to control the ISR state machine. */ +typedef uint8_t intstate_t; + +/** + * @brief Driver configuration structure. + * @note Implementations may extend this structure to contain more, + * architecture dependent, fields. + */ + +/** + * @brief Driver configuration structure. + */ +typedef struct { + + /* @brief Clock to be used for the I2C bus. */ + uint32_t clock; + +} I2CConfig; + +/** + * @brief Type of a structure representing an I2C driver. + */ +typedef struct I2CDriver I2CDriver; + +/** + * @brief Structure representing an I2C driver. + */ +struct I2CDriver { + /** + * @brief Driver state. + */ + i2cstate_t state; + /** + * @brief Current configuration data. + */ + const I2CConfig *config; + /** + * @brief Error flags. + */ + i2cflags_t errors; +#if I2C_USE_MUTUAL_EXCLUSION || defined(__DOXYGEN__) +#if CH_CFG_USE_MUTEXES || defined(__DOXYGEN__) + /** + * @brief Mutex protecting the bus. + */ + mutex_t mutex; +#elif CH_CFG_USE_SEMAPHORES + semaphore_t semaphore; +#endif +#endif /* I2C_USE_MUTUAL_EXCLUSION */ +#if defined(I2C_DRIVER_EXT_FIELDS) + I2C_DRIVER_EXT_FIELDS +#endif + /* @brief Thread waiting for I/O completion. */ + thread_reference_t thread; + /* @brief Current slave address without R/W bit. */ + i2caddr_t addr; + + /* End of the mandatory fields.*/ + + /* @brief Pointer to the buffer with data to send. */ + const uint8_t *txbuf; + /* @brief Number of bytes of data to send. */ + size_t txbytes; + /* @brief Current index in buffer when sending data. */ + size_t txidx; + /* @brief Pointer to the buffer to put received data. */ + uint8_t *rxbuf; + /* @brief Number of bytes of data to receive. */ + size_t rxbytes; + /* @brief Current index in buffer when receiving data. */ + size_t rxidx; + /* @brief Tracks current ISR state. */ + intstate_t intstate; + /* @brief Low-level register access. */ + I2C_TypeDef *i2c; +}; + +/*===========================================================================*/ +/* Driver macros. */ +/*===========================================================================*/ + +/** + * @brief Get errors from I2C driver. + * + * @param[in] i2cp pointer to the @p I2CDriver object + * + * @notapi + */ +#define i2c_lld_get_errors(i2cp) ((i2cp)->errors) + +/*===========================================================================*/ +/* External declarations. */ +/*===========================================================================*/ + +#if !defined(__DOXYGEN__) + +#if KINETIS_I2C_USE_I2C0 +extern I2CDriver I2CD1; +#endif + +#if KINETIS_I2C_USE_I2C1 +extern I2CDriver I2CD2; +#endif + +#endif + +#ifdef __cplusplus +extern "C" { +#endif + void i2c_lld_init(void); + void i2c_lld_start(I2CDriver *i2cp); + void i2c_lld_stop(I2CDriver *i2cp); + 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); + msg_t i2c_lld_master_receive_timeout(I2CDriver *i2cp, i2caddr_t addr, + uint8_t *rxbuf, size_t rxbytes, + systime_t timeout); +#ifdef __cplusplus +} +#endif + +#endif /* HAL_USE_I2C */ + +#endif /* _I2C_LLD_H_ */ + +/** @} */ diff --git a/os/hal/ports/KINETIS/K20x/platform.mk b/os/hal/ports/KINETIS/K20x/platform.mk index 2d5ab5859..afe7c21ad 100644 --- a/os/hal/ports/KINETIS/K20x/platform.mk +++ b/os/hal/ports/KINETIS/K20x/platform.mk @@ -4,6 +4,7 @@ PLATFORMSRC = ${CHIBIOS}/os/hal/ports/common/ARMCMx/nvic.c \ ${CHIBIOS}/os/hal/ports/KINETIS/K20x/pal_lld.c \ ${CHIBIOS}/os/hal/ports/KINETIS/K20x/serial_lld.c \ ${CHIBIOS}/os/hal/ports/KINETIS/K20x/spi_lld.c \ + ${CHIBIOS}/os/hal/ports/KINETIS/K20x/i2c_lld.c \ ${CHIBIOS}/os/hal/ports/KINETIS/K20x/st_lld.c # Required include directories -- cgit v1.2.3