From e4d6718eea902793832520ae38d8a5c3f46731e4 Mon Sep 17 00:00:00 2001 From: Dhruv Menon Date: Tue, 22 Oct 2024 00:43:31 +0530 Subject: [PATCH] drivers: i2c: Added Bus recovery support to OMAP I2C. 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 --- drivers/i2c/Kconfig.omap | 8 +- drivers/i2c/i2c_omap.c | 163 +++++++++++++++++++++++++++++++++------ 2 files changed, 145 insertions(+), 26 deletions(-) diff --git a/drivers/i2c/Kconfig.omap b/drivers/i2c/Kconfig.omap index 37184a6436d5fb9..9291a1770323be1 100644 --- a/drivers/i2c/Kconfig.omap +++ b/drivers/i2c/Kconfig.omap @@ -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. diff --git a/drivers/i2c/i2c_omap.c b/drivers/i2c/i2c_omap.c index 6a7697cc3e6c4e1..8ede1b2b13c0576 100644 --- a/drivers/i2c/i2c_omap.c +++ b/drivers/i2c/i2c_omap.c @@ -13,6 +13,10 @@ #include #include +#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 @@ -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. * @@ -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 @@ -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); } @@ -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 = { \