1233 lines
32 KiB
C
1233 lines
32 KiB
C
/* dw_i2c.c - I2C file for Design Ware */
|
|
|
|
/*
|
|
* Copyright (c) 2015 Intel Corporation
|
|
* Copyright (c) 2022 Andrei-Edward Popa
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
#include <stddef.h>
|
|
#include <zephyr/types.h>
|
|
#include <stdlib.h>
|
|
#include <stdbool.h>
|
|
|
|
#include <zephyr/drivers/i2c.h>
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/init.h>
|
|
#include <zephyr/pm/device.h>
|
|
#include <zephyr/arch/cpu.h>
|
|
#include <string.h>
|
|
|
|
#if defined(CONFIG_PINCTRL)
|
|
#include <zephyr/drivers/pinctrl.h>
|
|
#endif
|
|
#if defined(CONFIG_RESET)
|
|
#include <zephyr/drivers/reset.h>
|
|
#endif
|
|
|
|
#include <errno.h>
|
|
#include <zephyr/sys/sys_io.h>
|
|
|
|
#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
|
|
|
|
#include "i2c_dw.h"
|
|
#include "i2c_dw_registers.h"
|
|
#define LOG_LEVEL CONFIG_I2C_LOG_LEVEL
|
|
#include <zephyr/logging/log.h>
|
|
#include <zephyr/irq.h>
|
|
LOG_MODULE_REGISTER(i2c_dw);
|
|
|
|
#include "i2c-priv.h"
|
|
|
|
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;
|
|
const struct i2c_dw_rom_config * const rom = dev->config;
|
|
struct i2c_dw_dev_config *const dw = dev->data;
|
|
|
|
dma_stop(rom->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;
|
|
const struct i2c_dw_rom_config * const rom = dev->config;
|
|
|
|
struct dma_config dma_cfg = { 0 };
|
|
struct dma_block_config dma_block_cfg = { 0 };
|
|
|
|
if (!device_is_ready(rom->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_dis = 0U;
|
|
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(rom->dma_dev, DMA_INTEL_LPSS_RX_CHAN, &dma_cfg)) {
|
|
LOG_DBG("Error transfer");
|
|
return -EIO;
|
|
}
|
|
|
|
if (dma_start(rom->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)
|
|
{
|
|
const struct i2c_dw_rom_config * const rom = dev->config;
|
|
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(rom->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_dis = 0U;
|
|
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(rom->dma_dev, DMA_INTEL_LPSS_TX_CHAN, &dma_cfg)) {
|
|
LOG_DBG("Error transfer");
|
|
return -EIO;
|
|
}
|
|
|
|
if (dma_start(rom->dma_dev, DMA_INTEL_LPSS_TX_CHAN)) {
|
|
LOG_DBG("Error transfer");
|
|
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;
|
|
uint32_t data;
|
|
int tx_empty;
|
|
int rx_empty;
|
|
int cnt;
|
|
int rx_buffer_depth, tx_buffer_depth;
|
|
union ic_comp_param_1_register ic_comp_param_1;
|
|
uint32_t reg_base = get_regs(dev);
|
|
|
|
/* No more bytes to request, so command queue is no longer needed */
|
|
if (dw->request_bytes == 0U) {
|
|
clear_bit_intr_mask_tx_empty(reg_base);
|
|
return;
|
|
}
|
|
|
|
/* Get the FIFO depth that could be from 2 to 256 from HW spec */
|
|
ic_comp_param_1.raw = read_comp_param_1(reg_base);
|
|
rx_buffer_depth = ic_comp_param_1.bits.rx_buffer_depth + 1;
|
|
tx_buffer_depth = ic_comp_param_1.bits.tx_buffer_depth + 1;
|
|
|
|
/* How many bytes we can actually ask */
|
|
rx_empty = (rx_buffer_depth - read_rxflr(reg_base)) - dw->rx_pending;
|
|
|
|
if (rx_empty < 0) {
|
|
/* RX FIFO expected to be full.
|
|
* So don't request any bytes, yet.
|
|
*/
|
|
return;
|
|
}
|
|
|
|
/* How many empty slots in TX FIFO (as command queue) */
|
|
tx_empty = tx_buffer_depth - read_txflr(reg_base);
|
|
|
|
/* Figure out how many bytes we can request */
|
|
cnt = MIN(rx_buffer_depth, dw->request_bytes);
|
|
cnt = MIN(MIN(tx_empty, rx_empty), cnt);
|
|
|
|
while (cnt > 0) {
|
|
/* Tell controller to get another byte */
|
|
data = IC_DATA_CMD_CMD;
|
|
|
|
/* Send RESTART if needed */
|
|
if (dw->xfr_flags & I2C_MSG_RESTART) {
|
|
data |= IC_DATA_CMD_RESTART;
|
|
dw->xfr_flags &= ~(I2C_MSG_RESTART);
|
|
}
|
|
|
|
/* After receiving the last byte, send STOP if needed */
|
|
if ((dw->xfr_flags & I2C_MSG_STOP)
|
|
&& (dw->request_bytes == 1U)) {
|
|
data |= IC_DATA_CMD_STOP;
|
|
}
|
|
|
|
#ifdef CONFIG_I2C_TARGET
|
|
clear_bit_intr_mask_tx_empty(reg_base);
|
|
#endif /* CONFIG_I2C_TARGET */
|
|
|
|
write_cmd_data(data, reg_base);
|
|
|
|
dw->rx_pending++;
|
|
dw->request_bytes--;
|
|
cnt--;
|
|
}
|
|
|
|
}
|
|
|
|
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);
|
|
|
|
dw->xfr_buf++;
|
|
dw->xfr_len--;
|
|
dw->rx_pending--;
|
|
|
|
if (dw->xfr_len == 0U) {
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
/* Nothing to receive anymore */
|
|
if (dw->xfr_len == 0U) {
|
|
dw->state &= ~I2C_DW_CMD_RECV;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
static int i2c_dw_data_send(const struct device *dev)
|
|
{
|
|
struct i2c_dw_dev_config * const dw = dev->data;
|
|
uint32_t data = 0U;
|
|
uint32_t reg_base = get_regs(dev);
|
|
|
|
/* Nothing to send anymore, mask the interrupt */
|
|
if (dw->xfr_len == 0U) {
|
|
clear_bit_intr_mask_tx_empty(reg_base);
|
|
|
|
dw->state &= ~I2C_DW_CMD_SEND;
|
|
|
|
return 0;
|
|
}
|
|
|
|
while (test_bit_status_tfnt(reg_base) && (dw->xfr_len > 0)) {
|
|
/* We have something to transmit to a specific host */
|
|
data = dw->xfr_buf[0];
|
|
|
|
/* Send RESTART if needed */
|
|
if (dw->xfr_flags & I2C_MSG_RESTART) {
|
|
data |= IC_DATA_CMD_RESTART;
|
|
dw->xfr_flags &= ~(I2C_MSG_RESTART);
|
|
}
|
|
|
|
/* Send STOP if needed */
|
|
if ((dw->xfr_len == 1U) && (dw->xfr_flags & I2C_MSG_STOP)) {
|
|
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++;
|
|
|
|
if (test_bit_intr_stat_tx_abrt(reg_base)) {
|
|
return -EIO;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline void i2c_dw_transfer_complete(const struct device *dev)
|
|
{
|
|
struct i2c_dw_dev_config * const dw = dev->data;
|
|
uint32_t value;
|
|
uint32_t reg_base = get_regs(dev);
|
|
|
|
write_intr_mask(DW_DISABLE_ALL_I2C_INT, reg_base);
|
|
value = read_clr_intr(reg_base);
|
|
|
|
k_sem_give(&dw->device_sync_sem);
|
|
}
|
|
|
|
#ifdef CONFIG_I2C_TARGET
|
|
static inline uint8_t i2c_dw_read_byte_non_blocking(const struct device *dev);
|
|
static inline void i2c_dw_write_byte_non_blocking(const struct device *dev, uint8_t data);
|
|
static void i2c_dw_slave_read_clear_intr_bits(const struct device *dev);
|
|
#endif
|
|
|
|
static void i2c_dw_isr(const struct device *port)
|
|
{
|
|
struct i2c_dw_dev_config * const dw = port->data;
|
|
union ic_interrupt_register intr_stat;
|
|
uint32_t value;
|
|
int ret = 0;
|
|
uint32_t reg_base = get_regs(port);
|
|
|
|
/* Cache ic_intr_stat for processing, so there is no need to read
|
|
* the register multiple times.
|
|
*/
|
|
intr_stat.raw = read_intr_stat(reg_base);
|
|
|
|
/*
|
|
* Causes of an interrupt:
|
|
* - STOP condition is detected
|
|
* - Transfer is aborted
|
|
* - Transmit FIFO is empty
|
|
* - Transmit FIFO has overflowed
|
|
* - Receive FIFO is full
|
|
* - Receive FIFO has overflowed
|
|
* - Received FIFO has underrun
|
|
* - Transmit data is required (tx_req)
|
|
* - Receive data is available (rx_avail)
|
|
*/
|
|
|
|
LOG_DBG("I2C: interrupt received");
|
|
|
|
/* 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) {
|
|
const struct i2c_dw_rom_config * const rom = port->config;
|
|
/* Handle the DMA interrupt */
|
|
dma_intel_lpss_isr(rom->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) &
|
|
intr_stat.raw) {
|
|
dw->state = I2C_DW_CMD_ERROR;
|
|
goto done;
|
|
}
|
|
|
|
/* Check if the RX FIFO reached threshold */
|
|
if (intr_stat.bits.rx_full) {
|
|
i2c_dw_data_read(port);
|
|
}
|
|
|
|
#ifdef CONFIG_I2C_TARGET
|
|
/* Check if the TX FIFO is ready for commands.
|
|
* TX FIFO also serves as command queue where read requests
|
|
* are written to TX FIFO.
|
|
*/
|
|
if ((dw->xfr_flags & I2C_MSG_RW_MASK)
|
|
== I2C_MSG_READ) {
|
|
set_bit_intr_mask_tx_empty(reg_base);
|
|
}
|
|
#endif /* CONFIG_I2C_TARGET */
|
|
|
|
if (intr_stat.bits.tx_empty) {
|
|
if ((dw->xfr_flags & I2C_MSG_RW_MASK)
|
|
== I2C_MSG_WRITE) {
|
|
ret = i2c_dw_data_send(port);
|
|
} else {
|
|
i2c_dw_data_ask(port);
|
|
}
|
|
|
|
/* If STOP is not expected, finish processing this
|
|
* message if there is nothing left to do anymore.
|
|
*/
|
|
if (((dw->xfr_len == 0U)
|
|
&& !(dw->xfr_flags & I2C_MSG_STOP))
|
|
|| (ret != 0)) {
|
|
goto done;
|
|
}
|
|
|
|
}
|
|
|
|
/* STOP detected: finish processing this message */
|
|
if (intr_stat.bits.stop_det) {
|
|
value = read_clr_stop_det(reg_base);
|
|
goto done;
|
|
}
|
|
|
|
} else {
|
|
#ifdef CONFIG_I2C_TARGET
|
|
const struct i2c_target_callbacks *slave_cb = dw->slave_cfg->callbacks;
|
|
uint32_t slave_activity = test_bit_status_activity(reg_base);
|
|
uint8_t data;
|
|
|
|
i2c_dw_slave_read_clear_intr_bits(port);
|
|
|
|
if (intr_stat.bits.rx_full) {
|
|
if (dw->state != I2C_DW_CMD_SEND) {
|
|
dw->state = I2C_DW_CMD_SEND;
|
|
if (slave_cb->write_requested) {
|
|
slave_cb->write_requested(dw->slave_cfg);
|
|
}
|
|
}
|
|
/* FIFO needs to be drained here so we don't miss the next interrupt */
|
|
do {
|
|
data = i2c_dw_read_byte_non_blocking(port);
|
|
if (slave_cb->write_received) {
|
|
slave_cb->write_received(dw->slave_cfg, data);
|
|
}
|
|
} while (test_bit_status_rfne(reg_base));
|
|
}
|
|
|
|
if (intr_stat.bits.rd_req) {
|
|
if (slave_activity) {
|
|
read_clr_rd_req(reg_base);
|
|
dw->state = I2C_DW_CMD_RECV;
|
|
if (slave_cb->read_requested) {
|
|
slave_cb->read_requested(dw->slave_cfg, &data);
|
|
i2c_dw_write_byte_non_blocking(port, data);
|
|
}
|
|
if (slave_cb->read_processed) {
|
|
slave_cb->read_processed(dw->slave_cfg, &data);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return;
|
|
|
|
done:
|
|
i2c_dw_transfer_complete(port);
|
|
}
|
|
|
|
|
|
static int i2c_dw_setup(const struct device *dev, uint16_t slave_address)
|
|
{
|
|
struct i2c_dw_dev_config * const dw = dev->data;
|
|
uint32_t value;
|
|
union ic_con_register ic_con;
|
|
union ic_tar_register ic_tar;
|
|
uint32_t reg_base = get_regs(dev);
|
|
|
|
ic_con.raw = 0U;
|
|
|
|
/* Disable the device controller to be able set TAR */
|
|
clear_bit_enable_en(reg_base);
|
|
|
|
/* Disable interrupts */
|
|
write_intr_mask(0, reg_base);
|
|
|
|
/* Clear interrupts */
|
|
value = read_clr_intr(reg_base);
|
|
|
|
/* Set master or slave mode - (initialization = slave) */
|
|
if (I2C_MODE_CONTROLLER & dw->app_config) {
|
|
/*
|
|
* Make sure to set both the master_mode and slave_disable_bit
|
|
* to both 0 or both 1
|
|
*/
|
|
LOG_DBG("I2C: host configured as Master Device");
|
|
ic_con.bits.master_mode = 1U;
|
|
ic_con.bits.slave_disable = 1U;
|
|
} else {
|
|
return -EINVAL;
|
|
}
|
|
|
|
ic_con.bits.restart_en = 1U;
|
|
|
|
/* Set addressing mode - (initialization = 7 bit) */
|
|
if (I2C_ADDR_10_BITS & dw->app_config) {
|
|
LOG_DBG("I2C: using 10-bit address");
|
|
ic_con.bits.addr_master_10bit = 1U;
|
|
ic_con.bits.addr_slave_10bit = 1U;
|
|
}
|
|
|
|
/* Setup the clock frequency and speed mode */
|
|
switch (I2C_SPEED_GET(dw->app_config)) {
|
|
case I2C_SPEED_STANDARD:
|
|
LOG_DBG("I2C: speed set to STANDARD");
|
|
write_ss_scl_lcnt(dw->lcnt, reg_base);
|
|
write_ss_scl_hcnt(dw->hcnt, reg_base);
|
|
ic_con.bits.speed = I2C_DW_SPEED_STANDARD;
|
|
|
|
break;
|
|
case I2C_SPEED_FAST:
|
|
__fallthrough;
|
|
case I2C_SPEED_FAST_PLUS:
|
|
LOG_DBG("I2C: speed set to FAST or FAST_PLUS");
|
|
write_fs_scl_lcnt(dw->lcnt, reg_base);
|
|
write_fs_scl_hcnt(dw->hcnt, reg_base);
|
|
ic_con.bits.speed = I2C_DW_SPEED_FAST;
|
|
|
|
break;
|
|
case I2C_SPEED_HIGH:
|
|
if (!dw->support_hs_mode) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
LOG_DBG("I2C: speed set to HIGH");
|
|
write_hs_scl_lcnt(dw->lcnt, reg_base);
|
|
write_hs_scl_hcnt(dw->hcnt, reg_base);
|
|
ic_con.bits.speed = I2C_DW_SPEED_HIGH;
|
|
|
|
break;
|
|
default:
|
|
LOG_DBG("I2C: invalid speed requested");
|
|
return -EINVAL;
|
|
}
|
|
|
|
LOG_DBG("I2C: lcnt = %d", dw->lcnt);
|
|
LOG_DBG("I2C: hcnt = %d", dw->hcnt);
|
|
|
|
/* Set the IC_CON register */
|
|
write_con(ic_con.raw, reg_base);
|
|
|
|
/* Set RX fifo threshold level.
|
|
* Setting it to zero automatically triggers interrupt
|
|
* RX_FULL whenever there is data received.
|
|
*
|
|
* TODO: extend the threshold for multi-byte RX.
|
|
*/
|
|
write_rx_tl(0, reg_base);
|
|
|
|
/* Set TX fifo threshold level.
|
|
* TX_EMPTY interrupt is triggered only when the
|
|
* TX FIFO is truly empty. So that we can let
|
|
* the controller do the transfers for longer period
|
|
* before we need to fill the FIFO again. This may
|
|
* cause some pauses during transfers, but this keeps
|
|
* the device from interrupting often.
|
|
*/
|
|
write_tx_tl(0, reg_base);
|
|
|
|
ic_tar.raw = read_tar(reg_base);
|
|
|
|
if (test_bit_con_master_mode(reg_base)) {
|
|
/* Set address of target slave */
|
|
ic_tar.bits.ic_tar = slave_address;
|
|
} else {
|
|
/* Set slave address for device */
|
|
write_sar(slave_address, reg_base);
|
|
}
|
|
|
|
/* If I2C is being operated in master mode and I2C_DYNAMIC_TAR_UPDATE
|
|
* configuration parameter is set to Yes (1), the ic_10bitaddr_master
|
|
* bit in ic_tar register would control whether the DW_apb_i2c starts
|
|
* its transfers in 7-bit or 10-bit addressing mode.
|
|
*/
|
|
if (I2C_MODE_CONTROLLER & dw->app_config) {
|
|
if (I2C_ADDR_10_BITS & dw->app_config) {
|
|
ic_tar.bits.ic_10bitaddr_master = 1U;
|
|
} else {
|
|
ic_tar.bits.ic_10bitaddr_master = 0U;
|
|
}
|
|
}
|
|
|
|
write_tar(ic_tar.raw, reg_base);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int i2c_dw_transfer(const struct device *dev,
|
|
struct i2c_msg *msgs, uint8_t num_msgs,
|
|
uint16_t slave_address)
|
|
{
|
|
struct i2c_dw_dev_config * const dw = dev->data;
|
|
struct i2c_msg *cur_msg = msgs;
|
|
uint8_t msg_left = num_msgs;
|
|
uint8_t pflags;
|
|
int ret;
|
|
uint32_t reg_base = get_regs(dev);
|
|
uint32_t value = 0;
|
|
|
|
__ASSERT_NO_MSG(msgs);
|
|
if (!num_msgs) {
|
|
return 0;
|
|
}
|
|
|
|
ret = k_mutex_lock(&dw->bus_mutex, K_FOREVER);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
/* First step, check if there is current activity */
|
|
if (test_bit_status_activity(reg_base) || (dw->state & I2C_DW_BUSY)) {
|
|
ret = -EBUSY;
|
|
goto error;
|
|
}
|
|
|
|
dw->state |= I2C_DW_BUSY;
|
|
|
|
ret = i2c_dw_setup(dev, slave_address);
|
|
if (ret) {
|
|
goto error;
|
|
}
|
|
|
|
/* Enable controller */
|
|
set_bit_enable_en(reg_base);
|
|
|
|
/*
|
|
* While waiting at device_sync_sem, kernel can switch to idle
|
|
* task which in turn can call pm_system_suspend() hook of Power
|
|
* Management App (PMA).
|
|
* pm_device_busy_set() call here, would indicate to PMA that it should
|
|
* not execute PM policies that would turn off this ip block, causing an
|
|
* ongoing hw transaction to be left in an inconsistent state.
|
|
* Note : This is just a sample to show a possible use of the API, it is
|
|
* upto the driver expert to see, if he actually needs it here, or
|
|
* somewhere else, or not needed as the driver's suspend()/resume()
|
|
* can handle everything
|
|
*/
|
|
pm_device_busy_set(dev);
|
|
|
|
/* Process all the messages */
|
|
while (msg_left > 0) {
|
|
/* Workaround for I2C scanner as DW HW does not support 0 byte transfers.*/
|
|
if ((cur_msg->len == 0) && (cur_msg->buf != NULL)) {
|
|
cur_msg->len = 1;
|
|
}
|
|
|
|
pflags = dw->xfr_flags;
|
|
|
|
dw->xfr_buf = cur_msg->buf;
|
|
dw->xfr_len = cur_msg->len;
|
|
dw->xfr_flags = cur_msg->flags;
|
|
dw->rx_pending = 0U;
|
|
|
|
/* Need to RESTART if changing transfer direction */
|
|
if ((pflags & I2C_MSG_RW_MASK)
|
|
!= (dw->xfr_flags & I2C_MSG_RW_MASK)) {
|
|
dw->xfr_flags |= I2C_MSG_RESTART;
|
|
}
|
|
|
|
dw->state &= ~(I2C_DW_CMD_SEND | I2C_DW_CMD_RECV);
|
|
|
|
if ((dw->xfr_flags & I2C_MSG_RW_MASK) == I2C_MSG_WRITE) {
|
|
dw->state |= I2C_DW_CMD_SEND;
|
|
dw->request_bytes = 0U;
|
|
} else {
|
|
dw->state |= I2C_DW_CMD_RECV;
|
|
dw->request_bytes = dw->xfr_len;
|
|
}
|
|
|
|
/* Enable interrupts to trigger ISR */
|
|
if (test_bit_con_master_mode(reg_base)) {
|
|
/* Enable necessary interrupts */
|
|
write_intr_mask((DW_ENABLE_TX_INT_I2C_MASTER |
|
|
DW_ENABLE_RX_INT_I2C_MASTER), reg_base);
|
|
} else {
|
|
/* Enable necessary interrupts */
|
|
write_intr_mask(DW_ENABLE_TX_INT_I2C_SLAVE, reg_base);
|
|
}
|
|
|
|
/* Wait for transfer to be done */
|
|
ret = k_sem_take(&dw->device_sync_sem, K_MSEC(CONFIG_I2C_DW_RW_TIMEOUT_MS));
|
|
if (ret != 0) {
|
|
write_intr_mask(DW_DISABLE_ALL_I2C_INT, reg_base);
|
|
value = read_clr_intr(reg_base);
|
|
break;
|
|
}
|
|
|
|
if (dw->state & I2C_DW_CMD_ERROR) {
|
|
ret = -EIO;
|
|
break;
|
|
}
|
|
|
|
/* Something wrong if there is something left to do */
|
|
if (dw->xfr_len > 0) {
|
|
ret = -EIO;
|
|
break;
|
|
}
|
|
|
|
cur_msg++;
|
|
msg_left--;
|
|
}
|
|
|
|
pm_device_busy_clear(dev);
|
|
|
|
error:
|
|
dw->state = I2C_DW_STATE_READY;
|
|
k_mutex_unlock(&dw->bus_mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int i2c_dw_runtime_configure(const struct device *dev, uint32_t config)
|
|
{
|
|
struct i2c_dw_dev_config * const dw = dev->data;
|
|
uint32_t value = 0U;
|
|
uint32_t rc = 0U;
|
|
uint32_t reg_base = get_regs(dev);
|
|
|
|
dw->app_config = config;
|
|
|
|
/* Make sure we have a supported speed for the DesignWare model */
|
|
/* and have setup the clock frequency and speed mode */
|
|
switch (I2C_SPEED_GET(dw->app_config)) {
|
|
case I2C_SPEED_STANDARD:
|
|
/* Following the directions on DW spec page 59, IC_SS_SCL_LCNT
|
|
* must have register values larger than IC_FS_SPKLEN + 7
|
|
*/
|
|
if (I2C_STD_LCNT <= (read_fs_spklen(reg_base) + 7)) {
|
|
value = read_fs_spklen(reg_base) + 8;
|
|
} else {
|
|
value = I2C_STD_LCNT;
|
|
}
|
|
|
|
dw->lcnt = value;
|
|
|
|
/* Following the directions on DW spec page 59, IC_SS_SCL_HCNT
|
|
* must have register values larger than IC_FS_SPKLEN + 5
|
|
*/
|
|
if (I2C_STD_HCNT <= (read_fs_spklen(reg_base) + 5)) {
|
|
value = read_fs_spklen(reg_base) + 6;
|
|
} else {
|
|
value = I2C_STD_HCNT;
|
|
}
|
|
|
|
dw->hcnt = value;
|
|
break;
|
|
case I2C_SPEED_FAST:
|
|
__fallthrough;
|
|
case I2C_SPEED_FAST_PLUS:
|
|
/*
|
|
* Following the directions on DW spec page 59, IC_FS_SCL_LCNT
|
|
* must have register values larger than IC_FS_SPKLEN + 7
|
|
*/
|
|
if (I2C_FS_LCNT <= (read_fs_spklen(reg_base) + 7)) {
|
|
value = read_fs_spklen(reg_base) + 8;
|
|
} else {
|
|
value = I2C_FS_LCNT;
|
|
}
|
|
|
|
dw->lcnt = value;
|
|
|
|
/*
|
|
* Following the directions on DW spec page 59, IC_FS_SCL_HCNT
|
|
* must have register values larger than IC_FS_SPKLEN + 5
|
|
*/
|
|
if (I2C_FS_HCNT <= (read_fs_spklen(reg_base) + 5)) {
|
|
value = read_fs_spklen(reg_base) + 6;
|
|
} else {
|
|
value = I2C_FS_HCNT;
|
|
}
|
|
|
|
dw->hcnt = value;
|
|
break;
|
|
case I2C_SPEED_HIGH:
|
|
if (dw->support_hs_mode) {
|
|
if (I2C_HS_LCNT <= (read_hs_spklen(reg_base) + 7)) {
|
|
value = read_hs_spklen(reg_base) + 8;
|
|
} else {
|
|
value = I2C_HS_LCNT;
|
|
}
|
|
|
|
dw->lcnt = value;
|
|
|
|
if (I2C_HS_HCNT <= (read_hs_spklen(reg_base) + 5)) {
|
|
value = read_hs_spklen(reg_base) + 6;
|
|
} else {
|
|
value = I2C_HS_HCNT;
|
|
}
|
|
|
|
dw->hcnt = value;
|
|
} else {
|
|
rc = -EINVAL;
|
|
}
|
|
break;
|
|
default:
|
|
/* TODO change */
|
|
rc = -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* Clear any interrupts currently waiting in the controller
|
|
*/
|
|
value = read_clr_intr(reg_base);
|
|
|
|
/*
|
|
* TEMPORARY HACK - The I2C does not work in any mode other than Master
|
|
* currently. This "hack" forces us to always be configured for master
|
|
* mode, until we can verify that Slave mode works correctly.
|
|
*/
|
|
dw->app_config |= I2C_MODE_CONTROLLER;
|
|
|
|
return rc;
|
|
}
|
|
|
|
#ifdef CONFIG_I2C_TARGET
|
|
static inline uint8_t i2c_dw_read_byte_non_blocking(const struct device *dev)
|
|
{
|
|
uint32_t reg_base = get_regs(dev);
|
|
|
|
if (!test_bit_status_rfne(reg_base)) { /* Rx FIFO must not be empty */
|
|
return -EIO;
|
|
}
|
|
|
|
return (uint8_t)read_cmd_data(reg_base);
|
|
}
|
|
|
|
static inline void i2c_dw_write_byte_non_blocking(const struct device *dev, uint8_t data)
|
|
{
|
|
uint32_t reg_base = get_regs(dev);
|
|
|
|
if (!test_bit_status_tfnt(reg_base)) { /* Tx FIFO must not be full */
|
|
return;
|
|
}
|
|
|
|
write_cmd_data(data, reg_base);
|
|
}
|
|
|
|
static int i2c_dw_set_master_mode(const struct device *dev)
|
|
{
|
|
union ic_comp_param_1_register ic_comp_param_1;
|
|
uint32_t reg_base = get_regs(dev);
|
|
union ic_con_register ic_con;
|
|
|
|
clear_bit_enable_en(reg_base);
|
|
|
|
ic_con.bits.master_mode = 1U;
|
|
ic_con.bits.slave_disable = 1U;
|
|
ic_con.bits.rx_fifo_full = 0U;
|
|
write_con(ic_con.raw, reg_base);
|
|
|
|
set_bit_enable_en(reg_base);
|
|
|
|
ic_comp_param_1.raw = read_comp_param_1(reg_base);
|
|
|
|
write_tx_tl(ic_comp_param_1.bits.tx_buffer_depth + 1, reg_base);
|
|
write_rx_tl(ic_comp_param_1.bits.rx_buffer_depth + 1, reg_base);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int i2c_dw_set_slave_mode(const struct device *dev, uint8_t addr)
|
|
{
|
|
uint32_t reg_base = get_regs(dev);
|
|
union ic_con_register ic_con;
|
|
|
|
ic_con.raw = read_con(reg_base);
|
|
|
|
clear_bit_enable_en(reg_base);
|
|
|
|
ic_con.bits.master_mode = 0U;
|
|
ic_con.bits.slave_disable = 0U;
|
|
ic_con.bits.rx_fifo_full = 1U;
|
|
ic_con.bits.restart_en = 1U;
|
|
ic_con.bits.stop_det = 1U;
|
|
|
|
write_con(ic_con.raw, reg_base);
|
|
write_sar(addr, reg_base);
|
|
write_intr_mask(~DW_INTR_MASK_RESET, reg_base);
|
|
|
|
set_bit_enable_en(reg_base);
|
|
|
|
write_tx_tl(0, reg_base);
|
|
write_rx_tl(0, reg_base);
|
|
|
|
LOG_DBG("I2C: Host registered as Slave Device");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int i2c_dw_slave_register(const struct device *dev,
|
|
struct i2c_target_config *cfg)
|
|
{
|
|
struct i2c_dw_dev_config * const dw = dev->data;
|
|
uint32_t reg_base = get_regs(dev);
|
|
int ret;
|
|
|
|
dw->slave_cfg = cfg;
|
|
ret = i2c_dw_set_slave_mode(dev, cfg->address);
|
|
write_intr_mask(DW_INTR_MASK_RX_FULL |
|
|
DW_INTR_MASK_RD_REQ |
|
|
DW_INTR_MASK_TX_ABRT |
|
|
DW_INTR_MASK_STOP_DET, reg_base);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int i2c_dw_slave_unregister(const struct device *dev,
|
|
struct i2c_target_config *cfg)
|
|
{
|
|
struct i2c_dw_dev_config * const dw = dev->data;
|
|
int ret;
|
|
|
|
dw->state = I2C_DW_STATE_READY;
|
|
ret = i2c_dw_set_master_mode(dev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void i2c_dw_slave_read_clear_intr_bits(const struct device *dev)
|
|
{
|
|
struct i2c_dw_dev_config * const dw = dev->data;
|
|
union ic_interrupt_register intr_stat;
|
|
uint32_t reg_base = get_regs(dev);
|
|
|
|
const struct i2c_target_callbacks *slave_cb = dw->slave_cfg->callbacks;
|
|
|
|
intr_stat.raw = read_intr_stat(reg_base);
|
|
|
|
if (intr_stat.bits.tx_abrt) {
|
|
read_clr_tx_abrt(reg_base);
|
|
dw->state = I2C_DW_STATE_READY;
|
|
}
|
|
|
|
if (intr_stat.bits.rx_under) {
|
|
read_clr_rx_under(reg_base);
|
|
dw->state = I2C_DW_STATE_READY;
|
|
}
|
|
|
|
if (intr_stat.bits.rx_over) {
|
|
read_clr_rx_over(reg_base);
|
|
dw->state = I2C_DW_STATE_READY;
|
|
}
|
|
|
|
if (intr_stat.bits.tx_over) {
|
|
read_clr_tx_over(reg_base);
|
|
dw->state = I2C_DW_STATE_READY;
|
|
}
|
|
|
|
if (intr_stat.bits.rx_done) {
|
|
read_clr_rx_done(reg_base);
|
|
dw->state = I2C_DW_STATE_READY;
|
|
}
|
|
|
|
if (intr_stat.bits.activity) {
|
|
read_clr_activity(reg_base);
|
|
dw->state = I2C_DW_STATE_READY;
|
|
}
|
|
|
|
if (intr_stat.bits.stop_det) {
|
|
read_clr_stop_det(reg_base);
|
|
dw->state = I2C_DW_STATE_READY;
|
|
if (slave_cb->stop) {
|
|
slave_cb->stop(dw->slave_cfg);
|
|
}
|
|
}
|
|
|
|
if (intr_stat.bits.start_det) {
|
|
read_clr_start_det(reg_base);
|
|
dw->state = I2C_DW_STATE_READY;
|
|
}
|
|
|
|
if (intr_stat.bits.gen_call) {
|
|
read_clr_gen_call(reg_base);
|
|
dw->state = I2C_DW_STATE_READY;
|
|
}
|
|
}
|
|
#endif /* CONFIG_I2C_TARGET */
|
|
|
|
static const struct i2c_driver_api funcs = {
|
|
.configure = i2c_dw_runtime_configure,
|
|
.transfer = i2c_dw_transfer,
|
|
#ifdef CONFIG_I2C_TARGET
|
|
.target_register = i2c_dw_slave_register,
|
|
.target_unregister = i2c_dw_slave_unregister,
|
|
#endif /* CONFIG_I2C_TARGET */
|
|
#ifdef CONFIG_I2C_RTIO
|
|
.iodev_submit = i2c_iodev_submit_fallback,
|
|
#endif
|
|
};
|
|
|
|
static int i2c_dw_initialize(const struct device *dev)
|
|
{
|
|
const struct i2c_dw_rom_config * const rom = dev->config;
|
|
struct i2c_dw_dev_config * const dw = dev->data;
|
|
union ic_con_register ic_con;
|
|
int ret = 0;
|
|
|
|
#if defined(CONFIG_RESET)
|
|
if (rom->reset.dev) {
|
|
ret = reset_line_toggle_dt(&rom->reset);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if defined(CONFIG_PINCTRL)
|
|
ret = pinctrl_apply_state(rom->pcfg, PINCTRL_STATE_DEFAULT);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
#if DT_ANY_INST_ON_BUS_STATUS_OKAY(pcie)
|
|
if (rom->pcie) {
|
|
struct pcie_bar mbar;
|
|
|
|
if (rom->pcie->bdf == PCIE_BDF_NONE) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
pcie_probe_mbar(rom->pcie->bdf, 0, &mbar);
|
|
pcie_set_cmd(rom->pcie->bdf, PCIE_CONF_CMDSTAT_MEM, true);
|
|
|
|
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
|
|
uintptr_t base;
|
|
|
|
base = DEVICE_MMIO_GET(dev) + DMA_INTEL_LPSS_OFFSET;
|
|
dma_intel_lpss_set_base(rom->dma_dev, base);
|
|
dma_intel_lpss_setup(rom->dma_dev);
|
|
|
|
/* 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
|
|
{
|
|
DEVICE_MMIO_MAP(dev, K_MEM_CACHE_NONE);
|
|
}
|
|
|
|
k_sem_init(&dw->device_sync_sem, 0, K_SEM_MAX_LIMIT);
|
|
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 */
|
|
if (read_comp_type(reg_base) != I2C_DW_MAGIC_KEY) {
|
|
LOG_DBG("I2C: DesignWare magic key not found, check base "
|
|
"address. Stopping initialization");
|
|
return -EIO;
|
|
}
|
|
|
|
/*
|
|
* grab the default value on initialization. This should be set to the
|
|
* IC_MAX_SPEED_MODE in the hardware. If it does support high speed we
|
|
* can move provide support for it
|
|
*/
|
|
ic_con.raw = read_con(reg_base);
|
|
if (ic_con.bits.speed == I2C_DW_SPEED_HIGH) {
|
|
LOG_DBG("I2C: high speed supported");
|
|
dw->support_hs_mode = true;
|
|
} else {
|
|
LOG_DBG("I2C: high speed NOT supported");
|
|
dw->support_hs_mode = false;
|
|
}
|
|
|
|
rom->config_func(dev);
|
|
|
|
dw->app_config = I2C_MODE_CONTROLLER | i2c_map_dt_bitrate(rom->bitrate);
|
|
|
|
if (i2c_dw_runtime_configure(dev, dw->app_config) != 0) {
|
|
LOG_DBG("I2C: Cannot set default configuration");
|
|
return -EIO;
|
|
}
|
|
|
|
dw->state = I2C_DW_STATE_READY;
|
|
|
|
return ret;
|
|
}
|
|
|
|
#if defined(CONFIG_PINCTRL)
|
|
#define PINCTRL_DW_DEFINE(n) PINCTRL_DT_INST_DEFINE(n)
|
|
#define PINCTRL_DW_CONFIG(n) .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n),
|
|
#else
|
|
#define PINCTRL_DW_DEFINE(n)
|
|
#define PINCTRL_DW_CONFIG(n)
|
|
#endif
|
|
|
|
#if defined(CONFIG_RESET)
|
|
#define RESET_DW_CONFIG(n) \
|
|
IF_ENABLED(DT_INST_NODE_HAS_PROP(0, resets), \
|
|
(.reset = RESET_DT_SPEC_INST_GET(n),))
|
|
#else
|
|
#define RESET_DW_CONFIG(n)
|
|
#endif
|
|
|
|
#define I2C_DW_INIT_PCIE0(n)
|
|
#define I2C_DW_INIT_PCIE1(n) DEVICE_PCIE_INST_INIT(n, pcie),
|
|
#define I2C_DW_INIT_PCIE(n) \
|
|
_CONCAT(I2C_DW_INIT_PCIE, DT_INST_ON_BUS(n, pcie))(n)
|
|
|
|
#define I2C_DEFINE_PCIE0(n)
|
|
#define I2C_DEFINE_PCIE1(n) DEVICE_PCIE_INST_DECLARE(n)
|
|
#define I2C_PCIE_DEFINE(n) \
|
|
_CONCAT(I2C_DEFINE_PCIE, DT_INST_ON_BUS(n, pcie))(n)
|
|
|
|
#define I2C_DW_IRQ_FLAGS_SENSE0(n) 0
|
|
#define I2C_DW_IRQ_FLAGS_SENSE1(n) DT_INST_IRQ(n, sense)
|
|
#define I2C_DW_IRQ_FLAGS(n) \
|
|
_CONCAT(I2C_DW_IRQ_FLAGS_SENSE, DT_INST_IRQ_HAS_CELL(n, sense))(n)
|
|
|
|
/* not PCI(e) */
|
|
#define I2C_DW_IRQ_CONFIG_PCIE0(n) \
|
|
static void i2c_config_##n(const struct device *port) \
|
|
{ \
|
|
ARG_UNUSED(port); \
|
|
IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), \
|
|
i2c_dw_isr, DEVICE_DT_INST_GET(n), \
|
|
I2C_DW_IRQ_FLAGS(n)); \
|
|
irq_enable(DT_INST_IRQN(n)); \
|
|
}
|
|
|
|
/* PCI(e) with auto IRQ detection */
|
|
#define I2C_DW_IRQ_CONFIG_PCIE1(n) \
|
|
static void i2c_config_##n(const struct device *port) \
|
|
{ \
|
|
BUILD_ASSERT(DT_INST_IRQN(n) == PCIE_IRQ_DETECT, \
|
|
"Only runtime IRQ configuration is supported"); \
|
|
BUILD_ASSERT(IS_ENABLED(CONFIG_DYNAMIC_INTERRUPTS), \
|
|
"DW I2C PCI needs CONFIG_DYNAMIC_INTERRUPTS"); \
|
|
const struct i2c_dw_rom_config * const dev_cfg = port->config;\
|
|
unsigned int irq = pcie_alloc_irq(dev_cfg->pcie->bdf); \
|
|
if (irq == PCIE_CONF_INTR_IRQ_NONE) { \
|
|
return; \
|
|
} \
|
|
pcie_connect_dynamic_irq(dev_cfg->pcie->bdf, irq, \
|
|
DT_INST_IRQ(n, priority), \
|
|
(void (*)(const void *))i2c_dw_isr, \
|
|
DEVICE_DT_INST_GET(n), \
|
|
I2C_DW_IRQ_FLAGS(n)); \
|
|
pcie_irq_enable(dev_cfg->pcie->bdf, irq); \
|
|
}
|
|
|
|
#define I2C_DW_IRQ_CONFIG(n) \
|
|
_CONCAT(I2C_DW_IRQ_CONFIG_PCIE, DT_INST_ON_BUS(n, pcie))(n)
|
|
|
|
#define I2C_CONFIG_REG_INIT_PCIE0(n) DEVICE_MMIO_ROM_INIT(DT_DRV_INST(n)),
|
|
#define I2C_CONFIG_REG_INIT_PCIE1(n)
|
|
#define I2C_CONFIG_REG_INIT(n) \
|
|
_CONCAT(I2C_CONFIG_REG_INIT_PCIE, DT_INST_ON_BUS(n, pcie))(n)
|
|
|
|
#define I2C_CONFIG_DMA_INIT(n) \
|
|
COND_CODE_1(CONFIG_I2C_DW_LPSS_DMA, \
|
|
(COND_CODE_1(DT_INST_NODE_HAS_PROP(n, dmas), \
|
|
(.dma_dev = DEVICE_DT_GET(DT_INST_DMAS_CTLR_BY_IDX(n, 0)),), \
|
|
())), ())
|
|
|
|
#define I2C_DEVICE_INIT_DW(n) \
|
|
PINCTRL_DW_DEFINE(n); \
|
|
I2C_PCIE_DEFINE(n); \
|
|
static void i2c_config_##n(const struct device *port); \
|
|
static const struct i2c_dw_rom_config i2c_config_dw_##n = { \
|
|
I2C_CONFIG_REG_INIT(n) \
|
|
.config_func = i2c_config_##n, \
|
|
.bitrate = DT_INST_PROP(n, clock_frequency), \
|
|
RESET_DW_CONFIG(n) \
|
|
PINCTRL_DW_CONFIG(n) \
|
|
I2C_DW_INIT_PCIE(n) \
|
|
I2C_CONFIG_DMA_INIT(n) \
|
|
}; \
|
|
static struct i2c_dw_dev_config i2c_##n##_runtime; \
|
|
I2C_DEVICE_DT_INST_DEFINE(n, i2c_dw_initialize, NULL, \
|
|
&i2c_##n##_runtime, &i2c_config_dw_##n, \
|
|
POST_KERNEL, CONFIG_I2C_INIT_PRIORITY, \
|
|
&funcs); \
|
|
I2C_DW_IRQ_CONFIG(n)
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(I2C_DEVICE_INIT_DW)
|