Skip to content

Commit

Permalink
drivers: i2c: Added Bus recovery support to OMAP I2C.
Browse files Browse the repository at this point in the history
The bus recovery feature utilizing bit-bang operations, which sends
SCL clock pulses to recover the bus and checks if the line is down.
Upon recovery, it disables the system test register before resuming
the transactions.

Signed-off-by: Dhruv Menon <[email protected]>
  • Loading branch information
malto101 committed Nov 20, 2024
1 parent 4f78c52 commit e4d6718
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 26 deletions.
8 changes: 7 additions & 1 deletion drivers/i2c/Kconfig.omap
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ config I2C_OMAP
bool "TI OMAP I2C Driver"
default y
depends on DT_HAS_TI_OMAP_I2C_ENABLED
select I2C_BITBANG
help
Enable the I2C driver for TI OMAP SoCs.

config I2C_OMAP_BUS_RECOVERY
bool "Bus recovery support"
depends on I2C_OMAP
select I2C_BITBANG
help
Enable OMAP I2C driver bus recovery support via bitbanging.
163 changes: 138 additions & 25 deletions drivers/i2c/i2c_omap.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
#include <zephyr/irq.h>
#include <zephyr/drivers/i2c.h>

#ifdef CONFIG_I2C_OMAP_BUS_RECOVERY
#include "i2c_bitbang.h"
#endif /* CONFIG_I2C_OMAP_BUS_RECOVERY */

LOG_MODULE_REGISTER(omap_i2c, CONFIG_I2C_LOG_LEVEL);

#define I2C_OMAP_TIMEOUT 100U
Expand Down Expand Up @@ -216,30 +220,6 @@ static int i2c_omap_set_speed(const struct device *dev, uint32_t speed)
return 0;
}

/**
* @brief Initialize the OMAP I2C controller.
*
* This function initializes the OMAP I2C controller by setting the speed and
* performing any necessary initialization steps.
*
* @param dev Pointer to the device structure for the I2C controller.
* @return 0 if successful, negative error code otherwise.
*/
static int i2c_omap_init(const struct device *dev)
{
struct i2c_omap_data *data = DEV_DATA(dev);
const struct i2c_omap_cfg *cfg = DEV_CFG(dev);
k_sem_init(&data->lock, 1, 1);

/* Set the speed for I2C */
if (i2c_omap_set_speed(dev, cfg->speed)) {
LOG_ERR("Failed to set speed");
return -ENOTSUP;
}
i2c_omap_init_ll(dev);
return 0;
}

/**
* @brief Configure the OMAP I2C controller with the specified device configuration.
*
Expand Down Expand Up @@ -322,12 +302,113 @@ static void i2c_omap_resize_fifo(const struct device *dev, uint8_t size)
}
}

#ifdef CONFIG_I2C_OMAP_BUS_RECOVERY
/**
* @brief Get the state of the SDA line.
*
* This function retrieves the state of the SDA (data) line for the OMAP I2C controller.
*
* @param io_context The I2C context.
* @return The state of the SDA line.
*/
static int i2c_omap_get_sda(void *io_context)
{
const struct i2c_omap_cfg *cfg = (const struct i2c_omap_cfg *)io_context;
i2c_omap_regs_t *i2c_base_addr = (i2c_omap_regs_t *)cfg->base.addr;

return (i2c_base_addr->SYSTEST & I2C_OMAP_SYSTEST_SDA_I_FUNC) ? 1 : 0;
}

/**
* @brief Set the state of the SDA line.
*
* This function sets the state of the SDA (data) line for the OMAP I2C controller.
*
* @param io_context The I2C context.
* @param state The state to set (0 for low, 1 for high).
*/
static void i2c_omap_set_sda(void *io_context, int state)
{
const struct i2c_omap_cfg *cfg = (const struct i2c_omap_cfg *)io_context;
i2c_omap_regs_t *i2c_base_addr = (i2c_omap_regs_t *)cfg->base.addr;

if (state) {
i2c_base_addr->SYSTEST |= I2C_OMAP_SYSTEST_SDA_O;
} else {
i2c_base_addr->SYSTEST &= ~I2C_OMAP_SYSTEST_SDA_O;
}
}

/**
* @brief Set the state of the SCL line.
*
* This function sets the state of the SCL (clock) line for the OMAP I2C controller.
*
* @param io_context The I2C context.
* @param state The state to set (0 for low, 1 for high).
*/
static void i2c_omap_set_scl(void *io_context, int state)
{
const struct i2c_omap_cfg *cfg = (const struct i2c_omap_cfg *)io_context;
i2c_omap_regs_t *i2c_base_addr = (i2c_omap_regs_t *)cfg->base.addr;

if (state) {
i2c_base_addr->SYSTEST |= I2C_OMAP_SYSTEST_SCL_O;
} else {
i2c_base_addr->SYSTEST &= ~I2C_OMAP_SYSTEST_SCL_O;
}
}
/**
* @brief Recovers the I2C bus using the OMAP I2C controller.
*
* This function attempts to recover the I2C bus by performing a bus recovery
* sequence using the OMAP I2C controller. It uses the provided device
* configuration and bit-banging operations to recover the bus.
*
* @param dev Pointer to the device structure.
* @return 0 on success, negative error code on failure.
*/

static int i2c_omap_recover_bus(const struct device *dev)
{
const struct i2c_omap_cfg *cfg = DEV_CFG(dev);
i2c_omap_regs_t *i2c_base_addr = DEV_I2C_BASE(dev);
struct i2c_omap_data *data = DEV_DATA(dev);

struct i2c_bitbang bitbang_omap;
struct i2c_bitbang_io bitbang_omap_io = {
.get_sda = i2c_omap_get_sda,
.set_scl = i2c_omap_set_scl,
.set_sda = i2c_omap_set_sda,
};
int error = 0;

k_sem_take(&data->lock, K_FOREVER);
i2c_base_addr->SYSTEST |= I2C_OMAP_SYSTEST_ST_EN | (3 << I2C_OMAP_SYSTEST_TMODE_SHIFT) |
I2C_OMAP_SYSTEST_SCL_O | I2C_OMAP_SYSTEST_SDA_O;
i2c_bitbang_init(&bitbang_omap, &bitbang_omap_io, (void *)cfg);
error = i2c_bitbang_recover_bus(&bitbang_omap);
if (error != 0) {
LOG_ERR("failed to recover bus (err %d)", error);
goto restore;
}

restore:
i2c_base_addr->SYSTEST &= ~(I2C_OMAP_SYSTEST_ST_EN | I2C_OMAP_SYSTEST_TMODE_MASK |
I2C_OMAP_SYSTEST_SCL_O | I2C_OMAP_SYSTEST_SDA_O);
i2c_omap_reset(dev);
k_sem_give(&data->lock);
return error;
}
#endif /* CONFIG_I2C_OMAP_BUS_RECOVERY */

/**
* @brief Wait for the bus to become free (no longer busy).
*
* This function waits for the bus to become free by continuously checking the
* status register of the OMAP I2C controller. If the bus remains busy for a
* certain timeout period, the function will return timeout error.
* certain timeout period, the function will return attempts to recover the bus by calling
* i2c_omap_recover_bus().
*
* @param dev The I2C device structure.
* @return 0 if the bus becomes free, or a negative error code if the bus cannot
Expand All @@ -341,6 +422,11 @@ static int i2c_omap_wait_for_bb(const struct device *dev)
while (i2c_base_addr->STAT & I2C_OMAP_STAT_BB) {
if (k_uptime_get_32() > timeout) {
LOG_ERR("Bus busy timeout");
#ifdef CONFIG_I2C_OMAP_BUS_RECOVERY
return i2c_omap_recover_bus(dev);
#else
return -ETIMEDOUT;
#endif /* CONFIG_I2C_OMAP_BUS_RECOVERY */
}
k_busy_wait(100);
}
Expand Down Expand Up @@ -576,8 +662,35 @@ static int i2c_omap_transfer_polling(const struct device *dev, struct i2c_msg ms
static const struct i2c_driver_api i2c_omap_api = {
.transfer = i2c_omap_transfer_polling,
.configure = i2c_omap_configure,
#ifdef CONFIG_I2C_OMAP_BUS_RECOVERY
.recover_bus = i2c_omap_recover_bus,
#endif /* CONFIG_I2C_OMAP_BUS_RECOVERY */
};

/**
* @brief Initialize the OMAP I2C controller.
*
* This function initializes the OMAP I2C controller by setting the speed and
* performing any necessary initialization steps.
*
* @param dev Pointer to the device structure for the I2C controller.
* @return 0 if successful, negative error code otherwise.
*/
static int i2c_omap_init(const struct device *dev)
{
struct i2c_omap_data *data = DEV_DATA(dev);
const struct i2c_omap_cfg *cfg = DEV_CFG(dev);

k_sem_init(&data->lock, 1, 1);
/* Set the speed for I2C */
if (i2c_omap_set_speed(dev, cfg->speed)) {
LOG_ERR("Failed to set speed");
return -ENOTSUP;
}
i2c_omap_init_ll(dev);
return 0;
}

#define I2C_OMAP_INIT(inst) \
LOG_INSTANCE_REGISTER(omap_i2c, inst, CONFIG_I2C_LOG_LEVEL); \
static const struct i2c_omap_cfg i2c_omap_cfg_##inst = { \
Expand Down

0 comments on commit e4d6718

Please sign in to comment.