blob: 5755165cf06549ee149a7c98f49ca389ddb2d8d9 [file] [log] [blame]
/*
* Copyright (c) 2025 Nuvoton Technology Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
/* I2C controller functions for 'DMA' mode */
#include <zephyr/drivers/i2c.h>
#include <soc.h>
#include <zephyr/logging/log.h>
#include "i2c_npcx_controller.h"
LOG_MODULE_REGISTER(i2c_npcx_dma, CONFIG_I2C_LOG_LEVEL);
static inline uint16_t i2c_ctrl_dma_transferred_bytes(const struct device *dev)
{
uint16_t lens;
struct smb_reg *const inst = HAL_I2C_INSTANCE(dev);
/* return number of bytes of DMA transmitted or received transactions */
lens = (inst->DATA_CNT1 << 8) + inst->DATA_CNT2;
return lens;
}
static inline void i2c_ctrl_dma_nack(const struct device *dev)
{
struct smb_reg *const inst = HAL_I2C_INSTANCE(dev);
inst->DMA_CTRL |= BIT(NPCX_DMA_CTL_LAST_PEC);
}
static size_t i2c_ctrl_calc_dma_lens(const struct device *dev)
{
size_t remains = i2c_ctrl_calculate_msg_remains(dev);
return MIN(remains, NPCX_I2C_DMA_MAX_SIZE);
}
static bool i2c_ctrl_dma_is_last_pkg(const struct device *dev, size_t remains)
{
struct i2c_ctrl_data *const data = dev->data;
return data->ptr_msg + remains == data->msg->buf + data->msg->len;
}
static inline void i2c_ctrl_dma_start(const struct device *dev, uint8_t *addr, uint16_t lens)
{
struct smb_reg *const inst = HAL_I2C_INSTANCE(dev);
uint32_t dma_addr = (uint32_t)addr;
if (lens == 0) {
return;
}
/* Configure the address of DMA transmitted or received transactions */
inst->DMA_ADDR1 = (uint8_t)(dma_addr & 0xff);
inst->DMA_ADDR2 = (uint8_t)((dma_addr >> 8) & 0xff);
inst->DMA_ADDR3 = (uint8_t)((dma_addr >> 16) & 0xff);
inst->DMA_ADDR4 = (uint8_t)((dma_addr >> 24) & 0xff);
/* Configure the length of DMA transmitted or received transactions */
inst->DATA_LEN1 = (uint8_t)(lens & 0xff);
inst->DATA_LEN2 = (uint8_t)((lens >> 8) & 0xff);
/* Clear DMA status bit and release bus */
if (IS_BIT_SET(inst->DMA_CTRL, NPCX_DMA_CTL_IRQSTS)) {
i2c_ctrl_dma_clear_status(dev);
}
/* Start the DMA transaction */
inst->DMA_CTRL |= BIT(NPCX_DMA_CTL_ENABLE);
}
size_t i2c_ctrl_dma_proceed_write(const struct device *dev)
{
/* Calculate how many remaining bytes need to transmit */
size_t dma_lens = i2c_ctrl_calc_dma_lens(dev);
struct i2c_ctrl_data *const data = dev->data;
LOG_DBG("W: dma lens %d, last %d", dma_lens, i2c_ctrl_dma_transferred_bytes(dev));
/* No DMA transactions */
if (dma_lens == 0) {
return 0;
}
/* Start DMA transmitted transaction again */
i2c_ctrl_dma_start(dev, data->ptr_msg, dma_lens);
return dma_lens;
}
size_t i2c_ctrl_dma_proceed_read(const struct device *dev)
{
/* Calculate how many remaining bytes need to receive */
size_t dma_lens = i2c_ctrl_calc_dma_lens(dev);
struct i2c_ctrl_data *const data = dev->data;
LOG_DBG("R: dma lens %d, last %d", dma_lens, i2c_ctrl_dma_transferred_bytes(dev));
if (dma_lens == 0) {
return 0;
}
/* Last byte for NACK in received transaction */
if (i2c_ctrl_dma_is_last_pkg(dev, dma_lens) && (data->msg->flags & I2C_MSG_STOP) != 0) {
/* Issue NACK in the end of DMA transation */
i2c_ctrl_dma_nack(dev);
}
/* Start DMA if bus is idle */
i2c_ctrl_dma_start(dev, data->ptr_msg, dma_lens);
return dma_lens;
}
/* I2C controller recover function in `DMA` mode */
bool i2c_ctrl_toggle_scls(const struct device *dev)
{
struct smb_reg *const inst = HAL_I2C_INSTANCE(dev);
/*
* Toggle SCL to generate 9 clocks. If the I2C target releases the SDA, we can stop
* toggle the SCL and issue a STOP.
*/
for (int j = 0; j < 9; j++) {
if (IS_BIT_SET(inst->SMBCTL3, NPCX_SMBCTL3_SDA_LVL)) {
break;
}
/* Toggle SCL line for one cycle. */
inst->SMBCST |= BIT(NPCX_SMBCST_TGSCL);
k_busy_wait(I2C_RECOVER_BUS_DELAY_US);
}
/* Generate a STOP condition */
i2c_ctrl_stop(dev);
k_busy_wait(I2C_RECOVER_BUS_DELAY_US);
if (i2c_ctrl_is_scl_sda_both_high(dev)) {
return true;
}
return false;
}
/* I2C controller `DMA` interrupt functions */
void i2c_ctrl_handle_write_int_event(const struct device *dev)
{
struct i2c_ctrl_data *const data = dev->data;
/* START condition is issued */
if (data->oper_state == NPCX_I2C_WAIT_START) {
/* Write slave address with W bit */
i2c_ctrl_data_write(dev, ((data->addr << 1) & ~BIT(0)));
/* Start first DMA transmitted transaction */
i2c_ctrl_dma_proceed_write(dev);
/* Start to proceed write process */
data->oper_state = NPCX_I2C_WRITE_DATA;
}
/* Skip the other SDAST events */
}
void i2c_ctrl_handle_read_int_event(const struct device *dev)
{
struct i2c_ctrl_data *const data = dev->data;
/* START or RESTART condition is issued */
if (data->oper_state == NPCX_I2C_WAIT_START || data->oper_state == NPCX_I2C_WAIT_RESTART) {
/* Configure first DMA received transaction before sending address */
i2c_ctrl_dma_proceed_read(dev);
/* Write slave address with R bit */
i2c_ctrl_data_write(dev, ((data->addr << 1) | BIT(0)));
/* Start to proceed read process */
data->oper_state = NPCX_I2C_READ_DATA;
}
/* Skip the other SDAST events */
}
void i2c_ctrl_handle_write_dma_int_event(const struct device *dev)
{
struct i2c_ctrl_data *const data = dev->data;
/* Write message data bytes to FIFO */
if (data->oper_state == NPCX_I2C_WRITE_DATA) {
/* Record how many bytes transmitted via DMA */
data->ptr_msg += i2c_ctrl_dma_transferred_bytes(dev);
/* If next DMA transmitted transaction proceeds, return immediately */
if (i2c_ctrl_dma_proceed_write(dev) != 0) {
return;
}
/* No more remaining bytes */
if (data->msg->flags & I2C_MSG_STOP) {
/* Generate a STOP condition immediately */
i2c_ctrl_stop(dev);
/* Clear DMA status bit and release bus */
i2c_ctrl_dma_clear_status(dev);
/* Wait for STOP completed */
data->oper_state = NPCX_I2C_WAIT_STOP;
} else {
uint8_t next_msg_idx = data->msg_curr_idx + 1;
if (next_msg_idx < data->msg_max_num) {
struct i2c_msg *msg;
data->msg_curr_idx = next_msg_idx;
msg = data->msg_head + next_msg_idx;
data->msg = msg;
data->ptr_msg = msg->buf;
if ((msg->flags & I2C_MSG_RW_MASK) == I2C_MSG_WRITE) {
i2c_ctrl_dma_proceed_write(dev);
} else {
data->is_write = 0;
data->oper_state = NPCX_I2C_WAIT_RESTART;
i2c_ctrl_start(dev);
/* Clear DMA status bit and release bus */
i2c_ctrl_dma_clear_status(dev);
}
return;
}
/* Disable interrupt and hold bus until handling next message */
i2c_ctrl_irq_enable(dev, 0);
/* Wait for the other messages */
data->oper_state = NPCX_I2C_WRITE_SUSPEND;
}
return i2c_ctrl_notify(dev, 0);
}
}
void i2c_ctrl_handle_read_dma_int_event(const struct device *dev)
{
struct i2c_ctrl_data *const data = dev->data;
/* Read message data bytes from FIFO */
if (data->oper_state == NPCX_I2C_READ_DATA) {
/* Record how many bytes received via DMA */
data->ptr_msg += i2c_ctrl_dma_transferred_bytes(dev);
/* If next DMA received transaction proceeds, return immediately */
if (i2c_ctrl_dma_proceed_read(dev) != 0) {
return;
}
/* Is the STOP condition issued? */
if ((data->msg->flags & I2C_MSG_STOP) != 0) {
/* Generate a STOP condition immediately */
i2c_ctrl_stop(dev);
/* Clear DMA status bit and release bus */
i2c_ctrl_dma_clear_status(dev);
/* Wait for STOP completed */
data->oper_state = NPCX_I2C_WAIT_STOP;
} else {
uint8_t next_msg_idx = data->msg_curr_idx + 1;
if (next_msg_idx < data->msg_max_num) {
struct i2c_msg *msg;
msg = data->msg_head + next_msg_idx;
if ((msg->flags & I2C_MSG_RW_MASK) == I2C_MSG_READ) {
data->msg_curr_idx = next_msg_idx;
data->msg = msg;
data->ptr_msg = msg->buf;
i2c_ctrl_dma_proceed_read(dev);
return;
}
}
/* Disable i2c interrupt first */
i2c_ctrl_irq_enable(dev, 0);
data->oper_state = NPCX_I2C_READ_SUSPEND;
}
return i2c_ctrl_notify(dev, 0);
}
}