| /* Copyright 2019 The ChromiumOS Authors |
| * 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 "printf.h" |
| #include "registers.h" |
| #include "task.h" |
| |
| #include <stddef.h> |
| #include <string.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]) { |
| char str_buf[hex_str_buf_size( |
| I2C_MAX_BUFFER_SIZE)]; |
| /* |
| * TODO(b:129360157): Handle controller |
| * write data by "in_data" array. |
| */ |
| snprintf_hex_buffer( |
| str_buf, sizeof(str_buf), |
| HEX_BUF(in_data[idx], |
| I2C_MAX_BUFFER_SIZE)); |
| CPRINTS("WData: %s", str_buf); |
| 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_POST_I2C); |