From bf6b1a78f63bdb401c097220d0192c4c58eddffc Mon Sep 17 00:00:00 2001 From: Dhruv Menon Date: Sun, 20 Oct 2024 23:57:57 +0530 Subject: [PATCH 1/3] drivers: i2c: Base OMAP I2C support for TI-K3 processor The OMAP I2C provides support for I2C serial interface on TI K3 series. It is compatible with Philips I2C physical layer. The commit includes: Zephyr i2c api implementation Polling Mode Signed-off-by: Dhruv Menon --- drivers/i2c/CMakeLists.txt | 1 + drivers/i2c/Kconfig | 1 + drivers/i2c/Kconfig.omap | 12 + drivers/i2c/i2c_omap.c | 595 ++++++++++++++++++++++++++++++ dts/bindings/i2c/ti,omap-i2c.yaml | 20 + 5 files changed, 629 insertions(+) create mode 100644 drivers/i2c/Kconfig.omap create mode 100644 drivers/i2c/i2c_omap.c create mode 100644 dts/bindings/i2c/ti,omap-i2c.yaml diff --git a/drivers/i2c/CMakeLists.txt b/drivers/i2c/CMakeLists.txt index 2fdd0abf4f5378..61dc2201fe6445 100644 --- a/drivers/i2c/CMakeLists.txt +++ b/drivers/i2c/CMakeLists.txt @@ -69,6 +69,7 @@ else() ) endif() zephyr_library_sources_ifdef(CONFIG_I2C_NUMAKER i2c_numaker.c) +zephyr_library_sources_ifdef(CONFIG_I2C_OMAP i2c_omap.c) zephyr_library_sources_ifdef(CONFIG_I2C_SAM_TWI i2c_sam_twi.c) if(CONFIG_I2C_RTIO) zephyr_library_sources_ifdef(CONFIG_I2C_SAM_TWIHS diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig index e04f9e71ec1e31..b08cde10a75eb8 100644 --- a/drivers/i2c/Kconfig +++ b/drivers/i2c/Kconfig @@ -139,6 +139,7 @@ source "drivers/i2c/Kconfig.mcux" source "drivers/i2c/Kconfig.npcx" source "drivers/i2c/Kconfig.nrfx" source "drivers/i2c/Kconfig.numaker" +source "drivers/i2c/Kconfig.omap" source "drivers/i2c/Kconfig.renesas_ra" source "drivers/i2c/Kconfig.rcar" source "drivers/i2c/Kconfig.sam0" diff --git a/drivers/i2c/Kconfig.omap b/drivers/i2c/Kconfig.omap new file mode 100644 index 00000000000000..37184a6436d5fb --- /dev/null +++ b/drivers/i2c/Kconfig.omap @@ -0,0 +1,12 @@ +# Copyright (C) 2024 BeagleBoard.org Foundation +# Copyright (C) 2024 Dhruv Menon + +# SPDX-License-Identifier: Apache-2.0 + +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. diff --git a/drivers/i2c/i2c_omap.c b/drivers/i2c/i2c_omap.c new file mode 100644 index 00000000000000..2d5779521033f2 --- /dev/null +++ b/drivers/i2c/i2c_omap.c @@ -0,0 +1,595 @@ +/* Copyright (C) 2024 BeagleBoard.org Foundation + * Copyright (C) 2024 Dhruv Menon + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT ti_omap_i2c +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(omap_i2c, CONFIG_I2C_LOG_LEVEL); + +#define I2C_OMAP_TIMEOUT 100U +/* OCP_SYSSTATUS bit definitions */ +#define SYSS_RESETDONE_MASK BIT(0) +#define RETRY -1 +#define I2C_BITRATE_FAST 400000 +#define I2C_BITRATE_STANDARD 100000 + +/* I2C Registers */ +typedef struct { + uint8_t RESERVED_0[0x10]; /**< Reserved, offset: 0x0 */ + + __IO uint32_t SYSC; /**< System Configuration, offset: 0x10 */ + uint8_t RESERVED_1[0x18]; /**< Reserved, offset: 0x14 - 0x2C */ + __IO uint32_t IRQENABLE_SET; /**< Interrupt Enable Set, offset: 0x2C */ + uint8_t RESERVED_2[0x4]; /**< Reserved, offset: 0x30 - 0x34 */ + __IO uint32_t WE; /**< Wakeup Enable, offset: 0x34 */ + uint8_t RESERVED_3[0x4C]; /**< Reserved, offset: 0x38 - 0x84 */ + __IO uint32_t IE; /**< Interrupt Enable (Legacy), offset: 0x84 */ + __IO uint32_t STAT; /**< Status, offset: 0x88 */ + uint8_t RESERVED_4[0x4]; /**< Reserved, offset: 0x8C - 0x90 */ + __IO uint32_t SYSS; /**< System Status, offset: 0x90 */ + __IO uint32_t BUF; /**< Buffer, offset: 0x94 */ + __IO uint32_t CNT; /**< Data Count, offset: 0x98 */ + __IO uint32_t DATA; /**< Data Access, offset: 0x9C */ + uint8_t RESERVED_5[0x4]; /**< Reserved, offset: 0xA0 - 0xA4 */ + __IO uint32_t CON; /**< Configuration, offset: 0xA4 */ + __IO uint32_t OA; /**< Own Address, offset: 0xA8 */ + __IO uint32_t SA; /**< Target Address, offset: 0xAC */ + __IO uint32_t PSC; /**< Clock Prescaler, offset: 0xB0 */ + __IO uint32_t SCLL; /**< SCL Low Time, offset: 0xB4 */ + __IO uint32_t SCLH; /**< SCL High Time, offset: 0xB8 */ + __IO uint32_t SYSTEST; /**< System Test, offset: 0xBC */ + __IO uint32_t BUFSTAT; /**< Buffer Status, offset: 0xC0 */ +} i2c_omap_regs_t; + +/* I2C Configuration Register (I2C_OMAP_CON) */ +#define I2C_OMAP_CON_EN BIT(15) /* I2C module enable */ +#define I2C_OMAP_CON_OPMODE_HS BIT(12) /* High Speed support */ +#define I2C_OMAP_CON_MST BIT(10) /* Controller/target mode */ +#define I2C_OMAP_CON_TRX BIT(9) /* TX/RX mode (controller only) */ +#define I2C_OMAP_CON_STP BIT(1) /* Stop condition (controller only) */ +#define I2C_OMAP_CON_STT BIT(0) /* Start condition (controller) */ + +/* I2C Buffer Configuration Register (I2C_OMAP_BUF): */ +#define I2C_OMAP_BUF_RXFIF_CLR BIT(14) /* RX FIFO Clear */ +#define I2C_OMAP_BUF_TXFIF_CLR BIT(6) /* TX FIFO Clear */ + +/* I2C Status Register (I2C_OMAP_STAT): */ +#define I2C_OMAP_STAT_XDR BIT(14) /* TX Buffer draining */ +#define I2C_OMAP_STAT_RDR BIT(13) /* RX Buffer draining */ +#define I2C_OMAP_STAT_BB BIT(12) /* Bus busy */ +#define I2C_OMAP_STAT_ROVR BIT(11) /* Receive overrun */ +#define I2C_OMAP_STAT_XUDF BIT(10) /* Transmit underflow */ +#define I2C_OMAP_STAT_AAS BIT(9) /* Address as target */ +#define I2C_OMAP_STAT_XRDY BIT(4) /* Transmit data ready */ +#define I2C_OMAP_STAT_RRDY BIT(3) /* Receive data ready */ +#define I2C_OMAP_STAT_ARDY BIT(2) /* Register access ready */ +#define I2C_OMAP_STAT_NACK BIT(1) /* No ack interrupt enable */ +#define I2C_OMAP_STAT_AL BIT(0) /* Arbitration lost */ + +/* I2C System Test Register (I2C_OMAP_SYSTEST): */ +#define I2C_OMAP_SYSTEST_ST_EN BIT(15) /* System test enable */ +#define I2C_OMAP_SYSTEST_FREE BIT(14) /* Free running mode */ +#define I2C_OMAP_SYSTEST_TMODE_MASK (3 << 12) /* Test mode select mask */ +#define I2C_OMAP_SYSTEST_TMODE_SHIFT (12) /* Test mode select shift */ + +/* Functional mode */ +#define I2C_OMAP_SYSTEST_SCL_I_FUNC BIT(8) /* SCL line input value */ +#define I2C_OMAP_SYSTEST_SDA_I_FUNC BIT(6) /* SDA line input value */ + +/* SDA/SCL IO mode */ +#define I2C_OMAP_SYSTEST_SCL_I BIT(3) /* SCL line sense in */ +#define I2C_OMAP_SYSTEST_SCL_O BIT(2) /* SCL line drive out */ +#define I2C_OMAP_SYSTEST_SDA_I BIT(1) /* SDA line sense in */ +#define I2C_OMAP_SYSTEST_SDA_O BIT(0) /* SDA line drive out */ + +typedef void (*init_func_t)(const struct device *dev); +#define DEV_CFG(dev) ((const struct i2c_omap_cfg *)(dev)->config) +#define DEV_DATA(dev) ((struct i2c_omap_data *)(dev)->data) +#define DEV_I2C_BASE(dev) ((i2c_omap_regs_t *)DEVICE_MMIO_NAMED_GET(dev, base)) + +struct i2c_omap_cfg { + DEVICE_MMIO_NAMED_ROM(base); + uint32_t irq; + uint32_t speed; +}; + +enum i2c_omap_speed { + I2C_OMAP_SPEED_STANDARD, + I2C_OMAP_SPEED_FAST, + I2C_OMAP_SPEED_FAST_PLUS, +}; + +struct i2c_omap_speed_config { + uint32_t pscstate; + uint32_t scllstate; + uint32_t sclhstate; +}; + +struct i2c_omap_data { + DEVICE_MMIO_NAMED_RAM(base); + enum i2c_omap_speed speed; + struct i2c_omap_speed_config speed_config; + struct i2c_msg current_msg; + struct k_sem lock; + bool receiver; + bool bb_valid; +}; + +/** + * @brief Initializes the OMAP I2C driver. + * + * This function is responsible for initializing the OMAP I2C driver. + * + * @param dev Pointer to the device structure for the I2C driver instance. + */ +static void i2c_omap_init_ll(const struct device *dev) +{ + + struct i2c_omap_data *data = DEV_DATA(dev); + i2c_omap_regs_t *i2c_base_addr = DEV_I2C_BASE(dev); + + i2c_base_addr->CON = 0; + i2c_base_addr->PSC = data->speed_config.pscstate; + i2c_base_addr->SCLL = data->speed_config.scllstate; + i2c_base_addr->SCLH = data->speed_config.sclhstate; + i2c_base_addr->CON = I2C_OMAP_CON_EN; +} + +/** + * @brief Reset the OMAP I2C controller. + * + * This function resets the OMAP I2C controller specified by the device pointer. + * + * @param dev Pointer to the device structure for the I2C controller. + * @return 0 on success, negative errno code on failure. + */ +static int i2c_omap_reset(const struct device *dev) +{ + struct i2c_omap_data *data = DEV_DATA(dev); + i2c_omap_regs_t *i2c_base_addr = DEV_I2C_BASE(dev); + uint64_t timeout; + uint16_t sysc; + + sysc = i2c_base_addr->SYSC; + i2c_base_addr->CON &= ~I2C_OMAP_CON_EN; + timeout = k_uptime_get() + I2C_OMAP_TIMEOUT; + i2c_base_addr->CON = I2C_OMAP_CON_EN; + while (!(i2c_base_addr->SYSS & SYSS_RESETDONE_MASK)) { + if (k_uptime_get() > timeout) { + LOG_WRN("timeout waiting for controller reset"); + return -ETIMEDOUT; + } + k_busy_wait(100); + } + i2c_base_addr->SYSC = sysc; + data->bb_valid = 0; + return 0; +} + +/** + * @brief Set the speed of the OMAP I2C controller. + * + * This function sets the speed of the OMAP I2C controller based on the + * specified speed parameter. The speed can be set to either Fast mode or + * Standard mode. + * + * @param dev The pointer to the device structure. + * @param speed The desired speed for the I2C controller. + * + * @return 0 on success, negative error code on failure. + */ +static int i2c_omap_set_speed(const struct device *dev, uint32_t speed) +{ + struct i2c_omap_data *data = DEV_DATA(dev); + + /* If configured for High Speed */ + switch (speed) { + case I2C_BITRATE_FAST: + /* Fast mode */ + data->speed_config = (struct i2c_omap_speed_config){ + .pscstate = 9, + .scllstate = 7, + .sclhstate = 5, + }; + break; + case I2C_BITRATE_STANDARD: + /* Standard mode */ + data->speed_config = (struct i2c_omap_speed_config){ + .pscstate = 23, + .scllstate = 13, + .sclhstate = 15, + }; + break; + default: + return -ERANGE; + } + + 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. + * + * This function configures the OMAP I2C controller with the specified device configuration. + * + * @param dev The pointer to the device structure. + * @param dev_config The device configuration to be applied. + * + * @return 0 on success, negative error code on failure. + */ +static int i2c_omap_configure(const struct device *dev, uint32_t dev_config) +{ + uint32_t speed_cfg = I2C_BITRATE_STANDARD; + struct i2c_omap_data *data = DEV_DATA(dev); + + switch (I2C_SPEED_GET(dev_config)) { + case I2C_SPEED_STANDARD: + speed_cfg = I2C_BITRATE_STANDARD; + break; + case I2C_SPEED_FAST: + speed_cfg = I2C_BITRATE_FAST; + break; + default: + return -ENOTSUP; + } + if ((dev_config & I2C_MODE_CONTROLLER) != I2C_MODE_CONTROLLER) { + return -ENOTSUP; + } + k_sem_take(&data->lock, K_FOREVER); + i2c_omap_set_speed(dev, speed_cfg); + i2c_omap_init_ll(dev); + k_sem_give(&data->lock); + return 0; +} + +/** + * @brief Transmit or receive data over I2C bus + * + * This function transmits or receives data over the I2C bus using the OMAP I2C controller. + * + * @param dev Pointer to the I2C device structure + * @param num_bytes Number of bytes to transmit or receive + */ +static void i2c_omap_transmit_receive_data(const struct device *dev, uint8_t num_bytes) +{ + struct i2c_omap_data *data = DEV_DATA(dev); + i2c_omap_regs_t *i2c_base_addr = DEV_I2C_BASE(dev); + uint8_t *buf_ptr = data->current_msg.buf; + + while (num_bytes--) { + if (data->receiver) { + *buf_ptr++ = i2c_base_addr->DATA; + } else { + i2c_base_addr->DATA = *(buf_ptr++); + } + } +} + +/** + * @brief Resize the FIFO buffer for the OMAP I2C controller. + * + * This function resizes the FIFO buffer for the OMAP I2C controller based on the specified size. + * It clears the RX threshold and sets the new size for the receiver, or clears the TX threshold + * and sets the new size for the transmitter. + * + * @param dev Pointer to the device structure. + * @param size The new size of the FIFO buffer. + */ +static void i2c_omap_resize_fifo(const struct device *dev, uint8_t size) +{ + struct i2c_omap_data *data = DEV_DATA(dev); + i2c_omap_regs_t *i2c_base_addr = DEV_I2C_BASE(dev); + + if (data->receiver) { + i2c_base_addr->BUF &= I2C_OMAP_BUF_RXFIF_CLR; + i2c_base_addr->BUF |= ((size) << 8) | I2C_OMAP_BUF_RXFIF_CLR; + } else { + i2c_base_addr->BUF &= I2C_OMAP_BUF_TXFIF_CLR; + i2c_base_addr->BUF |= (size) | I2C_OMAP_BUF_TXFIF_CLR; + } +} + +/** + * @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. + * + * @param dev The I2C device structure. + * @return 0 if the bus becomes free, or a negative error code if the bus cannot + * be recovered. + */ +static int i2c_omap_wait_for_bb(const struct device *dev) +{ + i2c_omap_regs_t *i2c_base_addr = DEV_I2C_BASE(dev); + uint32_t timeout = k_uptime_get_32() + I2C_OMAP_TIMEOUT; + + while (i2c_base_addr->STAT & I2C_OMAP_STAT_BB) { + if (k_uptime_get_32() > timeout) { + LOG_ERR("Bus busy timeout"); + } + k_busy_wait(100); + } + return 0; +} + +/** + * @brief Performs data transfer for the OMAP I2C driver. + * + * This function is responsible for handling the data transfer logic for the OMAP I2C driver. + * It reads the status register and performs the necessary actions based on the status flags. + * It handles both receive and transmit logic, and also handles error conditions such as NACK, + * arbitration lost, receive overrun, and transmit underflow. + * + * @param dev Pointer to the device structure. + * + * @return Returns 0 on success, or a negative error code on failure. + */ +static int i2c_omap_transfer_message_ll(const struct device *dev) +{ + struct i2c_omap_data *data = DEV_DATA(dev); + i2c_omap_regs_t *i2c_base_addr = DEV_I2C_BASE(dev); + uint16_t stat = i2c_base_addr->STAT, result = 0; + + if (data->receiver) { + stat &= ~(I2C_OMAP_STAT_XDR | I2C_OMAP_STAT_XRDY); + } else { + stat &= ~(I2C_OMAP_STAT_RDR | I2C_OMAP_STAT_RRDY); + } + if (stat & I2C_OMAP_STAT_NACK) { + result |= I2C_OMAP_STAT_NACK; + i2c_base_addr->STAT |= I2C_OMAP_STAT_NACK; + } + if (stat & I2C_OMAP_STAT_AL) { + result |= I2C_OMAP_STAT_AL; + i2c_base_addr->STAT |= I2C_OMAP_STAT_AL; + } + if (stat & I2C_OMAP_STAT_ARDY) { + i2c_base_addr->STAT |= I2C_OMAP_STAT_ARDY; + } + if (stat & (I2C_OMAP_STAT_ARDY | I2C_OMAP_STAT_NACK | I2C_OMAP_STAT_AL)) { + + i2c_base_addr->STAT |= + (I2C_OMAP_STAT_RRDY | I2C_OMAP_STAT_RDR | I2C_OMAP_STAT_XRDY | + I2C_OMAP_STAT_XDR | I2C_OMAP_STAT_ARDY); + return result; + } + + /* Handle receive logic */ + if (stat & (I2C_OMAP_STAT_RRDY | I2C_OMAP_STAT_RDR)) { + int buffer = + (stat & I2C_OMAP_STAT_RRDY) ? i2c_base_addr->BUF : i2c_base_addr->BUFSTAT; + i2c_omap_transmit_receive_data(dev, buffer); + i2c_base_addr->STAT |= + (stat & I2C_OMAP_STAT_RRDY) ? I2C_OMAP_STAT_RRDY : I2C_OMAP_STAT_RDR; + return RETRY; + } + + /* Handle transmit logic */ + if (stat & (I2C_OMAP_STAT_XRDY | I2C_OMAP_STAT_XDR)) { + int buffer = + (stat & I2C_OMAP_STAT_XRDY) ? i2c_base_addr->BUF : i2c_base_addr->BUFSTAT; + i2c_omap_transmit_receive_data(dev, buffer); + i2c_base_addr->STAT |= + (stat & I2C_OMAP_STAT_XRDY) ? I2C_OMAP_STAT_XRDY : I2C_OMAP_STAT_XDR; + return RETRY; + } + + if (stat & I2C_OMAP_STAT_ROVR) { + i2c_base_addr->STAT |= I2C_OMAP_STAT_ROVR; + return I2C_OMAP_STAT_ROVR; + } + if (stat & I2C_OMAP_STAT_XUDF) { + i2c_base_addr->STAT |= I2C_OMAP_STAT_XUDF; + return I2C_OMAP_STAT_XUDF; + } + return RETRY; +} + +/** + * @brief Performs an I2C transfer of a single message. + * + * This function is responsible for performing an I2C transfer of a single message. + * It sets up the necessary configurations, writes the target device address, + * sets the buffer and buffer length, and handles various error conditions. + * + * @param dev The I2C device structure. + * @param msg Pointer to the I2C message structure. + * @param polling Flag indicating whether to use polling mode or not. + * @param addr The target device address. + * + * @return 0 on success, negative error code on failure. + * Possible error codes include: + * - ETIMEDOUT: Timeout occurred during the transfer. + * - EIO: I/O error due to receiver overrun or transmit underflow. + * - EAGAIN: Arbitration lost error, try again. + * - ENOMSG: Message error due to NACK. + */ +static int i2c_omap_transfer_message(const struct device *dev, struct i2c_msg *msg, bool polling, + uint16_t addr) +{ + struct i2c_omap_data *data = DEV_DATA(dev); + i2c_omap_regs_t *i2c_base_addr = DEV_I2C_BASE(dev); + unsigned long time_left = 1000; + uint16_t control_reg; + int result = 0; + /* Determine message direction (read or write) and update the receiver flag */ + data->receiver = msg->flags & I2C_MSG_READ; + /* Adjust the FIFO size according to the message length */ + i2c_omap_resize_fifo(dev, msg->len); + /* Set the target I2C address for the transfer */ + i2c_base_addr->SA = addr; + /* Store the message in the data structure */ + data->current_msg = *msg; + /* Set the message length in the I2C controller */ + i2c_base_addr->CNT = msg->len; + /* Clear FIFO buffers */ + control_reg = i2c_base_addr->BUF; + control_reg |= I2C_OMAP_BUF_RXFIF_CLR | I2C_OMAP_BUF_TXFIF_CLR; + i2c_base_addr->BUF = control_reg; + /* If we're not polling, reset the command completion semaphore */ + if (!polling) { + k_sem_reset(&data->lock); + } + /* Reset the command error status */ + /* Prepare the control register for the I2C operation */ + control_reg = I2C_OMAP_CON_EN | I2C_OMAP_CON_MST | I2C_OMAP_CON_STT; + /* Enable high-speed mode if required by the transfer speed */ + if (data->speed > I2C_BITRATE_FAST) { + control_reg |= I2C_OMAP_CON_OPMODE_HS; + } + /* Set the STOP condition if it's specified in the message flags */ + if (msg->flags & I2C_MSG_STOP) { + control_reg |= I2C_OMAP_CON_STP; + } + /* Set the transmission mode based on whether it's a read or write operation */ + if (!(msg->flags & I2C_MSG_READ)) { + control_reg |= I2C_OMAP_CON_TRX; + } + /* Start the I2C transfer by writing the control register */ + i2c_base_addr->CON = control_reg; + /* Poll for status until the transfer is complete */ + /* Call a lower-level function to continue the transfer */ + do { + result = i2c_omap_transfer_message_ll(dev); + time_left--; + } while (result == RETRY && time_left); + + /* If no errors occurred, return success */ + if (!result) { + return 0; + } + + /* Handle timeout or specific error conditions */ + if (result & (I2C_OMAP_STAT_ROVR | I2C_OMAP_STAT_XUDF)) { + i2c_omap_reset(dev); + i2c_omap_init_ll(dev); + /* Return an error code based on whether it was a timeout or buffer error */ + return -EIO; /* Receiver overrun or transmitter underflow */ + } + /* Handle arbitration loss and NACK errors */ + if (result & (I2C_OMAP_STAT_AL | -EAGAIN)) { + return -EAGAIN; + } + if (result & I2C_OMAP_STAT_NACK) { + /* Issue a STOP condition after NACK */ + i2c_base_addr->CON |= I2C_OMAP_CON_STP; + return -ENOMSG; /* Indicate a message error due to NACK */ + } + + /* Return a general I/O error if no specific error conditions matched */ + return -EIO; +} + +/** + * @brief Performs a common transfer operation for OMAP I2C devices. + * + * This function is responsible for transferring multiple I2C messages in a common way + * for OMAP I2C devices. It waits for the bus to be idle, then iterates through each + * message in the provided array and transfers them one by one using the i2c_omap_transfer_message() + * function. After all messages have been transferred, it waits for the bus to be idle again + * before returning. + * + * @param dev The pointer to the I2C device structure. + * @param msg An array of I2C messages to be transferred. + * @param num The number of messages in the array. + * @param polling Specifies whether to use polling or interrupt-based transfer. + * @param addr The I2C target address. + * @return 0 on success, or a negative error code on failure. + */ +static int i2c_omap_transfer_main(const struct device *dev, struct i2c_msg msg[], int num, + bool polling, uint16_t addr) +{ + int ret; + + ret = i2c_omap_wait_for_bb(dev); + struct i2c_omap_data *data = DEV_DATA(dev); + + k_sem_take(&data->lock, K_FOREVER); + if (ret < 0) { + return ret; + } + for (int msg_idx = 0; msg_idx < num; msg_idx++) { + ret = i2c_omap_transfer_message(dev, &msg[msg_idx], polling, addr); + if (ret < 0) { + break; + } + } + k_sem_give(&data->lock); + i2c_omap_wait_for_bb(dev); + return ret; +} + +/** + * @brief OMAP I2C transfer function using polling. + * + * This function performs the I2C transfer using the OMAP I2C controller + * in polling mode. It calls the common transfer function with the + * specified messages, number of messages, and target address. + * + * @param dev Pointer to the I2C device structure. + * @param msgs Array of I2C messages to be transferred. + * @param num_msgs Number of I2C messages in the array. + * @param addr Target address. + * @return 0 on success, negative error code on failure. + */ +static int i2c_omap_transfer_polling(const struct device *dev, struct i2c_msg msgs[], + uint8_t num_msgs, uint16_t addr) +{ + return i2c_omap_transfer_main(dev, msgs, num_msgs, true, addr); +} + +static DEVICE_API(i2c, i2c_omap_api) = { + .transfer = i2c_omap_transfer_polling, + .configure = i2c_omap_configure, +}; + +#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 = { \ + DEVICE_MMIO_NAMED_ROM_INIT(base, DT_DRV_INST(inst)), \ + .irq = DT_INST_IRQN(inst), \ + .speed = DT_INST_PROP(inst, clock_frequency), \ + }; \ + \ + static struct i2c_omap_data i2c_omap_data_##inst; \ + \ + I2C_DEVICE_DT_INST_DEFINE(inst, i2c_omap_init, NULL, &i2c_omap_data_##inst, \ + &i2c_omap_cfg_##inst, POST_KERNEL, CONFIG_I2C_INIT_PRIORITY, \ + &i2c_omap_api); + +DT_INST_FOREACH_STATUS_OKAY(I2C_OMAP_INIT) diff --git a/dts/bindings/i2c/ti,omap-i2c.yaml b/dts/bindings/i2c/ti,omap-i2c.yaml new file mode 100644 index 00000000000000..b9c976d08816cf --- /dev/null +++ b/dts/bindings/i2c/ti,omap-i2c.yaml @@ -0,0 +1,20 @@ +description: TI OMAP I2C Controller +compatible: "ti,omap-i2c" +include: [i2c-controller.yaml, pinctrl-device.yaml] +properties: + reg: + description: Base address and size of the I2C registers. + required: true + interrupts: + description: Interrupt specifier for the I2C controller. + required: true + pinctrl-0: + description: Pin control groups for this I2C instance. + required: true + pinctrl-names: + description: Names for the pin control groups. + required: true + clock-frequency: + description: The frequency of the I2C bus. + default: 100000 + required: true From e9e524fa94ed5b63f653e45347edf0676ed769bd Mon Sep 17 00:00:00 2001 From: Dhruv Menon Date: Tue, 22 Oct 2024 00:43:31 +0530 Subject: [PATCH 2/3] 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 37184a6436d5fb..8081cfc6ebdd9e 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 + 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 the I2C driver for TI OMAP SoCs. + Enable OMAP I2C driver bus recovery support via bitbanging. diff --git a/drivers/i2c/i2c_omap.c b/drivers/i2c/i2c_omap.c index 2d5779521033f2..ea953978614acd 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 DEVICE_API(i2c, 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 = { \ From 2ab31bd5d26b4307cd831fd04c5d8c644ac08f95 Mon Sep 17 00:00:00 2001 From: Dhruv Menon Date: Wed, 6 Nov 2024 01:05:15 +0530 Subject: [PATCH 3/3] boards: beagle: Enable I2C6 on BeagleBone AI64 board Provide I2C Support to BeagleBone AI64 board. Signed-off-by: Dhruv Menon --- ...lebone_ai64_j721e_main_r5f0_0-pinctrl.dtsi | 8 ++ .../beaglebone_ai64_j721e_main_r5f0_0.dts | 6 ++ .../beaglebone_ai64_j721e_main_r5f0_0.yaml | 1 + boards/beagle/beaglebone_ai64/doc/index.rst | 4 +- dts/arm/ti/j721e_main_r5.dtsi | 77 +++++++++++++++++++ 5 files changed, 95 insertions(+), 1 deletion(-) diff --git a/boards/beagle/beaglebone_ai64/beaglebone_ai64_j721e_main_r5f0_0-pinctrl.dtsi b/boards/beagle/beaglebone_ai64/beaglebone_ai64_j721e_main_r5f0_0-pinctrl.dtsi index dfc744b72e4a12..9cf1363ba226f5 100644 --- a/boards/beagle/beaglebone_ai64/beaglebone_ai64_j721e_main_r5f0_0-pinctrl.dtsi +++ b/boards/beagle/beaglebone_ai64/beaglebone_ai64_j721e_main_r5f0_0-pinctrl.dtsi @@ -18,4 +18,12 @@ /* 0x14 is address of padconfig register of p8.22 and 14 is mux mode */ pinmux = ; }; + i2c6_scl_default: i2c6_scl_default { + /* 0x1e0 is the address of padconfig register of p9.17 and 2 is mux mode */ + pinmux = ; + }; + i2c6_sda_default: i2c6_sda_default { + /* 0x1dc is the address of padconfig register of p9.18 and 2 is mux mode */ + pinmux = ; + }; }; diff --git a/boards/beagle/beaglebone_ai64/beaglebone_ai64_j721e_main_r5f0_0.dts b/boards/beagle/beaglebone_ai64/beaglebone_ai64_j721e_main_r5f0_0.dts index 8c4c8f0e8e4c94..804abfd95660a8 100644 --- a/boards/beagle/beaglebone_ai64/beaglebone_ai64_j721e_main_r5f0_0.dts +++ b/boards/beagle/beaglebone_ai64/beaglebone_ai64_j721e_main_r5f0_0.dts @@ -45,6 +45,12 @@ }; }; +&i2c6 { + status = "okay"; + pinctrl-0 = <&i2c6_scl_default &i2c6_sda_default>; + pinctrl-names = "default"; +}; + &uart2 { status = "okay"; pinctrl-0 = <&uart2_tx_default &uart2_rx_default>; diff --git a/boards/beagle/beaglebone_ai64/beaglebone_ai64_j721e_main_r5f0_0.yaml b/boards/beagle/beaglebone_ai64/beaglebone_ai64_j721e_main_r5f0_0.yaml index 0adaeddf82f47d..cecfa78f603d58 100644 --- a/boards/beagle/beaglebone_ai64/beaglebone_ai64_j721e_main_r5f0_0.yaml +++ b/boards/beagle/beaglebone_ai64/beaglebone_ai64_j721e_main_r5f0_0.yaml @@ -14,4 +14,5 @@ toolchain: - xtools supported: - uart + - i2c vendor: beagle diff --git a/boards/beagle/beaglebone_ai64/doc/index.rst b/boards/beagle/beaglebone_ai64/doc/index.rst index d54775011dbd56..3e62a1ad54a7aa 100644 --- a/boards/beagle/beaglebone_ai64/doc/index.rst +++ b/boards/beagle/beaglebone_ai64/doc/index.rst @@ -44,9 +44,11 @@ The board configuration supports, +-----------+------------+-----------------------+ | Interface | Controller | Driver/Component | +===========+============+=======================+ -| UART | on-chip | serial port-polling | +| UART | on-chip | serial port-polling, | | | | serial port-interrupt | +-----------+------------+-----------------------+ +| I2C | on-chip | i2c-polling | ++-----------+------------+-----------------------+ Other hardwares features are currently not supported. diff --git a/dts/arm/ti/j721e_main_r5.dtsi b/dts/arm/ti/j721e_main_r5.dtsi index 7b8f72ba5aed13..e088321e9664d3 100644 --- a/dts/arm/ti/j721e_main_r5.dtsi +++ b/dts/arm/ti/j721e_main_r5.dtsi @@ -55,6 +55,83 @@ status = "okay"; }; + i2c0: i2c0@2000000 { + compatible = "ti,omap-i2c"; + reg = <0x02000000 0x1000>; + interrupts = <0 150 IRQ_TYPE_LEVEL IRQ_DEFAULT_PRIORITY>; + interrupt-parent = <&vim>; + clock-frequency = <100000>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + + i2c1: i2c1@2010000 { + compatible = "ti,omap-i2c"; + reg = <0x02010000 0x1000>; + interrupts = <0 151 IRQ_TYPE_LEVEL IRQ_DEFAULT_PRIORITY>; + interrupt-parent = <&vim>; + clock-frequency = <100000>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + + i2c2: i2c2@2020000 { + compatible = "ti,omap-i2c"; + reg = <0x02020000 0x1000>; + interrupts = <0 183 IRQ_TYPE_LEVEL IRQ_DEFAULT_PRIORITY>; + interrupt-parent = <&vim>; + clock-frequency = <100000>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + + i2c3: i2c3@2030000 { + compatible = "ti,omap-i2c"; + reg = <0x02030000 0x1000>; + interrupts = <0 184 IRQ_TYPE_LEVEL IRQ_DEFAULT_PRIORITY>; + interrupt-parent = <&vim>; + clock-frequency = <100000>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + + i2c4: i2c4@2040000 { + compatible = "ti,omap-i2c"; + reg = <0x02040000 0x1000>; + interrupts = <0 185 IRQ_TYPE_LEVEL IRQ_DEFAULT_PRIORITY>; + interrupt-parent = <&vim>; + clock-frequency = <100000>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + + i2c5: i2c5@2050000 { + compatible = "ti,omap-i2c"; + reg = <0x02050000 0x1000>; + interrupts = <0 186 IRQ_TYPE_LEVEL IRQ_DEFAULT_PRIORITY>; + interrupt-parent = <&vim>; + clock-frequency = <100000>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + + i2c6: i2c@2060000 { + compatible = "ti,omap-i2c"; + reg = <0x02060000 0x1000>; + interrupts = <0 187 IRQ_TYPE_LEVEL IRQ_DEFAULT_PRIORITY>; + interrupt-parent = <&vim>; + clock-frequency = <100000>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + uart1: uart@2810000 { compatible = "ns16550"; reg = <0x02810000 0x100>;