/* Copyright 2019 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ /* I2C module for Chrome EC */ #include "clock.h" #include "compile_time_macros.h" #include "console.h" #include "gpio.h" #include "hooks.h" #include "i2c_peripheral.h" #include "registers.h" #include #include #include "task.h" /* Console output macros */ #define CPRINTS(format, args...) cprints(CC_I2C, format, ## args) /* The size must be a power of 2 */ #define I2C_MAX_BUFFER_SIZE 0x100 #define I2C_SIZE_MASK (I2C_MAX_BUFFER_SIZE - 1) #define I2C_READ_MAXFIFO_DATA 16 #define I2C_ENHANCED_CH_INTERVAL 0x80 /* Store controller to peripheral data of channel D, E, F by DMA */ static uint8_t in_data[I2C_ENHANCED_PORT_COUNT][I2C_MAX_BUFFER_SIZE] __attribute__((section(".h2ram.pool.i2cslv"))); /* Store peripheral to controller data of channel D, E, F by DMA */ static uint8_t out_data[I2C_ENHANCED_PORT_COUNT][I2C_MAX_BUFFER_SIZE] __attribute__((section(".h2ram.pool.i2cslv"))); /* Store read and write data of channel A by FIFO mode */ static uint8_t pbuffer[I2C_MAX_BUFFER_SIZE]; static uint32_t w_index; static uint32_t r_index; static int wr_done[I2C_ENHANCED_PORT_COUNT]; void buffer_index_reset(void) { /* Reset write buffer index */ w_index = 0; /* Reset read buffer index */ r_index = 0; } /* Data structure to define I2C peripheral control configuration. */ struct i2c_periph_ctrl_t { int irq; /* peripheral irq */ /* offset from base 0x00F03500 register; -1 means unused. */ int offset; enum clock_gate_offsets clock_gate; int dma_index; }; /* I2C peripheral control */ const struct i2c_periph_ctrl_t i2c_periph_ctrl[] = { [IT83XX_I2C_CH_A] = {.irq = IT83XX_IRQ_SMB_A, .offset = -1, .clock_gate = CGC_OFFSET_SMBA, .dma_index = -1}, [IT83XX_I2C_CH_D] = {.irq = IT83XX_IRQ_SMB_D, .offset = 0x180, .clock_gate = CGC_OFFSET_SMBD, .dma_index = 0}, [IT83XX_I2C_CH_E] = {.irq = IT83XX_IRQ_SMB_E, .offset = 0x0, .clock_gate = CGC_OFFSET_SMBE, .dma_index = 1}, [IT83XX_I2C_CH_F] = {.irq = IT83XX_IRQ_SMB_F, .offset = 0x80, .clock_gate = CGC_OFFSET_SMBF, .dma_index = 2}, }; void i2c_peripheral_read_write_data(int port) { int periph_status, i; /* I2C peripheral channel A FIFO mode */ if (port < I2C_STANDARD_PORT_COUNT) { int count; periph_status = IT83XX_SMB_SLSTA; /* bit0-4 : FIFO byte count */ count = IT83XX_SMB_SFFSTA & 0x1F; /* Peripheral data register is waiting for read or write. */ if (periph_status & IT83XX_SMB_SDS) { /* Controller to read data */ if (periph_status & IT83XX_SMB_RCS) { for (i = 0; i < I2C_READ_MAXFIFO_DATA; i++) /* Return buffer data to controller */ IT83XX_SMB_SLDA = pbuffer[(i + r_index) & I2C_SIZE_MASK]; /* Index to next 16 bytes of read buffer */ r_index += I2C_READ_MAXFIFO_DATA; } /* Controller to write data */ else { /* FIFO Full */ if (IT83XX_SMB_SFFSTA & IT83XX_SMB_SFFFULL) { for (i = 0; i < count; i++) /* Get data from controller to buffer */ pbuffer[(w_index + i) & I2C_SIZE_MASK] = IT83XX_SMB_SLDA; } /* Index to next byte of write buffer */ w_index += count; } } /* Stop condition, indicate stop condition detected. */ if (periph_status & IT83XX_SMB_SPDS) { /* Read data less 16 bytes status */ if (periph_status & IT83XX_SMB_RCS) { /* Disable FIFO mode to clear left count */ IT83XX_SMB_SFFCTL &= ~IT83XX_SMB_SAFE; /* Peripheral A FIFO Enable */ IT83XX_SMB_SFFCTL |= IT83XX_SMB_SAFE; } /* Controller to write data */ else { for (i = 0; i < count; i++) /* Get data from controller to buffer */ pbuffer[(i + w_index) & I2C_SIZE_MASK] = IT83XX_SMB_SLDA; } /* Reset read and write buffer index */ buffer_index_reset(); } /* Peripheral time status, timeout status occurs. */ if (periph_status & IT83XX_SMB_STS) { /* Reset read and write buffer index */ buffer_index_reset(); } /* Write clear the peripheral status */ IT83XX_SMB_SLSTA = periph_status; } /* Enhanced I2C peripheral channel D, E, F DMA mode */ else { int ch, idx; /* Get enhanced i2c channel */ ch = i2c_periph_ctrl[port].offset / I2C_ENHANCED_CH_INTERVAL; idx = i2c_periph_ctrl[port].dma_index; /* Interrupt pending */ if (IT83XX_I2C_STR(ch) & IT83XX_I2C_INTPEND) { periph_status = IT83XX_I2C_IRQ_ST(ch); /* Controller to read data */ if (periph_status & IT83XX_I2C_IDR_CLR) { /* * TODO(b:129360157): Return buffer data by * "out_data" array. * Ex: Write data to buffer from 0x00 to 0xFF */ for (i = 0; i < I2C_MAX_BUFFER_SIZE; i++) out_data[idx][i] = i; } /* Controller to write data */ if (periph_status & IT83XX_I2C_IDW_CLR) { /* Controller to write data finish flag */ wr_done[idx] = 1; } /* Peripheral finish */ if (periph_status & IT83XX_I2C_P_CLR) { if (wr_done[idx]) { /* * TODO(b:129360157): Handle controller write * data by "in_data" array. */ CPRINTS("WData: %ph", HEX_BUF(in_data[idx], I2C_MAX_BUFFER_SIZE)); wr_done[idx] = 0; } } /* Write clear the peripheral status */ IT83XX_I2C_IRQ_ST(ch) = periph_status; } /* Hardware reset */ IT83XX_I2C_CTR(ch) |= IT83XX_I2C_HALT; } } void i2c_periph_interrupt(int port) { /* Peripheral to read and write fifo data */ i2c_peripheral_read_write_data(port); /* Clear the interrupt status */ task_clear_pending_irq(i2c_periph_ctrl[port].irq); } void i2c_peripheral_enable(int port, uint8_t periph_addr) { clock_enable_peripheral(i2c_periph_ctrl[port].clock_gate, 0, 0); /* I2C peripheral channel A FIFO mode */ if (port < I2C_STANDARD_PORT_COUNT) { /* This field defines the SMCLK0/1/2 clock/data low timeout. */ IT83XX_SMB_25MS = I2C_CLK_LOW_TIMEOUT; /* bit0 : Peripheral A FIFO Enable */ IT83XX_SMB_SFFCTL |= IT83XX_SMB_SAFE; /* * bit1 : Peripheral interrupt enable. * bit2 : SMCLK/SMDAT will be released if timeout. * bit3 : Peripheral detect STOP condition interrupt enable. */ IT83XX_SMB_SICR = 0x0E; /* Peripheral address 1 */ IT83XX_SMB_RESLADR = periph_addr; /* Write clear all peripheral status */ IT83XX_SMB_SLSTA = 0xE7; /* bit5 : Enable the SMBus peripheral device */ IT83XX_SMB_HOCTL2(port) |= IT83XX_SMB_SLVEN; } /* Enhanced I2C peripheral channel D, E, F DMA mode */ else { int ch, idx; uint32_t in_data_addr, out_data_addr; /* Get enhanced i2c channel */ ch = i2c_periph_ctrl[port].offset / I2C_ENHANCED_CH_INTERVAL; idx = i2c_periph_ctrl[port].dma_index; switch (port) { case IT83XX_I2C_CH_D: /* Enable I2C D channel */ IT83XX_GPIO_GRC2 |= (1 << 5); break; case IT83XX_I2C_CH_E: /* Enable I2C E channel */ IT83XX_GCTRL_PMER1 |= (1 << 0); break; case IT83XX_I2C_CH_F: /* Enable I2C F channel */ IT83XX_GCTRL_PMER1 |= (1 << 1); break; } /* Software reset */ IT83XX_I2C_DHTR(ch) |= (1 << 7); IT83XX_I2C_DHTR(ch) &= ~(1 << 7); /* This field defines the SMCLK3/4/5 clock/data low timeout. */ IT83XX_I2C_TOR(ch) = I2C_CLK_LOW_TIMEOUT; /* Bit stretching */ IT83XX_I2C_TOS(ch) |= IT83XX_I2C_CLK_STR; /* Peripheral address(8-bit)*/ IT83XX_I2C_IDR(ch) = periph_addr << 1; /* I2C interrupt enable and set acknowledge */ IT83XX_I2C_CTR(ch) = IT83XX_I2C_HALT | IT83XX_I2C_INTEN | IT83XX_I2C_ACK; /* * bit3 : Peripheral ID write flag * bit2 : Peripheral ID read flag * bit1 : Peripheral received data flag * bit0 : Peripheral finish */ IT83XX_I2C_IRQ_ST(ch) = 0xFF; /* Clear read and write data buffer of DMA */ memset(in_data[idx], 0, I2C_MAX_BUFFER_SIZE); memset(out_data[idx], 0, I2C_MAX_BUFFER_SIZE); if (IS_ENABLED(CHIP_ILM_DLM_ORDER)) { in_data_addr = (uint32_t)in_data[idx] & 0xffffff; out_data_addr = (uint32_t)out_data[idx] & 0xffffff; } else { in_data_addr = (uint32_t)in_data[idx] & 0xfff; out_data_addr = (uint32_t)out_data[idx] & 0xfff; } /* DMA write target address register */ IT83XX_I2C_RAMHA(ch) = in_data_addr >> 8; IT83XX_I2C_RAMLA(ch) = in_data_addr; if (IS_ENABLED(CHIP_ILM_DLM_ORDER)) { /* * DMA write target address register * for high order byte */ IT83XX_I2C_RAMH2A(ch) = in_data_addr >> 16; /* * DMA read target address register * for high order byte */ IT83XX_I2C_CMD_ADDH2(ch) = out_data_addr >> 16; IT83XX_I2C_CMD_ADDH(ch) = out_data_addr >> 8; IT83XX_I2C_CMD_ADDL(ch) = out_data_addr; } else { /* DMA read target address register */ IT83XX_I2C_RAMHA2(ch) = out_data_addr >> 8; IT83XX_I2C_RAMLA2(ch) = out_data_addr; } /* I2C module enable and command queue mode */ IT83XX_I2C_CTR1(ch) = IT83XX_I2C_COMQ_EN | IT83XX_I2C_MDL_EN; } } static void i2c_peripheral_init(void) { int i, p; /* DLM 52k~56k size select enable */ IT83XX_GCTRL_MCCR2 |= (1 << 4); /* Enable I2C Peripheral function */ for (i = 0; i < i2c_periphs_used; i++) { /* I2c peripheral port mapping. */ p = i2c_periph_ports[i].port; /* To enable peripheral ch[x] */ i2c_peripheral_enable(p, i2c_periph_ports[i].addr); /* Clear the interrupt status */ task_clear_pending_irq(i2c_periph_ctrl[p].irq); /* enable i2c interrupt */ task_enable_irq(i2c_periph_ctrl[p].irq); } } DECLARE_HOOK(HOOK_INIT, i2c_peripheral_init, HOOK_PRIO_INIT_I2C + 1);