| /* |
| * Copyright (c) 2019 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <drivers/clock_control.h> |
| #include <kernel.h> |
| #include <soc.h> |
| #include <errno.h> |
| #include <drivers/i2c.h> |
| |
| #define SPEED_100KHZ_BUS 0 |
| #define SPEED_400KHZ_BUS 1 |
| #define SPEED_1MHZ_BUS 2 |
| |
| #define WAIT_INTERVAL 5 |
| /* 250 us */ |
| #define TIMEOUT 100 |
| /* 25 us */ |
| #define MAX_CLK_STRETCHING 5 |
| |
| struct xec_speed_cfg { |
| u32_t bus_clk; |
| u32_t data_timing; |
| u32_t start_hold_time; |
| u32_t config; |
| u32_t timeout_scale; |
| }; |
| |
| struct i2c_xec_config { |
| u32_t port_sel; |
| u32_t base_addr; |
| }; |
| |
| struct i2c_xec_data { |
| u32_t pending_stop; |
| }; |
| |
| /* 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 int xec_spin_yield(int *counter) |
| { |
| *counter = *counter + 1; |
| |
| if (*counter > TIMEOUT) { |
| return -ETIMEDOUT; |
| } |
| |
| if (*counter > MAX_CLK_STRETCHING * 2) { |
| k_yield(); |
| } else { |
| k_busy_wait(5); |
| } |
| |
| return 0; |
| } |
| |
| static void recover_from_error(u32_t ba) |
| { |
| 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; |
| } |
| |
| static int wait_bus_free(u32_t ba) |
| { |
| int ret; |
| int counter = 0; |
| |
| while (!(MCHP_I2C_SMB_STS_RO(ba) & MCHP_I2C_SMB_STS_NBB)) { |
| ret = xec_spin_yield(&counter); |
| |
| if (ret < 0) { |
| return ret; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int wait_completion(u32_t ba) |
| { |
| int ret; |
| int counter = 0; |
| |
| /* Wait for transaction to be completed */ |
| while (MCHP_I2C_SMB_STS_RO(ba) & MCHP_I2C_SMB_STS_PIN) { |
| ret = xec_spin_yield(&counter); |
| |
| if (ret < 0) { |
| recover_from_error(ba); |
| return ret; |
| } |
| } |
| |
| /* Check if Slave send ACK/NACK */ |
| if (MCHP_I2C_SMB_STS_RO(ba) & MCHP_I2C_SMB_STS_LRB_AD0) { |
| recover_from_error(ba); |
| return -EIO; |
| } |
| |
| /* Check for bus error */ |
| if (MCHP_I2C_SMB_STS_RO(ba) & MCHP_I2C_SMB_STS_BER) { |
| recover_from_error(ba); |
| return -EBUSY; |
| } |
| |
| return 0; |
| } |
| |
| static bool check_lines(u32_t ba) |
| { |
| return ((!(MCHP_I2C_SMB_BB_CTRL(ba) & MCHP_I2C_SMB_BB_CLKI_RO)) || |
| (!(MCHP_I2C_SMB_BB_CTRL(ba) & MCHP_I2C_SMB_BB_DATI_RO))); |
| } |
| |
| static int i2c_xec_configure(struct device *dev, u32_t dev_config_raw) |
| { |
| const struct i2c_xec_config *config = |
| (const struct i2c_xec_config *const) (dev->config->config_info); |
| u32_t ba = config->base_addr; |
| u8_t port_sel = config->port_sel; |
| u32_t speed_id; |
| u32_t cfg, bb_ctrl; |
| u8_t ctrl; |
| |
| if (!(dev_config_raw & I2C_MODE_MASTER)) { |
| return -ENOTSUP; |
| } |
| |
| if (dev_config_raw & I2C_ADDR_10_BITS) { |
| return -ENOTSUP; |
| } |
| |
| switch (I2C_SPEED_GET(dev_config_raw)) { |
| case I2C_SPEED_STANDARD: |
| speed_id = SPEED_100KHZ_BUS; |
| break; |
| case I2C_SPEED_FAST: |
| speed_id = SPEED_400KHZ_BUS; |
| break; |
| case I2C_SPEED_FAST_PLUS: |
| speed_id = SPEED_1MHZ_BUS; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| cfg = MCHP_I2C_SMB_CFG(ba) & MCHP_I2C_SMB_CTRL_MASK; |
| bb_ctrl = MCHP_I2C_SMB_BB_CTRL(ba) & MCHP_I2C_SMB_BB_MASK; |
| ctrl = MCHP_I2C_SMB_CTRL(ba) & MCHP_I2C_SMB_CTRL_MASK; |
| |
| /* Assert RESET and clr others */ |
| cfg |= MCHP_I2C_SMB_CFG_RESET; |
| MCHP_I2C_SMB_CFG(ba) = cfg; |
| |
| /* Enable bit-bang mode, do not enable SMBus HW timeouts */ |
| bb_ctrl |= MCHP_I2C_SMB_BB_EN; |
| bb_ctrl |= MCHP_I2C_SMB_BB_SCL_DIR_OUT; |
| bb_ctrl |= MCHP_I2C_SMB_BB_SDA_DIR_OUT; |
| bb_ctrl |= (MCHP_I2C_SMB_BB_CL | MCHP_I2C_SMB_BB_DAT); |
| MCHP_I2C_SMB_BB_CTRL(ba) = bb_ctrl; |
| |
| /* Bus reset */ |
| cfg &= ~MCHP_I2C_SMB_CFG_RESET; |
| MCHP_I2C_SMB_CFG(ba) = cfg; |
| |
| /* Assert PIN bit, ESO = 0 and disables Interrupts (ENI) */ |
| ctrl = MCHP_I2C_SMB_CTRL_PIN; |
| MCHP_I2C_SMB_CTRL_WO(ba) = ctrl; |
| |
| /* Port selection */ |
| cfg |= (port_sel & MCHP_I2C_SMB_CFG_PORT_SEL_MASK); |
| MCHP_I2C_SMB_CFG(ba) = cfg; |
| |
| /* Enable controller and I2C filters*/ |
| cfg |= (MCHP_I2C_SMB_CFG_ENAB | MCHP_I2C_SMB_CFG_FEN); |
| MCHP_I2C_SMB_CFG(ba) = cfg; |
| |
| /* Set own address */ |
| MCHP_I2C_SMB_OWN_ADDR(ba) = 0x7F; |
| |
| /* Configure speed */ |
| MCHP_I2C_SMB_BUS_CLK(ba) = xec_cfg_params[speed_id].bus_clk; |
| MCHP_I2C_SMB_DATA_TM(ba) = xec_cfg_params[speed_id].data_timing; |
| MCHP_I2C_SMB_RSHT(ba) = xec_cfg_params[speed_id].start_hold_time; |
| MCHP_I2C_SMB_TMTSC(ba) = xec_cfg_params[speed_id].timeout_scale; |
| |
| ctrl |= (MCHP_I2C_SMB_CTRL_PIN | MCHP_I2C_SMB_CTRL_ESO | |
| MCHP_I2C_SMB_CTRL_ENI | MCHP_I2C_SMB_CTRL_ACK); |
| MCHP_I2C_SMB_CTRL_WO(ba) = ctrl; |
| |
| return 0; |
| } |
| |
| static int i2c_xec_poll_write(struct device *dev, struct i2c_msg msg, |
| u16_t addr) |
| { |
| const struct i2c_xec_config *config = |
| (const struct i2c_xec_config *const) (dev->config->config_info); |
| struct i2c_xec_data *data = |
| (struct i2c_xec_data *const) (dev->driver_data); |
| u32_t ba = config->base_addr; |
| int ret; |
| |
| if (data->pending_stop == 0) { |
| /* Check clock and data lines */ |
| if (check_lines(ba)) { |
| return -EBUSY; |
| } |
| |
| /* Wait until bus is free */ |
| ret = wait_bus_free(ba); |
| if (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(ba); |
| if (ret) { |
| return ret; |
| } |
| } |
| |
| /* Send bytes */ |
| for (int i = 0U; i < msg.len; i++) { |
| MCHP_I2C_SMB_DATA(ba) = msg.buf[i]; |
| ret = wait_completion(ba); |
| if (ret) { |
| return ret; |
| } |
| |
| /* Handle stop bit for last byte to write */ |
| if (i == (msg.len - 1)) { |
| 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(struct device *dev, struct i2c_msg msg, |
| u16_t addr) |
| { |
| const struct i2c_xec_config *config = |
| (const struct i2c_xec_config *const) (dev->config->config_info); |
| struct i2c_xec_data *data = |
| (struct i2c_xec_data *const) (dev->driver_data); |
| u32_t ba = config->base_addr; |
| u8_t byte, ctrl; |
| int ret; |
| |
| if (!(msg.flags & I2C_MSG_RESTART)) { |
| /* Check clock and data lines */ |
| if (check_lines(ba)) { |
| return -EBUSY; |
| } |
| |
| /* Wait until bus is free */ |
| ret = wait_bus_free(ba); |
| if (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(ba); |
| if (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); |
| ret = wait_completion(ba); |
| if (ret) { |
| return ret; |
| } |
| |
| for (int i = 0U; i < msg.len; i++) { |
| while (MCHP_I2C_SMB_STS_RO(ba) & MCHP_I2C_SMB_STS_PIN) { |
| if (MCHP_I2C_SMB_STS_RO(ba) & MCHP_I2C_SMB_STS_BER) { |
| return -EBUSY; |
| } |
| } |
| |
| 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(struct device *dev, struct i2c_msg *msgs, |
| u8_t num_msgs, u16_t addr) |
| { |
| int ret = 0; |
| |
| 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) { |
| return ret; |
| } |
| } else { |
| ret = i2c_xec_poll_read(dev, msgs[i], addr); |
| } |
| } |
| |
| return ret; |
| } |
| |
| #if defined(CONFIG_I2C_SLAVE) |
| static int i2c_xec_slave_register(struct device *dev) |
| { |
| return -ENOTSUP; |
| } |
| |
| static int i2c_xec_slave_unregister(struct device *dev) |
| { |
| return -ENOTSUP; |
| } |
| #endif |
| |
| static int i2c_xec_init(struct device *dev); |
| |
| static const struct i2c_driver_api i2c_xec_driver_api = { |
| .configure = i2c_xec_configure, |
| .transfer = i2c_xec_transfer, |
| #if defined(CONFIG_I2C_SLAVE) |
| .slave_register = i2c_xec_slave_register, |
| .slave_unregister = i2c_xec_slave_unregister, |
| #endif |
| }; |
| |
| #ifdef CONFIG_I2C_XEC_0 |
| static struct i2c_xec_data i2c_xec_data_0; |
| static const struct i2c_xec_config i2c_xec_config_0 = { |
| .base_addr = DT_INST_0_MICROCHIP_XEC_I2C_BASE_ADDRESS, |
| .port_sel = DT_INST_0_MICROCHIP_XEC_I2C_PORT_SEL, |
| }; |
| DEVICE_AND_API_INIT(i2c_xec_0, DT_INST_0_MICROCHIP_XEC_I2C_LABEL, |
| &i2c_xec_init, &i2c_xec_data_0, &i2c_xec_config_0, |
| POST_KERNEL, CONFIG_I2C_INIT_PRIORITY, |
| &i2c_xec_driver_api); |
| #endif /* CONFIG_I2C_XEC_0 */ |
| |
| #ifdef CONFIG_I2C_XEC_1 |
| static struct i2c_xec_data i2c_xec_data_1; |
| static const struct i2c_xec_config i2c_xec_config_1 = { |
| .base_addr = DT_INST_1_MICROCHIP_XEC_I2C_BASE_ADDRESS, |
| .port_sel = DT_INST_1_MICROCHIP_XEC_I2C_PORT_SEL, |
| }; |
| DEVICE_AND_API_INIT(i2c_xec_1, DT_INST_1_MICROCHIP_XEC_I2C_LABEL, |
| &i2c_xec_init, &i2c_xec_data_1, &i2c_xec_config_1, |
| POST_KERNEL, CONFIG_I2C_INIT_PRIORITY, |
| &i2c_xec_driver_api); |
| #endif /* CONFIG_I2C_XEC_1 */ |
| |
| #ifdef CONFIG_I2C_XEC_2 |
| static struct i2c_xec_data i2c_xec_data_2; |
| static const struct i2c_xec_config i2c_xec_config_2 = { |
| .base_addr = DT_INST_2_MICROCHIP_XEC_I2C_BASE_ADDRESS, |
| .port_sel = DT_INST_2_MICROCHIP_XEC_I2C_PORT_SEL, |
| }; |
| DEVICE_AND_API_INIT(i2c_xec_2, DT_INST_2_MICROCHIP_XEC_I2C_LABEL, |
| &i2c_xec_init, &i2c_xec_data_2, &i2c_xec_config_2, |
| POST_KERNEL, CONFIG_I2C_INIT_PRIORITY, |
| &i2c_xec_driver_api); |
| #endif /* CONFIG_I2C_XEC_2 */ |
| |
| static int i2c_xec_init(struct device *dev) |
| { |
| struct i2c_xec_data *data = |
| (struct i2c_xec_data *const) (dev->driver_data); |
| data->pending_stop = 0; |
| return 0; |
| } |