mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
98b38399df
Always enable the internal pull-up on the SCL line to always have a functional I2C bus. This may increase power consumption where an external pull up is present as well. But let's wait for a real world use case where this would help to extend battery life before making this configurable. This fixes https://github.com/RIOT-OS/RIOT/issues/19021
387 lines
12 KiB
C
387 lines
12 KiB
C
/*
|
|
* Copyright (C) 2020 iosabi
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU Lesser General
|
|
* Public License v2.1. See the file LICENSE in the top level directory for more
|
|
* details.
|
|
*/
|
|
|
|
/**
|
|
* @ingroup cpu_qn908x
|
|
* @ingroup drivers_periph_i2c
|
|
*
|
|
* @{
|
|
*
|
|
* @file
|
|
* @brief Low-level I2C driver implementation
|
|
*
|
|
* @author iosabi <iosabi@protonmail.com>
|
|
*
|
|
* @}
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "cpu.h"
|
|
#include "mutex.h"
|
|
|
|
#include "periph_conf.h"
|
|
#include "periph/i2c.h"
|
|
|
|
#include "vendor/drivers/fsl_clock.h"
|
|
#include "vendor/drivers/fsl_iocon.h"
|
|
|
|
#include "gpio_mux.h"
|
|
#include "flexcomm.h"
|
|
|
|
#define ENABLE_DEBUG 0
|
|
#include "debug.h"
|
|
|
|
static mutex_t locks[I2C_NUMOF];
|
|
|
|
/**
|
|
* @name Definitions for MSTSTATE bits in I2C Status register STAT
|
|
* @{
|
|
*/
|
|
/* Controller Idle State Code */
|
|
#define I2C_STAT_MSTSTATE_IDLE (0)
|
|
/* Controller Receive Ready State Code */
|
|
#define I2C_STAT_MSTSTATE_RXREADY (1)
|
|
/* Controller Transmit Ready State Code */
|
|
#define I2C_STAT_MSTSTATE_TXREADY (2)
|
|
/* Controller NACK by peripheral on address State Code */
|
|
#define I2C_STAT_MSTSTATE_NACKADR (3)
|
|
/* Controller NACK by peripheral on data State Code */
|
|
#define I2C_STAT_MSTSTATE_NACKDAT (4)
|
|
/** @} */
|
|
|
|
/**
|
|
* @brief Limit value I2C CLKDIV register.
|
|
*/
|
|
#define I2C_CLKDIV_MAX 0xffffu
|
|
|
|
/**
|
|
* @brief Set the I2C controller mode clock speed.
|
|
*/
|
|
static void _i2c_controller_set_speed(I2C_Type *i2c_dev, uint32_t speed_hz)
|
|
{
|
|
/* The I2C clock source is based on the FLEXCOMM clock with the following
|
|
* formula:
|
|
* i2c freq := flexcomm freq / ((CLKDIV + 1) * (MSTTIME + 2) * 2)
|
|
* Where MSTTIME is a number between 0 and 7, and CLKDIV is between 0 and
|
|
* 0xffff.
|
|
* The MSTTIME register controls for how many cycles does the clock stay
|
|
* low and high, allowing to use different values for each one but we only
|
|
* use symmetric ones here, which is why there's a * 2 in the formula above.
|
|
*/
|
|
assert(speed_hz > 0);
|
|
uint32_t bus_freq = CLOCK_GetFreq(kCLOCK_BusClk);
|
|
uint32_t target = bus_freq / (2 * speed_hz);
|
|
|
|
uint32_t best_error = UINT_MAX;
|
|
uint32_t best_clkdiv = 0;
|
|
uint32_t best_msttime = 0;
|
|
|
|
for (uint32_t msttime_p2 = 9; msttime_p2 >= 2; msttime_p2--) {
|
|
uint32_t clkdiv_p1 = (target + msttime_p2 / 2) / msttime_p2;
|
|
if (clkdiv_p1 >= I2C_CLKDIV_MAX + 1) {
|
|
clkdiv_p1 = I2C_CLKDIV_MAX + 1;
|
|
}
|
|
uint32_t error =
|
|
abs((int32_t)target - (int32_t)(clkdiv_p1 * msttime_p2));
|
|
if (error < best_error) {
|
|
best_error = error;
|
|
best_clkdiv = clkdiv_p1 - 1;
|
|
best_msttime = msttime_p2 - 2;
|
|
}
|
|
}
|
|
i2c_dev->CLKDIV = I2C_CLKDIV_DIVVAL(best_clkdiv);
|
|
i2c_dev->MSTTIME = I2C_MSTTIME_MSTSCLLOW(best_msttime) |
|
|
I2C_MSTTIME_MSTSCLHIGH(best_msttime);
|
|
DEBUG("[i2c]: bus_fq=%" PRIu32" target_freq=%" PRIu32" msttime=%" PRIu32
|
|
" clkdiv=%" PRIu32 " error=%" PRIu32 "\n",
|
|
bus_freq, speed_hz, best_msttime, best_clkdiv, best_error);
|
|
}
|
|
|
|
static void _i2c_init_pins(i2c_t dev)
|
|
{
|
|
const i2c_conf_t *conf = &i2c_config[dev];
|
|
|
|
/* Configure SDA and SCL pins, the function value depends on the pin:
|
|
* FUNC4: A6, A7, A8, A9, A12, A13, A22, A23, A26, A27
|
|
* FUNC5: A2, A3, A4, A5, A20, A21
|
|
*/
|
|
static const uint32_t func5_mask =
|
|
(1 << 2) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 20) | (1 << 21);
|
|
|
|
uint32_t sda_mode = IOCON_FUNC4;
|
|
if ((1u << GPIO_T_PIN(conf->pin_sda)) & func5_mask) {
|
|
sda_mode = IOCON_FUNC5;
|
|
}
|
|
|
|
uint32_t scl_mode = IOCON_FUNC4;
|
|
if ((1u << GPIO_T_PIN(conf->pin_scl)) & func5_mask) {
|
|
sda_mode = IOCON_FUNC5;
|
|
}
|
|
|
|
/* Using the internal pull up will make sure the I2C bus is also working
|
|
* when no external pull up is present. It can waste power by when both
|
|
* external and internal pull up are present. If a real world example
|
|
* shows up where disabling the internal pull up is helpful, we can extend
|
|
* i2c_conf_t to make it configurable */
|
|
scl_mode |= IOCON_MODE_PULLUP;
|
|
|
|
gpio_init_mux(conf->pin_sda, sda_mode);
|
|
gpio_init_mux(conf->pin_scl, scl_mode);
|
|
|
|
mutex_unlock(&locks[dev]);
|
|
}
|
|
|
|
static void _enable(I2C_Type *const i2c_dev)
|
|
{
|
|
i2c_dev->CFG = I2C_CFG_MSTEN_MASK;
|
|
while ((i2c_dev->STAT & I2C_STAT_MSTPENDING_MASK) == 0) {
|
|
/* I2C peripheral not yet available, spin more ... */
|
|
}
|
|
|
|
/* I2C peripheral is online, check state is indeed idle */
|
|
assert((i2c_dev->STAT & I2C_STAT_MSTSTATE_MASK) == I2C_STAT_MSTSTATE_IDLE);
|
|
}
|
|
|
|
void i2c_init(i2c_t dev)
|
|
{
|
|
assert(dev < I2C_NUMOF);
|
|
const i2c_conf_t *conf = &i2c_config[dev];
|
|
I2C_Type *const i2c_dev = conf->dev;
|
|
|
|
int flexcomm_num = flexcomm_init((FLEXCOMM_Type *)i2c_dev, FLEXCOMM_ID_I2C);
|
|
assert(flexcomm_num >= 0);
|
|
|
|
if (flexcomm_num == 1) {
|
|
/* Disable the FLECOMM1 MULT1/DIV1 divisor. FLEXCOMM2 doesn't have a
|
|
* fractional divisor. This divides the clock by (1 + N / 256) where
|
|
* N is set to 0 here, so the fractional divisor only divides by one. */
|
|
CLOCK_SetClkDiv(kCLOCK_DivFrg1, 0u);
|
|
}
|
|
|
|
/* Enable controller mode, no timeout, no monitor, no clock stretching. */
|
|
_enable(i2c_dev);
|
|
_i2c_controller_set_speed(i2c_dev, conf->speed);
|
|
locks[dev] = (mutex_t)MUTEX_INIT_LOCKED;
|
|
|
|
/* This also unlocks the mutex. */
|
|
_i2c_init_pins(dev);
|
|
}
|
|
|
|
#ifdef MODULE_PERIPH_I2C_RECONFIGURE
|
|
void i2c_init_pins(i2c_t dev)
|
|
{
|
|
assert(dev < I2C_NUMOF);
|
|
i2c_config[dev].dev->CFG |= I2C_CFG_MSTEN_MASK;
|
|
_i2c_init_pins(dev);
|
|
}
|
|
|
|
void i2c_deinit_pins(i2c_t dev)
|
|
{
|
|
assert(dev < I2C_NUMOF);
|
|
const i2c_conf_t *conf = &i2c_config[dev];
|
|
mutex_lock(&locks[dev]);
|
|
conf->dev->CFG &= ~I2C_CFG_MSTEN_MASK;
|
|
gpio_init_mux(conf->pin_sda, 0);
|
|
gpio_init_mux(conf->pin_scl, 0);
|
|
}
|
|
#endif /* MODULE_PERIPH_I2C_RECONFIGURE */
|
|
|
|
void i2c_acquire(i2c_t dev)
|
|
{
|
|
assert(dev < I2C_NUMOF);
|
|
mutex_lock(&locks[dev]);
|
|
}
|
|
|
|
void i2c_release(i2c_t dev)
|
|
{
|
|
assert(dev < I2C_NUMOF);
|
|
mutex_unlock(&locks[dev]);
|
|
}
|
|
|
|
static uint32_t _i2c_wait_idle(I2C_Type *i2c_dev)
|
|
{
|
|
uint32_t status;
|
|
|
|
do {
|
|
status = i2c_dev->STAT;
|
|
} while ((status & I2C_STAT_MSTPENDING_MASK) == 0);
|
|
|
|
i2c_dev->STAT = I2C_STAT_MSTARBLOSS_MASK | I2C_STAT_MSTSTSTPERR_MASK;
|
|
return status;
|
|
}
|
|
|
|
static void _i2c_start(I2C_Type *i2c_dev, uint8_t addr_dir)
|
|
{
|
|
_i2c_wait_idle(i2c_dev);
|
|
i2c_dev->MSTDAT = addr_dir;
|
|
i2c_dev->MSTCTL = I2C_MSTCTL_MSTSTART_MASK; /* start */
|
|
}
|
|
|
|
static uint32_t _i2c_stop(I2C_Type *i2c_dev)
|
|
{
|
|
uint32_t status = _i2c_wait_idle(i2c_dev);
|
|
|
|
i2c_dev->MSTCTL = I2C_MSTCTL_MSTSTOP_MASK; /* stop */
|
|
return status;
|
|
}
|
|
|
|
|
|
static int _i2c_transfer_blocking(i2c_t dev, uint32_t addr_dir, uint8_t *data,
|
|
size_t len, uint8_t flags)
|
|
{
|
|
assert((data != NULL) || (len == 0));
|
|
I2C_Type *i2c_dev = i2c_config[dev].dev;
|
|
|
|
uint32_t status = 0;
|
|
uint32_t controller_state;
|
|
|
|
if ((flags & I2C_NOSTART) == 0) {
|
|
if ((flags & I2C_ADDR10) != 0) {
|
|
_i2c_start(i2c_dev,
|
|
0xf0 | (addr_dir & 1) | ((addr_dir >> 8) & 0x6));
|
|
/* The second call to start sends a repeated start */
|
|
_i2c_start(i2c_dev, (addr_dir >> 1) & 0xff);
|
|
}
|
|
else {
|
|
_i2c_start(i2c_dev, addr_dir);
|
|
}
|
|
}
|
|
|
|
while (len) {
|
|
status = _i2c_wait_idle(i2c_dev);
|
|
if (status & (I2C_STAT_MSTARBLOSS_MASK | I2C_STAT_MSTSTSTPERR_MASK)) {
|
|
break;
|
|
}
|
|
controller_state = (status & I2C_STAT_MSTSTATE_MASK) >>
|
|
I2C_STAT_MSTSTATE_SHIFT;
|
|
switch (controller_state) {
|
|
case I2C_STAT_MSTSTATE_TXREADY:
|
|
/* I2C write case. */
|
|
if ((addr_dir & 1) != 0) {
|
|
/* This means that the direction requested in the transfer
|
|
* was to read, without a start condition and following
|
|
* a previous request to write, so the block is ready to
|
|
* write but we should be reading. */
|
|
_i2c_stop(i2c_dev);
|
|
return -EINVAL;
|
|
}
|
|
i2c_dev->MSTDAT = *(data++);
|
|
len--;
|
|
i2c_dev->MSTCTL = I2C_MSTCTL_MSTCONTINUE_MASK;
|
|
break;
|
|
case I2C_STAT_MSTSTATE_RXREADY:
|
|
/* I2C read case. */
|
|
if ((addr_dir & 1) != 1) {
|
|
/* Analog to the write case, we should be trying to writing
|
|
* in this case. */
|
|
i2c_dev->MSTDAT;
|
|
_i2c_stop(i2c_dev);
|
|
return -EINVAL;
|
|
}
|
|
|
|
*(data++) = i2c_dev->MSTDAT;
|
|
if (--len) {
|
|
i2c_dev->MSTCTL = I2C_MSTCTL_MSTCONTINUE_MASK;
|
|
}
|
|
else {
|
|
if ((flags & I2C_NOSTOP) == 0) {
|
|
/* initiate NAK and stop */
|
|
i2c_dev->MSTCTL = I2C_MSTCTL_MSTSTOP_MASK;
|
|
status = _i2c_wait_idle(i2c_dev);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case I2C_STAT_MSTSTATE_NACKADR:
|
|
/* NACK on the address means no device. */
|
|
i2c_dev->MSTCTL = I2C_MSTCTL_MSTSTOP_MASK;
|
|
return -ENXIO;
|
|
|
|
case I2C_STAT_MSTSTATE_NACKDAT:
|
|
/* NACK on the data means that the device didn't ACK the last
|
|
* byte. */
|
|
i2c_dev->MSTCTL = I2C_MSTCTL_MSTSTOP_MASK;
|
|
return -EIO;
|
|
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
/* In the write case we didn't send a stop condition yet. */
|
|
if ((flags & I2C_NOSTOP) == 0 && (addr_dir & 1) == 0) {
|
|
status = _i2c_stop(i2c_dev);
|
|
/* The status will contain the last ACK/NACK */
|
|
controller_state = (status & I2C_STAT_MSTSTATE_MASK) >>
|
|
I2C_STAT_MSTSTATE_SHIFT;
|
|
if (controller_state == I2C_STAT_MSTSTATE_NACKDAT) {
|
|
return -EIO;
|
|
}
|
|
}
|
|
if (status & I2C_STAT_MSTARBLOSS_MASK) {
|
|
DEBUG("[i2c] Controller arbitration loss error flag, status 0x%.2"
|
|
PRIx32 "\n", status);
|
|
return -EAGAIN;
|
|
}
|
|
|
|
if (status & I2C_STAT_MSTSTSTPERR_MASK) {
|
|
/* Controller start/stop error flag. */
|
|
DEBUG("[i2c] Controller start/stop error flag\n");
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#define I2C_ADDR_READ(addr) (((uint32_t)(addr) << 1u) | 1u)
|
|
#define I2C_ADDR_WRITE(addr) ((uint32_t)(addr) << 1u)
|
|
|
|
int i2c_read_bytes(i2c_t dev, uint16_t addr, void *data,
|
|
size_t len, uint8_t flags)
|
|
{
|
|
DEBUG("[i2c] R a=%.2x len=%2u f=%.2x, data:", addr, (unsigned)len, flags);
|
|
int ret =
|
|
_i2c_transfer_blocking(dev, I2C_ADDR_READ(addr), data, len, flags);
|
|
#if ENABLE_DEBUG
|
|
for (size_t i = 0; i < len; i++) {
|
|
DEBUG(" %.2x", ((uint8_t *)data)[i]);
|
|
}
|
|
DEBUG(", ret=%d\n", ret);
|
|
#endif
|
|
return ret;
|
|
}
|
|
|
|
int i2c_write_bytes(i2c_t dev, uint16_t addr, const void *data, size_t len,
|
|
uint8_t flags)
|
|
{
|
|
/* The write transfer should technically only use a const uint8_t* buffer
|
|
* but we are re-using the same function to save on code here. It will not
|
|
* be written to in the I2C write case. */
|
|
#if ENABLE_DEBUG
|
|
DEBUG("[i2c] W a=%.2x len=%2u f=%.2x, data:", addr, (unsigned)len, flags);
|
|
for (size_t i = 0; i < len; i++) {
|
|
DEBUG(" %.2x", ((uint8_t *)data)[i]);
|
|
}
|
|
#endif
|
|
int ret = _i2c_transfer_blocking(
|
|
dev, I2C_ADDR_WRITE(addr), (uint8_t *)data, len, flags);
|
|
DEBUG(", ret=%d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
void isr_flexcomm_i2c(USART_Type *dev, uint32_t flexcomm_num)
|
|
{
|
|
// TODO: Set up async mode with interrupts.
|
|
(void)dev;
|
|
(void)flexcomm_num;
|
|
|
|
cortexm_isr_end();
|
|
}
|