You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
736 lines
19 KiB
736 lines
19 KiB
/* dw_i2c.c - I2C file for Design Ware */ |
|
|
|
/* |
|
* Copyright (c) 2015 Intel Corporation |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
#include <stddef.h> |
|
#include <zephyr/types.h> |
|
#include <stdlib.h> |
|
#include <stdbool.h> |
|
|
|
#include <drivers/i2c.h> |
|
#include <kernel.h> |
|
#include <init.h> |
|
#include <arch/cpu.h> |
|
#include <string.h> |
|
|
|
#include <soc.h> |
|
#include <errno.h> |
|
#include <sys/sys_io.h> |
|
|
|
#include <sys/util.h> |
|
|
|
#ifdef CONFIG_IOAPIC |
|
#include <drivers/interrupt_controller/ioapic.h> |
|
#endif |
|
|
|
#include "i2c_dw.h" |
|
#include "i2c_dw_registers.h" |
|
#define LOG_LEVEL CONFIG_I2C_LOG_LEVEL |
|
#include <logging/log.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); |
|
} |
|
|
|
static inline void i2c_dw_data_ask(const struct device *dev) |
|
{ |
|
struct i2c_dw_dev_config * const dw = dev->data; |
|
uint32_t data; |
|
uint8_t tx_empty; |
|
int8_t rx_empty; |
|
uint8_t cnt; |
|
uint8_t 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; |
|
} |
|
|
|
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); |
|
|
|
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; |
|
} |
|
} |
|
|
|
/* 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; |
|
} |
|
|
|
write_cmd_data(data, reg_base); |
|
|
|
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); |
|
} |
|
|
|
static void i2c_dw_isr(void *arg) |
|
{ |
|
const struct device *port = (const struct device *)arg; |
|
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)) { |
|
/* 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); |
|
} |
|
|
|
/* 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 (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; |
|
} |
|
|
|
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_MASTER & 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_MASTER & 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); |
|
|
|
__ASSERT_NO_MSG(msgs); |
|
if (!num_msgs) { |
|
return 0; |
|
} |
|
|
|
/* First step, check if there is current activity */ |
|
if (test_bit_status_activity(reg_base) || (dw->state & I2C_DW_BUSY)) { |
|
return -EIO; |
|
} |
|
|
|
dw->state |= I2C_DW_BUSY; |
|
|
|
ret = i2c_dw_setup(dev, slave_address); |
|
if (ret) { |
|
dw->state = I2C_DW_STATE_READY; |
|
return ret; |
|
} |
|
|
|
/* 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) { |
|
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; |
|
} |
|
|
|
/* Send STOP if this is the last message */ |
|
if (msg_left == 1U) { |
|
dw->xfr_flags |= I2C_MSG_STOP; |
|
} |
|
|
|
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 */ |
|
k_sem_take(&dw->device_sync_sem, K_FOREVER); |
|
|
|
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); |
|
|
|
dw->state = I2C_DW_STATE_READY; |
|
|
|
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_MASTER; |
|
|
|
return rc; |
|
} |
|
|
|
static const struct i2c_driver_api funcs = { |
|
.configure = i2c_dw_runtime_configure, |
|
.transfer = i2c_dw_transfer, |
|
}; |
|
|
|
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; |
|
uint32_t reg_base = get_regs(dev); |
|
|
|
#if DT_ANY_INST_ON_BUS_STATUS_OKAY(pcie) |
|
if (rom->pcie) { |
|
struct pcie_mbar mbar; |
|
|
|
if (!pcie_probe(rom->pcie_bdf, rom->pcie_id)) { |
|
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); |
|
} else |
|
#endif |
|
{ |
|
DEVICE_MMIO_MAP(dev, K_MEM_CACHE_NONE); |
|
} |
|
|
|
k_sem_init(&dw->device_sync_sem, 0, K_SEM_MAX_LIMIT); |
|
|
|
/* 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_MASTER | 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 0; |
|
} |
|
|
|
#define I2C_DW_INIT_PCIE0(n) |
|
#define I2C_DW_INIT_PCIE1(n) \ |
|
.pcie = true, \ |
|
.pcie_bdf = DT_INST_REG_ADDR(n), \ |
|
.pcie_id = DT_INST_REG_SIZE(n), |
|
#define I2C_DW_INIT_PCIE(n) \ |
|
_CONCAT(I2C_DW_INIT_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) \ |
|
{ \ |
|
ARG_UNUSED(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"); \ |
|
unsigned int irq = pcie_alloc_irq(DT_INST_REG_ADDR(n)); \ |
|
if (irq == PCIE_CONF_INTR_IRQ_NONE) { \ |
|
return; \ |
|
} \ |
|
irq_connect_dynamic(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(DT_INST_REG_ADDR(n), irq); \ |
|
} |
|
|
|
#define I2C_DW_IRQ_CONFIG(n) \ |
|
_CONCAT(I2C_DW_IRQ_CONFIG_PCIE, DT_INST_ON_BUS(n, pcie))(n) |
|
|
|
#define I2C_DEVICE_INIT_DW(n) \ |
|
static void i2c_config_##n(const struct device *port); \ |
|
static const struct i2c_dw_rom_config i2c_config_dw_##n = { \ |
|
DEVICE_MMIO_ROM_INIT(DT_DRV_INST(n)), \ |
|
.config_func = i2c_config_##n, \ |
|
.bitrate = DT_INST_PROP(n, clock_frequency), \ |
|
I2C_DW_INIT_PCIE(n) \ |
|
}; \ |
|
static struct i2c_dw_dev_config i2c_##n##_runtime; \ |
|
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)
|
|
|