2025-12-17 15:55:25 +08:00

370 lines
12 KiB
C

/*
* Kneron Peripheral API - I2C
*
* Copyright (C) 2019 Kneron, Inc. All rights reserved.
*
*/
#include <stdlib.h>
#include "kdrv_scu.h"
#include "kdrv_clock.h" // for kdrv_delay_us
//#include "kdp_io.h"
#include "kdrv_i2c.h"
//#include "kmdw_console.h"
#include "system_config.h"
#define I2C_ENABLE_THREAD_SYNC // enable I2C thread synchronization
//#define I2C_DEBUG
/************************************************************************
* I2C Register Releated Macros
************************************************************************/
#define I2CRegRead(id, reg_offset) inw(IIC_FTIIC010_0_PA_BASE + id * 0x100000 + reg_offset)
#define I2CRegWrite(id, reg_offset, val) outw(IIC_FTIIC010_0_PA_BASE + id * 0x100000 + reg_offset, val)
#define I2CRegMaskedSet(id, reg_offset, val) masked_outw(IIC_FTIIC010_0_PA_BASE + id * 0x100000 + reg_offset, val, val)
#define I2CRegMaskedClr(id, reg_offset, val) masked_outw(IIC_FTIIC010_0_PA_BASE + id * 0x100000 + reg_offset, 0, val)
// I2C Control Register
#define REG_I2C_CR 0x00
#define CR_ALIRQ BIT13 /* arbitration lost interrupt (master) */
#define CR_SAMIRQ BIT12 /* slave address match interrupt (slave) */
#define CR_STOPIRQ BIT11 /* stop condition interrupt (slave) */
#define CR_NAKRIRQ BIT10 /* NACK response interrupt (master) */
#define CR_DRIRQ BIT9 /* rx interrupt (both) */
#define CR_DTIRQ BIT8 /* tx interrupt (both) */
#define CR_TBEN BIT7 /* tx enable (both) */
#define CR_NAK BIT6 /* NACK (both) */
#define CR_STOP BIT5 /* stop (master) */
#define CR_START BIT4 /* start (master) */
#define CR_GCEN BIT3 /* general call support (slave) */
#define CR_MST_EN BIT2 /* enable clock out (master) */
#define CR_I2C_EN BIT1 /* enable I2C (both) */
#define CR_I2C_RST BIT0 /* reset I2C (both) */
#define CR_ENABLE \
(CR_ALIRQ | CR_NAKRIRQ | CR_DRIRQ | CR_DTIRQ | CR_MST_EN | CR_I2C_EN)
// I2C Status Register
#define REG_I2C_SR 0x04
#define SR_SBS BIT23 /* start byte */
#define SR_HSS BIT22 /* high speed mode */
#define SR_START BIT11 /* start */
#define SR_AL BIT10 /* arbitration lost */
#define SR_GC BIT9 /* general call */
#define SR_SAM BIT8 /* slave address match */
#define SR_STOP BIT7 /* stop received */
#define SR_NACK BIT6 /* NACK received */
#define SR_TD BIT5 /* transfer done */
#define SR_BB BIT3 /* bus busy */
#define SR_I2CB BIT2 /* chip busy */
#define SR_RW BIT0 /* set when master-rx or slave-tx mode */
// I2C Clock Division Register
#define REG_I2C_CDR 0x08
#define CDR_COUNT_100KHZ \
((uint32_t)((APB_CLOCK) / (2 * 100000) - (GSR_VALUE / 2) - 2)) // if APB=100, this will be 496
#define CDR_COUNT_400KHZ \
((uint32_t)((APB_CLOCK) / (2 * 400000) - (GSR_VALUE / 2) - 2)) // if APB=100, this will be 121
#define CDR_COUNT_1MHZ \
((uint32_t)((APB_CLOCK) / (2 * 1000000) - (GSR_VALUE / 2) - 2)) // if APB=100, this will be 46
// I2C Data Register
#define REG_I2C_DR 0x0C
// I2C Address Register
#define REG_I2C_AR 0x10
// I2C Set/Hold Time and Glitch Suppresion Setting Register
#define REG_I2C_TGSR 0x14
#define GSR_VALUE 0x5
#define TSR_VALUE 0x5
// I2C Bus Monitor Register
#define REG_I2C_BMR 0x18
// I2C Burst Mode Register
#define REG_I2C_BSTMR 0x1C
// I2C Revision Register
#define REG_I2C_REVISION 0x30
#ifdef I2C_DEBUG
void i2c_dump_register(kdrv_i2c_ctrl_t ctrl_id)
{
info_msg("APB clock = %d\n", APB_CLOCK);
info_msg("(0x00) control register = 0x%x\n", I2CRegRead(ctrl_id, REG_I2C_CR));
info_msg("(0x04) status register = 0x%x\n", I2CRegRead(ctrl_id, REG_I2C_SR));
info_msg("(0x08) clock division = 0x%x\n", I2CRegRead(ctrl_id, REG_I2C_CDR));
info_msg("(0x0C) data register = 0x%x\n", I2CRegRead(ctrl_id, REG_I2C_DR));
info_msg("(0x10) address register = 0x%x\n", I2CRegRead(ctrl_id, REG_I2C_AR));
info_msg("(0x14) TGSR register = 0x%x\n", I2CRegRead(ctrl_id, REG_I2C_TGSR));
info_msg("(0x18) bus monitor = 0x%x\n", I2CRegRead(ctrl_id, REG_I2C_BMR));
info_msg("(0x1C) burst mode = 0x%x\n", I2CRegRead(ctrl_id, REG_I2C_BSTMR));
info_msg("(0x30) revision = 0x%x\n\n", I2CRegRead(ctrl_id, REG_I2C_REVISION));
}
#endif
static void i2c_power(kdrv_i2c_ctrl_t ctrl_id, int on)
{
uint32_t mask, val;
mask = SCU_REG_APBCLKG_PCLK_EN_I2C0_PCLK << ctrl_id;
if (on)
val = mask;
else
val = 0;
masked_outw(SCU_REG_APBCLKG, val, mask);
kdrv_delay_us(500);
}
static kdrv_status_t i2c_polling_completion(kdrv_i2c_ctrl_t ctrl_id, bool ignore_NAK)
{
// polling SR and take actions correspondingly
for (int i = 0; i < 10000; i++)
{
uint32_t sr_status = I2CRegRead(ctrl_id, REG_I2C_SR);
I2CRegWrite(ctrl_id, REG_I2C_SR, sr_status);
if (!ignore_NAK && (sr_status & SR_NACK))
return KDRV_STATUS_I2C_DEVICE_NACK;
else if (sr_status & (SR_AL))
return KDRV_STATUS_I2C_BUS_BUSY;
else if (sr_status & SR_TD)
return KDRV_STATUS_OK;
}
return KDRV_STATUS_I2C_TIMEOUT;
}
static bool i2c_waiting_for_bus_available(kdrv_i2c_ctrl_t ctrl_id)
{
for (int i = 0; i < 10000; i++)
{
if (I2CRegRead(ctrl_id, REG_I2C_SR) & SR_BB)
continue;
else
return true;
}
return false;
}
// data1 and num1 are first part of data
// data2 and num2 are first part of data
// doing this is for kdrv_i2c_write_register(), kind of workaround way of implementation
// data 1 must not be NULL, num1 must be greater than 0
// data 2 can be NULL, num2 can be 0 if no 2nd part of data
static kdrv_status_t i2c_tx_rx(kdrv_i2c_ctrl_t ctrl_id, uint16_t slave_addr,
uint8_t *data1, uint32_t num1, uint8_t *data2, uint32_t num2, bool isRead, bool with_STOP)
{
kdrv_status_t status = KDRV_STATUS_OK;
bool ignore_NAK = false;
#ifdef I2C_ENABLE_THREAD_SYNC
static osMutexId_t mutex_i2c = NULL;
if (mutex_i2c == NULL)
{
mutex_i2c = osMutexNew(NULL);
#ifdef I2C_DEBUG
if (mutex_i2c == NULL)
kmdw_printf("kdrv_i2c: %s: create mutex failed\n", __FUNCTION__);
#endif
}
osStatus_t osts = osMutexAcquire(mutex_i2c, 0);
#ifdef I2C_DEBUG
if (osts != osOK)
kmdw_printf("kdrv_i2c: %s: mutex lock failed, osStatus_t error = %d\n", __FUNCTION__, status);
#endif
#endif
do
{
if (!i2c_waiting_for_bus_available(ctrl_id))
{
status = KDRV_STATUS_I2C_BUS_BUSY;
break;
}
// send slave address and check status
// write address byte into DR
// TODO: support 10-bit address
uint8_t byte_addr = (uint8_t)((slave_addr << 1) & 0xFE);
if (isRead)
byte_addr |= 0x1;
I2CRegWrite(ctrl_id, REG_I2C_DR, byte_addr);
// send out slave address with START condition by setting CR
I2CRegWrite(ctrl_id, REG_I2C_CR, (CR_I2C_EN | CR_MST_EN | CR_TBEN | CR_START));
// wait for complete status
status = i2c_polling_completion(ctrl_id, ignore_NAK);
if (status != KDRV_STATUS_OK)
{
if (status == KDRV_STATUS_I2C_DEVICE_NACK)
I2CRegMaskedSet(ctrl_id, REG_I2C_CR, CR_STOP);
break;
}
if (!i2c_waiting_for_bus_available(ctrl_id))
{
status = KDRV_STATUS_I2C_BUS_BUSY;
break;
}
uint32_t num = num1 + num2;
uint8_t *data = data1;
// send or receive data
for (int i = 0; i < num; i++)
{
ignore_NAK = false;
uint32_t ctrl_flag = (CR_I2C_EN | CR_MST_EN | CR_TBEN);
if (with_STOP && (i == (num - 1)))
{
ctrl_flag |= CR_STOP;
if (isRead)
{
ctrl_flag |= CR_NAK;
ignore_NAK = true;
}
}
if (!isRead) // write data to the DR
I2CRegWrite(ctrl_id, REG_I2C_DR, *data);
// start transmission
I2CRegWrite(ctrl_id, REG_I2C_CR, ctrl_flag);
// check status
// wait for complete status
status = i2c_polling_completion(ctrl_id, ignore_NAK);
if (status != KDRV_STATUS_OK)
{
if (status == KDRV_STATUS_I2C_DEVICE_NACK)
I2CRegMaskedSet(ctrl_id, REG_I2C_CR, CR_STOP);
break;
}
if (isRead) // read data
*data = (uint8_t)(0xFF & I2CRegRead(ctrl_id, REG_I2C_DR));
++data;
// switch to 2nd part of data buffer, so strange ...
if ((i + 1) == num1)
data = data2;
}
} while (0);
#ifdef I2C_ENABLE_THREAD_SYNC
osMutexRelease(mutex_i2c);
#endif
return status;
}
/*******************************************************************/
/*** Public APIs ***/
/*******************************************************************/
kdrv_status_t kdrv_i2c_initialize(kdrv_i2c_ctrl_t ctrl_id, kdrv_i2c_bus_speed_t bus_speed)
{
if ((ctrl_id >= TOTAL_KDRV_I2C_CTRL) || (bus_speed > KDRV_I2C_SPEED_1M))
return KDRV_STATUS_INVALID_PARAM;
i2c_power(ctrl_id, 1);
// reset i2c controller
I2CRegMaskedSet(ctrl_id, REG_I2C_CR, CR_I2C_RST);
int i;
for (i = 0; i < 500; i++)
if (!(I2CRegRead(ctrl_id, REG_I2C_CR) & CR_I2C_RST))
break;
if (i >= 500)
{
return KDRV_STATUS_ERROR;
}
// set bus speed, SCL = PCLK / (2 * COUNT + GSR + 4)
// set GSR and TSR value
I2CRegWrite(ctrl_id, REG_I2C_TGSR, (GSR_VALUE << 10) | TSR_VALUE);
// select CDR Count value depending on bus speed
uint32_t count_bits;
switch (bus_speed)
{
case KDRV_I2C_SPEED_100K:
count_bits = CDR_COUNT_100KHZ;
break;
case KDRV_I2C_SPEED_400K:
count_bits = CDR_COUNT_400KHZ;
break;
case KDRV_I2C_SPEED_1M:
count_bits = CDR_COUNT_1MHZ;
break;
default:
// could be user specified clock in Hz
count_bits = ((APB_CLOCK) / (2 * (uint32_t)bus_speed) - (GSR_VALUE / 2) - 2);
break;
}
I2CRegWrite(ctrl_id, REG_I2C_CDR, count_bits); // not care about COUNTH and DUTY bits
return KDRV_STATUS_OK;
}
kdrv_status_t kdrv_i2c_uninitialize(kdrv_i2c_ctrl_t ctrl_id)
{
i2c_power(ctrl_id, 0);
return KDRV_STATUS_OK;
}
kdrv_status_t kdp_i2c_transmit(
kdrv_i2c_ctrl_t ctrl_id, uint16_t slave_addr, uint8_t *data, uint32_t num, bool with_STOP)
{
return i2c_tx_rx(ctrl_id, slave_addr, data, num, NULL, 0, false, with_STOP);
}
kdrv_status_t kdp_i2c_receive(
kdrv_i2c_ctrl_t ctrl_id, uint16_t slave_addr, uint8_t *data, uint32_t num, bool with_STOP)
{
return i2c_tx_rx(ctrl_id, slave_addr, data, num, NULL, 0, true, with_STOP);
}
kdrv_status_t kdrv_i2c_write_register(
kdrv_i2c_ctrl_t ctrl_id, uint16_t slave_addr, uint16_t reg, uint16_t reg_size, uint16_t len, uint8_t *data)
{
uint8_t reg_data[2];
int32_t i = 0;
if (reg_size == 2)
reg_data[i++] = reg >> 8; // store MSB of reg
reg_data[i++] = reg & 0xff;
return i2c_tx_rx(ctrl_id, slave_addr, reg_data, reg_size, data, len, false, true);
}
kdrv_status_t kdrv_i2c_read_register(
kdrv_i2c_ctrl_t ctrl_id, uint16_t slave_addr, uint16_t reg, uint16_t reg_size, uint16_t len, uint8_t *data)
{
uint8_t reg_data[2];
int32_t i = 0;
if (reg_size == 2)
reg_data[i++] = reg >> 8; // store MSB of reg
reg_data[i++] = reg & 0xff;
// first write register address to the device without STOP condition
kdrv_status_t ret = i2c_tx_rx(ctrl_id, slave_addr, reg_data, reg_size, NULL, 0, false, false);
// then read data from device
if (ret == KDRV_STATUS_OK)
ret = i2c_tx_rx(ctrl_id, slave_addr, data, len, NULL, 0, true, true);
return ret;
}