drivers: i2c: support for Nuvoton numaker series
Add Nuvoton numaker series I2C controller feature. Support dual role and at most one slave at one time Signed-off-by: cyliang tw <cyliang@nuvoton.com>
This commit is contained in:
parent
a97e30a829
commit
dfff1107b8
|
@ -55,6 +55,7 @@ zephyr_library_sources_ifdef(CONFIG_I2C_MCHP_MSS i2c_mchp_mss.c)
|
|||
zephyr_library_sources_ifdef(CONFIG_I2C_SEDI i2c_sedi.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_I2C_AMBIQ i2c_ambiq.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_GPIO_I2C_SWITCH gpio_i2c_switch.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_I2C_NUMAKER i2c_numaker.c)
|
||||
|
||||
zephyr_library_sources_ifdef(CONFIG_I2C_STM32_V1
|
||||
i2c_ll_stm32_v1.c
|
||||
|
|
|
@ -92,6 +92,7 @@ source "drivers/i2c/Kconfig.xilinx_axi"
|
|||
source "drivers/i2c/Kconfig.mchp_mss"
|
||||
source "drivers/i2c/Kconfig.sedi"
|
||||
source "drivers/i2c/Kconfig.ambiq"
|
||||
source "drivers/i2c/Kconfig.numaker"
|
||||
|
||||
config I2C_INIT_PRIORITY
|
||||
int "Init priority"
|
||||
|
|
14
drivers/i2c/Kconfig.numaker
Normal file
14
drivers/i2c/Kconfig.numaker
Normal file
|
@ -0,0 +1,14 @@
|
|||
# NUMAKER I2C driver configuration options
|
||||
|
||||
# Copyright (c) 2023 Nuvoton Technology Corporation
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
config I2C_NUMAKER
|
||||
bool "Nuvoton NuMaker I2C driver"
|
||||
default y
|
||||
select HAS_NUMAKER_I2C
|
||||
depends on DT_HAS_NUVOTON_NUMAKER_I2C_ENABLED
|
||||
help
|
||||
This option enables I2C driver for Nuvoton NuMaker family of
|
||||
processors.
|
||||
Say y if you wish to enable NuMaker I2C.
|
784
drivers/i2c/i2c_numaker.c
Normal file
784
drivers/i2c/i2c_numaker.c
Normal file
|
@ -0,0 +1,784 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Nuvoton Technology Corporation.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#define DT_DRV_COMPAT nuvoton_numaker_i2c
|
||||
|
||||
#include <zephyr/drivers/i2c.h>
|
||||
#include <zephyr/drivers/clock_control.h>
|
||||
#include <zephyr/drivers/clock_control/clock_control_numaker.h>
|
||||
#include <zephyr/drivers/reset.h>
|
||||
#include <zephyr/drivers/pinctrl.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
|
||||
LOG_MODULE_REGISTER(i2c_numaker, CONFIG_I2C_LOG_LEVEL);
|
||||
|
||||
#include "i2c-priv.h"
|
||||
#include <soc.h>
|
||||
#include <NuMicro.h>
|
||||
|
||||
/* i2c Master Mode Status */
|
||||
#define M_START 0x08 /* Start */
|
||||
#define M_REPEAT_START 0x10 /* Master Repeat Start */
|
||||
#define M_TRAN_ADDR_ACK 0x18 /* Master Transmit Address ACK */
|
||||
#define M_TRAN_ADDR_NACK 0x20 /* Master Transmit Address NACK */
|
||||
#define M_TRAN_DATA_ACK 0x28 /* Master Transmit Data ACK */
|
||||
#define M_TRAN_DATA_NACK 0x30 /* Master Transmit Data NACK */
|
||||
#define M_ARB_LOST 0x38 /* Master Arbitration Los */
|
||||
#define M_RECE_ADDR_ACK 0x40 /* Master Receive Address ACK */
|
||||
#define M_RECE_ADDR_NACK 0x48 /* Master Receive Address NACK */
|
||||
#define M_RECE_DATA_ACK 0x50 /* Master Receive Data ACK */
|
||||
#define M_RECE_DATA_NACK 0x58 /* Master Receive Data NACK */
|
||||
#define BUS_ERROR 0x00 /* Bus error */
|
||||
|
||||
/* i2c Slave Mode Status */
|
||||
#define S_REPEAT_START_STOP 0xA0 /* Slave Transmit Repeat Start or Stop */
|
||||
#define S_TRAN_ADDR_ACK 0xA8 /* Slave Transmit Address ACK */
|
||||
#define S_TRAN_DATA_ACK 0xB8 /* Slave Transmit Data ACK */
|
||||
#define S_TRAN_DATA_NACK 0xC0 /* Slave Transmit Data NACK */
|
||||
#define S_TRAN_LAST_DATA_ACK 0xC8 /* Slave Transmit Last Data ACK */
|
||||
#define S_RECE_ADDR_ACK 0x60 /* Slave Receive Address ACK */
|
||||
#define S_RECE_ARB_LOST 0x68 /* Slave Receive Arbitration Lost */
|
||||
#define S_RECE_DATA_ACK 0x80 /* Slave Receive Data ACK */
|
||||
#define S_RECE_DATA_NACK 0x88 /* Slave Receive Data NACK */
|
||||
|
||||
/* i2c GC Mode Status */
|
||||
#define GC_ADDR_ACK 0x70 /* GC mode Address ACK */
|
||||
#define GC_ARB_LOST 0x78 /* GC mode Arbitration Lost */
|
||||
#define GC_DATA_ACK 0x90 /* GC mode Data ACK */
|
||||
#define GC_DATA_NACK 0x98 /* GC mode Data NACK */
|
||||
|
||||
/* i2c Other Status */
|
||||
#define ADDR_TRAN_ARB_LOST 0xB0 /* Address Transmit Arbitration Lost */
|
||||
#define BUS_RELEASED 0xF8 /* Bus Released */
|
||||
|
||||
struct i2c_numaker_config {
|
||||
I2C_T *i2c_base;
|
||||
const struct reset_dt_spec reset;
|
||||
uint32_t clk_modidx;
|
||||
uint32_t clk_src;
|
||||
uint32_t clk_div;
|
||||
const struct device *clkctrl_dev;
|
||||
uint32_t irq_n;
|
||||
void (*irq_config_func)(const struct device *dev);
|
||||
const struct pinctrl_dev_config *pincfg;
|
||||
uint32_t bitrate;
|
||||
};
|
||||
|
||||
struct i2c_numaker_data {
|
||||
struct k_sem lock;
|
||||
uint32_t dev_config;
|
||||
/* Master transfer context */
|
||||
struct {
|
||||
struct k_sem xfer_sync;
|
||||
uint16_t addr;
|
||||
struct i2c_msg *msgs_beg;
|
||||
struct i2c_msg *msgs_pos;
|
||||
struct i2c_msg *msgs_end;
|
||||
uint8_t *buf_beg;
|
||||
uint8_t *buf_pos;
|
||||
uint8_t *buf_end;
|
||||
} master_xfer;
|
||||
#ifdef CONFIG_I2C_TARGET
|
||||
/* Slave transfer context */
|
||||
struct {
|
||||
struct i2c_target_config *slave_config;
|
||||
bool slave_addressed;
|
||||
} slave_xfer;
|
||||
#endif
|
||||
};
|
||||
|
||||
/* ACK/NACK last data byte, dependent on whether or not message merge is allowed */
|
||||
static void m_numaker_i2c_master_xfer_msg_read_last_byte(const struct device *dev)
|
||||
{
|
||||
const struct i2c_numaker_config *config = dev->config;
|
||||
struct i2c_numaker_data *data = dev->data;
|
||||
I2C_T *i2c_base = config->i2c_base;
|
||||
|
||||
/* Shouldn't invoke with message pointer OOB */
|
||||
__ASSERT_NO_MSG(data->master_xfer.msgs_pos < data->master_xfer.msgs_end);
|
||||
/* Should invoke with exactly one data byte remaining for read */
|
||||
__ASSERT_NO_MSG((data->master_xfer.msgs_pos->flags & I2C_MSG_RW_MASK) == I2C_MSG_READ);
|
||||
__ASSERT_NO_MSG((data->master_xfer.buf_end - data->master_xfer.buf_pos) == 1);
|
||||
|
||||
/* Flags of previous message */
|
||||
bool do_stop_prev = data->master_xfer.msgs_pos->flags & I2C_MSG_STOP;
|
||||
|
||||
/* Advance to next messages temporarily */
|
||||
data->master_xfer.msgs_pos++;
|
||||
|
||||
/* Has next message? */
|
||||
if (data->master_xfer.msgs_pos < data->master_xfer.msgs_end) {
|
||||
/* Flags of next message */
|
||||
struct i2c_msg *msgs_pos = data->master_xfer.msgs_pos;
|
||||
bool is_read_next = (msgs_pos->flags & I2C_MSG_RW_MASK) == I2C_MSG_READ;
|
||||
bool do_restart_next = data->master_xfer.msgs_pos->flags & I2C_MSG_RESTART;
|
||||
|
||||
/*
|
||||
* Different R/W bit so message merge is disallowed.
|
||||
* Force I2C Repeat Start on I2C Stop/Repeat Start missing
|
||||
*/
|
||||
if (!is_read_next) {
|
||||
if (!do_stop_prev && !do_restart_next) {
|
||||
do_restart_next = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (do_stop_prev || do_restart_next) {
|
||||
/* NACK last data byte (required for Master Receiver) */
|
||||
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk);
|
||||
} else {
|
||||
/* ACK last data byte, so to merge adjacent messages into one transaction */
|
||||
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk | I2C_CTL0_AA_Msk);
|
||||
}
|
||||
} else {
|
||||
/* NACK last data byte (required for Master Receiver) */
|
||||
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk);
|
||||
}
|
||||
|
||||
/* Roll back message pointer */
|
||||
data->master_xfer.msgs_pos--;
|
||||
}
|
||||
|
||||
/* End the transfer, involving I2C Stop and signal to thread */
|
||||
static void m_numaker_i2c_master_xfer_end(const struct device *dev, bool do_stop)
|
||||
{
|
||||
const struct i2c_numaker_config *config = dev->config;
|
||||
struct i2c_numaker_data *data = dev->data;
|
||||
I2C_T *i2c_base = config->i2c_base;
|
||||
|
||||
if (do_stop) {
|
||||
/* Do I2C Stop */
|
||||
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_STO_Msk | I2C_CTL0_SI_Msk);
|
||||
}
|
||||
|
||||
/* Signal master transfer end */
|
||||
k_sem_give(&data->master_xfer.xfer_sync);
|
||||
}
|
||||
|
||||
static void m_numaker_i2c_master_xfer_msg_end(const struct device *dev);
|
||||
/* Read next data byte, involving ACK/NACK last data byte and message merge */
|
||||
static void m_numaker_i2c_master_xfer_msg_read_next_byte(const struct device *dev)
|
||||
{
|
||||
const struct i2c_numaker_config *config = dev->config;
|
||||
struct i2c_numaker_data *data = dev->data;
|
||||
I2C_T *i2c_base = config->i2c_base;
|
||||
|
||||
switch (data->master_xfer.buf_end - data->master_xfer.buf_pos) {
|
||||
case 0:
|
||||
/* Last data byte ACKed, we'll do message merge */
|
||||
m_numaker_i2c_master_xfer_msg_end(dev);
|
||||
break;
|
||||
case 1:
|
||||
/* Read last data byte for this message */
|
||||
m_numaker_i2c_master_xfer_msg_read_last_byte(dev);
|
||||
break;
|
||||
default:
|
||||
/* ACK non-last data byte */
|
||||
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk | I2C_CTL0_AA_Msk);
|
||||
}
|
||||
}
|
||||
|
||||
/* End one message transfer, involving message merge and transfer end */
|
||||
static void m_numaker_i2c_master_xfer_msg_end(const struct device *dev)
|
||||
{
|
||||
const struct i2c_numaker_config *config = dev->config;
|
||||
struct i2c_numaker_data *data = dev->data;
|
||||
I2C_T *i2c_base = config->i2c_base;
|
||||
|
||||
/* Shouldn't invoke with message pointer OOB */
|
||||
__ASSERT_NO_MSG(data->master_xfer.msgs_pos < data->master_xfer.msgs_end);
|
||||
/* Should have transferred up */
|
||||
__ASSERT_NO_MSG((data->master_xfer.buf_end - data->master_xfer.buf_pos) == 0);
|
||||
|
||||
/* Flags of previous message */
|
||||
bool is_read_prev = (data->master_xfer.msgs_pos->flags & I2C_MSG_RW_MASK) == I2C_MSG_READ;
|
||||
bool do_stop_prev = data->master_xfer.msgs_pos->flags & I2C_MSG_STOP;
|
||||
|
||||
/* Advance to next messages */
|
||||
data->master_xfer.msgs_pos++;
|
||||
|
||||
/* Has next message? */
|
||||
if (data->master_xfer.msgs_pos < data->master_xfer.msgs_end) {
|
||||
/* Flags of next message */
|
||||
struct i2c_msg *msgs_pos = data->master_xfer.msgs_pos;
|
||||
bool is_read_next = (msgs_pos->flags & I2C_MSG_RW_MASK) == I2C_MSG_READ;
|
||||
bool do_restart_next = data->master_xfer.msgs_pos->flags & I2C_MSG_RESTART;
|
||||
|
||||
/*
|
||||
* Different R/W bit so message merge is disallowed.
|
||||
* Force I2C Repeat Start on I2C Stop/Repeat Start missing
|
||||
*/
|
||||
if (!is_read_prev != !is_read_next) { /* Logical XOR idiom */
|
||||
if (!do_stop_prev && !do_restart_next) {
|
||||
LOG_WRN("Cannot merge adjacent messages, force I2C Repeat Start");
|
||||
do_restart_next = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (do_stop_prev) {
|
||||
/* Do I2C Stop and then Start */
|
||||
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_STA_Msk |
|
||||
I2C_CTL0_STO_Msk | I2C_CTL0_SI_Msk);
|
||||
} else if (do_restart_next) {
|
||||
/* Do I2C Repeat Start */
|
||||
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_STA_Msk | I2C_CTL0_SI_Msk);
|
||||
} else {
|
||||
/* Merge into the same transaction */
|
||||
|
||||
/* Prepare buffer for current message */
|
||||
data->master_xfer.buf_beg = data->master_xfer.msgs_pos->buf;
|
||||
data->master_xfer.buf_pos = data->master_xfer.msgs_pos->buf;
|
||||
data->master_xfer.buf_end = data->master_xfer.msgs_pos->buf +
|
||||
data->master_xfer.msgs_pos->len;
|
||||
|
||||
if (is_read_prev) {
|
||||
m_numaker_i2c_master_xfer_msg_read_next_byte(dev);
|
||||
} else {
|
||||
/*
|
||||
* Interrupt flag not cleared, expect to re-enter ISR with
|
||||
* context unchanged, except buffer changed for message change.
|
||||
*/
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!do_stop_prev) {
|
||||
LOG_WRN("Last message not marked I2C Stop");
|
||||
}
|
||||
|
||||
m_numaker_i2c_master_xfer_end(dev, do_stop_prev);
|
||||
}
|
||||
}
|
||||
|
||||
static int i2c_numaker_configure(const struct device *dev, uint32_t dev_config)
|
||||
{
|
||||
const struct i2c_numaker_config *config = dev->config;
|
||||
struct i2c_numaker_data *data = dev->data;
|
||||
uint32_t bitrate;
|
||||
|
||||
/* Check address size */
|
||||
if (dev_config & I2C_ADDR_10_BITS) {
|
||||
LOG_ERR("10-bits address not supported");
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
switch (I2C_SPEED_GET(dev_config)) {
|
||||
case I2C_SPEED_STANDARD:
|
||||
bitrate = KHZ(100);
|
||||
break;
|
||||
case I2C_SPEED_FAST:
|
||||
bitrate = KHZ(400);
|
||||
break;
|
||||
case I2C_SPEED_FAST_PLUS:
|
||||
bitrate = MHZ(1);
|
||||
break;
|
||||
default:
|
||||
LOG_ERR("Speed code %d not supported", I2C_SPEED_GET(dev_config));
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
I2C_T *i2c_base = config->i2c_base;
|
||||
int err = 0;
|
||||
|
||||
k_sem_take(&data->lock, K_FOREVER);
|
||||
irq_disable(config->irq_n);
|
||||
|
||||
#ifdef CONFIG_I2C_TARGET
|
||||
if (data->slave_xfer.slave_addressed) {
|
||||
LOG_ERR("Reconfigure with slave being busy");
|
||||
err = -EBUSY;
|
||||
goto done;
|
||||
}
|
||||
#endif
|
||||
|
||||
I2C_Open(i2c_base, bitrate);
|
||||
/* INTEN bit and FSM control bits (STA, STO, SI, AA) are packed in one register CTL0. */
|
||||
i2c_base->CTL0 |= (I2C_CTL0_INTEN_Msk | I2C_CTL0_I2CEN_Msk);
|
||||
data->dev_config = dev_config;
|
||||
|
||||
done:
|
||||
|
||||
irq_enable(config->irq_n);
|
||||
k_sem_give(&data->lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int i2c_numaker_get_config(const struct device *dev, uint32_t *dev_config)
|
||||
{
|
||||
struct i2c_numaker_data *data = dev->data;
|
||||
|
||||
if (!dev_config) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
k_sem_take(&data->lock, K_FOREVER);
|
||||
*dev_config = data->dev_config;
|
||||
k_sem_give(&data->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Master active transfer:
|
||||
* 1. Do I2C Start to start the transfer (thread)
|
||||
* 2. I2C FSM (ISR)
|
||||
* 3. Force I2C Stop to end the transfer (thread)
|
||||
* Slave passive transfer:
|
||||
* 1. Prepare callback (thread)
|
||||
* 2. Do data transfer via above callback (ISR)
|
||||
*/
|
||||
static int i2c_numaker_transfer(const struct device *dev, struct i2c_msg *msgs,
|
||||
uint8_t num_msgs, uint16_t addr)
|
||||
{
|
||||
const struct i2c_numaker_config *config = dev->config;
|
||||
struct i2c_numaker_data *data = dev->data;
|
||||
I2C_T *i2c_base = config->i2c_base;
|
||||
int err = 0;
|
||||
|
||||
k_sem_take(&data->lock, K_FOREVER);
|
||||
irq_disable(config->irq_n);
|
||||
|
||||
if (data->slave_xfer.slave_addressed) {
|
||||
LOG_ERR("Master transfer with slave being busy");
|
||||
err = -EBUSY;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (num_msgs == 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Prepare to start transfer */
|
||||
data->master_xfer.addr = addr;
|
||||
data->master_xfer.msgs_beg = msgs;
|
||||
data->master_xfer.msgs_pos = msgs;
|
||||
data->master_xfer.msgs_end = msgs + num_msgs;
|
||||
|
||||
/* Do I2C Start to start the transfer */
|
||||
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_STA_Msk | I2C_CTL0_SI_Msk);
|
||||
|
||||
irq_enable(config->irq_n);
|
||||
k_sem_take(&data->master_xfer.xfer_sync, K_FOREVER);
|
||||
irq_disable(config->irq_n);
|
||||
|
||||
/* Check transfer result */
|
||||
if (data->master_xfer.msgs_pos != data->master_xfer.msgs_end) {
|
||||
bool is_read;
|
||||
bool is_10bit;
|
||||
|
||||
is_read = (data->master_xfer.msgs_pos->flags & I2C_MSG_RW_MASK) == I2C_MSG_READ;
|
||||
is_10bit = data->master_xfer.msgs_pos->flags & I2C_MSG_ADDR_10_BITS;
|
||||
LOG_ERR("Failed message:");
|
||||
LOG_ERR("MSG IDX: %d", data->master_xfer.msgs_pos - data->master_xfer.msgs_beg);
|
||||
LOG_ERR("ADDR (%d-bit): 0x%04X", is_10bit ? 10 : 7, addr);
|
||||
LOG_ERR("DIR: %s", is_read ? "R" : "W");
|
||||
LOG_ERR("Expected %d bytes transferred, but actual %d",
|
||||
data->master_xfer.msgs_pos->len,
|
||||
data->master_xfer.buf_pos - data->master_xfer.buf_beg);
|
||||
err = -EIO;
|
||||
goto i2c_stop;
|
||||
}
|
||||
|
||||
i2c_stop:
|
||||
|
||||
/* Do I2C Stop to release bus ownership */
|
||||
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_STO_Msk | I2C_CTL0_SI_Msk);
|
||||
|
||||
#ifdef CONFIG_I2C_TARGET
|
||||
/* Enable slave mode if one slave is registered */
|
||||
if (data->slave_xfer.slave_config) {
|
||||
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk | I2C_CTL0_AA_Msk);
|
||||
}
|
||||
#endif
|
||||
|
||||
cleanup:
|
||||
|
||||
irq_enable(config->irq_n);
|
||||
k_sem_give(&data->lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_I2C_TARGET
|
||||
static int i2c_numaker_slave_register(const struct device *dev,
|
||||
struct i2c_target_config *slave_config)
|
||||
{
|
||||
if (!slave_config || !slave_config->callbacks) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (slave_config->flags & I2C_ADDR_10_BITS) {
|
||||
LOG_ERR("10-bits address not supported");
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
const struct i2c_numaker_config *config = dev->config;
|
||||
struct i2c_numaker_data *data = dev->data;
|
||||
I2C_T *i2c_base = config->i2c_base;
|
||||
int err = 0;
|
||||
|
||||
k_sem_take(&data->lock, K_FOREVER);
|
||||
irq_disable(config->irq_n);
|
||||
|
||||
if (data->slave_xfer.slave_config) {
|
||||
err = -EBUSY;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
data->slave_xfer.slave_config = slave_config;
|
||||
/* Slave address */
|
||||
I2C_SetSlaveAddr(i2c_base,
|
||||
0,
|
||||
slave_config->address,
|
||||
I2C_GCMODE_DISABLE);
|
||||
|
||||
/* Slave address state */
|
||||
data->slave_xfer.slave_addressed = false;
|
||||
|
||||
/* Enable slave mode */
|
||||
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk | I2C_CTL0_AA_Msk);
|
||||
|
||||
cleanup:
|
||||
|
||||
irq_enable(config->irq_n);
|
||||
k_sem_give(&data->lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int i2c_numaker_slave_unregister(const struct device *dev,
|
||||
struct i2c_target_config *slave_config)
|
||||
{
|
||||
const struct i2c_numaker_config *config = dev->config;
|
||||
struct i2c_numaker_data *data = dev->data;
|
||||
I2C_T *i2c_base = config->i2c_base;
|
||||
int err = 0;
|
||||
|
||||
if (!slave_config) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
k_sem_take(&data->lock, K_FOREVER);
|
||||
irq_disable(config->irq_n);
|
||||
|
||||
if (data->slave_xfer.slave_config != slave_config) {
|
||||
err = -EINVAL;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (data->slave_xfer.slave_addressed) {
|
||||
LOG_ERR("Unregister slave driver with slave being busy");
|
||||
err = -EBUSY;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Slave address: Zero */
|
||||
I2C_SetSlaveAddr(i2c_base,
|
||||
0,
|
||||
0,
|
||||
I2C_GCMODE_DISABLE);
|
||||
|
||||
/* Slave address state */
|
||||
data->slave_xfer.slave_addressed = false;
|
||||
|
||||
/* Disable slave mode */
|
||||
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk);
|
||||
data->slave_xfer.slave_config = NULL;
|
||||
|
||||
cleanup:
|
||||
|
||||
irq_enable(config->irq_n);
|
||||
k_sem_give(&data->lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int i2c_numaker_recover_bus(const struct device *dev)
|
||||
{
|
||||
const struct i2c_numaker_config *config = dev->config;
|
||||
struct i2c_numaker_data *data = dev->data;
|
||||
I2C_T *i2c_base = config->i2c_base;
|
||||
|
||||
k_sem_take(&data->lock, K_FOREVER);
|
||||
/* Do I2C Stop to release bus ownership */
|
||||
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_STO_Msk | I2C_CTL0_SI_Msk);
|
||||
k_sem_give(&data->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void i2c_numaker_isr(const struct device *dev)
|
||||
{
|
||||
const struct i2c_numaker_config *config = dev->config;
|
||||
struct i2c_numaker_data *data = dev->data;
|
||||
I2C_T *i2c_base = config->i2c_base;
|
||||
#ifdef CONFIG_I2C_TARGET
|
||||
struct i2c_target_config *slave_config = data->slave_xfer.slave_config;
|
||||
const struct i2c_target_callbacks *slave_callbacks =
|
||||
slave_config ? slave_config->callbacks : NULL;
|
||||
uint8_t data_byte;
|
||||
#endif
|
||||
uint32_t status;
|
||||
|
||||
if (I2C_GET_TIMEOUT_FLAG(i2c_base)) {
|
||||
I2C_ClearTimeoutFlag(i2c_base);
|
||||
return;
|
||||
}
|
||||
|
||||
status = I2C_GET_STATUS(i2c_base);
|
||||
|
||||
switch (status) {
|
||||
case M_START: /* Start */
|
||||
case M_REPEAT_START: /* Master Repeat Start */
|
||||
/* Prepare buffer for current message */
|
||||
data->master_xfer.buf_beg = data->master_xfer.msgs_pos->buf;
|
||||
data->master_xfer.buf_pos = data->master_xfer.msgs_pos->buf;
|
||||
data->master_xfer.buf_end = data->master_xfer.msgs_pos->buf +
|
||||
data->master_xfer.msgs_pos->len;
|
||||
|
||||
/* Write I2C address */
|
||||
struct i2c_msg *msgs_pos = data->master_xfer.msgs_pos;
|
||||
bool is_read = (msgs_pos->flags & I2C_MSG_RW_MASK) == I2C_MSG_READ;
|
||||
uint16_t addr = data->master_xfer.addr;
|
||||
int addr_rw = is_read ? ((addr << 1) | 1) : (addr << 1);
|
||||
|
||||
I2C_SET_DATA(i2c_base, (uint8_t) (addr_rw & 0xFF));
|
||||
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk);
|
||||
break;
|
||||
case M_TRAN_ADDR_ACK: /* Master Transmit Address ACK */
|
||||
case M_TRAN_DATA_ACK: /* Master Transmit Data ACK */
|
||||
__ASSERT_NO_MSG(data->master_xfer.buf_pos);
|
||||
if (data->master_xfer.buf_pos < data->master_xfer.buf_end) {
|
||||
I2C_SET_DATA(i2c_base, *data->master_xfer.buf_pos++);
|
||||
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk | I2C_CTL0_AA_Msk);
|
||||
} else {
|
||||
/* End this message */
|
||||
m_numaker_i2c_master_xfer_msg_end(dev);
|
||||
}
|
||||
break;
|
||||
case M_TRAN_ADDR_NACK: /* Master Transmit Address NACK */
|
||||
case M_TRAN_DATA_NACK: /* Master Transmit Data NACK */
|
||||
case M_RECE_ADDR_NACK: /* Master Receive Address NACK */
|
||||
case M_ARB_LOST: /* Master Arbitration Lost */
|
||||
m_numaker_i2c_master_xfer_end(dev, true);
|
||||
break;
|
||||
case M_RECE_ADDR_ACK: /* Master Receive Address ACK */
|
||||
case M_RECE_DATA_ACK: /* Master Receive Data ACK */
|
||||
__ASSERT_NO_MSG(data->master_xfer.buf_pos);
|
||||
|
||||
if (status == M_RECE_ADDR_ACK) {
|
||||
__ASSERT_NO_MSG(data->master_xfer.buf_pos < data->master_xfer.buf_end);
|
||||
} else if (status == M_RECE_DATA_ACK) {
|
||||
__ASSERT_NO_MSG((data->master_xfer.buf_end -
|
||||
data->master_xfer.buf_pos) >= 1);
|
||||
*data->master_xfer.buf_pos++ = I2C_GET_DATA(i2c_base);
|
||||
}
|
||||
|
||||
m_numaker_i2c_master_xfer_msg_read_next_byte(dev);
|
||||
break;
|
||||
case M_RECE_DATA_NACK: /* Master Receive Data NACK */
|
||||
__ASSERT_NO_MSG((data->master_xfer.buf_end - data->master_xfer.buf_pos) == 1);
|
||||
*data->master_xfer.buf_pos++ = I2C_GET_DATA(i2c_base);
|
||||
/* End this message */
|
||||
m_numaker_i2c_master_xfer_msg_end(dev);
|
||||
break;
|
||||
case BUS_ERROR: /* Bus error */
|
||||
m_numaker_i2c_master_xfer_end(dev, true);
|
||||
break;
|
||||
#ifdef CONFIG_I2C_TARGET
|
||||
/* NOTE: Don't disable interrupt here because slave mode relies on */
|
||||
/* for passive transfer in ISR. */
|
||||
|
||||
/* Slave Transmit */
|
||||
case S_TRAN_ADDR_ACK: /* Slave Transmit Address ACK */
|
||||
case ADDR_TRAN_ARB_LOST: /* Slave Transmit Arbitration Lost */
|
||||
data->slave_xfer.slave_addressed = true;
|
||||
if (slave_callbacks->read_requested(slave_config, &data_byte) == 0) {
|
||||
/* Non-last data byte */
|
||||
I2C_SET_DATA(i2c_base, data_byte);
|
||||
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk | I2C_CTL0_AA_Msk);
|
||||
} else {
|
||||
/* Go S_TRAN_LAST_DATA_ACK on error */
|
||||
I2C_SET_DATA(i2c_base, 0xFF);
|
||||
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk);
|
||||
}
|
||||
break;
|
||||
case S_TRAN_DATA_ACK: /* Slave Transmit Data ACK */
|
||||
if (slave_callbacks->read_processed(slave_config, &data_byte) == 0) {
|
||||
/* Non-last data byte */
|
||||
I2C_SET_DATA(i2c_base, data_byte);
|
||||
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk | I2C_CTL0_AA_Msk);
|
||||
} else {
|
||||
/* Go S_TRAN_LAST_DATA_ACK on error */
|
||||
I2C_SET_DATA(i2c_base, 0xFF);
|
||||
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk);
|
||||
}
|
||||
break;
|
||||
case S_TRAN_DATA_NACK: /* Slave Transmit Data NACK */
|
||||
case S_TRAN_LAST_DATA_ACK: /* Slave Transmit Last Data ACK */
|
||||
/* Go slave end */
|
||||
data->slave_xfer.slave_addressed = false;
|
||||
slave_callbacks->stop(slave_config);
|
||||
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk | I2C_CTL0_AA_Msk);
|
||||
break;
|
||||
/* Slave Receive */
|
||||
case S_RECE_DATA_ACK: /* Slave Receive Data ACK */
|
||||
data_byte = I2C_GET_DATA(i2c_base);
|
||||
if (slave_callbacks->write_received(slave_config, data_byte) == 0) {
|
||||
/* Write OK, ACK next data byte */
|
||||
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk | I2C_CTL0_AA_Msk);
|
||||
} else {
|
||||
/* Write FAILED, NACK next data byte */
|
||||
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk);
|
||||
}
|
||||
break;
|
||||
case S_RECE_DATA_NACK: /* Slave Receive Data NACK */
|
||||
/* Go slave end */
|
||||
data->slave_xfer.slave_addressed = false;
|
||||
slave_callbacks->stop(slave_config);
|
||||
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk | I2C_CTL0_AA_Msk);
|
||||
break;
|
||||
case S_RECE_ADDR_ACK: /* Slave Receive Address ACK */
|
||||
case S_RECE_ARB_LOST: /* Slave Receive Arbitration Lost */
|
||||
data->slave_xfer.slave_addressed = true;
|
||||
if (slave_callbacks->write_requested(slave_config) == 0) {
|
||||
/* Write ready, ACK next byte */
|
||||
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk | I2C_CTL0_AA_Msk);
|
||||
} else {
|
||||
/* Write not ready, NACK next byte */
|
||||
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk);
|
||||
}
|
||||
break;
|
||||
case S_REPEAT_START_STOP: /* Slave Transmit/Receive Repeat Start or Stop */
|
||||
/* Go slave end */
|
||||
data->slave_xfer.slave_addressed = false;
|
||||
slave_callbacks->stop(slave_config);
|
||||
I2C_SET_CONTROL_REG(i2c_base, I2C_CTL0_SI_Msk | I2C_CTL0_AA_Msk);
|
||||
break;
|
||||
#endif /* CONFIG_I2C_TARGET */
|
||||
|
||||
case BUS_RELEASED: /* Bus Released */
|
||||
/* Ignore the interrupt raised by BUS_RELEASED. */
|
||||
break;
|
||||
default:
|
||||
__ASSERT(false, "Uncaught I2C FSM state");
|
||||
m_numaker_i2c_master_xfer_end(dev, true);
|
||||
}
|
||||
}
|
||||
|
||||
static int i2c_numaker_init(const struct device *dev)
|
||||
{
|
||||
const struct i2c_numaker_config *config = dev->config;
|
||||
struct i2c_numaker_data *data = dev->data;
|
||||
int err = 0;
|
||||
struct numaker_scc_subsys scc_subsys;
|
||||
|
||||
/* Validate this module's reset object */
|
||||
if (!device_is_ready(config->reset.dev)) {
|
||||
LOG_ERR("reset controller not ready");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Clean mutable context */
|
||||
memset(data, 0x00, sizeof(*data));
|
||||
|
||||
k_sem_init(&data->lock, 1, 1);
|
||||
k_sem_init(&data->master_xfer.xfer_sync, 0, 1);
|
||||
|
||||
SYS_UnlockReg();
|
||||
|
||||
memset(&scc_subsys, 0x00, sizeof(scc_subsys));
|
||||
scc_subsys.subsys_id = NUMAKER_SCC_SUBSYS_ID_PCC;
|
||||
scc_subsys.pcc.clk_modidx = config->clk_modidx;
|
||||
scc_subsys.pcc.clk_src = config->clk_src;
|
||||
scc_subsys.pcc.clk_div = config->clk_div;
|
||||
|
||||
/* Equivalent to CLK_EnableModuleClock() */
|
||||
err = clock_control_on(config->clkctrl_dev, (clock_control_subsys_t) &scc_subsys);
|
||||
if (err != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
/* Equivalent to CLK_SetModuleClock() */
|
||||
err = clock_control_configure(config->clkctrl_dev,
|
||||
(clock_control_subsys_t) &scc_subsys,
|
||||
NULL);
|
||||
if (err != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Configure pinmux (NuMaker's SYS MFP) */
|
||||
err = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT);
|
||||
if (err != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Reset I2C to default state, same as BSP's SYS_ResetModule(id_rst) */
|
||||
reset_line_toggle_dt(&config->reset);
|
||||
|
||||
err = i2c_numaker_configure(dev, I2C_MODE_CONTROLLER | i2c_map_dt_bitrate(config->bitrate));
|
||||
if (err != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
config->irq_config_func(dev);
|
||||
|
||||
cleanup:
|
||||
|
||||
SYS_LockReg();
|
||||
return err;
|
||||
}
|
||||
|
||||
static const struct i2c_driver_api i2c_numaker_driver_api = {
|
||||
.configure = i2c_numaker_configure,
|
||||
.get_config = i2c_numaker_get_config,
|
||||
.transfer = i2c_numaker_transfer,
|
||||
#ifdef CONFIG_I2C_TARGET
|
||||
.target_register = i2c_numaker_slave_register,
|
||||
.target_unregister = i2c_numaker_slave_unregister,
|
||||
#endif
|
||||
.recover_bus = i2c_numaker_recover_bus,
|
||||
};
|
||||
|
||||
#define I2C_NUMAKER_INIT(inst) \
|
||||
PINCTRL_DT_INST_DEFINE(inst); \
|
||||
\
|
||||
static void i2c_numaker_irq_config_func_##inst(const struct device *dev) \
|
||||
{ \
|
||||
IRQ_CONNECT(DT_INST_IRQN(inst), \
|
||||
DT_INST_IRQ(inst, priority), \
|
||||
i2c_numaker_isr, \
|
||||
DEVICE_DT_INST_GET(inst), \
|
||||
0); \
|
||||
\
|
||||
irq_enable(DT_INST_IRQN(inst)); \
|
||||
} \
|
||||
\
|
||||
static const struct i2c_numaker_config i2c_numaker_config_##inst = { \
|
||||
.i2c_base = (I2C_T *) DT_INST_REG_ADDR(inst), \
|
||||
.reset = RESET_DT_SPEC_INST_GET(inst), \
|
||||
.clk_modidx = DT_INST_CLOCKS_CELL(inst, clock_module_index), \
|
||||
.clk_src = DT_INST_CLOCKS_CELL(inst, clock_source), \
|
||||
.clk_div = DT_INST_CLOCKS_CELL(inst, clock_divider), \
|
||||
.clkctrl_dev = DEVICE_DT_GET(DT_PARENT(DT_INST_CLOCKS_CTLR(inst))),\
|
||||
.irq_n = DT_INST_IRQN(inst), \
|
||||
.irq_config_func = i2c_numaker_irq_config_func_##inst, \
|
||||
.pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst), \
|
||||
.bitrate = DT_INST_PROP(inst, clock_frequency), \
|
||||
}; \
|
||||
\
|
||||
static struct i2c_numaker_data i2c_numaker_data_##inst; \
|
||||
\
|
||||
I2C_DEVICE_DT_INST_DEFINE(inst, \
|
||||
i2c_numaker_init, \
|
||||
NULL, \
|
||||
&i2c_numaker_data_##inst, \
|
||||
&i2c_numaker_config_##inst, \
|
||||
POST_KERNEL, \
|
||||
CONFIG_I2C_INIT_PRIORITY, \
|
||||
&i2c_numaker_driver_api);
|
||||
|
||||
DT_INST_FOREACH_STATUS_OKAY(I2C_NUMAKER_INIT);
|
|
@ -10,6 +10,7 @@
|
|||
#include <zephyr/dt-bindings/clock/numaker_m46x_clock.h>
|
||||
#include <zephyr/dt-bindings/reset/numaker_m46x_reset.h>
|
||||
#include <zephyr/dt-bindings/gpio/gpio.h>
|
||||
#include <zephyr/dt-bindings/i2c/i2c.h>
|
||||
|
||||
/ {
|
||||
chosen {
|
||||
|
@ -502,6 +503,66 @@
|
|||
clocks = <&pcc NUMAKER_EMAC0_MODULE 0 0>;
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
i2c0: i2c@40080000 {
|
||||
compatible = "nuvoton,numaker-i2c";
|
||||
clock-frequency = <I2C_BITRATE_STANDARD>;
|
||||
reg = <0x40080000 0x1000>;
|
||||
interrupts = <38 0>;
|
||||
resets = <&rst NUMAKER_I2C0_RST>;
|
||||
clocks = <&pcc NUMAKER_I2C0_MODULE 0 0>;
|
||||
status = "disabled";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
};
|
||||
|
||||
i2c1: i2c@40081000 {
|
||||
compatible = "nuvoton,numaker-i2c";
|
||||
clock-frequency = <I2C_BITRATE_STANDARD>;
|
||||
reg = <0x40081000 0x1000>;
|
||||
interrupts = <39 0>;
|
||||
resets = <&rst NUMAKER_I2C1_RST>;
|
||||
clocks = <&pcc NUMAKER_I2C1_MODULE 0 0>;
|
||||
status = "disabled";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
};
|
||||
|
||||
i2c2: i2c@40082000 {
|
||||
compatible = "nuvoton,numaker-i2c";
|
||||
clock-frequency = <I2C_BITRATE_STANDARD>;
|
||||
reg = <0x40082000 0x1000>;
|
||||
interrupts = <82 0>;
|
||||
resets = <&rst NUMAKER_I2C2_RST>;
|
||||
clocks = <&pcc NUMAKER_I2C2_MODULE 0 0>;
|
||||
status = "disabled";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
};
|
||||
|
||||
i2c3: i2c@40083000 {
|
||||
compatible = "nuvoton,numaker-i2c";
|
||||
clock-frequency = <I2C_BITRATE_STANDARD>;
|
||||
reg = <0x40083000 0x1000>;
|
||||
interrupts = <83 0>;
|
||||
resets = <&rst NUMAKER_I2C3_RST>;
|
||||
clocks = <&pcc NUMAKER_I2C3_MODULE 0 0>;
|
||||
status = "disabled";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
};
|
||||
|
||||
i2c4: i2c@40084000 {
|
||||
compatible = "nuvoton,numaker-i2c";
|
||||
clock-frequency = <I2C_BITRATE_STANDARD>;
|
||||
reg = <0x40084000 0x1000>;
|
||||
interrupts = <118 0>;
|
||||
resets = <&rst NUMAKER_I2C4_RST>;
|
||||
clocks = <&pcc NUMAKER_I2C4_MODULE 0 0>;
|
||||
status = "disabled";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
|
21
dts/bindings/i2c/nuvoton,numaker-i2c.yaml
Normal file
21
dts/bindings/i2c/nuvoton,numaker-i2c.yaml
Normal file
|
@ -0,0 +1,21 @@
|
|||
# Copyright (c) 2023 Nuvoton Technology Corporation
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
description: Nuvoton, NuMaker I2C controller
|
||||
|
||||
compatible: "nuvoton,numaker-i2c"
|
||||
|
||||
include: [i2c-controller.yaml, reset-device.yaml, pinctrl-device.yaml]
|
||||
|
||||
properties:
|
||||
reg:
|
||||
required: true
|
||||
|
||||
interrupts:
|
||||
required: true
|
||||
|
||||
resets:
|
||||
required: true
|
||||
|
||||
clocks:
|
||||
required: true
|
Loading…
Reference in a new issue