diff --git a/boards/arm/mec172xevb_assy6906/mec172xevb_assy6906.dts b/boards/arm/mec172xevb_assy6906/mec172xevb_assy6906.dts index 61d316cbb74..fb7c7231a30 100644 --- a/boards/arm/mec172xevb_assy6906/mec172xevb_assy6906.dts +++ b/boards/arm/mec172xevb_assy6906/mec172xevb_assy6906.dts @@ -21,6 +21,9 @@ aliases { led0 = &led4; led1 = &led3; + i2c0 = &i2c_smb_0; + i2c1 = &i2c_smb_1; + i2c7 = &i2c_smb_2; }; leds { @@ -67,3 +70,38 @@ &adc0 { status = "okay"; }; + +&i2c_smb_0 { + status = "okay"; + label = "I2C0"; + port_sel = <0>; + + pca9555@26 { + compatible = "nxp,pca95xx"; + label = "GPIO_P0"; + + /* Depends on JP53 for device address. + * Pin 1-2 = A0, pin 3-4 = A1, pin 5-6 = A2. + * Address is: 0100b. + * + * Default has pin 1-2 on JP53 connected, + * resulting in device address 0x26. + */ + reg = <0x26>; + + gpio-controller; + #gpio-cells = <2>; + }; +}; + +&i2c_smb_1 { + status = "okay"; + label = "I2C1"; + port_sel = <1>; +}; + +&i2c_smb_2 { + status = "okay"; + label = "I2C7"; + port_sel = <7>; +}; diff --git a/boards/arm/mec172xevb_assy6906/mec172xevb_assy6906.yaml b/boards/arm/mec172xevb_assy6906/mec172xevb_assy6906.yaml index fe7bb7e3c26..69e9d64ced3 100644 --- a/boards/arm/mec172xevb_assy6906/mec172xevb_assy6906.yaml +++ b/boards/arm/mec172xevb_assy6906/mec172xevb_assy6906.yaml @@ -16,3 +16,4 @@ flash: 352 supported: - gpio - pinmux + - i2c diff --git a/boards/arm/mec172xevb_assy6906/mec172xevb_assy6906_defconfig b/boards/arm/mec172xevb_assy6906/mec172xevb_assy6906_defconfig index 5ee01d77437..8c03840f5d9 100644 --- a/boards/arm/mec172xevb_assy6906/mec172xevb_assy6906_defconfig +++ b/boards/arm/mec172xevb_assy6906/mec172xevb_assy6906_defconfig @@ -16,3 +16,4 @@ CONFIG_SERIAL=y CONFIG_CONSOLE=y CONFIG_UART_CONSOLE=y CONFIG_ADC=y +CONFIG_I2C=y diff --git a/drivers/i2c/CMakeLists.txt b/drivers/i2c/CMakeLists.txt index da4c767f4ea..0c7b29b0589 100644 --- a/drivers/i2c/CMakeLists.txt +++ b/drivers/i2c/CMakeLists.txt @@ -34,6 +34,7 @@ zephyr_library_sources_ifdef(CONFIG_I2C_NPCX i2c_npcx_port.c) zephyr_library_sources_ifdef(CONFIG_I2C_DW i2c_dw.c) zephyr_library_sources_ifdef(CONFIG_I2C_RCAR i2c_rcar.c) zephyr_library_sources_ifdef(CONFIG_I2C_TCA9546A i2c_tca9546a.c) +zephyr_library_sources_ifdef(CONFIG_I2C_XEC_V2 i2c_mchp_xec_v2.c) zephyr_library_sources_ifdef(CONFIG_I2C_STM32_V1 i2c_ll_stm32_v1.c diff --git a/drivers/i2c/Kconfig.xec b/drivers/i2c/Kconfig.xec index 64242ba1e31..c21ac17635a 100644 --- a/drivers/i2c/Kconfig.xec +++ b/drivers/i2c/Kconfig.xec @@ -5,6 +5,12 @@ config I2C_XEC bool "XEC Microchip I2C driver" - depends on SOC_FAMILY_MEC + depends on SOC_SERIES_MEC1501X help Enable the Microchip XEC I2C driver. + +config I2C_XEC_V2 + bool "XEC Microchip I2C driver" + depends on SOC_SERIES_MEC172X + help + Enable the Microchip XEC I2C V2 driver. diff --git a/drivers/i2c/i2c_mchp_xec_v2.c b/drivers/i2c/i2c_mchp_xec_v2.c new file mode 100644 index 00000000000..f38bbd03709 --- /dev/null +++ b/drivers/i2c/i2c_mchp_xec_v2.c @@ -0,0 +1,1180 @@ +/* + * Copyright (c) 2019 Intel Corporation + * Copyright (c) 2021 Microchip Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT microchip_xec_i2c_v2 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +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 + +/* I2C timeout is 10 ms (WAIT_INTERVAL * WAIT_COUNT) */ +#define WAIT_INTERVAL 50 +#define WAIT_COUNT 200 +#define STOP_WAIT_COUNT 500 +#define PIN_CFG_WAIT 50 + +/* I2C Read/Write bit pos */ +#define I2C_READ_WRITE_POS 0 + +/* I2C recover SCL low retries */ +#define I2C_RECOVER_SCL_LOW_RETRIES 10 +/* I2C recover SDA low retries */ +#define I2C_RECOVER_SDA_LOW_RETRIES 3 +/* I2C recovery bit bang delay */ +#define I2C_RECOVER_BB_DELAY_US 5 +/* I2C recovery SCL sample delay */ +#define I2C_RECOVER_SCL_DELAY_US 50 + +/* I2C SCL and SDA lines(signals) */ +#define I2C_LINES_SCL_HI BIT(0) +#define I2C_LINES_SDA_HI BIT(1) +#define I2C_LINES_BOTH_HI (I2C_LINES_SCL_HI | I2C_LINES_SDA_HI) + +#define I2C_START 0U +#define I2C_RPT_START 1U + +#define I2C_ENI_DIS 0U +#define I2C_ENI_EN 1U + +#define I2C_WAIT_PIN_DEASSERT 0U +#define I2C_WAIT_PIN_ASSERT 1U + +#define I2C_XEC_CTRL_WR_DLY 8 + +#define I2C_XEC_STATE_STOPPED 1U +#define I2C_XEC_STATE_OPEN 2U + +#define I2C_XEC_OK 0 +#define I2C_XEC_ERR_LAB 1 +#define I2C_XEC_ERR_BUS 2 +#define I2C_XEC_ERR_TMOUT 3 + +#define XEC_GPIO_CTRL_BASE DT_REG_ADDR(DT_NODELABEL(gpio_000_036)) + +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; + uint8_t girq_pos; + uint8_t pcr_idx; + uint8_t pcr_bitpos; + void (*irq_config_func)(void); +}; + +struct i2c_xec_data { + uint8_t state; + uint8_t read_discard; + uint8_t speed_id; + struct i2c_slave_config *target_cfg; + bool target_attached; + bool target_read; + uint32_t i2c_compl; + uint8_t i2c_ctrl; + uint8_t i2c_addr; + uint8_t i2c_status; +}; + +/* 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, + }, +}; + +struct xec_i2c_port { + uint8_t scl_pin; + uint8_t scl_func; + uint8_t sda_pin; + uint8_t sda_func; +}; + +/* Indexed by port number */ +static const struct xec_i2c_port xec_i2c_ports[] = { + { 0004, 1, 0003, 1 }, + { 0131, 1, 0130, 1 }, + { 0155, 1, 0154, 1 }, + { 0010, 1, 0007, 1 }, + { 0144, 1, 0143, 1 }, + { 0142, 1, 0141, 1 }, + { 0140, 1, 0132, 1 }, + { 0013, 1, 0012, 1 }, +#ifdef CONFIG_SOC_SERIES_MEC172X + { 0230, 1, 0231, 1 }, +#else + { 0212, 1, 0211, 1 }, +#endif + { 0146, 1, 0145, 1 }, + { 0107, 3, 0030, 2 }, + { 0062, 2, 0000, 3 }, + { 0027, 3, 0026, 3 }, + { 0065, 2, 0066, 2 }, + { 0071, 2, 0070, 2 }, + { 0150, 1, 0147, 1 }, +}; + +/* returns b[0]=SCL pin state, b[1]=SDA pin state */ +static uint32_t xec_i2c_port_lines(uint8_t port) +{ + uintptr_t base = (uintptr_t)XEC_GPIO_CTRL_BASE; + uint32_t lines = 0; + + if (port < ARRAY_SIZE(xec_i2c_ports)) { + const struct xec_i2c_port *p = &xec_i2c_ports[port]; + uintptr_t ctrl_addr = base + p->scl_pin * 4; + + lines = (sys_read32(ctrl_addr) >> 24) & BIT(0); + ctrl_addr = base + p->scl_pin * 4; + lines |= ((sys_read32(ctrl_addr) >> 23) & BIT(1)); + } + + return lines; +} + +#define XEC_I2C_PIN_PRE_CFG1 \ + (MCHP_GPIO_CTRL_OUTV_HI | MCHP_GPIO_CTRL_MUX_GPIO | \ + MCHP_GPIO_CTRL_DIR_OUTPUT | MCHP_GPIO_CTRL_BUFT_OPENDRAIN | \ + MCHP_GPIO_CTRL_IDET_DISABLE | MCHP_GPIO_CTRL_PUD_NONE) + +static int xec_i2c_port_cfg(uint8_t port, uint8_t enable) +{ + if (port >= ARRAY_SIZE(xec_i2c_ports)) { + return -EINVAL; + } + + const struct xec_i2c_port *p = &xec_i2c_ports[port]; + uintptr_t scl_addr = (uintptr_t)XEC_GPIO_CTRL_BASE + p->scl_pin * 4; + uintptr_t sda_addr = (uintptr_t)XEC_GPIO_CTRL_BASE + p->sda_pin * 4; + + if (enable) { + sys_write32(XEC_I2C_PIN_PRE_CFG1, sda_addr); + sys_write32(XEC_I2C_PIN_PRE_CFG1, scl_addr); + + k_busy_wait(PIN_CFG_WAIT); + + sys_write32(sys_read32(sda_addr) | + ((uint32_t)(p->sda_func) << MCHP_GPIO_CTRL_MUX_POS), + sda_addr); + sys_write32(sys_read32(scl_addr) | + ((uint32_t)(p->scl_func) << MCHP_GPIO_CTRL_MUX_POS), + scl_addr); + } else { + sys_write32(MCHP_GPIO_CTRL_DIS_PIN, scl_addr); + sys_write32(MCHP_GPIO_CTRL_DIS_PIN, sda_addr); + } + + return 0; +} + +static void i2c_ctl_wr(const struct device *dev, uint8_t ctrl) +{ + const struct i2c_xec_config *cfg = + (const struct i2c_xec_config *const) (dev->config); + struct i2c_xec_data *data = + (struct i2c_xec_data *const) (dev->data); + struct i2c_smb_regs *regs = (struct i2c_smb_regs *)cfg->base_addr; + + data->i2c_ctrl = ctrl; + regs->CTRLSTS = ctrl; + for (int i = 0; i < I2C_XEC_CTRL_WR_DLY; i++) { + regs->BLKID = ctrl; + } +} + +static int i2c_xec_reset_config(const struct device *dev); + +static int wait_bus_free(const struct device *dev, uint32_t nwait) +{ + const struct i2c_xec_config *cfg = + (const struct i2c_xec_config *const) (dev->config); + struct i2c_xec_data *data = + (struct i2c_xec_data *const) (dev->data); + struct i2c_smb_regs *regs = (struct i2c_smb_regs *)cfg->base_addr; + uint32_t count = nwait; + uint8_t sts = 0; + + while (count--) { + sts = regs->CTRLSTS; + data->i2c_status = sts; + if (sts & MCHP_I2C_SMB_STS_NBB) { + break; /* bus is free */ + } + k_busy_wait(WAIT_INTERVAL); + } + + /* NBB -> 1 not busy can occur for STOP, BER, or LAB */ + if (sts == (MCHP_I2C_SMB_STS_PIN | MCHP_I2C_SMB_STS_NBB)) { + /* No service requested(PIN=1), NotBusy(NBB=1), and no errors */ + return 0; + } + + if (sts & MCHP_I2C_SMB_STS_BER) { + return I2C_XEC_ERR_BUS; + } + + if (sts & MCHP_I2C_SMB_STS_LAB) { + return I2C_XEC_ERR_LAB; + } + + return I2C_XEC_ERR_TMOUT; +} + +/* + * returns state of I2C SCL and SDA lines. + * b[0] = SCL, b[1] = SDA + */ +static uint32_t get_lines(const struct device *dev) +{ + const struct i2c_xec_config *cfg = + (const struct i2c_xec_config *const) (dev->config); + struct i2c_smb_regs *regs = (struct i2c_smb_regs *)cfg->base_addr; + uint8_t port = regs->CFG & MCHP_I2C_SMB_CFG_PORT_SEL_MASK; + + return xec_i2c_port_lines(port); +} + +static int i2c_xec_reset_config(const struct device *dev) +{ + const struct i2c_xec_config *cfg = + (const struct i2c_xec_config *const) (dev->config); + struct i2c_xec_data *data = + (struct i2c_xec_data *const) (dev->data); + struct i2c_smb_regs *regs = (struct i2c_smb_regs *)cfg->base_addr; + + data->state = I2C_XEC_STATE_STOPPED; + data->read_discard = 0; + + /* Assert RESET and clr others */ + regs->CFG = MCHP_I2C_SMB_CFG_RESET; + k_busy_wait(RESET_WAIT_US); + /* clear reset, set filter enable, select port */ + regs->CFG = 0; + regs->CFG = MCHP_I2C_SMB_CFG_FLUSH_SXBUF_WO | + MCHP_I2C_SMB_CFG_FLUSH_SRBUF_WO | + MCHP_I2C_SMB_CFG_FLUSH_MXBUF_WO | + MCHP_I2C_SMB_CFG_FLUSH_MRBUF_WO; + + mchp_xec_ecia_girq_src_clr(cfg->girq, cfg->girq_pos); + + /* PIN=1 to clear all status except NBB and synchronize */ + i2c_ctl_wr(dev, MCHP_I2C_SMB_CTRL_PIN); + + /* + * Controller implements two peripheral addresses for itself. + * It always monitors whether an external controller issues START + * plus target address. We should write valid peripheral addresses + * that do not match any peripheral on the bus. + * An alternative is to use the default 0 value which is the + * general call address and disable the general call match + * enable in the configuration register. + */ + regs->OWN_ADDR = EC_OWN_I2C_ADDR | (EC_OWN_I2C_ADDR << 8); +#ifdef CONFIG_I2C_SLAVE + if (data->target_cfg) { + regs->OWN_ADDR = data->target_cfg->address; + } +#endif + /* Port number and filter enable MUST be written before enabling */ + regs->CFG |= BIT(14); /* disable general call */ + regs->CFG |= MCHP_I2C_SMB_CFG_FEN; + regs->CFG |= (cfg->port_sel & MCHP_I2C_SMB_CFG_PORT_SEL_MASK); + + /* + * Before enabling the controller program the desired bus clock, + * repeated start hold time, data timing, and timeout scaling + * registers. + */ + regs->BUSCLK = xec_cfg_params[data->speed_id].bus_clk; + regs->RSHTM = xec_cfg_params[data->speed_id].start_hold_time; + regs->DATATM = xec_cfg_params[data->speed_id].data_timing; + regs->TMOUTSC = xec_cfg_params[data->speed_id].timeout_scale; + + /* + * PIN=1 clears all status except NBB + * ESO=1 enables output drivers + * ACK=1 enable ACK generation when data/address is clocked in. + */ + i2c_ctl_wr(dev, MCHP_I2C_SMB_CTRL_PIN | + MCHP_I2C_SMB_CTRL_ESO | + MCHP_I2C_SMB_CTRL_ACK); + + /* Enable controller */ + regs->CFG |= MCHP_I2C_SMB_CFG_ENAB; + k_busy_wait(RESET_WAIT_US); + + /* wait for NBB=1, BER, LAB, or timeout */ + int rc = wait_bus_free(dev, WAIT_COUNT); + + return rc; +} + +/* + * If SCL is low sample I2C_RECOVER_SCL_LOW_RETRIES times with a 5 us delay + * between samples. If SCL remains low then return -EBUSY + * If SCL is High and SDA is low then loop up to I2C_RECOVER_SDA_LOW_RETRIES + * times driving the pins: + * Drive SCL high + * delay I2C_RECOVER_BB_DELAY_US + * Generate 9 clock pulses on SCL checking SDA before each falling edge of SCL + * If SDA goes high exit clock loop else to all 9 clocks + * Drive SDA low, delay 5 us, release SDA, delay 5 us + * Both lines are high then exit SDA recovery loop + * Both lines should not be driven + * Check both lines: if any bad return error else return success + * NOTE 1: Bit-bang mode uses a HW MUX to switch the lines away from the I2C + * controller logic to BB logic. + * NOTE 2: Bit-bang mode requires HW timeouts to be disabled. + * NOTE 3: Bit-bang mode requires the controller's configuration enable bit + * to be set. + * NOTE 4: The controller must be reset after using bit-bang mode. + */ +static int i2c_xec_recover_bus(const struct device *dev) +{ + const struct i2c_xec_config *cfg = + (const struct i2c_xec_config *const) (dev->config); + struct i2c_smb_regs *regs = (struct i2c_smb_regs *)cfg->base_addr; + int i, j, ret; + + LOG_ERR("I2C attempt bus recovery\n"); + + /* reset controller to a known state */ + regs->CFG = MCHP_I2C_SMB_CFG_RESET; + k_busy_wait(RESET_WAIT_US); + regs->CFG = BIT(14) | MCHP_I2C_SMB_CFG_FEN | + (cfg->port_sel & MCHP_I2C_SMB_CFG_PORT_SEL_MASK); + regs->CFG |= MCHP_I2C_SMB_CFG_FLUSH_SXBUF_WO | + MCHP_I2C_SMB_CFG_FLUSH_SRBUF_WO | + MCHP_I2C_SMB_CFG_FLUSH_MXBUF_WO | + MCHP_I2C_SMB_CFG_FLUSH_MRBUF_WO; + regs->CTRLSTS = MCHP_I2C_SMB_CTRL_PIN; + regs->BBCTRL = MCHP_I2C_SMB_BB_EN | MCHP_I2C_SMB_BB_CL | + MCHP_I2C_SMB_BB_DAT; + regs->CFG |= MCHP_I2C_SMB_CFG_ENAB; + + if (!(regs->BBCTRL & MCHP_I2C_SMB_BB_CLKI_RO)) { + for (i = 0;; i++) { + if (i >= I2C_RECOVER_SCL_LOW_RETRIES) { + ret = -EBUSY; + goto recov_exit; + } + k_busy_wait(I2C_RECOVER_SCL_DELAY_US); + if (regs->BBCTRL & MCHP_I2C_SMB_BB_CLKI_RO) { + break; /* SCL went High */ + } + } + } + + if (regs->BBCTRL & MCHP_I2C_SMB_BB_DATI_RO) { + ret = 0; + goto recov_exit; + } + + ret = -EBUSY; + /* SDA recovery */ + for (i = 0; i < I2C_RECOVER_SDA_LOW_RETRIES; i++) { + /* SCL output mode and tri-stated */ + regs->BBCTRL = MCHP_I2C_SMB_BB_EN | + MCHP_I2C_SMB_BB_SCL_DIR_OUT | + MCHP_I2C_SMB_BB_CL | + MCHP_I2C_SMB_BB_DAT; + k_busy_wait(I2C_RECOVER_BB_DELAY_US); + + for (j = 0; j < 9; j++) { + if (regs->BBCTRL & MCHP_I2C_SMB_BB_DATI_RO) { + break; + } + /* drive SCL low */ + regs->BBCTRL = MCHP_I2C_SMB_BB_EN | + MCHP_I2C_SMB_BB_SCL_DIR_OUT | + MCHP_I2C_SMB_BB_DAT; + k_busy_wait(I2C_RECOVER_BB_DELAY_US); + /* release SCL: pulled high by external pull-up */ + regs->BBCTRL = MCHP_I2C_SMB_BB_EN | + MCHP_I2C_SMB_BB_SCL_DIR_OUT | + MCHP_I2C_SMB_BB_CL | + MCHP_I2C_SMB_BB_DAT; + k_busy_wait(I2C_RECOVER_BB_DELAY_US); + } + + /* SCL is High. Produce rising edge on SCL for STOP */ + regs->BBCTRL = MCHP_I2C_SMB_BB_EN | MCHP_I2C_SMB_BB_CL | + MCHP_I2C_SMB_BB_SDA_DIR_OUT; /* drive low */ + k_busy_wait(I2C_RECOVER_BB_DELAY_US); + regs->BBCTRL = MCHP_I2C_SMB_BB_EN | MCHP_I2C_SMB_BB_CL | + MCHP_I2C_SMB_BB_DAT; /* release SCL */ + k_busy_wait(I2C_RECOVER_BB_DELAY_US); + + /* check if SCL and SDA are both high */ + uint8_t bb = regs->BBCTRL & + (MCHP_I2C_SMB_BB_CLKI_RO | MCHP_I2C_SMB_BB_DATI_RO); + + if (bb == (MCHP_I2C_SMB_BB_CLKI_RO | MCHP_I2C_SMB_BB_DATI_RO)) { + ret = 0; /* successful recovery */ + goto recov_exit; + } + } + +recov_exit: + /* BB mode disable reconnects SCL and SDA to I2C logic. */ + regs->BBCTRL = 0; + regs->CTRLSTS = MCHP_I2C_SMB_CTRL_PIN; /* clear status */ + i2c_xec_reset_config(dev); /* reset controller */ + + return ret; +} + +#ifdef CONFIG_I2C_SLAVE +/* + * Restart I2C controller as target for ACK of address match. + * Setting PIN clears all status in I2C.Status register except NBB. + */ +static void restart_target(const struct device *dev) +{ + i2c_ctl_wr(dev, MCHP_I2C_SMB_CTRL_PIN | MCHP_I2C_SMB_CTRL_ESO | + MCHP_I2C_SMB_CTRL_ACK | MCHP_I2C_SMB_CTRL_ENI); +} + +/* + * Configure I2C controller acting as target to NACK the next received byte. + * NOTE: Firmware must re-enable ACK generation before the start of the next + * transaction otherwise the controller will NACK its target addresses. + */ +static void target_config_for_nack(const struct device *dev) +{ + i2c_ctl_wr(dev, MCHP_I2C_SMB_CTRL_PIN | MCHP_I2C_SMB_CTRL_ESO | + MCHP_I2C_SMB_CTRL_ENI); +} +#endif + +static int wait_pin(const struct device *dev, bool pin_assert, uint32_t nwait) +{ + const struct i2c_xec_config *cfg = + (const struct i2c_xec_config *const) (dev->config); + struct i2c_xec_data *data = + (struct i2c_xec_data *const) (dev->data); + struct i2c_smb_regs *regs = (struct i2c_smb_regs *)cfg->base_addr; + + for (;;) { + k_busy_wait(WAIT_INTERVAL); + + data->i2c_compl = regs->COMPL; + data->i2c_status = regs->CTRLSTS; + + if (data->i2c_status & MCHP_I2C_SMB_STS_BER) { + return I2C_XEC_ERR_BUS; + } + + if (data->i2c_status & MCHP_I2C_SMB_STS_LAB) { + return I2C_XEC_ERR_LAB; + } + + if (!(data->i2c_status & MCHP_I2C_SMB_STS_PIN)) { + if (pin_assert) { + return 0; + } + } else if (!pin_assert) { + return 0; + } + + if (nwait) { + --nwait; + } else { + break; + } + } + + return I2C_XEC_ERR_TMOUT; +} + +static int gen_start(const struct device *dev, uint8_t addr8, + bool is_repeated) +{ + const struct i2c_xec_config *cfg = + (const struct i2c_xec_config *const) (dev->config); + struct i2c_xec_data *data = + (struct i2c_xec_data *const) (dev->data); + struct i2c_smb_regs *regs = (struct i2c_smb_regs *)cfg->base_addr; + uint8_t ctrl = MCHP_I2C_SMB_CTRL_ESO | MCHP_I2C_SMB_CTRL_STA | + MCHP_I2C_SMB_CTRL_ACK; + + data->i2c_addr = addr8; + + if (is_repeated) { + i2c_ctl_wr(dev, ctrl); + regs->I2CDATA = addr8; + } else { + ctrl |= MCHP_I2C_SMB_CTRL_PIN; + regs->I2CDATA = addr8; + i2c_ctl_wr(dev, ctrl); + } + + return 0; +} + +static int gen_stop(const struct device *dev) +{ + const struct i2c_xec_config *cfg = + (const struct i2c_xec_config *const) (dev->config); + struct i2c_xec_data *data = + (struct i2c_xec_data *const) (dev->data); + struct i2c_smb_regs *regs = (struct i2c_smb_regs *)cfg->base_addr; + uint8_t ctrl = MCHP_I2C_SMB_CTRL_PIN | MCHP_I2C_SMB_CTRL_ESO | + MCHP_I2C_SMB_CTRL_STO | MCHP_I2C_SMB_CTRL_ACK; + + data->i2c_ctrl = ctrl; + regs->CTRLSTS = ctrl; + + return 0; +} + +static int do_stop(const struct device *dev, uint32_t nwait) +{ + const struct i2c_xec_config *cfg = + (const struct i2c_xec_config *const) (dev->config); + struct i2c_xec_data *data = + (struct i2c_xec_data *const) (dev->data); + struct i2c_smb_regs *regs = (struct i2c_smb_regs *)cfg->base_addr; + int ret; + + data->state = I2C_XEC_STATE_STOPPED; + data->read_discard = 0; + + gen_stop(dev); + ret = wait_bus_free(dev, nwait); + if (ret) { + uint32_t lines = get_lines(dev); + + if (lines != I2C_LINES_BOTH_HI) { + i2c_xec_recover_bus(dev); + } else { + ret = i2c_xec_reset_config(dev); + } + } + + if (ret == 0) { + /* stop success: prepare for next transaction */ + regs->CTRLSTS = MCHP_I2C_SMB_CTRL_PIN | MCHP_I2C_SMB_CTRL_ESO | + MCHP_I2C_SMB_CTRL_ACK; + } + + return ret; +} + +static int do_start(const struct device *dev, uint8_t addr8, bool is_repeated) +{ + struct i2c_xec_data *data = + (struct i2c_xec_data *const) (dev->data); + int ret; + + gen_start(dev, addr8, is_repeated); + ret = wait_pin(dev, I2C_WAIT_PIN_ASSERT, WAIT_COUNT); + if (ret) { + i2c_xec_reset_config(dev); + return ret; + } + + /* PIN 1->0: check for NACK */ + if (data->i2c_status & MCHP_I2C_SMB_STS_LRB_AD0) { + gen_stop(dev); + ret = wait_bus_free(dev, WAIT_COUNT); + if (ret) { + i2c_xec_reset_config(dev); + } + return -EIO; + } + + return 0; +} + +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_MASTER)) { + 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; + } + + int ret = i2c_xec_reset_config(dev); + + return ret; +} + +/* I2C Controller transmit: polling implementation */ +static int ctrl_tx(const struct device *dev, struct i2c_msg *msg, uint16_t addr) +{ + const struct i2c_xec_config *cfg = + (const struct i2c_xec_config *const) (dev->config); + struct i2c_xec_data *data = + (struct i2c_xec_data *const) (dev->data); + struct i2c_smb_regs *regs = (struct i2c_smb_regs *)cfg->base_addr; + int ret = 0; + uint8_t mflags = msg->flags; + uint8_t addr8 = (uint8_t)((addr & 0x7FU) << 1); + + if (data->state == I2C_XEC_STATE_STOPPED) { + data->i2c_addr = addr8; + /* Is bus free and controller ready? */ + ret = wait_bus_free(dev, WAIT_COUNT); + if (ret) { + ret = i2c_xec_recover_bus(dev); + if (ret) { + return ret; + } + } + + ret = do_start(dev, addr8, I2C_START); + if (ret) { + return ret; + } + + data->state = I2C_XEC_STATE_OPEN; + + } else if (mflags & I2C_MSG_RESTART) { + data->i2c_addr = addr8; + ret = do_start(dev, addr8, I2C_RPT_START); + if (ret) { + return ret; + } + } + + for (size_t n = 0; n < msg->len; n++) { + regs->I2CDATA = msg->buf[n]; + ret = wait_pin(dev, I2C_WAIT_PIN_ASSERT, WAIT_COUNT); + if (ret) { + i2c_xec_reset_config(dev); + return ret; + } + if (data->i2c_status & MCHP_I2C_SMB_STS_LRB_AD0) { /* NACK? */ + do_stop(dev, STOP_WAIT_COUNT); + return -EIO; + } + } + + if (mflags & I2C_MSG_STOP) { + ret = do_stop(dev, STOP_WAIT_COUNT); + } + + return ret; +} + +/* + * I2C Controller receive: polling implementation + * Transmitting a target address with BIT[0] == 1 causes the controller + * to enter controller-read mode where every read of I2CDATA generates + * clocks for the next byte. When we generate START or Repeated-START + * and transmit an address the address is also clocked in during + * address transmission. The address must read and discarded. + * Read of I2CDATA returns data currently in I2C read buffer, sets + * I2CSTATUS.PIN = 1, and !!generates clocks for the next + * byte!! + * For this controller to NACK the last byte we must clear the + * I2C CTRL register ACK bit BEFORE reading the next to last + * byte. Before reading the last byte we configure I2C CTRL to generate a STOP + * and then read the last byte from I2 DATA. + * When controller is in STOP mode it will not generate clocks when I2CDATA is + * read. UGLY HW DESIGN. + * We will NOT attempt to follow this HW design for Controller read except + * when all information is available: STOP message flag set AND number of + * bytes to read including dummy is >= 2. General usage can result in the + * controller not NACK'ing the last byte. + */ +static int ctrl_rx(const struct device *dev, struct i2c_msg *msg, uint16_t addr) +{ + const struct i2c_xec_config *cfg = + (const struct i2c_xec_config *const) (dev->config); + struct i2c_xec_data *data = + (struct i2c_xec_data *const) (dev->data); + struct i2c_smb_regs *regs = (struct i2c_smb_regs *)cfg->base_addr; + int ret = 0; + size_t data_len = msg->len; + uint8_t mflags = msg->flags; + uint8_t addr8 = (uint8_t)(((addr & 0x7FU) << 1) | BIT(0)); + uint8_t temp = 0; + + if (data->state == I2C_XEC_STATE_STOPPED) { + data->i2c_addr = addr8; + /* Is bus free and controller ready? */ + ret = wait_bus_free(dev, WAIT_COUNT); + if (ret) { + i2c_xec_reset_config(dev); + return ret; + } + + ret = do_start(dev, addr8, I2C_START); + if (ret) { + return ret; + } + + data->state = I2C_XEC_STATE_OPEN; + + /* controller clocked address into I2CDATA */ + data->read_discard = 1U; + + } else if (mflags & I2C_MSG_RESTART) { + data->i2c_addr = addr8; + ret = do_start(dev, addr8, I2C_RPT_START); + if (ret) { + return ret; + } + + /* controller clocked address into I2CDATA */ + data->read_discard = 1U; + } + + if (!data_len) { /* requested message length is 0 */ + ret = 0; + if (mflags & I2C_MSG_STOP) { + data->state = I2C_XEC_STATE_STOPPED; + data->read_discard = 0; + ret = do_stop(dev, STOP_WAIT_COUNT); + } + return ret; + } + + if (data->read_discard) { + data_len++; + } + + uint8_t *p8 = &msg->buf[0]; + + while (data_len) { + if (mflags & I2C_MSG_STOP) { + if (data_len == 2) { + i2c_ctl_wr(dev, MCHP_I2C_SMB_CTRL_ESO); + } else if (data_len == 1) { + break; + } + } + temp = regs->I2CDATA; /* generates clocks */ + if (data->read_discard) { + data->read_discard = 0; + } else { + *p8++ = temp; + } + ret = wait_pin(dev, I2C_WAIT_PIN_ASSERT, WAIT_COUNT); + if (ret) { + i2c_xec_reset_config(dev); + return ret; + } + data_len--; + } + + if (mflags & I2C_MSG_STOP) { + data->state = I2C_XEC_STATE_STOPPED; + data->read_discard = 0; + ret = do_stop(dev, STOP_WAIT_COUNT); + if (ret == 0) { + *p8 = regs->I2CDATA; + } + } + + return ret; +} + +static int i2c_xec_transfer(const struct device *dev, struct i2c_msg *msgs, + uint8_t num_msgs, uint16_t addr) +{ + struct i2c_xec_data *data = dev->data; + int ret = 0; + +#ifdef CONFIG_I2C_SLAVE + if (data->target_attached) { + LOG_ERR("Device is registered as target"); + return -EBUSY; + } +#endif + + for (uint8_t i = 0; i < num_msgs; i++) { + struct i2c_msg *m = &msgs[i]; + + if ((m->flags & I2C_MSG_RW_MASK) == I2C_MSG_WRITE) { + ret = ctrl_tx(dev, m, addr); + } else { + ret = ctrl_rx(dev, m, addr); + } + if (ret) { + data->state = I2C_XEC_STATE_STOPPED; + data->read_discard = 0; + LOG_ERR("i2x_xfr: flags: %x error: %d", m->flags, ret); + break; + } + } + + return ret; +} + +static void i2c_xec_bus_isr(void *arg) +{ +#ifdef CONFIG_I2C_SLAVE + struct device *dev = (struct device *)arg; + const struct i2c_xec_config *cfg = + (const struct i2c_xec_config *const) (dev->config); + struct i2c_xec_data *data = dev->data; + const struct i2c_slave_callbacks *target_cb = + data->target_cfg->callbacks; + struct i2c_smb_regs *regs = (struct i2c_smb_regs *)cfg->base_addr; + + int ret; + uint32_t status; + uint32_t compl_status; + uint8_t val; + + uint8_t dummy = 0U; + + /* Get current status */ + status = regs->CTRLSTS; + compl_status = regs->COMPL & MCHP_I2C_SMB_CMPL_RW1C_MASK; + + /* Idle interrupt enabled and active? */ + if ((regs->CFG & MCHP_I2C_SMB_CFG_ENIDI) && + (compl_status & MCHP_I2C_SMB_CMPL_IDLE_RWC)) { + regs->CFG &= ~MCHP_I2C_SMB_CFG_ENIDI; + if (status & MCHP_I2C_SMB_STS_NBB) { + restart_target(dev); + goto clear_iag; + } + } + + if (!data->target_attached) { + goto clear_iag; + } + + /* Bus Error */ + if (status & MCHP_I2C_SMB_STS_BER) { + if (target_cb->stop) { + target_cb->stop(data->target_cfg); + } + restart_target(dev); + goto clear_iag; + } + + /* External stop */ + if (status & MCHP_I2C_SMB_STS_EXT_STOP) { + if (target_cb->stop) { + target_cb->stop(data->target_cfg); + } + restart_target(dev); + goto clear_iag; + } + + /* Address byte handling */ + if (status & MCHP_I2C_SMB_STS_AAS) { + if (status & MCHP_I2C_SMB_STS_PIN) { + goto clear_iag; + } + + uint8_t rx_data = regs->I2CDATA; + + if (rx_data & BIT(I2C_READ_WRITE_POS)) { + /* target transmitter mode */ + data->target_read = true; + val = dummy; + if (target_cb->read_requested) { + target_cb->read_requested( + data->target_cfg, &val); + + /* Application target transmit handler + * does not have data to send. In + * target transmit mode the external + * Controller is ACK's data we send. + * All we can do is keep sending dummy + * data. We assume read_requested does + * not modify the value pointed to by val + * if it has not data(returns error). + */ + } + /* + * Writing I2CData causes this HW to release SCL + * ending clock stretching. The external Controller + * senses SCL released and begins generating clocks + * and capturing data driven by this controller + * on SDA. External Controller ACK's data until it + * wants no more then it will NACK. + */ + regs->I2CDATA = val; + goto clear_iag; /* Exit ISR */ + } else { + /* target receiver mode */ + data->target_read = false; + if (target_cb->write_requested) { + ret = target_cb->write_requested( + data->target_cfg); + if (ret) { + /* + * Application handler can't accept + * data. Configure HW to NACK next + * data transmitted by external + * Controller. + * !!! TODO We must re-program our HW + * for address ACK before next + * transaction is begun !!! + */ + target_config_for_nack(dev); + } + } + goto clear_iag; /* Exit ISR */ + } + } + + if (data->target_read) { /* Target transmitter mode */ + + /* Master has Nacked, then just write a dummy byte */ + status = regs->CTRLSTS; + if (status & MCHP_I2C_SMB_STS_LRB_AD0) { + + /* + * ISSUE: HW will not detect external STOP in + * target transmit mode. Enable IDLE interrupt + * to catch PIN 0 -> 1 and NBB 0 -> 1. + */ + regs->CFG |= MCHP_I2C_SMB_CFG_ENIDI; + + /* + * dummy write causes this controller's PIN status + * to de-assert 0 -> 1. Data is not transmitted. + * SCL is not driven low by this controller. + */ + regs->I2CDATA = dummy; + + status = regs->CTRLSTS; + + } else { + val = dummy; + if (target_cb->read_processed) { + target_cb->read_processed( + data->target_cfg, &val); + } + regs->I2CDATA = val; + } + } else { /* target receiver mode */ + /* + * Reading the I2CData register causes this target to release + * SCL. The external Controller senses SCL released generates + * clocks for transmitting the next data byte. + * Reading I2C Data register causes PIN status 0 -> 1. + */ + val = regs->I2CDATA; + if (target_cb->write_received) { + /* + * Call back returns error if we should NACK + * next byte. + */ + ret = target_cb->write_received(data->target_cfg, val); + if (ret) { + /* + * Configure HW to NACK next byte. It will not + * generate clocks for another byte of data + */ + target_config_for_nack(dev); + } + } + } + +clear_iag: + regs->COMPL = compl_status; + mchp_xec_ecia_girq_src_clr(cfg->girq, cfg->girq_pos); +#endif +} + +#ifdef CONFIG_I2C_SLAVE +static int i2c_xec_target_register(const struct device *dev, + struct i2c_slave_config *config) +{ + const struct i2c_xec_config *cfg = dev->config; + struct i2c_xec_data *data = dev->data; + int ret; + + if (!config) { + return -EINVAL; + } + + if (data->target_attached) { + return -EBUSY; + } + + /* Wait for any outstanding transactions to complete so that + * the bus is free + */ + ret = wait_bus_free(dev, WAIT_COUNT); + if (ret) { + return ret; + } + + data->target_cfg = config; + + ret = i2c_xec_reset_config(dev); + if (ret) { + return ret; + } + + restart_target(dev); + + data->target_attached = true; + + /* Clear before enabling girq bit */ + mchp_xec_ecia_girq_src_clr(cfg->girq, cfg->girq_pos); + mchp_xec_ecia_girq_src_en(cfg->girq, cfg->girq_pos); + + return 0; +} + +static int i2c_xec_target_unregister(const struct device *dev, + struct i2c_slave_config *config) +{ + const struct i2c_xec_config *cfg = dev->config; + struct i2c_xec_data *data = dev->data; + + if (!data->target_attached) { + return -EINVAL; + } + + data->target_cfg = NULL; + data->target_attached = false; + + mchp_xec_ecia_girq_src_dis(cfg->girq, cfg->girq_pos); + + return 0; +} +#endif + +static const struct i2c_driver_api i2c_xec_driver_api = { + .configure = i2c_xec_configure, + .transfer = i2c_xec_transfer, +#ifdef CONFIG_I2C_SLAVE + .slave_register = i2c_xec_target_register, + .slave_unregister = i2c_xec_target_unregister, +#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->state = I2C_XEC_STATE_STOPPED; + data->target_cfg = NULL; + data->target_attached = false; + + ret = xec_i2c_port_cfg(cfg->port_sel, 1); + if (ret) { + return ret; + } + + /* Default configuration */ + ret = i2c_xec_configure(dev, + I2C_MODE_MASTER | + I2C_SPEED_SET(I2C_SPEED_STANDARD)); + if (ret) { + return ret; + } + +#ifdef CONFIG_I2C_SLAVE + 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) \ + 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 = DT_INST_PROP_BY_IDX(n, girqs, 0), \ + .girq_pos = DT_INST_PROP_BY_IDX(n, girqs, 1), \ + .pcr_idx = DT_INST_PROP_BY_IDX(n, pcrs, 0), \ + .pcr_bitpos = DT_INST_PROP_BY_IDX(n, pcrs, 1), \ + .irq_config_func = i2c_xec_irq_config_func_##n, \ + }; \ + 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) diff --git a/dts/arm/microchip/mec172xnsz.dtsi b/dts/arm/microchip/mec172xnsz.dtsi index 0b6449afa0f..f19e95cc4a6 100644 --- a/dts/arm/microchip/mec172xnsz.dtsi +++ b/dts/arm/microchip/mec172xnsz.dtsi @@ -533,6 +533,7 @@ status = "disabled"; }; i2c_smb_0: i2c@40004000 { + compatible = "microchip,xec-i2c-v2"; reg = <0x40004000 0x80>; clock-frequency = ; interrupts = <20 1>; @@ -544,6 +545,7 @@ status = "disabled"; }; i2c_smb_1: i2c@40004400 { + compatible = "microchip,xec-i2c-v2"; reg = <0x40004400 0x80>; clock-frequency = ; interrupts = <21 1>; @@ -555,6 +557,7 @@ status = "disabled"; }; i2c_smb_2: i2c@40004800 { + compatible = "microchip,xec-i2c-v2"; reg = <0x40004800 0x80>; clock-frequency = ; interrupts = <22 1>; @@ -566,6 +569,7 @@ status = "disabled"; }; i2c_smb_3: i2c@40004c00 { + compatible = "microchip,xec-i2c-v2"; reg = <0x40004C00 0x80>; clock-frequency = ; interrupts = <23 1>; @@ -577,6 +581,7 @@ status = "disabled"; }; i2c_smb_4: i2c@40005000 { + compatible = "microchip,xec-i2c-v2"; reg = <0x40005000 0x80>; clock-frequency = ; interrupts = <158 1>; diff --git a/dts/bindings/i2c/microchip,xec-i2c-v2.yaml b/dts/bindings/i2c/microchip,xec-i2c-v2.yaml new file mode 100644 index 00000000000..1934fe2dbb8 --- /dev/null +++ b/dts/bindings/i2c/microchip,xec-i2c-v2.yaml @@ -0,0 +1,43 @@ +# Copyright (c) 2019 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +description: Microchip I2C/SMB V2 controller + +compatible: "microchip,xec-i2c-v2" + +include: i2c-controller.yaml + +properties: + reg: + required: true + + port_sel: + type: int + description: soc block mapping to pin + required: true + + girqs: + type: array + required: true + description: array of GIRQ numbers [8:26] and bit positions [0:31] + + pcrs: + type: array + required: true + description: PCR sleep register index and bit position + + "#girq-cells": + type: int + const: 2 + + "pcr-cells": + type: int + const: 2 + +girq-cells: + - girq_num + - bitpos + +pcr-cells: + - regidx + - bitpos diff --git a/soc/arm/microchip_mec/mec172x/Kconfig.defconfig.mec172xnsz b/soc/arm/microchip_mec/mec172x/Kconfig.defconfig.mec172xnsz index 1f5aebd1735..29b21eff03c 100644 --- a/soc/arm/microchip_mec/mec172x/Kconfig.defconfig.mec172xnsz +++ b/soc/arm/microchip_mec/mec172x/Kconfig.defconfig.mec172xnsz @@ -27,4 +27,8 @@ config ADC_XEC_V2 default y depends on ADC +config I2C_XEC_V2 + default y + depends on I2C + endif # SOC_MEC172X_NSZ