From 877208dc7855fb7c40c40c328819cf35008ec170 Mon Sep 17 00:00:00 2001 From: Bindu S Date: Tue, 8 Aug 2023 10:45:49 +0530 Subject: [PATCH] drivers: i2c: i2c_dw: Added intel lpss dma support for I2C Enabled intel LPSS DMA interface using dw common to support usage of internal DMA in LPSS I2C to transfer and receive data. Signed-off-by: Bindu S --- drivers/i2c/Kconfig.dw | 9 ++ drivers/i2c/i2c_dw.c | 191 ++++++++++++++++++++++++++++++++- drivers/i2c/i2c_dw.h | 7 ++ drivers/i2c/i2c_dw_registers.h | 19 ++++ 4 files changed, 224 insertions(+), 2 deletions(-) diff --git a/drivers/i2c/Kconfig.dw b/drivers/i2c/Kconfig.dw index b6350b269f4..c84215f4571 100644 --- a/drivers/i2c/Kconfig.dw +++ b/drivers/i2c/Kconfig.dw @@ -12,3 +12,12 @@ config I2C_DW_CLOCK_SPEED int "Set the clock speed for I2C" depends on I2C_DW default 32 + +config I2C_DW_LPSS_DMA + bool "Use I2C integrated DMA for asynchronous transfer" + select DMA + select DMA_INTEL_LPSS + help + This option enables I2C DMA feature to be used for asynchrounous + data transfers. All Tx operaton are done using dma channel 0 and + all Rx operations are done using dma channel 1. diff --git a/drivers/i2c/i2c_dw.c b/drivers/i2c/i2c_dw.c index bb5ecc3779f..ca4485606b7 100644 --- a/drivers/i2c/i2c_dw.c +++ b/drivers/i2c/i2c_dw.c @@ -30,6 +30,11 @@ #include +#if defined(CONFIG_I2C_DW_LPSS_DMA) +#include +#include +#endif + #ifdef CONFIG_IOAPIC #include #endif @@ -48,6 +53,147 @@ static inline uint32_t get_regs(const struct device *dev) return (uint32_t)DEVICE_MMIO_GET(dev); } +#ifdef CONFIG_I2C_DW_LPSS_DMA +void i2c_dw_enable_idma(const struct device *dev, bool enable) +{ + uint32_t reg; + uint32_t reg_base = get_regs(dev); + + if (enable) { + write_dma_cr(DW_IC_DMA_ENABLE, reg_base); + reg = sys_read32(reg_base + DW_IC_REG_DMA_CR); + } else { + reg = read_dma_cr(reg_base); + reg &= ~DW_IC_DMA_ENABLE; + write_dma_cr(reg, reg_base); + reg = sys_read32(reg_base + DW_IC_REG_DMA_CR); + } +} + +void cb_i2c_idma_transfer(const struct device *dma, void *user_data, + uint32_t channel, int status) +{ + const struct device *dev = (const struct device *)user_data; + struct i2c_dw_dev_config *const dw = dev->data; + + dma_stop(dw->dma_dev, channel); + i2c_dw_enable_idma(dev, false); + + if (status) { + dw->xfr_status = true; + } else { + dw->xfr_status = false; + } +} + +void i2c_dw_set_fifo_th(const struct device *dev, uint8_t fifo_depth) +{ + uint32_t reg_base = get_regs(dev); + + write_tdlr(fifo_depth, reg_base); + write_rdlr(fifo_depth - 1, reg_base); +} + +inline void *i2c_dw_dr_phy_addr(const struct device *dev) +{ + struct i2c_dw_dev_config *const dw = dev->data; + + return (void *) (dw->phy_addr + DW_IC_REG_DATA_CMD); +} + +int32_t i2c_dw_idma_rx_transfer(const struct device *dev) +{ + struct i2c_dw_dev_config *const dw = dev->data; + + struct dma_config dma_cfg = { 0 }; + struct dma_block_config dma_block_cfg = { 0 }; + + if (!device_is_ready(dw->dma_dev)) { + LOG_DBG("DMA device is not ready"); + return -ENODEV; + } + + dma_cfg.dma_slot = 1U; + dma_cfg.channel_direction = PERIPHERAL_TO_MEMORY; + dma_cfg.source_data_size = 1U; + dma_cfg.dest_data_size = 1U; + dma_cfg.source_burst_length = 1U; + dma_cfg.dest_burst_length = 1U; + dma_cfg.dma_callback = cb_i2c_idma_transfer; + dma_cfg.user_data = (void *)dev; + dma_cfg.complete_callback_en = 0U; + dma_cfg.error_callback_en = 1U; + dma_cfg.block_count = 1U; + dma_cfg.head_block = &dma_block_cfg; + + dma_block_cfg.block_size = dw->xfr_len; + dma_block_cfg.dest_address = (uint64_t)&dw->xfr_buf[0]; + dma_block_cfg.source_address = (uint64_t)i2c_dw_dr_phy_addr(dev); + dw->xfr_status = false; + + if (dma_config(dw->dma_dev, DMA_INTEL_LPSS_RX_CHAN, &dma_cfg)) { + LOG_DBG("Error transfer"); + return -EIO; + } + + if (dma_start(dw->dma_dev, DMA_INTEL_LPSS_RX_CHAN)) { + LOG_DBG("Error transfer"); + return -EIO; + } + + i2c_dw_enable_idma(dev, true); + i2c_dw_set_fifo_th(dev, 1); + + return 0; +} + +int32_t i2c_dw_idma_tx_transfer(const struct device *dev, + uint64_t data) +{ + struct i2c_dw_dev_config *const dw = dev->data; + + struct dma_config dma_cfg = { 0 }; + struct dma_block_config dma_block_cfg = { 0 }; + + if (!device_is_ready(dw->dma_dev)) { + LOG_DBG("DMA device is not ready"); + return -ENODEV; + } + + dma_cfg.dma_slot = 0U; + dma_cfg.channel_direction = MEMORY_TO_PERIPHERAL; + dma_cfg.source_data_size = 1U; + dma_cfg.dest_data_size = 1U; + dma_cfg.source_burst_length = 1U; + dma_cfg.dest_burst_length = 1U; + dma_cfg.dma_callback = cb_i2c_idma_transfer; + dma_cfg.user_data = (void *)dev; + dma_cfg.complete_callback_en = 0U; + dma_cfg.error_callback_en = 1U; + dma_cfg.block_count = 1U; + dma_cfg.head_block = &dma_block_cfg; + + dma_block_cfg.block_size = 1; + dma_block_cfg.source_address = (uint64_t)&data; + dma_block_cfg.dest_address = (uint64_t)i2c_dw_dr_phy_addr(dev); + dw->xfr_status = false; + + if (dma_config(dw->dma_dev, DMA_INTEL_LPSS_TX_CHAN, &dma_cfg)) { + LOG_DBG("Error transfer"); + return -EIO; + } + + if (dma_start(dw->dma_dev, DMA_INTEL_LPSS_TX_CHAN)) { + LOG_DBG("Error trnasfer"); + return -EIO; + } + i2c_dw_enable_idma(dev, true); + i2c_dw_set_fifo_th(dev, 1); + + return 0; +} +#endif + static inline void i2c_dw_data_ask(const struct device *dev) { struct i2c_dw_dev_config * const dw = dev->data; @@ -119,6 +265,13 @@ static void i2c_dw_data_read(const struct device *dev) struct i2c_dw_dev_config * const dw = dev->data; uint32_t reg_base = get_regs(dev); +#ifdef CONFIG_I2C_DW_LPSS_DMA + if (test_bit_status_rfne(reg_base) && (dw->xfr_len > 0)) { + i2c_dw_idma_rx_transfer(dev); + dw->xfr_len = 0; + dw->rx_pending = 0; + } +#else while (test_bit_status_rfne(reg_base) && (dw->xfr_len > 0)) { dw->xfr_buf[0] = (uint8_t)read_cmd_data(reg_base); @@ -130,7 +283,7 @@ static void i2c_dw_data_read(const struct device *dev) break; } } - +#endif /* Nothing to receive anymore */ if (dw->xfr_len == 0U) { dw->state &= ~I2C_DW_CMD_RECV; @@ -169,8 +322,11 @@ static int i2c_dw_data_send(const struct device *dev) data |= IC_DATA_CMD_STOP; } +#ifdef CONFIG_I2C_DW_LPSS_DMA + i2c_dw_idma_tx_transfer(dev, data); +#else write_cmd_data(data, reg_base); - +#endif dw->xfr_len--; dw->xfr_buf++; @@ -230,6 +386,16 @@ static void i2c_dw_isr(const struct device *port) /* Check if we are configured as a master device */ if (test_bit_con_master_mode(reg_base)) { +#ifdef CONFIG_I2C_DW_LPSS_DMA + uint32_t stat = sys_read32(reg_base + IDMA_REG_INTR_STS); + + if (stat & IDMA_TX_RX_CHAN_MASK) { + /* Handle the DMA interrupt */ + dma_intel_lpss_isr(dw->dma_dev); + + } +#endif + /* Bail early if there is any error. */ if ((DW_INTR_STAT_TX_ABRT | DW_INTR_STAT_TX_OVER | DW_INTR_STAT_RX_OVER | DW_INTR_STAT_RX_UNDER) & @@ -879,6 +1045,26 @@ static int i2c_dw_initialize(const struct device *dev) device_map(DEVICE_MMIO_RAM_PTR(dev), mbar.phys_addr, mbar.size, K_MEM_CACHE_NONE); + + pcie_set_cmd(rom->pcie->bdf, PCIE_CONF_CMDSTAT_MASTER, true); + +#ifdef CONFIG_I2C_DW_LPSS_DMA + size_t nhdls = 0; + const device_handle_t *hdls; + + hdls = device_supported_handles_get(dev, &nhdls); + dw->dma_dev = device_from_handle(*hdls); + + /* Assign physical & virtual address to dma instance */ + dw->phy_addr = mbar.phys_addr; + dw->base_addr = (uint32_t)(DEVICE_MMIO_GET(dev) + DMA_INTEL_LPSS_OFFSET); + sys_write32((uint32_t)dw->phy_addr, + DEVICE_MMIO_GET(dev) + DMA_INTEL_LPSS_REMAP_LOW); + sys_write32((uint32_t)(dw->phy_addr >> DMA_INTEL_LPSS_ADDR_RIGHT_SHIFT), + DEVICE_MMIO_GET(dev) + DMA_INTEL_LPSS_REMAP_HI); + LOG_DBG("i2c instance physical addr: [0x%lx], virtual addr: [0x%lx]", + dw->phy_addr, dw->base_addr); +#endif } else #endif { @@ -889,6 +1075,7 @@ static int i2c_dw_initialize(const struct device *dev) k_mutex_init(&dw->bus_mutex); uint32_t reg_base = get_regs(dev); + clear_bit_enable_en(reg_base); /* verify that we have a valid DesignWare register first */ diff --git a/drivers/i2c/i2c_dw.h b/drivers/i2c/i2c_dw.h index 1aac3d20340..34427504708 100644 --- a/drivers/i2c/i2c_dw.h +++ b/drivers/i2c/i2c_dw.h @@ -123,6 +123,13 @@ struct i2c_dw_dev_config { uint8_t request_bytes; uint8_t xfr_flags; bool support_hs_mode; +#ifdef CONFIG_I2C_DW_LPSS_DMA + const struct device *dma_dev; + uintptr_t phy_addr; + uintptr_t base_addr; + /* For dma transfer */ + bool xfr_status; +#endif struct i2c_target_config *slave_cfg; }; diff --git a/drivers/i2c/i2c_dw_registers.h b/drivers/i2c/i2c_dw_registers.h index be76cbdaad9..6b2a11dffa4 100644 --- a/drivers/i2c/i2c_dw_registers.h +++ b/drivers/i2c/i2c_dw_registers.h @@ -144,14 +144,25 @@ union ic_comp_param_1_register { #define DW_IC_REG_STATUS (0x70) #define DW_IC_REG_TXFLR (0x74) #define DW_IC_REG_RXFLR (0x78) +#define DW_IC_REG_DMA_CR (0x88) +#define DW_IC_REG_TDLR (0x8C) +#define DW_IC_REG_RDLR (0x90) #define DW_IC_REG_FS_SPKLEN (0xA0) #define DW_IC_REG_HS_SPKLEN (0xA4) #define DW_IC_REG_COMP_PARAM_1 (0xF4) #define DW_IC_REG_COMP_TYPE (0xFC) +#define IDMA_REG_INTR_STS 0xAE8 +#define IDMA_TX_RX_CHAN_MASK 0x3 + /* CON Bit */ #define DW_IC_CON_MASTER_MODE_BIT (0) +/* DMA control bits */ +#define DW_IC_DMA_RX_ENABLE BIT(0) +#define DW_IC_DMA_TX_ENABLE BIT(1) +#define DW_IC_DMA_ENABLE (BIT(0) | BIT(1)) + DEFINE_TEST_BIT_OP(con_master_mode, DW_IC_REG_CON, DW_IC_CON_MASTER_MODE_BIT) DEFINE_MM_REG_WRITE(con, DW_IC_REG_CON, 32) DEFINE_MM_REG_READ(con, DW_IC_REG_CON, 32) @@ -209,6 +220,14 @@ DEFINE_TEST_BIT_OP(status_rfne, DW_IC_REG_STATUS, DW_IC_STATUS_RFNE_BIT) DEFINE_MM_REG_READ(txflr, DW_IC_REG_TXFLR, 32) DEFINE_MM_REG_READ(rxflr, DW_IC_REG_RXFLR, 32) +DEFINE_MM_REG_READ(dma_cr, DW_IC_REG_DMA_CR, 32) +DEFINE_MM_REG_WRITE(dma_cr, DW_IC_REG_DMA_CR, 32) + +DEFINE_MM_REG_READ(tdlr, DW_IC_REG_TDLR, 32) +DEFINE_MM_REG_WRITE(tdlr, DW_IC_REG_TDLR, 32) +DEFINE_MM_REG_READ(rdlr, DW_IC_REG_RDLR, 32) +DEFINE_MM_REG_WRITE(rdlr, DW_IC_REG_RDLR, 32) + DEFINE_MM_REG_READ(fs_spklen, DW_IC_REG_FS_SPKLEN, 32) DEFINE_MM_REG_READ(hs_spklen, DW_IC_REG_HS_SPKLEN, 32)