Skip to content

Commit

Permalink
drivers: i2c: Introduce I2C timeout and bus recovery for SAM0
Browse files Browse the repository at this point in the history
Adds configurable timeout for I2C transactions and provides
bus recovery functionality.

Signed-off-by: Jan Kowalewski <[email protected]>
  • Loading branch information
kowalewskijan committed Oct 2, 2024
1 parent 9655c2f commit 515dc4d
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 1 deletion.
8 changes: 8 additions & 0 deletions drivers/i2c/Kconfig.sam0
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,11 @@ config I2C_SAM0_DMA_DRIVEN
DMA driven mode requires fewer interrupts to handle the
transaction and ensures that high speed modes are not delayed
by data reloading.

config I2C_SAM0_TRANSFER_TIMEOUT
int "Transfer timeout [ms]"
default 500
help
Timeout in milliseconds used for each I2C transfer.
0 means that the driver should use the K_FOREVER value,
i.e. it should wait as long as necessary.
77 changes: 76 additions & 1 deletion drivers/i2c/i2c_sam0.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/dma.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/drivers/gpio.h>

#include <zephyr/logging/log.h>
#include <zephyr/irq.h>
Expand All @@ -24,6 +25,30 @@ LOG_MODULE_REGISTER(i2c_sam0, CONFIG_I2C_LOG_LEVEL);
#define SERCOM_I2CM_CTRLA_MODE_I2C_MASTER SERCOM_I2CM_CTRLA_MODE(5)
#endif

#if CONFIG_I2C_SAM0_TRANSFER_TIMEOUT
#define I2C_TRANSFER_TIMEOUT_MSEC K_MSEC(CONFIG_I2C_SAM0_TRANSFER_TIMEOUT)
#else
#define I2C_TRANSFER_TIMEOUT_MSEC K_FOREVER
#endif

#define SERCOM_I2CM_SDA_PIN_IDX 0
#define SERCOM_I2CM_SCL_PIN_IDX 1

/** Utility macro that expands to the PORT device if it exists */
#define SAM_PORT_DEV_OR_NONE(nodelabel) \
IF_ENABLED(DT_NODE_EXISTS(DT_NODELABEL(nodelabel)), \
(DEVICE_DT_GET(DT_NODELABEL(nodelabel)),))

/** SAM0 port devices */
static const struct device * sam_port_devs[] = {

Check failure on line 43 in drivers/i2c/i2c_sam0.c

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

POINTER_LOCATION

drivers/i2c/i2c_sam0.c:43 "foo * bar" should be "foo *bar"
SAM_PORT_DEV_OR_NONE(porta)
SAM_PORT_DEV_OR_NONE(portb)
SAM_PORT_DEV_OR_NONE(portc)
SAM_PORT_DEV_OR_NONE(portd)
SAM_PORT_DEV_OR_NONE(porte)
SAM_PORT_DEV_OR_NONE(portf)
};

Check notice on line 51 in drivers/i2c/i2c_sam0.c

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

You may want to run clang-format on this change

drivers/i2c/i2c_sam0.c:51 -#define SAM_PORT_DEV_OR_NONE(nodelabel) \ +#define SAM_PORT_DEV_OR_NONE(nodelabel) \ IF_ENABLED(DT_NODE_EXISTS(DT_NODELABEL(nodelabel)), \ (DEVICE_DT_GET(DT_NODELABEL(nodelabel)),)) /** SAM0 port devices */ -static const struct device * sam_port_devs[] = { - SAM_PORT_DEV_OR_NONE(porta) - SAM_PORT_DEV_OR_NONE(portb) - SAM_PORT_DEV_OR_NONE(portc) - SAM_PORT_DEV_OR_NONE(portd) - SAM_PORT_DEV_OR_NONE(porte) - SAM_PORT_DEV_OR_NONE(portf) -}; +static const struct device *sam_port_devs[] = {SAM_PORT_DEV_OR_NONE(porta) SAM_PORT_DEV_OR_NONE( + portb) SAM_PORT_DEV_OR_NONE(portc) SAM_PORT_DEV_OR_NONE(portd) SAM_PORT_DEV_OR_NONE(porte) + SAM_PORT_DEV_OR_NONE(portf)};
struct i2c_sam0_dev_config {
SercomI2cm *regs;
const struct pinctrl_dev_config *pcfg;
Expand Down Expand Up @@ -60,6 +85,50 @@ struct i2c_sam0_dev_data {
uint8_t num_msgs;
};

static int i2c_sam0_recover_bus(const struct device *dev)
{
const struct i2c_sam0_dev_config *cfg = dev->config;
pinctrl_soc_pin_t soc_pin_sda, soc_pin_scl;

/* Save original pin config values */
soc_pin_sda = cfg->pcfg->states[PINCTRL_STATE_DEFAULT].pins[SERCOM_I2CM_SDA_PIN_IDX];
soc_pin_scl = cfg->pcfg->states[PINCTRL_STATE_DEFAULT].pins[SERCOM_I2CM_SCL_PIN_IDX];

uint8_t sda_port_idx = SAM_PINMUX_PORT_GET(soc_pin_sda);
uint8_t scl_port_idx = SAM_PINMUX_PORT_GET(soc_pin_scl);
uint32_t sda_pin = SAM_PINMUX_PIN_GET(soc_pin_sda);
uint32_t scl_pin = SAM_PINMUX_PIN_GET(soc_pin_scl);
const struct device *sda_dev = sam_port_devs[sda_port_idx];
const struct device *scl_dev = sam_port_devs[scl_port_idx];

gpio_pin_configure(sda_dev, sda_pin, GPIO_INPUT | GPIO_ACTIVE_HIGH);
gpio_pin_configure(scl_dev, scl_pin, GPIO_OUTPUT | GPIO_OUTPUT_INIT_HIGH);

/* Recover bus */
for (uint8_t i = 0; i < 9; i++) {
if (gpio_pin_get(sda_dev, sda_pin)) {
break;
}
gpio_pin_set(scl_dev, scl_pin, 0);
k_usleep(4);
gpio_pin_set(scl_dev, scl_pin, 1);
k_usleep(4);
}

/* Generate stop condition */
gpio_pin_configure(sda_dev, sda_pin, GPIO_OUTPUT | GPIO_OUTPUT_INIT_HIGH);
gpio_pin_set(sda_dev, sda_pin, 0);
k_usleep(4);
gpio_pin_set(sda_dev, sda_pin, 1);
k_usleep(4);

/* Recover pinmux */
pinctrl_configure_pins(&soc_pin_sda, 1, PINCTRL_REG_NONE);
pinctrl_configure_pins(&soc_pin_scl, 1, PINCTRL_REG_NONE);

return 0;
}

static void wait_synchronization(SercomI2cm *regs)
{
#if defined(SERCOM_I2CM_SYNCBUSY_MASK)
Expand Down Expand Up @@ -504,7 +573,13 @@ static int i2c_sam0_transfer(const struct device *dev, struct i2c_msg *msgs,
irq_unlock(key);

/* Now wait for the ISR to handle everything */
k_sem_take(&data->sem, K_FOREVER);
ret = k_sem_take(&data->sem, I2C_TRANSFER_TIMEOUT_MSEC);

if (ret != 0) {
i2c_sam0_recover_bus(dev);
ret = -EIO;
goto unlock;
}

if (data->msg.status) {
if (data->msg.status & SERCOM_I2CM_STATUS_ARBLOST) {
Expand Down

0 comments on commit 515dc4d

Please sign in to comment.