blob: 85d335c5e92545b5068e9492023123c35a2ff05c [file] [log] [blame]
/*
* Copyright (c) 2023 Microchip Technology Inc.
* Copyright (C) 2025 embedded brains GmbH & Co. KG
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/irq.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/sys_io.h>
#include <zephyr/sys/util.h>
LOG_MODULE_REGISTER(i2c_mchp, CONFIG_I2C_LOG_LEVEL);
#define DT_DRV_COMPAT microchip_mpfs_i2c
/* Is MSS I2C module 'resets' line property defined */
#define MSS_I2C_RESET_ENABLED DT_ANY_INST_HAS_PROP_STATUS_OKAY(resets)
#if MSS_I2C_RESET_ENABLED
#include <zephyr/drivers/reset.h>
#endif
#define CORE_I2C_CTRL (0x00)
#define CORE_I2C_STATUS (0x04)
#define CORE_I2C_DATA (0x08)
#define CORE_I2C_ADDR_0 (0x0C)
#define CORE_I2C_FREQ (0x14)
#define CORE_I2C_GLITCHREG (0x18)
#define CORE_I2C_ADDR_1 (0x1C)
#define CTRL_CR0 BIT(0)
#define CTRL_CR1 BIT(1)
#define CTRL_AA BIT(2)
#define CTRL_SI BIT(3)
#define CTRL_STO BIT(4)
#define CTRL_STA BIT(5)
#define CTRL_ENS1 BIT(6)
#define CTRL_CR2 BIT(7)
#define STATUS_BUS_ERROR (0x00)
#define STATUS_M_START_SENT (0x08)
#define STATUS_M_REPEATED_START_SENT (0x10)
#define STATUS_M_SLAW_ACK (0x18)
#define STATUS_M_SLAW_NACK (0x20)
#define STATUS_M_TX_DATA_ACK (0x28)
#define STATUS_M_TX_DATA_NACK (0x30)
#define STATUS_M_ARB_LOST (0x38)
#define STATUS_M_SLAR_ACK (0x40)
#define STATUS_M_SLAR_NACK (0x48)
#define STATUS_M_RX_DATA_ACKED (0x50)
#define STATUS_M_RX_DATA_NACKED (0x58)
#define STATUS_S_SLAW_ACKED (0x60)
#define STATUS_S_ARB_LOST_SLAW_ACKED (0x68)
#define STATUS_S_GENERAL_CALL_ACKED (0x70)
#define STATUS_S_ARB_LOST_GENERAL_CALL_ACKED (0x78)
#define STATUS_S_RX_DATA_ACKED (0x80)
#define STATUS_S_RX_DATA_NACKED (0x88)
#define STATUS_S_GENERAL_CALL_RX_DATA_ACKED (0x90)
#define STATUS_S_GENERAL_CALL_RX_DATA_NACKED (0x98)
#define STATUS_S_RX_STOP (0xA0)
#define STATUS_S_SLAR_ACKED (0xA8)
#define STATUS_S_ARB_LOST_SLAR_ACKED (0xB0)
#define STATUS_S_TX_DATA_ACK (0xB8)
#define STATUS_S_TX_DATA_NACK (0xC0)
#define STATUS_LAST_DATA_ACK (0xC8)
#define STATUS_NO_INFO (0xF8)
#define PCLK_DIV_960 (CTRL_CR2)
#define PCLK_DIV_256 (0)
#define PCLK_DIV_224 (CTRL_CR0)
#define PCLK_DIV_192 (CTRL_CR1)
#define PCLK_DIV_160 (CTRL_CR0 | CTRL_CR1)
#define PCLK_DIV_120 (CTRL_CR0 | CTRL_CR2)
#define PCLK_DIV_60 (CTRL_CR1 | CTRL_CR2)
#define BCLK_DIV_8 (CTRL_CR0 | CTRL_CR1 | CTRL_CR2)
#define CLK_MASK (CTRL_CR0 | CTRL_CR1 | CTRL_CR2)
struct mss_i2c_config {
uint32_t clock_freq;
uintptr_t i2c_base_addr;
uint32_t i2c_irq_base;
#if MSS_I2C_RESET_ENABLED
struct reset_dt_spec reset_spec;
#endif
};
struct mss_i2c_data {
struct k_mutex mtx;
struct k_sem done;
const struct i2c_msg *msg_curr;
const struct i2c_msg *msg_last;
uint8_t *byte_curr;
const uint8_t *byte_end;
uint8_t addr;
int ret;
};
static void mss_i2c_reset(const struct mss_i2c_config *cfg)
{
/* Disable the module */
uint8_t ctrl = sys_read8(cfg->i2c_base_addr + CORE_I2C_CTRL);
ctrl &= ~CTRL_ENS1;
sys_write8(ctrl, cfg->i2c_base_addr + CORE_I2C_CTRL);
/* Make sure the write completed */
ctrl = sys_read8(cfg->i2c_base_addr + CORE_I2C_CTRL);
/* Enable the module */
ctrl &= ~(CTRL_AA | CTRL_SI | CTRL_STA | CTRL_STO);
ctrl |= CTRL_ENS1;
sys_write8(ctrl, cfg->i2c_base_addr + CORE_I2C_CTRL);
}
static int mss_i2c_configure(const struct device *dev, uint32_t dev_config)
{
const struct mss_i2c_config *cfg = dev->config;
uint8_t ctrl = sys_read8(cfg->i2c_base_addr + CORE_I2C_CTRL);
ctrl &= ~CLK_MASK;
switch (I2C_SPEED_GET(dev_config)) {
case I2C_SPEED_STANDARD:
ctrl |= PCLK_DIV_960;
break;
case I2C_SPEED_FAST:
ctrl |= PCLK_DIV_256;
break;
default:
return -EINVAL;
}
sys_write8(ctrl, cfg->i2c_base_addr + CORE_I2C_CTRL);
return 0;
}
static uint8_t *mss_i2c_set_byte_end(struct mss_i2c_data *data, const struct i2c_msg *msg)
{
uint8_t *byte_curr = msg->buf;
data->byte_end = byte_curr + msg->len;
return byte_curr;
}
static int mss_i2c_transfer(const struct device *dev, struct i2c_msg *msgs, uint8_t num_msgs,
uint16_t addr)
{
const struct mss_i2c_config *cfg = dev->config;
struct mss_i2c_data *data = dev->data;
/*
* Check for validity of all messages, to prevent having to abort in
* the middle of a transfer.
*/
struct i2c_msg *curr = msgs;
if (curr->len == 0) {
/*
* There are potential issues with zero length buffers. For example, zero length
* transfers seem to prevent that a following restart or stop condition is
* generated. It is unclear if this is a hardware or driver issue.
*
* Independent of potential hardware issues, the driver definitely does not support
* zero length continuation buffers. They would complicate the message handling.
*/
return -EINVAL;
}
for (int i = 1; i < num_msgs; ++i) {
struct i2c_msg *next = curr + 1;
if (next->len == 0) {
/* Zero length buffers are not supported, see above */
return -EINVAL;
}
if ((curr->flags & I2C_MSG_STOP) != 0) {
/* Stop condition is only allowed on last message */
return -EINVAL;
}
if ((curr->flags & I2C_MSG_RW_MASK) == (next->flags & I2C_MSG_RW_MASK)) {
if ((next->flags & I2C_MSG_RESTART) != 0) {
/*
* Restart condition between messages of the same direction
* is not supported.
*/
return -EINVAL;
}
} else if ((next->flags & I2C_MSG_RESTART) == 0) {
/*
* Restart condition between messages of different directions
* is required.
*/
return -EINVAL;
}
curr = next;
}
/* Add RW bit to address, so that we can write it directly to the data register */
addr <<= 1;
if ((msgs->flags & I2C_MSG_RW_MASK) == I2C_MSG_READ) {
addr |= 1;
}
(void)k_mutex_lock(&data->mtx, K_FOREVER);
data->ret = 0;
data->addr = (uint8_t)addr;
data->msg_curr = msgs;
data->msg_last = msgs + num_msgs - 1;
data->byte_curr = mss_i2c_set_byte_end(data, msgs);
/* Start the transfer */
uint8_t ctrl = sys_read8(cfg->i2c_base_addr + CORE_I2C_CTRL);
ctrl &= ~(CTRL_AA | CTRL_SI | CTRL_STO);
ctrl |= CTRL_STA;
sys_write8(ctrl, cfg->i2c_base_addr + CORE_I2C_CTRL);
/*
* Clear a potentially erroneous done condition caused by a spurious interrupt. Enable
* interrupts and wait for the transfer completion.
*/
k_sem_reset(&data->done);
irq_enable(cfg->i2c_irq_base);
int ret = k_sem_take(&data->done, K_TICKS(1000));
irq_disable(cfg->i2c_irq_base);
if (unlikely(ret != 0)) {
/*
* In case of a timeout, reset the module. This could be caused by an SCL line held
* low.
*/
mss_i2c_reset(cfg);
} else {
ret = data->ret;
}
(void)k_mutex_unlock(&data->mtx);
return ret;
}
static DEVICE_API(i2c, mss_i2c_driver_api) = {
.configure = mss_i2c_configure,
.transfer = mss_i2c_transfer,
#ifdef CONFIG_I2C_RTIO
.iodev_submit = i2c_iodev_submit_fallback,
#endif
};
static void mss_i2c_init(const struct device *dev)
{
const struct mss_i2c_config *cfg = dev->config;
struct mss_i2c_data *data = dev->data;
(void)k_mutex_init(&data->mtx);
(void)k_sem_init(&data->done, 0, 1);
#if MSS_I2C_RESET_ENABLED
if (cfg->reset_spec.dev != NULL) {
(void)reset_line_deassert_dt(&cfg->reset_spec);
}
#endif
mss_i2c_reset(cfg);
}
static inline bool mss_i2c_is_last_write_byte(struct mss_i2c_data *data, const struct i2c_msg *msg)
{
return msg == data->msg_last || ((msg + 1)->flags & I2C_MSG_RW_MASK) == I2C_MSG_READ;
}
static inline bool mss_i2c_is_last_read_byte(struct mss_i2c_data *data, const struct i2c_msg *msg)
{
return msg == data->msg_last || ((msg + 1)->flags & I2C_MSG_RW_MASK) == I2C_MSG_WRITE;
}
static uint8_t mss_i2c_set_ctrl_aa(struct mss_i2c_data *data, const struct i2c_msg *msg,
uint8_t ctrl)
{
if (mss_i2c_is_last_read_byte(data, msg)) {
return ctrl;
}
return ctrl | CTRL_AA;
}
static void mss_i2c_irq_handler(const struct device *dev)
{
const struct mss_i2c_config *cfg = dev->config;
struct mss_i2c_data *data = dev->data;
uintptr_t i2c_base_addr = cfg->i2c_base_addr;
const struct i2c_msg *msg_curr = data->msg_curr;
uint8_t *byte_curr = data->byte_curr;
uint8_t ctrl = sys_read8(i2c_base_addr + CORE_I2C_CTRL);
bool done = false;
do {
uint8_t status = sys_read8(i2c_base_addr + CORE_I2C_STATUS);
if (status == STATUS_NO_INFO) {
break;
}
ctrl &= ~(CTRL_AA | CTRL_STA | CTRL_STO);
switch (status) {
case STATUS_M_START_SENT:
case STATUS_M_REPEATED_START_SENT:
sys_write8(ctrl, i2c_base_addr + CORE_I2C_CTRL);
sys_write8(data->addr, i2c_base_addr + CORE_I2C_DATA);
break;
case STATUS_M_TX_DATA_NACK:
if (byte_curr != data->byte_end &&
!mss_i2c_is_last_write_byte(data, msg_curr)) {
/*
* A not acknowledged write is only acceptable for the last byte
* written.
*/
data->ret = -EIO;
done = true;
}
__fallthrough;
case STATUS_M_SLAW_ACK:
case STATUS_M_TX_DATA_ACK:
if (byte_curr != data->byte_end) {
sys_write8(*byte_curr, i2c_base_addr + CORE_I2C_DATA);
++byte_curr;
} else if (msg_curr == data->msg_last) {
ctrl |= CTRL_STO;
done = true;
} else {
++msg_curr;
byte_curr = mss_i2c_set_byte_end(data, msg_curr);
if ((msg_curr->flags & I2C_MSG_RW_MASK) == I2C_MSG_READ) {
/* Direction change with repeated start */
ctrl |= CTRL_STA;
data->addr |= 1;
} else {
/*
* Continue write with the new buffer. The message check in
* mss_i2c_transfer() ensures that this is a non-zero length
* buffer.
*/
sys_write8(*byte_curr, i2c_base_addr + CORE_I2C_DATA);
++byte_curr;
}
}
break;
case STATUS_M_RX_DATA_ACKED:
case STATUS_M_RX_DATA_NACKED:
if (byte_curr != data->byte_end) {
*byte_curr = sys_read8(cfg->i2c_base_addr + CORE_I2C_DATA);
++byte_curr;
} else {
/* This is an error and should not happen */
data->ret = -EIO;
done = true;
}
__fallthrough;
case STATUS_M_SLAR_ACK:
if (byte_curr + 1 == data->byte_end) {
ctrl = mss_i2c_set_ctrl_aa(data, msg_curr, ctrl);
} else if (byte_curr != data->byte_end) {
ctrl |= CTRL_AA;
} else if (msg_curr == data->msg_last) {
ctrl |= CTRL_STO;
done = true;
} else {
++msg_curr;
byte_curr = mss_i2c_set_byte_end(data, msg_curr);
if ((msg_curr->flags & I2C_MSG_RW_MASK) == I2C_MSG_WRITE) {
/* Direction change with repeated start */
ctrl |= CTRL_STA;
data->addr &= ~1;
} else if (byte_curr + 1 == data->byte_end) {
ctrl = mss_i2c_set_ctrl_aa(data, msg_curr, ctrl);
} else {
ctrl |= CTRL_AA;
}
}
break;
default:
ctrl |= CTRL_STO;
data->ret = -EIO;
done = true;
break;
}
ctrl &= ~CTRL_SI;
sys_write8(ctrl, i2c_base_addr + CORE_I2C_CTRL);
} while (!done);
data->msg_curr = msg_curr;
data->byte_curr = byte_curr;
if (done) {
k_sem_give(&data->done);
}
}
#define MSS_I2C_INIT(n) \
static int mss_i2c_init_##n(const struct device *dev) \
{ \
mss_i2c_init(dev); \
IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), mss_i2c_irq_handler, \
DEVICE_DT_INST_GET(n), 0); \
return 0; \
} \
\
static struct mss_i2c_data mss_i2c_data_##n; \
\
static const struct mss_i2c_config mss_i2c_config_##n = { \
.i2c_base_addr = DT_INST_REG_ADDR(n), \
.i2c_irq_base = DT_INST_IRQN(n), \
.clock_freq = DT_INST_PROP(n, clock_frequency), \
IF_ENABLED(DT_INST_NODE_HAS_PROP(n, resets), \
(.reset_spec = RESET_DT_SPEC_INST_GET(n),)) \
}; \
\
I2C_DEVICE_DT_INST_DEFINE(n, mss_i2c_init_##n, NULL, &mss_i2c_data_##n, \
&mss_i2c_config_##n, PRE_KERNEL_1, CONFIG_I2C_INIT_PRIORITY, \
&mss_i2c_driver_api);
DT_INST_FOREACH_STATUS_OKAY(MSS_I2C_INIT)