| /* |
| * Copyright (c) 2019 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT microchip_xec_i2c |
| |
| #include <zephyr/drivers/clock_control.h> |
| #include <zephyr/drivers/clock_control/mchp_xec_clock_control.h> |
| #include <zephyr/kernel.h> |
| #include <soc.h> |
| #include <errno.h> |
| #include <zephyr/drivers/gpio.h> |
| #include <zephyr/drivers/i2c.h> |
| #include <zephyr/drivers/pinctrl.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/irq.h> |
| LOG_MODULE_REGISTER(i2c_mchp, CONFIG_I2C_LOG_LEVEL); |
| |
| #define SPEED_100KHZ_BUS 0 |
| #define SPEED_400KHZ_BUS 1 |
| #define SPEED_1MHZ_BUS 2 |
| |
| #define EC_OWN_I2C_ADDR 0x7F |
| #define RESET_WAIT_US 20 |
| #define BUS_IDLE_US_DFLT 5 |
| |
| /* I2C timeout is 10 ms (WAIT_INTERVAL * WAIT_COUNT) */ |
| #define WAIT_INTERVAL 50 |
| #define WAIT_COUNT 200 |
| |
| /* Line High Timeout is 2.5 ms (WAIT_LINE_HIGH_USEC * WAIT_LINE_HIGH_COUNT) */ |
| #define WAIT_LINE_HIGH_USEC 25 |
| #define WAIT_LINE_HIGH_COUNT 100 |
| |
| /* I2C Read/Write bit pos */ |
| #define I2C_READ_WRITE_POS 0 |
| |
| struct xec_speed_cfg { |
| uint32_t bus_clk; |
| uint32_t data_timing; |
| uint32_t start_hold_time; |
| uint32_t config; |
| uint32_t timeout_scale; |
| }; |
| |
| struct i2c_xec_config { |
| uint32_t port_sel; |
| uint32_t base_addr; |
| uint8_t girq_id; |
| uint8_t girq_bit; |
| uint8_t pcr_idx; |
| uint8_t pcr_bitpos; |
| struct gpio_dt_spec sda_gpio; |
| struct gpio_dt_spec scl_gpio; |
| const struct pinctrl_dev_config *pcfg; |
| void (*irq_config_func)(void); |
| }; |
| |
| struct i2c_xec_data { |
| uint32_t pending_stop; |
| uint32_t error_seen; |
| uint32_t timeout_seen; |
| uint32_t previously_in_read; |
| uint32_t speed_id; |
| struct i2c_target_config *slave_cfg; |
| bool slave_attached; |
| bool slave_read; |
| }; |
| |
| /* Recommended programming values based on 16MHz |
| * i2c_baud_clk_period/bus_clk_period - 2 = (low_period + hi_period) |
| * bus_clk_reg (16MHz/100KHz -2) = 0x4F + 0x4F |
| * (16MHz/400KHz -2) = 0x0F + 0x17 |
| * (16MHz/1MHz -2) = 0x05 + 0x09 |
| */ |
| static const struct xec_speed_cfg xec_cfg_params[] = { |
| [SPEED_100KHZ_BUS] = { |
| .bus_clk = 0x00004F4F, |
| .data_timing = 0x0C4D5006, |
| .start_hold_time = 0x0000004D, |
| .config = 0x01FC01ED, |
| .timeout_scale = 0x4B9CC2C7, |
| }, |
| [SPEED_400KHZ_BUS] = { |
| .bus_clk = 0x00000F17, |
| .data_timing = 0x040A0A06, |
| .start_hold_time = 0x0000000A, |
| .config = 0x01000050, |
| .timeout_scale = 0x159CC2C7, |
| }, |
| [SPEED_1MHZ_BUS] = { |
| .bus_clk = 0x00000509, |
| .data_timing = 0x04060601, |
| .start_hold_time = 0x00000006, |
| .config = 0x10000050, |
| .timeout_scale = 0x089CC2C7, |
| }, |
| }; |
| |
| static void i2c_xec_reset_config(const struct device *dev) |
| { |
| const struct i2c_xec_config *config = |
| (const struct i2c_xec_config *const) (dev->config); |
| struct i2c_xec_data *data = |
| (struct i2c_xec_data *const) (dev->data); |
| uint32_t ba = config->base_addr; |
| |
| /* Assert RESET */ |
| z_mchp_xec_pcr_periph_reset(config->pcr_idx, config->pcr_bitpos); |
| /* Write 0x80. i.e Assert PIN bit, ESO = 0 and Interrupts |
| * disabled (ENI) |
| */ |
| MCHP_I2C_SMB_CTRL_WO(ba) = MCHP_I2C_SMB_CTRL_PIN; |
| |
| /* Enable controller and I2C filters */ |
| MCHP_I2C_SMB_CFG(ba) = MCHP_I2C_SMB_CFG_GC_EN | |
| MCHP_I2C_SMB_CFG_ENAB | |
| MCHP_I2C_SMB_CFG_FEN | |
| (config->port_sel & |
| MCHP_I2C_SMB_CFG_PORT_SEL_MASK); |
| |
| /* Configure bus clock register, Data Timing register, |
| * Repeated Start Hold Time register, |
| * and Timeout Scaling register |
| */ |
| MCHP_I2C_SMB_BUS_CLK(ba) = xec_cfg_params[data->speed_id].bus_clk; |
| MCHP_I2C_SMB_DATA_TM(ba) = xec_cfg_params[data->speed_id].data_timing; |
| MCHP_I2C_SMB_RSHT(ba) = |
| xec_cfg_params[data->speed_id].start_hold_time; |
| MCHP_I2C_SMB_TMTSC(ba) = xec_cfg_params[data->speed_id].timeout_scale; |
| |
| MCHP_I2C_SMB_CTRL_WO(ba) = MCHP_I2C_SMB_CTRL_PIN | |
| MCHP_I2C_SMB_CTRL_ESO | |
| MCHP_I2C_SMB_CTRL_ACK; |
| |
| k_busy_wait(RESET_WAIT_US); |
| } |
| |
| static int xec_spin_yield(int *counter) |
| { |
| *counter = *counter + 1; |
| |
| if (*counter > WAIT_COUNT) { |
| return -ETIMEDOUT; |
| } |
| |
| k_busy_wait(WAIT_INTERVAL); |
| |
| return 0; |
| } |
| |
| static void cleanup_registers(uint32_t ba) |
| { |
| uint32_t cfg = MCHP_I2C_SMB_CFG(ba); |
| |
| cfg |= MCHP_I2C_SMB_CFG_FLUSH_MXBUF_WO; |
| MCHP_I2C_SMB_CFG(ba) = cfg; |
| cfg &= ~MCHP_I2C_SMB_CFG_FLUSH_MXBUF_WO; |
| |
| cfg |= MCHP_I2C_SMB_CFG_FLUSH_MRBUF_WO; |
| MCHP_I2C_SMB_CFG(ba) = cfg; |
| cfg &= ~MCHP_I2C_SMB_CFG_FLUSH_MRBUF_WO; |
| |
| cfg |= MCHP_I2C_SMB_CFG_FLUSH_SXBUF_WO; |
| MCHP_I2C_SMB_CFG(ba) = cfg; |
| cfg &= ~MCHP_I2C_SMB_CFG_FLUSH_SXBUF_WO; |
| |
| cfg |= MCHP_I2C_SMB_CFG_FLUSH_SRBUF_WO; |
| MCHP_I2C_SMB_CFG(ba) = cfg; |
| cfg &= ~MCHP_I2C_SMB_CFG_FLUSH_SRBUF_WO; |
| } |
| |
| #ifdef CONFIG_I2C_TARGET |
| static void restart_slave(uint32_t ba) |
| { |
| MCHP_I2C_SMB_CTRL_WO(ba) = MCHP_I2C_SMB_CTRL_PIN | |
| MCHP_I2C_SMB_CTRL_ESO | |
| MCHP_I2C_SMB_CTRL_ACK | |
| MCHP_I2C_SMB_CTRL_ENI; |
| } |
| #endif |
| |
| static void recover_from_error(const struct device *dev) |
| { |
| const struct i2c_xec_config *config = |
| (const struct i2c_xec_config *const) (dev->config); |
| uint32_t ba = config->base_addr; |
| |
| cleanup_registers(ba); |
| i2c_xec_reset_config(dev); |
| } |
| |
| static int wait_bus_free(const struct device *dev) |
| { |
| const struct i2c_xec_config *config = |
| (const struct i2c_xec_config *const) (dev->config); |
| int ret; |
| int counter = 0; |
| uint32_t ba = config->base_addr; |
| |
| while (!(MCHP_I2C_SMB_STS_RO(ba) & MCHP_I2C_SMB_STS_NBB)) { |
| ret = xec_spin_yield(&counter); |
| |
| if (ret < 0) { |
| return ret; |
| } |
| } |
| |
| /* Check for bus error */ |
| if (MCHP_I2C_SMB_STS_RO(ba) & MCHP_I2C_SMB_STS_BER) { |
| recover_from_error(dev); |
| return -EBUSY; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Wait with timeout for I2C controller to finish transmit/receive of one |
| * byte(address or data). |
| * When transmit/receive operation is started the I2C PIN status is 1. Upon |
| * normal completion I2C PIN status asserts(0). |
| * We loop checking I2C status for the following events: |
| * Bus Error: |
| * Reset controller and return -EBUSY |
| * Lost Arbitration: |
| * Return -EPERM. We lost bus to another controller. No reset. |
| * PIN == 0: I2C Status LRB is valid and contains ACK/NACK data on 9th clock. |
| * ACK return 0 (success) |
| * NACK Issue STOP, wait for bus minimum idle time, return -EIO. |
| * Timeout: |
| * Reset controller and return -ETIMEDOUT |
| * |
| * NOTE: After generating a STOP the controller will not generate a START until |
| * Bus Minimum Idle time has expired. |
| */ |
| static int wait_completion(const struct device *dev) |
| { |
| const struct i2c_xec_config *config = |
| (const struct i2c_xec_config *const) (dev->config); |
| int ret; |
| int counter = 0; |
| uint32_t ba = config->base_addr; |
| |
| while (1) { |
| uint8_t status = MCHP_I2C_SMB_STS_RO(ba); |
| |
| /* Is bus error ? */ |
| if (status & MCHP_I2C_SMB_STS_BER) { |
| recover_from_error(dev); |
| return -EBUSY; |
| } |
| |
| /* Is Lost arbitration ? */ |
| status = MCHP_I2C_SMB_STS_RO(ba); |
| if (status & MCHP_I2C_SMB_STS_LAB) { |
| recover_from_error(dev); |
| return -EPERM; |
| } |
| |
| status = MCHP_I2C_SMB_STS_RO(ba); |
| /* PIN -> 0 indicates I2C is done */ |
| if (!(status & MCHP_I2C_SMB_STS_PIN)) { |
| /* PIN == 0. LRB contains state of 9th bit */ |
| if (status & MCHP_I2C_SMB_STS_LRB_AD0) { /* NACK? */ |
| /* Send STOP */ |
| MCHP_I2C_SMB_CTRL_WO(ba) = |
| MCHP_I2C_SMB_CTRL_PIN | |
| MCHP_I2C_SMB_CTRL_ESO | |
| MCHP_I2C_SMB_CTRL_STO | |
| MCHP_I2C_SMB_CTRL_ACK; |
| k_busy_wait(BUS_IDLE_US_DFLT); |
| return -EIO; |
| } |
| break; /* success: ACK */ |
| } |
| |
| ret = xec_spin_yield(&counter); |
| if (ret < 0) { |
| return ret; |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Call GPIO driver to read state of pins. |
| * Return boolean true if both lines HIGH else return boolean false |
| */ |
| static bool check_lines_high(const struct device *dev) |
| { |
| const struct i2c_xec_config *config = |
| (const struct i2c_xec_config *const)(dev->config); |
| gpio_port_value_t sda = 0, scl = 0; |
| |
| if (gpio_port_get_raw(config->sda_gpio.port, &sda)) { |
| LOG_ERR("gpio_port_get_raw for %s SDA failed", dev->name); |
| return false; |
| } |
| |
| /* both pins could be on same GPIO group */ |
| if (config->sda_gpio.port == config->scl_gpio.port) { |
| scl = sda; |
| } else { |
| if (gpio_port_get_raw(config->scl_gpio.port, &scl)) { |
| LOG_ERR("gpio_port_get_raw for %s SCL failed", |
| dev->name); |
| return false; |
| } |
| } |
| |
| return (sda & BIT(config->sda_gpio.pin)) && (scl & BIT(config->scl_gpio.pin)); |
| |
| } |
| |
| static int i2c_xec_configure(const struct device *dev, |
| uint32_t dev_config_raw) |
| { |
| struct i2c_xec_data *data = |
| (struct i2c_xec_data *const) (dev->data); |
| |
| if (!(dev_config_raw & I2C_MODE_CONTROLLER)) { |
| return -ENOTSUP; |
| } |
| |
| if (dev_config_raw & I2C_ADDR_10_BITS) { |
| return -ENOTSUP; |
| } |
| |
| switch (I2C_SPEED_GET(dev_config_raw)) { |
| case I2C_SPEED_STANDARD: |
| data->speed_id = SPEED_100KHZ_BUS; |
| break; |
| case I2C_SPEED_FAST: |
| data->speed_id = SPEED_400KHZ_BUS; |
| break; |
| case I2C_SPEED_FAST_PLUS: |
| data->speed_id = SPEED_1MHZ_BUS; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| i2c_xec_reset_config(dev); |
| |
| return 0; |
| } |
| |
| static int i2c_xec_poll_write(const struct device *dev, struct i2c_msg msg, |
| uint16_t addr) |
| { |
| const struct i2c_xec_config *config = |
| (const struct i2c_xec_config *const) (dev->config); |
| struct i2c_xec_data *data = |
| (struct i2c_xec_data *const) (dev->data); |
| uint32_t ba = config->base_addr; |
| uint8_t i2c_timer = 0, byte; |
| int ret; |
| |
| if (data->timeout_seen == 1) { |
| /* Wait to see if the slave has released the CLK */ |
| ret = wait_completion(dev); |
| if (ret) { |
| data->timeout_seen = 1; |
| LOG_ERR("%s: %s wait_completion failure %d\n", |
| __func__, dev->name, ret); |
| return ret; |
| } |
| data->timeout_seen = 0; |
| |
| /* If we are here, it means the slave has finally released |
| * the CLK. The master needs to end that transaction |
| * gracefully by sending a STOP on the bus. |
| */ |
| LOG_DBG("%s: %s Force Stop", __func__, dev->name); |
| MCHP_I2C_SMB_CTRL_WO(ba) = |
| MCHP_I2C_SMB_CTRL_PIN | |
| MCHP_I2C_SMB_CTRL_ESO | |
| MCHP_I2C_SMB_CTRL_STO | |
| MCHP_I2C_SMB_CTRL_ACK; |
| k_busy_wait(BUS_IDLE_US_DFLT); |
| data->pending_stop = 0; |
| |
| /* If the timeout had occurred while the master was reading |
| * something from the slave, that read needs to be completed |
| * to clear the bus. |
| */ |
| if (data->previously_in_read == 1) { |
| data->previously_in_read = 0; |
| byte = MCHP_I2C_SMB_DATA(ba); |
| } |
| return -EBUSY; |
| } |
| |
| if ((data->pending_stop == 0) || (data->error_seen == 1)) { |
| /* Wait till clock and data lines are HIGH */ |
| while (check_lines_high(dev) == false) { |
| if (i2c_timer >= WAIT_LINE_HIGH_COUNT) { |
| LOG_DBG("%s: %s not high", |
| __func__, dev->name); |
| data->error_seen = 1; |
| return -EBUSY; |
| } |
| k_busy_wait(WAIT_LINE_HIGH_USEC); |
| i2c_timer++; |
| } |
| |
| if (data->error_seen) { |
| LOG_DBG("%s: Recovering %s previously in error", |
| __func__, dev->name); |
| data->error_seen = 0; |
| recover_from_error(dev); |
| } |
| |
| /* Wait until bus is free */ |
| ret = wait_bus_free(dev); |
| if (ret) { |
| data->error_seen = 1; |
| LOG_DBG("%s: %s wait_bus_free failure %d", |
| __func__, dev->name, ret); |
| return ret; |
| } |
| |
| /* Send slave address */ |
| MCHP_I2C_SMB_DATA(ba) = (addr & ~BIT(0)); |
| |
| /* Send start and ack bits */ |
| MCHP_I2C_SMB_CTRL_WO(ba) = MCHP_I2C_SMB_CTRL_PIN | |
| MCHP_I2C_SMB_CTRL_ESO | MCHP_I2C_SMB_CTRL_STA | |
| MCHP_I2C_SMB_CTRL_ACK; |
| |
| ret = wait_completion(dev); |
| switch (ret) { |
| case 0: /* Success */ |
| break; |
| |
| case -EIO: |
| LOG_WRN("%s: No Addr ACK from Slave 0x%x on %s", |
| __func__, addr >> 1, dev->name); |
| return ret; |
| |
| default: |
| data->error_seen = 1; |
| LOG_ERR("%s: %s wait_comp error %d for addr send", |
| __func__, dev->name, ret); |
| return ret; |
| } |
| } |
| |
| /* Send bytes */ |
| for (int i = 0U; i < msg.len; i++) { |
| MCHP_I2C_SMB_DATA(ba) = msg.buf[i]; |
| ret = wait_completion(dev); |
| |
| switch (ret) { |
| case 0: /* Success */ |
| break; |
| |
| case -EIO: |
| LOG_ERR("%s: No Data ACK from Slave 0x%x on %s", |
| __func__, addr >> 1, dev->name); |
| return ret; |
| |
| case -ETIMEDOUT: |
| data->timeout_seen = 1; |
| LOG_ERR("%s: Clk stretch Timeout - Slave 0x%x on %s", |
| __func__, addr >> 1, dev->name); |
| return ret; |
| |
| default: |
| data->error_seen = 1; |
| LOG_ERR("%s: %s wait_completion error %d for data send", |
| __func__, dev->name, ret); |
| return ret; |
| } |
| } |
| |
| /* Handle stop bit for last byte to write */ |
| if (msg.flags & I2C_MSG_STOP) { |
| /* Send stop and ack bits */ |
| MCHP_I2C_SMB_CTRL_WO(ba) = |
| MCHP_I2C_SMB_CTRL_PIN | |
| MCHP_I2C_SMB_CTRL_ESO | |
| MCHP_I2C_SMB_CTRL_STO | |
| MCHP_I2C_SMB_CTRL_ACK; |
| data->pending_stop = 0; |
| } else { |
| data->pending_stop = 1; |
| } |
| |
| return 0; |
| } |
| |
| static int i2c_xec_poll_read(const struct device *dev, struct i2c_msg msg, |
| uint16_t addr) |
| { |
| const struct i2c_xec_config *config = |
| (const struct i2c_xec_config *const) (dev->config); |
| struct i2c_xec_data *data = |
| (struct i2c_xec_data *const) (dev->data); |
| uint32_t ba = config->base_addr; |
| uint8_t byte, ctrl, i2c_timer = 0; |
| int ret; |
| |
| if (data->timeout_seen == 1) { |
| /* Wait to see if the slave has released the CLK */ |
| ret = wait_completion(dev); |
| if (ret) { |
| data->timeout_seen = 1; |
| LOG_ERR("%s: %s wait_completion failure %d\n", |
| __func__, dev->name, ret); |
| return ret; |
| } |
| data->timeout_seen = 0; |
| |
| /* If we are here, it means the slave has finally released |
| * the CLK. The master needs to end that transaction |
| * gracefully by sending a STOP on the bus. |
| */ |
| LOG_DBG("%s: %s Force Stop", __func__, dev->name); |
| MCHP_I2C_SMB_CTRL_WO(ba) = |
| MCHP_I2C_SMB_CTRL_PIN | |
| MCHP_I2C_SMB_CTRL_ESO | |
| MCHP_I2C_SMB_CTRL_STO | |
| MCHP_I2C_SMB_CTRL_ACK; |
| k_busy_wait(BUS_IDLE_US_DFLT); |
| return -EBUSY; |
| } |
| |
| if (!(msg.flags & I2C_MSG_RESTART) || (data->error_seen == 1)) { |
| /* Wait till clock and data lines are HIGH */ |
| while (check_lines_high(dev) == false) { |
| if (i2c_timer >= WAIT_LINE_HIGH_COUNT) { |
| LOG_DBG("%s: %s not high", |
| __func__, dev->name); |
| data->error_seen = 1; |
| return -EBUSY; |
| } |
| k_busy_wait(WAIT_LINE_HIGH_USEC); |
| i2c_timer++; |
| } |
| |
| if (data->error_seen) { |
| LOG_DBG("%s: Recovering %s previously in error", |
| __func__, dev->name); |
| data->error_seen = 0; |
| recover_from_error(dev); |
| } |
| |
| /* Wait until bus is free */ |
| ret = wait_bus_free(dev); |
| if (ret) { |
| data->error_seen = 1; |
| LOG_DBG("%s: %s wait_bus_free failure %d", |
| __func__, dev->name, ret); |
| return ret; |
| } |
| } |
| |
| /* MCHP I2C spec recommends that for repeated start to write to control |
| * register before writing to data register |
| */ |
| MCHP_I2C_SMB_CTRL_WO(ba) = MCHP_I2C_SMB_CTRL_ESO | |
| MCHP_I2C_SMB_CTRL_STA | MCHP_I2C_SMB_CTRL_ACK; |
| |
| /* Send slave address */ |
| MCHP_I2C_SMB_DATA(ba) = (addr | BIT(0)); |
| |
| ret = wait_completion(dev); |
| switch (ret) { |
| case 0: /* Success */ |
| break; |
| |
| case -EIO: |
| data->error_seen = 1; |
| LOG_WRN("%s: No Addr ACK from Slave 0x%x on %s", |
| __func__, addr >> 1, dev->name); |
| return ret; |
| |
| case -ETIMEDOUT: |
| data->previously_in_read = 1; |
| data->timeout_seen = 1; |
| LOG_ERR("%s: Clk stretch Timeout - Slave 0x%x on %s", |
| __func__, addr >> 1, dev->name); |
| return ret; |
| |
| default: |
| data->error_seen = 1; |
| LOG_ERR("%s: %s wait_completion error %d for address send", |
| __func__, dev->name, ret); |
| return ret; |
| } |
| |
| if (msg.len == 1) { |
| /* Send NACK for last transaction */ |
| MCHP_I2C_SMB_CTRL_WO(ba) = MCHP_I2C_SMB_CTRL_ESO; |
| } |
| |
| /* Read dummy byte */ |
| byte = MCHP_I2C_SMB_DATA(ba); |
| |
| for (int i = 0U; i < msg.len; i++) { |
| ret = wait_completion(dev); |
| switch (ret) { |
| case 0: /* Success */ |
| break; |
| |
| case -EIO: |
| LOG_ERR("%s: No Data ACK from Slave 0x%x on %s", |
| __func__, addr >> 1, dev->name); |
| return ret; |
| |
| case -ETIMEDOUT: |
| data->previously_in_read = 1; |
| data->timeout_seen = 1; |
| LOG_ERR("%s: Clk stretch Timeout - Slave 0x%x on %s", |
| __func__, addr >> 1, dev->name); |
| return ret; |
| |
| default: |
| data->error_seen = 1; |
| LOG_ERR("%s: %s wait_completion error %d for data send", |
| __func__, dev->name, ret); |
| return ret; |
| } |
| |
| if (i == (msg.len - 1)) { |
| if (msg.flags & I2C_MSG_STOP) { |
| /* Send stop and ack bits */ |
| ctrl = (MCHP_I2C_SMB_CTRL_PIN | |
| MCHP_I2C_SMB_CTRL_ESO | |
| MCHP_I2C_SMB_CTRL_STO | |
| MCHP_I2C_SMB_CTRL_ACK); |
| MCHP_I2C_SMB_CTRL_WO(ba) = ctrl; |
| data->pending_stop = 0; |
| } |
| } else if (i == (msg.len - 2)) { |
| /* Send NACK for last transaction */ |
| MCHP_I2C_SMB_CTRL_WO(ba) = MCHP_I2C_SMB_CTRL_ESO; |
| } |
| msg.buf[i] = MCHP_I2C_SMB_DATA(ba); |
| } |
| |
| return 0; |
| } |
| |
| static int i2c_xec_transfer(const struct device *dev, struct i2c_msg *msgs, |
| uint8_t num_msgs, uint16_t addr) |
| { |
| int ret = 0; |
| |
| #ifdef CONFIG_I2C_TARGET |
| struct i2c_xec_data *data = dev->data; |
| |
| if (data->slave_attached) { |
| LOG_ERR("%s Device is registered as slave", dev->name); |
| return -EBUSY; |
| } |
| #endif |
| |
| addr <<= 1; |
| for (int i = 0U; i < num_msgs; i++) { |
| if ((msgs[i].flags & I2C_MSG_RW_MASK) == I2C_MSG_WRITE) { |
| ret = i2c_xec_poll_write(dev, msgs[i], addr); |
| if (ret) { |
| LOG_ERR("%s Write error: %d", dev->name, ret); |
| return ret; |
| } |
| } else { |
| ret = i2c_xec_poll_read(dev, msgs[i], addr); |
| if (ret) { |
| LOG_ERR("%s Read error: %d", dev->name, ret); |
| return ret; |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| static void i2c_xec_bus_isr(const struct device *dev) |
| { |
| #ifdef CONFIG_I2C_TARGET |
| const struct i2c_xec_config *config = |
| (const struct i2c_xec_config *const) (dev->config); |
| struct i2c_xec_data *data = dev->data; |
| const struct i2c_target_callbacks *slave_cb = data->slave_cfg->callbacks; |
| uint32_t ba = config->base_addr; |
| |
| uint32_t status; |
| uint8_t val; |
| |
| uint8_t dummy = 0U; |
| |
| if (!data->slave_attached) { |
| return; |
| } |
| |
| /* Get current status */ |
| status = MCHP_I2C_SMB_STS_RO(ba); |
| |
| /* Bus Error */ |
| if (status & MCHP_I2C_SMB_STS_BER) { |
| if (slave_cb->stop) { |
| slave_cb->stop(data->slave_cfg); |
| } |
| restart_slave(ba); |
| goto clear_iag; |
| } |
| |
| /* External stop */ |
| if (status & MCHP_I2C_SMB_STS_EXT_STOP) { |
| if (slave_cb->stop) { |
| slave_cb->stop(data->slave_cfg); |
| } |
| dummy = MCHP_I2C_SMB_DATA(ba); |
| restart_slave(ba); |
| goto clear_iag; |
| } |
| |
| /* Address byte handling */ |
| if (status & MCHP_I2C_SMB_STS_AAS) { |
| uint8_t slv_data = MCHP_I2C_SMB_DATA(ba); |
| |
| if (!(slv_data & BIT(I2C_READ_WRITE_POS))) { |
| /* Slave receive */ |
| data->slave_read = false; |
| if (slave_cb->write_requested) { |
| slave_cb->write_requested(data->slave_cfg); |
| } |
| goto clear_iag; |
| } else { |
| /* Slave transmit */ |
| data->slave_read = true; |
| if (slave_cb->read_requested) { |
| slave_cb->read_requested(data->slave_cfg, &val); |
| } |
| MCHP_I2C_SMB_DATA(ba) = val; |
| goto clear_iag; |
| } |
| } |
| |
| /* Slave transmit */ |
| if (data->slave_read) { |
| /* Master has Nacked, then just write a dummy byte */ |
| if (MCHP_I2C_SMB_STS_RO(ba) & MCHP_I2C_SMB_STS_LRB_AD0) { |
| MCHP_I2C_SMB_DATA(ba) = dummy; |
| } else { |
| if (slave_cb->read_processed) { |
| slave_cb->read_processed(data->slave_cfg, &val); |
| } |
| MCHP_I2C_SMB_DATA(ba) = val; |
| } |
| } else { |
| val = MCHP_I2C_SMB_DATA(ba); |
| /* TODO NACK Master */ |
| if (slave_cb->write_received) { |
| slave_cb->write_received(data->slave_cfg, val); |
| } |
| } |
| |
| clear_iag: |
| MCHP_GIRQ_SRC(config->girq_id) = BIT(config->girq_bit); |
| #endif |
| } |
| |
| #ifdef CONFIG_I2C_TARGET |
| static int i2c_xec_target_register(const struct device *dev, |
| struct i2c_target_config *config) |
| { |
| const struct i2c_xec_config *cfg = dev->config; |
| struct i2c_xec_data *data = dev->data; |
| uint32_t ba = cfg->base_addr; |
| int ret; |
| int counter = 0; |
| |
| if (!config) { |
| return -EINVAL; |
| } |
| |
| if (data->slave_attached) { |
| return -EBUSY; |
| } |
| |
| /* Wait for any outstanding transactions to complete so that |
| * the bus is free |
| */ |
| while (!(MCHP_I2C_SMB_STS_RO(ba) & MCHP_I2C_SMB_STS_NBB)) { |
| ret = xec_spin_yield(&counter); |
| |
| if (ret < 0) { |
| return ret; |
| } |
| } |
| |
| data->slave_cfg = config; |
| |
| /* Set own address */ |
| MCHP_I2C_SMB_OWN_ADDR(ba) = data->slave_cfg->address; |
| restart_slave(ba); |
| |
| data->slave_attached = true; |
| |
| /* Clear before enabling girq bit */ |
| MCHP_GIRQ_SRC(cfg->girq_id) = BIT(cfg->girq_bit); |
| MCHP_GIRQ_ENSET(cfg->girq_id) = BIT(cfg->girq_bit); |
| |
| return 0; |
| } |
| |
| static int i2c_xec_target_unregister(const struct device *dev, |
| struct i2c_target_config *config) |
| { |
| const struct i2c_xec_config *cfg = dev->config; |
| struct i2c_xec_data *data = dev->data; |
| |
| if (!data->slave_attached) { |
| return -EINVAL; |
| } |
| |
| data->slave_attached = false; |
| |
| MCHP_GIRQ_ENCLR(cfg->girq_id) = BIT(cfg->girq_bit); |
| |
| return 0; |
| } |
| #endif |
| |
| static const struct i2c_driver_api i2c_xec_driver_api = { |
| .configure = i2c_xec_configure, |
| .transfer = i2c_xec_transfer, |
| #ifdef CONFIG_I2C_TARGET |
| .target_register = i2c_xec_target_register, |
| .target_unregister = i2c_xec_target_unregister, |
| #endif |
| #ifdef CONFIG_I2C_RTIO |
| .iodev_submit = i2c_iodev_submit_fallback, |
| #endif |
| }; |
| |
| static int i2c_xec_init(const struct device *dev) |
| { |
| const struct i2c_xec_config *cfg = dev->config; |
| struct i2c_xec_data *data = |
| (struct i2c_xec_data *const) (dev->data); |
| int ret; |
| |
| data->pending_stop = 0; |
| data->slave_attached = false; |
| |
| ret = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT); |
| if (ret != 0) { |
| LOG_ERR("XEC I2C pinctrl setup failed (%d)", ret); |
| return ret; |
| } |
| |
| if (!gpio_is_ready_dt(&cfg->sda_gpio)) { |
| LOG_ERR("%s GPIO device is not ready for SDA GPIO", dev->name); |
| return -ENODEV; |
| } |
| |
| if (!gpio_is_ready_dt(&cfg->scl_gpio)) { |
| LOG_ERR("%s GPIO device is not ready for SCL GPIO", dev->name); |
| return -ENODEV; |
| } |
| |
| /* Default configuration */ |
| ret = i2c_xec_configure(dev, |
| I2C_MODE_CONTROLLER | |
| I2C_SPEED_SET(I2C_SPEED_STANDARD)); |
| if (ret) { |
| LOG_ERR("%s configure failed %d", dev->name, ret); |
| return ret; |
| } |
| |
| #ifdef CONFIG_I2C_TARGET |
| const struct i2c_xec_config *config = |
| (const struct i2c_xec_config *const) (dev->config); |
| |
| config->irq_config_func(); |
| #endif |
| return 0; |
| } |
| |
| #define I2C_XEC_DEVICE(n) \ |
| \ |
| PINCTRL_DT_INST_DEFINE(n); \ |
| \ |
| static void i2c_xec_irq_config_func_##n(void); \ |
| \ |
| static struct i2c_xec_data i2c_xec_data_##n; \ |
| static const struct i2c_xec_config i2c_xec_config_##n = { \ |
| .base_addr = \ |
| DT_INST_REG_ADDR(n), \ |
| .port_sel = DT_INST_PROP(n, port_sel), \ |
| .girq_id = DT_INST_PROP(n, girq), \ |
| .girq_bit = DT_INST_PROP(n, girq_bit), \ |
| .pcr_idx = DT_INST_PROP_BY_IDX(n, pcrs, 0), \ |
| .pcr_bitpos = DT_INST_PROP_BY_IDX(n, pcrs, 1), \ |
| .sda_gpio = GPIO_DT_SPEC_INST_GET(n, sda_gpios), \ |
| .scl_gpio = GPIO_DT_SPEC_INST_GET(n, scl_gpios), \ |
| .irq_config_func = i2c_xec_irq_config_func_##n, \ |
| .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \ |
| }; \ |
| I2C_DEVICE_DT_INST_DEFINE(n, i2c_xec_init, NULL, \ |
| &i2c_xec_data_##n, &i2c_xec_config_##n, \ |
| POST_KERNEL, CONFIG_I2C_INIT_PRIORITY, \ |
| &i2c_xec_driver_api); \ |
| \ |
| static void i2c_xec_irq_config_func_##n(void) \ |
| { \ |
| IRQ_CONNECT(DT_INST_IRQN(n), \ |
| DT_INST_IRQ(n, priority), \ |
| i2c_xec_bus_isr, \ |
| DEVICE_DT_INST_GET(n), 0); \ |
| irq_enable(DT_INST_IRQN(n)); \ |
| } |
| |
| DT_INST_FOREACH_STATUS_OKAY(I2C_XEC_DEVICE) |