531 lines
14 KiB
C
531 lines
14 KiB
C
/*
|
|
* This file is part of FH8620 BSP for RT-Thread distribution.
|
|
*
|
|
* Copyright (c) 2016 Shanghai Fullhan Microelectronics Co., Ltd.
|
|
* All rights reserved
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Visit http://www.fullhan.com to get contact with Fullhan.
|
|
*
|
|
* Change Logs:
|
|
* Date Author Notes
|
|
*/
|
|
|
|
#include <rtdevice.h>
|
|
#include <rthw.h>
|
|
#include "i2c.h"
|
|
#include "inc/fh_driverlib.h"
|
|
#include "board_info.h"
|
|
|
|
//#define FH_I2C_DEBUG
|
|
|
|
#ifdef FH_I2C_DEBUG
|
|
#define PRINT_I2C_DBG(fmt, args...) \
|
|
do \
|
|
{ \
|
|
rt_kprintf("FH_I2C_DEBUG: "); \
|
|
rt_kprintf(fmt, ## args); \
|
|
} \
|
|
while(0)
|
|
#else
|
|
#define PRINT_I2C_DBG(fmt, args...) do { } while (0)
|
|
#endif
|
|
|
|
|
|
|
|
static void fh_i2c_xfer_init(struct rt_i2c_bus_device *dev, struct rt_i2c_msg msgs[], rt_uint32_t num)
|
|
{
|
|
struct i2c_driver *i2c_drv = (struct i2c_driver *)dev->priv;
|
|
struct fh_i2c_obj *i2c_obj = (struct fh_i2c_obj *)i2c_drv->priv;
|
|
rt_uint32_t ic_con;
|
|
|
|
/* if the slave address is ten bit address, ERROR*/
|
|
if (msgs[i2c_drv->msg_write_idx].flags & I2C_M_TEN)
|
|
{
|
|
rt_kprintf("ERROR: %s, ten bit address is NOT supported\n", __func__);
|
|
return;
|
|
}
|
|
|
|
/* Disable the adapter */
|
|
I2C_WaitMasterIdle(i2c_obj);
|
|
|
|
I2C_Enable(i2c_obj, RT_FALSE);
|
|
|
|
/* set the slave (target) address */
|
|
I2C_SetSlaveAddress(i2c_obj, msgs[i2c_drv->msg_write_idx].addr);
|
|
|
|
/* Enable interrupts */
|
|
I2C_SetInterruptMask(i2c_obj, DW_IC_INTR_DEFAULT_MASK);
|
|
|
|
/* Enable the adapter */
|
|
I2C_Enable(i2c_obj, RT_TRUE);
|
|
}
|
|
|
|
|
|
static rt_size_t fh_i2c_xfer(struct rt_i2c_bus_device *dev,
|
|
struct rt_i2c_msg msgs[], rt_uint32_t num)
|
|
{
|
|
struct i2c_driver *i2c_drv = (struct i2c_driver *)dev->priv;
|
|
struct fh_i2c_obj *i2c_obj = (struct fh_i2c_obj *)i2c_drv->priv;
|
|
int ret;
|
|
struct rt_i2c_msg *pmsg = RT_NULL;
|
|
|
|
PRINT_I2C_DBG(">>>>>>>>>>>>>%s start\n", __func__);
|
|
|
|
rt_completion_init(&i2c_drv->transfer_completion);
|
|
|
|
ret = rt_mutex_take(i2c_drv->lock, RT_WAITING_FOREVER);
|
|
if (ret != RT_EOK) {
|
|
goto done;
|
|
}
|
|
|
|
i2c_drv->msgs = msgs;
|
|
i2c_drv->msgs_num = num;
|
|
i2c_drv->msg_read_idx = 0;
|
|
i2c_drv->msg_write_idx = 0;
|
|
i2c_drv->cmd_err = 0;
|
|
i2c_drv->msg_err = 0;
|
|
i2c_drv->status = STATUS_IDLE;
|
|
i2c_obj->abort_source = 0;
|
|
|
|
ret = I2C_WaitDeviceIdle(i2c_obj);
|
|
if (ret < 0)
|
|
{
|
|
//I2C_SetDataCmd(i2c_obj, 0x200);
|
|
//goto done;
|
|
}
|
|
|
|
fh_i2c_xfer_init(dev, msgs, num);
|
|
|
|
ret = rt_completion_wait(&i2c_drv->transfer_completion, RT_TICK_PER_SECOND);
|
|
PRINT_I2C_DBG("%s transfer finished\n", "rt_completion_wait");
|
|
if(ret)
|
|
{
|
|
rt_kprintf("ERROR: %s, transfer timeout\n", __func__);
|
|
I2C_SetDataCmd(i2c_obj, 0x200);
|
|
I2C_Init(i2c_obj);
|
|
ret = -RT_ETIMEOUT;
|
|
goto done;
|
|
}
|
|
|
|
if (i2c_drv->msg_err)
|
|
{
|
|
rt_kprintf("i2c_priv->msg_err: %d\n", i2c_drv->msg_err);
|
|
ret = i2c_drv->msg_err;
|
|
goto done;
|
|
}
|
|
|
|
/* no error */
|
|
if (!i2c_drv->cmd_err)
|
|
{
|
|
/* Disable the adapter */
|
|
I2C_WaitMasterIdle(i2c_obj);
|
|
I2C_Enable(i2c_obj, RT_FALSE);
|
|
ret = num;
|
|
goto done;
|
|
}
|
|
|
|
/* We have an error */
|
|
if (i2c_drv->cmd_err == DW_IC_ERR_TX_ABRT)
|
|
{
|
|
rt_kprintf("ERROR: %s, i2c_priv>cmd_err == DW_IC_ERR_TX_ABRT\n", __func__);
|
|
ret = I2C_HandleTxAbort(i2c_obj);
|
|
goto done;
|
|
}
|
|
|
|
ret = 1;
|
|
|
|
done:
|
|
I2C_Enable(i2c_obj, RT_FALSE);
|
|
rt_mutex_release(i2c_drv->lock);
|
|
PRINT_I2C_DBG(">>>>>>>>>>>>>%s end\n", __func__);
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
* Initiate (and continue) low level master read/write transaction.
|
|
* This function is only called from i2c_fh_isr, and pumping i2c_msg
|
|
* messages into the tx buffer. Even if the size of i2c_msg data is
|
|
* longer than the size of the tx buffer, it handles everything.
|
|
*/
|
|
static void i2c_fh_xfer_msg(struct rt_i2c_bus_device *dev)
|
|
{
|
|
struct i2c_driver *i2c_drv = (struct i2c_driver *)dev->priv;
|
|
struct fh_i2c_obj *i2c_obj = (struct fh_i2c_obj *)i2c_drv->priv;
|
|
struct rt_i2c_msg *msgs = i2c_drv->msgs;
|
|
rt_uint32_t intr_mask, cmd;
|
|
int tx_limit, rx_limit;
|
|
rt_uint32_t addr = msgs[i2c_drv->msg_write_idx].addr;
|
|
rt_uint32_t buf_len = i2c_drv->tx_buf_len;
|
|
rt_uint8_t *buf = i2c_drv->tx_buf;
|
|
|
|
PRINT_I2C_DBG("%s start, msgs_num: %d, write_idx: %d\n", __func__, i2c_drv->msgs_num, i2c_drv->msg_write_idx);
|
|
|
|
intr_mask = DW_IC_INTR_DEFAULT_MASK;
|
|
|
|
for (; i2c_drv->msg_write_idx < i2c_drv->msgs_num; i2c_drv->msg_write_idx++)
|
|
{
|
|
/*
|
|
* if target address has changed, we need to
|
|
* reprogram the target address in the i2c
|
|
* adapter when we are done with this transfer
|
|
*/
|
|
if (msgs[i2c_drv->msg_write_idx].addr != addr) {
|
|
rt_kprintf(
|
|
"ERROR: %s, invalid target address\n", __func__);
|
|
i2c_drv->msg_err = 1;
|
|
break;
|
|
}
|
|
|
|
if (msgs[i2c_drv->msg_write_idx].len == 0) {
|
|
rt_kprintf(
|
|
"ERROR: %s, invalid message length\n", __func__);
|
|
i2c_drv->msg_err = 1;
|
|
break;
|
|
}
|
|
|
|
if (!(i2c_drv->status & STATUS_WRITE_IN_PROGRESS))
|
|
{
|
|
/* new i2c_msg */
|
|
buf = msgs[i2c_drv->msg_write_idx].buf;
|
|
buf_len = msgs[i2c_drv->msg_write_idx].len;
|
|
|
|
PRINT_I2C_DBG("new msg: len: %d, buf: 0x%x\n", buf_len, buf[0]);
|
|
}
|
|
|
|
tx_limit = i2c_obj->config.tx_fifo_depth - I2C_GetTransmitFifoLevel(i2c_obj);
|
|
rx_limit = i2c_obj->config.rx_fifo_depth - I2C_GetReceiveFifoLevel(i2c_obj);
|
|
|
|
while (buf_len > 0 && tx_limit > 0 && rx_limit > 0)
|
|
{
|
|
if (msgs[i2c_drv->msg_write_idx].flags & RT_I2C_RD)
|
|
{
|
|
cmd = 0x100;
|
|
rx_limit--;
|
|
}
|
|
else
|
|
{
|
|
cmd = *buf++;
|
|
}
|
|
|
|
tx_limit--; buf_len--;
|
|
|
|
if(!buf_len)
|
|
{
|
|
//2015-11-8 ar0130 bug fixed
|
|
while(I2C_GetTransmitFifoLevel(i2c_obj));
|
|
cmd |= 0x200;
|
|
}
|
|
|
|
I2C_SetDataCmd(i2c_obj, cmd);
|
|
}
|
|
|
|
i2c_drv->tx_buf = buf;
|
|
i2c_drv->tx_buf_len = buf_len;
|
|
|
|
if (buf_len > 0)
|
|
{
|
|
/* more bytes to be written */
|
|
i2c_drv->status |= STATUS_WRITE_IN_PROGRESS;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
i2c_drv->status &= ~STATUS_WRITE_IN_PROGRESS;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If i2c_msg index search is completed, we don't need TX_EMPTY
|
|
* interrupt any more.
|
|
*/
|
|
|
|
if (i2c_drv->msg_write_idx == i2c_drv->msgs_num)
|
|
intr_mask &= ~DW_IC_INTR_TX_EMPTY;
|
|
|
|
if (i2c_drv->msg_err)
|
|
{
|
|
rt_kprintf("ERROR: %s, msg_err: %d\n", __func__, i2c_drv->msg_err);
|
|
intr_mask = 0;
|
|
}
|
|
|
|
I2C_SetInterruptMask(i2c_obj, intr_mask);
|
|
|
|
PRINT_I2C_DBG("%s end\n", __func__);
|
|
}
|
|
|
|
static void i2c_fh_read(struct rt_i2c_bus_device *dev)
|
|
{
|
|
struct i2c_driver *i2c_drv = (struct i2c_driver *)dev->priv;
|
|
struct fh_i2c_obj *i2c_obj = (struct fh_i2c_obj *)i2c_drv->priv;
|
|
struct rt_i2c_msg *msgs = i2c_drv->msgs;
|
|
int rx_valid;
|
|
|
|
PRINT_I2C_DBG("%s start, msgs_num: %d, read_idx: %d\n", __func__, i2c_drv->msgs_num, i2c_drv->msg_read_idx);
|
|
|
|
for (; i2c_drv->msg_read_idx < i2c_drv->msgs_num; i2c_drv->msg_read_idx++)
|
|
{
|
|
rt_uint32_t len;
|
|
rt_uint8_t *buf;
|
|
|
|
if (!(msgs[i2c_drv->msg_read_idx].flags & RT_I2C_RD))
|
|
continue;
|
|
|
|
if (!(i2c_drv->status & STATUS_READ_IN_PROGRESS))
|
|
{
|
|
len = msgs[i2c_drv->msg_read_idx].len;
|
|
buf = msgs[i2c_drv->msg_read_idx].buf;
|
|
}
|
|
else
|
|
{
|
|
PRINT_I2C_DBG("STATUS_READ_IN_PROGRESS\n");
|
|
len = i2c_drv->rx_buf_len;
|
|
buf = i2c_drv->rx_buf;
|
|
}
|
|
|
|
rx_valid = I2C_GetReceiveFifoLevel(i2c_obj);
|
|
|
|
if(rx_valid == 0)
|
|
{
|
|
rt_kprintf("ERROR: %s, rx_valid == 0\n", __func__);
|
|
}
|
|
PRINT_I2C_DBG("%s, len=%d, rx_valid=%d\n", __func__, len, rx_valid);
|
|
for (; len > 0 && rx_valid > 0; len--, rx_valid--)
|
|
{
|
|
*buf++ = I2C_GetData(i2c_obj);
|
|
}
|
|
|
|
PRINT_I2C_DBG("i2c_fh_read, len: %d, buf[0]: 0x%x\n", msgs[i2c_drv->msg_read_idx].len, msgs[i2c_drv->msg_read_idx].buf[0]);
|
|
|
|
if (len > 0)
|
|
{
|
|
PRINT_I2C_DBG("len > 0\n");
|
|
i2c_drv->status |= STATUS_READ_IN_PROGRESS;
|
|
i2c_drv->rx_buf_len = len;
|
|
i2c_drv->rx_buf = buf;
|
|
return;
|
|
}
|
|
else
|
|
i2c_drv->status &= ~STATUS_READ_IN_PROGRESS;
|
|
}
|
|
|
|
PRINT_I2C_DBG("%s end\n", __func__);
|
|
}
|
|
|
|
/*
|
|
* Interrupt service routine. This gets called whenever an I2C interrupt
|
|
* occurs.
|
|
*/
|
|
static void fh_i2c_interrupt(int this_irq, void *dev_id)
|
|
{
|
|
struct i2c_driver *i2c_drv = dev_id;
|
|
struct rt_i2c_bus_device *i2c_bus_dev = i2c_drv->i2c_bus_dev;
|
|
struct fh_i2c_obj *i2c_obj = (struct fh_i2c_obj *)i2c_drv->priv;
|
|
rt_uint32_t stat;
|
|
|
|
stat = I2C_ClearAndGetInterrupts(i2c_obj);
|
|
PRINT_I2C_DBG("status: 0x%x, mask: 0x%x\n", stat, I2C_GetInterruptMask(i2c_obj));
|
|
|
|
if (stat & DW_IC_INTR_TX_ABRT)
|
|
{
|
|
PRINT_I2C_DBG("DW_IC_INTR_TX_ABRT\n");
|
|
i2c_drv->cmd_err |= DW_IC_ERR_TX_ABRT;
|
|
i2c_drv->status = STATUS_IDLE;
|
|
|
|
/*
|
|
* Anytime TX_ABRT is set, the contents of the tx/rx
|
|
* buffers are flushed. Make sure to skip them.
|
|
*/
|
|
I2C_SetInterruptMask(i2c_obj, 0);
|
|
goto tx_aborted;
|
|
}
|
|
|
|
if (stat & DW_IC_INTR_RX_FULL)
|
|
{
|
|
i2c_fh_read(i2c_bus_dev);
|
|
}
|
|
|
|
if (stat & DW_IC_INTR_TX_EMPTY)
|
|
{
|
|
i2c_fh_xfer_msg(i2c_bus_dev);
|
|
}
|
|
|
|
/*
|
|
* No need to modify or disable the interrupt mask here.
|
|
* i2c_fh_xfer_msg() will take care of it according to
|
|
* the current transmit status.
|
|
*/
|
|
|
|
tx_aborted:
|
|
if ((stat & (DW_IC_INTR_TX_ABRT | DW_IC_INTR_STOP_DET)) || i2c_drv->msg_err)
|
|
rt_completion_done(&i2c_drv->transfer_completion);
|
|
|
|
}
|
|
|
|
static const struct rt_i2c_bus_device_ops fh_i2c_ops =
|
|
{
|
|
.master_xfer = fh_i2c_xfer,
|
|
};
|
|
|
|
int fh_i2c_probe(void *priv_data)
|
|
{
|
|
int ret;
|
|
struct i2c_driver *i2c_drv;
|
|
struct rt_i2c_bus_device *i2c_bus_dev;
|
|
struct fh_i2c_obj *i2c_obj = (struct fh_i2c_obj *)priv_data;
|
|
char i2c_dev_name[5] = {0};
|
|
|
|
PRINT_I2C_DBG("%s start\n", __func__);
|
|
|
|
i2c_bus_dev = (struct rt_i2c_bus_device*)rt_calloc(1, sizeof(struct rt_i2c_bus_device));
|
|
if (RT_NULL == i2c_bus_dev)
|
|
{
|
|
return -RT_ENOMEM;
|
|
}
|
|
|
|
i2c_bus_dev->ops = &fh_i2c_ops;
|
|
|
|
rt_sprintf(i2c_dev_name, "%s%d", "i2c", i2c_obj->id);
|
|
ret = rt_i2c_bus_device_register(i2c_bus_dev, i2c_dev_name);
|
|
|
|
if (ret != RT_EOK)
|
|
{
|
|
rt_kprintf("ERROR:rt_spi_bus_register failed, ret=%d\n", ret);
|
|
return -RT_ENOMEM;
|
|
}
|
|
|
|
//priv struct init
|
|
i2c_drv = (struct i2c_driver*)rt_calloc(1, sizeof(struct i2c_driver));
|
|
if (RT_NULL == i2c_drv)
|
|
{
|
|
return -RT_ENOMEM;
|
|
}
|
|
|
|
i2c_drv->i2c_bus_dev = i2c_bus_dev;
|
|
i2c_drv->priv = priv_data;
|
|
i2c_bus_dev->priv = i2c_drv;
|
|
|
|
i2c_drv->lock = rt_mutex_create("i2c_mux", RT_IPC_FLAG_FIFO);
|
|
if(i2c_obj->id == 0){
|
|
rt_hw_interrupt_install(i2c_obj->irq, fh_i2c_interrupt,
|
|
(void *)i2c_drv, "i2c_0");
|
|
}
|
|
else if(i2c_obj->id == 1){
|
|
rt_hw_interrupt_install(i2c_obj->irq, fh_i2c_interrupt,
|
|
(void *)i2c_drv, "i2c_1");
|
|
}
|
|
|
|
rt_hw_interrupt_umask(i2c_obj->irq);
|
|
|
|
//fixme: get from clk tree
|
|
i2c_obj->input_clock = 15000;
|
|
|
|
I2C_Init(i2c_obj);
|
|
|
|
PRINT_I2C_DBG("%s end\n", __func__);
|
|
return ret;
|
|
|
|
}
|
|
|
|
int fh_i2c_exit(void *priv_data)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
struct fh_board_ops i2c_driver_ops =
|
|
{
|
|
.probe = fh_i2c_probe,
|
|
.exit = fh_i2c_exit,
|
|
};
|
|
|
|
void rt_hw_i2c_init(void)
|
|
{
|
|
int ret;
|
|
|
|
PRINT_I2C_DBG("%s start\n", __func__);
|
|
fh_board_driver_register("i2c", &i2c_driver_ops);
|
|
PRINT_I2C_DBG("%s end\n", __func__);
|
|
//fixme: never release?
|
|
}
|
|
|
|
static rt_err_t fh_i2c_read_reg(struct rt_i2c_bus_device *fh81_i2c,
|
|
rt_uint16_t reg, rt_uint8_t *data) {
|
|
struct rt_i2c_msg msg[2];
|
|
rt_uint8_t send_buf[2];
|
|
rt_uint8_t recv_buf[1] = {0};
|
|
|
|
PRINT_I2C_DBG("%s start\n", __func__);
|
|
|
|
// send_buf[0] = ((reg >> 8) & 0xff);
|
|
send_buf[0] = (reg & 0xFF);
|
|
|
|
msg[0].addr = 0x51;
|
|
msg[0].flags = RT_I2C_WR;
|
|
msg[0].len = 1;
|
|
msg[0].buf = send_buf;
|
|
|
|
msg[1].addr = 0x51;
|
|
msg[1].flags = RT_I2C_RD;
|
|
msg[1].len = 1;
|
|
msg[1].buf = recv_buf;
|
|
|
|
rt_i2c_transfer(fh81_i2c, msg, 2);
|
|
*data = recv_buf[0];
|
|
return RT_EOK;
|
|
}
|
|
static rt_err_t fh_i2c_write_reg(struct rt_i2c_bus_device *fh81_i2c,
|
|
rt_uint16_t reg, rt_uint8_t data) {
|
|
struct rt_i2c_msg msg;
|
|
rt_uint8_t send_buf[3];
|
|
|
|
PRINT_I2C_DBG("%s start\n", __func__);
|
|
|
|
// send_buf[0] = ((reg >> 8) & 0xff);
|
|
send_buf[1] = (reg & 0xFF);
|
|
send_buf[2] = data;
|
|
|
|
msg.addr = 0x51;
|
|
msg.flags = RT_I2C_WR;
|
|
msg.len = 2;
|
|
msg.buf = send_buf;
|
|
|
|
rt_i2c_transfer(fh81_i2c, &msg, 1);
|
|
PRINT_I2C_DBG("%s end\n", __func__);
|
|
return RT_EOK;
|
|
}
|
|
|
|
void i2c_test_sensor() {
|
|
struct rt_i2c_bus_device *fh81_i2c;
|
|
struct rt_i2c_msg msg[2];
|
|
rt_uint8_t data[1] = { 0x00 };
|
|
|
|
fh81_i2c = rt_i2c_bus_device_find("i2c1");
|
|
|
|
fh_i2c_write_reg(fh81_i2c, 0x04, 0x02);
|
|
|
|
fh_i2c_read_reg(fh81_i2c, 0x02, data);
|
|
|
|
rt_kprintf("data read from 0x3038 is 0x%x\r\n", data[0]);
|
|
PRINT_I2C_DBG("%s end\n", __func__);
|
|
}
|
|
#ifdef RT_USING_FINSH
|
|
#include <finsh.h>
|
|
FINSH_FUNCTION_EXPORT(i2c_test_sensor, sensor i2c test);
|
|
#endif
|
|
|