/* * Copyright (c) 2023 Cypress Semiconductor Corporation (an Infineon company) or * an affiliate of Cypress Semiconductor Corporation * * SPDX-License-Identifier: Apache-2.0 */ /** * @brief I2C driver for Infineon XMC MCU family. */ #define DT_DRV_COMPAT infineon_xmc4xxx_i2c #include LOG_MODULE_REGISTER(i2c_infineon_xmc4, CONFIG_I2C_LOG_LEVEL); #include #include #include "i2c-priv.h" #include #include #define USIC_IRQ_MIN 84 #define IRQS_PER_USIC 6 #define I2C_XMC_EVENTS_MASK ( \ XMC_I2C_CH_EVENT_RECEIVE_START | \ XMC_I2C_CH_EVENT_DATA_LOST | \ XMC_I2C_CH_EVENT_TRANSMIT_SHIFT | \ XMC_I2C_CH_EVENT_TRANSMIT_BUFFER | \ XMC_I2C_CH_EVENT_STANDARD_RECEIVE | \ XMC_I2C_CH_EVENT_ALTERNATIVE_RECEIVE | \ XMC_I2C_CH_EVENT_BAUD_RATE_GENERATOR | \ XMC_I2C_CH_EVENT_START_CONDITION_RECEIVED | \ XMC_I2C_CH_EVENT_REPEATED_START_CONDITION_RECEIVED | \ XMC_I2C_CH_EVENT_STOP_CONDITION_RECEIVED | \ XMC_I2C_CH_EVENT_NACK | \ XMC_I2C_CH_EVENT_ARBITRATION_LOST | \ XMC_I2C_CH_EVENT_SLAVE_READ_REQUEST | \ XMC_I2C_CH_EVENT_ERROR | \ XMC_I2C_CH_EVENT_ACK) #define I2C_XMC_STATUS_FLAG_ERROR_MASK ( \ XMC_I2C_CH_STATUS_FLAG_WRONG_TDF_CODE_FOUND | \ XMC_I2C_CH_STATUS_FLAG_NACK_RECEIVED | \ XMC_I2C_CH_STATUS_FLAG_ARBITRATION_LOST | \ XMC_I2C_CH_STATUS_FLAG_ERROR | \ XMC_I2C_CH_STATUS_FLAG_DATA_LOST_INDICATION) /* I2C speed */ #define XMC4_I2C_SPEED_STANDARD (100000UL) #define XMC4_I2C_SPEED_FAST (400000UL) /* Data structure */ struct ifx_xmc4_i2c_data { XMC_I2C_CH_CONFIG_t cfg; struct k_sem operation_sem; struct k_sem target_sem; struct i2c_target_config *p_target_config; uint32_t dev_config; uint8_t target_wr_byte; uint8_t target_wr_buffer[CONFIG_I2C_INFINEON_XMC4_TARGET_BUF]; bool ignore_slave_select; bool is_configured; }; /* Device config structure */ struct ifx_xmc4_i2c_config { XMC_USIC_CH_t *i2c; const struct pinctrl_dev_config *pcfg; uint8_t scl_src; uint8_t sda_src; uint32_t bitrate; void (*irq_config_func)(const struct device *dev); }; static int ifx_xmc4_i2c_configure(const struct device *dev, uint32_t dev_config) { struct ifx_xmc4_i2c_data *data = dev->data; const struct ifx_xmc4_i2c_config *config = dev->config; /* This is deprecated and could be ignored in the future */ if (dev_config & I2C_ADDR_10_BITS) { LOG_ERR("Use I2C_MSG_ADDR_10_BITS instead of I2C_ADDR_10_BITS"); return -EIO; } switch (I2C_SPEED_GET(dev_config)) { case I2C_SPEED_STANDARD: data->cfg.baudrate = XMC4_I2C_SPEED_STANDARD; break; case I2C_SPEED_FAST: data->cfg.baudrate = XMC4_I2C_SPEED_FAST; break; default: LOG_ERR("Unsupported speed"); return -ERANGE; } data->dev_config = dev_config; /* Acquire semaphore (block I2C operation for another thread) */ if (k_sem_take(&data->operation_sem, K_FOREVER)) { return -EIO; } XMC_I2C_CH_Stop(config->i2c); /* Configure the I2C resource */ data->cfg.normal_divider_mode = false; XMC_I2C_CH_Init(config->i2c, &data->cfg); XMC_I2C_CH_SetInputSource(config->i2c, XMC_I2C_CH_INPUT_SCL, config->scl_src); XMC_I2C_CH_SetInputSource(config->i2c, XMC_I2C_CH_INPUT_SDA, config->sda_src); if (data->dev_config & I2C_MODE_CONTROLLER) { XMC_USIC_CH_SetFractionalDivider(config->i2c, XMC_USIC_CH_BRG_CLOCK_DIVIDER_MODE_FRACTIONAL, 1023U); } else { config->irq_config_func(dev); } XMC_I2C_CH_Start(config->i2c); data->is_configured = true; /* Release semaphore */ k_sem_give(&data->operation_sem); return 0; } static int ifx_xmc4_i2c_get_config(const struct device *dev, uint32_t *dev_config) { struct ifx_xmc4_i2c_data *data = dev->data; const struct ifx_xmc4_i2c_config *config = dev->config; if (!data->is_configured) { /* if not yet configured return configuration that will be used */ /* when transfer() is called */ uint32_t bitrate_cfg = i2c_map_dt_bitrate(config->bitrate); *dev_config = I2C_MODE_CONTROLLER | bitrate_cfg; return 0; } *dev_config = data->dev_config; return 0; } static int ifx_xmc4_i2c_msg_validate(struct i2c_msg *msg, uint8_t num_msgs) { for (uint32_t i = 0u; i < num_msgs; i++) { if ((I2C_MSG_ADDR_10_BITS & msg[i].flags) || (msg[i].buf == NULL)) { return -EINVAL; } } return 0; } static int ifx_xmc4_i2c_transfer(const struct device *dev, struct i2c_msg *msg, uint8_t num_msgs, uint16_t addr) { struct ifx_xmc4_i2c_data *data = dev->data; const struct ifx_xmc4_i2c_config *config = dev->config; XMC_I2C_CH_CMD_t cmd_type; if (!num_msgs) { return 0; } if (!data->is_configured) { int ret; uint32_t bitrate_cfg = i2c_map_dt_bitrate(config->bitrate); ret = ifx_xmc4_i2c_configure(dev, I2C_MODE_CONTROLLER | bitrate_cfg); if (ret) { return ret; } } /* Acquire semaphore (block I2C transfer for another thread) */ if (k_sem_take(&data->operation_sem, K_FOREVER)) { return -EIO; } /* This function checks if msg.buf is not NULL and if target address is not 10 bit. */ if (ifx_xmc4_i2c_msg_validate(msg, num_msgs) != 0) { k_sem_give(&data->operation_sem); return -EINVAL; } for (uint32_t msg_index = 0u; msg_index < num_msgs; msg_index++) { XMC_I2C_CH_ClearStatusFlag(config->i2c, 0xFFFFFFFF); if ((msg_index == 0) || (msg[msg_index].flags & I2C_MSG_RESTART)) { /* Send START condition */ cmd_type = ((msg[msg_index].flags & I2C_MSG_RW_MASK) == I2C_MSG_READ) ? XMC_I2C_CH_CMD_READ : XMC_I2C_CH_CMD_WRITE; if (msg[msg_index].flags & I2C_MSG_RESTART) { XMC_I2C_CH_MasterRepeatedStart(config->i2c, addr << 1, cmd_type); } else { XMC_I2C_CH_MasterStart(config->i2c, addr << 1, cmd_type); } /* Wait for acknowledge */ while ((XMC_I2C_CH_GetStatusFlag(config->i2c) & XMC_I2C_CH_STATUS_FLAG_ACK_RECEIVED) == 0U) { /* wait for ACK from slave */ if (XMC_I2C_CH_GetStatusFlag(config->i2c) & I2C_XMC_STATUS_FLAG_ERROR_MASK) { k_sem_give(&data->operation_sem); return -EIO; } } XMC_I2C_CH_ClearStatusFlag(config->i2c, XMC_I2C_CH_STATUS_FLAG_ACK_RECEIVED); } for (uint32_t buf_index = 0u; buf_index < msg[msg_index].len; buf_index++) { if (cmd_type == XMC_I2C_CH_CMD_WRITE) { /* Transmit next command from I2C master to I2C slave */ XMC_I2C_CH_MasterTransmit(config->i2c, msg[msg_index].buf[buf_index]); /* Wait for acknowledge */ while ((XMC_I2C_CH_GetStatusFlag(config->i2c) & XMC_I2C_CH_STATUS_FLAG_ACK_RECEIVED) == 0U) { /* wait for ACK from slave */ if (XMC_I2C_CH_GetStatusFlag(config->i2c) & I2C_XMC_STATUS_FLAG_ERROR_MASK) { k_sem_give(&data->operation_sem); return -EIO; } } XMC_I2C_CH_ClearStatusFlag(config->i2c, XMC_I2C_CH_STATUS_FLAG_ACK_RECEIVED); /* Wait until TX FIFO is empty */ while (!XMC_USIC_CH_TXFIFO_IsEmpty(config->i2c)) { /* wait until all data is sent by HW */ if (XMC_I2C_CH_GetStatusFlag(config->i2c) & I2C_XMC_STATUS_FLAG_ERROR_MASK) { k_sem_give(&data->operation_sem); return -EIO; } } } else { if (buf_index == (msg[msg_index].len - 1)) { XMC_I2C_CH_MasterReceiveNack(config->i2c); } else { XMC_I2C_CH_MasterReceiveAck(config->i2c); } while ((XMC_I2C_CH_GetStatusFlag(config->i2c) & (XMC_I2C_CH_STATUS_FLAG_ALTERNATIVE_RECEIVE_INDICATION | XMC_I2C_CH_STATUS_FLAG_RECEIVE_INDICATION)) == 0U) { /* wait for data byte from slave */ if (XMC_I2C_CH_GetStatusFlag(config->i2c) & I2C_XMC_STATUS_FLAG_ERROR_MASK) { k_sem_give(&data->operation_sem); return -EIO; } } XMC_I2C_CH_ClearStatusFlag(config->i2c, XMC_I2C_CH_STATUS_FLAG_ALTERNATIVE_RECEIVE_INDICATION | XMC_I2C_CH_STATUS_FLAG_RECEIVE_INDICATION); msg[msg_index].buf[buf_index] = XMC_I2C_CH_GetReceivedData(config->i2c); } } /* Send STOP condition */ if (msg[msg_index].flags & I2C_MSG_STOP) { XMC_I2C_CH_MasterStop(config->i2c); } } /* Release semaphore (After I2C transfer is complete) */ k_sem_give(&data->operation_sem); return 0; } static int ifx_xmc4_i2c_init(const struct device *dev) { struct ifx_xmc4_i2c_data *data = dev->data; const struct ifx_xmc4_i2c_config *config = dev->config; int ret; /* Configure semaphores */ ret = k_sem_init(&data->operation_sem, 1, 1); if (ret) { return ret; } ret = k_sem_init(&data->target_sem, 1, 1); if (ret) { return ret; } /* Configure dt provided device signals when available */ return pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT); } static int ifx_xmc4_i2c_target_register(const struct device *dev, struct i2c_target_config *cfg) { struct ifx_xmc4_i2c_data *data = dev->data; const struct ifx_xmc4_i2c_config *config = dev->config; uint32_t bitrate_cfg; if (!cfg || !cfg->callbacks->read_requested || !cfg->callbacks->read_processed || !cfg->callbacks->write_requested || !cfg->callbacks->write_received || !cfg->callbacks->stop) { return -EINVAL; } if (cfg->flags & I2C_TARGET_FLAGS_ADDR_10_BITS) { return -ENOTSUP; } /* Acquire semaphore (block I2C operation for another thread) */ if (k_sem_take(&data->target_sem, K_FOREVER)) { return -EIO; } data->p_target_config = cfg; data->cfg.address = cfg->address << 1; if (data->is_configured) { uint32_t bitrate; bitrate = I2C_SPEED_GET(data->dev_config); bitrate_cfg = i2c_map_dt_bitrate(bitrate); } else { bitrate_cfg = i2c_map_dt_bitrate(config->bitrate); } if (ifx_xmc4_i2c_configure(dev, bitrate_cfg) != 0) { /* Release semaphore */ k_sem_give(&data->target_sem); return -EIO; } k_sem_give(&data->target_sem); return 0; } static int ifx_xmc4_i2c_target_unregister(const struct device *dev, struct i2c_target_config *cfg) { struct ifx_xmc4_i2c_data *data = dev->data; const struct ifx_xmc4_i2c_config *config = dev->config; /* Acquire semaphore (block I2C operation for another thread) */ if (k_sem_take(&data->operation_sem, K_FOREVER)) { return -EIO; } data->p_target_config = NULL; XMC_I2C_CH_DisableEvent(config->i2c, I2C_XMC_EVENTS_MASK); /* Release semaphore */ k_sem_give(&data->operation_sem); return 0; } static void i2c_xmc4_isr(const struct device *dev) { struct ifx_xmc4_i2c_data *data = dev->data; const struct ifx_xmc4_i2c_config *config = dev->config; const struct i2c_target_callbacks *callbacks = data->p_target_config->callbacks; uint32_t status = XMC_I2C_CH_GetStatusFlag(config->i2c); while (status) { XMC_I2C_CH_ClearStatusFlag(config->i2c, status); if (status & XMC_I2C_CH_STATUS_FLAG_STOP_CONDITION_RECEIVED) { /* Flush the TX buffer */ XMC_USIC_CH_SetTransmitBufferStatus(config->i2c, XMC_USIC_CH_TBUF_STATUS_SET_IDLE); callbacks->stop(data->p_target_config); break; } if (!data->ignore_slave_select && (status & XMC_I2C_CH_STATUS_FLAG_SLAVE_SELECT)) { data->ignore_slave_select = true; /* Start a slave read */ if (status & XMC_I2C_CH_STATUS_FLAG_SLAVE_READ_REQUESTED) { callbacks->read_requested(data->p_target_config, &data->target_wr_byte); XMC_I2C_CH_SlaveTransmit(config->i2c, data->target_wr_byte); } else { callbacks->write_requested(data->p_target_config); } } /* Continue a slave read */ if (status & XMC_I2C_CH_STATUS_FLAG_TRANSMIT_SHIFT_INDICATION) { callbacks->read_processed(data->p_target_config, &data->target_wr_byte); XMC_I2C_CH_SlaveTransmit(config->i2c, data->target_wr_byte); } /* Start/Continue a slave write */ if (status & (XMC_I2C_CH_STATUS_FLAG_RECEIVE_INDICATION | XMC_I2C_CH_STATUS_FLAG_ALTERNATIVE_RECEIVE_INDICATION)) { callbacks->write_received(data->p_target_config, XMC_I2C_CH_GetReceivedData(config->i2c)); } if ((status & XMC_I2C_CH_STATUS_FLAG_START_CONDITION_RECEIVED) || (status & XMC_I2C_CH_STATUS_FLAG_REPEATED_START_CONDITION_RECEIVED)) { data->ignore_slave_select = false; } status = XMC_I2C_CH_GetStatusFlag(config->i2c); } } /* I2C API structure */ static DEVICE_API(i2c, i2c_xmc4_driver_api) = { .configure = ifx_xmc4_i2c_configure, .transfer = ifx_xmc4_i2c_transfer, .get_config = ifx_xmc4_i2c_get_config, .target_register = ifx_xmc4_i2c_target_register, .target_unregister = ifx_xmc4_i2c_target_unregister, #ifdef CONFIG_I2C_RTIO .iodev_submit = i2c_iodev_submit_fallback, #endif }; /* Macros for I2C instance declaration */ #define XMC4_IRQ_HANDLER_INIT(index) \ static void i2c_xmc4_irq_setup_##index(const struct device *dev) \ { \ const struct ifx_xmc4_i2c_config *config = dev->config; \ uint8_t irq_num = DT_INST_IRQN(index); \ uint8_t service_request = (irq_num - USIC_IRQ_MIN) % IRQS_PER_USIC; \ \ XMC_I2C_CH_SelectInterruptNodePointer(config->i2c, \ XMC_I2C_CH_INTERRUPT_NODE_POINTER_RECEIVE, service_request); \ XMC_I2C_CH_SelectInterruptNodePointer(config->i2c, \ XMC_I2C_CH_INTERRUPT_NODE_POINTER_ALTERNATE_RECEIVE, service_request); \ \ XMC_I2C_CH_EnableEvent(config->i2c, I2C_XMC_EVENTS_MASK); \ \ IRQ_CONNECT(DT_INST_IRQN(index), DT_INST_IRQ(index, priority), i2c_xmc4_isr, \ DEVICE_DT_INST_GET(index), 0); \ \ irq_enable(irq_num); \ } #define XMC4_IRQ_HANDLER_STRUCT_INIT(index) .irq_config_func = i2c_xmc4_irq_setup_##index #define INFINEON_XMC4_I2C_INIT(n) \ PINCTRL_DT_INST_DEFINE(n); \ XMC4_IRQ_HANDLER_INIT(n) \ \ static struct ifx_xmc4_i2c_data ifx_xmc4_i2c_data##n; \ \ static const struct ifx_xmc4_i2c_config i2c_xmc4_cfg_##n = { \ .i2c = (XMC_USIC_CH_t *)DT_INST_REG_ADDR(n), \ .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \ .scl_src = DT_INST_ENUM_IDX(n, scl_src), \ .sda_src = DT_INST_ENUM_IDX(n, sda_src), \ .bitrate = DT_INST_PROP_OR(n, clock_frequency, I2C_SPEED_STANDARD), \ XMC4_IRQ_HANDLER_STRUCT_INIT(n) \ }; \ \ I2C_DEVICE_DT_INST_DEFINE(n, ifx_xmc4_i2c_init, NULL, &ifx_xmc4_i2c_data##n, \ &i2c_xmc4_cfg_##n, POST_KERNEL, \ CONFIG_I2C_INIT_PRIORITY, &i2c_xmc4_driver_api); DT_INST_FOREACH_STATUS_OKAY(INFINEON_XMC4_I2C_INIT)