/* * Copyright (c) 2019 Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #define SPEED_100KHZ_BUS 0 #define SPEED_400KHZ_BUS 1 #define SPEED_1MHZ_BUS 2 #define WAIT_INTERVAL 5 #define TIMEOUT 20 /* 100 us */ #define MAX_CLK_STRETCHING 5 /* 25 us */ 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 int wait_completion(u32_t ba) { int ret; int counter = 0; while (MCHP_I2C_SMB_STS_RO(ba) & MCHP_I2C_SMB_STS_PIN) { ret = xec_spin_yield(&counter); if (ret < 0) { return ret; } } if (MCHP_I2C_SMB_STS_RO(ba) & MCHP_I2C_SMB_STS_LRB_AD0) { return -EINVAL; } if (MCHP_I2C_SMB_STS_RO(ba) & MCHP_I2C_SMB_STS_BER) { 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 counter = 0; int ret; if (data->pending_stop == 0) { /* Check clock and data lines */ if (check_lines(ba)) { return -EBUSY; } /* Wait until bus is not busy */ counter = 0; while (!(MCHP_I2C_SMB_STS_RO(ba) & MCHP_I2C_SMB_STS_NBB)) { ret = xec_spin_yield(&counter); if (ret < 0) { 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; /* Send slave address */ MCHP_I2C_SMB_DATA(ba) = (addr | BIT(0)); MCHP_I2C_SMB_CTRL_WO(ba) = MCHP_I2C_SMB_CTRL_ESO | MCHP_I2C_SMB_CTRL_STA | MCHP_I2C_SMB_CTRL_ACK; 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_STA; } /* 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; } } /* Send NACK for last transaction */ else if (i == (msg.len - 2)) { MCHP_I2C_SMB_CTRL_WO(ba) = MCHP_I2C_SMB_CTRL_STA; } msg.buf[i] = MCHP_I2C_SMB_DATA(ba); } /* Check clock and data lines */ if (check_lines(ba)) { return -EBUSY; } 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); } 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; }