509 lines
12 KiB
C
509 lines
12 KiB
C
/*
|
|
* Copyright (c) 2021-2022 Amlogic, Inc. All rights reserved.
|
|
*
|
|
* SPDX-License-Identifier: MIT
|
|
*/
|
|
|
|
#include "common.h"
|
|
#include "uart.h"
|
|
#include "FreeRTOS.h"
|
|
#include <task.h>
|
|
#include "meson_i2c.h"
|
|
#include <i2c_plat.h>
|
|
#include "timer_source.h"
|
|
#include <string.h>
|
|
#include "drv_errno.h"
|
|
#include "suspend.h"
|
|
|
|
#define ERR_I2C(errno) (DRV_ERRNO_I2C_BASE | errno)
|
|
|
|
#define BIT(nr) (1UL << (nr))
|
|
#define GENMASK(h, l) (((~0UL) << (l)) & (~0UL >> (BITS_PER_LONG - 1 - (h))))
|
|
|
|
#define I2C_TIMEOUT_MS (1000 * 100)
|
|
|
|
/* Control register fields */
|
|
#define REG_CTRL_START BIT(0)
|
|
#define REG_CTRL_ACK_IGNORE BIT(1)
|
|
#define REG_CTRL_STATUS BIT(2)
|
|
#define REG_CTRL_ERROR BIT(3)
|
|
#define REG_CTRL_CLKDIV_SHIFT 12
|
|
#define REG_CTRL_CLKDIV_MASK GENMASK(21, 12)
|
|
#define REG_CTRL_CLKDIVEXT_SHIFT 28
|
|
#define REG_CTRL_CLKDIVEXT_MASK GENMASK(29, 28)
|
|
|
|
#define I2C_DEBUG 0
|
|
|
|
#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d))
|
|
#undef MIN
|
|
#define MIN(X, Y)\
|
|
({\
|
|
typeof(X) x_ = (X);\
|
|
typeof(Y) y_ = (Y);\
|
|
(x_ < y_) ? x_ : y_;\
|
|
})
|
|
|
|
enum bool {
|
|
false,
|
|
true
|
|
};
|
|
|
|
enum {
|
|
TOKEN_END = 0,
|
|
TOKEN_START,
|
|
TOKEN_SLAVE_ADDR_WRITE,
|
|
TOKEN_SLAVE_ADDR_READ,
|
|
TOKEN_DATA,
|
|
TOKEN_DATA_LAST,
|
|
TOKEN_STOP,
|
|
};
|
|
|
|
struct xI2cRegs {
|
|
uint32_t ctrl;
|
|
uint32_t slave_addr;
|
|
uint32_t tok_list0;
|
|
uint32_t tok_list1;
|
|
uint32_t tok_wdata0;
|
|
uint32_t tok_wdata1;
|
|
uint32_t tok_rdata0;
|
|
uint32_t tok_rdata1;
|
|
};
|
|
|
|
struct xMesonI2c {
|
|
struct xI2cRegs *regs;
|
|
struct xI2cMsg *msg; /* Current I2C message */
|
|
enum bool last; /* Whether the message is the last */
|
|
uint32_t count; /* Number of bytes in the current transfer */
|
|
uint32_t pos; /* Position of current transfer in message */
|
|
uint32_t tokens[2]; /* Sequence of tokens to be written */
|
|
uint32_t num_tokens; /* Number of tokens to be written */
|
|
uint32_t clock_frequency;
|
|
uint32_t div_factor;
|
|
uint32_t delay_ajust;
|
|
uint32_t clkin_rate;
|
|
};
|
|
|
|
struct xMesonI2c i2cs[8];
|
|
struct xMesonI2cPlatdata *plat;
|
|
|
|
uint32_t current_id;
|
|
|
|
#define DEFAULT_CLK81 0
|
|
|
|
static void prvSetBitsLe32(uint32_t *reg, uint32_t set)
|
|
{
|
|
uint32_t val;
|
|
|
|
val = REG32(reg);
|
|
val |= (set);
|
|
REG32(reg) = val;
|
|
}
|
|
|
|
static void prvClrBitsLe32(uint32_t *reg, uint32_t clr)
|
|
{
|
|
uint32_t val;
|
|
|
|
val = REG32(reg);
|
|
val &= (~(clr));
|
|
REG32(reg) = val;
|
|
}
|
|
|
|
static void prvClrSetBitsLe32(uint32_t *reg, uint32_t clr, uint32_t set)
|
|
{
|
|
uint32_t val;
|
|
|
|
val = REG32(reg);
|
|
val &= (~(clr));
|
|
val |= (set);
|
|
REG32(reg) = val;
|
|
}
|
|
|
|
#if I2C_DEBUG
|
|
void xI2cDumpRegs(void)
|
|
{
|
|
iprintf("i2c reg : 0x%x = 0x%x\n", &i2cs[current_id].regs->ctrl,
|
|
i2cs[current_id].regs->ctrl);
|
|
iprintf("i2c reg : 0x%x = 0x%x\n", &i2cs[current_id].regs->slave_addr,
|
|
i2cs[current_id].regs->slave_addr);
|
|
iprintf("i2c reg : 0x%x = 0x%x\n", &i2cs[current_id].regs->tok_list0,
|
|
i2cs[current_id].regs->tok_list0);
|
|
iprintf("i2c reg : 0x%x = 0x%x\n", &i2cs[current_id].regs->tok_list1,
|
|
i2cs[current_id].regs->tok_list1);
|
|
iprintf("i2c reg : 0x%x = 0x%x\n", &i2cs[current_id].regs->tok_wdata0,
|
|
i2cs[current_id].regs->tok_wdata0);
|
|
iprintf("i2c reg : 0x%x = 0x%x\n", &i2cs[current_id].regs->tok_wdata1,
|
|
i2cs[current_id].regs->tok_wdata1);
|
|
iprintf("i2c reg : 0x%x = 0x%x\n", &i2cs[current_id].regs->tok_rdata0,
|
|
i2cs[current_id].regs->tok_rdata0);
|
|
iprintf("i2c reg : 0x%x = 0x%x\n", &i2cs[current_id].regs->tok_rdata1,
|
|
i2cs[current_id].regs->tok_rdata1);
|
|
}
|
|
#endif
|
|
|
|
static void prvMesonI2cRegsInit(void)
|
|
{
|
|
uint32_t i;
|
|
|
|
for (i = 0; i < 8; i++)
|
|
REG32(&i2cs[current_id].regs->ctrl + i) = 0;
|
|
}
|
|
|
|
static void prvMesonI2cResetTokens(void)
|
|
{
|
|
i2cs[current_id].tokens[0] = 0;
|
|
i2cs[current_id].tokens[1] = 0;
|
|
i2cs[current_id].num_tokens = 0;
|
|
}
|
|
|
|
static void prvMesonI2cAddToken(uint32_t token)
|
|
{
|
|
if (i2cs[current_id].num_tokens < 8)
|
|
i2cs[current_id].tokens[0] |= (token & 0xf) << (i2cs[current_id].num_tokens * 4);
|
|
else
|
|
i2cs[current_id].tokens[1] |= (token & 0xf)
|
|
<< ((i2cs[current_id].num_tokens % 8) * 4);
|
|
|
|
i2cs[current_id].num_tokens++;
|
|
}
|
|
|
|
/*
|
|
* Retrieve data for the current transfer (which can be at most 8
|
|
* bytes) from the device internal buffer.
|
|
*/
|
|
static void prvMesonI2cGetData(uint8_t *buf, uint32_t len)
|
|
{
|
|
uint32_t rdata0, rdata1;
|
|
uint32_t i;
|
|
|
|
rdata0 = i2cs[current_id].regs->tok_rdata0;
|
|
rdata1 = i2cs[current_id].regs->tok_rdata1;
|
|
|
|
#if I2C_DEBUG
|
|
iprintf("meson i2c: read data %08x %08x len %d\n", rdata0, rdata1, len);
|
|
#endif
|
|
|
|
for (i = 0; i < MIN((uint32_t)4, len); i++)
|
|
*buf++ = (rdata0 >> i * 8) & 0xff;
|
|
|
|
for (i = 4; i < MIN((uint32_t)8, len); i++)
|
|
*buf++ = (rdata1 >> (i - 4) * 8) & 0xff;
|
|
}
|
|
|
|
/*
|
|
* Write data for the current transfer (which can be at most 8 bytes)
|
|
* to the device internal buffer.
|
|
*/
|
|
static void prvMesonI2cPutData(uint8_t *buf, uint32_t len)
|
|
{
|
|
uint32_t wdata0 = 0, wdata1 = 0;
|
|
uint32_t i;
|
|
|
|
for (i = 0; i < MIN((uint32_t)4, len); i++)
|
|
wdata0 |= *buf++ << (i * 8);
|
|
|
|
for (i = 4; i < MIN((uint32_t)8, len); i++)
|
|
wdata1 |= *buf++ << ((i - 4) * 8);
|
|
|
|
i2cs[current_id].regs->tok_wdata0 = wdata0;
|
|
i2cs[current_id].regs->tok_wdata1 = wdata1;
|
|
|
|
#if I2C_DEBUG
|
|
iprintf("meson i2c: write data 0x%08x 0x%08x len %d\n", wdata0, wdata1, len);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Prepare the next transfer: pick the next 8 bytes in the remaining
|
|
* part of message and write tokens and data (if needed) to the
|
|
* device.
|
|
*/
|
|
static void prvMesonI2cPrepareXfer(void)
|
|
{
|
|
enum bool write = !(i2cs[current_id].msg->flags & I2C_M_RD);
|
|
uint32_t i;
|
|
|
|
i2cs[current_id].count = MIN(i2cs[current_id].msg->len - i2cs[current_id].pos, 8u);
|
|
|
|
for (i = 0; i + 1 < i2cs[current_id].count; i++)
|
|
prvMesonI2cAddToken(TOKEN_DATA);
|
|
|
|
if (i2cs[current_id].count) {
|
|
if (write ||
|
|
i2cs[current_id].pos + i2cs[current_id].count < i2cs[current_id].msg->len)
|
|
prvMesonI2cAddToken(TOKEN_DATA);
|
|
else
|
|
prvMesonI2cAddToken(TOKEN_DATA_LAST);
|
|
}
|
|
|
|
if (write)
|
|
prvMesonI2cPutData(i2cs[current_id].msg->buf + i2cs[current_id].pos,
|
|
i2cs[current_id].count);
|
|
|
|
if (i2cs[current_id].last &&
|
|
i2cs[current_id].pos + i2cs[current_id].count >= i2cs[current_id].msg->len)
|
|
prvMesonI2cAddToken(TOKEN_STOP);
|
|
|
|
i2cs[current_id].regs->tok_list0 = i2cs[current_id].tokens[0];
|
|
i2cs[current_id].regs->tok_list1 = i2cs[current_id].tokens[1];
|
|
}
|
|
|
|
static void prvMesonI2cDoStart(struct xI2cMsg *msg)
|
|
{
|
|
uint32_t token;
|
|
|
|
token = (msg->flags & I2C_M_RD) ? TOKEN_SLAVE_ADDR_READ : TOKEN_SLAVE_ADDR_WRITE;
|
|
/* change it if duty change */
|
|
i2cs[current_id].regs->slave_addr = msg->addr << 1;
|
|
prvMesonI2cAddToken(TOKEN_START);
|
|
prvMesonI2cAddToken(token);
|
|
}
|
|
|
|
static int32_t prvMesonI2cXferMsg(struct xI2cMsg *msg, uint32_t last)
|
|
{
|
|
uint32_t time_count = 0;
|
|
uint32_t *ctrl = &i2cs[current_id].regs->ctrl;
|
|
|
|
taskENTER_CRITICAL();
|
|
#if I2C_DEBUG
|
|
iprintf("meson i2c: %s addr 0x%x len %u\n", (msg->flags & I2C_M_RD) ? "read" : "write",
|
|
msg->addr, msg->len);
|
|
#endif
|
|
i2cs[current_id].msg = msg;
|
|
i2cs[current_id].last = last;
|
|
i2cs[current_id].pos = 0;
|
|
i2cs[current_id].count = 0;
|
|
|
|
prvMesonI2cResetTokens();
|
|
prvMesonI2cDoStart(msg);
|
|
|
|
do {
|
|
prvMesonI2cPrepareXfer();
|
|
/* start the transfer */
|
|
prvSetBitsLe32(&(i2cs[current_id].regs->ctrl), REG_CTRL_START);
|
|
while (REG32(ctrl) & REG_CTRL_STATUS) {
|
|
if (time_count > I2C_TIMEOUT_MS) {
|
|
prvClrBitsLe32(&i2cs[current_id].regs->ctrl, REG_CTRL_START);
|
|
taskEXIT_CRITICAL();
|
|
return ERR_I2C(DRV_ERROR_TIMEOUT);
|
|
}
|
|
udelay(1);
|
|
time_count++;
|
|
}
|
|
prvMesonI2cResetTokens();
|
|
prvClrBitsLe32(&i2cs[current_id].regs->ctrl, REG_CTRL_START);
|
|
|
|
if (REG32(ctrl) & REG_CTRL_ERROR) {
|
|
taskEXIT_CRITICAL();
|
|
return ERR_I2C(DRV_ERROR_ACCESS);
|
|
}
|
|
|
|
if ((msg->flags & I2C_M_RD) && i2cs[current_id].count) {
|
|
prvMesonI2cGetData(i2cs[current_id].msg->buf + i2cs[current_id].pos,
|
|
i2cs[current_id].count);
|
|
}
|
|
i2cs[current_id].pos += i2cs[current_id].count;
|
|
|
|
} while (i2cs[current_id].pos < msg->len);
|
|
taskEXIT_CRITICAL();
|
|
|
|
return 0;
|
|
}
|
|
|
|
int32_t xI2cMesonXfer(struct xI2cMsg *msg, uint32_t nmsgs)
|
|
{
|
|
uint32_t i;
|
|
int32_t ret;
|
|
|
|
for (i = 0; i < nmsgs; i++) {
|
|
ret = prvMesonI2cXferMsg(msg + i, i == (nmsgs - 1));
|
|
if (ret) {
|
|
iprintf("meson_i2c_xfer error\n");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int32_t xI2cMesonSetBusSpeed(uint32_t speed)
|
|
{
|
|
uint32_t clk_rate = i2cs[current_id].clkin_rate;
|
|
uint32_t div;
|
|
|
|
taskENTER_CRITICAL();
|
|
div = DIV_ROUND_UP(clk_rate, speed * i2cs[current_id].div_factor);
|
|
|
|
/* clock divider has 12 bits */
|
|
if (div >= (1 << 12)) {
|
|
iprintf("meson i2c: requested bus frequency too low\n");
|
|
div = (1 << 12) - 1;
|
|
}
|
|
prvClrSetBitsLe32(&i2cs[current_id].regs->ctrl, REG_CTRL_CLKDIV_MASK,
|
|
(div & GENMASK(9, 0)) << REG_CTRL_CLKDIV_SHIFT);
|
|
|
|
prvClrSetBitsLe32(&i2cs[current_id].regs->ctrl, REG_CTRL_CLKDIVEXT_MASK,
|
|
(div >> 10) << REG_CTRL_CLKDIVEXT_SHIFT);
|
|
taskEXIT_CRITICAL();
|
|
|
|
return 0;
|
|
}
|
|
|
|
int32_t xI2cMesonRead(uint32_t addr, uint8_t offset, uint8_t *buffer, uint32_t len)
|
|
{
|
|
struct xI2cMsg msg[2], *ptr;
|
|
uint32_t msg_count;
|
|
int32_t ret;
|
|
|
|
if (!len)
|
|
return ERR_I2C(DRV_ERROR_PARAMETER);
|
|
|
|
ptr = msg;
|
|
|
|
ptr->addr = addr;
|
|
ptr->flags = 0;
|
|
ptr->len = 1;
|
|
ptr->buf = &offset;
|
|
ptr++;
|
|
|
|
ptr->addr = addr;
|
|
ptr->flags = I2C_M_RD;
|
|
ptr->len = len;
|
|
ptr->buf = buffer;
|
|
ptr++;
|
|
|
|
msg_count = ptr - msg;
|
|
ret = xI2cMesonXfer(msg, msg_count);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int32_t xI2cMesonWrite(uint32_t addr, uint8_t offset, uint8_t *buffer, uint32_t len)
|
|
{
|
|
struct xI2cMsg msg[1];
|
|
int32_t ret = 0;
|
|
uint8_t buf[len + 1];
|
|
|
|
buf[0] = offset;
|
|
|
|
msg->addr = addr;
|
|
msg->len = len + 1; /* addr's length + len */
|
|
msg->buf = buf;
|
|
msg->flags = 0;
|
|
|
|
memcpy(&buf[1], buffer, len);
|
|
ret = xI2cMesonXfer(msg, 1);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int32_t xI2cMesonRead16(uint32_t addr, unsigned int offset, uint8_t *buffer, uint32_t len)
|
|
{
|
|
struct xI2cMsg msg[2], *ptr;
|
|
uint8_t offset_buf[2];
|
|
unsigned int msg_count;
|
|
int ret;
|
|
|
|
if (!len)
|
|
return ERR_I2C(DRV_ERROR_PARAMETER);
|
|
|
|
offset_buf[0] = offset >> 8;
|
|
offset_buf[1] = offset & 0xff;
|
|
ptr = msg;
|
|
|
|
ptr->addr = addr;
|
|
ptr->flags = 0;
|
|
ptr->len = 2;
|
|
ptr->buf = offset_buf;
|
|
ptr++;
|
|
|
|
ptr->addr = addr;
|
|
ptr->flags = I2C_M_RD;
|
|
ptr->len = len;
|
|
ptr->buf = buffer;
|
|
ptr++;
|
|
|
|
msg_count = ptr - msg;
|
|
ret = xI2cMesonXfer(msg, msg_count);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int32_t xI2cMesonWrite16(uint32_t addr, unsigned int offset, uint8_t *buffer, uint32_t len)
|
|
{
|
|
struct xI2cMsg msg[1];
|
|
int ret = 0;
|
|
unsigned char buf[len + 2];
|
|
|
|
buf[0] = offset >> 8;
|
|
buf[1] = offset & 0xff;
|
|
msg->addr = addr;
|
|
msg->len = len + 2;/* addr's length + len */
|
|
msg->buf = buf;
|
|
msg->flags = 0;
|
|
|
|
memcpy(&buf[2], buffer, len);
|
|
ret = xI2cMesonXfer(msg, 1);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
*i2c master platform data init
|
|
*/
|
|
int32_t xI2cMesonPortInit(uint32_t id)
|
|
{
|
|
struct xMesonI2cPlatdata *cur_plat = NULL;
|
|
|
|
/* get the global i2c plat data */
|
|
plat = i2c_plat_data;
|
|
cur_plat = &plat[id];
|
|
|
|
i2cs[id].regs = (struct xI2cRegs *)cur_plat->reg;
|
|
i2cs[id].div_factor = cur_plat->div_factor;
|
|
i2cs[id].delay_ajust = cur_plat->delay_ajust;
|
|
i2cs[id].clock_frequency = cur_plat->clock_frequency;
|
|
|
|
if (cur_plat->clk_base)
|
|
REG32_UPDATE_BITS(cur_plat->clk_base, BIT(cur_plat->clk_offset),
|
|
BIT(cur_plat->clk_offset));
|
|
|
|
if (get_suspend_flag()) {
|
|
#if I2C_DEBUG
|
|
iprintf("meson i2c: clkin is 24M\n");
|
|
#endif
|
|
i2cs[id].clkin_rate = 24000000;
|
|
} else {
|
|
#if I2C_DEBUG
|
|
iprintf("meson i2c: clkin is 166M\n");
|
|
#endif
|
|
i2cs[id].clkin_rate = cur_plat->clkin_rate;
|
|
}
|
|
current_id = id;
|
|
|
|
#if I2C_DEBUG
|
|
iprintf("index = %u, reg = 0x%x, div = %u, delay = %u ,clock-frequency = %u\n",
|
|
cur_plat->bus_num, i2cs[id].regs, i2cs[id].div_factor, i2cs[id].delay_ajust,
|
|
i2cs[id].clock_frequency);
|
|
#endif
|
|
prvMesonI2cRegsInit();
|
|
|
|
xI2cMesonSetBusSpeed(i2cs[id].clock_frequency); /*init i2c work speed */
|
|
|
|
return 0;
|
|
}
|
|
|
|
void xI2cSetCurrentId(uint32_t id)
|
|
{
|
|
taskENTER_CRITICAL();
|
|
current_id = id;
|
|
taskEXIT_CRITICAL();
|
|
}
|