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 <bindu.s@intel.com>
This commit is contained in:
Bindu S 2023-08-08 10:45:49 +05:30 committed by Carles Cufí
commit 877208dc78
4 changed files with 224 additions and 2 deletions

View file

@ -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.

View file

@ -30,6 +30,11 @@
#include <zephyr/sys/util.h>
#if defined(CONFIG_I2C_DW_LPSS_DMA)
#include <zephyr/drivers/dma.h>
#include <zephyr/drivers/dma/dma_intel_lpss.h>
#endif
#ifdef CONFIG_IOAPIC
#include <zephyr/drivers/interrupt_controller/ioapic.h>
#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 */

View file

@ -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;
};

View file

@ -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)