diff --git a/os/hal/lib/fallback/I2C/hal_i2c_lld.c b/os/hal/lib/fallback/I2C/hal_i2c_lld.c new file mode 100644 index 0000000000..f01acce89a --- /dev/null +++ b/os/hal/lib/fallback/I2C/hal_i2c_lld.c @@ -0,0 +1,482 @@ +/* + ChibiOS - Copyright (C) 2006..2018 Giovanni Di Sirio + + 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 common/I2C/hal_i2c_lld.c + * @brief SW I2C subsystem low level driver source. + * + * @addtogroup I2C + * @{ + */ + +#include "hal.h" + +#if HAL_USE_I2C || defined(__DOXYGEN__) + +/*===========================================================================*/ +/* Driver local definitions. */ +/*===========================================================================*/ +#if (SW_I2C_USE_OPENDRAIN == TRUE) || defined(__DOXYGEN__) +# define CHECK_ERROR(msg) \ + if ((msg) < (msg_t)0) { \ + palSetLine(i2cp->config->sda); \ + palSetLine(i2cp->config->scl); \ + return MSG_TIMEOUT; \ + } +#else +# define CHECK_ERROR(msg) \ + if ((msg) < (msg_t)0) { \ + palSetLineMode(i2cp->config->scl, PAL_MODE_OUTPUT_PUSHPULL); \ + palSetLineMode(i2cp->config->sda, PAL_MODE_OUTPUT_PUSHPULL); \ + palSetLine(i2cp->config->sda); \ + palSetLine(i2cp->config->scl); \ + return MSG_TIMEOUT; \ + } +#endif + +/*===========================================================================*/ +/* Driver constants. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* Driver exported variables. */ +/*===========================================================================*/ + +/** @brief I2C1 driver identifier.*/ +#if SW_I2C_USE_I2C1 || defined(__DOXYGEN__) +I2CDriver I2CD1; +#endif + +/** @brief I2C2 driver identifier.*/ +#if SW_I2C_USE_I2C2 || defined(__DOXYGEN__) +I2CDriver I2CD2; +#endif + +/** @brief I2C3 driver identifier.*/ +#if SW_I2C_USE_I2C3 || defined(__DOXYGEN__) +I2CDriver I2CD3; +#endif + +/** @brief I2C4 driver identifier.*/ +#if SW_I2C_USE_I2C4 || defined(__DOXYGEN__) +I2CDriver I2CD4; +#endif + +/*===========================================================================*/ +/* Driver local variables and types. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* Driver local functions. */ +/*===========================================================================*/ + +static msg_t i2c_write_stop(I2CDriver *i2cp); + +static inline void i2c_delay(I2CDriver *i2cp) { + +#if SW_I2C_USE_OSAL_DELAY || defined(__DOXYGEN__) + osalThreadSleepS(i2cp->config->ticks); +#else + i2cp->config->delay(); +#endif +} + +static inline msg_t i2c_check_arbitration(I2CDriver *i2cp) { +#if (SW_I2C_USE_OPENDRAIN == FALSE) + palSetLineMode(i2cp->config->sda, PAL_MODE_INPUT); +#endif + i2c_delay(i2cp); + if (palReadLine(i2cp->config->sda) == PAL_LOW) { + i2cp->errors |= I2C_ARBITRATION_LOST; + return MSG_RESET; + } +#if (SW_I2C_USE_OPENDRAIN == FALSE) + palSetLineMode(i2cp->config->sda, PAL_MODE_OUTPUT_PUSHPULL); +#endif + return MSG_OK; +} + +static inline msg_t i2c_check_timeout(I2CDriver *i2cp) { + + if ((i2cp->start != i2cp->end) && + (!osalTimeIsInRangeX(osalOsGetSystemTimeX(), i2cp->start, i2cp->end))) { + i2c_write_stop(i2cp); + return MSG_TIMEOUT; + } + + return MSG_OK; +} + +static msg_t i2c_wait_clock(I2CDriver *i2cp) { +#if (SW_I2C_USE_OPENDRAIN == FALSE) + palSetLineMode(i2cp->config->scl, PAL_MODE_INPUT); +#endif + i2c_delay(i2cp); + + while (palReadLine(i2cp->config->scl) == PAL_LOW) { + if ((i2cp->start != i2cp->end) && + (!osalTimeIsInRangeX(osalOsGetSystemTimeX(), i2cp->start, i2cp->end))) { + return MSG_TIMEOUT; + } + i2c_delay(i2cp); + } +#if (SW_I2C_USE_OPENDRAIN == FALSE) + palSetLineMode(i2cp->config->scl, PAL_MODE_OUTPUT_PUSHPULL); +#endif + return MSG_OK; +} + +static inline msg_t i2c_write_start(I2CDriver *i2cp) { + + /* Arbitration check.*/ + CHECK_ERROR(i2c_check_arbitration(i2cp)); + + palClearLine(i2cp->config->sda); + i2c_delay(i2cp); + palClearLine(i2cp->config->scl); + i2c_delay(i2cp); + + return MSG_OK; +} + +static msg_t i2c_write_restart(I2CDriver *i2cp) { + + palSetLine(i2cp->config->sda); + i2c_delay(i2cp); + palSetLine(i2cp->config->scl); + + /* Clock stretching.*/ + CHECK_ERROR(i2c_wait_clock(i2cp)); + + i2c_delay(i2cp); + i2c_write_start(i2cp); + + return MSG_OK; +} + +static msg_t i2c_write_stop(I2CDriver *i2cp) { + + palClearLine(i2cp->config->sda); + i2c_delay(i2cp); + palSetLine(i2cp->config->scl); + + /* Clock stretching.*/ + CHECK_ERROR(i2c_wait_clock(i2cp)); + + i2c_delay(i2cp); + palSetLine(i2cp->config->sda); + i2c_delay(i2cp); + + /* Arbitration check.*/ + CHECK_ERROR(i2c_check_arbitration(i2cp)); + + i2c_delay(i2cp); + + return MSG_OK; +} + +static msg_t i2c_write_bit(I2CDriver *i2cp, unsigned bit) { + + palWriteLine(i2cp->config->sda, bit); + + i2c_delay(i2cp); + palSetLine(i2cp->config->scl); + i2c_delay(i2cp); + + /* Clock stretching.*/ + CHECK_ERROR(i2c_wait_clock(i2cp)); + + /* Arbitration check.*/ + if (bit == PAL_HIGH) { + CHECK_ERROR(i2c_check_arbitration(i2cp)); + } + + palClearLine(i2cp->config->scl); + i2c_delay(i2cp); + + return MSG_OK; +} + +static msg_t i2c_read_bit(I2CDriver *i2cp) { + msg_t bit; +#if (SW_I2C_USE_OPENDRAIN == FALSE) + palSetLineMode(i2cp->config->sda, PAL_MODE_INPUT); +#endif + i2c_delay(i2cp); + palSetLine(i2cp->config->scl); + + /* Clock stretching.*/ + CHECK_ERROR(i2c_wait_clock(i2cp)); + + i2c_delay(i2cp); + + bit = palReadLine(i2cp->config->sda); +#if (SW_I2C_USE_OPENDRAIN == FALSE) + palSetLineMode(i2cp->config->sda, PAL_MODE_OUTPUT_PUSHPULL); +#endif + palClearLine(i2cp->config->scl); + i2c_delay(i2cp); + + return bit; +} + +static msg_t i2c_write_byte(I2CDriver *i2cp, uint8_t byte) { + msg_t msg; + + CHECK_ERROR(i2c_check_timeout(i2cp)); + +#if (SW_I2C_USE_OPENDRAIN == TRUE) || defined(__DOXYGEN__) + uint8_t mask; + for (mask = 0x80U; mask > 0U; mask >>= 1U) { + CHECK_ERROR(i2c_write_bit(i2cp, (byte & mask) != 0)); + } +#else + for(uint8_t i = 0; i < 8; i++) { + unsigned bit = ((0x80U >> i) & byte); + CHECK_ERROR(i2c_write_bit(i2cp, bit)); + } +#endif + msg = i2c_read_bit(i2cp); + CHECK_ERROR(msg); + + /* Checking for NACK.*/ + if (msg == PAL_HIGH) { + i2cp->errors |= I2C_ACK_FAILURE; + return MSG_RESET; + } + + return MSG_OK; +} + +static msg_t i2c_read_byte(I2CDriver *i2cp, unsigned nack) { + msg_t byte; + unsigned i; + + CHECK_ERROR(i2c_check_timeout(i2cp)); + + byte = 0U; + for (i = 0; i < 8; i++) { + msg_t msg = i2c_read_bit(i2cp); + CHECK_ERROR(msg); + byte = (byte << 1U) | msg; + } + + CHECK_ERROR(i2c_write_bit(i2cp, nack)); + + return byte; +} + +static msg_t i2c_write_header(I2CDriver *i2cp, i2caddr_t addr, bool rw) { + + /* Check for 10 bits addressing.*/ + if (i2cp->config->addr10) { + /* It is 10 bits.*/ + uint8_t b1, b2; + + b1 = 0xF0U | ((addr >> 8U) << 1U); + b2 = (uint8_t)(addr & 255U); + if (rw) { + b1 |= 1U; + } + CHECK_ERROR(i2c_write_byte(i2cp, b1)); + CHECK_ERROR(i2c_write_byte(i2cp, b2)); + } + else { + /* It is 7 bits.*/ + if (rw) { + /* Read.*/ + CHECK_ERROR(i2c_write_byte(i2cp, (addr << 1U) | 1U)); + } + else { + /* Write.*/ + CHECK_ERROR(i2c_write_byte(i2cp, (addr << 1U) | 0U)); + } + } + + return MSG_OK; +} + +/*===========================================================================*/ +/* Driver interrupt handlers. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* Driver exported functions. */ +/*===========================================================================*/ + +/** + * @brief Low level I2C driver initialization. + * + * @notapi + */ +void i2c_lld_init(void) { + +#if SW_I2C_USE_I2C1 + i2cObjectInit(&I2CD1); +#endif +#if SW_I2C_USE_I2C2 + i2cObjectInit(&I2CD2); +#endif +#if SW_I2C_USE_I2C3 + i2cObjectInit(&I2CD3); +#endif +#if SW_I2C_USE_I2C4 + i2cObjectInit(&I2CD4); +#endif +} + +/** + * @brief Configures and activates the I2C peripheral. + * + * @param[in] i2cp pointer to the @p I2CDriver object + * + * @notapi + */ +void i2c_lld_start(I2CDriver *i2cp) { + + /* Does nothing.*/ + (void)i2cp; +} + +/** + * @brief Deactivates the I2C peripheral. + * + * @param[in] i2cp pointer to the @p I2CDriver object + * + * @notapi + */ +void i2c_lld_stop(I2CDriver *i2cp) { + + /* Does nothing.*/ + (void)i2cp; +} + +/** + * @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, + sysinterval_t timeout) { + + /* Setting timeout fields.*/ + i2cp->start = osalOsGetSystemTimeX(); + i2cp->end = i2cp->start; + if (timeout != TIME_INFINITE) { + i2cp->end = osalTimeAddX(i2cp->start, timeout); + } + + CHECK_ERROR(i2c_write_start(i2cp)); + + /* Sending address and mode.*/ + CHECK_ERROR(i2c_write_header(i2cp, addr, true)); + + do { + /* Last byte sends a NACK.*/ + msg_t msg = i2c_read_byte(i2cp, rxbytes > 1U ? 0U : 1U); + CHECK_ERROR(msg); + *rxbuf++ = (uint8_t)msg; + } while (--rxbytes); + + return i2c_write_stop(i2cp); +} + +/** + * @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. 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, + sysinterval_t timeout) { + + /* Setting timeout fields.*/ + i2cp->start = osalOsGetSystemTimeX(); + i2cp->end = i2cp->start; + if (timeout != TIME_INFINITE) { + i2cp->end = osalTimeAddX(i2cp->start, timeout); + } + + /* Sending start condition.*/ + CHECK_ERROR(i2c_write_start(i2cp)); + + /* Sending address and mode.*/ + CHECK_ERROR(i2c_write_header(i2cp, addr, false)); + + do { + CHECK_ERROR(i2c_write_byte(i2cp, *txbuf++)); + } while (--txbytes); + + /* Is there a read phase? */ + if (rxbytes > 0U) { + + /* Sending restart condition.*/ + CHECK_ERROR(i2c_write_restart(i2cp)); + /* Sending address and mode.*/ + CHECK_ERROR(i2c_write_header(i2cp, addr, true)); + + do { + /* Last byte sends a NACK.*/ + msg_t msg = i2c_read_byte(i2cp, rxbytes > 1U ? 0U : 1U); + CHECK_ERROR(msg); + *rxbuf++ = (uint8_t)msg; + } while (--rxbytes); + } + + return i2c_write_stop(i2cp); +} + +#endif /* HAL_USE_I2C */ + +/** @} */ diff --git a/os/hal/lib/fallback/I2C/hal_i2c_lld.h b/os/hal/lib/fallback/I2C/hal_i2c_lld.h new file mode 100644 index 0000000000..3350dbbf72 --- /dev/null +++ b/os/hal/lib/fallback/I2C/hal_i2c_lld.h @@ -0,0 +1,248 @@ +/* + ChibiOS - Copyright (C) 2006..2018 Giovanni Di Sirio + + 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 common/I2C/hal_i2c_lld.h + * @brief SW I2C subsystem low level driver header. + * + * @addtogroup I2C + * @{ + */ + +#ifndef HAL_I2C_LLD_H +#define HAL_I2C_LLD_H + +#if HAL_USE_I2C || defined(__DOXYGEN__) + +/*===========================================================================*/ +/* Driver constants. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* Driver pre-compile time settings. */ +/*===========================================================================*/ + +/** + * @name Configuration options + * @{ + */ +/** + * @brief Use Open-drain configuration. + * @details If set to @p TRUE then the driver expects the SDA and SCL + * pins to be in open-drain configuration else it handles + * I2C functionality by switching input and output state. + * @note The default is @p TRUE. + */ +#if !defined(SW_I2C_USE_OPENDRAIN) || defined(__DOXYGEN__) +#define SW_I2C_USE_OPENDRAIN TRUE +#endif + +/** + * @brief Use OSAL delays. + * @details If set to @p TRUE then delays are implemented using the + * thread-friendly delay function else a delay function must + * be provided externally. + */ +#if !defined(SW_I2C_USE_OSAL_DELAY) || defined(__DOXYGEN__) +#define SW_I2C_USE_OSAL_DELAY TRUE +#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(SW_I2C_USE_I2C1) || defined(__DOXYGEN__) +#define SW_I2C_USE_I2C1 FALSE +#endif + +/** + * @brief I2C2 driver enable switch. + * @details If set to @p TRUE the support for I2C2 is included. + * @note The default is @p FALSE. + */ +#if !defined(SW_I2C_USE_I2C2) || defined(__DOXYGEN__) +#define SW_I2C_USE_I2C2 FALSE +#endif + +/** + * @brief I2C3 driver enable switch. + * @details If set to @p TRUE the support for I2C3 is included. + * @note The default is @p FALSE. + */ +#if !defined(SW_I2C_USE_I2C3) || defined(__DOXYGEN__) +#define SW_I2C_USE_I2C3 FALSE +#endif + +/** + * @brief I2C4 driver enable switch. + * @details If set to @p TRUE the support for I2C4 is included. + * @note The default is @p FALSE. + */ +#if !defined(SW_I2C_USE_I2C4) || defined(__DOXYGEN__) +#define SW_I2C_USE_I2C4 FALSE +#endif +/** @} */ + +/*===========================================================================*/ +/* Derived constants and error checks. */ +/*===========================================================================*/ + +/*===========================================================================*/ +/* Driver data structures and types. */ +/*===========================================================================*/ + +/** + * @brief Type representing an I2C address. + */ +typedef uint16_t i2caddr_t; + +/** + * @brief Type of I2C driver condition flags. + */ +typedef uint8_t i2cflags_t; + +/** + * @brief Type of a delay function. + */ +typedef void (*i2c_delay_t)(void); + +/** + * @brief I2C driver configuration structure. + */ +struct hal_i2c_config { + /** + * @brief 10 bits addressing switch. + */ + bool addr10; + /** + * @brief I2C clock line. + */ + ioline_t scl; + /** + * @brief I2C data line. + */ + ioline_t sda; +#if SW_I2C_USE_OSAL_DELAY || defined(__DOXYGEN__) + /** + * @brief Delay of an half bit time in system ticks. + */ + systime_t ticks; +#else + /** + * @brief Pointer to an externally defined delay function. + */ + i2c_delay_t delay; +#endif +}; + +/** + * @brief Type of a structure representing an I2C configuration. + */ +typedef struct hal_i2c_config I2CConfig; + +/** + * @brief Type of a structure representing an I2C driver. + */ +typedef struct hal_i2c_driver I2CDriver; + +/** + * @brief Structure representing an I2C driver. + */ +struct hal_i2c_driver { + /** + * @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__) + mutex_t mutex; +#endif /* I2C_USE_MUTUAL_EXCLUSION */ +#if defined(I2C_DRIVER_EXT_FIELDS) + I2C_DRIVER_EXT_FIELDS +#endif + /* End of the mandatory fields.*/ + /** + * @brief Time of operation begin. + */ + systime_t start; + /** + * @brief Time of operation timeout. + */ + systime_t end; +}; + +/*===========================================================================*/ +/* 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 SW_I2C_USE_I2C1 +extern I2CDriver I2CD1; +#endif +#if SW_I2C_USE_I2C2 +extern I2CDriver I2CD2; +#endif +#if SW_I2C_USE_I2C3 +extern I2CDriver I2CD3; +#endif +#if SW_I2C_USE_I2C4 +extern I2CDriver I2CD4; +#endif +#endif /* !defined(__DOXYGEN__) */ + +#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, + sysinterval_t timeout); + msg_t i2c_lld_master_receive_timeout(I2CDriver *i2cp, i2caddr_t addr, + uint8_t *rxbuf, size_t rxbytes, + sysinterval_t timeout); +#ifdef __cplusplus +} +#endif + +#endif /* HAL_USE_I2C */ + +#endif /* HAL_I2C_LLD_H */ + +/** @} */ diff --git a/os/hal/ports/SN32/LLD/SN32F2xx/I2C/driver.mk b/os/hal/ports/SN32/LLD/SN32F2xx/I2C/driver.mk index 7864517f37..117f1cc48b 100644 --- a/os/hal/ports/SN32/LLD/SN32F2xx/I2C/driver.mk +++ b/os/hal/ports/SN32/LLD/SN32F2xx/I2C/driver.mk @@ -2,12 +2,12 @@ ifeq ($(USE_HAL_I2C_FALLBACK),yes) # Fallback SW driver. ifeq ($(USE_SMART_BUILD),yes) ifneq ($(findstring HAL_USE_I2C TRUE,$(HALCONF)),) - PLATFORMSRC_CONTRIB += $(CHIBIOS)/os/hal/lib/fallback/I2C/hal_i2c_lld.c + PLATFORMSRC_CONTRIB += $(CHIBIOS_CONTRIB)/os/hal/lib/fallback/I2C/hal_i2c_lld.c endif else - PLATFORMSRC_CONTRIB += $(CHIBIOS)/os/hal/lib/fallback/I2C/hal_i2c_lld.c + PLATFORMSRC_CONTRIB += $(CHIBIOS_CONTRIB)/os/hal/lib/fallback/I2C/hal_i2c_lld.c endif - PLATFORMINC_CONTRIB += $(CHIBIOS)/os/hal/lib/fallback/I2C + PLATFORMINC_CONTRIB += $(CHIBIOS_CONTRIB)/os/hal/lib/fallback/I2C else # Default HW driver. ifeq ($(USE_SMART_BUILD),yes)