rt-thread-official/bsp/nuvoton/libraries/n9h30/rtt_port/drv_i2c.c

575 lines
15 KiB
C

/**************************************************************************//**
*
* @copyright (C) 2020 Nuvoton Technology Corp. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2021-04-20 Wayne First version
******************************************************************************/
#include <rtconfig.h>
#if defined( BSP_USING_I2C)
#include <rtdevice.h>
#include "NuMicro.h"
//#include <drv_i2c.h>
#include <drv_sys.h>
/* Private define ---------------------------------------------------------------*/
#define LOG_TAG "drv.i2c"
#define DBG_ENABLE
#define DBG_SECTION_NAME LOG_TAG
#define DBG_LEVEL DBG_ERROR
#define DBG_COLOR
#include <rtdbg.h>
#define I2C_REG_WRITE(dev, addr, byte) outpw(dev->base + addr, byte)
#define I2C_REG_READ(dev, addr) inpw(dev->base + addr)
#define I2C_DISABLE(dev) I2C_REG_WRITE(dev, I2C_CSR, 0x00) /* Disable i2c core and interrupt */
#define I2C_ENABLE(dev) I2C_REG_WRITE(dev, I2C_CSR, 0x3) /* Enable i2c core and interrupt */
#define I2C_ISBUSFREE(dev) (((I2C_REG_READ(dev, I2C_SWR) & 0x18) == 0x18 && (I2C_REG_READ(dev, I2C_CSR) & 0x0400) == 0) ? 1 : 0)
#define I2C_SIGNAL_TIMEOUT 5000
enum
{
I2C_START = -1,
#if defined(BSP_USING_I2C0)
I2C0_IDX,
#endif
#if defined(BSP_USING_I2C1)
I2C1_IDX,
#endif
I2C_CNT
};
/* Private typedef --------------------------------------------------------------*/
typedef struct
{
int32_t base; /* i2c bus number */
volatile int32_t state;
int32_t addr;
uint32_t last_error;
int32_t bNackValid;
uint32_t subaddr;
int32_t subaddr_len;
volatile uint32_t pos;
volatile uint32_t len;
uint8_t *buffer;
struct rt_completion signal;
} nu_i2c_dev;
typedef nu_i2c_dev *nu_i2c_dev_t;
typedef struct
{
struct rt_i2c_bus_device parent;
char *name;
IRQn_Type irqn;
E_SYS_IPRST rstidx;
E_SYS_IPCLK clkidx;
struct rt_i2c_msg *cur_i2c_msg;
nu_i2c_dev dev;
} nu_i2c_bus ;
typedef nu_i2c_bus *nu_i2c_bus_t;
/* Private variables ------------------------------------------------------------*/
static nu_i2c_bus nu_i2c_arr [ ] =
{
#if defined(BSP_USING_I2C0)
{
.dev =
{
.base = I2C0_BA,
},
.name = "i2c0",
.irqn = IRQ_I2C0,
.rstidx = I2C0RST,
.clkidx = I2C0CKEN,
},
#endif
#if defined(BSP_USING_I2C1)
{
.dev =
{
.base = I2C1_BA,
},
.name = "i2c1",
.irqn = IRQ_I2C1,
.rstidx = I2C1RST,
.clkidx = I2C1CKEN,
},
#endif
};
/* Private functions ------------------------------------------------------------*/
/**
* @brief Set i2c interface speed
* @param[in] dev i2c device structure pointer
* @param[in] sp i2c speed
* @return 0 or I2C_ERR_NOTTY
*/
static int32_t nu_i2c_set_speed(nu_i2c_dev_t psNuI2cDev, int32_t sp)
{
uint32_t d;
if ((sp != 100) && (sp != 400))
return (I2C_ERR_NOTTY);
d = (sysGetClock(SYS_PCLK) * 1000) / (sp * 5) - 1;
I2C_REG_WRITE(psNuI2cDev, I2C_DIVIDER, d & 0xffff);
return 0;
}
/**
* @brief Configure i2c command
* @param[in] dev i2c device structure pointer
* @param[in] cmd command
* @return None
*/
static void nu_i2c_command(nu_i2c_dev_t psNuI2cDev, int32_t cmd)
{
psNuI2cDev->bNackValid = (cmd & I2C_CMD_WRITE) ? 1 : 0;
I2C_REG_WRITE(psNuI2cDev, I2C_CMDR, cmd);
}
/**
* @brief Configure slave address data
* @param[in] dev i2c device structure pointer
* @param[in] mode could be write or read
* @return None
*/
static void nu_i2c_calculate_address(nu_i2c_dev_t psNuI2cDev, int32_t mode)
{
int32_t i;
uint32_t subaddr = psNuI2cDev->subaddr;
psNuI2cDev->buffer[0] = (((psNuI2cDev->addr << 1) & 0xfe) | I2C_WRITE) & 0xff;
for (i = psNuI2cDev->subaddr_len; i > 0; i--)
{
psNuI2cDev->buffer[i] = subaddr & 0xff;
subaddr >>= 8;
}
if (mode == I2C_STATE_READ)
{
i = psNuI2cDev->subaddr_len + 1;
psNuI2cDev->buffer[i] = (((psNuI2cDev->addr << 1) & 0xfe)) | I2C_READ;
}
}
/**
* @brief Reset some variables
* @param[in] dev i2c device structure pointer
* @return None
*/
static void nu_i2c_reset(nu_i2c_dev_t psNuI2cDev)
{
psNuI2cDev->addr = -1;
psNuI2cDev->last_error = 0;
psNuI2cDev->subaddr = 0;
psNuI2cDev->subaddr_len = 0;
}
static void nu_i2c_isr(int vector, void *param)
{
nu_i2c_bus_t psNuI2CBus = (nu_i2c_bus_t)param;
nu_i2c_dev_t psNuI2CDev = (nu_i2c_dev_t)&psNuI2CBus->dev;
struct rt_i2c_msg *pmsg = psNuI2CBus->cur_i2c_msg;
uint32_t msg_flag = pmsg->flags;
uint32_t csr, val;
csr = I2C_REG_READ(psNuI2CDev, I2C_CSR);
csr |= 0x04;
/* Clear interrupt flag */
I2C_REG_WRITE(psNuI2CDev, I2C_CSR, csr);
if (psNuI2CDev->state == I2C_STATE_NOP)
return;
/* NACK only valid in WRITE */
if ((csr & 0x800) && psNuI2CDev->bNackValid && !(msg_flag & RT_I2C_IGNORE_NACK))
{
rt_kprintf("I2C W/ NACK\n");
psNuI2CDev->last_error = I2C_ERR_NACK;
nu_i2c_command(psNuI2CDev, I2C_CMD_STOP);
psNuI2CDev->state = I2C_STATE_NOP;
rt_completion_done(&psNuI2CDev->signal);
}
/* Arbitration lost */
else if (csr & 0x200)
{
rt_kprintf("Arbitration lost\n");
psNuI2CDev->last_error = I2C_ERR_LOSTARBITRATION;
psNuI2CDev->state = I2C_STATE_NOP;
rt_completion_done(&psNuI2CDev->signal);
}
/* Transmit complete */
else if (!(csr & 0x100))
{
/* Send address state */
if (psNuI2CDev->pos < psNuI2CDev->subaddr_len + 1)
{
val = psNuI2CDev->buffer[psNuI2CDev->pos++] & 0xff;
I2C_REG_WRITE(psNuI2CDev, I2C_TxR, val);
nu_i2c_command(psNuI2CDev, I2C_CMD_WRITE);
}
else if (psNuI2CDev->state == I2C_STATE_READ)
{
/* Sub-address send over, begin restart a read command */
if (psNuI2CDev->pos == psNuI2CDev->subaddr_len + 1)
{
val = psNuI2CDev->buffer[psNuI2CDev->pos++];
I2C_REG_WRITE(psNuI2CDev, I2C_TxR, val);
nu_i2c_command(psNuI2CDev, I2C_CMD_START | I2C_CMD_WRITE);
}
else
{
psNuI2CDev->buffer[psNuI2CDev->pos++] = I2C_REG_READ(psNuI2CDev, I2C_RxR) & 0xff;
if (psNuI2CDev->pos < psNuI2CDev->len)
{
/* Last character */
if (psNuI2CDev->pos == psNuI2CDev->len - 1)
nu_i2c_command(psNuI2CDev, I2C_CMD_READ | I2C_CMD_STOP | I2C_CMD_NACK);
else
nu_i2c_command(psNuI2CDev, I2C_CMD_READ);
}
else
{
psNuI2CDev->state = I2C_STATE_NOP;
rt_completion_done(&psNuI2CDev->signal);
}
}
}
/* Write data */
else if (psNuI2CDev->state == I2C_STATE_WRITE)
{
if (psNuI2CDev->pos < psNuI2CDev->len)
{
val = psNuI2CDev->buffer[psNuI2CDev->pos];
I2C_REG_WRITE(psNuI2CDev, I2C_TxR, val);
/* Last character */
if (psNuI2CDev->pos == psNuI2CDev->len - 1)
nu_i2c_command(psNuI2CDev, I2C_CMD_WRITE | I2C_CMD_STOP);
else
nu_i2c_command(psNuI2CDev, I2C_CMD_WRITE);
psNuI2CDev->pos ++;
}
else
{
psNuI2CDev->state = I2C_STATE_NOP;
rt_completion_done(&psNuI2CDev->signal);
}
}
}
}
/**
* @brief Read data from I2C slave.
* @param[in] psNuI2cDev is interface structure pointer.
* @param[in] pmsg is pointer of rt i2c message structure.
* @return read status.
* @retval >0 length when success.
* @retval I2C_ERR_BUSY Interface busy.
* @retval I2C_ERR_IO Interface not opened.
* @retval I2C_ERR_NODEV No such device.
* @retval I2C_ERR_NACK Slave returns an erroneous ACK.
* @retval I2C_ERR_LOSTARBITRATION arbitration lost happen.
*/
static int32_t nu_i2c_read(nu_i2c_dev_t psNuI2cDev, struct rt_i2c_msg *pmsg)
{
uint8_t *buf = pmsg->buf;
uint32_t len = pmsg->len;
RT_ASSERT(len);
RT_ASSERT(buf);
if (len > I2C_MAX_BUF_LEN - 10)
len = I2C_MAX_BUF_LEN - 10;
psNuI2cDev->state = I2C_STATE_READ;
psNuI2cDev->pos = 1;
/* Current ISR design will get one garbage byte */
/* plus 1 unused char */
psNuI2cDev->len = psNuI2cDev->subaddr_len + 1 + len + 2;
psNuI2cDev->last_error = 0;
/* Get slave address */
nu_i2c_calculate_address(psNuI2cDev, I2C_STATE_READ);
/* Enable I2C-EN */
I2C_ENABLE(psNuI2cDev);
/* Send first byte to transfer the message. */
I2C_REG_WRITE(psNuI2cDev, I2C_TxR, psNuI2cDev->buffer[0] & 0xff);
if (!I2C_ISBUSFREE(psNuI2cDev))
return (I2C_ERR_BUSY);
rt_completion_init(&psNuI2cDev->signal);
nu_i2c_command(psNuI2cDev, I2C_CMD_START | I2C_CMD_WRITE);
if ((RT_EOK == rt_completion_wait(&psNuI2cDev->signal, I2C_SIGNAL_TIMEOUT)))
{
rt_memcpy(buf, psNuI2cDev->buffer + psNuI2cDev->subaddr_len + 3, len);
psNuI2cDev->subaddr += len;
}
else
{
rt_kprintf("[%s]Wait signal timeout.\n", __func__);
len = 0;
}
/* Disable I2C-EN */
I2C_DISABLE(psNuI2cDev);
if (psNuI2cDev->last_error)
return (psNuI2cDev->last_error);
return len;
}
/**
* @brief Write data from I2C slave.
* @param[in] psNuI2cDev is interface structure pointer.
* @param[in] pmsg is pointer of rt i2c message structure.
* @return write status.
* @retval >0 length when success.
* @retval I2C_ERR_BUSY Interface busy.
* @retval I2C_ERR_IO Interface not opened.
* @retval I2C_ERR_NODEV No such device.
* @retval I2C_ERR_NACK Slave returns an erroneous ACK.
* @retval I2C_ERR_LOSTARBITRATION arbitration lost happen.
*/
static int32_t nu_i2c_write(nu_i2c_dev_t psNuI2cDev, struct rt_i2c_msg *pmsg)
{
uint8_t *buf = pmsg->buf;
uint32_t len = pmsg->len;
RT_ASSERT(len);
RT_ASSERT(buf);
if (len > I2C_MAX_BUF_LEN - 10)
len = I2C_MAX_BUF_LEN - 10;
rt_memcpy(psNuI2cDev->buffer + psNuI2cDev->subaddr_len + 1, buf, len);
psNuI2cDev->state = I2C_STATE_WRITE;
psNuI2cDev->pos = 1;
psNuI2cDev->len = psNuI2cDev->subaddr_len + 1 + len;
psNuI2cDev->last_error = 0;
/* Get slave address */
nu_i2c_calculate_address(psNuI2cDev, I2C_STATE_WRITE);
/* Enable I2C-EN */
I2C_ENABLE(psNuI2cDev);
/* Send first byte to transfer the message. */
I2C_REG_WRITE(psNuI2cDev, I2C_TxR, psNuI2cDev->buffer[0] & 0xff);
if (!I2C_ISBUSFREE(psNuI2cDev))
return (I2C_ERR_BUSY);
rt_completion_init(&psNuI2cDev->signal);
nu_i2c_command(psNuI2cDev, I2C_CMD_START | I2C_CMD_WRITE);
if ((RT_EOK == rt_completion_wait(&psNuI2cDev->signal, I2C_SIGNAL_TIMEOUT)))
{
psNuI2cDev->subaddr += len;
}
else
{
rt_kprintf("[%s]Wait signal timeout.\n", __func__);
len = 0;
}
/* Disable I2C-EN */
I2C_DISABLE(psNuI2cDev);
if (psNuI2cDev->last_error)
return (psNuI2cDev->last_error);
return len;
}
/**
* @brief Support some I2C driver commands for application.
* @param[in] psNuI2cDev is interface structure pointer.
* @param[in] cmd is command.
* @param[in] arg0 is the first argument of command.
* @param[in] arg1 is the second argument of command.
* @return command status.
* @retval 0 Success.
* @retval I2C_ERR_IO Interface not opened.
* @retval I2C_ERR_NODEV No such device.
* @retval I2C_ERR_NOTTY Command not support, or parameter error.
*/
static int32_t nu_i2c_ioctl(nu_i2c_dev_t psNuI2cDev, uint32_t cmd, uint32_t arg0, uint32_t arg1)
{
switch (cmd)
{
case I2C_IOC_SET_DEV_ADDRESS:
psNuI2cDev->addr = arg0;
break;
case I2C_IOC_SET_SPEED:
return nu_i2c_set_speed(psNuI2cDev, (int32_t)arg0);
case I2C_IOC_SET_SUB_ADDRESS:
if (arg1 > 4)
{
return (I2C_ERR_NOTTY);
}
psNuI2cDev->subaddr = arg0;
psNuI2cDev->subaddr_len = arg1;
break;
default:
return (I2C_ERR_NOTTY);
}
return (0);
}
static rt_ssize_t nu_i2c_mst_xfer(struct rt_i2c_bus_device *bus,
struct rt_i2c_msg msgs[],
rt_uint32_t num)
{
nu_i2c_bus_t psNuI2cBus;
nu_i2c_dev_t psNuI2cDev;
rt_size_t i;
rt_err_t ret;
struct rt_i2c_msg *pmsg;
RT_ASSERT(bus);
psNuI2cBus = (nu_i2c_bus_t) bus;
psNuI2cDev = &psNuI2cBus->dev;
for (i = 0; i < num; i++)
{
if (!I2C_ISBUSFREE(psNuI2cDev))
break;
pmsg = psNuI2cBus->cur_i2c_msg = &msgs[i];
/* Not support 10bit. */
if ((pmsg->flags & RT_I2C_ADDR_10BIT)
|| (pmsg->len == 0))
break;
/* Set device address */
nu_i2c_reset(psNuI2cDev);
nu_i2c_ioctl(psNuI2cDev, I2C_IOC_SET_DEV_ADDRESS, pmsg->addr, 0);
if (pmsg->flags & RT_I2C_RD)
{
ret = nu_i2c_read(psNuI2cDev, pmsg);
}
else
{
ret = nu_i2c_write(psNuI2cDev, pmsg);
}
if (ret != pmsg->len) break;
}
return i;
}
static rt_err_t nu_i2c_bus_control(struct rt_i2c_bus_device *bus, int cmd, void *args)
{
nu_i2c_bus_t psNuI2cBus;
nu_i2c_dev_t psNuI2cDev;
RT_ASSERT(bus);
psNuI2cBus = (nu_i2c_bus_t) bus;
psNuI2cDev = &psNuI2cBus->dev;
switch (cmd)
{
case RT_I2C_DEV_CTRL_CLK:
nu_i2c_set_speed(psNuI2cDev, *(int32_t *)args);
break;
default:
return -RT_EIO;
}
return RT_EOK;
}
static const struct rt_i2c_bus_device_ops nu_i2c_ops =
{
.master_xfer = nu_i2c_mst_xfer,
.slave_xfer = NULL,
.i2c_bus_control = nu_i2c_bus_control,
};
/* Public functions -------------------------------------------------------------*/
int rt_hw_i2c_init(void)
{
int i;
rt_err_t ret;
for (i = (I2C_START + 1); i < I2C_CNT; i++)
{
nu_i2c_dev_t psNuI2cDev = &nu_i2c_arr[i].dev;
nu_i2c_arr[i].parent.ops = &nu_i2c_ops;
psNuI2cDev->buffer = rt_malloc(I2C_MAX_BUF_LEN);
RT_ASSERT(psNuI2cDev->buffer);
/* Enable I2C engine clock and reset. */
nu_sys_ipclk_enable(nu_i2c_arr[i].clkidx);
nu_sys_ip_reset(nu_i2c_arr[i].rstidx);
nu_i2c_ioctl(psNuI2cDev, I2C_IOC_SET_SPEED, 100, 0);
/* Register ISR and Respond IRQ. */
rt_hw_interrupt_install(nu_i2c_arr[i].irqn, nu_i2c_isr, &nu_i2c_arr[i], nu_i2c_arr[i].name);
rt_hw_interrupt_umask(nu_i2c_arr[i].irqn);
ret = rt_i2c_bus_device_register(&nu_i2c_arr[i].parent, nu_i2c_arr[i].name);
RT_ASSERT(RT_EOK == ret);
}
return 0;
}
INIT_DEVICE_EXPORT(rt_hw_i2c_init);
#endif /* BSP_USING_I2C */